| /* | |
| * Copyright (C) 2008 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| package dxconvext; | |
| import com.android.dx.cf.direct.ClassPathOpener; | |
| import com.android.dx.cf.direct.DirectClassFile; | |
| import com.android.dx.cf.direct.StdAttributeFactory; | |
| import com.android.dx.cf.iface.Member; | |
| import com.android.dx.cf.iface.ParseObserver; | |
| import com.android.dx.util.ByteArray; | |
| import com.android.dx.util.FileUtils; | |
| import java.io.BufferedWriter; | |
| import java.io.File; | |
| import java.io.FileNotFoundException; | |
| import java.io.FileOutputStream; | |
| import java.io.IOException; | |
| import java.io.OutputStreamWriter; | |
| import java.io.Writer; | |
| public class ClassFileParser { | |
| private BufferedWriter bw; // the writer to write the result to. | |
| /** | |
| * Parses a .class file and outputs a .cfh (class file in hex format) file. | |
| * | |
| * args[0] is the absolute path to the java src directory e.g. | |
| * /home/fjost/android/workspace/dxconverter/src | |
| * | |
| * args[1] is the absolute path to the classes directory e.g. | |
| * /home/fjost/android/workspace/out/classes_javac this is the place where | |
| * | |
| * args[2] is the absolute path to the java source file, e.g. | |
| * /home/fjost/android/workspace/dxconverter/src/test/MyTest.java | |
| * | |
| * | |
| * | |
| * @param args | |
| */ | |
| public static void main(String[] args) throws IOException { | |
| ClassFileParser cfp = new ClassFileParser(); | |
| cfp.process(args[0], args[1], args[2]); | |
| } | |
| private void process(final String srcDir, final String classesDir, | |
| final String absSrcFilePath) throws IOException { | |
| ClassPathOpener opener; | |
| String fileName = absSrcFilePath; | |
| // e.g. test/p1/MyTest.java | |
| String pckPath = fileName.substring(srcDir.length() + 1); | |
| // e.g. test/p1 | |
| String pck = pckPath.substring(0, pckPath.lastIndexOf("/")); | |
| // e.g. MyTest | |
| String cName = pckPath.substring(pck.length() + 1); | |
| cName = cName.substring(0, cName.lastIndexOf(".")); | |
| String cfName = pck+"/"+cName+".class"; | |
| // 2. calculate the target file name: | |
| // e.g. <out-path>/test/p1/MyTest.class | |
| String inFile = classesDir + "/" + pck + "/" + cName + ".class"; | |
| if (!new File(inFile).exists()) { | |
| throw new RuntimeException("cannot read:" + inFile); | |
| } | |
| byte[] bytes = FileUtils.readFile(inFile); | |
| // write the outfile to the same directory as the corresponding .java | |
| // file | |
| String outFile = absSrcFilePath.substring(0, absSrcFilePath | |
| .lastIndexOf("/"))+ "/" + cName + ".cfh"; | |
| Writer w; | |
| try { | |
| w = new OutputStreamWriter(new FileOutputStream(new File(outFile))); | |
| } catch (FileNotFoundException e) { | |
| throw new RuntimeException("cannot write to file:"+outFile, e); | |
| } | |
| // Writer w = new OutputStreamWriter(System.out); | |
| ClassFileParser.this.processFileBytes(w, cfName, bytes); | |
| } | |
| /** | |
| * | |
| * @param w the writer to write the generated .cfh file to | |
| * @param name the relative name of the java src file, e.g. | |
| * dxc/util/Util.java | |
| * @param allbytes the bytes of this java src file | |
| * @return true if everthing went alright | |
| */ | |
| void processFileBytes(Writer w, String name, final byte[] allbytes) throws IOException { | |
| String fixedPathName = fixPath(name); | |
| DirectClassFile cf = new DirectClassFile(allbytes, fixedPathName, true); | |
| bw = new BufferedWriter(w); | |
| String className = fixedPathName.substring(0, fixedPathName.lastIndexOf(".")); | |
| out("//@class:" + className, 0); | |
| cf.setObserver(new ParseObserver() { | |
| private int cur_indent = 0; | |
| private int checkpos = 0; | |
| /** | |
| * Indicate that the level of indentation for a dump should increase | |
| * or decrease (positive or negative argument, respectively). | |
| * | |
| * @param indentDelta the amount to change indentation | |
| */ | |
| public void changeIndent(int indentDelta) { | |
| cur_indent += indentDelta; | |
| } | |
| /** | |
| * Indicate that a particular member is now being parsed. | |
| * | |
| * @param bytes non-null; the source that is being parsed | |
| * @param offset offset into <code>bytes</code> for the start of | |
| * the member | |
| * @param name non-null; name of the member | |
| * @param descriptor non-null; descriptor of the member | |
| */ | |
| public void startParsingMember(ByteArray bytes, int offset, | |
| String name, String descriptor) { | |
| // ByteArray ba = bytes.slice(offset, bytes.size()); | |
| out("// ========== start-ParseMember:" + name + ", offset " | |
| + offset + ", len:" + (bytes.size() - offset) | |
| + ",desc: " + descriptor); | |
| // out("// "+dumpReadableString(ba)); | |
| // out(" "+dumpBytes(ba)); | |
| } | |
| /** | |
| * Indicate that a particular member is no longer being parsed. | |
| * | |
| * @param bytes non-null; the source that was parsed | |
| * @param offset offset into <code>bytes</code> for the end of the | |
| * member | |
| * @param name non-null; name of the member | |
| * @param descriptor non-null; descriptor of the member | |
| * @param member non-null; the actual member that was parsed | |
| */ | |
| public void endParsingMember(ByteArray bytes, int offset, | |
| String name, String descriptor, Member member) { | |
| ByteArray ba = bytes.slice(offset, bytes.size()); | |
| out("// ========== end-ParseMember:" + name + ", desc: " | |
| + descriptor); | |
| // out("// "+dumpReadableString(ba)); | |
| // out(" "+dumpBytes(ba)); | |
| } | |
| /** | |
| * Indicate that some parsing happened. | |
| * | |
| * @param bytes non-null; the source that was parsed | |
| * @param offset offset into <code>bytes</code> for what was | |
| * parsed | |
| * @param len number of bytes parsed | |
| * @param human non-null; human form for what was parsed | |
| */ | |
| public void parsed(ByteArray bytes, int offset, int len, | |
| String human) { | |
| human = human.replace('\n', ' '); | |
| out("// parsed:" + ", offset " + offset + ", len " + len | |
| + ", h: " + human); | |
| if (len > 0) { | |
| ByteArray ba = bytes.slice(offset, offset + len); | |
| check(ba); | |
| out("// " + dumpReadableString(ba)); | |
| out(" " + dumpBytes(ba)); | |
| } | |
| } | |
| private void out(String msg) { | |
| ClassFileParser.this.out(msg, cur_indent); | |
| } | |
| private void check(ByteArray ba) { | |
| int len = ba.size(); | |
| int offset = checkpos; | |
| for (int i = 0; i < len; i++) { | |
| int b = ba.getByte(i); | |
| byte b2 = allbytes[i + offset]; | |
| if (b != b2) | |
| throw new RuntimeException("byte dump mismatch at pos " | |
| + (i + offset)); | |
| } | |
| checkpos += len; | |
| } | |
| private String dumpBytes(ByteArray ba) { | |
| String s = ""; | |
| for (int i = 0; i < ba.size(); i++) { | |
| int byt = ba.getUnsignedByte(i); | |
| String hexVal = Integer.toHexString(byt); | |
| if (hexVal.length() == 1) { | |
| hexVal = "0" + hexVal; | |
| } | |
| s += hexVal + " "; | |
| } | |
| return s; | |
| } | |
| private String dumpReadableString(ByteArray ba) { | |
| String s = ""; | |
| for (int i = 0; i < ba.size(); i++) { | |
| int bb = ba.getUnsignedByte(i); | |
| if (bb > 31 && bb < 127) { | |
| s += (char) bb; | |
| } else { | |
| s += "."; | |
| } | |
| s += " "; | |
| } | |
| return s; | |
| } | |
| }); | |
| cf.setAttributeFactory(StdAttributeFactory.THE_ONE); | |
| // what is needed to force parsing to the end? | |
| cf.getMagic(); | |
| // cf.getFields(); | |
| // cf.getAttributes(); | |
| // cf.getMethods(); | |
| bw.close(); | |
| } | |
| private String getIndent(int indent) { | |
| StringBuilder sb = new StringBuilder(); | |
| for (int i = 0; i < indent * 4; i++) { | |
| sb.append(' '); | |
| } | |
| return sb.toString(); | |
| } | |
| private void out(String msg, int cur_indent) { | |
| try { | |
| bw.write(getIndent(cur_indent) + msg); | |
| bw.newLine(); | |
| } catch (IOException ioe) { | |
| throw new RuntimeException("error while writing to the writer", ioe); | |
| } | |
| } | |
| private static String fixPath(String path) { | |
| /* | |
| * If the path separator is \ (like on windows), we convert the path to | |
| * a standard '/' separated path. | |
| */ | |
| if (File.separatorChar == '\\') { | |
| path = path.replace('\\', '/'); | |
| } | |
| int index = path.lastIndexOf("/./"); | |
| if (index != -1) { | |
| return path.substring(index + 3); | |
| } | |
| if (path.startsWith("./")) { | |
| return path.substring(2); | |
| } | |
| return path; | |
| } | |
| } |