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