Index: src/test/java/org/apache/harmony/archive/tests/internal/pack200/AllTests.java =================================================================== --- src/test/java/org/apache/harmony/archive/tests/internal/pack200/AllTests.java (revision 465556) +++ src/test/java/org/apache/harmony/archive/tests/internal/pack200/AllTests.java (working copy) @@ -18,6 +18,9 @@ // NOTE: Do not use generics in this code; it needs to run on JVMs < 1.5 // NOTE: Do not extract strings as messages; this code is still a work-in-progress // NOTE: Also, don't get rid of 'else' statements for the hell of it ... +import org.apache.harmony.archive.tests.internal.pack200.bytecode.ClassFileEntryTest; +import org.apache.harmony.archive.tests.internal.pack200.bytecode.ConstantPoolTest; + import junit.framework.Test; import junit.framework.TestSuite; /** @@ -35,8 +38,10 @@ // $JUnit-BEGIN$ suite.addTestSuite(AttributeLayoutMapTest.class); suite.addTestSuite(AttributeLayoutTest.class); + suite.addTestSuite(ClassFileEntryTest.class); suite.addTestSuite(CodecEncodingTest.class); suite.addTestSuite(CodecTest.class); + suite.addTestSuite(ConstantPoolTest.class); suite.addTestSuite(PopulationCodecTest.class); suite.addTestSuite(SegmentOptionsTest.class); suite.addTestSuite(SegmentTest.class); Index: src/test/java/org/apache/harmony/archive/tests/internal/pack200/bytecode/ConstantPoolTest.java =================================================================== --- src/test/java/org/apache/harmony/archive/tests/internal/pack200/bytecode/ConstantPoolTest.java (revision 0) +++ src/test/java/org/apache/harmony/archive/tests/internal/pack200/bytecode/ConstantPoolTest.java (revision 0) @@ -0,0 +1,37 @@ +package org.apache.harmony.archive.tests.internal.pack200.bytecode; + +import junit.framework.TestCase; + +import org.apache.harmony.archive.internal.pack200.bytecode.CPMember; +import org.apache.harmony.archive.internal.pack200.bytecode.CPUTF8; +import org.apache.harmony.archive.internal.pack200.bytecode.ClassConstantPool; + +public class ConstantPoolTest extends TestCase { + private ClassConstantPool pool; + + public void setUp() { + pool = new ClassConstantPool(); + } + public void testDuplicateUTF8() { + CPUTF8 u1 = new CPUTF8("thing"); + CPUTF8 u2 = new CPUTF8("thing"); + pool.add(u1); + pool.add(u2); + assertEquals(1,pool.size()); + } + public void testDuplicateField() { + CPMember cp1 = new CPMember("name:I",0,null); + pool.add(cp1); + assertEquals(2,pool.size()); + CPMember cp2 = new CPMember("name:I",0,null); + pool.add(cp2); + assertEquals(2,pool.size()); + } + public void testIndex() { + pool.add(new CPUTF8("OtherThing")); + CPUTF8 u1 = new CPUTF8("thing"); + pool.add(u1); + pool.resolve(); + assertTrue(pool.indexOf(u1) > 0); + } +} Index: src/test/java/org/apache/harmony/archive/tests/internal/pack200/bytecode/ClassFileEntryTest.java =================================================================== --- src/test/java/org/apache/harmony/archive/tests/internal/pack200/bytecode/ClassFileEntryTest.java (revision 0) +++ src/test/java/org/apache/harmony/archive/tests/internal/pack200/bytecode/ClassFileEntryTest.java (revision 0) @@ -0,0 +1,75 @@ +package org.apache.harmony.archive.tests.internal.pack200.bytecode; + +import junit.framework.TestCase; + +import org.apache.harmony.archive.internal.pack200.bytecode.CPDouble; +import org.apache.harmony.archive.internal.pack200.bytecode.CPFloat; +import org.apache.harmony.archive.internal.pack200.bytecode.CPInteger; +import org.apache.harmony.archive.internal.pack200.bytecode.CPLong; +import org.apache.harmony.archive.internal.pack200.bytecode.CPMember; +import org.apache.harmony.archive.internal.pack200.bytecode.CPString; +import org.apache.harmony.archive.internal.pack200.bytecode.CPUTF8; +import org.apache.harmony.archive.internal.pack200.bytecode.SourceFileAttribute; + +public class ClassFileEntryTest extends TestCase { + public void testUTF8() { + CPUTF8 u1 = new CPUTF8(new String("thing")); //$NON-NLS-1$ + CPUTF8 u2 = new CPUTF8(new String("thing")); //$NON-NLS-1$ + CPUTF8 u3 = new CPUTF8(new String("otherthing")); //$NON-NLS-1$ + checkEquality(u1, u2, "thing", u3); + } + private void checkEquality(Object equal1, Object equal2, String toString, Object unequal) { + assertEquals(equal1,equal2); + assertEquals(equal1.hashCode(),equal2.hashCode()); + assertTrue(equal1.toString().indexOf(toString)>=0); //$NON-NLS-1$ + assertFalse(equal1.equals(unequal)); + assertFalse(equal2.equals(unequal)); + assertFalse(unequal.equals(equal1)); + assertFalse(unequal.equals(equal2)); + } + public void testSourceAttribute() { + SourceFileAttribute sfa1 = new SourceFileAttribute(new String("Thing.java")); //$NON-NLS-1$ + SourceFileAttribute sfa2 = new SourceFileAttribute(new String("Thing.java")); //$NON-NLS-1$ + SourceFileAttribute sfa3 = new SourceFileAttribute(new String("OtherThing.java")); //$NON-NLS-1$ + checkEquality(sfa1,sfa2,"Thing.java",sfa3); //$NON-NLS-1$ + } + public void testCPInteger() { + CPInteger cp1 = new CPInteger(new Integer(3)); + CPInteger cp2 = new CPInteger(new Integer(3)); + CPInteger cp3 = new CPInteger(new Integer(5)); + checkEquality(cp1,cp2,"3",cp3); //$NON-NLS-1$ + } + public void testCPLong() { + CPLong cp1 = new CPLong(new Long(3)); + CPLong cp2 = new CPLong(new Long(3)); + CPLong cp3 = new CPLong(new Long(5)); + checkEquality(cp1,cp2,"3",cp3); //$NON-NLS-1$ + } + public void testCPDouble() { + CPDouble cp1 = new CPDouble(new Double(3)); + CPDouble cp2 = new CPDouble(new Double(3)); + CPDouble cp3 = new CPDouble(new Double(5)); + checkEquality(cp1,cp2,"3",cp3); //$NON-NLS-1$ + } + public void testCPFloat() { + CPFloat cp1 = new CPFloat(new Float(3)); + CPFloat cp2 = new CPFloat(new Float(3)); + CPFloat cp3 = new CPFloat(new Float(5)); + checkEquality(cp1,cp2,"3",cp3); //$NON-NLS-1$ + } + public void testCPString() { + CPString cp1 = new CPString(new String("3")); + CPString cp2 = new CPString(new String("3")); + CPString cp3 = new CPString(new String("5")); + checkEquality(cp1,cp2,"3",cp3); //$NON-NLS-1$ + } + public void testCPField() { + CPMember cp1 = new CPMember("Name:I", 0, null); + CPMember cp2 = new CPMember("Name:I", 0, null); + CPMember cp3 = new CPMember("Name:Z", 0, null); + CPMember cp4 = new CPMember("GName:I", 0, null); + checkEquality(cp1,cp2,"Name",cp3); //$NON-NLS-1$ + checkEquality(cp1,cp2,"I",cp4); //$NON-NLS-1$ + } + +} Index: src/main/java/org/apache/harmony/archive/internal/pack200/AttributeLayoutMap.java =================================================================== --- src/main/java/org/apache/harmony/archive/internal/pack200/AttributeLayoutMap.java (revision 467508) +++ src/main/java/org/apache/harmony/archive/internal/pack200/AttributeLayoutMap.java (working copy) @@ -27,61 +27,59 @@ * internationalised, and should not be translated. */ public class AttributeLayoutMap { - // private static final String METADATA = "[NH[(1)]][RSHNH[RUH(1)]][TB(66,67,73,83,90)[KIH](68)[KDH](70)[KFH](74)[KJH](99)[RSH](101)[RSHRUH](115)[RUH](91)[NH[(0)]](64)[RSH[RUH(0)]]()[]]"; - // create a whole bunch of AttributeLayouts here private static AttributeLayout[] getDefaultAttributeLayouts() throws Pack200Exception { return new AttributeLayout[] { - new AttributeLayout("LineNumberTable", + new AttributeLayout(AttributeLayout.ATTRIBUTE_LINE_NUMBER_TABLE, AttributeLayout.CONTEXT_CODE, "NH[PHH]", 1), - new AttributeLayout("LocalVariableTable", + new AttributeLayout(AttributeLayout.ATTRIBUTE_LOCAL_VARIABLE_TABLE, AttributeLayout.CONTEXT_CODE, "NH[PHOHRUHRSHH]", 2), - new AttributeLayout("LocalVariableTypeTable", + new AttributeLayout(AttributeLayout.ATTRIBUTE_LOCAL_VARIABLE_TYPE_TABLE, AttributeLayout.CONTEXT_CODE, "NH[PHOHRUHRSHH]", 3), - new AttributeLayout("SourceFile", + new AttributeLayout(AttributeLayout.ATTRIBUTE_SOURCE_FILE, AttributeLayout.CONTEXT_CLASS, "RUNH", 17), - new AttributeLayout("ConstantValue", + new AttributeLayout(AttributeLayout.ATTRIBUTE_CONSTANT_VALUE, AttributeLayout.CONTEXT_FIELD, "KQH", 17), - new AttributeLayout("Code", AttributeLayout.CONTEXT_METHOD, + new AttributeLayout(AttributeLayout.ATTRIBUTE_CODE, AttributeLayout.CONTEXT_METHOD, "*", 17), - new AttributeLayout("EnclosingMethod", + new AttributeLayout(AttributeLayout.ATTRIBUTE_ENCLOSING_METHOD, AttributeLayout.CONTEXT_CLASS, "RCHRDNH", 18), - new AttributeLayout("Exceptions", + new AttributeLayout(AttributeLayout.ATTRIBUTE_EXCEPTIONS, AttributeLayout.CONTEXT_METHOD, "NH[RCH]", 18), - new AttributeLayout("Signature", AttributeLayout.CONTEXT_CLASS, + new AttributeLayout(AttributeLayout.ATTRIBUTE_SIGNATURE, AttributeLayout.CONTEXT_CLASS, "RSH", 19), - new AttributeLayout("Signature", AttributeLayout.CONTEXT_FIELD, + new AttributeLayout(AttributeLayout.ATTRIBUTE_SIGNATURE, AttributeLayout.CONTEXT_FIELD, "RSH", 19), - new AttributeLayout("Signature", + new AttributeLayout(AttributeLayout.ATTRIBUTE_SIGNATURE, AttributeLayout.CONTEXT_METHOD, "RSH", 19), - new AttributeLayout("Deprecated", + new AttributeLayout(AttributeLayout.ATTRIBUTE_DEPRECATED, AttributeLayout.CONTEXT_CLASS, "", 20), - new AttributeLayout("Deprecated", + new AttributeLayout(AttributeLayout.ATTRIBUTE_DEPRECATED, AttributeLayout.CONTEXT_FIELD, "", 20), - new AttributeLayout("Deprecated", + new AttributeLayout(AttributeLayout.ATTRIBUTE_DEPRECATED, AttributeLayout.CONTEXT_METHOD, "", 20), - new AttributeLayout("RuntimeVisibleAnnotations", + new AttributeLayout(AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, AttributeLayout.CONTEXT_CLASS, "*", 21), - new AttributeLayout("RuntimeVisibleAnnotations", + new AttributeLayout(AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, AttributeLayout.CONTEXT_FIELD, "*", 21), - new AttributeLayout("RuntimeVisibleAnnotations", + new AttributeLayout(AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, AttributeLayout.CONTEXT_METHOD, "*", 21), - new AttributeLayout("RuntimeInvisibleAnnotations", + new AttributeLayout(AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS, AttributeLayout.CONTEXT_CLASS, "*", 22), - new AttributeLayout("RuntimeInvisibleAnnotations", + new AttributeLayout(AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS, AttributeLayout.CONTEXT_FIELD, "*", 22), - new AttributeLayout("RuntimeInvisibleAnnotations", + new AttributeLayout(AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS, AttributeLayout.CONTEXT_METHOD, "*", 22), - new AttributeLayout("InnerClasses", + new AttributeLayout(AttributeLayout.ATTRIBUTE_INNER_CLASSES, AttributeLayout.CONTEXT_CLASS, "*", 23), - new AttributeLayout("RuntimeVisibleParameterAnnotations", + new AttributeLayout(AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, AttributeLayout.CONTEXT_METHOD, "*", 23), - new AttributeLayout("class-file version", + new AttributeLayout(AttributeLayout.ATTRIBUTE_CLASS_FILE_VERSION, AttributeLayout.CONTEXT_CLASS, "*", 24), - new AttributeLayout("RuntimeInvisibleParameterAnnotations", + new AttributeLayout(AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, AttributeLayout.CONTEXT_METHOD, "*", 24), - new AttributeLayout("AnnotationDefault", + new AttributeLayout(AttributeLayout.ATTRIBUTE_ANNOTATION_DEFAULT, AttributeLayout.CONTEXT_METHOD, "*", 25) }; } Index: src/main/java/org/apache/harmony/archive/internal/pack200/AttributeLayout.java =================================================================== --- src/main/java/org/apache/harmony/archive/internal/pack200/AttributeLayout.java (revision 467508) +++ src/main/java/org/apache/harmony/archive/internal/pack200/AttributeLayout.java (working copy) @@ -15,9 +15,11 @@ * limitations under the License. */ package org.apache.harmony.archive.internal.pack200; -//NOTE: Do not use generics in this code; it needs to run on JVMs < 1.5 -//NOTE: Do not extract strings as messages; this code is still a work-in-progress -//NOTE: Also, don't get rid of 'else' statements for the hell of it ... + +// NOTE: Do not use generics in this code; it needs to run on JVMs < 1.5 +// NOTE: Do not extract strings as messages; this code is still a +// work-in-progress +// NOTE: Also, don't get rid of 'else' statements for the hell of it ... import org.apache.harmony.archive.internal.pack200.Segment.SegmentConstantPool; public class AttributeLayout { @@ -78,6 +80,40 @@ private long mask; + public static final String ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations"; + + public static final String ATTRIBUTE_CLASS_FILE_VERSION = "class-file version"; + + public static final String ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS = "RuntimeInvisibleParameterAnnotations"; + + public static final String ATTRIBUTE_ANNOTATION_DEFAULT = "AnnotationDefault"; + + public static final String ATTRIBUTE_CODE = "Code"; + + public static final String ATTRIBUTE_SOURCE_FILE = "SourceFile"; + + public static final String ATTRIBUTE_LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable"; + + public static final String ATTRIBUTE_LOCAL_VARIABLE_TABLE = "LocalVariableTable"; + + public static final String ATTRIBUTE_LINE_NUMBER_TABLE = "LineNumberTable"; + + public static final String ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = "RuntimeVisibleParameterAnnotations"; + + public static final String ATTRIBUTE_INNER_CLASSES = "InnerClasses"; + + public static final String ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations"; + + public static final String ATTRIBUTE_DEPRECATED = "Deprecated"; + + public static final String ATTRIBUTE_CONSTANT_VALUE = "ConstantValue"; + + public static final String ATTRIBUTE_ENCLOSING_METHOD = "EnclosingMethod"; + + public static final String ATTRIBUTE_EXCEPTIONS = "Exceptions"; + + public static final String ATTRIBUTE_SIGNATURE = "Signature"; + public static final int CONTEXT_CODE = 1 << 4; public static final int CONTEXT_CLASS = 1 << 0; @@ -112,17 +148,54 @@ } } + public Object getValue(long value, String type, Segment segment) + throws Pack200Exception { + // TODO This really needs to be better tested, esp. the different types + // TODO This should have the ability to deal with RUN stuff too, and unions + if (layout.startsWith("KQ")) { + if (type.equals("Ljava/lang/String;")) { + Object value2 = getValue("KS", value, segment); + return value2; + } else { + return getValue("K" + type + layout.substring(2), value, + segment); + } + } else { + return getValue(layout, value, segment); + } + } + public Object getValue(long value, Segment segment) throws Pack200Exception { + return getValue(layout, value, segment); + } + + private static Object getValue(String layout, long value, Segment segment) + throws Pack200Exception { + SegmentConstantPool pool = segment.getConstantPool(); if (layout.startsWith("R")) { // references if (layout.indexOf('N') != -1) value--; - SegmentConstantPool pool = segment.getConstantPool(); if (layout.startsWith("RU")) { return pool.getValue(SegmentConstantPool.UTF_8, value); } else if (layout.startsWith("RS")) { return pool.getValue(SegmentConstantPool.SIGNATURE, value); } + } else if (layout.startsWith("K")) { + char type = layout.charAt(1); + switch (type) { + case 'S': // String + return pool.getValue(SegmentConstantPool.CP_STRING, value); + case 'I': // Int (or byte or short) + case 'C': // Char + return pool.getValue(SegmentConstantPool.CP_INT, value); + case 'F': // Float + return pool.getValue(SegmentConstantPool.CP_FLOAT, value); + case 'J': // Long + return pool.getValue(SegmentConstantPool.CP_LONG,value); + case 'D': // Double + return pool.getValue(SegmentConstantPool.CP_DOUBLE,value); + } } throw new Pack200Exception("Unknown layout encoding: " + layout); } Index: src/main/java/org/apache/harmony/archive/internal/pack200/Segment.java =================================================================== --- src/main/java/org/apache/harmony/archive/internal/pack200/Segment.java (revision 467508) +++ src/main/java/org/apache/harmony/archive/internal/pack200/Segment.java (working copy) @@ -15,20 +15,32 @@ * limitations under the License. */ package org.apache.harmony.archive.internal.pack200; -//NOTE: Do not use generics in this code; it needs to run on JVMs < 1.5 -//NOTE: Do not extract strings as messages; this code is still a work-in-progress -//NOTE: Also, don't get rid of 'else' statements for the hell of it ... + +// NOTE: Do not use generics in this code; it needs to run on JVMs < 1.5 +// NOTE: Do not extract strings as messages; this code is still a +// work-in-progress +// NOTE: Also, don't get rid of 'else' statements for the hell of it ... import java.io.ByteArrayInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.zip.GZIPInputStream; -import org.apache.harmony.archive.internal.pack200.ClassFileEntry.SourceFile; +import org.apache.harmony.archive.internal.pack200.bytecode.Attribute; +import org.apache.harmony.archive.internal.pack200.bytecode.CPClass; +import org.apache.harmony.archive.internal.pack200.bytecode.CPField; +import org.apache.harmony.archive.internal.pack200.bytecode.CPMethod; +import org.apache.harmony.archive.internal.pack200.bytecode.ClassConstantPool; +import org.apache.harmony.archive.internal.pack200.bytecode.ClassFile; +import org.apache.harmony.archive.internal.pack200.bytecode.ClassFileEntry; +import org.apache.harmony.archive.internal.pack200.bytecode.ConstantValueAttribute; +import org.apache.harmony.archive.internal.pack200.bytecode.ExceptionsAttribute; +import org.apache.harmony.archive.internal.pack200.bytecode.SourceFileAttribute; /** * A Pack200 archive consists of one (or more) segments. Each segment is @@ -65,23 +77,46 @@ public class SegmentConstantPool { public static final int ALL = 0; + public static final int CP_DOUBLE = 7; + + // define in archive order + + public static final int CP_FLOAT = 4; // TODO Check this + + public static final int CP_INT = 3; + + public static final int CP_LONG = 6; + + public static final int CP_STRING = 5; + public static final int SIGNATURE = 2; // TODO and more to come -- - // define in archive order public static final int UTF_8 = 1; public Object getValue(int cp, long value) throws Pack200Exception { - int index = (int)value; - if (index == -1) + int index = (int) value; + if (index == -1) { return null; - if (index < 0) + } else if (index < 0) { throw new Pack200Exception("Cannot have a negative range"); - if (cp == UTF_8) + } else if (cp == UTF_8) { return cpUTF8[index]; - if (cp == SIGNATURE) + } else if (cp == CP_STRING) { + return cpString[index]; + } else if (cp == SIGNATURE) { return cpSignature[index]; - // etc - throw new Error("Get value incomplete"); + } else if (cp == CP_INT) { + return new Integer(cpInt[index]); + } else if (cp == CP_FLOAT) { + return new Float(cpFloat[index]); + } else if (cp == CP_DOUBLE) { + return new Double(cpDouble[index]); + } else if (cp == CP_LONG) { + return new Long(cpLong[index]); + } else { + // etc + throw new Error("Get value incomplete"); + } } } @@ -144,6 +179,10 @@ } } + private int archiveMajor; + + private int archiveMinor; + private long archiveModtime; private long archiveSize; @@ -238,6 +277,8 @@ private int fieldAttrCount; + private ArrayList[][] fieldAttributes; + private String[][] fieldDescr; private long[][] fieldFlags; @@ -262,16 +303,16 @@ private int innerClassCount; - private int archiveMajor; - private int methodAttrCount; + private ArrayList[][] methodAttributes; + private String[][] methodDescr; + private ExceptionsAttribute[][] methodExceptions; + private long[][] methodFlags; - private int archiveMinor; - private int numberOfFiles; private SegmentOptions options; @@ -280,6 +321,68 @@ private int segmentsRemaining; + private ClassFile buildClassFile(int classNum) { + ClassFile classFile = new ClassFile(); + classFile.major = defaultClassMajorVersion; // TODO If + // classVersionMajor[] use + // that instead + classFile.minor = defaultClassMinorVersion; // TODO if + // classVersionMinor[] use + // that instead + // build constant pool + ClassConstantPool cp = classFile.pool; + String fullName = classThis[classNum]; + // SourceFile attribute + int i = fullName.lastIndexOf("/") + 1; // if lastIndexOf==-1, then + // -1+1=0, so str.substring(0) + // == str + String fileName = fullName.substring(i) + ".java"; + classFile.attributes = new Attribute[] { (Attribute) cp + .add(new SourceFileAttribute(fileName)) }; + // this/superclass + ClassFileEntry cfThis = cp.add(new CPClass(fullName)); + ClassFileEntry cfSuper = cp.add(new CPClass(classSuper[classNum])); + // add interfaces + ClassFileEntry cfInterfaces[] = new ClassFileEntry[classInterfaces[classNum].length]; + for (i = 0; i < cfInterfaces.length; i++) { + cfInterfaces[i] = cp.add(new CPClass(classInterfaces[classNum][i])); + } + // add fields + ClassFileEntry cfFields[] = new ClassFileEntry[classFieldCount[classNum]]; + // fieldDescr and fieldFlags used to create this + for (i = 0; i < cfFields.length; i++) { + cfFields[i] = cp.add(new CPField(fieldDescr[classNum][i], + fieldFlags[classNum][i], fieldAttributes[classNum][i])); + } + // add methods + ClassFileEntry cfMethods[] = new ClassFileEntry[classMethodCount[classNum]]; + // fieldDescr and fieldFlags used to create this + for (i = 0; i < cfMethods.length; i++) { + cfMethods[i] = cp.add(new CPMethod(methodDescr[classNum][i], + methodFlags[classNum][i], methodAttributes[classNum][i])); + } + // sort CP according to cp_All + cp.resolve(); + // print out entries + debug("Constant pool looks like:"); + for (i = 1; i <= cp.size(); i++) { + debug(String.valueOf(i) + ":" + String.valueOf(cp.get(i))); + } + // NOTE the indexOf is only valid after the cp.resolve() + // build up remainder of file + classFile.accessFlags = (int) classFlags[classNum]; + classFile.thisClass = cp.indexOf(cfThis); + classFile.superClass = cp.indexOf(cfSuper); + // TODO placate format of file for writing purposes + classFile.interfaces = new int[cfInterfaces.length]; + for (i = 0; i < cfInterfaces.length; i++) { + classFile.interfaces[i] = cp.indexOf(cfInterfaces[i]); + } + classFile.fields = cfFields; + classFile.methods = cfMethods; + return classFile; + } + /** * This is a local debugging message to aid the developer in writing this * class. It will be removed before going into production. If the property @@ -462,90 +565,7 @@ public int getNumberOfFiles() { return numberOfFiles; } - /** - * Writes the segment to an output stream. The output stream should be pre-buffered for - * efficiency. Also takes the same input stream for reading, since the file bits may - * not be loaded and thus just copied from one stream to another. - * Doesn't close the output stream when finished, in case there are more entries (e.g. - * further segments) to be written. - * @param out the JarOutputStream to write data to - * @param in the same InputStream that was used to parse the segment - * @throws IOException if an error occurs whilst reading or writing to the streams - * @throws Pack200Exception if an error occurs whilst unpacking data - */ - public void writeJar(JarOutputStream out, InputStream in ) throws IOException, Pack200Exception { - processFileBits(in); - DataOutputStream dos = new DataOutputStream(out); - // out.setLevel(JarEntry.DEFLATED) - // now write the files out - int classNum = 0; - for(int i=0;i 0) + throw new Error("Can't handle non-abstract, non-native methods/initialisers at the moment (found " + codeBands + " code bands)"); debug("unimplemented code_headers"); debug("unimplemented code_max_stack"); debug("unimplemented code_max_na_locals"); @@ -855,8 +961,8 @@ private void parseCpLong(InputStream in) throws IOException, Pack200Exception { - cpLong = parseFlags("cp_Long", in, cpLongCount, Codec.UDELTA5, - Codec.DELTA5); + cpLong = parseFlags("cp_Long", in, cpLongCount, new int[] { 1 }, + Codec.UDELTA5, Codec.DELTA5)[0]; } /** @@ -1015,17 +1121,11 @@ private void parseFieldBands(InputStream in) throws IOException, Pack200Exception { - fieldDescr = new String[classCount][]; + fieldDescr = parseReferences("field_descr", in, Codec.DELTA5, + classCount, classFieldCount, cpDescriptor); + fieldFlags = parseFlags("field_flags", in, classCount, classFieldCount, + Codec.UNSIGNED5, options.hasFieldFlagsHi()); for (int i = 0; i < classCount; i++) { - fieldDescr[i] = parseReferences("field_descr", in, Codec.DELTA5, - classFieldCount[i], cpDescriptor); - } - fieldFlags = new long[classCount][]; - for (int i = 0; i < classCount; i++) { - fieldFlags[i] = parseFlags("field_flags", in, classFieldCount[i], - Codec.UNSIGNED5, options.hasFieldFlagsHi()); - } - for (int i = 0; i < classCount; i++) { for (int j = 0; j < fieldFlags[i].length; j++) { long flag = fieldFlags[i][j]; if ((flag & (1 << 16)) != 0) @@ -1037,7 +1137,33 @@ "There are attribute flags, and I don't know what to do with them"); debug("unimplemented field_attr_indexes"); debug("unimplemented field_attr_calls"); - debug("unimplemented field_ConstantValueKQ"); + AttributeLayout layout = attributeDefinitionMap.getAttributeLayout( + "ConstantValue", AttributeLayout.CONTEXT_FIELD); + Codec codec = layout.getCodec(); + fieldAttributes = new ArrayList[classCount][]; + for (int i = 0; i < classCount; i++) { + fieldAttributes[i] = new ArrayList[fieldFlags[i].length]; + for (int j = 0; j < fieldFlags[i].length; j++) { + fieldAttributes[i][j] = new ArrayList(); + long flag = fieldFlags[i][j]; + if (layout.matches(flag)) { + // we've got a value to read + long result = codec.decode(in); + String desc = fieldDescr[i][j]; + int colon = desc.indexOf(':'); + // String name = desc.substring(0, colon); + String type = desc.substring(colon + 1); + // TODO Got to get better at this ... in any case, it should + // be e.g. KIB or KIH + if (type.equals("B") || type.equals("H")) + type = "I"; + Object value = layout.getValue(result, type, this); + fieldAttributes[i][j] + .add(new ConstantValueAttribute(value)); + debug("Processed value " + value + " for ConstantValue"); + } + } + } debug("unimplemented field_Signature_RS"); parseMetadataBands("field"); } @@ -1060,6 +1186,15 @@ */ private void parseFileBands(InputStream in) throws IOException, Pack200Exception { + if (false && System.getProperty("debug.pack200") != null) { + // TODO HACK + fileSize = new long[numberOfFiles]; + fileModtime = new long[numberOfFiles]; + fileOptions = new long[numberOfFiles]; + fileName = new String[numberOfFiles]; + Arrays.fill(fileName, ""); + return; + } long last; fileName = parseReferences("file_name", in, Codec.UNSIGNED5, numberOfFiles, cpUTF8); @@ -1091,28 +1226,43 @@ } private long[] parseFlags(String name, InputStream in, int count, - Codec codec) throws IOException, Pack200Exception { - return parseFlags(name, in, count, codec, true); + Codec codec, boolean hasHi) throws IOException, Pack200Exception { + return parseFlags(name, in, 1, new int[] { count }, (hasHi ? codec + : null), codec)[0]; } - private long[] parseFlags(String name, InputStream in, int count, - Codec codec, boolean hasHi) throws IOException, Pack200Exception { - return parseFlags(name, in, count, (hasHi ? codec : null), codec); + private long[][] parseFlags(String name, InputStream in, int count, + int counts[], Codec codec, boolean hasHi) throws IOException, + Pack200Exception { + return parseFlags(name, in, count, counts, (hasHi ? codec : null), + codec); } - private long[] parseFlags(String name, InputStream in, int count, - Codec hiCodec, Codec loCodec) throws IOException, Pack200Exception { - long[] result = new long[count]; - // TODO Refactor into band parsing + private long[][] parseFlags(String name, InputStream in, int count, + int counts[], Codec hiCodec, Codec loCodec) throws IOException, + Pack200Exception { + // TODO Move away from decoding into a parseBand type structure + if (count == 0) { + return new long[][] { {} }; + } + long[][] result = new long[count][]; + // TODO What happens when the decode here indicates a different + // encoding? + // TODO Move this to a decodeBandInt long last = 0; - for (int i = 0; i < count && hiCodec != null; i++) { - last = hiCodec.decode(in, last); - result[i] = last << 32; + for (int j = 0; j < count; j++) { + result[j] = new long[counts[j]]; + for (int i = 0; i < counts[j] && hiCodec != null; i++) { + last = hiCodec.decode(in, last); + result[j][i] = last << 32; + } } - for (int i = 0; i < count; i++) { - last = loCodec.decode(in, last); - result[i] = result[i] | last; - } + last = 0; + for (int j = 0; j < count; j++) + for (int i = 0; i < counts[j]; i++) { + last = loCodec.decode(in, last); + result[j][i] = result[j][i] | last; + } // TODO Remove debugging code debug("Parsed *" + name + " (" + result.length + ")"); return result; @@ -1175,18 +1325,11 @@ private void parseMethodBands(InputStream in) throws IOException, Pack200Exception { - methodDescr = new String[classCount][]; + methodDescr = parseReferences("method_descr", in, Codec.MDELTA5, + classCount, classMethodCount, cpDescriptor); + methodFlags = parseFlags("method_flags", in, classCount, + classMethodCount, Codec.UNSIGNED5, options.hasMethodFlagsHi()); for (int i = 0; i < classCount; i++) { - methodDescr[i] = parseReferences("method_descr", in, Codec.MDELTA5, - classMethodCount[i], cpDescriptor); - } - methodFlags = new long[classCount][]; - for (int i = 0; i < classCount; i++) { - methodFlags[i] = parseFlags("method_flags", in, - classMethodCount[i], Codec.UNSIGNED5, options - .hasMethodFlagsHi()); - } - for (int i = 0; i < classCount; i++) { for (int j = 0; j < methodFlags[i].length; j++) { long flag = methodFlags[i][j]; if ((flag & (1 << 16)) != 0) @@ -1199,9 +1342,16 @@ debug("unimplemented method_attr_count"); debug("unimplemented method_attr_indexes"); debug("unimplemented method_attr_calls"); - debug("unimplemented method_Exceptions_N"); - debug("unimplemented method_Exceptions_RC"); - debug("unimplemented method_Signature_RS"); + // assign empty method attributes + methodAttributes = new ArrayList[classCount][]; + for (int i = 0; i < classCount; i++) { + methodAttributes[i] = new ArrayList[methodFlags[i].length]; + for (int j = 0; j < methodFlags[i].length; j++) { + methodAttributes[i][j] = new ArrayList(); + } + } + parseAttributeMethodExceptions(in); + parseAttributeMethodSignature(in); parseMetadataBands("method"); } @@ -1210,7 +1360,8 @@ * using codec to decode the values as indexes into * reference (which is populated prior to this call). An * exception is thrown if a decoded index falls outside the range - * [0..reference.length-1]. + * [0..reference.length-1]. Unlike the other parseReferences, this + * post-processes the result into an array of results. * * @param name * TODO @@ -1230,22 +1381,73 @@ * if a problem occurs with an unexpected value or unsupported * codec */ - private String[] parseReferences(String name, InputStream in, - BHSDCodec codec, int count, String[] reference) throws IOException, - Pack200Exception { - String[] result = new String[count]; - int[] decode = decodeBandInt(name, in, codec, count); + private String[][] parseReferences(String name, InputStream in, + BHSDCodec codec, int count, int counts[], String[] reference) + throws IOException, Pack200Exception { + if (count == 0) { + return new String[][] { {} }; + } + String[][] result = new String[count][]; + int sum = 0; for (int i = 0; i < count; i++) { - int index = decode[i]; + result[i] = new String[counts[i]]; + sum += counts[i]; + } + // TODO Merge the decode and parsing of a multiple structure into one + String[] result1 = new String[sum]; + int[] decode = decodeBandInt(name, in, codec, sum); + for (int i1 = 0; i1 < sum; i1++) { + int index = decode[i1]; if (index < 0 || index >= reference.length) throw new Pack200Exception( "Something has gone wrong during parsing references"); - result[i] = reference[index]; + result1[i1] = reference[index]; } + String[] refs = result1; + // TODO Merge the decode and parsing of a multiple structure into one + int pos = 0; + for (int i = 0; i < count; i++) { + int num = counts[i]; + result[i] = new String[num]; + System.arraycopy(refs, pos, result[i], 0, num); + pos += num; + } return result; } /** + * Helper method to parse count references from in, + * using codec to decode the values as indexes into + * reference (which is populated prior to this call). An + * exception is thrown if a decoded index falls outside the range + * [0..reference.length-1]. + * + * @param name + * TODO + * @param in + * the input stream to read from + * @param codec + * the codec to use for decoding + * @param count + * the number of references to decode + * @param reference + * the array of values to use for the indexes; often + * {@link #cpUTF8} + * + * @throws IOException + * if a problem occurs during reading from the underlying stream + * @throws Pack200Exception + * if a problem occurs with an unexpected value or unsupported + * codec + */ + private String[] parseReferences(String name, InputStream in, + BHSDCodec codec, int count, String[] reference) throws IOException, + Pack200Exception { + return parseReferences(name, in, codec, 1, new int[] { count }, + reference)[0]; + } + + /** * This performs the actual work of parsing against a non-static instance of * Segment. * @@ -1282,7 +1484,6 @@ parseIcBands(in); parseClassBands(in); parseBcBands(in); - // TODO Re-enable these after completing class/bytecode bands parseFileBands(in); } @@ -1321,6 +1522,34 @@ } } + /** + * Sets the major version of this archive. + * + * @param version + * the minor version of the archive + * @throws Pack200Exception + * if the major version is not 150 + */ + private void setArchiveMajorVersion(int version) throws Pack200Exception { + if (version != 150) + throw new Pack200Exception("Invalid segment major version"); + archiveMajor = version; + } + + /** + * Sets the minor version of this archive + * + * @param version + * the minor version of the archive + * @throws Pack200Exception + * if the minor version is not 7 + */ + private void setArchiveMinorVersion(int version) throws Pack200Exception { + if (version != 7) + throw new Pack200Exception("Invalid segment minor version"); + archiveMinor = version; + } + public void setArchiveModtime(long archiveModtime) { this.archiveModtime = archiveModtime; } @@ -1405,43 +1634,89 @@ innerClassCount = (int) value; } + public void setNumberOfFiles(long value) { + numberOfFiles = (int) value; + } + + private void setOptions(SegmentOptions options) { + this.options = options; + } + + public void setSegmentsRemaining(long value) { + segmentsRemaining = (int) value; + } + /** - * Sets the major version of this archive. + * This is only here to provide a mechanism to turn off the warnings (and to + * prevent anyone from accidentally removing them from the file) * - * @param version - * the minor version of the archive - * @throws Pack200Exception - * if the major version is not 150 + * @deprecated this will be deleted in the future, once I've started to use + * them + * */ - private void setArchiveMajorVersion(int version) throws Pack200Exception { - if (version != 150) - throw new Pack200Exception("Invalid segment major version"); - archiveMajor = version; + int shutUpAboutTheStupidNotReadVariablesYetIHaventImplementedIt() { + return archiveMajor + archiveMinor + cpLong.hashCode() + + icName.hashCode() + icOuterClass.hashCode() + + icThisClass.hashCode(); } /** - * Sets the minor version of this archive + * Writes the segment to an output stream. The output stream should be + * pre-buffered for efficiency. Also takes the same input stream for + * reading, since the file bits may not be loaded and thus just copied from + * one stream to another. Doesn't close the output stream when finished, in + * case there are more entries (e.g. further segments) to be written. * - * @param version - * the minor version of the archive + * @param out + * the JarOutputStream to write data to + * @param in + * the same InputStream that was used to parse the segment + * @throws IOException + * if an error occurs whilst reading or writing to the streams * @throws Pack200Exception - * if the minor version is not 7 + * if an error occurs whilst unpacking data */ - private void setArchiveMinorVersion(int version) throws Pack200Exception { - if (version != 7) - throw new Pack200Exception("Invalid segment minor version"); - archiveMinor = version; - } + public void writeJar(JarOutputStream out, InputStream in) + throws IOException, Pack200Exception { + processFileBits(in); + DataOutputStream dos = new DataOutputStream(out); + // out.setLevel(JarEntry.DEFLATED) + // now write the files out + int classNum = 0; + for (int i = 0; i < numberOfFiles; i++) { + String name = fileName[i]; + long modtime = archiveModtime + fileModtime[i]; + boolean deflate = (fileOptions[i] & 1) == 1 + || options.shouldDeflate(); + boolean isClass = (fileOptions[i] & 2) == 2 || name == null + || name.equals(""); + if (isClass) { + // pull from headers + if (name == null || name.equals("")) + name = classThis[classNum] + ".class"; + } + JarEntry entry = new JarEntry(name); + if (deflate) + entry.setMethod(JarEntry.DEFLATED); + entry.setTime(modtime); + out.putNextEntry(entry); - public void setNumberOfFiles(long value) { - numberOfFiles = (int) value; + if (isClass) { + // write to dos + ClassFile classFile = buildClassFile(classNum); + classFile.write(dos); + dos.flush(); + classNum++; + } else { + long size = fileSize[i]; + entry.setSize(size); + // TODO pull from in + byte[] data = fileBits[i]; + out.write(data); + } + } + dos.flush(); + out.finish(); + out.flush(); } - - private void setOptions(SegmentOptions options) { - this.options = options; - } - - public void setSegmentsRemaining(long value) { - segmentsRemaining = (int) value; - } } Index: src/main/java/org/apache/harmony/archive/internal/pack200/ConstantPoolEntry.java =================================================================== --- src/main/java/org/apache/harmony/archive/internal/pack200/ConstantPoolEntry.java (revision 440520) +++ src/main/java/org/apache/harmony/archive/internal/pack200/ConstantPoolEntry.java (working copy) @@ -1,192 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.harmony.archive.internal.pack200; -//NOTE: Do not use generics in this code; it needs to run on JVMs < 1.5 -//NOTE: Do not extract strings as messages; this code is still a work-in-progress -//NOTE: Also, don't get rid of 'else' statements for the hell of it ... -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; - -/** - * @author alex - * - */ -abstract class ConstantPoolEntry extends ClassFileEntry { - static class Class extends ConstantPoolEntry { - private int index; - - public String name; - - private UTF8 utf8; - - Class(String name) { - super(CP_Class); - this.name = name; - this.utf8 = new UTF8(name); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final Class other = (Class) obj; - if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - if (utf8 == null) { - if (other.utf8 != null) - return false; - } else if (!utf8.equals(other.utf8)) - return false; - return true; - } - - protected ClassFileEntry[] getNestedClassFileEntries() { - return new ClassFileEntry[] { utf8, }; - } - - @Override - public int hashCode() { - final int PRIME = 31; - int result = 1; - result = PRIME * result + ((name == null) ? 0 : name.hashCode()); - result = PRIME * result + ((utf8 == null) ? 0 : utf8.hashCode()); - return result; - } - - protected void resolve(ClassConstantPool pool) { - super.resolve(pool); - index = pool.indexOf(utf8); - } - - void writeBody(DataOutputStream dos) throws IOException { - dos.writeShort(index); - } - } - - static class UTF8 extends ConstantPoolEntry { - private String utf8; - - UTF8(String utf8) { - super(CP_UTF8); - this.utf8 = utf8; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final UTF8 other = (UTF8) obj; - if (utf8 == null) { - if (other.utf8 != null) - return false; - } else if (!utf8.equals(other.utf8)) - return false; - return true; - } - - @Override - public int hashCode() { - final int PRIME = 31; - int result = 1; - result = PRIME * result + ((utf8 == null) ? 0 : utf8.hashCode()); - return result; - } - - void writeBody(DataOutputStream dos) throws IOException { - byte[] bytes; - try { - // TODO Check that this is the right UTF-8 for bytes - if (utf8 == null) { - bytes = new byte[0]; - } else { - bytes = utf8.getBytes("UTF-8"); - } - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Couldn't convert string " + utf8 - + " to UTF-8"); - } - dos.writeShort(bytes.length); - dos.write(bytes); - } - - } - - public static final byte CP_Class = 7; - - public static final byte CP_Double = 6; - - public static final byte CP_Fieldref = 9; - - public static final byte CP_Float = 4; - - public static final byte CP_Integer = 3; - - /* - * class MemberRef extends ConstantPoolEntry { private int index; - * - * Class(String name) { super(CP_Class); index = pool.indexOf(name); } - * - * void writeBody(DataOutputStream dos) throws IOException { - * dos.writeShort(index); } - * } - */ - - public static final byte CP_InterfaceMethodref = 11; - - public static final byte CP_Long = 5; - - public static final byte CP_Methodref = 10; - - public static final byte CP_NameAndType = 12; - - public static final byte CP_String = 8; - - public static final byte CP_UTF8 = 1; - - byte tag; - - ConstantPoolEntry(byte tag) { - this.tag = tag; - } - - public abstract boolean equals(Object obj); - - public byte getTag() { - return tag; - } - - public abstract int hashCode(); - - public void write(DataOutputStream dos) throws IOException { - dos.writeByte(tag); - writeBody(dos); - } - - abstract void writeBody(DataOutputStream dos) throws IOException; -} Index: src/main/java/org/apache/harmony/archive/internal/pack200/ClassFile.java =================================================================== --- src/main/java/org/apache/harmony/archive/internal/pack200/ClassFile.java (revision 440520) +++ src/main/java/org/apache/harmony/archive/internal/pack200/ClassFile.java (working copy) @@ -1,72 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.harmony.archive.internal.pack200; -//NOTE: Do not use generics in this code; it needs to run on JVMs < 1.5 -//NOTE: Do not extract strings as messages; this code is still a work-in-progress -//NOTE: Also, don't get rid of 'else' statements for the hell of it ... -import java.io.DataOutputStream; -import java.io.IOException; - -import org.apache.harmony.archive.internal.pack200.ClassFileEntry.Attribute; - -public class ClassFile { - int major; - int minor; - private int magic = 0xCAFEBABE; - ClassConstantPool pool = new ClassConstantPool(); - int accessFlags; - int thisClass; - int superClass; - int[] interfaces; - int[] fields; - int[] methods; - Attribute[] attributes; - public void write(DataOutputStream dos) throws IOException { - dos.writeInt(magic); - dos.writeShort(minor); - dos.writeShort(major); - dos.writeShort(pool.size()+1); - for(int i=1;i<=pool.size();i++) { - ConstantPoolEntry entry; - (entry = (ConstantPoolEntry)pool.get(i)).write(dos); - // Doubles and longs take up two spaces in the pool, but only one gets written - if (entry.getTag() == ConstantPoolEntry.CP_Double || entry.getTag() == ConstantPoolEntry.CP_Long) - i++; - }; - dos.writeShort(accessFlags); - dos.writeShort(thisClass); - dos.writeShort(superClass); - dos.writeShort(interfaces.length); - for(int i=0;i