blob: 037de37e540aac65ed9265b5630f6cfc40fd63f1 [file] [log] [blame]
/*
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
import java.util.*;
import java.lang.reflect.*;
import java.io.*;
import xmlkit.XMLKit.Element;
/*
* @author jrose
*/
public class ClassWriter extends ClassSyntax implements ClassSyntax.GetCPIndex {
private static final CommandLineParser CLP = new CommandLineParser(""
+ "-source: +> = \n"
+ "-dest: +> = \n"
+ "-encoding: +> = \n"
+ "-parseBytes $ \n"
+ "- *? \n"
+ "\n");
public static void main(String[] ava) throws IOException {
ArrayList<String> av = new ArrayList<String>(Arrays.asList(ava));
HashMap<String, String> props = new HashMap<String, String>();
props.put("-encoding:", "UTF8"); // default
CLP.parse(av, props);
File source = asFile(props.get("-source:"));
File dest = asFile(props.get("-dest:"));
String encoding = props.get("-encoding:");
boolean parseBytes = props.containsKey("-parseBytes");
boolean destMade = false;
for (String a : av) {
File f;
File inf = new File(source, a);
System.out.println("Reading " + inf);
Element e;
if (inf.getName().endsWith(".class")) {
ClassReader cr = new ClassReader();
cr.parseBytes = parseBytes;
e = cr.readFrom(inf);
f = new File(a);
} else if (inf.getName().endsWith(".xml")) {
InputStream in = new FileInputStream(inf);
Reader inw = ClassReader.makeReader(in, encoding);
e = XMLKit.readFrom(inw);
e.findAllInTree(XMLKit.and(XMLKit.elementFilter(nonAttrTags()),
XMLKit.methodFilter(Element.method("trimText"))));
//System.out.println(e);
inw.close();
f = new File(a.substring(0, a.length() - ".xml".length()) + ".class");
} else {
System.out.println("Warning: unknown input " + a);
continue;
}
// Now write it:
if (!destMade) {
destMade = true;
if (dest == null) {
dest = File.createTempFile("TestOut", ".dir", new File("."));
dest.delete();
System.out.println("Writing results to " + dest);
}
if (!(dest.isDirectory() || dest.mkdir())) {
throw new RuntimeException("Cannot create " + dest);
}
}
File outf = new File(dest, f.isAbsolute() ? f.getName() : f.getPath());
outf.getParentFile().mkdirs();
new ClassWriter(e).writeTo(outf);
}
}
private static File asFile(String str) {
return (str == null) ? null : new File(str);
}
public void writeTo(File file) throws IOException {
OutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
writeTo(out);
} finally {
if (out != null) {
out.close();
}
}
}
protected String[] callables; // varies
protected int cpoolSize = 0;
protected HashMap<String, String> attrTypesByTag;
protected OutputStream out;
protected HashMap<String, int[]> cpMap = new HashMap<String, int[]>();
protected ArrayList<ByteArrayOutputStream> attrBufs = new ArrayList<ByteArrayOutputStream>();
private void setupAttrTypes() {
attrTypesByTag = new HashMap<String, String>();
for (String key : attrTypes.keySet()) {
String pfx = key.substring(0, key.indexOf('.') + 1);
String val = attrTypes.get(key);
int pos = val.indexOf('<');
if (pos >= 0) {
String tag = val.substring(pos + 1, val.indexOf('>', pos));
attrTypesByTag.put(pfx + tag, key);
}
}
//System.out.println("attrTypesByTag: "+attrTypesByTag);
}
protected ByteArrayOutputStream getAttrBuf() {
int nab = attrBufs.size();
if (nab == 0) {
return new ByteArrayOutputStream(1024);
}
ByteArrayOutputStream ab = attrBufs.get(nab - 1);
attrBufs.remove(nab - 1);
return ab;
}
protected void putAttrBuf(ByteArrayOutputStream ab) {
ab.reset();
attrBufs.add(ab);
}
public ClassWriter(Element root) {
this(root, null);
}
public ClassWriter(Element root, ClassSyntax cr) {
if (cr != null) {
attrTypes = cr.attrTypes;
}
setupAttrTypes();
if (root.getName() == "ClassFile") {
cfile = root;
cpool = root.findElement("ConstantPool");
klass = root.findElement("Class");
} else if (root.getName() == "Class") {
cfile = new Element("ClassFile",
new String[]{
"magic", String.valueOf(0xCAFEBABE),
"minver", "0", "majver", "46",});
cpool = new Element("ConstantPool");
klass = root;
} else {
throw new IllegalArgumentException("bad element type " + root.getName());
}
if (cpool == null) {
cpool = new Element("ConstantPool");
}
int cpLen = 1 + cpool.size();
for (Element c : cpool.elements()) {
int id = (int) c.getAttrLong("id");
int tag = cpTagValue(c.getName());
setCPIndex(tag, c.getText().toString(), id);
switch (tag) {
case CONSTANT_Long:
case CONSTANT_Double:
cpLen += 1;
}
}
cpoolSize = cpLen;
}
public int findCPIndex(int tag, String name) {
if (name == null) {
return 0;
}
int[] ids = cpMap.get(name.toString());
return (ids == null) ? 0 : ids[tag];
}
public int getCPIndex(int tag, String name) {
//System.out.println("getCPIndex "+cpTagName(tag)+" "+name);
if (name == null) {
return 0;
}
int id = findCPIndex(tag, name);
if (id == 0) {
id = cpoolSize;
cpoolSize += 1;
setCPIndex(tag, name, id);
cpool.add(new Element(cpTagName(tag),
new String[]{"id", "" + id},
new Object[]{name}));
int pos;
switch (tag) {
case CONSTANT_Long:
case CONSTANT_Double:
cpoolSize += 1;
break;
case CONSTANT_Class:
case CONSTANT_String:
getCPIndex(CONSTANT_Utf8, name);
break;
case CONSTANT_Fieldref:
case CONSTANT_Methodref:
case CONSTANT_InterfaceMethodref:
pos = name.indexOf(' ');
getCPIndex(CONSTANT_Class, name.substring(0, pos));
getCPIndex(CONSTANT_NameAndType, name.substring(pos + 1));
break;
case CONSTANT_NameAndType:
pos = name.indexOf(' ');
getCPIndex(CONSTANT_Utf8, name.substring(0, pos));
getCPIndex(CONSTANT_Utf8, name.substring(pos + 1));
break;
}
}
return id;
}
public void setCPIndex(int tag, String name, int id) {
//System.out.println("setCPIndex id="+id+" tag="+tag+" name="+name);
int[] ids = cpMap.get(name);
if (ids == null) {
cpMap.put(name, ids = new int[13]);
}
if (ids[tag] != 0 && ids[tag] != id) {
System.out.println("Warning: Duplicate CP entries for " + ids[tag] + " and " + id);
}
//assert(ids[tag] == 0 || ids[tag] == id);
ids[tag] = id;
}
public int parseFlags(String flagString) {
int flags = 0;
int i = -1;
for (String[] names : modifierNames) {
++i;
for (String name : names) {
if (name == null) {
continue;
}
int pos = flagString.indexOf(name);
if (pos >= 0) {
flags |= (1 << i);
}
}
}
return flags;
}
public void writeTo(OutputStream realOut) throws IOException {
OutputStream headOut = realOut;
ByteArrayOutputStream tailOut = new ByteArrayOutputStream();
// write the body of the class file first
this.out = tailOut;
writeClass();
// write the file header last
this.out = headOut;
u4((int) cfile.getAttrLong("magic"));
u2((int) cfile.getAttrLong("minver"));
u2((int) cfile.getAttrLong("majver"));
writeCP();
// recopy the file tail
this.out = null;
tailOut.writeTo(realOut);
}
void writeClass() throws IOException {
int flags = parseFlags(klass.getAttr("flags"));
flags ^= Modifier.SYNCHRONIZED;
u2(flags);
cpRef(CONSTANT_Class, klass.getAttr("name"));
cpRef(CONSTANT_Class, klass.getAttr("super"));
Element interfaces = klass.findAllElements("Interface");
u2(interfaces.size());
for (Element e : interfaces.elements()) {
cpRef(CONSTANT_Class, e.getAttr("name"));
}
for (int isMethod = 0; isMethod <= 1; isMethod++) {
Element members = klass.findAllElements(isMethod != 0 ? "Method" : "Field");
u2(members.size());
for (Element m : members.elements()) {
writeMember(m, isMethod != 0);
}
}
writeAttributesFor(klass);
}
private void writeMember(Element member, boolean isMethod) throws IOException {
//System.out.println("writeMember "+member);
u2(parseFlags(member.getAttr("flags")));
cpRef(CONSTANT_Utf8, member.getAttr("name"));
cpRef(CONSTANT_Utf8, member.getAttr("type"));
writeAttributesFor(member);
}
protected void writeAttributesFor(Element x) throws IOException {
LinkedHashSet<String> attrNames = new LinkedHashSet<String>();
for (Element e : x.elements()) {
attrNames.add(e.getName()); // uniquifying
}
attrNames.removeAll(nonAttrTags());
u2(attrNames.size());
if (attrNames.isEmpty()) {
return;
}
Element prevCurrent;
if (x.getName() == "Code") {
prevCurrent = currentCode;
currentCode = x;
} else {
prevCurrent = currentMember;
currentMember = x;
}
OutputStream realOut = this.out;
for (String utag : attrNames) {
String qtag = x.getName() + "." + utag;
String wtag = "*." + utag;
String key = attrTypesByTag.get(qtag);
if (key == null) {
key = attrTypesByTag.get(wtag);
}
String type = attrTypes.get(key);
//System.out.println("tag "+qtag+" => key "+key+"; type "+type);
Element attrs = x.findAllElements(utag);
ByteArrayOutputStream attrBuf = getAttrBuf();
if (type == null) {
if (attrs.size() != 1 || !attrs.get(0).equals(new Element(utag))) {
System.out.println("Warning: No attribute type description: " + qtag);
}
key = wtag;
} else {
try {
this.out = attrBuf;
// unparse according to type desc.
if (type.equals("<Code>...")) {
writeCode((Element) attrs.get(0)); // assume only 1
} else if (type.equals("<Frame>...")) {
writeStackMap(attrs, false);
} else if (type.equals("<FrameX>...")) {
writeStackMap(attrs, true);
} else if (type.startsWith("[")) {
writeAttributeRecursive(attrs, type);
} else {
writeAttribute(attrs, type);
}
} finally {
//System.out.println("Attr Bytes = \""+attrBuf.toString(EIGHT_BIT_CHAR_ENCODING).replace('"', (char)('"'|0x80))+"\"");
this.out = realOut;
}
}
cpRef(CONSTANT_Utf8, key.substring(key.indexOf('.') + 1));
u4(attrBuf.size());
attrBuf.writeTo(out);
putAttrBuf(attrBuf);
}
if (x.getName() == "Code") {
currentCode = prevCurrent;
} else {
currentMember = prevCurrent;
}
}
private void writeAttributeRecursive(Element aval, String type) throws IOException {
assert (callables == null);
callables = getBodies(type);
writeAttribute(aval, callables[0]);
callables = null;
}
private void writeAttribute(Element aval, String type) throws IOException {
//System.out.println("writeAttribute "+aval+" using "+type);
String nextAttrName = null;
boolean afterElemHead = false;
for (int len = type.length(), next, i = 0; i < len; i = next) {
int value;
char intKind;
int tag;
int sigChar;
String attrValue;
switch (type.charAt(i)) {
case '<':
assert (nextAttrName == null);
next = type.indexOf('>', i);
String form = type.substring(i + 1, next++);
if (form.indexOf('=') < 0) {
// elem_placement = '<' elemname '>'
if (aval.isAnonymous()) {
assert (aval.size() == 1);
aval = (Element) aval.get(0);
}
assert (aval.getName().equals(form)) : aval + " // " + form;
afterElemHead = true;
} else {
// attr_placement = '(' attrname '=' (value)? ')'
int eqPos = form.indexOf('=');
assert (eqPos >= 0);
nextAttrName = form.substring(0, eqPos).intern();
if (eqPos != form.length() - 1) {
// value is implicit, not placed in file
nextAttrName = null;
}
afterElemHead = false;
}
continue;
case '(':
next = type.indexOf(')', ++i);
int callee = Integer.parseInt(type.substring(i, next++));
writeAttribute(aval, callables[callee]);
continue;
case 'N': // replication = 'N' int '[' type ... ']'
{
assert (nextAttrName == null);
afterElemHead = false;
char countType = type.charAt(i + 1);
next = i + 2;
String type1 = getBody(type, next);
Element elems = aval;
if (type1.startsWith("<")) {
// Select only matching members of aval.
String elemName = type1.substring(1, type1.indexOf('>'));
elems = aval.findAllElements(elemName);
}
putInt(elems.size(), countType);
next += type1.length() + 2; // skip body and brackets
for (Element elem : elems.elements()) {
writeAttribute(elem, type1);
}
}
continue;
case 'T': // union = 'T' any_int union_case* '(' ')' '[' body ']'
// write the value
value = (int) aval.getAttrLong("tag");
assert (aval.getAttr("tag") != null) : aval;
intKind = type.charAt(++i);
if (intKind == 'S') {
intKind = type.charAt(++i);
}
putInt(value, intKind);
nextAttrName = null;
afterElemHead = false;
++i; // skip the int type char
// union_case = '(' ('-')? digit+ ')' '[' body ']'
for (boolean foundCase = false;;) {
assert (type.charAt(i) == '(');
next = type.indexOf(')', ++i);
assert (next >= i);
String caseStr = type.substring(i, next++);
String type1 = getBody(type, next);
next += type1.length() + 2; // skip body and brackets
boolean lastCase = (caseStr.length() == 0);
if (!foundCase
&& (lastCase || matchTag(value, caseStr))) {
foundCase = true;
// Execute this body.
writeAttribute(aval, type1);
}
if (lastCase) {
break;
}
}
continue;
case 'B':
case 'H':
case 'I': // int = oneof "BHI"
value = (int) aval.getAttrLong(nextAttrName);
intKind = type.charAt(i);
next = i + 1;
break;
case 'K':
sigChar = type.charAt(i + 1);
if (sigChar == 'Q') {
assert (currentMember.getName() == "Field");
assert (aval.getName() == "ConstantValue");
String sig = currentMember.getAttr("type");
sigChar = sig.charAt(0);
switch (sigChar) {
case 'Z':
case 'B':
case 'C':
case 'S':
sigChar = 'I';
break;
}
}
switch (sigChar) {
case 'I':
tag = CONSTANT_Integer;
break;
case 'J':
tag = CONSTANT_Long;
break;
case 'F':
tag = CONSTANT_Float;
break;
case 'D':
tag = CONSTANT_Double;
break;
case 'L':
tag = CONSTANT_String;
break;
default:
assert (false);
tag = 0;
}
assert (type.charAt(i + 2) == 'H'); // only H works for now
next = i + 3;
assert (afterElemHead || nextAttrName != null);
//System.out.println("get attr "+nextAttrName+" in "+aval);
if (nextAttrName != null) {
attrValue = aval.getAttr(nextAttrName);
assert (attrValue != null);
} else {
assert (aval.isText()) : aval;
attrValue = aval.getText().toString();
}
value = getCPIndex(tag, attrValue);
intKind = 'H'; //type.charAt(i+2);
break;
case 'R':
sigChar = type.charAt(i + 1);
switch (sigChar) {
case 'C':
tag = CONSTANT_Class;
break;
case 'S':
tag = CONSTANT_Utf8;
break;
case 'D':
tag = CONSTANT_Class;
break;
case 'F':
tag = CONSTANT_Fieldref;
break;
case 'M':
tag = CONSTANT_Methodref;
break;
case 'I':
tag = CONSTANT_InterfaceMethodref;
break;
case 'U':
tag = CONSTANT_Utf8;
break;
//case 'Q': tag = CONSTANT_Class; break;
default:
assert (false);
tag = 0;
}
assert (type.charAt(i + 2) == 'H'); // only H works for now
next = i + 3;
assert (afterElemHead || nextAttrName != null);
//System.out.println("get attr "+nextAttrName+" in "+aval);
if (nextAttrName != null) {
attrValue = aval.getAttr(nextAttrName);
} else if (aval.hasText()) {
attrValue = aval.getText().toString();
} else {
attrValue = null;
}
value = getCPIndex(tag, attrValue);
intKind = 'H'; //type.charAt(i+2);
break;
case 'P': // bci = 'P' int
case 'S': // signed_int = 'S' int
next = i + 2;
value = (int) aval.getAttrLong(nextAttrName);
intKind = type.charAt(i + 1);
break;
case 'F':
next = i + 2;
value = parseFlags(aval.getAttr(nextAttrName));
intKind = type.charAt(i + 1);
break;
default:
throw new RuntimeException("bad attr format '" + type.charAt(i) + "': " + type);
}
// write the value
putInt(value, intKind);
nextAttrName = null;
afterElemHead = false;
}
assert (nextAttrName == null);
}
private void putInt(int x, char ch) throws IOException {
switch (ch) {
case 'B':
u1(x);
break;
case 'H':
u2(x);
break;
case 'I':
u4(x);
break;
}
assert ("BHI".indexOf(ch) >= 0);
}
private void writeCode(Element code) throws IOException {
//System.out.println("writeCode "+code);
//Element m = new Element(currentMember); m.remove(code);
//System.out.println(" in "+m);
int stack = (int) code.getAttrLong("stack");
int local = (int) code.getAttrLong("local");
Element bytes = code.findElement("Bytes");
Element insns = code.findElement("Instructions");
String bytecodes;
if (insns == null) {
bytecodes = bytes.getText().toString();
} else {
bytecodes = InstructionSyntax.assemble(insns, this);
// Cache the assembled bytecodes:
bytes = new Element("Bytes", (String[]) null, bytecodes);
code.add(0, bytes);
}
u2(stack);
u2(local);
int length = bytecodes.length();
u4(length);
for (int i = 0; i < length; i++) {
u1((byte) bytecodes.charAt(i));
}
Element handlers = code.findAllElements("Handler");
u2(handlers.size());
for (Element handler : handlers.elements()) {
int start = (int) handler.getAttrLong("start");
int end = (int) handler.getAttrLong("end");
int catsh = (int) handler.getAttrLong("catch");
u2(start);
u2(end);
u2(catsh);
cpRef(CONSTANT_Class, handler.getAttr("class"));
}
writeAttributesFor(code);
}
protected void writeStackMap(Element attrs, boolean hasXOption) throws IOException {
Element bytes = currentCode.findElement("Bytes");
assert (bytes != null && bytes.size() == 1);
int byteLength = ((String) bytes.get(0)).length();
boolean uoffsetIsU4 = (byteLength >= (1 << 16));
boolean ulocalvarIsU4 = currentCode.getAttrLong("local") >= (1 << 16);
boolean ustackIsU4 = currentCode.getAttrLong("stack") >= (1 << 16);
if (uoffsetIsU4) {
u4(attrs.size());
} else {
u2(attrs.size());
}
for (Element frame : attrs.elements()) {
int bci = (int) frame.getAttrLong("bci");
if (uoffsetIsU4) {
u4(bci);
} else {
u2(bci);
}
if (hasXOption) {
u1((int) frame.getAttrLong("flags"));
}
// Scan local and stack types in this frame:
final int LOCALS = 0, STACK = 1;
for (int j = LOCALS; j <= STACK; j++) {
Element types = frame.findElement(j == LOCALS ? "Local" : "Stack");
int typeSize = (types == null) ? 0 : types.size();
if (j == LOCALS) {
if (ulocalvarIsU4) {
u4(typeSize);
} else {
u2(typeSize);
}
} else { // STACK
if (ustackIsU4) {
u4(typeSize);
} else {
u2(typeSize);
}
}
if (types == null) {
continue;
}
for (Element type : types.elements()) {
int tag = itemTagValue(type.getName());
u1(tag);
switch (tag) {
case ITEM_Object:
cpRef(CONSTANT_Class, type.getAttr("class"));
break;
case ITEM_Uninitialized:
case ITEM_ReturnAddress: {
int offset = (int) type.getAttrLong("bci");
if (uoffsetIsU4) {
u4(offset);
} else {
u2(offset);
}
}
break;
}
}
}
}
}
public void writeCP() throws IOException {
int cpLen = cpoolSize;
u2(cpLen);
ByteArrayOutputStream buf = getAttrBuf();
for (Element c : cpool.elements()) {
if (!c.isText()) {
System.out.println("## !isText " + c);
}
int id = (int) c.getAttrLong("id");
int tag = cpTagValue(c.getName());
String name = c.getText().toString();
int pos;
u1(tag);
switch (tag) {
case CONSTANT_Utf8: {
int done = 0;
buf.reset();
int nameLen = name.length();
while (done < nameLen) {
int next = name.indexOf((char) 0, done);
if (next < 0) {
next = nameLen;
}
if (done < next) {
buf.write(name.substring(done, next).getBytes(UTF8_ENCODING));
}
if (next < nameLen) {
buf.write(0300);
buf.write(0200);
next++;
}
done = next;
}
u2(buf.size());
buf.writeTo(out);
}
break;
case CONSTANT_Integer:
u4(Integer.parseInt(name));
break;
case CONSTANT_Float:
u4(Float.floatToIntBits(Float.parseFloat(name)));
break;
case CONSTANT_Long:
u8(Long.parseLong(name));
//i += 1; // no need: extra cp slot is implicit
break;
case CONSTANT_Double:
u8(Double.doubleToLongBits(Double.parseDouble(name)));
//i += 1; // no need: extra cp slot is implicit
break;
case CONSTANT_Class:
case CONSTANT_String:
u2(getCPIndex(CONSTANT_Utf8, name));
break;
case CONSTANT_Fieldref:
case CONSTANT_Methodref:
case CONSTANT_InterfaceMethodref:
pos = name.indexOf(' ');
u2(getCPIndex(CONSTANT_Class, name.substring(0, pos)));
u2(getCPIndex(CONSTANT_NameAndType, name.substring(pos + 1)));
break;
case CONSTANT_NameAndType:
pos = name.indexOf(' ');
u2(getCPIndex(CONSTANT_Utf8, name.substring(0, pos)));
u2(getCPIndex(CONSTANT_Utf8, name.substring(pos + 1)));
break;
}
}
putAttrBuf(buf);
}
public void cpRef(int tag, String name) throws IOException {
u2(getCPIndex(tag, name));
}
public void u8(long x) throws IOException {
u4((int) (x >>> 32));
u4((int) (x >>> 0));
}
public void u4(int x) throws IOException {
u2(x >>> 16);
u2(x >>> 0);
}
public void u2(int x) throws IOException {
u1(x >>> 8);
u1(x >>> 0);
}
public void u1(int x) throws IOException {
out.write(x & 0xFF);
}
}