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