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