Brian Muramatsu | 0748ed5 | 2011-11-29 14:43:10 -0800 | [diff] [blame] | 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 | */ |
| 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 | |
| 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 | */ |
| 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 | |
| 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 | |
| 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 | |
| 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 | } |