| /* |
| * 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; |
| } |
| |
| |
| |
| } |