1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dxconvext; 18 19 import com.android.dx.cf.direct.ClassPathOpener; 20 import com.android.dx.cf.direct.DirectClassFile; 21 import com.android.dx.cf.direct.StdAttributeFactory; 22 import com.android.dx.cf.iface.Member; 23 import com.android.dx.cf.iface.ParseObserver; 24 import com.android.dx.util.ByteArray; 25 import com.android.dx.util.FileUtils; 26 27 import java.io.BufferedWriter; 28 import java.io.File; 29 import java.io.FileNotFoundException; 30 import java.io.FileOutputStream; 31 import java.io.IOException; 32 import java.io.OutputStreamWriter; 33 import java.io.Writer; 34 35 public class ClassFileParser { 36 37 private BufferedWriter bw; // the writer to write the result to. 38 39 /** 40 * Parses a .class file and outputs a .cfh (class file in hex format) file. 41 * 42 * args[0] is the absolute path to the java src directory e.g. 43 * /home/fjost/android/workspace/dxconverter/src 44 * 45 * args[1] is the absolute path to the classes directory e.g. 46 * /home/fjost/android/workspace/out/classes_javac this is the place where 47 * 48 * args[2] is the absolute path to the java source file, e.g. 49 * /home/fjost/android/workspace/dxconverter/src/test/MyTest.java 50 * 51 * 52 * 53 * @param args 54 */ 55 public static void main(String[] args) throws IOException { 56 ClassFileParser cfp = new ClassFileParser(); 57 cfp.process(args[0], args[1], args[2]); 58 } 59 60 private void process(final String srcDir, final String classesDir, 61 final String absSrcFilePath) throws IOException { 62 ClassPathOpener opener; 63 64 String fileName = absSrcFilePath; 65 // e.g. test/p1/MyTest.java 66 String pckPath = fileName.substring(srcDir.length() + 1); 67 // e.g. test/p1 68 String pck = pckPath.substring(0, pckPath.lastIndexOf("/")); 69 // e.g. MyTest 70 String cName = pckPath.substring(pck.length() + 1); 71 cName = cName.substring(0, cName.lastIndexOf(".")); 72 String cfName = pck+"/"+cName+".class"; 73 // 2. calculate the target file name: 74 // e.g. <out-path>/test/p1/MyTest.class 75 String inFile = classesDir + "/" + pck + "/" + cName + ".class"; 76 if (!new File(inFile).exists()) { 77 throw new RuntimeException("cannot read:" + inFile); 78 } 79 byte[] bytes = FileUtils.readFile(inFile); 80 // write the outfile to the same directory as the corresponding .java 81 // file 82 String outFile = absSrcFilePath.substring(0, absSrcFilePath 83 .lastIndexOf("/"))+ "/" + cName + ".cfh"; 84 Writer w; 85 try { 86 w = new OutputStreamWriter(new FileOutputStream(new File(outFile))); 87 } catch (FileNotFoundException e) { 88 throw new RuntimeException("cannot write to file:"+outFile, e); 89 } 90 // Writer w = new OutputStreamWriter(System.out); 91 ClassFileParser.this.processFileBytes(w, cfName, bytes); 92 93 } 94 95 /** 96 * 97 * @param w the writer to write the generated .cfh file to 98 * @param name the relative name of the java src file, e.g. 99 * dxc/util/Util.java 100 * @param allbytes the bytes of this java src file 101 * @return true if everthing went alright 102 */ 103 void processFileBytes(Writer w, String name, final byte[] allbytes) throws IOException { 104 String fixedPathName = fixPath(name); 105 DirectClassFile cf = new DirectClassFile(allbytes, fixedPathName, true); 106 bw = new BufferedWriter(w); 107 String className = fixedPathName.substring(0, fixedPathName.lastIndexOf(".")); 108 out("//@class:" + className, 0); 109 cf.setObserver(new ParseObserver() { 110 private int cur_indent = 0; 111 private int checkpos = 0; 112 113 /** 114 * Indicate that the level of indentation for a dump should increase 115 * or decrease (positive or negative argument, respectively). 116 * 117 * @param indentDelta the amount to change indentation 118 */ 119 public void changeIndent(int indentDelta) { 120 cur_indent += indentDelta; 121 } 122 123 /** 124 * Indicate that a particular member is now being parsed. 125 * 126 * @param bytes non-null; the source that is being parsed 127 * @param offset offset into <code>bytes</code> for the start of 128 * the member 129 * @param name non-null; name of the member 130 * @param descriptor non-null; descriptor of the member 131 */ 132 public void startParsingMember(ByteArray bytes, int offset, 133 String name, String descriptor) { 134 // ByteArray ba = bytes.slice(offset, bytes.size()); 135 out("// ========== start-ParseMember:" + name + ", offset " 136 + offset + ", len:" + (bytes.size() - offset) 137 + ",desc: " + descriptor); 138 // out("// "+dumpReadableString(ba)); 139 // out(" "+dumpBytes(ba)); 140 } 141 142 /** 143 * Indicate that a particular member is no longer being parsed. 144 * 145 * @param bytes non-null; the source that was parsed 146 * @param offset offset into <code>bytes</code> for the end of the 147 * member 148 * @param name non-null; name of the member 149 * @param descriptor non-null; descriptor of the member 150 * @param member non-null; the actual member that was parsed 151 */ 152 public void endParsingMember(ByteArray bytes, int offset, 153 String name, String descriptor, Member member) { 154 ByteArray ba = bytes.slice(offset, bytes.size()); 155 out("// ========== end-ParseMember:" + name + ", desc: " 156 + descriptor); 157 // out("// "+dumpReadableString(ba)); 158 // out(" "+dumpBytes(ba)); 159 } 160 161 /** 162 * Indicate that some parsing happened. 163 * 164 * @param bytes non-null; the source that was parsed 165 * @param offset offset into <code>bytes</code> for what was 166 * parsed 167 * @param len number of bytes parsed 168 * @param human non-null; human form for what was parsed 169 */ 170 public void parsed(ByteArray bytes, int offset, int len, 171 String human) { 172 human = human.replace('\n', ' '); 173 out("// parsed:" + ", offset " + offset + ", len " + len 174 + ", h: " + human); 175 if (len > 0) { 176 ByteArray ba = bytes.slice(offset, offset + len); 177 check(ba); 178 out("// " + dumpReadableString(ba)); 179 out(" " + dumpBytes(ba)); 180 } 181 } 182 183 private void out(String msg) { 184 ClassFileParser.this.out(msg, cur_indent); 185 186 } 187 188 private void check(ByteArray ba) { 189 int len = ba.size(); 190 int offset = checkpos; 191 for (int i = 0; i < len; i++) { 192 int b = ba.getByte(i); 193 byte b2 = allbytes[i + offset]; 194 if (b != b2) 195 throw new RuntimeException("byte dump mismatch at pos " 196 + (i + offset)); 197 } 198 checkpos += len; 199 } 200 201 202 203 private String dumpBytes(ByteArray ba) { 204 String s = ""; 205 for (int i = 0; i < ba.size(); i++) { 206 int byt = ba.getUnsignedByte(i); 207 String hexVal = Integer.toHexString(byt); 208 if (hexVal.length() == 1) { 209 hexVal = "0" + hexVal; 210 } 211 s += hexVal + " "; 212 } 213 return s; 214 } 215 216 private String dumpReadableString(ByteArray ba) { 217 String s = ""; 218 for (int i = 0; i < ba.size(); i++) { 219 int bb = ba.getUnsignedByte(i); 220 if (bb > 31 && bb < 127) { 221 s += (char) bb; 222 } else { 223 s += "."; 224 } 225 s += " "; 226 } 227 return s; 228 } 229 230 231 }); 232 cf.setAttributeFactory(StdAttributeFactory.THE_ONE); 233 // what is needed to force parsing to the end? 234 cf.getMagic(); 235 // cf.getFields(); 236 // cf.getAttributes(); 237 // cf.getMethods(); 238 bw.close(); 239 } 240 241 242 private String getIndent(int indent) { 243 StringBuilder sb = new StringBuilder(); 244 for (int i = 0; i < indent * 4; i++) { 245 sb.append(' '); 246 } 247 return sb.toString(); 248 } 249 250 private void out(String msg, int cur_indent) { 251 try { 252 bw.write(getIndent(cur_indent) + msg); 253 bw.newLine(); 254 } catch (IOException ioe) { 255 throw new RuntimeException("error while writing to the writer", ioe); 256 } 257 } 258 259 private static String fixPath(String path) { 260 /* 261 * If the path separator is \ (like on windows), we convert the path to 262 * a standard '/' separated path. 263 */ 264 if (File.separatorChar == '\\') { 265 path = path.replace('\\', '/'); 266 } 267 268 int index = path.lastIndexOf("/./"); 269 270 if (index != -1) { 271 return path.substring(index + 3); 272 } 273 274 if (path.startsWith("./")) { 275 return path.substring(2); 276 } 277 278 return path; 279 } 280 281 282 283 } 284