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      *
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      */
processFileBytes(Writer w, String name, final byte[] allbytes)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 
getIndent(int indent)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 
out(String msg, int cur_indent)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 
fixPath(String path)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