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 */ main(String[] args)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 process(final String srcDir, final String classesDir, final String absSrcFilePath)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 * @param w the writer to write the generated .cfh file to 97 * @param name the relative name of the java src file, e.g. 98 * dxc/util/Util.java 99 * @param allbytes the bytes of this java src file 100 * @return true if everything went alright 101 */ processFileBytes(Writer w, String name, final byte[] allbytes)102 void processFileBytes(Writer w, String name, final byte[] allbytes) throws IOException { 103 String fixedPathName = fixPath(name); 104 DirectClassFile cf = new DirectClassFile(allbytes, fixedPathName, true); 105 bw = new BufferedWriter(w); 106 String className = fixedPathName.substring(0, fixedPathName.lastIndexOf(".")); 107 out("//@class:" + className, 0); 108 cf.setObserver(new ParseObserver() { 109 private int cur_indent = 0; 110 private int checkpos = 0; 111 112 /** 113 * Indicate that the level of indentation for a dump should increase 114 * or decrease (positive or negative argument, respectively). 115 * 116 * @param indentDelta the amount to change indentation 117 */ 118 public void changeIndent(int indentDelta) { 119 cur_indent += indentDelta; 120 } 121 122 /** 123 * Indicate that a particular member is now being parsed. 124 * 125 * @param bytes non-null; the source that is being parsed 126 * @param offset offset into <code>bytes</code> for the start of 127 * the member 128 * @param name non-null; name of the member 129 * @param descriptor non-null; descriptor of the member 130 */ 131 public void startParsingMember(ByteArray bytes, int offset, 132 String name, String descriptor) { 133 // ByteArray ba = bytes.slice(offset, bytes.size()); 134 out("// ========== start-ParseMember:" + name + ", offset " 135 + offset + ", len:" + (bytes.size() - offset) 136 + ",desc: " + descriptor); 137 // out("// "+dumpReadableString(ba)); 138 // out(" "+dumpBytes(ba)); 139 } 140 141 /** 142 * Indicate that a particular member is no longer being parsed. 143 * 144 * @param bytes non-null; the source that was parsed 145 * @param offset offset into <code>bytes</code> for the end of the 146 * member 147 * @param name non-null; name of the member 148 * @param descriptor non-null; descriptor of the member 149 * @param member non-null; the actual member that was parsed 150 */ 151 public void endParsingMember(ByteArray bytes, int offset, 152 String name, String descriptor, Member member) { 153 ByteArray ba = bytes.slice(offset, bytes.size()); 154 out("// ========== end-ParseMember:" + name + ", desc: " 155 + descriptor); 156 // out("// "+dumpReadableString(ba)); 157 // out(" "+dumpBytes(ba)); 158 } 159 160 /** 161 * Indicate that some parsing happened. 162 * 163 * @param bytes non-null; the source that was parsed 164 * @param offset offset into <code>bytes</code> for what was 165 * parsed 166 * @param len number of bytes parsed 167 * @param human non-null; human form for what was parsed 168 */ 169 public void parsed(ByteArray bytes, int offset, int len, 170 String human) { 171 human = human.replace('\n', ' '); 172 out("// parsed:" + ", offset " + offset + ", len " + len 173 + ", h: " + human); 174 if (len > 0) { 175 ByteArray ba = bytes.slice(offset, offset + len); 176 check(ba); 177 out("// " + dumpReadableString(ba)); 178 out(" " + dumpBytes(ba)); 179 } 180 } 181 182 private void out(String msg) { 183 ClassFileParser.this.out(msg, cur_indent); 184 185 } 186 187 private void check(ByteArray ba) { 188 int len = ba.size(); 189 int offset = checkpos; 190 for (int i = 0; i < len; i++) { 191 int b = ba.getByte(i); 192 byte b2 = allbytes[i + offset]; 193 if (b != b2) 194 throw new RuntimeException("byte dump mismatch at pos " 195 + (i + offset)); 196 } 197 checkpos += len; 198 } 199 200 private String dumpBytes(ByteArray ba) { 201 String s = ""; 202 for (int i = 0; i < ba.size(); i++) { 203 int byt = ba.getUnsignedByte(i); 204 String hexVal = Integer.toHexString(byt); 205 if (hexVal.length() == 1) { 206 hexVal = "0" + hexVal; 207 } 208 s += hexVal + " "; 209 } 210 return s; 211 } 212 213 private String dumpReadableString(ByteArray ba) { 214 String s = ""; 215 for (int i = 0; i < ba.size(); i++) { 216 int bb = ba.getUnsignedByte(i); 217 if (bb > 31 && bb < 127) { 218 s += (char) bb; 219 } else { 220 s += "."; 221 } 222 s += " "; 223 } 224 return s; 225 } 226 }); 227 cf.setAttributeFactory(StdAttributeFactory.THE_ONE); 228 // what is needed to force parsing to the end? 229 cf.getMagic(); 230 // cf.getFields(); 231 // cf.getAttributes(); 232 // cf.getMethods(); 233 bw.close(); 234 } 235 getIndent(int indent)236 private String getIndent(int indent) { 237 StringBuilder sb = new StringBuilder(); 238 for (int i = 0; i < indent * 4; i++) { 239 sb.append(' '); 240 } 241 return sb.toString(); 242 } 243 out(String msg, int cur_indent)244 private void out(String msg, int cur_indent) { 245 try { 246 bw.write(getIndent(cur_indent) + msg); 247 bw.newLine(); 248 } catch (IOException ioe) { 249 throw new RuntimeException("error while writing to the writer", ioe); 250 } 251 } 252 fixPath(String path)253 private static String fixPath(String path) { 254 /* 255 * If the path separator is \ (like on windows), we convert the path to 256 * a standard '/' separated path. 257 */ 258 if (File.separatorChar == '\\') { 259 path = path.replace('\\', '/'); 260 } 261 262 int index = path.lastIndexOf("/./"); 263 264 if (index != -1) { 265 return path.substring(index + 3); 266 } 267 268 if (path.startsWith("./")) { 269 return path.substring(2); 270 } 271 272 return path; 273 } 274 } 275