commit 57d03126b86e4583d0a152300b5d1c0da2607d58 Author: Mark Hindess Date: Mon Mar 22 20:24:33 2010 +0000 Copy classlib to federated build. git-svn-id: https://svn.apache.org/repos/asf/harmony/enhanced/trunk@926318 13f79535-47bb-0310-9956-ffa450edef68 diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..7e9a2ed --- /dev/null +++ b/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..3fdb7ac --- /dev/null +++ b/.project @@ -0,0 +1,28 @@ + + + pack200 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..8aa4423 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Tue Jan 09 09:46:02 GMT 2007 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.4 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning +org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning +org.eclipse.jdt.core.compiler.source=1.3 diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..5ddd4ac --- /dev/null +++ b/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,50 @@ +#Sun Feb 11 23:16:33 GMT 2007 +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.format_source_code=true +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=false +sp_cleanup.remove_trailing_whitespaces_ignore_empty=true +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=true +sp_cleanup.sort_members_all=true +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000..ad8383f --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,25 @@ +Manifest-Version: 1.0 +Specification-Title: Java Platform API Specification +Specification-Version: 1.5 +Implementation-Title: Apache Harmony +Implementation-Vendor: The Apache Software Foundation +Implementation-Vendor-Id: org.apache.harmony +Implementation-URL: http://harmony.apache.org +Bundle-ManifestVersion: 2 +Bundle-Name: Harmony Pack200 +Bundle-SymbolicName: org.apache.harmony.pack200 +Bundle-Version: 1.0.0 +Bundle-ClassPath: . +Eclipse-JREBundle: true +Import-Package: java.io;resolution:=optional, + java.lang;resolution:=optional, + java.util;resolution:=optional, + java.util.jar;resolution:=optional, + org.objectweb.asm +Export-Package: org.apache.harmony.pack200, + org.apache.harmony.unpack200, + org.apache.harmony.unpack200.bytecode, + org.apache.harmony.unpack200.bytecode.forms +Bundle-RequiredExecutionEnvironment: J2SE-1.4, + J2SE-1.5 + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..c405e15 --- /dev/null +++ b/README.txt @@ -0,0 +1,5 @@ +The java5 source directory are files that need to be compiled under Java 5. +Currently, they're not visible in the Eclipse project. +At build time, the contents of the src/main/java5 project are be added to the +build (with a 1.5 target) and the src/main/java should be compiled with a +1.4 target. diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..898d80e --- /dev/null +++ b/build.xml @@ -0,0 +1,200 @@ + + + + + Build for PACK200 component + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/depends/manifests/asm-3.1/META-INF/MANIFEST.MF b/depends/manifests/asm-3.1/META-INF/MANIFEST.MF new file mode 100644 index 0000000..e10a573 --- /dev/null +++ b/depends/manifests/asm-3.1/META-INF/MANIFEST.MF @@ -0,0 +1,10 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: ASM +Bundle-SymbolicName: org.objectweb.asm;singleton:=true +Bundle-Version: 3.1 +Bundle-ClassPath: asm-3.1.jar +Eclipse-JREBundle: true +Import-Package: java.io, + java.lang +Export-Package: org.objectweb.asm, org.objectweb.asm.signature diff --git a/make/depends.properties b/make/depends.properties new file mode 100644 index 0000000..636c440 --- /dev/null +++ b/make/depends.properties @@ -0,0 +1,21 @@ +# 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. + +depends.jars=${depends.dir}/jars +asm.ver=asm-3.1 +asm.dir=${depends.jars}/${asm.ver} +asm.jar=${asm.dir}/${asm.ver}.jar +asm.url=http://download.forge.objectweb.org/asm/${asm.ver}.jar +asm.md5=4fbe0fd975ecc71480846ce272b483b0 diff --git a/make/exclude.common b/make/exclude.common new file mode 100644 index 0000000..e69de29 diff --git a/make/exclude.linux.x86.drl b/make/exclude.linux.x86.drl new file mode 100644 index 0000000..e69de29 diff --git a/make/exclude.linux.x86.ibm b/make/exclude.linux.x86.ibm new file mode 100644 index 0000000..e69de29 diff --git a/make/exclude.linux.x86_64.drl b/make/exclude.linux.x86_64.drl new file mode 100644 index 0000000..e69de29 diff --git a/make/exclude.linux.x86_64.ibm b/make/exclude.linux.x86_64.ibm new file mode 100644 index 0000000..e69de29 diff --git a/make/exclude.windows.x86.drl b/make/exclude.windows.x86.drl new file mode 100644 index 0000000..e69de29 diff --git a/make/exclude.windows.x86.ibm b/make/exclude.windows.x86.ibm new file mode 100644 index 0000000..e69de29 diff --git a/make/exclude.windows.x86_64.drl b/make/exclude.windows.x86_64.drl new file mode 100644 index 0000000..e69de29 diff --git a/make/findbugs-exclude-filter.xml b/make/findbugs-exclude-filter.xml new file mode 100644 index 0000000..e192978 --- /dev/null +++ b/make/findbugs-exclude-filter.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/make/run-test.xml b/make/run-test.xml new file mode 100644 index 0000000..8268be3 --- /dev/null +++ b/make/run-test.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/apache/harmony/pack200/Archive.java b/src/main/java/org/apache/harmony/pack200/Archive.java new file mode 100644 index 0000000..89b996e --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/Archive.java @@ -0,0 +1,328 @@ +/* + * 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.pack200; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * Archive is the main entry point to pack200 and represents a packed archive. + * An archive is constructed with either a JarInputStream and an output stream + * or a JarFile as input and an OutputStream. Options can be set, then + * pack() is called, to pack the Jar file into a pack200 archive. + */ +public class Archive { + + private final JarInputStream jarInputStream; + private final OutputStream outputStream; + private JarFile jarFile; + private long currentSegmentSize; + private final PackingOptions options; + + /** + * Creates an Archive with streams for the input and output. + * + * @param inputStream + * @param outputStream + * @param options - packing options (if null then defaults are used) + * @throws IOException + */ + public Archive(JarInputStream inputStream, OutputStream outputStream, + PackingOptions options) throws IOException { + jarInputStream = inputStream; + if(options == null) { + // use all defaults + options = new PackingOptions(); + } + this.options = options; + if (options.isGzip()) { + outputStream = new GZIPOutputStream(outputStream); + } + this.outputStream = new BufferedOutputStream(outputStream); + PackingUtils.config(options); + } + + /** + * Creates an Archive with the given input file and a stream for the output + * + * @param jarFile - the input file + * @param outputStream + * @param options - packing options (if null then defaults are used) + * @throws IOException + */ + public Archive(JarFile jarFile, OutputStream outputStream, + PackingOptions options) throws IOException { + if(options == null) { // use all defaults + options = new PackingOptions(); + } + this.options = options; + if (options.isGzip()) { + outputStream = new GZIPOutputStream(outputStream); + } + this.outputStream = new BufferedOutputStream(outputStream); + this.jarFile = jarFile; + jarInputStream = null; + PackingUtils.config(options); + } + + /** + * Pack the archive + * @throws Pack200Exception + * @throws IOException + */ + public void pack() throws Pack200Exception, IOException { + if (0 == options.getEffort()) { + doZeroEffortPack(); + } else { + doNormalPack(); + } + } + + private void doZeroEffortPack() throws IOException, Pack200Exception { + PackingUtils.log("Start to perform a zero-effort packing"); + if (jarInputStream != null) { + PackingUtils.copyThroughJar(jarInputStream, outputStream); + } else { + PackingUtils.copyThroughJar(jarFile, outputStream); + } + } + + private void doNormalPack() throws IOException, Pack200Exception { + PackingUtils.log("Start to perform a normal packing"); + List packingFileList; + if (jarInputStream != null) { + packingFileList = PackingUtils.getPackingFileListFromJar( + jarInputStream, options.isKeepFileOrder()); + } else { + packingFileList = PackingUtils.getPackingFileListFromJar(jarFile, + options.isKeepFileOrder()); + } + + List segmentUnitList = splitIntoSegments(packingFileList); + int previousByteAmount = 0; + int packedByteAmount = 0; + + int segmentSize = segmentUnitList.size(); + SegmentUnit segmentUnit; + for (int index = 0; index < segmentSize; index++) { + segmentUnit = (SegmentUnit) segmentUnitList.get(index); + new Segment().pack(segmentUnit, outputStream, options); + previousByteAmount += segmentUnit.getByteAmount(); + packedByteAmount += segmentUnit.getPackedByteAmount(); + } + + PackingUtils.log("Total: Packed " + previousByteAmount + + " input bytes of " + packingFileList.size() + " files into " + + packedByteAmount + " bytes in " + segmentSize + " segments"); + + outputStream.close(); + } + + private List splitIntoSegments(List packingFileList) throws IOException, + Pack200Exception { + List segmentUnitList = new ArrayList(); + List classes = new ArrayList(); + List files = new ArrayList(); + long segmentLimit = options.getSegmentLimit(); + + int size = packingFileList.size(); + PackingFile packingFile; + for (int index = 0; index < size; index++) { + packingFile = (PackingFile) packingFileList.get(index); + if (!addJarEntry(packingFile, classes, files)) { + // not added because segment has reached maximum size + segmentUnitList.add(new SegmentUnit(classes, files)); + classes = new ArrayList(); + files = new ArrayList(); + currentSegmentSize = 0; + // add the jar to a new segment + addJarEntry(packingFile, classes, files); + // ignore the size of first entry for compatibility with RI + currentSegmentSize = 0; + } else if (segmentLimit == 0 && estimateSize(packingFile) > 0) { + // create a new segment for each class unless size is 0 + segmentUnitList.add(new SegmentUnit(classes, files)); + classes = new ArrayList(); + files = new ArrayList(); + } + } + if (classes.size() > 0 && files.size() > 0) { + segmentUnitList.add(new SegmentUnit(classes, files)); + } + return segmentUnitList; + } + + private boolean addJarEntry(PackingFile packingFile, List javaClasses, + List files) throws IOException, Pack200Exception { + long segmentLimit = options.getSegmentLimit(); + if (segmentLimit != -1 && segmentLimit != 0) { + // -1 is a special case where only one segment is created and + // 0 is a special case where one segment is created for each file + // except for files in "META-INF" + long packedSize = estimateSize(packingFile); + if (packedSize + currentSegmentSize > segmentLimit + && currentSegmentSize > 0) { + // don't add this JarEntry to the current segment + return false; + } else { + // do add this JarEntry + currentSegmentSize += packedSize; + } + } + + String name = packingFile.getName(); + if (name.endsWith(".class") && !options.isPassFile(name)) { + Pack200ClassReader classParser = new Pack200ClassReader( + packingFile.contents); + classParser.setFileName(name); + javaClasses.add(classParser); + packingFile.contents = new byte[0]; + } + files.add(packingFile); + return true; + } + + private long estimateSize(PackingFile packingFile) { + // The heuristic used here is for compatibility with the RI and should + // not be changed + String name = packingFile.getName(); + if (name.startsWith("META-INF") || name.startsWith("/META-INF")) { + return 0; + } else { + long fileSize = packingFile.contents.length; + if (fileSize < 0) { + fileSize = 0; + } + return name.length() + fileSize + 5; + } + } + + static class SegmentUnit { + + private List classList; + + private List fileList; + + private int byteAmount = 0; + + private int packedByteAmount = 0; + + public SegmentUnit(List classes, List files) { + classList = classes; + fileList = files; + + // Calculate the amount of bytes in classes and files before packing + Pack200ClassReader classReader; + for (Iterator iterator = classList.iterator(); iterator.hasNext();) { + classReader = (Pack200ClassReader) iterator.next(); + byteAmount += classReader.b.length; + } + + PackingFile file; + for (Iterator iterator = fileList.iterator(); iterator.hasNext();) { + file = (PackingFile) iterator.next(); + byteAmount += file.contents.length; + } + } + + public List getClassList() { + return classList; + } + + public int classListSize() { + return classList.size(); + } + + public int fileListSize() { + return fileList.size(); + } + + public List getFileList() { + return fileList; + } + + public int getByteAmount() { + return byteAmount; + } + + public int getPackedByteAmount() { + return packedByteAmount; + } + + public void addPackedByteAmount(int amount) { + packedByteAmount += amount; + } + } + + static class PackingFile { + + private final String name; + private byte[] contents; + private final long modtime; + private final boolean deflateHint; + private final boolean isDirectory; + + public PackingFile(String name, byte[] contents, long modtime) { + this.name = name; + this.contents = contents; + this.modtime = modtime; + deflateHint = false; + isDirectory = false; + } + + public PackingFile(byte[] bytes, JarEntry jarEntry) { + name = jarEntry.getName(); + contents = bytes; + modtime = jarEntry.getTime(); + deflateHint = jarEntry.getMethod() == JarEntry.DEFLATED; + isDirectory = jarEntry.isDirectory(); + } + + public byte[] getContents() { + return contents; + } + + public String getName() { + return name; + } + + public long getModtime() { + return modtime; + } + + public void setContents(byte[] contents) { + this.contents = contents; + } + + public boolean isDefalteHint() { + return deflateHint; + } + + public boolean isDirectory(){ + return isDirectory; + } + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/AttributeDefinitionBands.java b/src/main/java/org/apache/harmony/pack200/AttributeDefinitionBands.java new file mode 100644 index 0000000..ca30d03 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/AttributeDefinitionBands.java @@ -0,0 +1,258 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.objectweb.asm.Attribute; + +/** + * Attribute Definition bands define how any unknown attributes should be read + * by the decompressor. + */ +public class AttributeDefinitionBands extends BandSet { + + public static final int CONTEXT_CLASS = 0; + public static final int CONTEXT_CODE = 3; + public static final int CONTEXT_FIELD = 1; + public static final int CONTEXT_METHOD = 2; + + private final List classAttributeLayouts = new ArrayList(); + private final List methodAttributeLayouts = new ArrayList(); + private final List fieldAttributeLayouts = new ArrayList(); + private final List codeAttributeLayouts = new ArrayList(); + + private final List attributeDefinitions = new ArrayList(); + + private final CpBands cpBands; + private final Segment segment; + + public AttributeDefinitionBands(Segment segment, int effort, + Attribute[] attributePrototypes) { + super(effort, segment.getSegmentHeader()); + this.cpBands = segment.getCpBands(); + this.segment = segment; + Map classLayouts = new HashMap(); + Map methodLayouts = new HashMap(); + Map fieldLayouts = new HashMap(); + Map codeLayouts = new HashMap(); + + for (int i = 0; i < attributePrototypes.length; i++) { + NewAttribute newAttribute = (NewAttribute) attributePrototypes[i]; + if (newAttribute.isContextClass()) { + classLayouts.put(newAttribute.type, newAttribute.getLayout()); + } + if (newAttribute.isContextMethod()) { + methodLayouts.put(newAttribute.type, newAttribute.getLayout()); + } + if (newAttribute.isContextField()) { + fieldLayouts.put(newAttribute.type, newAttribute.getLayout()); + } + if (newAttribute.isContextCode()) { + codeLayouts.put(newAttribute.type, newAttribute.getLayout()); + } + } + if (classLayouts.keySet().size() > 7) { + segmentHeader.setHave_class_flags_hi(true); + } + if (methodLayouts.keySet().size() > 6) { + segmentHeader.setHave_method_flags_hi(true); + } + if (fieldLayouts.keySet().size() > 10) { + segmentHeader.setHave_field_flags_hi(true); + } + if (codeLayouts.keySet().size() > 15) { + segmentHeader.setHave_code_flags_hi(true); + } + int[] availableClassIndices = new int[] { 25, 26, 27, 28, 29, 30, 31 }; + if (classLayouts.size() > 7) { + availableClassIndices = addHighIndices(availableClassIndices); + } + addAttributeDefinitions(classLayouts, availableClassIndices, + CONTEXT_CLASS); + int[] availableMethodIndices = new int[] { 26, 27, 28, 29, 30, 31 }; + if (methodAttributeLayouts.size() > 6) { + availableMethodIndices = addHighIndices(availableMethodIndices); + } + addAttributeDefinitions(methodLayouts, availableMethodIndices, + CONTEXT_METHOD); + int[] availableFieldIndices = new int[] { 18, 23, 24, 25, 26, 27, 28, + 29, 30, 31 }; + if (fieldAttributeLayouts.size() > 10) { + availableFieldIndices = addHighIndices(availableFieldIndices); + } + addAttributeDefinitions(fieldLayouts, availableFieldIndices, + CONTEXT_FIELD); + int[] availableCodeIndices = new int[] { 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31 }; + if (codeAttributeLayouts.size() > 15) { + availableCodeIndices = addHighIndices(availableCodeIndices); + } + addAttributeDefinitions(codeLayouts, availableCodeIndices, CONTEXT_CODE); + } + + /** + * All input classes for the segment have now been read in, so this method + * is called so that this class can calculate/complete anything it could not + * do while classes were being read. + */ + public void finaliseBands() { + addSyntheticDefinitions(); + segmentHeader.setAttribute_definition_count(attributeDefinitions.size()); + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + PackingUtils.log("Writing attribute definition bands..."); + int[] attributeDefinitionHeader = new int[attributeDefinitions.size()]; + int[] attributeDefinitionName = new int[attributeDefinitions.size()]; + int[] attributeDefinitionLayout = new int[attributeDefinitions.size()]; + for (int i = 0; i < attributeDefinitionLayout.length; i++) { + AttributeDefinition def = (AttributeDefinition) attributeDefinitions + .get(i); + attributeDefinitionHeader[i] = def.contextType + | (def.index + 1 << 2); + attributeDefinitionName[i] = def.name.getIndex(); + attributeDefinitionLayout[i] = def.layout.getIndex(); + } + + byte[] encodedBand = encodeBandInt("attributeDefinitionHeader", + attributeDefinitionHeader, Codec.BYTE1); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from attributeDefinitionHeader[" + + attributeDefinitionHeader.length + "]"); + + encodedBand = encodeBandInt("attributeDefinitionName", + attributeDefinitionName, Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from attributeDefinitionName[" + + attributeDefinitionName.length + "]"); + + encodedBand = encodeBandInt("attributeDefinitionLayout", + attributeDefinitionLayout, Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from attributeDefinitionLayout[" + + attributeDefinitionLayout.length + "]"); + } + + private void addSyntheticDefinitions() { + boolean anySytheticClasses = segment.getClassBands() + .isAnySyntheticClasses(); + boolean anySyntheticMethods = segment.getClassBands() + .isAnySyntheticMethods(); + boolean anySyntheticFields = segment.getClassBands() + .isAnySyntheticFields(); + if (anySytheticClasses || anySyntheticMethods || anySyntheticFields) { + CPUTF8 syntheticUTF = cpBands.getCPUtf8("Synthetic"); + CPUTF8 emptyUTF = cpBands.getCPUtf8(""); + if (anySytheticClasses) { + attributeDefinitions.add(new AttributeDefinition(12, + CONTEXT_CLASS, syntheticUTF, emptyUTF)); + } + if (anySyntheticMethods) { + attributeDefinitions.add(new AttributeDefinition(12, + CONTEXT_METHOD, syntheticUTF, emptyUTF)); + } + if (anySyntheticFields) { + attributeDefinitions.add(new AttributeDefinition(12, + CONTEXT_FIELD, syntheticUTF, emptyUTF)); + } + } + } + + private int[] addHighIndices(int[] availableIndices) { + int[] temp = new int[availableIndices.length + 32]; + for (int i = 0; i < availableIndices.length; i++) { + temp[i] = availableIndices[i]; + } + int j = 32; + for (int i = availableIndices.length; i < temp.length; i++) { + temp[i] = j; + j++; + } + return temp; + } + + private void addAttributeDefinitions(Map layouts, int[] availableIndices, + int contextType) { + int i = 0; + for (Iterator iterator = layouts.keySet().iterator(); iterator + .hasNext();) { + String name = (String) iterator.next(); + String layout = (String) layouts.get(name); + int index = availableIndices[i]; + AttributeDefinition definition = new AttributeDefinition(index, + contextType, cpBands.getCPUtf8(name), cpBands + .getCPUtf8(layout)); + attributeDefinitions.add(definition); + switch (contextType) { + case CONTEXT_CLASS: + classAttributeLayouts.add(definition); + break; + case CONTEXT_METHOD: + methodAttributeLayouts.add(definition); + break; + case CONTEXT_FIELD: + fieldAttributeLayouts.add(definition); + break; + case CONTEXT_CODE: + codeAttributeLayouts.add(definition); + } + } + } + + public List getClassAttributeLayouts() { + return classAttributeLayouts; + } + + public List getMethodAttributeLayouts() { + return methodAttributeLayouts; + } + + public List getFieldAttributeLayouts() { + return fieldAttributeLayouts; + } + + public List getCodeAttributeLayouts() { + return codeAttributeLayouts; + } + + public static class AttributeDefinition { + + public int index; + public int contextType; + public CPUTF8 name; + public CPUTF8 layout; + + public AttributeDefinition(int index, int contextType, CPUTF8 name, + CPUTF8 layout) { + this.index = index; + this.contextType = contextType; + this.name = name; + this.layout = layout; + } + + } +} diff --git a/src/main/java/org/apache/harmony/pack200/BHSDCodec.java b/src/main/java/org/apache/harmony/pack200/BHSDCodec.java new file mode 100644 index 0000000..8836098 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/BHSDCodec.java @@ -0,0 +1,516 @@ +/* + * 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.pack200; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + + +/** + * A BHSD codec is a means of encoding integer values as a sequence of bytes or + * vice versa using a specified "BHSD" encoding mechanism. It uses a + * variable-length encoding and a modified sign representation such that small + * numbers are represented as a single byte, whilst larger numbers take more + * bytes to encode. The number may be signed or unsigned; if it is unsigned, it + * can be weighted towards positive numbers or equally distributed using a one's + * complement. The Codec also supports delta coding, where a sequence of numbers + * is represented as a series of first-order differences. So a delta encoding of + * the integers [1..10] would be represented as a sequence of 10x1s. This allows + * the absolute value of a coded integer to fall outside of the 'small number' + * range, whilst still being encoded as a single byte. + * + * A BHSD codec is configured with four parameters: + *
+ *
B
+ *
The maximum number of bytes that each value is encoded as. B must be a + * value between [1..5]. For a pass-through coding (where each byte is encoded + * as itself, aka {@link #BYTE1}, B is 1 (each byte takes a maximum of 1 byte).
+ *
H
+ *
The radix of the integer. Values are defined as a sequence of values, + * where value n is multiplied by H^n. + * So the number 1234 may be represented as the sequence 4 3 2 1 with a radix + * (H) of 10. Note that other permutations are also possible; 43 2 1 will also + * encode 1234. The co-parameter L is defined as 256-H. This is important + * because only the last value in a sequence may be < L; all prior values + * must be > L.
+ *
S
+ *
Whether the codec represents signed values (or not). This may have 3 + * values; 0 (unsigned), 1 (signed, one's complement) or 2 (signed, two's + * complement)
+ *
D
+ *
Whether the codec represents a delta encoding. This may be 0 (no delta) + * or 1 (delta encoding). A delta encoding of 1 indicates that values are + * cumulative; a sequence of 1 1 1 1 1 will represent the + * sequence 1 2 3 4 5. For this reason, the codec supports two + * variants of decode; one {@link #decode(InputStream, long) with} and one + * {@link #decode(InputStream) without} a last parameter. If the + * codec is a non-delta encoding, then the value is ignored if passed. If the + * codec is a delta encoding, it is a run-time error to call the value without + * the extra parameter, and the previous value should be returned. (It was + * designed this way to support multi-threaded access without requiring a new + * instance of the Codec to be cloned for each use.) + *
+ *
+ * + * Codecs are notated as (B,H,S,D) and either D or S,D may be omitted if zero. + * Thus {@link #BYTE1} is denoted (1,256,0,0) or (1,256). The + * {@link #toString()} method prints out the condensed form of the encoding. + * Often, the last character in the name ({@link #BYTE1}, {@link #UNSIGNED5}) + * gives a clue as to the B value. Those that start with U ({@link #UDELTA5}, + * {@link #UNSIGNED5}) are unsigned; otherwise, in most cases, they are signed. + * The presence of the word Delta ({@link #DELTA5}, {@link #UDELTA5}) + * indicates a delta encoding is used. + * + */ +public final class BHSDCodec extends Codec { + + /** + * The maximum number of bytes in each coding word + */ + private final int b; + + /** + * Whether delta encoding is used (0=false,1=true) + */ + private final int d; + + /** + * The radix of the encoding + */ + private final int h; + + /** + * The co-parameter of h; 256-h + */ + private final int l; + + /** + * Represents signed numbers or not (0=unsigned,1/2=signed) + */ + private final int s; + + private long cardinality; + + private final long smallest; + + private final long largest; + + /** + * radix^i powers + */ + private final long[] powers; + + /** + * Constructs an unsigned, non-delta Codec with the given B and H values. + * + * @param b + * the maximum number of bytes that a value can be encoded as + * [1..5] + * @param h + * the radix of the encoding [1..256] + */ + public BHSDCodec(int b, int h) { + this(b, h, 0, 0); + } + + /** + * Constructs a non-delta Codec with the given B, H and S values. + * + * @param b + * the maximum number of bytes that a value can be encoded as + * [1..5] + * @param h + * the radix of the encoding [1..256] + * @param s + * whether the encoding represents signed numbers (s=0 is + * unsigned; s=1 is signed with 1s complement; s=2 is signed with ?) + */ + public BHSDCodec(int b, int h, int s) { + this(b, h, s, 0); + } + + /** + * Constructs a Codec with the given B, H, S and D values. + * + * @param b + * the maximum number of bytes that a value can be encoded as + * [1..5] + * @param h + * the radix of the encoding [1..256] + * @param s + * whether the encoding represents signed numbers (s=0 is + * unsigned; s=1 is signed with 1s complement; s=2 is signed with ?) + * @param d + * whether this is a delta encoding (d=0 is non-delta; d=1 is + * delta) + */ + public BHSDCodec(int b, int h, int s, int d) { + if (b < 1 || b > 5) { + throw new IllegalArgumentException("1<=b<=5"); + } + if (h < 1 || h > 256) { + throw new IllegalArgumentException("1<=h<=256"); + } + if (s < 0 || s > 2) { + throw new IllegalArgumentException("0<=s<=2"); + } + if (d < 0 || d > 1) { + throw new IllegalArgumentException("0<=d<=1"); + } + if (b == 1 && h != 256) { + throw new IllegalArgumentException("b=1 -> h=256"); + } + if (h == 256 && b == 5) { + throw new IllegalArgumentException("h=256 -> b!=5"); + } + this.b = b; + this.h = h; + this.s = s; + this.d = d; + this.l = 256 - h; + if (h == 1) { + cardinality = b * 255 + 1; + } else { + cardinality = (long) ((long) (l * (1 - Math.pow(h, b)) / (1 - h)) + Math + .pow(h, b)); + } + smallest = calculateSmallest(); + largest = calculateLargest(); + + powers = new long[b]; + for(int c = 0; c < b; c++) { + powers[c] = (long)Math.pow(h, c); + } + } + + /** + * Returns the cardinality of this codec; that is, the number of distinct + * values that it can contain. + * + * @return the cardinality of this codec + */ + public long cardinality() { + return cardinality; + } + + public int decode(InputStream in) throws IOException, Pack200Exception { + if (d != 0) { + throw new Pack200Exception( + "Delta encoding used without passing in last value; this is a coding error"); + } + return decode(in, 0); + } + + public int decode(InputStream in, long last) throws IOException, + Pack200Exception { + int n = 0; + long z = 0; + long x = 0; + + do { + x = in.read(); + lastBandLength ++; + z += x * powers[n]; + n++; + } while (x >= l && n < b); + + if (x == -1) { + throw new EOFException("End of stream reached whilst decoding"); + } + + if (isSigned()) { + int u = ((1 << s) - 1); + if ((z & u) == u) { + z = z >>> s ^ -1L; + } else { + z = z - (z >>> s); + } + } + // This algorithm does the same thing, but is probably slower. Leaving + // in for now for readability + // if(isSigned()) { + // long u = z; + // long twoPowS = (long)Math.pow(2, s); + // double twoPowSMinusOne = twoPowS-1; + // if(u % twoPowS < twoPowSMinusOne) { + // if(cardinality < Math.pow(2, 32)) { + // z = (long) (u - (Math.floor(u/ twoPowS))); + // } else { + // z = cast32((long) (u - (Math.floor(u/ twoPowS)))); + // } + // } else { + // z = (long) (-Math.floor(u/ twoPowS) - 1); + // } + // } + if (isDelta()) { + z += last; + } + return (int)z; + } + + public int[] decodeInts(int n, InputStream in) throws IOException, + Pack200Exception { + int[] band = super.decodeInts(n, in); + if (isDelta()) { + for (int i = 0; i < band.length; i++) { + while (band[i] > largest) { + band[i] -= cardinality; + } + while (band[i] < smallest) { + band[i] += cardinality; + } + } + } + return band; + } + + public int[] decodeInts(int n, InputStream in, int firstValue) + throws IOException, Pack200Exception { + int[] band = super.decodeInts(n, in, firstValue); + if (isDelta()) { + for (int i = 0; i < band.length; i++) { + while (band[i] > largest) { + band[i] -= cardinality; + } + while (band[i] < smallest) { + band[i] += cardinality; + } + } + } + return band; + } + + // private long cast32(long u) { + // u = (long) ((long) ((u + Math.pow(2, 31)) % Math.pow(2, 32)) - + // Math.pow(2, 31)); + // return u; + // } + + /** + * True if this encoding can code the given value + * + * @param value + * the value to check + * @return true if the encoding can encode this value + */ + public boolean encodes(long value) { + return value >= smallest && value <= largest; + } + + public byte[] encode(int value, int last) throws Pack200Exception { + if(!encodes(value)) { + throw new Pack200Exception("The codec " + toString() + + " does not encode the value " + value); + } + + long z = value; + if (isDelta()) { + z -= last; + } + if (isSigned()) { + if(z < Integer.MIN_VALUE) { + z += 4294967296L; + } else if (z > Integer.MAX_VALUE) { + z -= 4294967296L; + } + if (z < 0) { + z = (-z << s) - 1; + } else { + if (s == 1) { + z = z << s; + } else { + z += (z - z % 3) / 3; + } + } + } else { + if (z < 0) { + // Need to use integer overflow here to represent negatives. + if (cardinality < 4294967296L) { + z += cardinality; + } else { + z += 4294967296L; // this value is equal to (1 << 32). + } + } + } + if (z < 0) { + throw new Pack200Exception("unable to encode"); + } + + List byteList = new ArrayList(); + for (int n = 0; n < b; n++) { + long byteN; + if (z < l) { + byteN = z; + } else { + byteN = z % h; + while (byteN < l) { + byteN += h; + } + } + byteList.add(new Byte((byte) byteN)); + if (byteN < l) { + break; + } + z -= byteN; + z /= h; + } + byte[] bytes = new byte[byteList.size()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = ((Byte) byteList.get(i)).byteValue(); + } + return bytes; + } + + public byte[] encode(int value) throws Pack200Exception { + return encode(value, 0); + } + + /** + * Returns true if this codec is a delta codec + * + * @return true if this codec is a delta codec + */ + public boolean isDelta() { + return d != 0; + } + + /** + * Returns true if this codec is a signed codec + * + * @return true if this codec is a signed codec + */ + public boolean isSigned() { + return s != 0; + } + + /** + * Returns the largest value that this codec can represent. + * + * @return the largest value that this codec can represent. + */ + public long largest() { + return largest; + } + + private long calculateLargest() { + long result; + // TODO This can probably be optimized into a better mathematical + // statement + if (d == 1) { + BHSDCodec bh0 = new BHSDCodec(b, h); + return bh0.largest(); + } else if (s == 0) { + result = cardinality() - 1; + } else if (s == 1) { + result = cardinality() / 2 - 1; + } else if (s == 2) { + result = (3L * cardinality()) / 4 - 1; + } else { + throw new Error("Unknown s value"); + } + return Math.min((s == 0 ? ((long) Integer.MAX_VALUE) << 1 + : Integer.MAX_VALUE) - 1, result); + } + + /** + * Returns the smallest value that this codec can represent. + * + * @return the smallest value that this codec can represent. + */ + public long smallest() { + return smallest; + } + + private long calculateSmallest() { + long result; + if (d == 1 || !isSigned()) { + if (cardinality >= 4294967296L) { // 2^32 + result = Integer.MIN_VALUE; + } else { + result = 0; + } + } else { + result = Math.max(Integer.MIN_VALUE, -cardinality() / (1 << s)); + } + return result; + } + + /** + * Returns the codec in the form (1,256) or (1,64,1,1). Note that trailing + * zero fields are not shown. + */ + public String toString() { + StringBuffer buffer = new StringBuffer(11); + buffer.append('('); + buffer.append(b); + buffer.append(','); + buffer.append(h); + if (s != 0 || d != 0) { + buffer.append(','); + buffer.append(s); + } + if (d != 0) { + buffer.append(','); + buffer.append(d); + } + buffer.append(')'); + return buffer.toString(); + } + + /** + * @return the b + */ + public int getB() { + return b; + } + + /** + * @return the h + */ + public int getH() { + return h; + } + + /** + * @return the s + */ + public int getS() { + return s; + } + + /** + * @return the l + */ + public int getL() { + return l; + } + + public boolean equals(Object o) { + if (o instanceof BHSDCodec) { + BHSDCodec codec = (BHSDCodec) o; + return codec.b == b && codec.h == h && codec.s == s && codec.d == d; + } + return false; + } + + public int hashCode() { + return ((b* 37 + h) * 37 + s) * 37 + d; + } +} diff --git a/src/main/java/org/apache/harmony/pack200/BandSet.java b/src/main/java/org/apache/harmony/pack200/BandSet.java new file mode 100644 index 0000000..a45dd83 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/BandSet.java @@ -0,0 +1,808 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Abstract superclass for a set of bands + */ +public abstract class BandSet { + + protected final SegmentHeader segmentHeader; + final int effort; + + // Minimum size of band for each effort level where we consider alternative codecs + // Note: these values have been tuned - please test carefully if changing them + private static final int[] effortThresholds = new int[] {0, 0, 1000, 500, 100, 100, 100, 100, 100, 0}; + + private long[] canonicalLargest; + private long[] canonicalSmallest; + + /** + * Create a new BandSet + * @param effort - the packing effort to be used (must be 1-9) + * @param header - the segment header + */ + public BandSet(int effort, SegmentHeader header) { + this.effort = effort; + this.segmentHeader = header; + } + + /** + * Write the packed set of bands to the given output stream + * @param out + * @throws IOException + * @throws Pack200Exception + */ + public abstract void pack(OutputStream out) throws IOException, Pack200Exception; + + /** + * Encode a band without considering other Codecs + * @param band - the band + * @param codec - the Codec to use + * @return the encoded band + * @throws Pack200Exception + */ + public byte[] encodeScalar(int[] band, BHSDCodec codec) throws Pack200Exception { + return codec.encode(band); + } + + /** + * Encode a single value with the given Codec + * @param value - the value to encode + * @param codec - Codec to use + * @return the encoded value + * @throws Pack200Exception + */ + public byte[] encodeScalar(int value, BHSDCodec codec) throws Pack200Exception { + return codec.encode(value); + } + + /** + * Encode a band of integers. The default codec may be used, but other + * Codecs are considered if effort is greater than 1. + * + * @param name + * - name of the band (used for debugging) + * @param ints + * - the band + * @param defaultCodec + * - the default Codec + * @return the encoded band + * @throws Pack200Exception + */ + public byte[] encodeBandInt(String name, int[] ints, BHSDCodec defaultCodec) throws Pack200Exception { + byte[] encodedBand = null; + // Useful for debugging +// if(ints.length > 0) { +// System.out.println("encoding " + name + " " + ints.length); +// } + if(effort > 1 && (ints.length >= effortThresholds[effort])) { + BandAnalysisResults results = analyseBand(name, ints, defaultCodec); + Codec betterCodec = results.betterCodec; + encodedBand = results.encodedBand; + if(betterCodec != null) { + if(betterCodec instanceof BHSDCodec) { + int[] specifierBand = CodecEncoding.getSpecifier(betterCodec, defaultCodec); + int specifier = specifierBand[0]; + if(specifierBand.length > 1) { + for (int i = 1; i < specifierBand.length; i++) { + segmentHeader.appendBandCodingSpecifier(specifierBand[i]); + } + } + if(defaultCodec.isSigned()) { + specifier = -1 -specifier; + } else { + specifier = specifier + defaultCodec.getL(); + } + byte[] specifierEncoded = defaultCodec.encode(new int[] {specifier}); + byte[] band = new byte[specifierEncoded.length + encodedBand.length]; + System.arraycopy(specifierEncoded, 0, band, 0, specifierEncoded.length); + System.arraycopy(encodedBand, 0, band, specifierEncoded.length, encodedBand.length); + return band; + } else if (betterCodec instanceof PopulationCodec) { + int[] extraSpecifierInfo = results.extraMetadata; + for (int i = 0; i < extraSpecifierInfo.length; i++) { + segmentHeader.appendBandCodingSpecifier(extraSpecifierInfo[i]); + } + return encodedBand; + } else if (betterCodec instanceof RunCodec) { + + } + } + } + + // If we get here then we've decided to use the default codec. + if(ints.length > 0) { + if(encodedBand == null) { + encodedBand = defaultCodec.encode(ints); + } + int first = ints[0]; + if(defaultCodec.getB() != 1) { + if (defaultCodec.isSigned() && first >= -256 && first <= -1) { + int specifier = -1 - CodecEncoding.getSpecifierForDefaultCodec(defaultCodec); + byte[] specifierEncoded = defaultCodec.encode(new int[] {specifier}); + byte[] band = new byte[specifierEncoded.length + encodedBand.length]; + System.arraycopy(specifierEncoded, 0, band, 0, specifierEncoded.length); + System.arraycopy(encodedBand, 0, band, specifierEncoded.length, encodedBand.length); + return band; + } else if (!defaultCodec.isSigned() && first >= defaultCodec.getL() + && first <= defaultCodec.getL() + 255) { + int specifier = CodecEncoding.getSpecifierForDefaultCodec(defaultCodec) + defaultCodec.getL(); + byte[] specifierEncoded = defaultCodec.encode(new int[] {specifier}); + byte[] band = new byte[specifierEncoded.length + encodedBand.length]; + System.arraycopy(specifierEncoded, 0, band, 0, specifierEncoded.length); + System.arraycopy(encodedBand, 0, band, specifierEncoded.length, encodedBand.length); + return band; + } + } + return encodedBand; + } + return new byte[0]; + } + + private BandAnalysisResults analyseBand(String name, int[] band, + BHSDCodec defaultCodec) throws Pack200Exception { + + BandAnalysisResults results = new BandAnalysisResults(); + + if(canonicalLargest == null) { + canonicalLargest = new long[116]; + canonicalSmallest = new long[116]; + for (int i = 1; i < canonicalLargest.length; i++) { + canonicalLargest[i] = CodecEncoding.getCanonicalCodec(i).largest(); + canonicalSmallest[i] = CodecEncoding.getCanonicalCodec(i).smallest(); + } + } + BandData bandData = new BandData(band); + + // Check that there is a reasonable saving to be made + byte[] encoded = defaultCodec.encode(band); + results.encodedBand = encoded; + + // Note: these values have been tuned - please test carefully if changing them + if(encoded.length <= band.length + 23 - 2*effort) { // TODO: tweak + return results; + } + + // Check if we can use BYTE1 as that's a 1:1 mapping if we can + if(!bandData.anyNegatives() && bandData.largest <= Codec.BYTE1.largest()) { + results.encodedBand = Codec.BYTE1.encode(band) ; + results.betterCodec = Codec.BYTE1; + return results; + } + + // Consider a population codec (but can't be nested) + if(effort > 3 && !name.equals("POPULATION")) { + int numDistinctValues = bandData.numDistinctValues(); + float distinctValuesAsProportion = (float)numDistinctValues / (float)band.length; + + // Note: these values have been tuned - please test carefully if changing them + if(numDistinctValues < 100 || distinctValuesAsProportion < 0.02 || (effort > 6 && distinctValuesAsProportion < 0.04)) { // TODO: tweak + encodeWithPopulationCodec(name, band, defaultCodec, bandData, results); + if(timeToStop(results)) { + return results; + } + } + } + + List codecFamiliesToTry = new ArrayList(); + + // See if the deltas are mainly small increments + if(bandData.mainlyPositiveDeltas() && bandData.mainlySmallDeltas()) { + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs2); + } + + if (bandData.wellCorrelated()) { // Try delta encodings + if (bandData.mainlyPositiveDeltas()) { + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs1); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs3); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs4); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs5); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs1); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs3); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs4); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs5); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs2); + } else { + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs1); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs3); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs2); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs4); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs5); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaSignedCodecs1); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaSignedCodecs2); + } + } else { + if (bandData.anyNegatives()) { + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaSignedCodecs1); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaSignedCodecs2); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs1); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs2); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs3); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs4); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaSignedCodecs5); + } else { + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs1); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs3); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs4); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs5); + codecFamiliesToTry.add(CanonicalCodecFamilies.nonDeltaUnsignedCodecs2); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs1); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs3); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs4); + codecFamiliesToTry.add(CanonicalCodecFamilies.deltaUnsignedCodecs5); + } + } + if(name.equalsIgnoreCase("cpint")) { + System.out.print(""); + } + + for (Iterator iterator = codecFamiliesToTry.iterator(); iterator + .hasNext();) { + BHSDCodec[] family = (BHSDCodec[]) iterator.next(); + tryCodecs(name, band, defaultCodec, bandData, results, encoded, + family); + if (timeToStop(results)) { + break; + } + } + + return results; + } + + private boolean timeToStop(BandAnalysisResults results) { + // if tried more than effort number of codecs for this band then return + // Note: these values have been tuned - please test carefully if changing them + if(effort > 6) { + return results.numCodecsTried >= effort * 2; + } + return results.numCodecsTried >= effort; + // May want to also check how much we've saved if performance needs improving, e.g. saved more than effort*2 % + // || (float)results.saved/(float)results.encodedBand.length > (float)effort * 2/100; + } + + private void tryCodecs(String name, int[] band, BHSDCodec defaultCodec, BandData bandData, + BandAnalysisResults results, byte[] encoded, + BHSDCodec[] potentialCodecs) throws Pack200Exception { + for (int i = 0; i < potentialCodecs.length; i++) { + BHSDCodec potential = potentialCodecs[i]; + if(potential.equals(defaultCodec)) { + return; // don't try codecs with greater cardinality in the same 'family' as the default codec as there won't be any savings + } + if (potential.isDelta()) { + if (potential.largest() >= bandData.largestDelta + && potential.smallest() <= bandData.smallestDelta + && potential.largest() >= bandData.largest + && potential.smallest() <= bandData.smallest) { + // TODO: can represent some negative deltas with overflow + byte[] encoded2 = potential.encode(band); + results.numCodecsTried++; + byte[] specifierEncoded = defaultCodec.encode(CodecEncoding + .getSpecifier(potential, null)); + int saved = encoded.length - encoded2.length + - specifierEncoded.length; + if (saved > results.saved) { + results.betterCodec = potential; + results.encodedBand = encoded2; + results.saved = saved; + } + } + } else if (potential.largest() >= bandData.largest + && potential.smallest() <= bandData.smallest) { + byte[] encoded2 = potential.encode(band); + results.numCodecsTried++; + byte[] specifierEncoded = defaultCodec.encode(CodecEncoding + .getSpecifier(potential, null)); + int saved = encoded.length - encoded2.length + - specifierEncoded.length; + if (saved > results.saved) { + results.betterCodec = potential; + results.encodedBand = encoded2; + results.saved = saved; + } + } + if(timeToStop(results)) { + return; + } + } + } + + /** + * Returns true if the name of the source file can be predicted from the + * class name + * + * @param className + * the class name + * @param sourceFileName + * the source file name + */ + public boolean isPredictableSourceFileName(String className, String sourceFileName) { + if (className.indexOf('.') != -1) { + className = className.substring(className.lastIndexOf('.') + 1); + } + if (className.indexOf('$') != -1) { + className = className.substring(0, className.indexOf('$')); + } + className += ".java"; + return className.equals(sourceFileName); + } + +// This could be useful if further enhancements are done but is not currently used +// +// private void encodeWithRunCodec(String name, int[] band, int index, +// BHSDCodec defaultCodec, BandData bandData, +// BandAnalysisResults results) throws Pack200Exception { +// int[] firstBand = new int[index]; +// int[] secondBand = new int[band.length - index]; +// System.arraycopy(band, 0, firstBand, 0, index); +// System.arraycopy(band, index, secondBand, 0, secondBand.length); +// BandAnalysisResults firstResults = analyseBand(name + "A", firstBand, defaultCodec); +// BandAnalysisResults secondResults = analyseBand(name + "B", secondBand, defaultCodec); +// int specifier = 117; +// byte[] specifierEncoded = defaultCodec.encode(new int[] {specifier}); +// int totalLength = firstResults.encodedBand.length + secondResults.encodedBand.length + specifierEncoded.length + 4; // TODO actual +// if(totalLength < results.encodedBand.length) { +// System.out.println("using run codec"); +// results.saved += results.encodedBand.length - totalLength; +// byte[] encodedBand = new byte[specifierEncoded.length + firstResults.encodedBand.length + secondResults.encodedBand.length]; +// System.arraycopy(specifierEncoded, 0, encodedBand, 0, specifierEncoded.length); +// System.arraycopy(firstResults.encodedBand, 0, encodedBand, specifierEncoded.length, firstResults.encodedBand.length); +// System.arraycopy(secondResults.encodedBand, 0, encodedBand, specifierEncoded.length + firstResults.encodedBand.length, secondResults.encodedBand.length); +// results.encodedBand = encodedBand; +// results.betterCodec = new RunCodec(index, firstResults.betterCodec, secondResults.betterCodec); +// } +// } + + private void encodeWithPopulationCodec(String name, int[] band, + BHSDCodec defaultCodec, BandData bandData, BandAnalysisResults results) throws Pack200Exception { + results.numCodecsTried += 3; // quite a bit more effort to try this codec + final Map distinctValues = bandData.distinctValues; + + List favoured = new ArrayList(); + for (Iterator iterator = distinctValues.keySet().iterator(); iterator + .hasNext();) { + Integer value = (Integer) iterator.next(); + Integer count = (Integer) distinctValues.get(value); + if(count.intValue() > 2 || distinctValues.size() < 256) { // TODO: tweak + favoured.add(value); + } + } + + // Sort the favoured list with the most commonly occurring first + if(distinctValues.size() > 255) { + Collections.sort(favoured, new Comparator() { + public int compare(Object arg0, Object arg1) { + return ((Integer)distinctValues.get(arg1)).compareTo((Integer)distinctValues.get(arg0)); + } + }); + } + + IntList unfavoured = new IntList(); + Map favouredToIndex = new HashMap(); + for (int i = 0; i < favoured.size(); i++) { + Integer value = (Integer) favoured.get(i); + favouredToIndex.put(value, new Integer(i)); + } + + int[] tokens = new int[band.length]; + for (int i = 0; i < band.length; i++) { + Integer favouredIndex = (Integer)favouredToIndex.get(new Integer(band[i])); + if(favouredIndex == null) { + tokens[i] = 0; + unfavoured.add(band[i]); + } else { + tokens[i] = favouredIndex.intValue() + 1; + } + } + favoured.add(favoured.get(favoured.size() - 1)); // repeat last value + int[] favouredBand = integerListToArray(favoured); + int[] unfavouredBand = unfavoured.toArray(); + + // Analyse the three bands to get the best codec + BandAnalysisResults favouredResults = analyseBand("POPULATION", favouredBand, defaultCodec); + BandAnalysisResults unfavouredResults = analyseBand("POPULATION", unfavouredBand, defaultCodec); + + int tdefL = 0; + int l = 0; + Codec tokenCodec = null; + byte[] tokensEncoded; + int k = favoured.size() - 1; + if(k < 256) { + tdefL = 1; + tokensEncoded = Codec.BYTE1.encode(tokens); + } else { + BandAnalysisResults tokenResults = analyseBand("POPULATION", tokens, defaultCodec); + tokenCodec = tokenResults.betterCodec; + tokensEncoded = tokenResults.encodedBand; + if(tokenCodec == null) { + tokenCodec = defaultCodec; + } + l = ((BHSDCodec) tokenCodec).getL(); + int h = ((BHSDCodec) tokenCodec).getH(); + int s = ((BHSDCodec) tokenCodec).getS(); + int b = ((BHSDCodec) tokenCodec).getB(); + int d = ((BHSDCodec) tokenCodec).isDelta() ? 1 : 0; + if(s == 0 && d == 0) { + boolean canUseTDefL = true; + if(b > 1) { + BHSDCodec oneLowerB = new BHSDCodec(b-1, h); + if(oneLowerB.largest() >= k) { + canUseTDefL = false; + } + } + if(canUseTDefL) { + switch (l) { + case 4: + tdefL = 1; + break; + case 8: + tdefL = 2; + break; + case 16: + tdefL = 3; + break; + case 32: + tdefL = 4; + break; + case 64: + tdefL = 5; + break; + case 128: + tdefL = 6; + break; + case 192: + tdefL = 7; + break; + case 224: + tdefL = 8; + break; + case 240: + tdefL = 9; + break; + case 248: + tdefL = 10; + break; + case 252: + tdefL = 11; + break; + } + } + } + } + + byte[] favouredEncoded = favouredResults.encodedBand; + byte[] unfavouredEncoded = unfavouredResults.encodedBand; + Codec favouredCodec = favouredResults.betterCodec; + Codec unfavouredCodec = unfavouredResults.betterCodec; + + int specifier = 141 + (favouredCodec == null ? 1 : 0) + (4 * tdefL) + (unfavouredCodec == null ? 2 : 0); + IntList extraBandMetadata = new IntList(3); + if(favouredCodec != null) { + int[] specifiers = CodecEncoding.getSpecifier(favouredCodec, null); + for (int i = 0; i < specifiers.length; i++) { + extraBandMetadata.add(specifiers[i]); + } + } + if(tdefL == 0) { + int[] specifiers = CodecEncoding.getSpecifier(tokenCodec, null); + for (int i = 0; i < specifiers.length; i++) { + extraBandMetadata.add(specifiers[i]); + } + } + if(unfavouredCodec != null) { + int[] specifiers = CodecEncoding.getSpecifier(unfavouredCodec, null); + for (int i = 0; i < specifiers.length; i++) { + extraBandMetadata.add(specifiers[i]); + } + } + int[] extraMetadata = extraBandMetadata.toArray(); + byte[] extraMetadataEncoded = Codec.UNSIGNED5.encode(extraMetadata); + if(defaultCodec.isSigned()) { + specifier = -1 -specifier; + } else { + specifier = specifier + defaultCodec.getL(); + } + byte[] firstValueEncoded = defaultCodec.encode(new int[] {specifier}); + int totalBandLength = firstValueEncoded.length + favouredEncoded.length + tokensEncoded.length + unfavouredEncoded.length; + + if(totalBandLength + extraMetadataEncoded.length < results.encodedBand.length) { + results.saved += results.encodedBand.length - (totalBandLength + extraMetadataEncoded.length); + byte[] encodedBand = new byte[totalBandLength]; + System.arraycopy(firstValueEncoded, 0, encodedBand, 0, firstValueEncoded.length); + System.arraycopy(favouredEncoded, 0, encodedBand, firstValueEncoded.length, favouredEncoded.length); + System.arraycopy(tokensEncoded, 0, encodedBand, firstValueEncoded.length + favouredEncoded.length, tokensEncoded.length); + System.arraycopy(unfavouredEncoded, 0, encodedBand, firstValueEncoded.length + favouredEncoded.length + tokensEncoded.length, unfavouredEncoded.length); + results.encodedBand = encodedBand; + results.extraMetadata = extraMetadata; + if(l != 0) { + results.betterCodec = new PopulationCodec(favouredCodec, l, unfavouredCodec); + } else { + results.betterCodec = new PopulationCodec(favouredCodec, tokenCodec, unfavouredCodec); + } + } + } + + /** + * Encode a band of longs (values are split into their high and low 32 bits + * and then encoded as two separate bands + * + * @param name + * - name of the band (for debugging purposes) + * @param flags + * - the band + * @param loCodec + * - Codec for the low 32-bits band + * @param hiCodec + * - Codec for the high 32-bits band + * @param haveHiFlags + * - ignores the high band if true as all values would be zero + * @return the encoded band + * @throws Pack200Exception + */ + protected byte[] encodeFlags(String name, long[] flags, BHSDCodec loCodec, BHSDCodec hiCodec, + boolean haveHiFlags) throws Pack200Exception { + if(!haveHiFlags) { + int[] loBits = new int[flags.length]; + for (int i = 0; i < flags.length; i++) { + loBits[i] = (int) flags[i]; + } + return encodeBandInt(name, loBits, loCodec); + } else { + + int[] hiBits = new int[flags.length]; + int[] loBits = new int[flags.length]; + for (int i = 0; i < flags.length; i++) { + long l = flags[i]; + hiBits[i] = (int) (l >> 32); + loBits[i] = (int) l; + } + byte[] hi = encodeBandInt(name, hiBits, hiCodec); + byte[] lo = encodeBandInt(name, loBits, loCodec); + byte[] total = new byte[hi.length + lo.length]; + System.arraycopy(hi, 0, total, 0, hi.length); + System.arraycopy(lo, 0, total, hi.length + 1, lo.length); + return total; + } + } + + /** + * Converts a list of Integers to an int[] array + */ + protected int[] integerListToArray(List integerList) { + int[] array = new int[integerList.size()]; + for (int i = 0; i < array.length; i++) { + array[i] = ((Integer)integerList.get(i)).intValue(); + } + return array; + } + + /** + * Converts a list of Longs to an long[] array + */ + protected long[] longListToArray(List longList) { + long[] array = new long[longList.size()]; + for (int i = 0; i < array.length; i++) { + array[i] = ((Long)longList.get(i)).longValue(); + } + return array; + } + + /** + * Converts a list of ConstantPoolEntrys to an int[] array of their indices + */ + protected int[] cpEntryListToArray(List list) { + int[] array = new int[list.size()]; + for (int i = 0; i < array.length; i++) { + array[i] = ((ConstantPoolEntry)list.get(i)).getIndex(); + if(array[i] < 0) { + throw new RuntimeException("Index should be > 0"); + } + } + return array; + } + + /** + * Converts a list of ConstantPoolEntrys or nulls to an int[] array of their + * indices +1 (or 0 for nulls) + */ + protected int[] cpEntryOrNullListToArray(List theList) { + int[] array = new int[theList.size()]; + for (int j = 0; j < array.length; j++) { + ConstantPoolEntry cpEntry = (ConstantPoolEntry) theList.get(j); + array[j] = cpEntry == null ? 0 : cpEntry.getIndex() + 1; + if(cpEntry != null && cpEntry.getIndex() < 0) { + throw new RuntimeException("Index should be > 0"); + } + } + return array; + } + + protected byte[] encodeFlags(String name, long[][] flags, BHSDCodec loCodec, BHSDCodec hiCodec, + boolean haveHiFlags) throws Pack200Exception { + return encodeFlags(name, flatten(flags), loCodec, hiCodec, haveHiFlags); + } + + /* + * Flatten a 2-dimension array into a 1-dimension array + */ + private long[] flatten(long[][] flags) { + int totalSize = 0; + for (int i = 0; i < flags.length; i++) { + totalSize += flags[i].length; + } + long[] flatArray = new long[totalSize]; + int index = 0; + for (int i = 0; i < flags.length; i++) { + for (int j = 0; j < flags[i].length; j++) { + flatArray[index] = flags[i][j]; + index++; + } + } + return flatArray; + } + + /** + * BandData represents information about a band, e.g. largest value etc + * and is used in the heuristics that calculate whether an alternative + * Codec could make the encoded band smaller. + */ + public class BandData { + + private final int[] band; + private int smallest = Integer.MAX_VALUE; + private int largest = Integer.MIN_VALUE; + private int smallestDelta; + private int largestDelta; + + private int deltaIsAscending = 0; + private int smallDeltaCount = 0; + + private double averageAbsoluteDelta = 0; + private double averageAbsoluteValue = 0; + + private Map distinctValues; + + /** + * Create a new instance of BandData. The band is then analysed. + * @param band - the band of integers + */ + public BandData(int[] band) { + this.band = band; + Integer one = new Integer(1); + for (int i = 0; i < band.length; i++) { + if(band[i] < smallest) { + smallest = band[i]; + } + if(band[i] > largest) { + largest = band[i]; + } + if(i != 0) { + int delta = band[i] - band[i - 1]; + if(delta < smallestDelta) { + smallestDelta = delta; + } + if(delta > largestDelta) { + largestDelta = delta; + } + if(delta >= 0) { + deltaIsAscending++; + } + averageAbsoluteDelta += (double)Math.abs(delta)/(double)(band.length - 1); + if(Math.abs(delta) < 256) { + smallDeltaCount++; + } + } else { + smallestDelta = band[0]; + largestDelta = band[0]; + } + averageAbsoluteValue += (double)Math.abs(band[i])/(double)band.length; + if(effort > 3) { // do calculations needed to consider population codec + if(distinctValues == null) { + distinctValues = new HashMap(); + } + Integer value = new Integer(band[i]); + Integer count = (Integer) distinctValues.get(value); + if(count == null) { + count = one; + } else { + count = new Integer(count.intValue() + 1); + } + distinctValues.put(value, count); + } + } + } + + /** + * Returns true if the deltas between adjacent band elements are mainly + * small (heuristic) + */ + public boolean mainlySmallDeltas() { + // Note: the value below has been tuned - please test carefully if changing it + return (float)smallDeltaCount/(float)band.length > 0.7F; + } + + /** + * Returns true if the band is well correlated (i.e. would be suitable + * for a delta encoding) (heuristic) + */ + public boolean wellCorrelated() { + // Note: the value below has been tuned - please test carefully if changing it + return averageAbsoluteDelta * 3.1 < averageAbsoluteValue; + } + + /** + * Returns true if the band deltas are mainly positive (heuristic) + */ + public boolean mainlyPositiveDeltas() { + // Note: the value below has been tuned - please test carefully if changing it + return (float)deltaIsAscending/(float)band.length > 0.95F; + } + + /** + * Returns true if any band elements are negative + */ + public boolean anyNegatives() { + return smallest < 0; + } + + /** + * Returns the total number of distinct values found in the band + */ + public int numDistinctValues() { + if(distinctValues == null) { + return band.length; + } + return distinctValues.size(); + } + + } + + /** + * Results obtained by trying different Codecs to encode a band + */ + public class BandAnalysisResults { + + // The number of Codecs tried so far + private int numCodecsTried = 0; + + // The number of bytes saved by using betterCodec instead of the default codec + private int saved = 0; + + // Extra metadata to pass to the segment header (to be appended to the + // band_headers band) + private int[] extraMetadata; + + // The results of encoding the band with betterCodec + private byte[] encodedBand; + + // The best Codec found so far, or should be null if the default is the + // best so far + private Codec betterCodec; + + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/BcBands.java b/src/main/java/org/apache/harmony/pack200/BcBands.java new file mode 100644 index 0000000..3b541e4 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/BcBands.java @@ -0,0 +1,582 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.objectweb.asm.Label; + +/** + * Bytecode bands (corresponds to the bc_bands set of bands in the + * pack200 specification) + */ +public class BcBands extends BandSet { + + private final CpBands cpBands; + private final Segment segment; + + public BcBands(CpBands cpBands, Segment segment, int effort) { + super(effort, segment.getSegmentHeader()); + this.cpBands = cpBands; + this.segment = segment; + } + + private final IntList bcCodes = new IntList(); + private final IntList bcCaseCount = new IntList(); + private final IntList bcCaseValue = new IntList(); + private final IntList bcByte = new IntList(); + private final IntList bcShort = new IntList(); + private final IntList bcLocal = new IntList(); + private final List bcLabel = new ArrayList(); + private final List bcIntref = new ArrayList(); + private final List bcFloatRef = new ArrayList(); + private final List bcLongRef = new ArrayList(); + private final List bcDoubleRef = new ArrayList(); + private final List bcStringRef = new ArrayList(); + private final List bcClassRef = new ArrayList(); + private final List bcFieldRef = new ArrayList(); + private final List bcMethodRef = new ArrayList(); + private final List bcIMethodRef = new ArrayList(); + private List bcThisField = new ArrayList(); + private final List bcSuperField = new ArrayList(); + private List bcThisMethod = new ArrayList(); + private List bcSuperMethod = new ArrayList(); + private List bcInitRef = new ArrayList(); + + private String currentClass; + private String superClass; + private String currentNewClass; + + private static final int MULTIANEWARRAY = 197; + private static final int ALOAD_0 = 42; + private static final int WIDE = 196; + private static final int INVOKEINTERFACE = 185; + private static final int TABLESWITCH = 170; + private static final int IINC = 132; + private static final int LOOKUPSWITCH = 171; + private static final int endMarker = 255; + + private final IntList bciRenumbering = new IntList(); + private final Map labelsToOffsets = new HashMap(); + private int byteCodeOffset; + private int renumberedOffset; + private final IntList bcLabelRelativeOffsets = new IntList(); + + public void setCurrentClass(String name, String superName) { + currentClass = name; + superClass = superName; + } + + /** + * All input classes for the segment have now been read in, so this method + * is called so that this class can calculate/complete anything it could not + * do while classes were being read. + */ + public void finaliseBands() { + bcThisField = getIndexInClass(bcThisField); + bcThisMethod = getIndexInClass(bcThisMethod); + bcSuperMethod = getIndexInClass(bcSuperMethod); + bcInitRef = getIndexInClassForConstructor(bcInitRef); + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + PackingUtils.log("Writing byte code bands..."); + byte[] encodedBand = encodeBandInt("bcCodes", bcCodes.toArray(), + Codec.BYTE1); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcCodes[" + + bcCodes.size() + "]"); + + encodedBand = encodeBandInt("bcCaseCount", bcCaseCount.toArray(), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcCaseCount[" + bcCaseCount.size() + "]"); + + encodedBand = encodeBandInt("bcCaseValue", bcCaseValue.toArray(), + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcCaseValue[" + bcCaseValue.size() + "]"); + + encodedBand = encodeBandInt("bcByte", bcByte.toArray(), Codec.BYTE1); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcByte[" + + bcByte.size() + "]"); + + encodedBand = encodeBandInt("bcShort", bcShort.toArray(), Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcShort[" + + bcShort.size() + "]"); + + encodedBand = encodeBandInt("bcLocal", bcLocal.toArray(), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcLocal[" + + bcLocal.size() + "]"); + + encodedBand = encodeBandInt("bcLabel", integerListToArray(bcLabel), + Codec.BRANCH5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcLabel[" + + bcLabel.size() + "]"); + + encodedBand = encodeBandInt("bcIntref", cpEntryListToArray(bcIntref), + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcIntref[" + bcIntref.size() + "]"); + + encodedBand = encodeBandInt("bcFloatRef", + cpEntryListToArray(bcFloatRef), Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcFloatRef[" + bcFloatRef.size() + "]"); + + encodedBand = encodeBandInt("bcLongRef", cpEntryListToArray(bcLongRef), + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcLongRef[" + bcLongRef.size() + "]"); + + encodedBand = encodeBandInt("bcDoubleRef", + cpEntryListToArray(bcDoubleRef), Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcDoubleRef[" + bcDoubleRef.size() + "]"); + + encodedBand = encodeBandInt("bcStringRef", + cpEntryListToArray(bcStringRef), Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcStringRef[" + bcStringRef.size() + "]"); + + encodedBand = encodeBandInt("bcClassRef", + cpEntryOrNullListToArray(bcClassRef), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcClassRef[" + bcClassRef.size() + "]"); + + encodedBand = encodeBandInt("bcFieldRef", + cpEntryListToArray(bcFieldRef), Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcFieldRef[" + bcFieldRef.size() + "]"); + + encodedBand = encodeBandInt("bcMethodRef", + cpEntryListToArray(bcMethodRef), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcMethodRef[" + bcMethodRef.size() + "]"); + + encodedBand = encodeBandInt("bcIMethodRef", + cpEntryListToArray(bcIMethodRef), Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcIMethodRef[" + bcIMethodRef.size() + "]"); + + encodedBand = encodeBandInt("bcThisField", + integerListToArray(bcThisField), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcThisField[" + bcThisField.size() + "]"); + + encodedBand = encodeBandInt("bcSuperField", + integerListToArray(bcSuperField), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcSuperField[" + bcSuperField.size() + "]"); + + encodedBand = encodeBandInt("bcThisMethod", + integerListToArray(bcThisMethod), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcThisMethod[" + bcThisMethod.size() + "]"); + + encodedBand = encodeBandInt("bcSuperMethod", + integerListToArray(bcSuperMethod), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcSuperMethod[" + bcSuperMethod.size() + "]"); + + encodedBand = encodeBandInt("bcInitRef", integerListToArray(bcInitRef), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from bcInitRef[" + bcInitRef.size() + "]"); + + // out.write(encodeBandInt(cpEntryintegerListToArray(bcEscRef), + // Codec.UNSIGNED5)); + // out.write(encodeBandInt(integerListToArray(bcEscRefSize), + // Codec.UNSIGNED5)); + // out.write(encodeBandInt(integerListToArray(bcEscSize), + // Codec.UNSIGNED5)); + // out.write(encodeBandInt(integerListToArray(bcEscByte), Codec.BYTE1)); + } + + private List getIndexInClass(List cPMethodOrFieldList) { + List indices = new ArrayList(cPMethodOrFieldList.size()); + for (int i = 0; i < cPMethodOrFieldList.size(); i++) { + CPMethodOrField cpMF = (CPMethodOrField) cPMethodOrFieldList.get(i); + indices.add(new Integer(cpMF.getIndexInClass())); + } + return indices; + } + + private List getIndexInClassForConstructor(List cPMethodList) { + List indices = new ArrayList(cPMethodList.size()); + for (int i = 0; i < cPMethodList.size(); i++) { + CPMethodOrField cpMF = (CPMethodOrField) cPMethodList.get(i); + indices.add(new Integer(cpMF.getIndexInClassForConstructor())); + } + return indices; + } + + public void visitEnd() { + for (int i = 0; i < bciRenumbering.size(); i++) { + if (bciRenumbering.get(i) == -1) { + bciRenumbering.remove(i); + bciRenumbering.add(i, ++renumberedOffset); + } + } + if (renumberedOffset != 0) { + if(renumberedOffset + 1 != bciRenumbering.size()) { + throw new RuntimeException("Mistake made with renumbering"); + } + for (int i = bcLabel.size() - 1; i >= 0; i--) { + Object label = bcLabel.get(i); + if (label instanceof Integer) { + break; + } else if (label instanceof Label) { + bcLabel.remove(i); + Integer offset = (Integer) labelsToOffsets.get(label); + int relativeOffset = bcLabelRelativeOffsets.get(i); + bcLabel.add(i, new Integer(bciRenumbering.get(offset.intValue()) - bciRenumbering.get(relativeOffset))); + } + } + bcCodes.add(endMarker); + segment.getClassBands().doBciRenumbering(bciRenumbering, + labelsToOffsets); + bciRenumbering.clear(); + labelsToOffsets.clear(); + byteCodeOffset = 0; + renumberedOffset = 0; + } + } + + public void visitLabel(Label label) { + labelsToOffsets.put(label, new Integer(byteCodeOffset)); + } + + public void visitFieldInsn(int opcode, String owner, String name, + String desc) { + byteCodeOffset += 3; + updateRenumbering(); + boolean aload_0 = false; + if (bcCodes.size() > 0 + && (bcCodes.get(bcCodes.size() - 1)) == ALOAD_0) { + bcCodes.remove(bcCodes.size() - 1); + aload_0 = true; + } + CPMethodOrField cpField = cpBands.getCPField(owner, name, desc); + if (aload_0) { + opcode += 7; + } + if (owner.equals(currentClass)) { + opcode += 24; // change to getstatic_this, putstatic_this etc. + bcThisField.add(cpField); +// } else if (owner.equals(superClass)) { +// opcode += 38; // change to getstatic_super etc. +// bcSuperField.add(cpField); + } else { + if (aload_0) { + opcode -= 7; + bcCodes.add(ALOAD_0); // add aload_0 back in because + // there's no special rewrite in + // this case. + } + bcFieldRef.add(cpField); + } + aload_0 = false; + bcCodes.add(opcode); + } + + private void updateRenumbering() { + if(bciRenumbering.isEmpty()) { + bciRenumbering.add(0); + } + renumberedOffset ++; + for (int i = bciRenumbering.size(); i < byteCodeOffset; i++) { + bciRenumbering.add(-1); + } + bciRenumbering.add(renumberedOffset); + } + + public void visitIincInsn(int var, int increment) { + if (var > 255 || increment > 255) { + byteCodeOffset += 6; + bcCodes.add(WIDE); + bcCodes.add(IINC); + bcLocal.add(var); + bcShort.add(increment); + } else { + byteCodeOffset += 3; + bcCodes.add(IINC); + bcLocal.add(var); + bcByte.add(increment & 0xFF); + } + updateRenumbering(); + } + + public void visitInsn(int opcode) { + if (opcode >= 202) { + throw new RuntimeException( + "Non-standard bytecode instructions not supported"); + } else { + bcCodes.add(opcode); + byteCodeOffset++; + updateRenumbering(); + } + } + + public void visitIntInsn(int opcode, int operand) { + switch (opcode) { + case 17: // sipush + bcCodes.add(opcode); + bcShort.add(operand); + byteCodeOffset += 3; + break; + case 16: // bipush + case 188: // newarray + bcCodes.add(opcode); + bcByte.add(operand & 0xFF); + byteCodeOffset += 2; + } + updateRenumbering(); + } + + public void visitJumpInsn(int opcode, Label label) { + bcCodes.add(opcode); + bcLabel.add(label); + bcLabelRelativeOffsets.add(byteCodeOffset); + byteCodeOffset += 3; + updateRenumbering(); + } + + public void visitLdcInsn(Object cst) { + CPConstant constant = cpBands.getConstant(cst); + if (segment.lastConstantHadWideIndex() || constant instanceof CPLong + || constant instanceof CPDouble) { + byteCodeOffset += 3; + if (constant instanceof CPInt) { + bcCodes.add(237); // ildc_w + bcIntref.add(constant); + } else if (constant instanceof CPFloat) { + bcCodes.add(238); // fldc + bcFloatRef.add(constant); + } else if (constant instanceof CPLong) { + bcCodes.add(20); // lldc2_w + bcLongRef.add(constant); + } else if (constant instanceof CPDouble) { + bcCodes.add(239); // dldc2_w + bcDoubleRef.add(constant); + } else if (constant instanceof CPString) { + bcCodes.add(19); // aldc + bcStringRef.add(constant); + } else if (constant instanceof CPClass) { + bcCodes.add(236); // cldc + bcClassRef.add(constant); + } else { + throw new RuntimeException("Constant should not be null"); + } + } else { + byteCodeOffset += 2; + if (constant instanceof CPInt) { + bcCodes.add(234); // ildc + bcIntref.add(constant); + } else if (constant instanceof CPFloat) { + bcCodes.add(235); // fldc + bcFloatRef.add(constant); + } else if (constant instanceof CPString) { + bcCodes.add(18); // aldc + bcStringRef.add(constant); + } else if (constant instanceof CPClass) { + bcCodes.add(233); // cldc + bcClassRef.add(constant); + } + } + updateRenumbering(); + } + + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + bcCodes.add(LOOKUPSWITCH); + bcLabel.add(dflt); + bcLabelRelativeOffsets.add(byteCodeOffset); + bcCaseCount.add(keys.length); + for (int i = 0; i < labels.length; i++) { + bcCaseValue.add(keys[i]); + bcLabel.add(labels[i]); + bcLabelRelativeOffsets.add(byteCodeOffset); + } + int padding = (byteCodeOffset + 1) % 4 == 0 ? 0 : 4 - ((byteCodeOffset + 1) % 4); + byteCodeOffset += padding + 8 + 8 * keys.length; + updateRenumbering(); + } + + public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + byteCodeOffset += 3; + updateRenumbering(); + switch (opcode) { + case 182: // invokevirtual + case 183: // invokespecial + case 184: // invokestatic + boolean aload_0 = false; + if (bcCodes.size() > 0 + && (bcCodes.get(bcCodes.size() - 1)) + == (ALOAD_0)) { + bcCodes.remove(bcCodes.size() - 1); + aload_0 = true; + opcode += 7; + } + if (owner.equals(currentClass)) { + opcode += 24; // change to invokevirtual_this, + // invokespecial_this etc. + + if(name.equals("") && opcode == 207) { + opcode = 230; // invokespecial_this_init + bcInitRef.add(cpBands.getCPMethod(owner, name, desc)); + } else { + bcThisMethod.add(cpBands.getCPMethod(owner, name, desc)); + } + } else if (owner.equals(superClass)) { // TODO + opcode += 38; // change to invokevirtual_super, + // invokespecial_super etc. + if(name.equals("") && opcode == 221) { + opcode = 231; // invokespecial_super_init + bcInitRef.add(cpBands.getCPMethod(owner, name, desc)); + } else { + bcSuperMethod.add(cpBands.getCPMethod(owner, name, desc)); + } + } else { + if (aload_0) { + opcode -= 7; + bcCodes.add(ALOAD_0); // add aload_0 back in + // because there's no + // special rewrite in this + // case. + } + if(name.equals("") && opcode == 183 && owner.equals(currentNewClass)) { + opcode = 232; // invokespecial_new_init + bcInitRef.add(cpBands.getCPMethod(owner, name, desc)); + } else { + bcMethodRef.add(cpBands.getCPMethod(owner, name, desc)); + } + } + bcCodes.add(opcode); + break; + case 185: // invokeinterface + CPMethodOrField cpIMethod = cpBands.getCPIMethod(owner, name, desc); + bcIMethodRef.add(cpIMethod); + bcCodes.add(INVOKEINTERFACE); + break; + } + } + + public void visitMultiANewArrayInsn(String desc, int dimensions) { + byteCodeOffset += 4; + updateRenumbering(); + bcCodes.add(MULTIANEWARRAY); + bcClassRef.add(cpBands.getCPClass(desc)); + bcByte.add(dimensions & 0xFF); + } + + public void visitTableSwitchInsn(int min, int max, Label dflt, + Label[] labels) { + bcCodes.add(TABLESWITCH); + bcLabel.add(dflt); + bcLabelRelativeOffsets.add(byteCodeOffset); + bcCaseValue.add(min); + int count = labels.length; + bcCaseCount.add(count); + for (int i = 0; i < count; i++) { + bcLabel.add(labels[i]); + bcLabelRelativeOffsets.add(byteCodeOffset); + } + int padding = (byteCodeOffset + 1) % 4 == 0 ? 0 : 4 - ((byteCodeOffset + 1) % 4); + byteCodeOffset+= (padding + 12 + 4 * labels.length); + updateRenumbering(); + } + + public void visitTypeInsn(int opcode, String type) { + // NEW, ANEWARRAY, CHECKCAST or INSTANCEOF + byteCodeOffset += 3; + updateRenumbering(); + bcCodes.add(opcode); + bcClassRef.add(cpBands.getCPClass(type)); + if(opcode == 187) { // NEW + currentNewClass = type; + } + } + + public void visitVarInsn(int opcode, int var) { + // ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET + if (var > 255) { + byteCodeOffset += 4; + bcCodes.add(WIDE); + bcCodes.add(opcode); + bcLocal.add(var); + } else { + if(var > 3 || opcode == 169 /* RET */) { + byteCodeOffset += 2; + bcCodes.add(opcode); + bcLocal.add(var); + } else { + byteCodeOffset +=1; + switch(opcode) { + case 21: // ILOAD + case 54: // ISTORE + bcCodes.add(opcode + 5 + var); + break; + case 22: // LLOAD + case 55: // LSTORE + bcCodes.add(opcode + 8 + var); + break; + case 23: // FLOAD + case 56: // FSTORE + bcCodes.add(opcode + 11 + var); + break; + case 24: // DLOAD + case 57: // DSTORE + bcCodes.add(opcode + 14 + var); + break; + case 25: // A_LOAD + case 58: // A_STORE + bcCodes.add(opcode + 17 + var); + break; + } + } + } + updateRenumbering(); + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/CPClass.java b/src/main/java/org/apache/harmony/pack200/CPClass.java new file mode 100644 index 0000000..1639613 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CPClass.java @@ -0,0 +1,57 @@ +/* + * 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.pack200; + +/** + * Constant pool entry for a class + */ +public class CPClass extends CPConstant implements Comparable { + + private final String className; + private final CPUTF8 utf8; + private final boolean isInnerClass; + + public CPClass(CPUTF8 utf8) { + this.utf8 = utf8; + this.className = utf8.getUnderlyingString(); + char[] chars = className.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if(chars[i] <= 0x2D) { + isInnerClass = true; + return; + } + } + isInnerClass = false; + } + + public int compareTo(Object arg0) { + return className.compareTo(((CPClass)arg0).className); + } + + public String toString() { + return className; + } + + public int getIndexInCpUtf8() { + return utf8.getIndex(); + } + + public boolean isInnerClass() { + return isInnerClass; + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/CPConstant.java b/src/main/java/org/apache/harmony/pack200/CPConstant.java new file mode 100644 index 0000000..ffd3229 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CPConstant.java @@ -0,0 +1,26 @@ +/* + * 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.pack200; + +/** + * Abstract superclass for constant pool constant entries such as numbers or + * Strings + */ +public abstract class CPConstant extends ConstantPoolEntry implements + Comparable { + +} diff --git a/src/main/java/org/apache/harmony/pack200/CPDouble.java b/src/main/java/org/apache/harmony/pack200/CPDouble.java new file mode 100644 index 0000000..210f774 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CPDouble.java @@ -0,0 +1,37 @@ +/* + * 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.pack200; + +/** + * Constant pool entry for a double. + */ +public class CPDouble extends CPConstant { + + private final double theDouble; + + public CPDouble(double theDouble) { + this.theDouble = theDouble; + } + + public int compareTo(Object obj) { + return Double.compare(theDouble, ((CPDouble)obj).theDouble); + } + + public double getDouble() { + return theDouble; + } +} diff --git a/src/main/java/org/apache/harmony/pack200/CPFloat.java b/src/main/java/org/apache/harmony/pack200/CPFloat.java new file mode 100644 index 0000000..6cb649d --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CPFloat.java @@ -0,0 +1,37 @@ +/* + * 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.pack200; + +/** + * Constant pool entry for a float. + */ +public class CPFloat extends CPConstant { + + private final float theFloat; + + public CPFloat(float theFloat) { + this.theFloat = theFloat; + } + + public int compareTo(Object obj) { + return Float.compare(theFloat, ((CPFloat)obj).theFloat); + } + + public float getFloat() { + return theFloat; + } +} diff --git a/src/main/java/org/apache/harmony/pack200/CPInt.java b/src/main/java/org/apache/harmony/pack200/CPInt.java new file mode 100644 index 0000000..abcfe30 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CPInt.java @@ -0,0 +1,43 @@ +/* + * 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.pack200; + +/** + * Constant pool entry for an int. + */ +public class CPInt extends CPConstant { + + private final int theInt; + + public CPInt(int theInt) { + this.theInt = theInt; + } + + public int compareTo(Object obj) { + if(theInt > ((CPInt)obj).theInt) { + return 1; + } else if (theInt == ((CPInt)obj).theInt) { + return 0; + } else { + return -1; + } + } + + public int getInt() { + return theInt; + } +} diff --git a/src/main/java/org/apache/harmony/pack200/CPLong.java b/src/main/java/org/apache/harmony/pack200/CPLong.java new file mode 100644 index 0000000..2f2d7b4 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CPLong.java @@ -0,0 +1,48 @@ +/* + * 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.pack200; + +/** + * Constant pool entry for a long. + */ +public class CPLong extends CPConstant { + + private final long theLong; + + public CPLong(long theLong) { + this.theLong = theLong; + } + + public int compareTo(Object obj) { + if(theLong > ((CPLong)obj).theLong) { + return 1; + } else if (theLong == ((CPLong)obj).theLong) { + return 0; + } else { + return -1; + } + } + + public long getLong() { + return theLong; + } + + public String toString() { + return "" + theLong; + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/CPMethodOrField.java b/src/main/java/org/apache/harmony/pack200/CPMethodOrField.java new file mode 100644 index 0000000..d3deae4 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CPMethodOrField.java @@ -0,0 +1,95 @@ +/* + * 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.pack200; + +/** + * Constant pool entry for a method or field. + */ +public class CPMethodOrField extends ConstantPoolEntry implements Comparable { + + private final CPClass className; + private final CPNameAndType nameAndType; + private int indexInClass = -1; + private int indexInClassForConstructor = -1; + + public CPMethodOrField(CPClass className, CPNameAndType nameAndType) { + this.className = className; + this.nameAndType = nameAndType; + } + + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof CPMethodOrField)) { + return false; + } + return ((CPMethodOrField) obj).className.equals(className) + && ((CPMethodOrField) obj).nameAndType.equals(nameAndType); + } + + public int hashCode() { + return className.hashCode() + nameAndType.hashCode(); + } + + public String toString() { + return className + ": " + nameAndType; + } + + public int compareTo(Object obj) { + if (obj instanceof CPMethodOrField) { + CPMethodOrField mof = (CPMethodOrField) obj; + int compareName = className.compareTo(mof.className); + if (compareName == 0) { + return nameAndType.compareTo(mof.nameAndType); + } else { + return compareName; + } + } + return 0; + } + + public int getClassIndex() { + return className.getIndex(); + } + + public CPClass getClassName() { + return className; + } + + public int getDescIndex() { + return nameAndType.getIndex(); + } + + public CPNameAndType getDesc() { + return nameAndType; + } + + public int getIndexInClass() { + return indexInClass; + } + + public void setIndexInClass(int index) { + indexInClass = index; + } + + public int getIndexInClassForConstructor() { + return indexInClassForConstructor; + } + + public void setIndexInClassForConstructor(int index) { + indexInClassForConstructor = index; + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/pack200/CPNameAndType.java b/src/main/java/org/apache/harmony/pack200/CPNameAndType.java new file mode 100644 index 0000000..5b4d76b --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CPNameAndType.java @@ -0,0 +1,73 @@ +/* + * 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.pack200; + +/** + * Constant pool entry for a name and type pair. + */ +public class CPNameAndType extends ConstantPoolEntry implements Comparable { + + private final CPUTF8 name; + private final CPSignature signature; + + public CPNameAndType(CPUTF8 name, CPSignature signature) { + this.name = name; + this.signature = signature; + } + + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof CPNameAndType)) { + return false; + } + return ((CPNameAndType) obj).name.equals(name) + && ((CPNameAndType) obj).signature.equals(signature); + } + + public int hashCode() { + return name.hashCode() + signature.hashCode(); + } + + public String toString() { + return name + ":" + signature; + } + + public int compareTo(Object obj) { + if (obj instanceof CPNameAndType) { + CPNameAndType nat = (CPNameAndType) obj; + int compareSignature = signature.compareTo(nat.signature);; + if(compareSignature == 0) { + return name.compareTo(nat.name); + } else { + return compareSignature; + } + } + return 0; + } + + public int getNameIndex() { + return name.getIndex(); + } + + public String getName() { + return name.getUnderlyingString(); + } + + public int getTypeIndex() { + return signature.getIndex(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/pack200/CPSignature.java b/src/main/java/org/apache/harmony/pack200/CPSignature.java new file mode 100644 index 0000000..d5e423c --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CPSignature.java @@ -0,0 +1,86 @@ +/* + * 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.pack200; + +import java.util.List; + +/** + * Constant pool entry for a signature. + */ +public class CPSignature extends ConstantPoolEntry implements Comparable { + + private final CPUTF8 signatureForm; + private final List classes; + private final String signature; + private final boolean formStartsWithBracket; + + public CPSignature(String signature, CPUTF8 signatureForm, List classes) { + this.signature = signature; + this.signatureForm = signatureForm; + this.classes = classes; + formStartsWithBracket = signatureForm.toString().startsWith("("); + } + + public int compareTo(Object arg0) { + if(signature.equals(((CPSignature) arg0).signature)) { + return 0; + } + if (formStartsWithBracket + && !((CPSignature) arg0).formStartsWithBracket) { + return 1; + } + if (((CPSignature) arg0).formStartsWithBracket + && !formStartsWithBracket) { + return -1; + } + if (classes.size() - ((CPSignature) arg0).classes.size() != 0) { + return classes.size() - ((CPSignature) arg0).classes.size(); + } + if (classes.size() > 0) { + for (int i = classes.size() - 1; i >=0; i--) { + CPClass cpClass = (CPClass) classes.get(i); + CPClass compareClass = (CPClass) ((CPSignature) arg0).classes + .get(i); + int classComp = cpClass.compareTo(compareClass); + if(classComp != 0) { + return classComp; + } + } + } + return signature.compareTo(((CPSignature) arg0).signature); + } + + public int getIndexInCpUtf8() { + return signatureForm.getIndex(); + } + + public List getClasses() { + return classes; + } + + public String toString() { + return signature; + } + + public String getUnderlyingString() { + return signature; + } + + public CPUTF8 getSignatureForm() { + return signatureForm; + } +} diff --git a/src/main/java/org/apache/harmony/pack200/CPString.java b/src/main/java/org/apache/harmony/pack200/CPString.java new file mode 100644 index 0000000..b8acca6 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CPString.java @@ -0,0 +1,44 @@ +/* + * 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.pack200; + +/** + * Constant pool entry for a String. + */ +public class CPString extends CPConstant { + + private final String string; + private final CPUTF8 utf8; + + public CPString(CPUTF8 utf8) { + this.utf8 = utf8; + this.string = utf8.getUnderlyingString(); + } + + public int compareTo(Object arg0) { + return string.compareTo(((CPString)arg0).string); + } + + public String toString() { + return string; + } + + public int getIndexInCpUtf8() { + return utf8.getIndex(); + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/CPUTF8.java b/src/main/java/org/apache/harmony/pack200/CPUTF8.java new file mode 100644 index 0000000..00a1526 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CPUTF8.java @@ -0,0 +1,42 @@ +/* + * 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.pack200; + +/** + * Constant pool entry for a UTF8 entry, used for storing long Strings. + */ +public class CPUTF8 extends ConstantPoolEntry implements Comparable { + + private final String string; + + public CPUTF8(String string) { + this.string = string; + } + + public int compareTo(Object arg0) { + return string.compareTo(((CPUTF8)arg0).string); + } + + public String toString() { + return string; + } + + public String getUnderlyingString() { + return string; + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/CanonicalCodecFamilies.java b/src/main/java/org/apache/harmony/pack200/CanonicalCodecFamilies.java new file mode 100644 index 0000000..174f533 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CanonicalCodecFamilies.java @@ -0,0 +1,210 @@ +/* + * 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.pack200; + +/** + * Sets of codecs that share characteristics. Mainly used for different effort + * compression heuristics in BandSet. + */ +public class CanonicalCodecFamilies { + + // Families of codecs for bands of positive integers that do not correlate + // well (i.e. would not benefit from delta encoding) + + public static BHSDCodec[] nonDeltaUnsignedCodecs1 = new BHSDCodec[] { + // (1,256) is a special case and is considered separately so shouldn't be included here +// CodecEncoding.getCanonicalCodec(1), // (1,256) + CodecEncoding.getCanonicalCodec(5), // (2,256) + CodecEncoding.getCanonicalCodec(9), // (3,256) + CodecEncoding.getCanonicalCodec(13) // (4,256) + }; + + public static BHSDCodec[] nonDeltaUnsignedCodecs2 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(17), // (5,4) + CodecEncoding.getCanonicalCodec(20), // (5,16) + CodecEncoding.getCanonicalCodec(23), // (5,32) + CodecEncoding.getCanonicalCodec(26), // (5,64) + CodecEncoding.getCanonicalCodec(29) // (5,128) + }; + + public static BHSDCodec[] nonDeltaUnsignedCodecs3 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(47), // (2,192) + CodecEncoding.getCanonicalCodec(48), // (2,224) + CodecEncoding.getCanonicalCodec(49), // (2,240) + CodecEncoding.getCanonicalCodec(50), // (2,248) + CodecEncoding.getCanonicalCodec(51) // (2,252) + }; + + public static BHSDCodec[] nonDeltaUnsignedCodecs4 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(70), // (3,192) + CodecEncoding.getCanonicalCodec(71), // (3,224) + CodecEncoding.getCanonicalCodec(72), // (3,240) + CodecEncoding.getCanonicalCodec(73), // (3,248) + CodecEncoding.getCanonicalCodec(74) // (3,252) + }; + + public static BHSDCodec[] nonDeltaUnsignedCodecs5 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(93), // (4,192) + CodecEncoding.getCanonicalCodec(94), // (4,224) + CodecEncoding.getCanonicalCodec(95), // (4,240) + CodecEncoding.getCanonicalCodec(96), // (4,248) + CodecEncoding.getCanonicalCodec(97) // (4,252) + }; + + // Families of codecs for bands of positive integers that do correlate well + // and would benefit from delta encoding + + public static BHSDCodec[] deltaUnsignedCodecs1 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(3), // (1,256,0,1) + CodecEncoding.getCanonicalCodec(7), // (2,256,0,1) + CodecEncoding.getCanonicalCodec(11), // (3,256,0,1) + CodecEncoding.getCanonicalCodec(15) // (4,256,0,1) + }; + + public static BHSDCodec[] deltaUnsignedCodecs2 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(32), // (5,4,0,1) + CodecEncoding.getCanonicalCodec(35), // (5,16,0,1) + CodecEncoding.getCanonicalCodec(38), // (5,32,0,1) + CodecEncoding.getCanonicalCodec(41), // (5,64,0,1) + CodecEncoding.getCanonicalCodec(44) // (5,128,0,1) + }; + + public static BHSDCodec[] deltaUnsignedCodecs3 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(52), // (2,8,0,1) + CodecEncoding.getCanonicalCodec(54), // (2,16,0,1) + CodecEncoding.getCanonicalCodec(56), // (2,32,0,1) + CodecEncoding.getCanonicalCodec(58), // (2,64,0,1) + CodecEncoding.getCanonicalCodec(60), // (2,128,0,1) + CodecEncoding.getCanonicalCodec(62), // (2,192,0,1) + CodecEncoding.getCanonicalCodec(64), // (2,224,0,1) + CodecEncoding.getCanonicalCodec(66), // (2,240,0,1) + CodecEncoding.getCanonicalCodec(68) // (2,248,0,1) + }; + + public static BHSDCodec[] deltaUnsignedCodecs4 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(75), // (3,8,0,1) + CodecEncoding.getCanonicalCodec(77), // (3,16,0,1) + CodecEncoding.getCanonicalCodec(79), // (3,32,0,1) + CodecEncoding.getCanonicalCodec(81), // (3,64,0,1) + CodecEncoding.getCanonicalCodec(83), // (3,128,0,1) + CodecEncoding.getCanonicalCodec(85), // (3,192,0,1) + CodecEncoding.getCanonicalCodec(87), // (3,224,0,1) + CodecEncoding.getCanonicalCodec(89), // (3,240,0,1) + CodecEncoding.getCanonicalCodec(91) // (3,248,0,1) + }; + + public static BHSDCodec[] deltaUnsignedCodecs5 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(98), // (4,8,0,1) + CodecEncoding.getCanonicalCodec(100), // (4,16,0,1) + CodecEncoding.getCanonicalCodec(102), // (4,32,0,1) + CodecEncoding.getCanonicalCodec(104), // (4,64,0,1) + CodecEncoding.getCanonicalCodec(106), // (4,128,0,1) + CodecEncoding.getCanonicalCodec(108), // (4,192,0,1) + CodecEncoding.getCanonicalCodec(110), // (4,224,0,1) + CodecEncoding.getCanonicalCodec(112), // (4,240,0,1) + CodecEncoding.getCanonicalCodec(114) // (4,248,0,1) + }; + + // Families of codecs for bands containing positive and negative integers + // that do correlate well (i.e. delta encoding is used) + + public static BHSDCodec[] deltaSignedCodecs1 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(4), // (1,256,1,1) + CodecEncoding.getCanonicalCodec(8), // (2,256,1,1) + CodecEncoding.getCanonicalCodec(12), // (3,256,1,1) + CodecEncoding.getCanonicalCodec(16) // (4,256,1,1) + }; + + public static BHSDCodec[] deltaSignedCodecs2 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(33), // (5,4,1,1) + CodecEncoding.getCanonicalCodec(36), // (5,16,1,1) + CodecEncoding.getCanonicalCodec(39), // (5,32,1,1) + CodecEncoding.getCanonicalCodec(42), // (5,64,1,1) + CodecEncoding.getCanonicalCodec(45) // (5,128,1,1) + }; + + public static BHSDCodec[] deltaSignedCodecs3 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(53), // (2,8,1,1) + CodecEncoding.getCanonicalCodec(55), // (2,16,1,1) + CodecEncoding.getCanonicalCodec(57), // (2,32,1,1) + CodecEncoding.getCanonicalCodec(59), // (2,64,1,1) + CodecEncoding.getCanonicalCodec(61), // (2,128,1,1) + CodecEncoding.getCanonicalCodec(63), // (2,192,1,1) + CodecEncoding.getCanonicalCodec(65), // (2,224,1,1) + CodecEncoding.getCanonicalCodec(67), // (2,240,1,1) + CodecEncoding.getCanonicalCodec(69) // (2,248,1,1) + }; + + public static BHSDCodec[] deltaSignedCodecs4 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(76), // (3,8,1,1) + CodecEncoding.getCanonicalCodec(78), // (3,16,1,1) + CodecEncoding.getCanonicalCodec(80), // (3,32,1,1) + CodecEncoding.getCanonicalCodec(82), // (3,64,1,1) + CodecEncoding.getCanonicalCodec(84), // (3,128,1,1) + CodecEncoding.getCanonicalCodec(86), // (3,192,1,1) + CodecEncoding.getCanonicalCodec(88), // (3,224,1,1) + CodecEncoding.getCanonicalCodec(90), // (3,240,1,1) + CodecEncoding.getCanonicalCodec(92) // (3,248,1,1) + }; + + public static BHSDCodec[] deltaSignedCodecs5 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(99), // (4,8,1,1) + CodecEncoding.getCanonicalCodec(101), // (4,16,1,1) + CodecEncoding.getCanonicalCodec(103), // (4,32,1,1) + CodecEncoding.getCanonicalCodec(105), // (4,64,1,1) + CodecEncoding.getCanonicalCodec(107), // (4,128,1,1) + CodecEncoding.getCanonicalCodec(109), // (4,192,1,1) + CodecEncoding.getCanonicalCodec(111), // (4,224,1,1) + CodecEncoding.getCanonicalCodec(113), // (4,240,1,1) + CodecEncoding.getCanonicalCodec(115) // (4,248,1,1) + }; + + public static BHSDCodec[] deltaDoubleSignedCodecs1 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(34), // (5,4,2,1) + CodecEncoding.getCanonicalCodec(37), // (5,16,2,1) + CodecEncoding.getCanonicalCodec(40), // (5,32,2,1) + CodecEncoding.getCanonicalCodec(43), // (5,64,2,1) + CodecEncoding.getCanonicalCodec(46) // (5,128,2,1) + }; + + // Families of codecs for bands containing positive and negative values that + // do not correlate well (i.e. delta encoding is not used) + + public static BHSDCodec[] nonDeltaSignedCodecs1 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(2), // (1,256,1) + CodecEncoding.getCanonicalCodec(6), // (2,256,1) + CodecEncoding.getCanonicalCodec(10), // (3,256,1) + CodecEncoding.getCanonicalCodec(14) // (4,256,1) + }; + + public static BHSDCodec[] nonDeltaSignedCodecs2 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(18), // (5,4,1) + CodecEncoding.getCanonicalCodec(21), // (5,16,1) + CodecEncoding.getCanonicalCodec(24), // (5,32,1) + CodecEncoding.getCanonicalCodec(27), // (5,64,1) + CodecEncoding.getCanonicalCodec(30) // (5,128,1) + }; + + public static BHSDCodec[] nonDeltaDoubleSignedCodecs1 = new BHSDCodec[] { + CodecEncoding.getCanonicalCodec(19), // (5,4,2) + CodecEncoding.getCanonicalCodec(22), // (5,16,2) + CodecEncoding.getCanonicalCodec(25), // (5,32,2) + CodecEncoding.getCanonicalCodec(28), // (5,64,2) + CodecEncoding.getCanonicalCodec(31) // (5,128,2) + }; + +} diff --git a/src/main/java/org/apache/harmony/pack200/ClassBands.java b/src/main/java/org/apache/harmony/pack200/ClassBands.java new file mode 100644 index 0000000..fb1de8d --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/ClassBands.java @@ -0,0 +1,1553 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.harmony.pack200.AttributeDefinitionBands.AttributeDefinition; +import org.apache.harmony.pack200.IcBands.IcTuple; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; + +/** + * Class bands (corresponds to the class_bands set of bands in the + * pack200 specification) + */ +public class ClassBands extends BandSet { + + private final CpBands cpBands; + private final AttributeDefinitionBands attrBands; + + private final CPClass[] class_this; + private final CPClass[] class_super; + private final CPClass[][] class_interface; + private final int[] class_interface_count; + + private final int[] major_versions; + + private final long[] class_flags; + private int[] class_attr_calls; + private final List classSourceFile = new ArrayList(); + private final List classEnclosingMethodClass = new ArrayList(); + private final List classEnclosingMethodDesc = new ArrayList(); + private final List classSignature = new ArrayList(); + + private final IntList classFileVersionMinor = new IntList(); + private final IntList classFileVersionMajor = new IntList(); + + private final int[] class_field_count; + private final CPNameAndType[][] field_descr; + private final long[][] field_flags; + private int[] field_attr_calls; + private final List fieldConstantValueKQ = new ArrayList(); + private final List fieldSignature = new ArrayList(); + + private final int[] class_method_count; + private final CPNameAndType[][] method_descr; + private final long[][] method_flags; + private int[] method_attr_calls; + private final List methodSignature = new ArrayList(); + private final IntList methodExceptionNumber = new IntList(); + private final List methodExceptionClasses = new ArrayList(); + + private int[] codeHeaders; + private final IntList codeMaxStack = new IntList(); + private final IntList codeMaxLocals = new IntList(); + private final IntList codeHandlerCount = new IntList(); + private final List codeHandlerStartP = new ArrayList(); + private final List codeHandlerEndPO = new ArrayList(); + private final List codeHandlerCatchPO = new ArrayList(); + private final List codeHandlerClass = new ArrayList(); + private final List codeFlags = new ArrayList(); + private int[] code_attr_calls; + private final IntList codeLineNumberTableN = new IntList(); + private final List codeLineNumberTableBciP = new ArrayList(); + private final IntList codeLineNumberTableLine = new IntList(); + private final IntList codeLocalVariableTableN = new IntList(); + private final List codeLocalVariableTableBciP = new ArrayList(); + private final List codeLocalVariableTableSpanO = new ArrayList(); + private final List codeLocalVariableTableNameRU = new ArrayList(); + private final List codeLocalVariableTableTypeRS = new ArrayList(); + private final IntList codeLocalVariableTableSlot = new IntList(); + private final IntList codeLocalVariableTypeTableN = new IntList(); + private final List codeLocalVariableTypeTableBciP = new ArrayList(); + private final List codeLocalVariableTypeTableSpanO = new ArrayList(); + private final List codeLocalVariableTypeTableNameRU = new ArrayList(); + private final List codeLocalVariableTypeTableTypeRS = new ArrayList(); + private final IntList codeLocalVariableTypeTableSlot = new IntList(); + + private final MetadataBandGroup class_RVA_bands; + private final MetadataBandGroup class_RIA_bands; + private final MetadataBandGroup field_RVA_bands; + private final MetadataBandGroup field_RIA_bands; + private final MetadataBandGroup method_RVA_bands; + private final MetadataBandGroup method_RIA_bands; + private final MetadataBandGroup method_RVPA_bands; + private final MetadataBandGroup method_RIPA_bands; + private final MetadataBandGroup method_AD_bands; + + private final List classAttributeBands = new ArrayList(); + private final List methodAttributeBands = new ArrayList(); + private final List fieldAttributeBands = new ArrayList(); + private final List codeAttributeBands = new ArrayList(); + + private final List tempFieldFlags = new ArrayList(); + private final List tempFieldDesc = new ArrayList(); + private final List tempMethodFlags = new ArrayList(); + private final List tempMethodDesc = new ArrayList(); + + private boolean anySyntheticClasses = false; + private boolean anySyntheticFields = false; + private boolean anySyntheticMethods = false; + private final Segment segment; + + private final Map classReferencesInnerClass = new HashMap(); + private final boolean stripDebug; + + private int index = 0; + + private int numMethodArgs = 0; + private int[] class_InnerClasses_N; + private CPClass[] class_InnerClasses_RC; + private int[] class_InnerClasses_F; + private List classInnerClassesOuterRCN; + private List classInnerClassesNameRUN; + + public ClassBands(Segment segment, int numClasses, int effort, boolean stripDebug) throws IOException { + super(effort, segment.getSegmentHeader()); + this.stripDebug = stripDebug; + this.segment = segment; + this.cpBands = segment.getCpBands(); + this.attrBands = segment.getAttrBands(); + class_this = new CPClass[numClasses]; + class_super = new CPClass[numClasses]; + class_interface_count = new int[numClasses]; + class_interface = new CPClass[numClasses][]; + class_field_count = new int[numClasses]; + class_method_count = new int[numClasses]; + field_descr = new CPNameAndType[numClasses][]; + field_flags = new long[numClasses][]; + method_descr = new CPNameAndType[numClasses][]; + method_flags = new long[numClasses][]; + // minor_versions = new int[numClasses]; + major_versions = new int[numClasses]; + class_flags = new long[numClasses]; + + class_RVA_bands = new MetadataBandGroup("RVA", MetadataBandGroup.CONTEXT_CLASS, cpBands, segmentHeader, effort); + class_RIA_bands = new MetadataBandGroup("RIA", MetadataBandGroup.CONTEXT_CLASS, cpBands, segmentHeader, effort); + field_RVA_bands = new MetadataBandGroup("RVA", MetadataBandGroup.CONTEXT_FIELD, cpBands, segmentHeader, effort); + field_RIA_bands = new MetadataBandGroup("RIA", MetadataBandGroup.CONTEXT_FIELD, cpBands, segmentHeader, effort); + method_RVA_bands = new MetadataBandGroup("RVA", MetadataBandGroup.CONTEXT_METHOD, cpBands, segmentHeader, effort); + method_RIA_bands = new MetadataBandGroup("RIA", MetadataBandGroup.CONTEXT_METHOD, cpBands, segmentHeader, effort); + method_RVPA_bands = new MetadataBandGroup("RVPA", MetadataBandGroup.CONTEXT_METHOD, cpBands, segmentHeader, effort); + method_RIPA_bands = new MetadataBandGroup("RIPA", MetadataBandGroup.CONTEXT_METHOD, cpBands, segmentHeader, effort); + method_AD_bands = new MetadataBandGroup("AD", MetadataBandGroup.CONTEXT_METHOD, cpBands, segmentHeader, effort); + + createNewAttributeBands(); + } + + private void createNewAttributeBands() throws IOException { + List classAttributeLayouts = attrBands.getClassAttributeLayouts(); + for (Iterator iterator = classAttributeLayouts.iterator(); iterator.hasNext();) { + AttributeDefinition def = (AttributeDefinition) iterator.next(); + classAttributeBands.add(new NewAttributeBands(effort, cpBands, segment.getSegmentHeader(), def)); + } + List methodAttributeLayouts = attrBands.getMethodAttributeLayouts(); + for (Iterator iterator = methodAttributeLayouts.iterator(); iterator.hasNext();) { + AttributeDefinition def = (AttributeDefinition) iterator.next(); + methodAttributeBands.add(new NewAttributeBands(effort, cpBands, segment.getSegmentHeader(), def)); + } + List fieldAttributeLayouts = attrBands.getFieldAttributeLayouts(); + for (Iterator iterator = fieldAttributeLayouts.iterator(); iterator.hasNext();) { + AttributeDefinition def = (AttributeDefinition) iterator.next(); + fieldAttributeBands.add(new NewAttributeBands(effort, cpBands, segment.getSegmentHeader(), def)); + } + List codeAttributeLayouts = attrBands.getCodeAttributeLayouts(); + for (Iterator iterator = codeAttributeLayouts.iterator(); iterator.hasNext();) { + AttributeDefinition def = (AttributeDefinition) iterator.next(); + codeAttributeBands.add(new NewAttributeBands(effort, cpBands, segment.getSegmentHeader(), def)); + } + } + + public void addClass(int major, int flags, String className, + String signature, String superName, String[] interfaces) { + class_this[index] = cpBands.getCPClass(className); + class_super[index] = cpBands.getCPClass(superName); + class_interface_count[index] = interfaces.length; + class_interface[index] = new CPClass[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + class_interface[index][i] = cpBands.getCPClass(interfaces[i]); + } + major_versions[index] = major; + class_flags[index] = flags; + if(!anySyntheticClasses && ((flags & (1 << 12)) != 0) && segment.getCurrentClassReader().hasSyntheticAttributes()) { + cpBands.addCPUtf8("Synthetic"); + anySyntheticClasses = true; + } + if((flags & Opcodes.ACC_DEPRECATED) != 0) { // ASM uses (1<<17) flag for deprecated + flags = flags & ~Opcodes.ACC_DEPRECATED; + flags = flags | (1<<20); + } + if(signature != null) { + class_flags[index] |= (1 << 19); + classSignature.add(cpBands.getCPSignature(signature)); + } + } + + public void currentClassReferencesInnerClass(CPClass inner) { + if(!(index >= class_this.length)) { + CPClass currentClass = class_this[index]; + if(currentClass != null && !currentClass.equals(inner) && !isInnerClassOf(currentClass.toString(), inner)) { + Set referencedInnerClasses = (Set)classReferencesInnerClass.get(currentClass); + if(referencedInnerClasses == null) { + referencedInnerClasses = new HashSet(); + classReferencesInnerClass.put(currentClass, referencedInnerClasses); + } + referencedInnerClasses.add(inner); + } + } + } + + private boolean isInnerClassOf(String possibleInner, CPClass possibleOuter) { + if(isInnerClass(possibleInner)) { + String superClassName = possibleInner.substring(0, possibleInner.lastIndexOf('$')); + if(superClassName.equals(possibleOuter.toString())) { + return true; + } else { // do this recursively + return isInnerClassOf(superClassName, possibleOuter); + } + } + return false; + } + + private boolean isInnerClass(String possibleInner) { + return possibleInner.indexOf('$') != -1; + } + + public void addField(int flags, String name, String desc, String signature, + Object value) { + flags = flags & 0xFFFF; + tempFieldDesc.add(cpBands.getCPNameAndType(name, desc)); + if (signature != null) { + fieldSignature.add(cpBands.getCPSignature(signature)); + flags |= (1 << 19); + } + if((flags & Opcodes.ACC_DEPRECATED) != 0) { // ASM uses (1<<17) flag for deprecated + flags = flags & ~Opcodes.ACC_DEPRECATED; + flags = flags | (1<<20); + } + if (value != null) { + fieldConstantValueKQ.add(cpBands.getConstant(value)); + flags |= (1 << 17); + } + if(!anySyntheticFields && ((flags & (1 << 12)) != 0) && segment.getCurrentClassReader().hasSyntheticAttributes()) { + cpBands.addCPUtf8("Synthetic"); + anySyntheticFields = true; + } + tempFieldFlags.add(new Long(flags)); + } + + /** + * All input classes for the segment have now been read in, so this method + * is called so that this class can calculate/complete anything it could not + * do while classes were being read. + */ + public void finaliseBands() { + int defaultMajorVersion = segmentHeader.getDefaultMajorVersion(); + for (int i = 0; i < class_flags.length; i++) { + int major = major_versions[i]; + if (major != defaultMajorVersion) { + class_flags[i] |= 1 << 24; + classFileVersionMajor.add(major); + classFileVersionMinor.add(0); + } + } + // Calculate code headers + codeHeaders = new int[codeHandlerCount.size()]; + int removed = 0; + for (int i = 0; i < codeHeaders.length; i++) { + int numHandlers = codeHandlerCount.get(i - removed); + int maxLocals = codeMaxLocals.get(i - removed); + int maxStack = codeMaxStack.get(i - removed); + if (numHandlers == 0) { + int header = maxLocals * 12 + maxStack + 1; + if (header < 145 && maxStack < 12) { + codeHeaders[i] = header; + } + } else if (numHandlers == 1) { + int header = maxLocals * 8 + maxStack + 145; + if (header < 209 && maxStack < 8) { + codeHeaders[i] = header; + } + } else if (numHandlers == 2) { + int header = maxLocals * 7 + maxStack + 209; + if (header < 256 && maxStack < 7) { + codeHeaders[i] = header; + } + } + if (codeHeaders[i] != 0) { // Remove the redundant values from + // codeHandlerCount, codeMaxLocals and + // codeMaxStack + codeHandlerCount.remove(i - removed); + codeMaxLocals.remove(i - removed); + codeMaxStack.remove(i - removed); + removed++; + } else if (!segment.getSegmentHeader().have_all_code_flags()) { + codeFlags.add(new Long(0)); + } + } + + // Compute any required IcLocals + IntList innerClassesN = new IntList(); + List icLocal = new ArrayList(); + for (int i = 0; i < class_this.length; i++) { + CPClass cpClass = class_this[i]; + Set referencedInnerClasses = (Set) classReferencesInnerClass.get(cpClass); + if(referencedInnerClasses != null) { + int innerN = 0; + List innerClasses = segment.getIcBands().getInnerClassesForOuter(cpClass.toString()); + if(innerClasses != null) { + for (Iterator iterator2 = innerClasses.iterator(); iterator2 + .hasNext();) { + referencedInnerClasses.remove(((IcTuple)iterator2.next()).C); + } + } + for (Iterator iterator2 = referencedInnerClasses.iterator(); iterator2 + .hasNext();) { + CPClass inner = (CPClass) iterator2.next(); + IcTuple icTuple = segment.getIcBands().getIcTuple(inner); + if(icTuple != null && ! icTuple.isAnonymous()) { + // should transmit an icLocal entry + icLocal.add(icTuple); + innerN++; + } + } + if(innerN != 0) { + innerClassesN.add(innerN); + class_flags[i] |= (1 << 23); + } + } + } + class_InnerClasses_N = innerClassesN.toArray(); + class_InnerClasses_RC = new CPClass[icLocal.size()]; + class_InnerClasses_F = new int[icLocal.size()]; + classInnerClassesOuterRCN = new ArrayList(); + classInnerClassesNameRUN = new ArrayList(); + for (int i = 0; i < class_InnerClasses_RC.length; i++) { + IcTuple icTuple = (IcTuple) icLocal.get(i); + class_InnerClasses_RC[i] = (icTuple.C); + if(icTuple.C2 == null && icTuple.N == null) { + class_InnerClasses_F[i] = 0; + } else { + if (icTuple.F == 0) { + class_InnerClasses_F[i] = 0x00010000; + } else { + class_InnerClasses_F[i] = icTuple.F; + } + classInnerClassesOuterRCN.add(icTuple.C2); + classInnerClassesNameRUN.add(icTuple.N); + } + } + // Calculate any backwards calls from metadata bands + IntList classAttrCalls = new IntList(); + IntList fieldAttrCalls = new IntList(); + IntList methodAttrCalls = new IntList(); + IntList codeAttrCalls = new IntList(); + + if(class_RVA_bands.hasContent()) { + classAttrCalls.add(class_RVA_bands.numBackwardsCalls()); + } + if(class_RIA_bands.hasContent()) { + classAttrCalls.add(class_RIA_bands.numBackwardsCalls()); + } + if(field_RVA_bands.hasContent()) { + fieldAttrCalls.add(field_RVA_bands.numBackwardsCalls()); + } + if(field_RIA_bands.hasContent()) { + fieldAttrCalls.add(field_RIA_bands.numBackwardsCalls()); + } + if(method_RVA_bands.hasContent()) { + methodAttrCalls.add(method_RVA_bands.numBackwardsCalls()); + } + if(method_RIA_bands.hasContent()) { + methodAttrCalls.add(method_RIA_bands.numBackwardsCalls()); + } + if(method_RVPA_bands.hasContent()) { + methodAttrCalls.add(method_RVPA_bands.numBackwardsCalls()); + } + if(method_RIPA_bands.hasContent()) { + methodAttrCalls.add(method_RIPA_bands.numBackwardsCalls()); + } + if(method_AD_bands.hasContent()) { + methodAttrCalls.add(method_AD_bands.numBackwardsCalls()); + } + + // Sort non-predefined attribute bands + Comparator comparator = new Comparator() { + public int compare(Object arg0, Object arg1) { + NewAttributeBands bands0 = (NewAttributeBands)arg0; + NewAttributeBands bands1 = (NewAttributeBands)arg1; + return bands0.getFlagIndex() - bands1.getFlagIndex(); + } + }; + Collections.sort(classAttributeBands, comparator); + Collections.sort(methodAttributeBands, comparator); + Collections.sort(fieldAttributeBands, comparator); + Collections.sort(codeAttributeBands, comparator); + + for (Iterator iterator = classAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + if(bands.isUsedAtLeastOnce()) { + int[] backwardsCallCounts = bands.numBackwardsCalls(); + for (int i = 0; i < backwardsCallCounts.length; i++) { + classAttrCalls.add(backwardsCallCounts[i]); + } + } + } + for (Iterator iterator = methodAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + if(bands.isUsedAtLeastOnce()) { + int[] backwardsCallCounts = bands.numBackwardsCalls(); + for (int i = 0; i < backwardsCallCounts.length; i++) { + methodAttrCalls.add(backwardsCallCounts[i]); + } + } + } + for (Iterator iterator = fieldAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + if(bands.isUsedAtLeastOnce()) { + int[] backwardsCallCounts = bands.numBackwardsCalls(); + for (int i = 0; i < backwardsCallCounts.length; i++) { + fieldAttrCalls.add(backwardsCallCounts[i]); + } + } + } + for (Iterator iterator = codeAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + if(bands.isUsedAtLeastOnce()) { + int[] backwardsCallCounts = bands.numBackwardsCalls(); + for (int i = 0; i < backwardsCallCounts.length; i++) { + codeAttrCalls.add(backwardsCallCounts[i]); + } + } + } + + class_attr_calls = classAttrCalls.toArray(); + field_attr_calls = fieldAttrCalls.toArray(); + method_attr_calls = methodAttrCalls.toArray(); + code_attr_calls = codeAttrCalls.toArray(); + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + PackingUtils.log("Writing class bands..."); + + byte[] encodedBand = encodeBandInt("class_this", getInts(class_this), + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_this[" + class_this.length + "]"); + + encodedBand = encodeBandInt("class_super", getInts(class_super), + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_super[" + class_super.length + "]"); + + encodedBand = encodeBandInt("class_interface_count", + class_interface_count, Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_interface_count[" + + class_interface_count.length + "]"); + + int totalInterfaces = sum(class_interface_count); + int[] classInterface = new int[totalInterfaces]; + int k = 0; + for (int i = 0; i < class_interface.length; i++) { + if (class_interface[i] != null) { + for (int j = 0; j < class_interface[i].length; j++) { + CPClass cpClass = class_interface[i][j]; + classInterface[k] = cpClass.getIndex(); + k++; + } + } + } + + encodedBand = encodeBandInt("class_interface", classInterface, + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_interface[" + classInterface.length + "]"); + + encodedBand = encodeBandInt("class_field_count", class_field_count, + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_field_count[" + class_field_count.length + + "]"); + + encodedBand = encodeBandInt("class_method_count", class_method_count, + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_method_count[" + class_method_count.length + + "]"); + + int totalFields = sum(class_field_count); + int[] fieldDescr = new int[totalFields]; + k = 0; + for (int i = 0; i < field_descr.length; i++) { + for (int j = 0; j < field_descr[i].length; j++) { + CPNameAndType descr = field_descr[i][j]; + fieldDescr[k] = descr.getIndex(); + k++; + } + } + + encodedBand = encodeBandInt("field_descr", fieldDescr, Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from field_descr[" + fieldDescr.length + "]"); + + writeFieldAttributeBands(out); + + int totalMethods = sum(class_method_count); + int[] methodDescr = new int[totalMethods]; + k = 0; + for (int i = 0; i < method_descr.length; i++) { + for (int j = 0; j < method_descr[i].length; j++) { + CPNameAndType descr = method_descr[i][j]; + methodDescr[k] = descr.getIndex(); + k++; + } + } + + encodedBand = encodeBandInt("method_descr", methodDescr, Codec.MDELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from method_descr[" + methodDescr.length + "]"); + + writeMethodAttributeBands(out); + writeClassAttributeBands(out); + writeCodeBands(out); + } + + private int sum(int[] ints) { + int sum = 0; + for (int i = 0; i < ints.length; i++) { + sum += ints[i]; + } + return sum; + } + + private void writeFieldAttributeBands(OutputStream out) throws IOException, + Pack200Exception { + byte[] encodedBand = encodeFlags("field_flags", field_flags, + Codec.UNSIGNED5, Codec.UNSIGNED5, segmentHeader + .have_field_flags_hi()); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from field_flags[" + field_flags.length + "]"); + + // *field_attr_count :UNSIGNED5 [COUNT(1<<16,...)] + // *field_attr_indexes :UNSIGNED5 [SUM(*field_attr_count)] + encodedBand = encodeBandInt("field_attr_calls", field_attr_calls, + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from field_attr_calls[" + field_attr_calls.length + + "]"); + + encodedBand = encodeBandInt("fieldConstantValueKQ", + cpEntryListToArray(fieldConstantValueKQ), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from fieldConstantValueKQ[" + + fieldConstantValueKQ.size() + "]"); + + encodedBand = encodeBandInt("fieldSignature", + cpEntryListToArray(fieldSignature), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from fieldSignature[" + fieldSignature.size() + "]"); + + field_RVA_bands.pack(out); + field_RIA_bands.pack(out); + for (Iterator iterator = fieldAttributeBands.iterator(); iterator + .hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + bands.pack(out); + } + } + + private void writeMethodAttributeBands(OutputStream out) + throws IOException, Pack200Exception { + byte[] encodedBand = encodeFlags("method_flags", method_flags, + Codec.UNSIGNED5, Codec.UNSIGNED5, segmentHeader + .have_method_flags_hi()); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from method_flags[" + method_flags.length + "]"); + + // *method_attr_count :UNSIGNED5 [COUNT(1<<16,...)] + // *method_attr_indexes :UNSIGNED5 [SUM(*method_attr_count)] + encodedBand = encodeBandInt("method_attr_calls", method_attr_calls, + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from method_attr_calls[" + method_attr_calls.length + + "]"); + + encodedBand = encodeBandInt("methodExceptionNumber", + methodExceptionNumber.toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from methodExceptionNumber[" + + methodExceptionNumber.size() + "]"); + + encodedBand = encodeBandInt("methodExceptionClasses", + cpEntryListToArray(methodExceptionClasses), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from methodExceptionClasses[" + + methodExceptionClasses.size() + "]"); + + encodedBand = encodeBandInt("methodSignature", + cpEntryListToArray(methodSignature), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils + .log("Wrote " + encodedBand.length + + " bytes from methodSignature[" + + methodSignature.size() + "]"); + + method_RVA_bands.pack(out); + method_RIA_bands.pack(out); + method_RVPA_bands.pack(out); + method_RIPA_bands.pack(out); + method_AD_bands.pack(out); + for (Iterator iterator = methodAttributeBands.iterator(); iterator + .hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + bands.pack(out); + } + } + + private void writeClassAttributeBands(OutputStream out) throws IOException, + Pack200Exception { + byte[] encodedBand = encodeFlags("class_flags", class_flags, + Codec.UNSIGNED5, Codec.UNSIGNED5, segmentHeader + .have_class_flags_hi()); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_flags[" + class_flags.length + "]"); + + // These bands are not needed, but could be used to reduce the size of + // the archive if there are enough different non-standard attributes + // defined that segmentHeader.have_class_flags_hi() is true. The same + // applies to method_attr_count, field_attr_count, code_attr_count etc. + + // *class_attr_count :UNSIGNED5 [COUNT(1<<16,...)] + // *class_attr_indexes :UNSIGNED5 [SUM(*class_attr_count)] + + encodedBand = encodeBandInt("class_attr_calls", class_attr_calls, + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_attr_calls[" + class_attr_calls.length + + "]"); + + encodedBand = encodeBandInt("classSourceFile", + cpEntryOrNullListToArray(classSourceFile), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils + .log("Wrote " + encodedBand.length + + " bytes from classSourceFile[" + + classSourceFile.size() + "]"); + + encodedBand = encodeBandInt("class_enclosing_method_RC", + cpEntryListToArray(classEnclosingMethodClass), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_enclosing_method_RC[" + + classEnclosingMethodClass.size() + "]"); + + encodedBand = encodeBandInt("class_EnclosingMethod_RDN", + cpEntryOrNullListToArray(classEnclosingMethodDesc), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_EnclosingMethod_RDN[" + + classEnclosingMethodDesc.size() + "]"); + + encodedBand = encodeBandInt("class_Signature_RS", + cpEntryListToArray(classSignature), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_Signature_RS[" + classSignature.size() + + "]"); + + class_RVA_bands.pack(out); + class_RIA_bands.pack(out); + + encodedBand = encodeBandInt("class_InnerClasses_N", + class_InnerClasses_N, Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_InnerClasses_N[" + + class_InnerClasses_N.length + "]"); + + encodedBand = encodeBandInt("class_InnerClasses_RC", + getInts(class_InnerClasses_RC), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_InnerClasses_RC[" + + class_InnerClasses_RC.length + "]"); + + encodedBand = encodeBandInt("class_InnerClasses_F", + class_InnerClasses_F, Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_InnerClasses_F[" + + class_InnerClasses_F.length + "]"); + + encodedBand = encodeBandInt("class_InnerClasses_outer_RCN", + cpEntryOrNullListToArray(classInnerClassesOuterRCN), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_InnerClasses_outer_RCN[" + + classInnerClassesOuterRCN.size() + "]"); + + encodedBand = encodeBandInt("class_InnerClasses_name_RUN", + cpEntryOrNullListToArray(classInnerClassesNameRUN), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from class_InnerClasses_name_RUN[" + + classInnerClassesNameRUN.size() + "]"); + + encodedBand = encodeBandInt("classFileVersionMinor", + classFileVersionMinor.toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from classFileVersionMinor[" + + classFileVersionMinor.size() + "]"); + + encodedBand = encodeBandInt("classFileVersionMajor", + classFileVersionMajor.toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from classFileVersionMajor[" + + classFileVersionMajor.size() + "]"); + + for (Iterator iterator = classAttributeBands.iterator(); iterator + .hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + bands.pack(out); + } + } + + private int[] getInts(CPClass[] cpClasses) { + int[] ints = new int[cpClasses.length]; + for (int i = 0; i < ints.length; i++) { + ints[i] = cpClasses[i].getIndex(); + } + return ints; + } + + private void writeCodeBands(OutputStream out) throws IOException, + Pack200Exception { + byte[] encodedBand = encodeBandInt("codeHeaders", codeHeaders, + Codec.BYTE1); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from codeHeaders[" + codeHeaders.length + "]"); + + encodedBand = encodeBandInt("codeMaxStack", codeMaxStack.toArray(), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from codeMaxStack[" + codeMaxStack.size() + "]"); + + encodedBand = encodeBandInt("codeMaxLocals", codeMaxLocals.toArray(), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from codeMaxLocals[" + codeMaxLocals.size() + "]"); + + encodedBand = encodeBandInt("codeHandlerCount", codeHandlerCount + .toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from codeHandlerCount[" + codeHandlerCount.size() + + "]"); + + encodedBand = encodeBandInt("codeHandlerStartP", + integerListToArray(codeHandlerStartP), Codec.BCI5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from codeHandlerStartP[" + codeHandlerStartP.size() + + "]"); + + encodedBand = encodeBandInt("codeHandlerEndPO", + integerListToArray(codeHandlerEndPO), Codec.BRANCH5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from codeHandlerEndPO[" + codeHandlerEndPO.size() + + "]"); + + encodedBand = encodeBandInt("codeHandlerCatchPO", + integerListToArray(codeHandlerCatchPO), Codec.BRANCH5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from codeHandlerCatchPO[" + codeHandlerCatchPO.size() + + "]"); + + encodedBand = encodeBandInt("codeHandlerClass", + cpEntryOrNullListToArray(codeHandlerClass), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from codeHandlerClass[" + codeHandlerClass.size() + + "]"); + + writeCodeAttributeBands(out); + } + + private void writeCodeAttributeBands(OutputStream out) throws IOException, + Pack200Exception { + byte[] encodedBand = encodeFlags("codeFlags", + longListToArray(codeFlags), Codec.UNSIGNED5, Codec.UNSIGNED5, + segmentHeader.have_code_flags_hi()); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from codeFlags[" + codeFlags.size() + "]"); + + // *code_attr_count :UNSIGNED5 [COUNT(1<<16,...)] + // *code_attr_indexes :UNSIGNED5 [SUM(*code_attr_count)] + encodedBand = encodeBandInt("code_attr_calls", code_attr_calls, + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils + .log("Wrote " + encodedBand.length + + " bytes from code_attr_calls[" + + code_attr_calls.length + "]"); + + encodedBand = encodeBandInt("code_LineNumberTable_N", + codeLineNumberTableN.toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LineNumberTable_N[" + + codeLineNumberTableN.size() + "]"); + + encodedBand = encodeBandInt("code_LineNumberTable_bci_P", + integerListToArray(codeLineNumberTableBciP), Codec.BCI5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LineNumberTable_bci_P[" + + codeLineNumberTableBciP.size() + "]"); + + encodedBand = encodeBandInt("code_LineNumberTable_line", + codeLineNumberTableLine.toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LineNumberTable_line[" + + codeLineNumberTableLine.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTable_N", + codeLocalVariableTableN.toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTable_N[" + + codeLocalVariableTableN.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTable_bci_P", + integerListToArray(codeLocalVariableTableBciP), Codec.BCI5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTable_bci_P[" + + codeLocalVariableTableBciP.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTable_span_O", + integerListToArray(codeLocalVariableTableSpanO), Codec.BRANCH5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTable_span_O[" + + codeLocalVariableTableSpanO.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTable_name_RU", + cpEntryListToArray(codeLocalVariableTableNameRU), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTable_name_RU[" + + codeLocalVariableTableNameRU.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTable_type_RS", + cpEntryListToArray(codeLocalVariableTableTypeRS), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTable_type_RS[" + + codeLocalVariableTableTypeRS.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTable_slot", + codeLocalVariableTableSlot.toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTable_slot[" + + codeLocalVariableTableSlot.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTypeTable_N", + codeLocalVariableTypeTableN.toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTypeTable_N[" + + codeLocalVariableTypeTableN.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTypeTable_bci_P", + integerListToArray(codeLocalVariableTypeTableBciP), Codec.BCI5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTypeTable_bci_P[" + + codeLocalVariableTypeTableBciP.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTypeTable_span_O", + integerListToArray(codeLocalVariableTypeTableSpanO), Codec.BRANCH5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTypeTable_span_O[" + + codeLocalVariableTypeTableSpanO.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTypeTable_name_RU", + cpEntryListToArray(codeLocalVariableTypeTableNameRU), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTypeTable_name_RU[" + + codeLocalVariableTypeTableNameRU.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTypeTable_type_RS", + cpEntryListToArray(codeLocalVariableTypeTableTypeRS), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTypeTable_type_RS[" + + codeLocalVariableTypeTableTypeRS.size() + "]"); + + encodedBand = encodeBandInt("code_LocalVariableTypeTable_slot", + codeLocalVariableTypeTableSlot.toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from code_LocalVariableTypeTable_slot[" + + codeLocalVariableTypeTableSlot.size() + "]"); + + for (Iterator iterator = codeAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + bands.pack(out); + } + } + + public void addMethod(int flags, String name, String desc, + String signature, String[] exceptions) { + CPNameAndType nt = cpBands.getCPNameAndType(name, desc); + tempMethodDesc.add(nt); + if (signature != null) { + methodSignature.add(cpBands.getCPSignature(signature)); + flags |= (1 << 19); + } + if (exceptions != null) { + methodExceptionNumber.add(exceptions.length); + for (int i = 0; i < exceptions.length; i++) { + methodExceptionClasses.add(cpBands.getCPClass(exceptions[i])); + } + flags |= (1 << 18); + } + if((flags & Opcodes.ACC_DEPRECATED) != 0) { // ASM uses (1<<17) flag for deprecated + flags = flags & ~Opcodes.ACC_DEPRECATED; + flags = flags | (1<<20); + } + tempMethodFlags.add(new Long(flags)); + numMethodArgs = countArgs(desc); + if(!anySyntheticMethods && ((flags & (1 << 12)) != 0) && segment.getCurrentClassReader().hasSyntheticAttributes()) { + cpBands.addCPUtf8("Synthetic"); + anySyntheticMethods = true; + } + } + + public void endOfMethod() { + if(codeFlags.size() > 0) { + long latestCodeFlag = ((Long)codeFlags.get(codeFlags.size() - 1)).longValue(); + int latestLocalVariableTableN = codeLocalVariableTableN.get(codeLocalVariableTableN.size() - 1); + if(latestCodeFlag == (1 << 2) && latestLocalVariableTableN == 0) { + codeLocalVariableTableN.remove(codeLocalVariableTableN.size() - 1); + codeFlags.remove(codeFlags.size() - 1); + codeFlags.add(new Integer(0)); + } + } + } + + protected static int countArgs(String descriptor) { + int bra = descriptor.indexOf('('); + int ket = descriptor.indexOf(')'); + if (bra == -1 || ket == -1 || ket < bra) + throw new IllegalArgumentException("No arguments"); + + boolean inType = false; + boolean consumingNextType = false; + int count = 0; + for (int i = bra + 1; i < ket; i++) { + char charAt = descriptor.charAt(i); + if (inType && charAt == ';') { + inType = false; + consumingNextType = false; + } else if (!inType && charAt == 'L') { + inType = true; + count++; + } else if (charAt == '[') { + consumingNextType = true; + } else if (inType) { + // NOP + } else { + if (consumingNextType) { + count++; + consumingNextType = false; + } else { + if (charAt == 'D' || charAt == 'J') { + count += 2; + } else { + count++; + } + } + } + } + return count; + } + + public void endOfClass() { // All the data for the current class has been + // read + int numFields = tempFieldDesc.size(); + class_field_count[index] = numFields; + field_descr[index] = new CPNameAndType[numFields]; + field_flags[index] = new long[numFields]; + for (int i = 0; i < numFields; i++) { + field_descr[index][i] = (CPNameAndType) tempFieldDesc.get(i); + field_flags[index][i] = ((Long) tempFieldFlags.get(i)).longValue(); + } + int numMethods = tempMethodDesc.size(); + class_method_count[index] = numMethods; + method_descr[index] = new CPNameAndType[numMethods]; + method_flags[index] = new long[numMethods]; + for (int i = 0; i < numMethods; i++) { + method_descr[index][i] = (CPNameAndType) tempMethodDesc.get(i); + method_flags[index][i] = ((Long) tempMethodFlags.get(i)) + .longValue(); + } + tempFieldDesc.clear(); + tempFieldFlags.clear(); + tempMethodDesc.clear(); + tempMethodFlags.clear(); + index++; + } + + public void addSourceFile(String source) { + String implicitSourceFileName = class_this[index].toString(); + if(implicitSourceFileName.indexOf('$') != -1) { + implicitSourceFileName = implicitSourceFileName.substring(0, implicitSourceFileName.indexOf('$')); + } + implicitSourceFileName = implicitSourceFileName + .substring(implicitSourceFileName.lastIndexOf('/') + 1) + + ".java"; + if (source.equals(implicitSourceFileName)) { + classSourceFile.add(null); + } else { + classSourceFile.add(cpBands.getCPUtf8(source)); + } + class_flags[index] |= (1 << 17); + } + + public void addEnclosingMethod(String owner, String name, String desc) { + class_flags[index] |= (1 << 18); + classEnclosingMethodClass.add(cpBands.getCPClass(owner)); + classEnclosingMethodDesc.add(name == null ? null : cpBands + .getCPNameAndType(name, desc)); + } + + public void addClassAttribute(NewAttribute attribute) { + // TODO: backwards calls + String attributeName = attribute.type; + for (Iterator iterator = classAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + if(bands.getAttributeName().equals(attributeName)) { + bands.addAttribute(attribute); + int flagIndex = bands.getFlagIndex(); + class_flags[index] |= (1 << flagIndex); + return; + } + } + throw new RuntimeException("No suitable definition for " + attributeName); + } + + public void addFieldAttribute(NewAttribute attribute) { + String attributeName = attribute.type; + for (Iterator iterator = fieldAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + if(bands.getAttributeName().equals(attributeName)) { + bands.addAttribute(attribute); + int flagIndex = bands.getFlagIndex(); + Long flags = (Long)tempFieldFlags.remove(tempFieldFlags.size() - 1); + tempFieldFlags.add(new Long(flags.longValue() | (1 << flagIndex))); + return; + } + } + throw new RuntimeException("No suitable definition for " + attributeName); + } + + public void addMethodAttribute(NewAttribute attribute) { + String attributeName = attribute.type; + for (Iterator iterator = methodAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + if(bands.getAttributeName().equals(attributeName)) { + bands.addAttribute(attribute); + int flagIndex = bands.getFlagIndex(); + Long flags = (Long)tempMethodFlags.remove(tempMethodFlags.size() - 1); + tempMethodFlags.add(new Long(flags.longValue() | (1 << flagIndex))); + return; + } + } + throw new RuntimeException("No suitable definition for " + attributeName); + } + + public void addCodeAttribute(NewAttribute attribute) { + String attributeName = attribute.type; + for (Iterator iterator = codeAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands bands = (NewAttributeBands) iterator.next(); + if(bands.getAttributeName().equals(attributeName)) { + bands.addAttribute(attribute); + int flagIndex = bands.getFlagIndex(); + Long flags = (Long)codeFlags.remove(codeFlags.size() - 1); + codeFlags.add(new Long(flags.longValue() | (1 << flagIndex))); + return; + } + } + throw new RuntimeException("No suitable definition for " + attributeName); + } + + public void addMaxStack(int maxStack, int maxLocals) { + Long latestFlag = (Long) tempMethodFlags + .remove(tempMethodFlags.size() - 1); + Long newFlag = new Long(latestFlag.intValue() | (1 << 17)); + tempMethodFlags.add(newFlag); + codeMaxStack.add(maxStack); + if ((newFlag.longValue() & (1 << 3)) == 0) { // not static + maxLocals--; // minus 'this' local + } + maxLocals -= numMethodArgs; + codeMaxLocals.add(maxLocals); + } + + public void addCode() { + codeHandlerCount.add(0); + if(!stripDebug) { + codeFlags.add(new Long((1 << 2))); + codeLocalVariableTableN.add(0); + } + } + + public void addHandler(Label start, Label end, Label handler, String type) { + int handlers = codeHandlerCount.remove(codeHandlerCount + .size() - 1); + codeHandlerCount.add(handlers + 1); + codeHandlerStartP.add(start); + codeHandlerEndPO.add(end); + codeHandlerCatchPO.add(handler); + codeHandlerClass.add(type == null ? null : cpBands.getCPClass(type)); + } + + public void addLineNumber(int line, Label start) { + Long latestCodeFlag = (Long) codeFlags.get(codeFlags.size() - 1); + if ((latestCodeFlag.intValue() & (1 << 1)) == 0) { + codeFlags.remove(codeFlags.size() - 1); + codeFlags.add(new Long(latestCodeFlag.intValue() | (1 << 1))); + codeLineNumberTableN.add(1); + } else { + codeLineNumberTableN + .increment(codeLineNumberTableN.size() - 1); + } + codeLineNumberTableLine.add(line); + codeLineNumberTableBciP.add(start); + } + + public void addLocalVariable(String name, String desc, String signature, + Label start, Label end, int indx) { + if (signature != null) { // LocalVariableTypeTable attribute + Long latestCodeFlag = (Long) codeFlags.get(codeFlags.size() - 1); + if ((latestCodeFlag.intValue() & (1 << 3)) == 0) { + codeFlags.remove(codeFlags.size() - 1); + codeFlags.add(new Long(latestCodeFlag.intValue() | (1 << 3))); + codeLocalVariableTypeTableN.add(1); + } else { + codeLocalVariableTypeTableN + .increment(codeLocalVariableTypeTableN.size() - 1); + } + codeLocalVariableTypeTableBciP.add(start); + codeLocalVariableTypeTableSpanO.add(end); + codeLocalVariableTypeTableNameRU.add(cpBands.getCPUtf8(name)); + codeLocalVariableTypeTableTypeRS.add(cpBands + .getCPSignature(signature)); + codeLocalVariableTypeTableSlot.add(indx); + } + // LocalVariableTable attribute + codeLocalVariableTableN + .increment(codeLocalVariableTableN.size() - 1); + codeLocalVariableTableBciP.add(start); + codeLocalVariableTableSpanO.add(end); + codeLocalVariableTableNameRU.add(cpBands.getCPUtf8(name)); + codeLocalVariableTableTypeRS.add(cpBands.getCPSignature(desc)); + codeLocalVariableTableSlot.add(indx); + } + + public void doBciRenumbering(IntList bciRenumbering, Map labelsToOffsets) { + renumberBci(codeLineNumberTableBciP, bciRenumbering, labelsToOffsets); + renumberBci(codeLocalVariableTableBciP, bciRenumbering, labelsToOffsets); + renumberOffsetBci(codeLocalVariableTableBciP, + codeLocalVariableTableSpanO, bciRenumbering, labelsToOffsets); + renumberBci(codeLocalVariableTypeTableBciP, bciRenumbering, + labelsToOffsets); + renumberOffsetBci(codeLocalVariableTypeTableBciP, + codeLocalVariableTypeTableSpanO, bciRenumbering, + labelsToOffsets); + renumberBci(codeHandlerStartP, bciRenumbering, labelsToOffsets); + renumberOffsetBci(codeHandlerStartP, codeHandlerEndPO, + bciRenumbering, labelsToOffsets); + renumberDoubleOffsetBci(codeHandlerStartP, codeHandlerEndPO, codeHandlerCatchPO, + bciRenumbering, labelsToOffsets); + + for (Iterator iterator = classAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands newAttributeBandSet = (NewAttributeBands) iterator.next(); + newAttributeBandSet.renumberBci(bciRenumbering, labelsToOffsets); + } + for (Iterator iterator = methodAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands newAttributeBandSet = (NewAttributeBands) iterator.next(); + newAttributeBandSet.renumberBci(bciRenumbering, labelsToOffsets); + } + for (Iterator iterator = fieldAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands newAttributeBandSet = (NewAttributeBands) iterator.next(); + newAttributeBandSet.renumberBci(bciRenumbering, labelsToOffsets); + } + for (Iterator iterator = codeAttributeBands.iterator(); iterator.hasNext();) { + NewAttributeBands newAttributeBandSet = (NewAttributeBands) iterator.next(); + newAttributeBandSet.renumberBci(bciRenumbering, labelsToOffsets); + } + } + + private void renumberBci(List list, IntList bciRenumbering, Map labelsToOffsets) { + for (int i = list.size() - 1; i >= 0; i--) { + Object label = list.get(i); + if (label instanceof Integer) { + break; + } else if (label instanceof Label) { + list.remove(i); + Integer bytecodeIndex = (Integer) labelsToOffsets.get(label); + list.add(i, new Integer(bciRenumbering.get(bytecodeIndex.intValue()))); + } + } + } + + private void renumberOffsetBci(List relative, List list, + IntList bciRenumbering, Map labelsToOffsets) { + for (int i = list.size() - 1; i >= 0; i--) { + Object label = list.get(i); + if (label instanceof Integer) { + break; + } else if (label instanceof Label) { + list.remove(i); + Integer bytecodeIndex = (Integer) labelsToOffsets.get(label); + Integer renumberedOffset = new Integer(bciRenumbering + .get(bytecodeIndex.intValue()) + - ((Integer) relative.get(i)).intValue()); + list.add(i, renumberedOffset); + } + } + } + + private void renumberDoubleOffsetBci(List relative, List firstOffset, List list, + IntList bciRenumbering, Map labelsToOffsets) { + // TODO: There's probably a nicer way of doing this... + for (int i = list.size() - 1; i >= 0; i--) { + Object label = list.get(i); + if (label instanceof Integer) { + break; + } else if (label instanceof Label) { + list.remove(i); + Integer bytecodeIndex = (Integer) labelsToOffsets.get(label); + Integer renumberedOffset = new Integer(bciRenumbering + .get(bytecodeIndex.intValue()) + - ((Integer) relative.get(i)).intValue() - ((Integer) firstOffset.get(i)).intValue()); + list.add(i, renumberedOffset); + } + } + } + + public boolean isAnySyntheticClasses() { + return anySyntheticClasses; + } + + public boolean isAnySyntheticFields() { + return anySyntheticFields; + } + + public boolean isAnySyntheticMethods() { + return anySyntheticMethods; + } + + public void addParameterAnnotation(int parameter, String desc, + boolean visible, List nameRU, List t, List values, List caseArrayN, List nestTypeRS, List nestNameRU, List nestPairN) { + if(visible) { + method_RVPA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + Integer flag = (Integer) tempMethodFlags.remove(tempMethodFlags.size() - 1); + if((flag.intValue() & (1<<23)) != 0) { + method_RVPA_bands.incrementAnnoN(); + } else { + method_RVPA_bands.newEntryInAnnoN(); + } + tempMethodFlags.add(new Integer(flag.intValue() | (1<<23))); + } else { + method_RIPA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + Integer flag = (Integer) tempMethodFlags.remove(tempMethodFlags.size() - 1); + if((flag.intValue() & (1<<24)) != 0) { + method_RIPA_bands.incrementAnnoN(); + } else { + method_RIPA_bands.newEntryInAnnoN(); + } + tempMethodFlags.add(new Integer(flag.intValue() | (1<<24))); + } + } + + public void addAnnotation(int context, String desc, boolean visible, List nameRU, List t, List values, List caseArrayN, List nestTypeRS, List nestNameRU, List nestPairN) { + switch (context) { + case MetadataBandGroup.CONTEXT_CLASS: + if(visible) { + class_RVA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + if((class_flags[index] & (1<<21)) != 0) { + class_RVA_bands.incrementAnnoN(); + } else { + class_RVA_bands.newEntryInAnnoN(); + class_flags[index] = class_flags[index] | (1<<21); + } + } else { + class_RIA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + if((class_flags[index] & (1<<22)) != 0) { + class_RIA_bands.incrementAnnoN(); + } else { + class_RIA_bands.newEntryInAnnoN(); + class_flags[index] = class_flags[index] | (1<<22); + } + } + break; + case MetadataBandGroup.CONTEXT_FIELD: + if(visible) { + field_RVA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + Long flag = (Long) tempFieldFlags.remove(tempFieldFlags.size() - 1); + if((flag.intValue() & (1<<21)) != 0) { + field_RVA_bands.incrementAnnoN(); + } else { + field_RVA_bands.newEntryInAnnoN(); + } + tempFieldFlags.add(new Long(flag.intValue() | (1<<21))); + } else { + field_RIA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + Long flag = (Long) tempFieldFlags.remove(tempFieldFlags.size() - 1); + if((flag.intValue() & (1<<22)) != 0) { + field_RIA_bands.incrementAnnoN(); + } else { + field_RIA_bands.newEntryInAnnoN(); + } + tempFieldFlags.add(new Long(flag.intValue() | (1<<22))); + } + break; + case MetadataBandGroup.CONTEXT_METHOD: + if(visible) { + method_RVA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + Long flag = (Long) tempMethodFlags.remove(tempMethodFlags.size() - 1); + if((flag.intValue() & (1<<21)) != 0) { + method_RVA_bands.incrementAnnoN(); + } else { + method_RVA_bands.newEntryInAnnoN(); + } + tempMethodFlags.add(new Long(flag.intValue() | (1<<21))); + } else { + method_RIA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + Long flag = (Long) tempMethodFlags.remove(tempMethodFlags.size() - 1); + if((flag.intValue() & (1<<22)) != 0) { + method_RIA_bands.incrementAnnoN(); + } else { + method_RIA_bands.newEntryInAnnoN(); + } + tempMethodFlags.add(new Long(flag.intValue() | (1<<22))); + } + break; + } + } + + public void addAnnotationDefault(List nameRU, List t, List values, List caseArrayN, List nestTypeRS, List nestNameRU, List nestPairN) { + method_AD_bands.addAnnotation(null, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + Long flag = (Long) tempMethodFlags.remove(tempMethodFlags.size() - 1); + tempMethodFlags.add(new Long(flag.longValue() | (1<<25))); + } + + /** + * Remove all entries for the current class + */ + public void removeCurrentClass() { + // Note - this doesn't remove any entries added to the constant pool but + // that shouldn't be a problem + if ((class_flags[index] & (1 << 17)) != 0) { + classSourceFile.remove(classSourceFile.size() - 1); + } + if ((class_flags[index] & (1 << 18)) != 0) { + classEnclosingMethodClass + .remove(classEnclosingMethodClass.size() - 1); + classEnclosingMethodDesc + .remove(classEnclosingMethodDesc.size() - 1); + } + if ((class_flags[index] & (1 << 19)) != 0) { + classSignature.remove(classSignature.size() - 1); + } + if ((class_flags[index] & (1 << 21)) != 0) { + class_RVA_bands.removeLatest(); + } + if ((class_flags[index] & (1 << 22)) != 0) { + class_RIA_bands.removeLatest(); + } + for (Iterator iterator = tempFieldFlags.iterator(); iterator.hasNext();) { + Long flagsL = (Long) iterator.next(); + long flags = flagsL.longValue(); + if ((flags & (1 << 19)) != 0) { + fieldSignature.remove(fieldSignature.size() - 1); + } + if ((flags & (1 << 17)) != 0) { + fieldConstantValueKQ.remove(fieldConstantValueKQ.size() - 1); + } + if ((flags & (1 << 21)) != 0) { + field_RVA_bands.removeLatest(); + } + if ((flags & (1 << 22)) != 0) { + field_RIA_bands.removeLatest(); + } + } + for (Iterator iterator = tempMethodFlags.iterator(); iterator.hasNext();) { + Long flagsL = (Long) iterator.next(); + long flags = flagsL.longValue(); + if ((flags & (1 << 19)) != 0) { + methodSignature.remove(methodSignature.size() - 1); + } + if ((flags & (1 << 18)) != 0) { + int exceptions = methodExceptionNumber + .remove(methodExceptionNumber.size() - 1); + for (int i = 0; i < exceptions; i++) { + methodExceptionClasses + .remove(methodExceptionClasses.size() - 1); + } + } + if ((flags & (1 << 17)) != 0) { // has code attribute + codeMaxLocals.remove(codeMaxLocals.size() - 1); + codeMaxStack.remove(codeMaxStack.size() - 1); + int handlers = codeHandlerCount + .remove(codeHandlerCount.size() - 1); + for (int i = 0; i < handlers; i++) { + int index = codeHandlerStartP.size() - 1; + codeHandlerStartP.remove(index); + codeHandlerEndPO.remove(index); + codeHandlerCatchPO.remove(index); + codeHandlerClass.remove(index); + } + if (!stripDebug) { + long cdeFlags = ((Long) codeFlags + .remove(codeFlags.size() - 1)).longValue(); + int numLocalVariables = codeLocalVariableTableN + .remove(codeLocalVariableTableN.size() - 1); + for (int i = 0; i < numLocalVariables; i++) { + int location = codeLocalVariableTableBciP.size() - 1; + codeLocalVariableTableBciP.remove(location); + codeLocalVariableTableSpanO.remove(location); + codeLocalVariableTableNameRU.remove(location); + codeLocalVariableTableTypeRS.remove(location); + codeLocalVariableTableSlot.remove(location); + } + if ((cdeFlags & (1 << 3)) != 0) { + int numLocalVariablesInTypeTable = codeLocalVariableTypeTableN + .remove(codeLocalVariableTypeTableN.size() - 1); + for (int i = 0; i < numLocalVariablesInTypeTable; i++) { + int location = codeLocalVariableTypeTableBciP + .size() - 1; + codeLocalVariableTypeTableBciP.remove(location); + codeLocalVariableTypeTableSpanO.remove(location); + codeLocalVariableTypeTableNameRU.remove(location); + codeLocalVariableTypeTableTypeRS.remove(location); + codeLocalVariableTypeTableSlot.remove(location); + } + } + if ((cdeFlags & (1 << 1)) != 0) { + int numLineNumbers = codeLineNumberTableN + .remove(codeLineNumberTableN.size() - 1); + for (int i = 0; i < numLineNumbers; i++) { + int location = codeLineNumberTableBciP.size() - 1; + codeLineNumberTableBciP.remove(location); + codeLineNumberTableLine.remove(location); + } + } + } + } + if ((flags & (1 << 21)) != 0) { + method_RVA_bands.removeLatest(); + } + if ((flags & (1 << 22)) != 0) { + method_RIA_bands.removeLatest(); + } + if ((flags & (1 << 23)) != 0) { + method_RVPA_bands.removeLatest(); + } + if ((flags & (1 << 24)) != 0) { + method_RIPA_bands.removeLatest(); + } + if ((flags & (1 << 25)) != 0) { + method_AD_bands.removeLatest(); + } + } + class_this[index] = null; + class_super[index] = null; + class_interface_count[index] = 0; + class_interface[index] = null; + major_versions[index] = 0; + class_flags[index] = 0; + tempFieldDesc.clear(); + tempFieldFlags.clear(); + tempMethodDesc.clear(); + tempMethodFlags.clear(); + index--; + } +} diff --git a/src/main/java/org/apache/harmony/pack200/Codec.java b/src/main/java/org/apache/harmony/pack200/Codec.java new file mode 100644 index 0000000..83b80e0 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/Codec.java @@ -0,0 +1,246 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.InputStream; + + +/** + * A Codec allows a sequence of bytes to be decoded into integer values (or vice + * versa). + * + * There are a number of standard Codecs ({@link #UDELTA5}, {@link #UNSIGNED5}, + * {@link #BYTE1}, {@link #CHAR3}) that are used in the implementation of many + * bands; but there are a variety of other ones, and indeed the specification + * assumes that other combinations of values can result in more specific and + * efficient formats. There are also a sequence of canonical encodings defined + * by the Pack200 specification, which allow a Codec to be referred to by + * canonical number. {@link CodecEncoding#getCodec(int, InputStream, Codec)}) + */ +public abstract class Codec { + + /** + * BCI5 = (5,4): Used for storing branching information in bytecode. + */ + public static final BHSDCodec BCI5 = new BHSDCodec(5, 4); + + /** + * BRANCH5 = (5,4,2): Used for storing branching information in bytecode. + */ + public static final BHSDCodec BRANCH5 = new BHSDCodec(5, 4, 2); + + /** + * BYTE1 = (1,256): Used for storing plain bytes. + */ + public static final BHSDCodec BYTE1 = new BHSDCodec(1, 256); + + /** + * CHAR3 = (3,128): Used for storing text (UTF-8) strings. NB This isn't + * quite the same as UTF-8, but has similar properties; ASCII characters + * < 127 are stored in a single byte. + */ + public static final BHSDCodec CHAR3 = new BHSDCodec(3, 128); + + /** + * DELTA5 = (5,64,1,1): Used for the majority of numerical codings where + * there is a correlated sequence of signed values. + */ + public static final BHSDCodec DELTA5 = new BHSDCodec(5, 64, 1, 1); + + /** + * MDELTA5 = (5,64,2,1): Used for the majority of numerical codings where + * there is a correlated sequence of signed values, but where most of them + * are expected to be non-negative. + */ + public static final BHSDCodec MDELTA5 = new BHSDCodec(5, 64, 2, 1); + + /** + * SIGNED5 = (5,64,1): Used for small signed values. + */ + public static final BHSDCodec SIGNED5 = new BHSDCodec(5, 64, 1); + + /** + * UDELTA5 = (5,64,0,1): Used for the majority of numerical codings where + * there is a correlated sequence of unsigned values. + */ + public static final BHSDCodec UDELTA5 = new BHSDCodec(5, 64, 0, 1); + + /** + * UNSIGNED5 = (5,64): Used for small unsigned values. + */ + public static final BHSDCodec UNSIGNED5 = new BHSDCodec(5, 64); + + public int lastBandLength; + + /** + * Decode a sequence of bytes from the given input stream, returning the + * value as a long. Note that this method can only be applied for non-delta + * encodings. + * + * @param in + * the input stream to read from + * @return the value as a long + * @throws IOException + * if there is a problem reading from the underlying input + * stream + * @throws Pack200Exception + * if the encoding is a delta encoding + */ + public abstract int decode(InputStream in) throws IOException, + Pack200Exception; + + /** + * Encode a single value into a sequence of bytes. + * + * @param value + * the value to encode + * @param last + * the previous value encoded (for delta encodings) + * @return the encoded bytes + * @throws Pack200Exception + */ + public abstract byte[] encode(int value, int last) + throws Pack200Exception; + + /** + * Encode a single value into a sequence of bytes. Note that this method can + * only be used for non-delta encodings. + * + * @param value + * the value to encode + * @return the encoded bytes + * @throws Pack200Exception + */ + public abstract byte[] encode(int value) throws Pack200Exception; + + /** + * Decode a sequence of bytes from the given input stream, returning the + * value as a long. If this encoding is a delta encoding (d=1) then the + * previous value must be passed in as a parameter. If it is a non-delta + * encoding, then it does not matter what value is passed in, so it makes + * sense for the value to be passed in by default using code similar to: + * + *
+     * long last = 0;
+     * while (condition) {
+     *     last = codec.decode(in, last);
+     *     // do something with last
+     * }
+     * 
+ * + * @param in + * the input stream to read from + * @param last + * the previous value read, which must be supplied if the codec + * is a delta encoding + * @return the value as a long + * @throws IOException + * if there is a problem reading from the underlying input + * stream + * @throws Pack200Exception + * if there is a problem decoding the value or that the value is + * invalid + */ + public abstract int decode(InputStream in, long last) throws IOException, + Pack200Exception; + + /** + * Decodes a sequence of n values from in. + * This should probably be used in most cases, since some codecs (such as + * + * @{link PopCodec}) only work when the number of values to be read is + * known. + * + * @param n + * the number of values to decode + * @param in + * the input stream to read from + * @return an array of int values corresponding to values + * decoded + * @throws IOException + * if there is a problem reading from the underlying input + * stream + * @throws Pack200Exception + * if there is a problem decoding the value or that the value is + * invalid + */ + public int[] decodeInts(int n, InputStream in) throws IOException, + Pack200Exception { + lastBandLength = 0; + int result[] = new int[n]; + int last = 0; + for (int i = 0; i < n; i++) { + result[i] = last = decode(in, last); + } + return result; + } + + /** + * Decodes a sequence of n values from in. + * + * @param n + * the number of values to decode + * @param in + * the input stream to read from + * @param firstValue + * the first value in the band if it has already been read + * @return an array of int values corresponding to values + * decoded, with firstValue as the first value in the array. + * @throws IOException + * if there is a problem reading from the underlying input + * stream + * @throws Pack200Exception + * if there is a problem decoding the value or that the value is + * invalid + */ + public int[] decodeInts(int n, InputStream in, int firstValue) + throws IOException, Pack200Exception { + int result[] = new int[n + 1]; + result[0] = firstValue; + int last = firstValue; + for (int i = 1; i < n + 1; i++) { + result[i] = last = decode(in, last); + } + return result; + } + + /** + * Encode a sequence of integers into a byte array + * + * @param ints + * the values to encode + * @return byte[] encoded bytes + * @throws Pack200Exception + * if there is a problem encoding any of the values + */ + public byte[] encode(int[] ints) throws Pack200Exception { + int total = 0; + byte[][] bytes = new byte[ints.length][]; + for (int i = 0; i < ints.length; i++) { + bytes[i] = encode(ints[i], i > 0 ? ints[i-1] : 0); + total += bytes[i].length; + } + byte[] encoded = new byte[total]; + int index = 0; + for (int i = 0; i < bytes.length; i++) { + System.arraycopy(bytes[i], 0, encoded, index, bytes[i].length); + index += bytes[i].length; + } + return encoded; + } +} diff --git a/src/main/java/org/apache/harmony/pack200/CodecEncoding.java b/src/main/java/org/apache/harmony/pack200/CodecEncoding.java new file mode 100644 index 0000000..c7df8a4 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CodecEncoding.java @@ -0,0 +1,346 @@ +/* + * 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.pack200; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + + +/** + * CodecEncoding is used to get the right Codec for a given meta-encoding + */ +public class CodecEncoding { + + /** + * The canonical encodings are defined to allow a single byte to represent + * one of the standard encodings. The following values are defined in the + * Pack200 specification, and this array cannot be changed. + */ + private static final BHSDCodec[] canonicalCodec = { null, + new BHSDCodec(1, 256), new BHSDCodec(1, 256, 1), + new BHSDCodec(1, 256, 0, 1), new BHSDCodec(1, 256, 1, 1), + new BHSDCodec(2, 256), new BHSDCodec(2, 256, 1), + new BHSDCodec(2, 256, 0, 1), new BHSDCodec(2, 256, 1, 1), + new BHSDCodec(3, 256), new BHSDCodec(3, 256, 1), + new BHSDCodec(3, 256, 0, 1), new BHSDCodec(3, 256, 1, 1), + new BHSDCodec(4, 256), new BHSDCodec(4, 256, 1), + new BHSDCodec(4, 256, 0, 1), new BHSDCodec(4, 256, 1, 1), + new BHSDCodec(5, 4), new BHSDCodec(5, 4, 1), + new BHSDCodec(5, 4, 2), new BHSDCodec(5, 16), + new BHSDCodec(5, 16, 1), new BHSDCodec(5, 16, 2), + new BHSDCodec(5, 32), new BHSDCodec(5, 32, 1), + new BHSDCodec(5, 32, 2), new BHSDCodec(5, 64), + new BHSDCodec(5, 64, 1), new BHSDCodec(5, 64, 2), + new BHSDCodec(5, 128), new BHSDCodec(5, 128, 1), + new BHSDCodec(5, 128, 2), new BHSDCodec(5, 4, 0, 1), + new BHSDCodec(5, 4, 1, 1), new BHSDCodec(5, 4, 2, 1), + new BHSDCodec(5, 16, 0, 1), new BHSDCodec(5, 16, 1, 1), + new BHSDCodec(5, 16, 2, 1), new BHSDCodec(5, 32, 0, 1), + new BHSDCodec(5, 32, 1, 1), new BHSDCodec(5, 32, 2, 1), + new BHSDCodec(5, 64, 0, 1), new BHSDCodec(5, 64, 1, 1), + new BHSDCodec(5, 64, 2, 1), new BHSDCodec(5, 128, 0, 1), + new BHSDCodec(5, 128, 1, 1), new BHSDCodec(5, 128, 2, 1), + new BHSDCodec(2, 192), new BHSDCodec(2, 224), + new BHSDCodec(2, 240), new BHSDCodec(2, 248), + new BHSDCodec(2, 252), new BHSDCodec(2, 8, 0, 1), + new BHSDCodec(2, 8, 1, 1), new BHSDCodec(2, 16, 0, 1), + new BHSDCodec(2, 16, 1, 1), new BHSDCodec(2, 32, 0, 1), + new BHSDCodec(2, 32, 1, 1), new BHSDCodec(2, 64, 0, 1), + new BHSDCodec(2, 64, 1, 1), new BHSDCodec(2, 128, 0, 1), + new BHSDCodec(2, 128, 1, 1), new BHSDCodec(2, 192, 0, 1), + new BHSDCodec(2, 192, 1, 1), new BHSDCodec(2, 224, 0, 1), + new BHSDCodec(2, 224, 1, 1), new BHSDCodec(2, 240, 0, 1), + new BHSDCodec(2, 240, 1, 1), new BHSDCodec(2, 248, 0, 1), + new BHSDCodec(2, 248, 1, 1), new BHSDCodec(3, 192), + new BHSDCodec(3, 224), new BHSDCodec(3, 240), + new BHSDCodec(3, 248), new BHSDCodec(3, 252), + new BHSDCodec(3, 8, 0, 1), new BHSDCodec(3, 8, 1, 1), + new BHSDCodec(3, 16, 0, 1), new BHSDCodec(3, 16, 1, 1), + new BHSDCodec(3, 32, 0, 1), new BHSDCodec(3, 32, 1, 1), + new BHSDCodec(3, 64, 0, 1), new BHSDCodec(3, 64, 1, 1), + new BHSDCodec(3, 128, 0, 1), new BHSDCodec(3, 128, 1, 1), + new BHSDCodec(3, 192, 0, 1), new BHSDCodec(3, 192, 1, 1), + new BHSDCodec(3, 224, 0, 1), new BHSDCodec(3, 224, 1, 1), + new BHSDCodec(3, 240, 0, 1), new BHSDCodec(3, 240, 1, 1), + new BHSDCodec(3, 248, 0, 1), new BHSDCodec(3, 248, 1, 1), + new BHSDCodec(4, 192), new BHSDCodec(4, 224), + new BHSDCodec(4, 240), new BHSDCodec(4, 248), + new BHSDCodec(4, 252), new BHSDCodec(4, 8, 0, 1), + new BHSDCodec(4, 8, 1, 1), new BHSDCodec(4, 16, 0, 1), + new BHSDCodec(4, 16, 1, 1), new BHSDCodec(4, 32, 0, 1), + new BHSDCodec(4, 32, 1, 1), new BHSDCodec(4, 64, 0, 1), + new BHSDCodec(4, 64, 1, 1), new BHSDCodec(4, 128, 0, 1), + new BHSDCodec(4, 128, 1, 1), new BHSDCodec(4, 192, 0, 1), + new BHSDCodec(4, 192, 1, 1), new BHSDCodec(4, 224, 0, 1), + new BHSDCodec(4, 224, 1, 1), new BHSDCodec(4, 240, 0, 1), + new BHSDCodec(4, 240, 1, 1), new BHSDCodec(4, 248, 0, 1), + new BHSDCodec(4, 248, 1, 1) }; + + private static Map canonicalCodecsToSpecifiers; + + + /** + * Returns the codec specified by the given value byte and optional byte + * header. If the value is >=116, then bytes may be consumed from the + * secondary input stream, which is taken to be the contents of the + * band_headers byte array. Since the values from this are consumed and not + * repeated, the input stream should be reused for subsequent encodings. + * This does not therefore close the input stream. + * + * @param value + * the canonical encoding value + * @param in + * the input stream to read additional byte headers from + * @param defaultCodec + * TODO + * @return the corresponding codec, or null if the default + * should be used + * + * @throws IOException + * if there is a problem reading from the input stream (which in + * reality, is never, since the band_headers are likely stored + * in a byte array and accessed via a ByteArrayInputStream. + * However, an EOFException could occur if things go wrong) + * @throws Pack200Exception + */ + public static Codec getCodec(int value, InputStream in, Codec defaultCodec) + throws IOException, Pack200Exception { + // Sanity check to make sure that no-one has changed + // the canonical codecs, which would really cause havoc + if (canonicalCodec.length != 116) { + throw new Error( + "Canonical encodings have been incorrectly modified"); + } + if (value < 0) { + throw new IllegalArgumentException( + "Encoding cannot be less than zero"); + } else if (value == 0) { + return defaultCodec; + } else if (value <= 115) { + return canonicalCodec[value]; + } else if (value == 116) { + int code = in.read(); + if (code == -1) { + throw new EOFException( + "End of buffer read whilst trying to decode codec"); + } + int d = (code & 0x01); + int s = (code >> 1 & 0x03); + int b = (code >> 3 & 0x07) + 1; // this might result in an invalid + // number, but it's checked in the + // Codec constructor + code = in.read(); + if (code == -1) { + throw new EOFException( + "End of buffer read whilst trying to decode codec"); + } + int h = code + 1; + // This handles the special cases for invalid combinations of data. + return new BHSDCodec(b, h, s, d); + } else if (value >= 117 && value <= 140) { // Run codec + int offset = value - 117; + int kx = offset & 3; + boolean kbflag = (offset >> 2 & 1) == 1; + boolean adef = (offset >> 3 & 1) == 1; + boolean bdef = (offset >> 4 & 1) == 1; + // If both A and B use the default encoding, what's the point of + // having a run of default values followed by default values + if (adef && bdef) { + throw new Pack200Exception( + "ADef and BDef should never both be true"); + } + int kb = (kbflag ? in.read() : 3); + int k = (kb + 1) * (int) Math.pow(16, kx); + Codec aCodec, bCodec; + if (adef) { + aCodec = defaultCodec; + } else { + aCodec = getCodec(in.read(), in, defaultCodec); + } + if (bdef) { + bCodec = defaultCodec; + } else { + bCodec = getCodec(in.read(), in, defaultCodec); + } + return new RunCodec(k, aCodec, bCodec); + } else if (value >= 141 && value <= 188) { // Population Codec + int offset = value - 141; + boolean fdef = (offset & 1) == 1; + boolean udef = (offset >> 1 & 1) == 1; + int tdefl = offset >> 2; + boolean tdef = tdefl != 0; + // From section 6.7.3 of spec + final int[] tdefToL = { 0, 4, 8, 16, 32, 64, 128, 192, 224, 240, + 248, 252 }; + int l = tdefToL[tdefl]; + // NOTE: Do not re-factor this to bring out uCodec; the order in + // which + // they are read from the stream is important + if (tdef) { + Codec fCodec = (fdef ? defaultCodec : getCodec(in.read(), in, + defaultCodec)); + Codec uCodec = (udef ? defaultCodec : getCodec(in.read(), in, + defaultCodec)); + // Unfortunately, if tdef, then tCodec depends both on l and + // also on k, the + // number of items read from the fCodec. So we don't know in + // advance what + // the codec will be. + return new PopulationCodec(fCodec, l, uCodec); + } else { + Codec fCodec = (fdef ? defaultCodec : getCodec(in.read(), in, + defaultCodec)); + Codec tCodec = getCodec(in.read(), in, defaultCodec); + Codec uCodec = (udef ? defaultCodec : getCodec(in.read(), in, + defaultCodec)); + return new PopulationCodec(fCodec, tCodec, uCodec); + } + } else { + throw new Pack200Exception("Invalid codec encoding byte (" + value + + ") found"); + } + } + + public static int getSpecifierForDefaultCodec(BHSDCodec defaultCodec) { + return getSpecifier(defaultCodec, null)[0]; + } + + public static int[] getSpecifier(Codec codec, Codec defaultForBand) { + // lazy initialization + if(canonicalCodecsToSpecifiers == null) { + HashMap reverseMap = new HashMap(canonicalCodec.length); + for (int i = 0; i < canonicalCodec.length; i++) { + reverseMap.put(canonicalCodec[i], new Integer(i)); + } + canonicalCodecsToSpecifiers = reverseMap; + } + + if(canonicalCodecsToSpecifiers.containsKey(codec)) { + return new int[] {((Integer)canonicalCodecsToSpecifiers.get(codec)).intValue()}; + } else if (codec instanceof BHSDCodec) { + // Cache these? + BHSDCodec bhsdCodec = (BHSDCodec)codec; + int[] specifiers = new int[3]; + specifiers[0] = 116; + specifiers[1] = (bhsdCodec.isDelta() ? 1 : 0) + 2 + * bhsdCodec.getS() + 8 * (bhsdCodec.getB()-1); + specifiers[2] = bhsdCodec.getH() - 1; + return specifiers; + } else if (codec instanceof RunCodec) { + RunCodec runCodec = (RunCodec) codec; + int k = runCodec.getK(); + int kb; + int kx; + if(k <= 256) { + kb = 0; + kx = k - 1; + } else if (k <= 4096) { + kb = 1; + kx = k/16 - 1; + } else if (k <= 65536) { + kb = 2; + kx = k/256 - 1; + } else { + kb = 3; + kx = k/4096 - 1; + } + Codec aCodec = runCodec.getACodec(); + Codec bCodec = runCodec.getBCodec(); + int abDef = 0; + if(aCodec.equals(defaultForBand)) { + abDef = 1; + } else if (bCodec.equals(defaultForBand)) { + abDef = 2; + } + int first = 117 + kb + (kx==3 ? 0 : 4) + (8 * abDef); + int[] aSpecifier = abDef == 1 ? new int[0] : getSpecifier(aCodec, defaultForBand); + int[] bSpecifier = abDef == 2 ? new int[0] : getSpecifier(bCodec, defaultForBand); + int[] specifier = new int[1 + (kx==3 ? 0 : 1) + aSpecifier.length + bSpecifier.length]; + specifier[0] = first; + int index = 1; + if(kx != 3) { + specifier[1] = kx; + index++; + } + for (int i = 0; i < aSpecifier.length; i++) { + specifier[index] = aSpecifier[i]; + index++; + } + for (int i = 0; i < bSpecifier.length; i++) { + specifier[index] = bSpecifier[i]; + index++; + } + return specifier; + } else if (codec instanceof PopulationCodec) { + PopulationCodec populationCodec = (PopulationCodec) codec; + Codec tokenCodec = populationCodec.getTokenCodec(); + Codec favouredCodec = populationCodec.getFavouredCodec(); + Codec unfavouredCodec = populationCodec.getUnfavouredCodec(); + int fDef = favouredCodec.equals(defaultForBand) ? 1 : 0; + int uDef = unfavouredCodec.equals(defaultForBand) ? 1 : 0; + int tDefL = 0; + int[] favoured = populationCodec.getFavoured(); + if(favoured != null) { + int k = favoured.length; + if(tokenCodec == Codec.BYTE1) { + tDefL = 1; + } else if (tokenCodec instanceof BHSDCodec) { + BHSDCodec tokenBHSD = (BHSDCodec) tokenCodec; + if(tokenBHSD.getS() == 0) { + int[] possibleLValues = new int[] {4, 8, 16, 32, 64, 128, 192, 224, 240, 248, 252}; + int l = 256-tokenBHSD.getH(); + int index = Arrays.binarySearch(possibleLValues, l); + if(index != -1) { + // TODO: check range is ok for ks + tDefL = index++; + } + } + } + } + int first = 141 + fDef + (2 * uDef) + (4 * tDefL); + int[] favouredSpecifier = fDef == 1 ? new int[0] : getSpecifier(favouredCodec, defaultForBand); + int[] tokenSpecifier = tDefL != 0 ? new int[0] : getSpecifier(tokenCodec, defaultForBand); + int[] unfavouredSpecifier = uDef == 1 ? new int[0] : getSpecifier(unfavouredCodec, defaultForBand); + int[] specifier = new int[1 + favouredSpecifier.length + unfavouredSpecifier.length + tokenSpecifier.length]; + specifier[0] = first; + int index = 1; + for (int i = 0; i < favouredSpecifier.length; i++) { + specifier[index] = favouredSpecifier[i]; + index++; + } + for (int i = 0; i < tokenSpecifier.length; i++) { + specifier[index] = tokenSpecifier[i]; + index++; + } + for (int i = 0; i < unfavouredSpecifier.length; i++) { + specifier[index] = unfavouredSpecifier[i]; + index++; + } + return specifier; + } + + return null; + } + + public static BHSDCodec getCanonicalCodec(int i) { + return canonicalCodec[i]; + } +} diff --git a/src/main/java/org/apache/harmony/pack200/ConstantPoolEntry.java b/src/main/java/org/apache/harmony/pack200/ConstantPoolEntry.java new file mode 100644 index 0000000..d2fd1b3 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/ConstantPoolEntry.java @@ -0,0 +1,34 @@ +/* + * 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.pack200; + +/** + * Abstract superclass for constant pool entries + */ +public abstract class ConstantPoolEntry { + + private int index = -1; + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/CpBands.java b/src/main/java/org/apache/harmony/pack200/CpBands.java new file mode 100644 index 0000000..a4cfebf --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/CpBands.java @@ -0,0 +1,694 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.objectweb.asm.Type; + +/** + * Pack200 Constant Pool Bands + */ +public class CpBands extends BandSet { + + // Don't need to include default attribute names in the constant pool bands + private final Set defaultAttributeNames = new HashSet(); + + private final Set cp_Utf8 = new TreeSet(); + private final Set cp_Int = new TreeSet(); + private final Set cp_Float = new TreeSet(); + private final Set cp_Long = new TreeSet(); + private final Set cp_Double = new TreeSet(); + private final Set cp_String = new TreeSet(); + private final Set cp_Class = new TreeSet(); + private final Set cp_Signature = new TreeSet(); + private final Set cp_Descr = new TreeSet(); + private final Set cp_Field = new TreeSet(); + private final Set cp_Method = new TreeSet(); + private final Set cp_Imethod = new TreeSet(); + + private final Map stringsToCpUtf8 = new HashMap(); + private final Map stringsToCpNameAndType = new HashMap(); + private final Map stringsToCpClass = new HashMap(); + private final Map stringsToCpSignature = new HashMap(); + private final Map stringsToCpMethod = new HashMap(); + private final Map stringsToCpField = new HashMap(); + private final Map stringsToCpIMethod = new HashMap(); + + private final Map objectsToCPConstant = new HashMap(); + + private final Segment segment; + + public CpBands(Segment segment, int effort) { + super(effort, segment.getSegmentHeader()); + this.segment = segment; + defaultAttributeNames.add("AnnotationDefault"); + defaultAttributeNames.add("RuntimeVisibleAnnotations"); + defaultAttributeNames.add("RuntimeInvisibleAnnotations"); + defaultAttributeNames.add("RuntimeVisibleParameterAnnotations"); + defaultAttributeNames.add("RuntimeInvisibleParameterAnnotations"); + defaultAttributeNames.add("Code"); + defaultAttributeNames.add("LineNumberTable"); + defaultAttributeNames.add("LocalVariableTable"); + defaultAttributeNames.add("LocalVariableTypeTable"); + defaultAttributeNames.add("ConstantValue"); + defaultAttributeNames.add("Deprecated"); + defaultAttributeNames.add("EnclosingMethod"); + defaultAttributeNames.add("Exceptions"); + defaultAttributeNames.add("InnerClasses"); + defaultAttributeNames.add("Signature"); + defaultAttributeNames.add("SourceFile"); + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + PackingUtils.log("Writing constant pool bands..."); + writeCpUtf8(out); + writeCpInt(out); + writeCpFloat(out); + writeCpLong(out); + writeCpDouble(out); + writeCpString(out); + writeCpClass(out); + writeCpSignature(out); + writeCpDescr(out); + writeCpMethodOrField(cp_Field, out, "cp_Field"); + writeCpMethodOrField(cp_Method, out, "cp_Method"); + writeCpMethodOrField(cp_Imethod, out, "cp_Imethod"); + } + + private void writeCpUtf8(OutputStream out) throws IOException, + Pack200Exception { + PackingUtils.log("Writing " + cp_Utf8.size() + " UTF8 entries..."); + int[] cpUtf8Prefix = new int[cp_Utf8.size() - 2]; + int[] cpUtf8Suffix = new int[cp_Utf8.size() - 1]; + List chars = new ArrayList(); + List bigSuffix = new ArrayList(); + List bigChars = new ArrayList(); + Object[] cpUtf8Array = cp_Utf8.toArray(); + String first = ((CPUTF8) cpUtf8Array[1]).getUnderlyingString(); + cpUtf8Suffix[0] = first.length(); + addCharacters(chars, first.toCharArray()); + for (int i = 2; i < cpUtf8Array.length; i++) { + char[] previous = ((CPUTF8) cpUtf8Array[i - 1]) + .getUnderlyingString().toCharArray(); + String currentStr = ((CPUTF8) cpUtf8Array[i]).getUnderlyingString(); + char[] current = currentStr.toCharArray(); + int prefix = 0; + for (int j = 0; j < previous.length; j++) { + if (previous[j] == current[j]) { + prefix++; + } else { + break; + } + } + cpUtf8Prefix[i - 2] = prefix; + currentStr = currentStr.substring(prefix); + char[] suffix = currentStr.toCharArray(); + if (suffix.length > 1000) { // big suffix (1000 is arbitrary - can we + // do better?) + cpUtf8Suffix[i - 1] = 0; + bigSuffix.add(new Integer(suffix.length)); + addCharacters(bigChars, suffix); + } else { + cpUtf8Suffix[i - 1] = suffix.length; + addCharacters(chars, suffix); + } + } + int[] cpUtf8Chars = new int[chars.size()]; + int[] cpUtf8BigSuffix = new int[bigSuffix.size()]; + int[][] cpUtf8BigChars = new int[bigSuffix.size()][]; + for (int i = 0; i < cpUtf8Chars.length; i++) { + cpUtf8Chars[i] = ((Character) chars.get(i)).charValue(); + } + for (int i = 0; i < cpUtf8BigSuffix.length; i++) { + int numBigChars = ((Integer) bigSuffix.get(i)).intValue(); + cpUtf8BigSuffix[i] = numBigChars; + cpUtf8BigChars[i] = new int[numBigChars]; + for (int j = 0; j < numBigChars; j++) { + cpUtf8BigChars[i][j] = ((Character) bigChars.remove(0)).charValue(); + } + } + + byte[] encodedBand = encodeBandInt("cpUtf8Prefix", cpUtf8Prefix, Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cpUtf8Prefix[" + cpUtf8Prefix.length + "]"); + + encodedBand = encodeBandInt("cpUtf8Suffix", cpUtf8Suffix, Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cpUtf8Suffix[" + cpUtf8Suffix.length + "]"); + + encodedBand = encodeBandInt("cpUtf8Chars", cpUtf8Chars, Codec.CHAR3); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cpUtf8Chars[" + cpUtf8Chars.length + "]"); + + encodedBand = encodeBandInt("cpUtf8BigSuffix", cpUtf8BigSuffix, + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cpUtf8BigSuffix[" + cpUtf8BigSuffix.length + "]"); + + for (int i = 0; i < cpUtf8BigChars.length; i++) { + encodedBand = encodeBandInt("cpUtf8BigChars " + i, + cpUtf8BigChars[i], Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cpUtf8BigChars" + i + "[" + + cpUtf8BigChars[i].length + "]"); + } + } + + private void addCharacters(List chars, char[] charArray) { + for (int i = 0; i < charArray.length; i++) { + chars.add(new Character(charArray[i])); + } + } + + private void writeCpInt(OutputStream out) throws IOException, + Pack200Exception { + PackingUtils.log("Writing " + cp_Int.size() + " Integer entries..."); + int[] cpInt = new int[cp_Int.size()]; + int i = 0; + for (Iterator iterator = cp_Int.iterator(); iterator.hasNext();) { + CPInt integer = (CPInt) iterator.next(); + cpInt[i] = integer.getInt(); + i++; + } + byte[] encodedBand = encodeBandInt("cp_Int", cpInt, Codec.UDELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cp_Int[" + cpInt.length + "]"); + } + + private void writeCpFloat(OutputStream out) throws IOException, + Pack200Exception { + PackingUtils.log("Writing " + cp_Float.size() + " Float entries..."); + int[] cpFloat = new int[cp_Float.size()]; + int i = 0; + for (Iterator iterator = cp_Float.iterator(); iterator.hasNext();) { + CPFloat fl = (CPFloat) iterator.next(); + cpFloat[i] = Float.floatToIntBits(fl.getFloat()); + i++; + } + byte[] encodedBand = encodeBandInt("cp_Float", cpFloat, Codec.UDELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cp_Float[" + cpFloat.length + "]"); + } + + private void writeCpLong(OutputStream out) throws IOException, + Pack200Exception { + PackingUtils.log("Writing " + cp_Long.size() + " Long entries..."); + int[] highBits = new int[cp_Long.size()]; + int[] loBits = new int[cp_Long.size()]; + int i = 0; + for (Iterator iterator = cp_Long.iterator(); iterator.hasNext();) { + CPLong lng = (CPLong) iterator.next(); + long l = lng.getLong(); + highBits[i] = (int) (l >> 32); + loBits[i] = (int) l; + i++; + } + byte[] encodedBand = encodeBandInt("cp_Long_hi", highBits, Codec.UDELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cp_Long_hi[" + highBits.length + "]"); + + encodedBand = encodeBandInt("cp_Long_lo", loBits, Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cp_Long_lo[" + loBits.length + "]"); + } + + private void writeCpDouble(OutputStream out) throws IOException, + Pack200Exception { + PackingUtils.log("Writing " + cp_Double.size() + " Double entries..."); + int[] highBits = new int[cp_Double.size()]; + int[] loBits = new int[cp_Double.size()]; + int i = 0; + for (Iterator iterator = cp_Double.iterator(); iterator.hasNext();) { + CPDouble dbl = (CPDouble) iterator.next(); + long l = Double.doubleToLongBits(dbl.getDouble()); + highBits[i] = (int) (l >> 32); + loBits[i] = (int) l; + i++; + } + byte[] encodedBand = encodeBandInt("cp_Double_hi", highBits, Codec.UDELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cp_Double_hi[" + highBits.length + "]"); + + encodedBand = encodeBandInt("cp_Double_lo", loBits, Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cp_Double_lo[" + loBits.length + "]"); + } + + private void writeCpString(OutputStream out) throws IOException, + Pack200Exception { + PackingUtils.log("Writing " + cp_String.size() + " String entries..."); + int[] cpString = new int[cp_String.size()]; + int i = 0; + for (Iterator iterator = cp_String.iterator(); iterator.hasNext();) { + CPString cpStr = (CPString) iterator.next(); + cpString[i] = cpStr.getIndexInCpUtf8(); + i++; + } + byte[] encodedBand = encodeBandInt("cpString", cpString, Codec.UDELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cpString[" + cpString.length + "]"); + } + + private void writeCpClass(OutputStream out) throws IOException, + Pack200Exception { + PackingUtils.log("Writing " + cp_Class.size() + " Class entries..."); + int[] cpClass = new int[cp_Class.size()]; + int i = 0; + for (Iterator iterator = cp_Class.iterator(); iterator.hasNext();) { + CPClass cpCl = (CPClass) iterator.next(); + cpClass[i] = cpCl.getIndexInCpUtf8(); + i++; + } + byte[] encodedBand = encodeBandInt("cpClass", cpClass, Codec.UDELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cpClass[" + cpClass.length + "]"); + } + + private void writeCpSignature(OutputStream out) throws IOException, + Pack200Exception { + PackingUtils.log("Writing " + cp_Signature.size() + " Signature entries..."); + int[] cpSignatureForm = new int[cp_Signature.size()]; + List classes = new ArrayList(); + int i = 0; + for (Iterator iterator = cp_Signature.iterator(); iterator.hasNext();) { + CPSignature cpS = (CPSignature) iterator.next(); + classes.addAll(cpS.getClasses()); + cpSignatureForm[i] = cpS.getIndexInCpUtf8(); + i++; + } + int[] cpSignatureClasses = new int[classes.size()]; + for (int j = 0; j < cpSignatureClasses.length; j++) { + cpSignatureClasses[j] = ((CPClass) classes.get(j)).getIndex(); + } + + byte[] encodedBand = encodeBandInt("cpSignatureForm", cpSignatureForm, + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cpSignatureForm[" + cpSignatureForm.length + "]"); + + encodedBand = encodeBandInt("cpSignatureClasses", cpSignatureClasses, + Codec.UDELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cpSignatureClasses[" + cpSignatureClasses.length + "]"); + } + + private void writeCpDescr(OutputStream out) throws IOException, + Pack200Exception { + PackingUtils.log("Writing " + cp_Descr.size() + + " Descriptor entries..."); + int[] cpDescrName = new int[cp_Descr.size()]; + int[] cpDescrType = new int[cp_Descr.size()]; + int i = 0; + for (Iterator iterator = cp_Descr.iterator(); iterator.hasNext();) { + CPNameAndType nameAndType = (CPNameAndType) iterator.next(); + cpDescrName[i] = nameAndType.getNameIndex(); + cpDescrType[i] = nameAndType.getTypeIndex(); + i++; + } + + byte[] encodedBand = encodeBandInt("cp_Descr_Name", cpDescrName, + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cp_Descr_Name[" + cpDescrName.length + "]"); + + encodedBand = encodeBandInt("cp_Descr_Type", cpDescrType, Codec.UDELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from cp_Descr_Type[" + cpDescrType.length + "]"); + } + + private void writeCpMethodOrField(Set cp, OutputStream out, String name) + throws IOException, Pack200Exception { + PackingUtils.log("Writing " + cp.size() + + " Method and Field entries..."); + int[] cp_methodOrField_class = new int[cp.size()]; + int[] cp_methodOrField_desc = new int[cp.size()]; + int i = 0; + for (Iterator iterator = cp.iterator(); iterator.hasNext();) { + CPMethodOrField mOrF = (CPMethodOrField) iterator.next(); + cp_methodOrField_class[i] = mOrF.getClassIndex(); + cp_methodOrField_desc[i] = mOrF.getDescIndex(); + i++; + } + byte[] encodedBand = encodeBandInt(name + "_class", + cp_methodOrField_class, Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + name + "_class[" + cp_methodOrField_class.length + "]"); + + encodedBand = encodeBandInt(name + "_desc", cp_methodOrField_desc, + Codec.UDELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + name + "_desc[" + cp_methodOrField_desc.length + "]"); + } + + /** + * All input classes for the segment have now been read in, so this method + * is called so that this class can calculate/complete anything it could not + * do while classes were being read. + */ + public void finaliseBands() { + addCPUtf8(""); + removeSignaturesFromCpUTF8(); + addIndices(); + segmentHeader.setCp_Utf8_count(cp_Utf8.size()); + segmentHeader.setCp_Int_count(cp_Int.size()); + segmentHeader.setCp_Float_count(cp_Float.size()); + segmentHeader.setCp_Long_count(cp_Long.size()); + segmentHeader.setCp_Double_count(cp_Double.size()); + segmentHeader.setCp_String_count(cp_String.size()); + segmentHeader.setCp_Class_count(cp_Class.size()); + segmentHeader.setCp_Signature_count(cp_Signature.size()); + segmentHeader.setCp_Descr_count(cp_Descr.size()); + segmentHeader.setCp_Field_count(cp_Field.size()); + segmentHeader.setCp_Method_count(cp_Method.size()); + segmentHeader.setCp_Imethod_count(cp_Imethod.size()); + } + + private void removeSignaturesFromCpUTF8() { + for (Iterator iterator = cp_Signature.iterator(); iterator.hasNext();) { + CPSignature signature = (CPSignature) iterator.next(); + String sigStr = signature.getUnderlyingString(); + CPUTF8 utf8 = signature.getSignatureForm(); + String form = utf8.getUnderlyingString(); + if (!sigStr.equals(form)) { + removeCpUtf8(sigStr); + } + } + } + + private void addIndices() { + Set[] sets = new Set[] { cp_Utf8, cp_Int, cp_Float, cp_Long, cp_Double, + cp_String, cp_Class, cp_Signature, cp_Descr, cp_Field, + cp_Method, cp_Imethod }; + for (int i = 0; i < sets.length; i++) { + int j = 0; + for (Iterator iterator = sets[i].iterator(); iterator.hasNext();) { + ConstantPoolEntry entry = (ConstantPoolEntry) iterator.next(); + entry.setIndex(j); + j++; + } + } + Map classNameToIndex = new HashMap(); + for (Iterator iterator = cp_Field.iterator(); iterator.hasNext();) { + CPMethodOrField mOrF = (CPMethodOrField) iterator.next(); + CPClass className = mOrF.getClassName(); + Integer index = (Integer) classNameToIndex.get(className); + if (index == null) { + classNameToIndex.put(className, new Integer(1)); + mOrF.setIndexInClass(0); + } else { + int theIndex = index.intValue(); + mOrF.setIndexInClass(theIndex); + classNameToIndex.put(className, new Integer(theIndex + 1)); + } + } + classNameToIndex.clear(); + Map classNameToConstructorIndex = new HashMap(); + for (Iterator iterator = cp_Method.iterator(); iterator.hasNext();) { + CPMethodOrField mOrF = (CPMethodOrField) iterator.next(); + CPClass className = mOrF.getClassName(); + Integer index = (Integer) classNameToIndex.get(className); + if (index == null) { + classNameToIndex.put(className, new Integer(1)); + mOrF.setIndexInClass(0); + } else { + int theIndex = index.intValue(); + mOrF.setIndexInClass(theIndex); + classNameToIndex.put(className, new Integer(theIndex + 1)); + } + if(mOrF.getDesc().getName().equals("")) { + Integer constructorIndex = (Integer) classNameToConstructorIndex.get(className); + if (constructorIndex == null) { + classNameToConstructorIndex.put(className, new Integer(1)); + mOrF.setIndexInClassForConstructor(0); + } else { + int theIndex = constructorIndex.intValue(); + mOrF.setIndexInClassForConstructor(theIndex); + classNameToConstructorIndex.put(className, new Integer(theIndex + 1)); + } + } + } + } + + private void removeCpUtf8(String string) { + CPUTF8 utf8 = (CPUTF8) stringsToCpUtf8.get(string); + if (utf8 != null) { + if(stringsToCpClass.get(string) == null) { // don't remove if strings are also in cpclass + stringsToCpUtf8.remove(string); + cp_Utf8.remove(utf8); + } + } + } + + void addCPUtf8(String utf8) { + getCPUtf8(utf8); + } + + public CPUTF8 getCPUtf8(String utf8) { + if(utf8 == null) { + return null; + } + CPUTF8 cpUtf8 = (CPUTF8) stringsToCpUtf8.get(utf8); + if (cpUtf8 == null) { + cpUtf8 = new CPUTF8(utf8); + cp_Utf8.add(cpUtf8); + stringsToCpUtf8.put(utf8, cpUtf8); + } + return cpUtf8; + } + + public void addCPNameAndType(String name, String signature) { + getCPNameAndType(name, signature); + } + + public void addCPSignature(String signature) { + getCPSignature(signature); + } + + public CPSignature getCPSignature(String signature) { + if(signature == null) { + return null; + } + CPSignature cpS = (CPSignature) stringsToCpSignature.get(signature); + if (cpS == null) { + List cpClasses = new ArrayList(); + CPUTF8 signatureUTF8; + if (signature.length() > 1 && signature.indexOf('L') != -1) { + List classes = new ArrayList(); + char[] chars = signature.toCharArray(); + StringBuffer signatureString = new StringBuffer(); + for (int i = 0; i < chars.length; i++) { + signatureString.append(chars[i]); + if (chars[i] == 'L') { + StringBuffer className = new StringBuffer(); + for (int j = i + 1; j < chars.length; j++) { + char c = chars[j]; + if (Character.isLetter(c) || Character.isDigit(c) + || c == '/' || c == '$' || c == '_') { + className.append(c); + } else { + classes.add(className.toString()); + i = j - 1; + break; + } + } + } + } + removeCpUtf8(signature); + for (Iterator iterator2 = classes.iterator(); iterator2 + .hasNext();) { + String className = (String) iterator2.next(); + CPClass cpClass = null; + if(className!= null) { + className = className.replace('.', '/'); + cpClass = (CPClass) stringsToCpClass.get(className); + if (cpClass == null) { + CPUTF8 cpUtf8 = getCPUtf8(className); + cpClass = new CPClass(cpUtf8); + cp_Class.add(cpClass); + stringsToCpClass.put(className, cpClass); + } + } + cpClasses.add(cpClass); + } + + signatureUTF8 = getCPUtf8(signatureString.toString()); + } else { + signatureUTF8 = getCPUtf8(signature); + } + cpS = new CPSignature(signature, signatureUTF8, cpClasses); + cp_Signature.add(cpS); + stringsToCpSignature.put(signature, cpS); + } + return cpS; + } + + public CPClass getCPClass(String className) { + if(className == null) { + return null; + } + className = className.replace('.', '/'); + CPClass cpClass = (CPClass) stringsToCpClass.get(className); + if (cpClass == null) { + CPUTF8 cpUtf8 = getCPUtf8(className); + cpClass = new CPClass(cpUtf8); + cp_Class.add(cpClass); + stringsToCpClass.put(className, cpClass); + } + if(cpClass.isInnerClass()) { + segment.getClassBands().currentClassReferencesInnerClass(cpClass); + } + return cpClass; + } + + public void addCPClass(String className) { + getCPClass(className); + } + + public CPNameAndType getCPNameAndType(String name, String signature) { + String descr = name + ":" + signature; + CPNameAndType nameAndType = (CPNameAndType) stringsToCpNameAndType + .get(descr); + if (nameAndType == null) { + nameAndType = new CPNameAndType(getCPUtf8(name), + getCPSignature(signature)); + stringsToCpNameAndType.put(descr, nameAndType); + cp_Descr.add(nameAndType); + } + return nameAndType; + } + + public CPMethodOrField getCPField(CPClass cpClass, String name, String desc) { + String key = cpClass.toString() + ":" + name + ":" + desc; + CPMethodOrField cpF = (CPMethodOrField) stringsToCpField + .get(key); + if (cpF == null) { + CPNameAndType nAndT = getCPNameAndType(name, desc); + cpF = new CPMethodOrField(cpClass, nAndT); + cp_Field.add(cpF); + stringsToCpField.put(key, cpF); + } + return cpF; + } + + public CPConstant getConstant(Object value) { + CPConstant constant = (CPConstant) objectsToCPConstant.get(value); + if (constant == null) { + if (value instanceof Integer) { + constant = new CPInt(((Integer) value).intValue()); + cp_Int.add(constant); + } else if (value instanceof Long) { + constant = new CPLong(((Long) value).longValue()); + cp_Long.add(constant); + } else if (value instanceof Float) { + constant = new CPFloat(((Float) value).floatValue()); + cp_Float.add(constant); + } else if (value instanceof Double) { + constant = new CPDouble(((Double) value).doubleValue()); + cp_Double.add(constant); + } else if (value instanceof String) { + constant = new CPString(getCPUtf8((String) value)); + cp_String.add(constant); + } else if (value instanceof Type) { + String className = ((Type) value).getClassName(); + if(className.endsWith("[]")) { + className = "[L" + className.substring(0, className.length() - 2); + while(className.endsWith("[]")) { + className = "[" + className.substring(0, className.length() - 2); + } + className += ";"; + } + constant = getCPClass(className); + } + objectsToCPConstant.put(value, constant); + } + return constant; + } + + public CPMethodOrField getCPMethod(CPClass cpClass, String name, String desc) { + String key = cpClass.toString() + ":" + name + ":" + desc; + CPMethodOrField cpM = (CPMethodOrField) stringsToCpMethod + .get(key); + if (cpM == null) { + CPNameAndType nAndT = getCPNameAndType(name, desc); + cpM = new CPMethodOrField(cpClass, nAndT); + cp_Method.add(cpM); + stringsToCpMethod.put(key, cpM); + } + return cpM; + } + + public CPMethodOrField getCPIMethod(CPClass cpClass, String name, + String desc) { + String key = cpClass.toString() + ":" + name + ":" + desc; + CPMethodOrField cpIM = (CPMethodOrField) stringsToCpIMethod + .get(key); + if (cpIM == null) { + CPNameAndType nAndT = getCPNameAndType(name, desc); + cpIM = new CPMethodOrField(cpClass, nAndT); + cp_Imethod.add(cpIM); + stringsToCpIMethod.put(key, cpIM); + } + return cpIM; + } + + public CPMethodOrField getCPField(String owner, String name, String desc) { + return getCPField(getCPClass(owner), name, desc); + } + + public CPMethodOrField getCPMethod(String owner, String name, String desc) { + return getCPMethod(getCPClass(owner), name, desc); + } + + public CPMethodOrField getCPIMethod(String owner, String name, String desc) { + return getCPIMethod(getCPClass(owner), name, desc); + } + + public boolean existsCpClass(String className) { + CPClass cpClass = (CPClass) stringsToCpClass.get(className); + return cpClass != null; + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/FileBands.java b/src/main/java/org/apache/harmony/pack200/FileBands.java new file mode 100644 index 0000000..38bfcef --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/FileBands.java @@ -0,0 +1,170 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TimeZone; + +import org.apache.harmony.pack200.Archive.PackingFile; +import org.apache.harmony.pack200.Archive.SegmentUnit; +import org.objectweb.asm.ClassReader; + +/** + * Bands containing information about files in the pack200 archive and the file + * contents for non-class-files. Corresponds to the file_bands set + * of bands described in the specification. + */ +public class FileBands extends BandSet { + + private final CPUTF8[] fileName; + private int[] file_name; + private final int[] file_modtime; + private final long[] file_size; + private final int[] file_options; + private final byte[][] file_bits; + + public FileBands(CpBands cpBands, SegmentHeader segmentHeader, + PackingOptions options, SegmentUnit segmentUnit, int effort) { + super(effort, segmentHeader); + List fileList = segmentUnit.getFileList(); + int size = fileList.size(); + fileName = new CPUTF8[size]; + file_modtime = new int[size]; + file_size = new long[size]; + file_options = new int[size]; + int totalSize = 0; + file_bits = new byte[size][]; + int archiveModtime = segmentHeader.getArchive_modtime(); + + Set classNames = new HashSet(); + for (Iterator iterator = segmentUnit.getClassList().iterator(); iterator + .hasNext();) { + ClassReader reader = (ClassReader) iterator.next(); + classNames.add(reader.getClassName()); + } + CPUTF8 emptyString = cpBands.getCPUtf8(""); + long modtime; + int latestModtime = Integer.MIN_VALUE; + boolean isLatest = !PackingOptions.KEEP.equals(options + .getModificationTime()); + for (int i = 0; i < size; i++) { + PackingFile packingFile = (PackingFile) fileList.get(i); + String name = packingFile.getName(); + if (name.endsWith(".class") && !options.isPassFile(name)) { + file_options[i] |= (1 << 1); + if (classNames.contains(name.substring(0, name.length() - 6))) { + fileName[i] = emptyString; + } else { + fileName[i] = cpBands.getCPUtf8(name); + } + } else { + fileName[i] = cpBands.getCPUtf8(name); + } + // set deflate_hint for file element + if (options.isKeepDeflateHint() && packingFile.isDefalteHint()) { + file_options[i] |= 0x1; + } + byte[] bytes = packingFile.getContents(); + file_size[i] = bytes.length; + totalSize += file_size[i]; + + // update modification time + modtime = (packingFile.getModtime() + TimeZone.getDefault().getRawOffset()) / 1000L; + file_modtime[i] = (int) (modtime - archiveModtime); + if (isLatest && latestModtime < file_modtime[i]) { + latestModtime = file_modtime[i]; + } + + file_bits[i] = packingFile.getContents(); + } + + if (isLatest) { + for (int index = 0; index < size; index++) { + file_modtime[index] = latestModtime; + } + } + } + + /** + * All input classes for the segment have now been read in, so this method + * is called so that this class can calculate/complete anything it could not + * do while classes were being read. + */ + public void finaliseBands() { + file_name = new int[fileName.length]; + for (int i = 0; i < file_name.length; i++) { + file_name[i] = fileName[i].getIndex(); + } + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + PackingUtils.log("Writing file bands..."); + byte[] encodedBand = encodeBandInt("file_name", file_name, + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from file_name[" + file_name.length + "]"); + + encodedBand = encodeFlags("file_size", file_size, Codec.UNSIGNED5, + Codec.UNSIGNED5, segmentHeader.have_file_size_hi()); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from file_size[" + file_size.length + "]"); + + if (segmentHeader.have_file_modtime()) { + encodedBand = encodeBandInt("file_modtime", file_modtime, + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from file_modtime[" + file_modtime.length + "]"); + } + if (segmentHeader.have_file_options()) { + encodedBand = encodeBandInt("file_options", file_options, + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from file_options[" + file_options.length + "]"); + } + + encodedBand = encodeBandInt("file_bits", flatten(file_bits), + Codec.BYTE1); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from file_bits[" + file_bits.length + "]"); + } + + private int[] flatten(byte[][] bytes) { + int total = 0; + for (int i = 0; i < bytes.length; i++) { + total += bytes[i].length; + } + int[] band = new int[total]; + int index = 0; + for (int i = 0; i < bytes.length; i++) { + for (int j = 0; j < bytes[i].length; j++) { + band[index++] = bytes[i][j] & 0xFF; + } + } + return band; + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/IcBands.java b/src/main/java/org/apache/harmony/pack200/IcBands.java new file mode 100644 index 0000000..6f1c375 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/IcBands.java @@ -0,0 +1,203 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + * Inner class bands (corresponds to the ic_bands set of bands in + * the pack200 specification) + */ +public class IcBands extends BandSet { + + private final Set innerClasses = new TreeSet(); + private final CpBands cpBands; + private int bit16Count = 0; + + private final Map outerToInner = new HashMap(); + + public IcBands(SegmentHeader segmentHeader, CpBands cpBands, int effort) { + super(effort, segmentHeader); + this.cpBands = cpBands; + } + + /** + * All input classes for the segment have now been read in, so this method + * is called so that this class can calculate/complete anything it could not + * do while classes were being read. + */ + public void finaliseBands() { + segmentHeader.setIc_count(innerClasses.size()); + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + PackingUtils.log("Writing internal class bands..."); + int[] ic_this_class = new int[innerClasses.size()]; + int[] ic_flags = new int[innerClasses.size()]; + int[] ic_outer_class = new int[bit16Count]; + int[] ic_name = new int[bit16Count]; + + int index2 = 0; + List innerClassesList = new ArrayList(innerClasses); + for (int i = 0; i < ic_this_class.length; i++) { + IcTuple icTuple = (IcTuple) innerClassesList.get(i); + ic_this_class[i] = icTuple.C.getIndex(); + ic_flags[i] = icTuple.F; + if((icTuple.F & (1<<16)) != 0) { + ic_outer_class[index2] = icTuple.C2 == null ? 0 : icTuple.C2.getIndex() + 1; + ic_name[index2] = icTuple.N == null ? 0 : icTuple.N.getIndex() + 1; + index2++; + } + } + byte[] encodedBand = encodeBandInt("ic_this_class", ic_this_class, + Codec.UDELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from ic_this_class[" + ic_this_class.length + "]"); + + encodedBand = encodeBandInt("ic_flags", ic_flags, Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from ic_flags[" + ic_flags.length + "]"); + + encodedBand = encodeBandInt("ic_outer_class", ic_outer_class, + Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from ic_outer_class[" + ic_outer_class.length + "]"); + + encodedBand = encodeBandInt("ic_name", ic_name, Codec.DELTA5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from ic_name[" + ic_name.length + "]"); + } + + public void addInnerClass(String name, String outerName, String innerName, + int flags) { + if(outerName != null || innerName != null) { + if(namesArePredictable(name, outerName, innerName)) { + IcTuple innerClass = new IcTuple(cpBands.getCPClass(name), flags, null, null); + addToMap(outerName, innerClass); + innerClasses.add(innerClass); + } else { + flags |= (1<<16); + IcTuple icTuple = new IcTuple(cpBands.getCPClass(name), flags, cpBands.getCPClass(outerName), cpBands.getCPUtf8(innerName)); + boolean added = innerClasses.add(icTuple); + if(added) { + bit16Count++; + addToMap(outerName, icTuple); + } + } + } else { + IcTuple innerClass = new IcTuple(cpBands.getCPClass(name), flags, null, null); + addToMap(getOuter(name), innerClass); + innerClasses.add(innerClass); + } + } + + public List getInnerClassesForOuter(String outerClassName) { + return (List) outerToInner.get(outerClassName); + } + + private String getOuter(String name) { + return name.substring(0, name.lastIndexOf('$')); + } + + private void addToMap(String outerName, IcTuple icTuple) { + List tuples = (List) outerToInner.get(outerName); + if(tuples == null) { + tuples = new ArrayList(); + outerToInner.put(outerName, tuples); + tuples.add(icTuple); + } else { + for (Iterator iterator = tuples.iterator(); iterator.hasNext();) { + IcTuple icT = (IcTuple) iterator.next(); + if(icTuple.equals(icT)) { + return; + } + } + tuples.add(icTuple); + } + } + + private boolean namesArePredictable(String name, String outerName, + String innerName) { + // TODO: Could be multiple characters, not just $ + return name.equals(outerName + '$' + innerName) && innerName.indexOf('$') == -1; + } + + class IcTuple implements Comparable { + + protected CPClass C; // this class + protected int F; // flags + protected CPClass C2; // outer class + protected CPUTF8 N; // name + + public IcTuple(CPClass C, int F, CPClass C2, CPUTF8 N) { + this.C = C; + this.F = F; + this.C2 = C2; + this.N = N; + } + + public boolean equals(Object o) { + if(o instanceof IcTuple) { + IcTuple icT = (IcTuple)o; + return C.equals(icT.C) && F == icT.F && (C2 != null ? C2.equals(icT.C2) : icT.C2 == null) && (N != null ? N.equals(icT.N) : icT.N == null); + } + return false; + } + + public int hashCode() { + return (C.hashCode() * 37) + F; + } + + public String toString() { + return C.toString(); + } + + public int compareTo(Object arg0) { + return C.compareTo(((IcTuple)arg0).C); + } + + public boolean isAnonymous() { + String className = C.toString(); + String innerName = className.substring(className.lastIndexOf('$') + 1); + return Character.isDigit(innerName.charAt(0)); + } + + } + + public IcTuple getIcTuple(CPClass inner) { + for (Iterator iterator = innerClasses.iterator(); iterator.hasNext();) { + IcTuple icTuple = (IcTuple) iterator.next(); + if(icTuple.C.equals(inner)) { + return icTuple; + } + } + return null; + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/IntList.java b/src/main/java/org/apache/harmony/pack200/IntList.java new file mode 100644 index 0000000..e2ba351 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/IntList.java @@ -0,0 +1,253 @@ +/* + * 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.pack200; + +import java.util.Arrays; + +/** + * IntList is based on java.util.ArrayList, but is written + * specifically for ints in order to reduce boxing and unboxing to Integers, + * reduce the memory required and improve performance of pack200. + */ +public class IntList { + + private int[] array; + private int firstIndex; + private int lastIndex; + private int modCount; + + /** + * Constructs a new instance of IntList with capacity for ten elements. + */ + public IntList() { + this(10); + } + + /** + * Constructs a new instance of IntList with the specified capacity. + * + * @param capacity + * the initial capacity of this IntList + */ + public IntList(int capacity) { + if (capacity < 0) { + throw new IllegalArgumentException(); + } + firstIndex = lastIndex = 0; + array = new int[capacity]; + } + + /** + * Adds the specified object at the end of this IntList. + * + * @param object + * the object to add + * @return true + */ + public boolean add(int object) { + if (lastIndex == array.length) { + growAtEnd(1); + } + array[lastIndex++] = object; + modCount++; + return true; + } + + public void add(int location, int object) { + int size = lastIndex - firstIndex; + if (0 < location && location < size) { + if (firstIndex == 0 && lastIndex == array.length) { + growForInsert(location, 1); + } else if ((location < size / 2 && firstIndex > 0) + || lastIndex == array.length) { + System.arraycopy(array, firstIndex, array, --firstIndex, + location); + } else { + int index = location + firstIndex; + System.arraycopy(array, index, array, index + 1, size + - location); + lastIndex++; + } + array[location + firstIndex] = object; + } else if (location == 0) { + if (firstIndex == 0) { + growAtFront(1); + } + array[--firstIndex] = object; + } else if (location == size) { + if (lastIndex == array.length) { + growAtEnd(1); + } + array[lastIndex++] = object; + } else { + throw new IndexOutOfBoundsException(); + } + + modCount++; + } + + public void clear() { + if (firstIndex != lastIndex) { + Arrays.fill(array, firstIndex, lastIndex, -1); + firstIndex = lastIndex = 0; + modCount++; + } + } + + public int get(int location) { + if (0 <= location && location < (lastIndex - firstIndex)) { + return array[firstIndex + location]; + } + throw new IndexOutOfBoundsException("" + location); + } + + private void growAtEnd(int required) { + int size = lastIndex - firstIndex; + if (firstIndex >= required - (array.length - lastIndex)) { + int newLast = lastIndex - firstIndex; + if (size > 0) { + System.arraycopy(array, firstIndex, array, 0, size); + } + firstIndex = 0; + lastIndex = newLast; + } else { + int increment = size / 2; + if (required > increment) { + increment = required; + } + if (increment < 12) { + increment = 12; + } + int[] newArray = new int[size + increment]; + if (size > 0) { + System.arraycopy(array, firstIndex, newArray, 0, size); + firstIndex = 0; + lastIndex = size; + } + array = newArray; + } + } + + private void growAtFront(int required) { + int size = lastIndex - firstIndex; + if (array.length - lastIndex + firstIndex >= required) { + int newFirst = array.length - size; + if (size > 0) { + System.arraycopy(array, firstIndex, array, newFirst, size); + } + firstIndex = newFirst; + lastIndex = array.length; + } else { + int increment = size / 2; + if (required > increment) { + increment = required; + } + if (increment < 12) { + increment = 12; + } + int[] newArray = new int[size + increment]; + if (size > 0) { + System.arraycopy(array, firstIndex, newArray, newArray.length + - size, size); + } + firstIndex = newArray.length - size; + lastIndex = newArray.length; + array = newArray; + } + } + + private void growForInsert(int location, int required) { + int size = lastIndex - firstIndex; + int increment = size / 2; + if (required > increment) { + increment = required; + } + if (increment < 12) { + increment = 12; + } + int[] newArray = new int[size + increment]; + int newFirst = increment - required; + // Copy elements after location to the new array skipping inserted + // elements + System.arraycopy(array, location + firstIndex, newArray, newFirst + + location + required, size - location); + // Copy elements before location to the new array from firstIndex + System.arraycopy(array, firstIndex, newArray, newFirst, location); + firstIndex = newFirst; + lastIndex = size + increment; + + array = newArray; + } + + public void increment(int location) { + if (0 <= location && location < (lastIndex - firstIndex)) { + array[firstIndex + location]++; + } else { + throw new IndexOutOfBoundsException("" + location); + } + } + + public boolean isEmpty() { + return lastIndex == firstIndex; + } + + public int remove(int location) { + int result; + int size = lastIndex - firstIndex; + if (0 <= location && location < size) { + if (location == size - 1) { + result = array[--lastIndex]; + array[lastIndex] = 0; + } else if (location == 0) { + result = array[firstIndex]; + array[firstIndex++] = 0; + } else { + int elementIndex = firstIndex + location; + result = array[elementIndex]; + if (location < size / 2) { + System.arraycopy(array, firstIndex, array, firstIndex + 1, + location); + array[firstIndex++] = 0; + } else { + System.arraycopy(array, elementIndex + 1, array, + elementIndex, size - location - 1); + array[--lastIndex] = 0; + } + } + if (firstIndex == lastIndex) { + firstIndex = lastIndex = 0; + } + } else { + throw new IndexOutOfBoundsException(); + } + + modCount++; + return result; + } + + public int size() { + return lastIndex - firstIndex; + } + + public int[] toArray() { + int size = lastIndex - firstIndex; + int[] result = new int[size]; + System.arraycopy(array, firstIndex, result, 0, size); + return result; + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/MetadataBandGroup.java b/src/main/java/org/apache/harmony/pack200/MetadataBandGroup.java new file mode 100644 index 0000000..9704e2b --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/MetadataBandGroup.java @@ -0,0 +1,375 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * A group of metadata (annotation) bands, such as class_RVA_bands, + * method_AD_bands etc. + */ +public class MetadataBandGroup extends BandSet { + + public static final int CONTEXT_CLASS = 0; + public static final int CONTEXT_FIELD = 1; + public static final int CONTEXT_METHOD = 2; + + private final String type; + private int numBackwardsCalls = 0; + + public List param_NB = new ArrayList(); // TODO: Lazy instantiation? + public IntList anno_N = new IntList(); + public List type_RS = new ArrayList(); + public IntList pair_N = new IntList(); + public List name_RU = new ArrayList(); + public List T = new ArrayList(); + public List caseI_KI = new ArrayList(); + public List caseD_KD = new ArrayList(); + public List caseF_KF = new ArrayList(); + public List caseJ_KJ = new ArrayList(); + public List casec_RS = new ArrayList(); + public List caseet_RS = new ArrayList(); + public List caseec_RU = new ArrayList(); + public List cases_RU = new ArrayList(); + public IntList casearray_N = new IntList(); + public List nesttype_RS = new ArrayList(); + public IntList nestpair_N = new IntList(); + public List nestname_RU = new ArrayList(); + + private final CpBands cpBands; + private final int context; + + /** + * Constructs a new MetadataBandGroup + * + * @param type + * must be either AD, RVA, RIA, RVPA or RIPA. + * @param context + * CONTEXT_CLASS, CONTEXT_METHOD or + * CONTEXT_FIELD + * @param cpBands + * constant pool bands + * @param segmentHeader + * segment header + * @param effort + * packing effort + */ + public MetadataBandGroup(String type, int context, CpBands cpBands, SegmentHeader segmentHeader, int effort) { + super(effort, segmentHeader); + this.type = type; + this.cpBands = cpBands; + this.context = context; + } + + /* (non-Javadoc) + * @see org.apache.harmony.pack200.BandSet#pack(java.io.OutputStream) + */ + public void pack(OutputStream out) throws IOException, Pack200Exception { + PackingUtils.log("Writing metadata band group..."); + if(hasContent()) { + String contextStr; + if(context == CONTEXT_CLASS) { + contextStr = "Class"; + } else if (context == CONTEXT_FIELD) { + contextStr = "Field"; + } else { + contextStr = "Method"; + } + byte[] encodedBand = null; + if(!type.equals("AD")) { + encodedBand = encodeBandInt( + contextStr + "_" + type + " anno_N", anno_N.toArray(), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from " + contextStr + "_" + type + " anno_N[" + + anno_N.size() + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + + " type_RS", cpEntryListToArray(type_RS), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from " + contextStr + "_" + type + + " type_RS[" + type_RS.size() + "]"); + + encodedBand = encodeBandInt( + contextStr + "_" + type + " pair_N", pair_N.toArray(), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from " + contextStr + "_" + type + " pair_N[" + + pair_N.size() + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + + " name_RU", cpEntryListToArray(name_RU), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + + " bytes from " + contextStr + "_" + type + + " name_RU[" + name_RU.size() + "]"); + } + encodedBand = encodeBandInt(contextStr + "_" + type + " T", + tagListToArray(T), Codec.BYTE1); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " T[" + T.size() + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + " caseI_KI", + cpEntryListToArray(caseI_KI), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " caseI_KI[" + caseI_KI.size() + + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + " caseD_KD", + cpEntryListToArray(caseD_KD), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " caseD_KD[" + caseD_KD.size() + + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + " caseF_KF", + cpEntryListToArray(caseF_KF), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " caseF_KF[" + caseF_KF.size() + + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + " caseJ_KJ", + cpEntryListToArray(caseJ_KJ), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " caseJ_KJ[" + caseJ_KJ.size() + + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + " casec_RS", + cpEntryListToArray(casec_RS), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " casec_RS[" + casec_RS.size() + + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + " caseet_RS", + cpEntryListToArray(caseet_RS), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " caseet_RS[" + + caseet_RS.size() + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + " caseec_RU", + cpEntryListToArray(caseec_RU), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " caseec_RU[" + + caseec_RU.size() + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + " cases_RU", + cpEntryListToArray(cases_RU), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " cases_RU[" + cases_RU.size() + + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + + " casearray_N", casearray_N.toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " casearray_N[" + + casearray_N.size() + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + + " nesttype_RS", cpEntryListToArray(nesttype_RS), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " nesttype_RS[" + + nesttype_RS.size() + "]"); + + encodedBand = encodeBandInt( + contextStr + "_" + type + " nestpair_N", nestpair_N + .toArray(), Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " nestpair_N[" + + nestpair_N.size() + "]"); + + encodedBand = encodeBandInt(contextStr + "_" + type + + " nestname_RU", cpEntryListToArray(nestname_RU), + Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + contextStr + "_" + type + " nestname_RU[" + + nestname_RU.size() + "]"); + } + } + + private int[] tagListToArray(List t2) { + int[] ints = new int[t2.size()]; + for (int i = 0; i < ints.length; i++) { + ints[i] = ((String)t2.get(i)).charAt(0); + } + return ints; + } + + /** + * Add an annotation to this set of bands + * + * @param desc + * @param nameRU + * @param t + * @param values + * @param caseArrayN + * @param nestTypeRS + * @param nestNameRU + * @param nestPairN + */ + public void addAnnotation(String desc, List nameRU, List t, List values, List caseArrayN, List nestTypeRS, List nestNameRU, List nestPairN) { + type_RS.add(cpBands.getCPSignature(desc)); + pair_N.add(t.size()); + + for (Iterator iterator = nameRU.iterator(); iterator.hasNext();) { + String name = (String) iterator.next(); + name_RU.add(cpBands.getCPUtf8(name)); + } + + Iterator valuesIterator = values.iterator(); + for (Iterator iterator = t.iterator(); iterator.hasNext();) { + String tag = (String) iterator.next(); + T.add(tag); + if (tag.equals("B") || tag.equals("C") || tag.equals("I") + || tag.equals("S") || tag.equals("Z")) { + Integer value = (Integer)valuesIterator.next(); + caseI_KI.add(cpBands.getConstant(value)); + } else if (tag.equals("D")) { + Double value = (Double)valuesIterator.next(); + caseD_KD.add(cpBands.getConstant(value)); + } else if (tag.equals("F")) { + Float value = (Float)valuesIterator.next(); + caseF_KF.add(cpBands.getConstant(value)); + } else if (tag.equals("J")) { + Long value = (Long)valuesIterator.next(); + caseJ_KJ.add(cpBands.getConstant(value)); + } else if (tag.equals("C")) { + String value = (String)valuesIterator.next(); + casec_RS.add(cpBands.getCPSignature(value)); + } else if (tag.equals("e")) { + String value = (String)valuesIterator.next(); + String value2 = (String)valuesIterator.next(); + caseet_RS.add(cpBands.getCPSignature(value)); + caseec_RU.add(cpBands.getCPUtf8(value2)); + } else if (tag.equals("s")) { + String value = (String)valuesIterator.next(); + cases_RU.add(cpBands.getCPUtf8(value)); + } + // do nothing here for [ or @ (handled below) + } + for (Iterator iterator = caseArrayN.iterator(); iterator.hasNext();) { + int arraySize = ((Integer)iterator.next()).intValue(); + casearray_N.add(arraySize); + numBackwardsCalls += arraySize; + } + for (Iterator iterator = nesttype_RS.iterator(); iterator.hasNext();) { + String type = (String) iterator.next(); + nesttype_RS.add(cpBands.getCPSignature(type)); + } + for (Iterator iterator = nestname_RU.iterator(); iterator.hasNext();) { + String name = (String) iterator.next(); + nestname_RU.add(cpBands.getCPUtf8(name)); + } + for (Iterator iterator = nestPairN.iterator(); iterator.hasNext();) { + Integer numPairs = (Integer) iterator.next(); + nestPairN.add(numPairs); + numBackwardsCalls += numPairs.intValue(); + } + } + + /** + * Returns true if any annotations have been added to this set of bands + */ + public boolean hasContent() { + return type_RS.size() > 0; + } + + public int numBackwardsCalls() { + return numBackwardsCalls; + } + + public void incrementAnnoN() { + anno_N.increment(anno_N.size() - 1); + } + + public void newEntryInAnnoN() { + anno_N.add(1); + } + + /** + * Remove the latest annotation that was added to this group + */ + public void removeLatest() { + int latest = anno_N.remove(anno_N.size() -1); + for (int i = 0; i < latest; i++) { + type_RS.remove(type_RS.size() - 1); + int pairs = pair_N.remove(pair_N.size() - 1); + for (int j = 0; j < pairs; j++) { + removeOnePair(); + } + } + } + + /* + * Convenience method for removeLatest + */ + private void removeOnePair() { + String tag = (String) T.remove(T.size() - 1); + if (tag.equals("B") || tag.equals("C") || tag.equals("I") + || tag.equals("S") || tag.equals("Z")) { + caseI_KI.remove(caseI_KI.size() - 1); + } else if (tag.equals("D")) { + caseD_KD.remove(caseD_KD.size() - 1); + } else if (tag.equals("F")) { + caseF_KF.remove(caseF_KF.size() - 1); + } else if (tag.equals("J")) { + caseJ_KJ.remove(caseJ_KJ.size() - 1); + } else if (tag.equals("C")) { + casec_RS.remove(casec_RS.size() - 1); + } else if (tag.equals("e")) { + caseet_RS.remove(caseet_RS.size() - 1); + caseec_RU.remove(caseet_RS.size() - 1); + } else if (tag.equals("s")) { + cases_RU.remove(cases_RU.size() - 1); + } else if (tag.equals("[")) { + int arraySize = casearray_N.remove(casearray_N.size() - 1); + numBackwardsCalls -= arraySize; + for (int k = 0; k < arraySize; k++) { + removeOnePair(); + } + } else if (tag.equals("@")) { + nesttype_RS.remove(nesttype_RS.size() - 1); + int numPairs = nestpair_N.remove(nestpair_N.size() - 1); + numBackwardsCalls -= numPairs; + for (int i = 0; i < numPairs; i++) { + removeOnePair(); + } + } + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/NewAttribute.java b/src/main/java/org/apache/harmony/pack200/NewAttribute.java new file mode 100644 index 0000000..3cab224 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/NewAttribute.java @@ -0,0 +1,145 @@ +/* + * 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.pack200; + +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Label; + +/** + * NewAttribute extends Attribute and manages unknown attributes + * encountered by ASM that have had a layout definition given to pack200 (e.g. + * via one of the -C, -M, -F or -D command line options) + */ +public class NewAttribute extends Attribute { + + private boolean contextClass = false; + private boolean contextMethod = false; + private boolean contextField = false; + private boolean contextCode = false; + + private final String layout; + private byte[] contents; + private int codeOff; + private Label[] labels; + private ClassReader classReader; + private char[] buf; + + public NewAttribute(String type, String layout, int context) { + super(type); + this.layout = layout; + addContext(context); + } + + public NewAttribute(ClassReader classReader, String type, String layout, byte[] contents, char[] buf, + int codeOff, Label[] labels) { + super(type); + this.classReader = classReader; + this.contents = contents; + this.layout = layout; + this.codeOff = codeOff; + this.labels = labels; + this.buf = buf; + } + + public void addContext(int context) { + switch(context) { + case AttributeDefinitionBands.CONTEXT_CLASS: + contextClass = true; + break; + case AttributeDefinitionBands.CONTEXT_METHOD: + contextMethod = true; + break; + case AttributeDefinitionBands.CONTEXT_FIELD: + contextField = true; + break; + case AttributeDefinitionBands.CONTEXT_CODE: + contextCode = true; + break; + } + } + + public boolean isContextClass() { + return contextClass; + } + + public boolean isContextMethod() { + return contextMethod; + } + + public boolean isContextField() { + return contextField; + } + + public boolean isContextCode() { + return contextCode; + } + + public String getLayout() { + return layout; + } + + public boolean isUnknown() { + return false; + } + + public boolean isCodeAttribute() { + return codeOff != -1; + } + + protected Attribute read(ClassReader cr, int off, int len, char[] buf, + int codeOff, Label[] labels) { + byte[] attributeContents = new byte[len]; + System.arraycopy(cr.b, off, attributeContents, 0, len); + return new NewAttribute(cr, type, layout, attributeContents, buf, codeOff, + labels); + } + + public boolean isUnknown(int context) { + switch(context) { + case AttributeDefinitionBands.CONTEXT_CLASS: + return contextClass; + case AttributeDefinitionBands.CONTEXT_METHOD: + return contextMethod; + case AttributeDefinitionBands.CONTEXT_FIELD: + return contextField; + case AttributeDefinitionBands.CONTEXT_CODE: + return contextCode; + } + return false; + } + + public String readUTF8(int index) { + return classReader.readUTF8(index, buf); + } + + public String readClass(int index) { + return classReader.readClass(index, buf); + } + + public Object readConst(int index) { + return classReader.readConst(index, buf); + } + + public byte[] getBytes() { + return contents; + } + + public Label getLabel(int index) { + return labels[index]; + } +} diff --git a/src/main/java/org/apache/harmony/pack200/NewAttributeBands.java b/src/main/java/org/apache/harmony/pack200/NewAttributeBands.java new file mode 100644 index 0000000..fe006ef --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/NewAttributeBands.java @@ -0,0 +1,968 @@ +/* + * 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.pack200; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.harmony.pack200.AttributeDefinitionBands.AttributeDefinition; +import org.objectweb.asm.Label; + +/** + * Set of bands relating to a non-predefined attribute that has had a layout + * definition given to pack200 (e.g. via one of the -C, -M, -F or -D command + * line options) + */ +public class NewAttributeBands extends BandSet { + + protected List attributeLayoutElements; + private int[] backwardsCallCounts; + private final CpBands cpBands; + private final AttributeDefinition def; + private boolean usedAtLeastOnce; + + // used when parsing + private Integral lastPIntegral; + + public NewAttributeBands(int effort, CpBands cpBands, SegmentHeader header, AttributeDefinition def) throws IOException { + super(effort, header); + this.def = def; + this.cpBands = cpBands; + parseLayout(); + } + + public void addAttribute(NewAttribute attribute) { + usedAtLeastOnce = true; + InputStream stream = new ByteArrayInputStream(attribute.getBytes()); + for (Iterator iterator = attributeLayoutElements.iterator(); iterator.hasNext();) { + AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next(); + layoutElement.addAttributeToBand(attribute, stream); + } + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + for (Iterator iterator = attributeLayoutElements.iterator(); iterator.hasNext();) { + AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next(); + layoutElement.pack(out); + } + } + + public String getAttributeName() { + return def.name.getUnderlyingString(); + } + + public int getFlagIndex() { + return def.index; + } + + public int[] numBackwardsCalls() { + return backwardsCallCounts; + } + + public boolean isUsedAtLeastOnce() { + return usedAtLeastOnce; + } + + private void parseLayout() throws IOException { + String layout = def.layout.getUnderlyingString(); + if (attributeLayoutElements == null) { + attributeLayoutElements = new ArrayList(); + StringReader stream = new StringReader(layout); + AttributeLayoutElement e; + while ((e = readNextAttributeElement(stream)) != null) { + attributeLayoutElements.add(e); + } + resolveCalls(); + } + } + + /** + * Resolve calls in the attribute layout and returns the number of backwards + * callables + * + * @param tokens - + * the attribute layout as a List of AttributeElements + */ + private void resolveCalls() { + for (int i = 0; i < attributeLayoutElements.size(); i++) { + AttributeLayoutElement element = (AttributeLayoutElement) attributeLayoutElements + .get(i); + if (element instanceof Callable) { + Callable callable = (Callable) element; + List body = callable.body; // Look for calls in the body + for (int iIndex = 0; iIndex < body.size(); iIndex++) { + LayoutElement layoutElement = (LayoutElement) body + .get(iIndex); + // Set the callable for each call + resolveCallsForElement(i, callable, layoutElement); + } + } + } + int backwardsCallableIndex = 0; + for (int i = 0; i < attributeLayoutElements.size(); i++) { + AttributeLayoutElement element = (AttributeLayoutElement) attributeLayoutElements + .get(i); + if (element instanceof Callable) { + Callable callable = (Callable) element; + if(callable.isBackwardsCallable) { + callable.setBackwardsCallableIndex(backwardsCallableIndex); + backwardsCallableIndex++; + } + } + } + backwardsCallCounts = new int[backwardsCallableIndex]; + } + + private void resolveCallsForElement(int i, + Callable currentCallable, LayoutElement layoutElement) { + if (layoutElement instanceof Call) { + Call call = (Call) layoutElement; + int index = call.callableIndex; + if (index == 0) { // Calls the parent callable + call.setCallable(currentCallable); + } else if (index > 0) { // Forwards call + for (int k = i + 1; k < attributeLayoutElements.size(); k++) { + AttributeLayoutElement el = (AttributeLayoutElement) attributeLayoutElements + .get(k); + if (el instanceof Callable) { + index--; + if (index == 0) { + call.setCallable((Callable) el); + break; + } + } + } + } else { // Backwards call + for (int k = i; k >= 0; k--) { + AttributeLayoutElement el = (AttributeLayoutElement) attributeLayoutElements + .get(k); + if (el instanceof Callable) { + index++; + if (index == 0) { + call.setCallable((Callable) el); + break; + } + } + } + } + } else if (layoutElement instanceof Replication) { + List children = ((Replication)layoutElement).layoutElements; + for (Iterator iterator = children.iterator(); iterator.hasNext();) { + LayoutElement object = (LayoutElement) iterator.next(); + resolveCallsForElement(i, currentCallable, object); + } + } + } + + private AttributeLayoutElement readNextAttributeElement(StringReader stream) + throws IOException { + stream.mark(1); + int nextChar = stream.read(); + if (nextChar == -1) { + return null; + } + if (nextChar == '[') { + List body = readBody(getStreamUpToMatchingBracket(stream)); + return new Callable(body); + } else { + stream.reset(); + return readNextLayoutElement(stream); + } + } + + private LayoutElement readNextLayoutElement(StringReader stream) + throws IOException { + int nextChar = stream.read(); + if (nextChar == -1) { + return null; + } + + switch (nextChar) { + // Integrals + case 'B': + case 'H': + case 'I': + case 'V': + return new Integral(new String(new char[] { (char)nextChar })); + case 'S': + case 'F': + return new Integral(new String(new char[] { (char)nextChar, + (char) stream.read() })); + case 'P': + stream.mark(1); + if (stream.read() != 'O') { + stream.reset(); + lastPIntegral = new Integral("P" + (char) stream.read()); + return lastPIntegral; + } else { + lastPIntegral = new Integral("PO" + (char) stream.read(), lastPIntegral); + return lastPIntegral; + } + case 'O': + stream.mark(1); + if (stream.read() != 'S') { + stream.reset(); + return new Integral("O" + (char) stream.read(), lastPIntegral); + } else { + return new Integral("OS" + (char) stream.read(), lastPIntegral); + } + + // Replication + case 'N': + char uint_type = (char) stream.read(); + stream.read(); // '[' + String str = readUpToMatchingBracket(stream); + return new Replication("" + uint_type, str); + + // Union + case 'T': + String int_type = "" + (char) stream.read(); + if (int_type.equals("S")) { + int_type += (char) stream.read(); + } + List unionCases = new ArrayList(); + UnionCase c; + while ((c = readNextUnionCase(stream)) != null) { + unionCases.add(c); + } + stream.read(); // '(' + stream.read(); // ')' + stream.read(); // '[' + List body = null; + stream.mark(1); + char next = (char) stream.read(); + if (next != ']') { + stream.reset(); + body = readBody(getStreamUpToMatchingBracket(stream)); + } + return new Union(int_type, unionCases, body); + + // Call + case '(': + int number = readNumber(stream).intValue(); + stream.read(); // ')' + return new Call(number); + // Reference + case 'K': + case 'R': + String string = "" + (char)nextChar + (char) stream.read(); + char nxt = (char) stream.read(); + string += nxt; + if (nxt == 'N') { + string += (char) stream.read(); + } + return new Reference(string); + } + return null; + } + + /** + * Read a UnionCase from the stream + * + * @param stream + * @return + * @throws IOException + */ + private UnionCase readNextUnionCase(StringReader stream) throws IOException { + stream.mark(2); + stream.read(); // '(' + char next = (char) stream.read(); + if (next == ')') { + stream.reset(); + return null; + } else { + stream.reset(); + stream.read(); // '(' + } + List tags = new ArrayList(); + Integer nextTag; + do { + nextTag = readNumber(stream); + if(nextTag != null) { + tags.add(nextTag); + stream.read(); // ',' or ')' + } + } while (nextTag != null); + stream.read(); // '[' + stream.mark(1); + next = (char) stream.read(); + if (next == ']') { + return new UnionCase(tags); + } else { + stream.reset(); + return new UnionCase(tags, + readBody(getStreamUpToMatchingBracket(stream))); + } + } + + /** + * An AttributeLayoutElement is a part of an attribute layout and has one or + * more bands associated with it, which transmit the AttributeElement data + * for successive Attributes of this type. + */ + public interface AttributeLayoutElement { + + public void addAttributeToBand(NewAttribute attribute, InputStream stream); + + public void pack(OutputStream out) throws IOException, Pack200Exception; + + public void renumberBci(IntList bciRenumbering, Map labelsToOffsets); + + } + + public abstract class LayoutElement implements AttributeLayoutElement { + + protected int getLength(char uint_type) { + int length = 0; + switch (uint_type) { + case 'B': + length = 1; + break; + case 'H': + length = 2; + break; + case 'I': + length = 4; + break; + case 'V': + length = 0; + break; + } + return length; + } + } + + public class Integral extends LayoutElement { + + private final String tag; + + private final List band = new ArrayList(); + private final BHSDCodec defaultCodec; + + // used for bytecode offsets (OH and POH) + private Integral previousIntegral; + private int previousPValue; + + public Integral(String tag) { + this.tag = tag; + this.defaultCodec = getCodec(tag); + } + + public Integral(String tag, Integral previousIntegral) { + this.tag = tag; + this.defaultCodec = getCodec(tag); + this.previousIntegral = previousIntegral; + } + + public String getTag() { + return tag; + } + + public void addAttributeToBand(NewAttribute attribute, + InputStream stream) { + Object val = null; + int value = 0; + if (tag.equals("B") || tag.equals("FB")) { + value = readInteger(1, stream) & 0xFF; // unsigned byte + } else if (tag.equals("SB")) { + value = readInteger(1, stream); + } else if (tag.equals("H") || tag.equals("FH")) { + value = readInteger(2, stream) & 0xFFFF; // unsigned short + } else if (tag.equals("SH")) { + value = readInteger(2, stream); + } else if (tag.equals("I") || tag.equals("FI")) { + value = readInteger(4, stream); + } else if (tag.equals("SI")) { + value = readInteger(4, stream); + } else if (tag.equals("V") || tag.equals("FV") || tag.equals("SV")) { + // Not currently supported + } else if (tag.startsWith("PO") || tag.startsWith("OS")) { + char uint_type = tag.substring(2).toCharArray()[0]; + int length = getLength(uint_type); + value = readInteger(length, stream); + value += previousIntegral.previousPValue; + val = attribute.getLabel(value); + previousPValue = value; + } else if (tag.startsWith("P")) { + char uint_type = tag.substring(1).toCharArray()[0]; + int length = getLength(uint_type); + value = readInteger(length, stream); + val = attribute.getLabel(value); + previousPValue = value; + } else if (tag.startsWith("O")) { + char uint_type = tag.substring(1).toCharArray()[0]; + int length = getLength(uint_type); + value = readInteger(length, stream); + value += previousIntegral.previousPValue; + val = attribute.getLabel(value); + previousPValue = value; + } + if(val == null) { + val = new Integer(value); + } + band.add(val); + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + PackingUtils.log("Writing new attribute bands..."); + byte[] encodedBand = encodeBandInt(tag, integerListToArray(band), + defaultCodec); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + tag + "[" + band.size() + "]"); + } + + public int latestValue() { + return ((Integer)band.get(band.size() - 1)).intValue(); + } + + public void renumberBci(IntList bciRenumbering, Map labelsToOffsets) { + if(tag.startsWith("O") || tag.startsWith("PO")) { + renumberOffsetBci(previousIntegral.band, bciRenumbering, labelsToOffsets); + } else if (tag.startsWith("P")) { + for (int i = band.size() - 1; i >= 0; i--) { + Object label = band.get(i); + if (label instanceof Integer) { + break; + } else if (label instanceof Label) { + band.remove(i); + Integer bytecodeIndex = (Integer) labelsToOffsets + .get(label); + band.add(i, new Integer(bciRenumbering.get(bytecodeIndex + .intValue()))); + } + } + } + } + + private void renumberOffsetBci(List relative, + IntList bciRenumbering, Map labelsToOffsets) { + for (int i = band.size() - 1; i >= 0; i--) { + Object label = band.get(i); + if (label instanceof Integer) { + break; + } else if (label instanceof Label) { + band.remove(i); + Integer bytecodeIndex = (Integer) labelsToOffsets.get(label); + Integer renumberedOffset = new Integer(bciRenumbering + .get(bytecodeIndex.intValue()) + - ((Integer) relative.get(i)).intValue()); + band.add(i, renumberedOffset); + } + } + } + + } + + /** + * A replication is an array of layout elements, with an associated count + */ + public class Replication extends LayoutElement { + + private final Integral countElement; + + private final List layoutElements = new ArrayList(); + + public Integral getCountElement() { + return countElement; + } + + public List getLayoutElements() { + return layoutElements; + } + + public Replication(String tag, String contents) throws IOException { + this.countElement = new Integral(tag); + StringReader stream = new StringReader(contents); + LayoutElement e; + while ((e = readNextLayoutElement(stream)) != null) { + layoutElements.add(e); + } + } + + public void addAttributeToBand(NewAttribute attribute, + InputStream stream) { + countElement.addAttributeToBand(attribute, stream); + int count = countElement.latestValue(); + for (int i = 0; i < count; i++) { + for (Iterator iterator = layoutElements.iterator(); iterator.hasNext();) { + AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next(); + layoutElement.addAttributeToBand(attribute, stream); + } + } + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + countElement.pack(out); + for (Iterator iterator = layoutElements.iterator(); iterator.hasNext();) { + AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next(); + layoutElement.pack(out); + } + } + + public void renumberBci(IntList bciRenumbering, Map labelsToOffsets) { + for (Iterator iterator = layoutElements.iterator(); iterator.hasNext();) { + AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next(); + layoutElement.renumberBci(bciRenumbering, labelsToOffsets); + } + } + } + + /** + * A Union is a type of layout element where the tag value acts as a + * selector for one of the union cases + */ + public class Union extends LayoutElement { + + private final Integral unionTag; + private final List unionCases; + private final List defaultCaseBody; + + public Union(String tag, List unionCases, List body) { + this.unionTag = new Integral(tag); + this.unionCases = unionCases; + this.defaultCaseBody = body; + } + + public void addAttributeToBand(NewAttribute attribute, + InputStream stream) { + unionTag.addAttributeToBand(attribute, stream); + long tag = unionTag.latestValue(); + boolean defaultCase = true; + for (int i = 0; i < unionCases.size(); i++) { + UnionCase element = (UnionCase) unionCases.get(i); + if (element.hasTag(tag)) { + defaultCase = false; + element.addAttributeToBand(attribute, stream); + } + } + if (defaultCase) { + for (int i = 0; i < defaultCaseBody.size(); i++) { + LayoutElement element = (LayoutElement) defaultCaseBody + .get(i); + element.addAttributeToBand(attribute, stream); + } + } + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + unionTag.pack(out); + for (Iterator iterator = unionCases.iterator(); iterator.hasNext();) { + UnionCase unionCase = (UnionCase) iterator.next(); + unionCase.pack(out); + } + for (Iterator iterator = defaultCaseBody.iterator(); iterator + .hasNext();) { + AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator + .next(); + layoutElement.pack(out); + } + } + + public void renumberBci(IntList bciRenumbering, Map labelsToOffsets) { + for (Iterator iterator = unionCases.iterator(); iterator.hasNext();) { + UnionCase unionCase = (UnionCase) iterator.next(); + unionCase.renumberBci(bciRenumbering, labelsToOffsets); + } + for (Iterator iterator = defaultCaseBody.iterator(); iterator + .hasNext();) { + AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator + .next(); + layoutElement.renumberBci(bciRenumbering, labelsToOffsets); + } + } + + public Integral getUnionTag() { + return unionTag; + } + + public List getUnionCases() { + return unionCases; + } + + public List getDefaultCaseBody() { + return defaultCaseBody; + } + } + + public class Call extends LayoutElement { + + private final int callableIndex; + private Callable callable; + + public Call(int callableIndex) { + this.callableIndex = callableIndex; + } + + public void setCallable(Callable callable) { + this.callable = callable; + if (callableIndex < 1) { + callable.setBackwardsCallable(); + } + } + + public void addAttributeToBand(NewAttribute attribute, + InputStream stream) { + callable.addAttributeToBand(attribute, stream); + if(callableIndex < 1) { + callable.addBackwardsCall(); + } + } + + public void pack(OutputStream out) { + // do nothing here as pack will be called on the callable at another time + } + + public void renumberBci(IntList bciRenumbering, Map labelsToOffsets) { + // do nothing here as renumberBci will be called on the callable at another time + } + + public int getCallableIndex() { + return callableIndex; + } + + public Callable getCallable() { + return callable; + } + } + + /** + * Constant Pool Reference + */ + public class Reference extends LayoutElement { + + private final String tag; + + private List band; + + private final int length; + + private boolean nullsAllowed = false; + + public Reference(String tag) { + this.tag = tag; + length = getLength(tag.charAt(tag.length() - 1)); + nullsAllowed = tag.indexOf('N') != -1; + } + + public void addAttributeToBand(NewAttribute attribute, + InputStream stream) { + int index = readInteger(4, stream); + if(tag.startsWith("RC")) { // Class + band.add(cpBands.getCPClass(attribute.readClass(index))); + } else if (tag.startsWith("RU")) { // UTF8 String + band.add(cpBands.getCPUtf8(attribute.readUTF8(index))); + } else if (tag.startsWith("RS")) { // Signature + band.add(cpBands.getCPSignature(attribute.readUTF8(index))); + } else { // Constant + band.add(cpBands.getConstant(attribute.readConst(index))); + } + // TODO method and field references + } + + public String getTag() { + return tag; + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + int[] ints; + if(nullsAllowed) { + ints = cpEntryOrNullListToArray(band); + } else { + ints = cpEntryListToArray(band); + } + byte[] encodedBand = encodeBandInt(tag, ints, Codec.UNSIGNED5); + out.write(encodedBand); + PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + + tag + "[" + ints.length + "]"); + } + + public void renumberBci(IntList bciRenumbering, Map labelsToOffsets) { + // nothing to do here + } + + } + + public class Callable implements AttributeLayoutElement { + + private final List body; + + private boolean isBackwardsCallable; + + private int backwardsCallableIndex; + + public Callable(List body) throws IOException { + this.body = body; + } + + public void setBackwardsCallableIndex(int backwardsCallableIndex) { + this.backwardsCallableIndex = backwardsCallableIndex; + } + + public void addBackwardsCall() { + backwardsCallCounts[backwardsCallableIndex]++; + } + + public boolean isBackwardsCallable() { + return isBackwardsCallable; + } + + /** + * Tells this Callable that it is a backwards callable + */ + public void setBackwardsCallable() { + this.isBackwardsCallable = true; + } + + public void addAttributeToBand(NewAttribute attribute, + InputStream stream) { + for (Iterator iterator = body.iterator(); iterator.hasNext();) { + AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next(); + layoutElement.addAttributeToBand(attribute, stream); + } + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + for (Iterator iterator = body.iterator(); iterator.hasNext();) { + AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next(); + layoutElement.pack(out); + } + } + + public void renumberBci(IntList bciRenumbering, Map labelsToOffsets) { + for (Iterator iterator = body.iterator(); iterator.hasNext();) { + AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next(); + layoutElement.renumberBci(bciRenumbering, labelsToOffsets); + } + } + + public List getBody() { + return body; + } + } + + /** + * A Union case + */ + public class UnionCase extends LayoutElement { + + private final List body; + + private final List tags; + + public UnionCase(List tags) { + this.tags = tags; + this.body = Collections.EMPTY_LIST; + } + + public boolean hasTag(long l) { + return tags.contains(new Integer((int) l)); + } + + public UnionCase(List tags, List body) throws IOException { + this.tags = tags; + this.body = body; + } + + public void addAttributeToBand(NewAttribute attribute, + InputStream stream) { + for (int i = 0; i < body.size(); i++) { + LayoutElement element = (LayoutElement) body.get(i); + element.addAttributeToBand(attribute, stream); + } + } + + public void pack(OutputStream out) throws IOException, Pack200Exception { + for (int i = 0; i < body.size(); i++) { + LayoutElement element = (LayoutElement) body.get(i); + element.pack(out); + } + } + + public void renumberBci(IntList bciRenumbering, Map labelsToOffsets) { + for (int i = 0; i < body.size(); i++) { + LayoutElement element = (LayoutElement) body.get(i); + element.renumberBci(bciRenumbering, labelsToOffsets); + } + } + + public List getBody() { + return body; + } + } + + /** + * Utility method to get the contents of the given stream, up to the next + * ']', (ignoring pairs of brackets '[' and ']') + * + * @param stream + * @return + * @throws IOException + */ + private StringReader getStreamUpToMatchingBracket(StringReader stream) + throws IOException { + StringBuffer sb = new StringBuffer(); + int foundBracket = -1; + while (foundBracket != 0) { + char c = (char) stream.read(); + if (c == ']') { + foundBracket++; + } + if (c == '[') { + foundBracket--; + } + if (!(foundBracket == 0)) { + sb.append(c); + } + } + return new StringReader(sb.toString()); + } + + private int readInteger(int i, InputStream stream) { + int result = 0; + for (int j = 0; j < i; j++) { + try { + result = result << 8 | stream.read(); + } catch (IOException e) { + throw new RuntimeException("Error reading unknown attribute"); + } + } + // use casting to preserve sign + if(i == 1) result = (byte) result; + if(i == 2) result = (short) result; + return result; + } + + /** + * Returns the {@link BHSDCodec} that should be used for the given layout + * element + * + * @param layoutElement + */ + private BHSDCodec getCodec(String layoutElement) { + if (layoutElement.indexOf('O') >= 0) { + return Codec.BRANCH5; + } else if (layoutElement.indexOf('P') >= 0) { + return Codec.BCI5; + } else if (layoutElement.indexOf('S') >= 0 && layoutElement.indexOf("KS") < 0 //$NON-NLS-1$ + && layoutElement.indexOf("RS") < 0) { //$NON-NLS-1$ + return Codec.SIGNED5; + } else if (layoutElement.indexOf('B') >= 0) { + return Codec.BYTE1; + } else { + return Codec.UNSIGNED5; + } + } + + /** + * Utility method to get the contents of the given stream, up to the next + * ']', (ignoring pairs of brackets '[' and ']') + * + * @param stream + * @return + * @throws IOException + */ + private String readUpToMatchingBracket(StringReader stream) + throws IOException { + StringBuffer sb = new StringBuffer(); + int foundBracket = -1; + while (foundBracket != 0) { + char c = (char) stream.read(); + if (c == ']') { + foundBracket++; + } + if (c == '[') { + foundBracket--; + } + if (!(foundBracket == 0)) { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Read a number from the stream and return it + * + * @param stream + * @return + * @throws IOException + */ + private Integer readNumber(StringReader stream) throws IOException { + stream.mark(1); + char first = (char) stream.read(); + boolean negative = first == '-'; + if (!negative) { + stream.reset(); + } + stream.mark(100); + int i; + int length = 0; + while ((i = (stream.read())) != -1 && Character.isDigit((char) i)) { + length++; + } + stream.reset(); + if(length == 0) { + return null; + } + char[] digits = new char[length]; + int read = stream.read(digits); + if (read != digits.length) { + throw new IOException("Error reading from the input stream"); + } + return new Integer(Integer.parseInt((negative ? "-" : "") + new String(digits))); + } + + /** + * Read a 'body' section of the layout from the given stream + * + * @param stream + * @return List of LayoutElements + * @throws IOException + */ + private List readBody(StringReader stream) throws IOException { + List layoutElements = new ArrayList(); + LayoutElement e; + while ((e = readNextLayoutElement(stream)) != null) { + layoutElements.add(e); + } + return layoutElements; + } + + /** + * Renumber any bytecode indexes or offsets as described in section 5.5.2 of + * the pack200 specification + * + * @param bciRenumbering + * @param labelsToOffsets + */ + public void renumberBci(IntList bciRenumbering, Map labelsToOffsets) { + for (Iterator iterator = attributeLayoutElements.iterator(); iterator.hasNext();) { + AttributeLayoutElement element = (AttributeLayoutElement) iterator.next(); + element.renumberBci(bciRenumbering, labelsToOffsets); + } + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/Pack200ClassReader.java b/src/main/java/org/apache/harmony/pack200/Pack200ClassReader.java new file mode 100644 index 0000000..78ded58 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/Pack200ClassReader.java @@ -0,0 +1,110 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.InputStream; + +import org.objectweb.asm.ClassReader; + +/** + * Wrapper for ClassReader that enables pack200 to obtain extra class file + * information + */ +public class Pack200ClassReader extends ClassReader { + + private boolean lastConstantHadWideIndex; + private int lastUnsignedShort; + private boolean anySyntheticAttributes; + private String fileName; + + /** + * @param b + * the contents of class file in the format of bytes + */ + public Pack200ClassReader(byte[] b) { + super(b); + } + + /** + * @param is + * the input stream of class file + * @throws IOException + */ + public Pack200ClassReader(InputStream is) throws IOException { + super(is); + } + + /** + * @param name + * @throws IOException + */ + public Pack200ClassReader(String name) throws IOException { + super(name); + } + + /** + * @param b + * @param off + * @param len + */ + public Pack200ClassReader(byte[] b, int off, int len) { + super(b, off, len); + } + + public int readUnsignedShort(int index) { + // Doing this to check whether last load-constant instruction was ldc (18) or ldc_w (19) + // TODO: Assess whether this impacts on performance + int unsignedShort = super.readUnsignedShort(index); + if(b[index - 1] == 19) { + lastUnsignedShort = unsignedShort; + } else { + lastUnsignedShort = Short.MIN_VALUE; + } + return unsignedShort; + } + + public Object readConst(int item, char[] buf) { + lastConstantHadWideIndex = item == lastUnsignedShort; + return super.readConst(item, buf); + } + + public String readUTF8(int arg0, char[] arg1) { + String utf8 = super.readUTF8(arg0, arg1); + if(!anySyntheticAttributes && "Synthetic".equals(utf8)) { + anySyntheticAttributes = true; + } + return utf8; + } + + public boolean lastConstantHadWideIndex() { + return lastConstantHadWideIndex; + } + + public boolean hasSyntheticAttributes() { + return anySyntheticAttributes; + } + + public void setFileName(String name) { + this.fileName = name; + } + + public String getFileName() { + return fileName; + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/Pack200Exception.java b/src/main/java/org/apache/harmony/pack200/Pack200Exception.java new file mode 100644 index 0000000..8f64351 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/Pack200Exception.java @@ -0,0 +1,58 @@ +/* + * 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.pack200; + +/** + * Represents a problem with a Pack200 coding/decoding issue. + */ +public class Pack200Exception extends Exception { + + private static final long serialVersionUID = 5168177401552611803L; + + /** + * Create a new Pack200 exception with the given message + * + * @param message + * the text message to display + */ + public Pack200Exception(String message) { + super(message); + } + + /** + * Create a new Pack200 exception with the given message and cause + * + * @param message + * the text message to display + * @param cause + * the {@link Throwable} that caused this problem + */ + public Pack200Exception(String message, Throwable cause) { + super(message, cause); + } + + /** + * Create a new Pack200 exception with the given cause + * + * @param cause + * the {@link Throwable} that caused this problem + */ + public Pack200Exception(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/PackingOptions.java b/src/main/java/org/apache/harmony/pack200/PackingOptions.java new file mode 100644 index 0000000..580c8a4 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/PackingOptions.java @@ -0,0 +1,369 @@ +/* + * 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.pack200; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.objectweb.asm.Attribute; + +/** + * Utility class to manage the various options available for pack200 + */ +public class PackingOptions { + + public static final String STRIP = "strip"; + public static final String ERROR = "error"; + public static final String PASS = "pass"; + public static final String KEEP = "keep"; + + // All options are initially set to their defaults + private boolean repack = false; + private boolean gzip = true; + private boolean stripDebug = false; + private boolean keepFileOrder = true; + private long segmentLimit = 1000000L; + private int effort = 5; + private String deflateHint = KEEP; + private String modificationTime = KEEP; + private List passFiles; + private String unknownAttributeAction = PASS; + private Map classAttributeActions; + private Map fieldAttributeActions; + private Map methodAttributeActions; + private Map codeAttributeActions; + private boolean verbose = false; + private boolean quiet = true; + private String logFile; + + private Attribute[] unknownAttributeTypes; + + public boolean isRepack() { + return repack; + } + + public void setRepack(boolean repack) { + this.repack = repack; + } + + public boolean isGzip() { + return gzip; + } + + public void setGzip(boolean gzip) { + this.gzip = gzip; + } + + public boolean isStripDebug() { + return stripDebug; + } + + /** + * Set strip debug attributes. If true, all debug attributes (i.e. + * LineNumberTable, SourceFile, LocalVariableTable and + * LocalVariableTypeTable attributes) are stripped when reading the input + * class files and not included in the output archive. + * + * @param stripDebug + */ + public void setStripDebug(boolean stripDebug) { + this.stripDebug = stripDebug; + } + + public boolean isKeepFileOrder() { + return keepFileOrder; + } + + public void setKeepFileOrder(boolean keepFileOrder) { + this.keepFileOrder = keepFileOrder; + } + + public long getSegmentLimit() { + return segmentLimit; + } + + /** + * Set the segment limit (equivalent to -S command line option) + * @param segmentLimit - the limit in bytes + */ + public void setSegmentLimit(long segmentLimit) { + this.segmentLimit = segmentLimit; + } + + public int getEffort() { + return effort; + } + + /** + * Set the compression effort level (0-9, equivalent to -E command line option) + * @param effort + */ + public void setEffort(int effort) { + this.effort = effort; + } + + public String getDeflateHint() { + return deflateHint; + } + + public boolean isKeepDeflateHint() { + return KEEP.equals(deflateHint); + } + + public void setDeflateHint(String deflateHint) { + this.deflateHint = deflateHint; + } + + public String getModificationTime() { + return modificationTime; + } + + public void setModificationTime(String modificationTime) { + if (!KEEP.equals(modificationTime) + && !"latest".equals(modificationTime)) { + throw new IllegalArgumentException( + "Bad argument: -m " + + modificationTime + + " ? transmit modtimes should be either latest or keep (default)"); + } + this.modificationTime = modificationTime; + } + + public boolean isPassFile(String passFileName) { + if (passFiles != null) { + for (Iterator iterator = passFiles.iterator(); iterator.hasNext();) { + String pass = (String) iterator.next(); + if (passFileName.equals(pass)) { + return true; + } else if (!pass.endsWith(".class")) { // a whole directory is + // passed + if (!pass.endsWith("/")) { + // Make sure we don't get any false positives (e.g. + // exclude "org/apache/harmony/pack" should not match + // files under "org/apache/harmony/pack200/") + pass = pass + "/"; + } + return passFileName.startsWith(pass); + } + } + } + return false; + } + + /** + * Tell the compressor to pass the file with the given name, or if the name + * is a directory name all files under that directory will be passed. + * + * @param passFileName + * the file name + */ + public void addPassFile(String passFileName) { + if(passFiles == null) { + passFiles = new ArrayList(); + } + String fileSeparator = System.getProperty("file.separator"); + if(fileSeparator.equals("\\")) { + // Need to escape backslashes for replaceAll(), which uses regex + fileSeparator += "\\"; + } + passFileName = passFileName.replaceAll(fileSeparator, "/"); + passFiles.add(passFileName); + } + + public void removePassFile(String passFileName) { + passFiles.remove(passFileName); + } + + public String getUnknownAttributeAction() { + return unknownAttributeAction; + } + + /** + * Tell the compressor what to do if an unknown attribute is encountered + * @param unknownAttributeAction - the action to perform + */ + public void setUnknownAttributeAction(String unknownAttributeAction) { + this.unknownAttributeAction = unknownAttributeAction; + if (!PASS.equals(unknownAttributeAction) + && !ERROR.equals(unknownAttributeAction) + && !STRIP.equals(unknownAttributeAction)) { + throw new RuntimeException("Incorrect option for -U, " + + unknownAttributeAction); + } + } + + public Map getClassAttributeActions() { + return classAttributeActions; + } + + public void addClassAttributeAction(String attributeName, String action) { + if(classAttributeActions == null) { + classAttributeActions = new HashMap(); + } + classAttributeActions.put(attributeName, action); + } + + public Map getFieldAttributeActions() { + return fieldAttributeActions; + } + + public void addFieldAttributeAction(String attributeName, String action) { + if(fieldAttributeActions == null) { + fieldAttributeActions = new HashMap(); + } + fieldAttributeActions.put(attributeName, action); + } + + public Map getMethodAttributeActions() { + return methodAttributeActions; + } + + public void addMethodAttributeAction(String attributeName, String action) { + if(methodAttributeActions == null) { + methodAttributeActions = new HashMap(); + } + methodAttributeActions.put(attributeName, action); + } + + public Map getCodeAttributeActions() { + return codeAttributeActions; + } + + public void addCodeAttributeAction(String attributeName, String action) { + if(codeAttributeActions == null) { + codeAttributeActions = new HashMap(); + } + codeAttributeActions.put(attributeName, action); + } + + public boolean isVerbose() { + return verbose; + } + + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + public boolean isQuiet() { + return quiet; + } + + public void setQuiet(boolean quiet) { + this.quiet = quiet; + } + + public String getLogFile() { + return logFile; + } + + public void setLogFile(String logFile) { + this.logFile = logFile; + } + + private void addOrUpdateAttributeActions(List prototypes, Map attributeActions, + int tag) { + if (attributeActions != null) { + if (attributeActions.size() > 0) { + String name, action; + boolean prototypeExists; + NewAttribute newAttribute; + for (Iterator iteratorI = attributeActions.keySet().iterator(); iteratorI + .hasNext();) { + name = (String) iteratorI.next(); + action = (String) attributeActions.get(name); + if (!ERROR.equals(action) && !STRIP.equals(action) + && !PASS.equals(action)) { + prototypeExists = false; + for (Iterator iteratorJ = prototypes.iterator(); iteratorJ + .hasNext();) { + newAttribute = (NewAttribute) iteratorJ.next(); + if (newAttribute.type.equals(name)) { + // if the attribute exists, update its context + newAttribute.addContext(tag); + prototypeExists = true; + break; + } + // if no attribute is found, add a new attribute + if (!prototypeExists) { + newAttribute = new NewAttribute(name, action, + tag); + prototypes.add(newAttribute); + } + } + } + } + } + } + } + + public Attribute[] getUnknownAttributePrototypes() { + if (unknownAttributeTypes == null) { + List prototypes = new ArrayList(); + addOrUpdateAttributeActions(prototypes, classAttributeActions, + AttributeDefinitionBands.CONTEXT_CLASS); + + addOrUpdateAttributeActions(prototypes, methodAttributeActions, + AttributeDefinitionBands.CONTEXT_METHOD); + + addOrUpdateAttributeActions(prototypes, fieldAttributeActions, + AttributeDefinitionBands.CONTEXT_FIELD); + + addOrUpdateAttributeActions(prototypes, codeAttributeActions, + AttributeDefinitionBands.CONTEXT_CODE); + + unknownAttributeTypes = (Attribute[]) prototypes + .toArray(new Attribute[0]); + } + return unknownAttributeTypes; + } + + public String getUnknownClassAttributeAction(String type) { + String action = (String) classAttributeActions.get(type); + if(action == null) { + action = unknownAttributeAction; + } + return action; + } + + public String getUnknownMethodAttributeAction(String type) { + String action = (String) methodAttributeActions.get(type); + if(action == null) { + action = unknownAttributeAction; + } + return action; + } + + public String getUnknownFieldAttributeAction(String type) { + String action = (String) fieldAttributeActions.get(type); + if(action == null) { + action = unknownAttributeAction; + } + return action; + } + + public String getUnknownCodeAttributeAction(String type) { + String action = (String) codeAttributeActions.get(type); + if(action == null) { + action = unknownAttributeAction; + } + return action; + } + +} diff --git a/src/main/java/org/apache/harmony/pack200/PackingUtils.java b/src/main/java/org/apache/harmony/pack200/PackingUtils.java new file mode 100644 index 0000000..5433e83 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/PackingUtils.java @@ -0,0 +1,250 @@ +/* + * 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.pack200; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import org.apache.harmony.pack200.Archive.PackingFile; + +public class PackingUtils { + + private static PackingLogger packingLogger; + + static { + packingLogger = new PackingLogger("org.harmony.apache.pack200", null); + LogManager.getLogManager().addLogger(packingLogger); + } + + private static class PackingLogger extends Logger { + + private boolean verbose = false; + + protected PackingLogger(String name, String resourceBundleName) { + super(name, resourceBundleName); + } + + public void log(LogRecord logRecord) { + if (verbose) { + super.log(logRecord); + } + } + + public void setVerbose(boolean isVerbose) { + verbose = isVerbose; + } + } + + public static void config(PackingOptions options) throws IOException { + String logFileName = options.getLogFile(); + if (logFileName != null) { + FileHandler fileHandler = new FileHandler(logFileName, false); + fileHandler.setFormatter(new SimpleFormatter()); + packingLogger.addHandler(fileHandler); + packingLogger.setUseParentHandlers(false); + } + + packingLogger.setVerbose(options.isVerbose()); + } + + public static void log(String message) { + packingLogger.log(Level.INFO, message); + } + + /** + * When effort is 0, the packer copys through the original jar input stream + * without compression + * + * @param jarInputStream + * the jar input stream + * @param jarOutputStream + * the jar output stream + * @throws IOException + */ + public static void copyThroughJar(JarInputStream jarInputStream, + OutputStream outputStream) throws IOException { + Manifest manifest = jarInputStream.getManifest(); + JarOutputStream jarOutputStream = new JarOutputStream(outputStream, + manifest); + jarOutputStream.setComment("PACK200"); + log("Packed " + JarFile.MANIFEST_NAME); + + byte[] bytes = new byte[16384]; + JarEntry jarEntry; + int bytesRead; + while ((jarEntry = jarInputStream.getNextJarEntry()) != null) { + jarOutputStream.putNextEntry(jarEntry); + while ((bytesRead = jarInputStream.read(bytes)) != -1) { + jarOutputStream.write(bytes, 0, bytesRead); + } + log("Packed " + jarEntry.getName()); + } + jarInputStream.close(); + jarOutputStream.close(); + } + + /** + * When effort is 0, the packer copys through the original jar file without + * compression + * + * @param jarFile + * the input jar file + * @param jarOutputStream + * the jar output stream + * @throws IOException + */ + public static void copyThroughJar(JarFile jarFile, OutputStream outputStream) + throws IOException { + JarOutputStream jarOutputStream = new JarOutputStream(outputStream); + jarOutputStream.setComment("PACK200"); + byte[] bytes = new byte[16384]; + Enumeration entries = jarFile.entries(); + InputStream inputStream; + JarEntry jarEntry; + int bytesRead; + while (entries.hasMoreElements()) { + jarEntry = (JarEntry) entries.nextElement(); + jarOutputStream.putNextEntry(jarEntry); + inputStream = jarFile.getInputStream(jarEntry); + while ((bytesRead = inputStream.read(bytes)) != -1) { + jarOutputStream.write(bytes, 0, bytesRead); + } + jarOutputStream.closeEntry(); + log("Packed " + jarEntry.getName()); + } + jarFile.close(); + jarOutputStream.close(); + } + + public static List getPackingFileListFromJar(JarInputStream jarInputStream, + boolean keepFileOrder) throws IOException { + List packingFileList = new ArrayList(); + + // add manifest file + Manifest manifest = jarInputStream.getManifest(); + if (manifest != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + manifest.write(baos); + packingFileList.add(new PackingFile(JarFile.MANIFEST_NAME, baos + .toByteArray(), 0)); + } + + // add rest of entries in the jar + JarEntry jarEntry; + byte[] bytes; + while ((jarEntry = jarInputStream.getNextJarEntry()) != null) { + bytes = readJarEntry(jarEntry, new BufferedInputStream( + jarInputStream)); + packingFileList.add(new PackingFile(bytes, jarEntry)); + } + + // check whether it need reorder packing file list + if (!keepFileOrder) { + reorderPackingFiles(packingFileList); + } + return packingFileList; + } + + public static List getPackingFileListFromJar(JarFile jarFile, + boolean keepFileOrder) throws IOException { + List packingFileList = new ArrayList(); + Enumeration jarEntries = jarFile.entries(); + JarEntry jarEntry; + byte[] bytes; + while (jarEntries.hasMoreElements()) { + jarEntry = (JarEntry) jarEntries.nextElement(); + bytes = readJarEntry(jarEntry, new BufferedInputStream(jarFile + .getInputStream(jarEntry))); + packingFileList.add(new PackingFile(bytes, jarEntry)); + } + + // check whether it need reorder packing file list + if (!keepFileOrder) { + reorderPackingFiles(packingFileList); + } + return packingFileList; + } + + private static byte[] readJarEntry(JarEntry jarEntry, + InputStream inputStream) throws IOException { + long size = jarEntry.getSize(); + if (size > Integer.MAX_VALUE) { + // TODO: Should probably allow this + throw new RuntimeException("Large Class!"); + } else if (size < 0) { + size = 0; + } + byte[] bytes = new byte[(int) size]; + if (inputStream.read(bytes) != size) { + throw new RuntimeException("Error reading from stream"); + } + return bytes; + } + + private static void reorderPackingFiles(List packingFileList) { + Iterator iterator = packingFileList.iterator(); + PackingFile packingFile; + while (iterator.hasNext()) { + packingFile = (PackingFile) iterator.next(); + if (packingFile.isDirectory()) { + // remove directory entries + iterator.remove(); + } + } + + // Sort files by name, "META-INF/MANIFEST.MF" should be put in the 1st + // position + Collections.sort(packingFileList, new Comparator() { + public int compare(Object arg0, Object arg1) { + if (arg0 instanceof PackingFile && arg1 instanceof PackingFile) { + String fileName0 = ((PackingFile) arg0).getName(); + String fileName1 = ((PackingFile) arg1).getName(); + if (fileName0.equals(fileName1)) { + return 0; + } else if (JarFile.MANIFEST_NAME.equals(fileName0)) { + return -1; + } else if (JarFile.MANIFEST_NAME.equals(fileName1)) { + return 1; + } + return fileName0.compareTo(fileName1); + } + throw new IllegalArgumentException(); + } + }); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/pack200/PopulationCodec.java b/src/main/java/org/apache/harmony/pack200/PopulationCodec.java new file mode 100644 index 0000000..48ad1e7 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/PopulationCodec.java @@ -0,0 +1,169 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.InputStream; + + +/** + * A PopulationCodec is a Codec that is well suited to encoding data that shows + * statistical or repetitive patterns, containing for example a few numbers + * which are repeated a lot throughout the set, but not necessarily + * sequentially. + */ +public class PopulationCodec extends Codec { + + private final Codec favouredCodec; + private Codec tokenCodec; + private final Codec unfavouredCodec; + private int l; + private int[] favoured; + + public PopulationCodec(Codec favouredCodec, Codec tokenCodec, + Codec unvafouredCodec) { + this.favouredCodec = favouredCodec; + this.tokenCodec = tokenCodec; + this.unfavouredCodec = unvafouredCodec; + } + + public PopulationCodec(Codec favouredCodec, int l, Codec unfavouredCodec) { + if (l >= 256 || l <= 0) { + throw new IllegalArgumentException("L must be between 1..255"); + } + this.favouredCodec = favouredCodec; + this.l = l; + this.unfavouredCodec = unfavouredCodec; + } + + public int decode(InputStream in) throws IOException, Pack200Exception { + throw new Pack200Exception( + "Population encoding does not work unless the number of elements are known"); + } + + public int decode(InputStream in, long last) throws IOException, + Pack200Exception { + throw new Pack200Exception( + "Population encoding does not work unless the number of elements are known"); + } + + public int[] decodeInts(int n, InputStream in) throws IOException, + Pack200Exception { + lastBandLength = 0; + favoured = new int[n]; // there must be <= n values, but probably a lot + // less + int result[]; + // read table of favorites first + int smallest = Integer.MAX_VALUE, absoluteSmallest; + int last = 0; + int value = 0, absoluteValue; + int k = -1; + while (true) { + value = favouredCodec.decode(in, last); + if (k > -1 && (value == smallest || value == last)) { + break; + } + favoured[++k] = value; + absoluteSmallest = Math.abs(smallest); + absoluteValue = Math.abs(value); + if (absoluteSmallest > absoluteValue) { + smallest = value; + } else if (absoluteSmallest == absoluteValue) { + // ensure that -X and +X -> +X + smallest = absoluteSmallest; + } + last = value; + } + lastBandLength += k; + // if tokenCodec needs to be derived from the T, L and K values + if (tokenCodec == null) { + if (k < 256) { + tokenCodec = Codec.BYTE1; + } else { + // if k >= 256, b >= 2 + int b = 1; + BHSDCodec codec = null; + while (++b < 5) { + codec = new BHSDCodec(b, 256 - l, 0); + if (codec.encodes(k)) { + tokenCodec = codec; + break; + } + } + if (tokenCodec == null) { + throw new Pack200Exception( + "Cannot calculate token codec from " + k + " and " + + l); + } + } + } + // read favorites + lastBandLength += n; + result = tokenCodec.decodeInts(n, in); + // read unfavorites + last = 0; + for (int i = 0; i < n; i++) { + int index = result[i]; + if (index == 0) { + lastBandLength++; + result[i] = last = unfavouredCodec.decode(in, last); + } else { + result[i] = favoured[index - 1]; + } + } + return result; + } + + public int[] getFavoured() { + return favoured; + } + + public Codec getFavouredCodec() { + return favouredCodec; + } + + public Codec getUnfavouredCodec() { + return unfavouredCodec; + } + + public byte[] encode(int value, int last) throws Pack200Exception { + throw new Pack200Exception( + "Population encoding does not work unless the number of elements are known"); + } + + public byte[] encode(int value) throws Pack200Exception { + throw new Pack200Exception( + "Population encoding does not work unless the number of elements are known"); + } + + public byte[] encode(int[] favoured, int[] tokens, int[] unfavoured) throws Pack200Exception { + byte[] favouredEncoded = favouredCodec.encode(favoured); + byte[] tokensEncoded = tokenCodec.encode(tokens); + byte[] unfavouredEncoded = unfavouredCodec.encode(unfavoured); + byte[] band = new byte[favouredEncoded.length + tokensEncoded.length + unfavouredEncoded.length]; + + return band; + } + + public Codec getTokenCodec() { + return tokenCodec; + } + + public int getL() { + return l; + } +} diff --git a/src/main/java/org/apache/harmony/pack200/RunCodec.java b/src/main/java/org/apache/harmony/pack200/RunCodec.java new file mode 100644 index 0000000..1f45a00 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/RunCodec.java @@ -0,0 +1,158 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + + +/** + * A run codec is a grouping of two nested codecs; K values are decoded from the + * first codec, and the remaining codes are decoded from the remaining codec. + * Note that since this codec maintains state, the instances are not reusable. + */ +public class RunCodec extends Codec { + + private int k; + private final Codec aCodec; + private final Codec bCodec; + private int last; + + public RunCodec(int k, Codec aCodec, Codec bCodec) throws Pack200Exception { + if (k <= 0) { + throw new Pack200Exception( + "Cannot have a RunCodec for a negative number of numbers"); + } + if (aCodec == null || bCodec == null) { + throw new Pack200Exception("Must supply both codecs for a RunCodec"); + } + this.k = k; + this.aCodec = aCodec; + this.bCodec = bCodec; + } + + public int decode(InputStream in) throws IOException, Pack200Exception { + return decode(in, this.last); + } + + public int decode(InputStream in, long last) throws IOException, + Pack200Exception { + if (--k >= 0) { + int value = aCodec.decode(in, this.last); + this.last = (k == 0 ? 0 : value); + return normalise(value, aCodec); + } else { + this.last = bCodec.decode(in, this.last); + return normalise(this.last, bCodec); + } + } + + private int normalise(int value, Codec codecUsed) { + if (codecUsed instanceof BHSDCodec) { + BHSDCodec bhsd = (BHSDCodec) codecUsed; + if (bhsd.isDelta()) { + long cardinality = bhsd.cardinality(); + while (value > bhsd.largest()) { + value -= cardinality; + } + while (value < bhsd.smallest()) { + value += cardinality; + } + } + } + return value; + } + + public int[] decodeInts(int n, InputStream in) throws IOException, + Pack200Exception { + int[] band = new int[n]; + int[] aValues = aCodec.decodeInts(k, in); + normalise(aValues, aCodec); + int[] bValues = bCodec.decodeInts(n - k, in); + normalise(bValues, bCodec); + System.arraycopy(aValues, 0, band, 0, k); + System.arraycopy(bValues, 0, band, k, n - k); + lastBandLength = aCodec.lastBandLength + bCodec.lastBandLength; + return band; + } + + private void normalise(int[] band, Codec codecUsed) { + if (codecUsed instanceof BHSDCodec) { + BHSDCodec bhsd = (BHSDCodec) codecUsed; + if (bhsd.isDelta()) { + long cardinality = bhsd.cardinality(); + for (int i = 0; i < band.length; i++) { + while (band[i] > bhsd.largest()) { + band[i] -= cardinality; + } + while (band[i] < bhsd.smallest()) { + band[i] += cardinality; + } + } + } + } else if (codecUsed instanceof PopulationCodec) { + PopulationCodec popCodec = (PopulationCodec) codecUsed; + int[] favoured = (int[]) popCodec.getFavoured().clone(); + Arrays.sort(favoured); + for (int i = 0; i < band.length; i++) { + boolean favouredValue = Arrays.binarySearch(favoured, band[i]) > -1; + Codec theCodec = favouredValue ? popCodec.getFavouredCodec() + : popCodec.getUnfavouredCodec(); + if (theCodec instanceof BHSDCodec) { + BHSDCodec bhsd = (BHSDCodec) theCodec; + if (bhsd.isDelta()) { + long cardinality = bhsd.cardinality(); + while (band[i] > bhsd.largest()) { + band[i] -= cardinality; + } + while (band[i] < bhsd.smallest()) { + band[i] += cardinality; + } + } + } + } + } + } + + public String toString() { + return "RunCodec[k=" + k + ";aCodec=" + aCodec + "bCodec=" + bCodec + + "]"; + } + + public byte[] encode(int value, int last) throws Pack200Exception { + // TODO Auto-generated method stub + return null; + } + + public byte[] encode(int value) throws Pack200Exception { + // TODO Auto-generated method stub + return null; + } + + public int getK() { + return k; + } + + public Codec getACodec() { + return aCodec; + } + + public Codec getBCodec() { + return bCodec; + } +} diff --git a/src/main/java/org/apache/harmony/pack200/Segment.java b/src/main/java/org/apache/harmony/pack200/Segment.java new file mode 100644 index 0000000..2f748c0 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/Segment.java @@ -0,0 +1,676 @@ +/* + * 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.pack200; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.harmony.pack200.Archive.PackingFile; +import org.apache.harmony.pack200.Archive.SegmentUnit; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +/** + * A Pack200 archive consists of one or more Segments. + */ +public class Segment implements ClassVisitor { + + private SegmentHeader segmentHeader; + private CpBands cpBands; + private AttributeDefinitionBands attributeDefinitionBands; + private IcBands icBands; + private ClassBands classBands; + private BcBands bcBands; + private FileBands fileBands; + + private final SegmentFieldVisitor fieldVisitor = new SegmentFieldVisitor(); + private final SegmentMethodVisitor methodVisitor = new SegmentMethodVisitor(); + private Pack200ClassReader currentClassReader; + private PackingOptions options; + private boolean stripDebug; + private Attribute[] nonStandardAttributePrototypes; + + /** + * The main method on Segment. Reads in all the class files, packs them and + * then writes the packed segment out to the given OutputStream. + * + * @param classes + * List of Pack200ClassReaders, one for each class file in the + * segment + * @param files + * List of Archive.Files, one for each file in the segment + * @param out + * the OutputStream to write the packed Segment to + * @param options + * packing options + * @throws IOException + * @throws Pack200Exception + */ + public void pack(SegmentUnit segmentUnit, OutputStream out, PackingOptions options) + throws IOException, Pack200Exception { + this.options = options; + this.stripDebug = options.isStripDebug(); + int effort = options.getEffort(); + nonStandardAttributePrototypes = options.getUnknownAttributePrototypes(); + + PackingUtils.log("Start to pack a new segment with " + + segmentUnit.fileListSize() + " files including " + + segmentUnit.classListSize() + " classes"); + + PackingUtils.log("Initialize a header for the segment"); + segmentHeader = new SegmentHeader(); + segmentHeader.setFile_count(segmentUnit.fileListSize()); + segmentHeader.setHave_all_code_flags(!stripDebug); + if (!options.isKeepDeflateHint()) { + segmentHeader.setDeflate_hint("true".equals(options + .getDeflateHint())); + } + + PackingUtils.log("Setup constant pool bands for the segment"); + cpBands = new CpBands(this, effort); + + PackingUtils.log("Setup attribute definition bands for the segment"); + attributeDefinitionBands = new AttributeDefinitionBands(this, effort, nonStandardAttributePrototypes); + + PackingUtils.log("Setup internal class bands for the segment"); + icBands = new IcBands(segmentHeader, cpBands, effort); + + PackingUtils.log("Setup class bands for the segment"); + classBands = new ClassBands(this, segmentUnit.classListSize(), effort, stripDebug); + + PackingUtils.log("Setup byte code bands for the segment"); + bcBands = new BcBands(cpBands, this, effort); + + PackingUtils.log("Setup file bands for the segment"); + fileBands = new FileBands(cpBands, segmentHeader, options, segmentUnit, effort); + + processClasses(segmentUnit); + + cpBands.finaliseBands(); + attributeDefinitionBands.finaliseBands(); + icBands.finaliseBands(); + classBands.finaliseBands(); + bcBands.finaliseBands(); + fileBands.finaliseBands(); + + // Using a temporary stream because we have to pack the other bands + // before segmentHeader because the band_headers band is only created + // when the other bands are packed, but comes before them in the packed + // file. + ByteArrayOutputStream bandsOutputStream = new ByteArrayOutputStream(); + + PackingUtils.log("Packing..."); + cpBands.pack(bandsOutputStream); + attributeDefinitionBands.pack(bandsOutputStream); + icBands.pack(bandsOutputStream); + classBands.pack(bandsOutputStream); + bcBands.pack(bandsOutputStream); + fileBands.pack(bandsOutputStream); + + ByteArrayOutputStream headerOutputStream = new ByteArrayOutputStream(); + segmentHeader.pack(headerOutputStream); + + headerOutputStream.writeTo(out); + bandsOutputStream.writeTo(out); + + segmentUnit.addPackedByteAmount(headerOutputStream.size()); + segmentUnit.addPackedByteAmount(bandsOutputStream.size()); + + PackingUtils.log("Wrote total of " + segmentUnit.getPackedByteAmount() + + " bytes"); + PackingUtils.log("Transmitted " + segmentUnit.fileListSize() + " files of " + + segmentUnit.getByteAmount() + " input bytes in a segment of " + + segmentUnit.getPackedByteAmount() + " bytes"); + } + + private void processClasses(SegmentUnit segmentUnit) throws Pack200Exception { + segmentHeader.setClass_count(segmentUnit.classListSize()); + for (Iterator iterator = segmentUnit.getClassList().iterator(); iterator.hasNext();) { + Pack200ClassReader classReader = (Pack200ClassReader) iterator + .next(); + currentClassReader = classReader; + int flags = 0; + if(stripDebug) { + flags |= ClassReader.SKIP_DEBUG; + } + try { + classReader.accept(this, flags); + } catch (PassException pe) { + // Pass this class through as-is rather than packing it + // TODO: probably need to deal with any inner classes + classBands.removeCurrentClass(); + String name = classReader.getFileName(); + boolean found = false; + for (Iterator iterator2 = segmentUnit.getFileList().iterator(); iterator2 + .hasNext();) { + PackingFile file = (PackingFile) iterator2.next(); + if(file.getName().equals(name)) { + found = true; + file.setContents(classReader.b); + break; + } + } + if(!found) { + throw new Pack200Exception("Error passing file " + name); + } + } + } + } + + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + bcBands.setCurrentClass(name, superName); + segmentHeader.addMajorVersion(version); + classBands.addClass(version, access, name, signature, superName, + interfaces); + } + + public void visitSource(String source, String debug) { + if(!stripDebug) { + classBands.addSourceFile(source); + } + } + + public void visitOuterClass(String owner, String name, String desc) { + classBands.addEnclosingMethod(owner, name, desc); + + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return new SegmentAnnotationVisitor(MetadataBandGroup.CONTEXT_CLASS, + desc, visible); + } + + public void visitAttribute(Attribute attribute) { + if(attribute.isUnknown()) { + String action = options.getUnknownAttributeAction(); + if(action.equals(PackingOptions.PASS)) { + passCurrentClass(); + } else if (action.equals(PackingOptions.ERROR)) { + throw new Error("Unknown attribute encountered"); + } // else skip + } else { + if(attribute instanceof NewAttribute) { + NewAttribute newAttribute = (NewAttribute) attribute; + if(newAttribute.isUnknown(AttributeDefinitionBands.CONTEXT_CLASS)) { + String action = options.getUnknownClassAttributeAction(newAttribute.type); + if(action.equals(PackingOptions.PASS)) { + passCurrentClass(); + } else if (action.equals(PackingOptions.ERROR)) { + throw new Error("Unknown attribute encountered"); + } // else skip + } + classBands.addClassAttribute(newAttribute); + } else { + throw new RuntimeException("Unexpected attribute encountered: " + attribute.type); + } + } + } + + public void visitInnerClass(String name, String outerName, + String innerName, int flags) { + icBands.addInnerClass(name, outerName, innerName, flags); + } + + public FieldVisitor visitField(int flags, String name, String desc, + String signature, Object value) { + classBands.addField(flags, name, desc, signature, value); + return fieldVisitor; + } + + public MethodVisitor visitMethod(int flags, String name, String desc, + String signature, String[] exceptions) { + classBands.addMethod(flags, name, desc, signature, exceptions); + return methodVisitor; + } + + public void visitEnd() { + classBands.endOfClass(); + } + + /** + * This class implements MethodVisitor to visit the contents and metadata + * related to methods in a class file. + * + * It delegates to BcBands for bytecode related visits and to ClassBands for + * everything else. + */ + public class SegmentMethodVisitor implements MethodVisitor { + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return new SegmentAnnotationVisitor( + MetadataBandGroup.CONTEXT_METHOD, desc, visible); + } + + public AnnotationVisitor visitAnnotationDefault() { + return new SegmentAnnotationVisitor(MetadataBandGroup.CONTEXT_METHOD); + } + + public void visitAttribute(Attribute attribute) { + if(attribute.isUnknown()) { + String action = options.getUnknownAttributeAction(); + if(action.equals(PackingOptions.PASS)) { + passCurrentClass(); + } else if (action.equals(PackingOptions.ERROR)) { + throw new Error("Unknown attribute encountered"); + } // else skip + } else { + if(attribute instanceof NewAttribute) { + NewAttribute newAttribute = (NewAttribute) attribute; + if (attribute.isCodeAttribute()) { + if (newAttribute.isUnknown(AttributeDefinitionBands.CONTEXT_CODE)) { + String action = options + .getUnknownCodeAttributeAction(newAttribute.type); + if (action.equals(PackingOptions.PASS)) { + passCurrentClass(); + } else if (action.equals(PackingOptions.ERROR)) { + throw new Error("Unknown attribute encountered"); + } // else skip + } + classBands.addCodeAttribute(newAttribute); + } else { + if (newAttribute.isUnknown(AttributeDefinitionBands.CONTEXT_METHOD)) { + String action = options + .getUnknownMethodAttributeAction(newAttribute.type); + if (action.equals(PackingOptions.PASS)) { + passCurrentClass(); + } else if (action.equals(PackingOptions.ERROR)) { + throw new Error("Unknown attribute encountered"); + } // else skip + } + classBands.addMethodAttribute(newAttribute); + } + } else { + throw new RuntimeException("Unexpected attribute encountered: " + attribute.type); + } + } + } + + public void visitCode() { + classBands.addCode(); + } + + public void visitFrame(int arg0, int arg1, Object[] arg2, int arg3, + Object[] arg4) { + // TODO: Java 6 - implement support for this + + } + + public void visitLabel(Label label) { + bcBands.visitLabel(label); + } + + public void visitLineNumber(int line, Label start) { + if(!stripDebug) { + classBands.addLineNumber(line, start); + } + } + + public void visitLocalVariable(String name, String desc, + String signature, Label start, Label end, int index) { + if(!stripDebug) { + classBands.addLocalVariable(name, desc, signature, start, end, + index); + } + } + + public void visitMaxs(int maxStack, int maxLocals) { + classBands.addMaxStack(maxStack, maxLocals); + } + + public AnnotationVisitor visitParameterAnnotation(int parameter, + String desc, boolean visible) { + return new SegmentAnnotationVisitor( + MetadataBandGroup.CONTEXT_METHOD, parameter, desc, visible); + } + + public void visitTryCatchBlock(Label start, Label end, Label handler, + String type) { + classBands.addHandler(start, end, handler, type); + } + + public void visitEnd() { + classBands.endOfMethod(); + bcBands.visitEnd(); + } + + public void visitFieldInsn(int opcode, String owner, String name, + String desc) { + bcBands.visitFieldInsn(opcode, owner, name, desc); + } + + public void visitIincInsn(int var, int increment) { + bcBands.visitIincInsn(var, increment); + } + + public void visitInsn(int opcode) { + bcBands.visitInsn(opcode); + } + + public void visitIntInsn(int opcode, int operand) { + bcBands.visitIntInsn(opcode, operand); + } + + public void visitJumpInsn(int opcode, Label label) { + bcBands.visitJumpInsn(opcode, label); + } + + public void visitLdcInsn(Object cst) { + bcBands.visitLdcInsn(cst); + } + + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + bcBands.visitLookupSwitchInsn(dflt, keys, labels); + } + + public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + bcBands.visitMethodInsn(opcode, owner, name, desc); + } + + public void visitMultiANewArrayInsn(String desc, int dimensions) { + bcBands.visitMultiANewArrayInsn(desc, dimensions); + } + + public void visitTableSwitchInsn(int min, int max, Label dflt, + Label[] labels) { + bcBands.visitTableSwitchInsn(min, max, dflt, labels); + } + + public void visitTypeInsn(int opcode, String type) { + bcBands.visitTypeInsn(opcode, type); + } + + public void visitVarInsn(int opcode, int var) { + bcBands.visitVarInsn(opcode, var); + } + + } + + public ClassBands getClassBands() { + return classBands; + } + + /** + * SegmentAnnotationVisitor implements AnnotationVisitor to + * visit Annotations found in a class file. + */ + public class SegmentAnnotationVisitor implements AnnotationVisitor { + + private int context = -1; + private int parameter = -1; + private String desc; + private boolean visible; + + private final List nameRU = new ArrayList(); + private final List T = new ArrayList(); // tags + private final List values = new ArrayList(); + private final List caseArrayN = new ArrayList(); + private final List nestTypeRS = new ArrayList(); + private final List nestNameRU = new ArrayList(); + private final List nestPairN = new ArrayList(); + + public SegmentAnnotationVisitor(int context, String desc, + boolean visible) { + this.context = context; + this.desc = desc; + this.visible = visible; + } + + public SegmentAnnotationVisitor(int context) { + this.context = context; + } + + public SegmentAnnotationVisitor(int context, int parameter, + String desc, boolean visible) { + this.context = context; + this.parameter = parameter; + this.desc = desc; + this.visible = visible; + } + + public void visit(String name, Object value) { + if (name == null) { + name = ""; + } + nameRU.add(name); + values.add(value); + addTag(value); + } + + private void addTag(Object value) { + if(value instanceof Integer) { + T.add("I"); + } else if (value instanceof Double) { + T.add("D"); + } else if (value instanceof Float) { + T.add("F"); + } else if (value instanceof Long) { + T.add("J"); + } else if (value instanceof Byte) { + T.add("B"); + } else if (value instanceof Character) { + T.add("C"); + } else if (value instanceof Short) { + T.add("S"); + } else if (value instanceof Boolean) { + T.add("Z"); + } else if (value instanceof String) { + T.add("s"); + } else if (value instanceof Type) { + T.add("c"); + } + } + + public AnnotationVisitor visitAnnotation(String name, String desc) { + T.add("@"); + if (name == null) { + name = ""; + } + nameRU.add(name); + nestTypeRS.add(desc); + nestPairN.add(new Integer(0)); + return new AnnotationVisitor() { + public void visit(String name, Object value) { + Integer numPairs = (Integer) nestPairN.remove(nestPairN.size() - 1); + nestPairN.add(new Integer(numPairs.intValue() + 1)); + nestNameRU.add(name); + values.add(value); + addTag(value); + } + + public AnnotationVisitor visitAnnotation(String arg0, + String arg1) { + throw new RuntimeException("Not yet supported"); +// return null; + } + + public AnnotationVisitor visitArray(String arg0) { + throw new RuntimeException("Not yet supported"); +// return null; + } + + public void visitEnd() { + } + + public void visitEnum(String name, String desc, String value) { + Integer numPairs = (Integer) nestPairN.remove(nestPairN.size() - 1); + nestPairN.add(new Integer(numPairs.intValue() + 1)); + T.add("e"); + nestNameRU.add(name); + values.add(desc); + values.add(value); + } + }; + } + + public AnnotationVisitor visitArray(String name) { + T.add("["); + if (name == null) { + name = ""; + } + nameRU.add(name); + caseArrayN.add(new Integer(0)); + return new AnnotationVisitor() { + public void visit(String name, Object value) { + Integer numCases = (Integer) caseArrayN.remove(caseArrayN.size() - 1); + caseArrayN.add(new Integer(numCases.intValue() + 1)); + if (name == null) { + name = ""; + } + nameRU.add(name); + values.add(value); + addTag(value); + } + + public AnnotationVisitor visitAnnotation(String arg0, + String arg1) { + throw new RuntimeException("Not yet supported"); + } + + public AnnotationVisitor visitArray(String arg0) { + throw new RuntimeException("Not yet supported"); +// return null; + } + + public void visitEnd() { + } + + public void visitEnum(String name, String desc, String value) { + Integer numCases = (Integer) caseArrayN.remove(caseArrayN.size() - 1); + caseArrayN.add(new Integer(numCases.intValue() + 1)); + T.add("e"); + if(name == null) { + name = ""; + } + nameRU.add(name); + values.add(desc); + values.add(value); + } + }; + } + + public void visitEnd() { + if (desc == null) { + Segment.this.classBands.addAnnotationDefault(nameRU, T, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + } else if(parameter != -1) { + Segment.this.classBands.addParameterAnnotation(parameter, desc, visible, nameRU, T, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + } else { + Segment.this.classBands.addAnnotation(context, desc, visible, nameRU, T, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN); + } + } + + public void visitEnum(String name, String desc, String value) { + T.add("e"); + if (name == null) { + name = ""; + } + nameRU.add(name); + values.add(desc); + values.add(value); + } + } + + /** + * SegmentFieldVisitor implements FieldVisitor to visit the + * metadata relating to fields in a class file. + */ + public class SegmentFieldVisitor implements FieldVisitor { + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return new SegmentAnnotationVisitor(MetadataBandGroup.CONTEXT_FIELD, + desc, visible); + } + + public void visitAttribute(Attribute attribute) { + if(attribute.isUnknown()) { + String action = options.getUnknownAttributeAction(); + if(action.equals(PackingOptions.PASS)) { + passCurrentClass(); + } else if (action.equals(PackingOptions.ERROR)) { + throw new Error("Unknown attribute encountered"); + } // else skip + } else { + if(attribute instanceof NewAttribute) { + NewAttribute newAttribute = (NewAttribute) attribute; + if(newAttribute.isUnknown(AttributeDefinitionBands.CONTEXT_FIELD)) { + String action = options.getUnknownFieldAttributeAction(newAttribute.type); + if(action.equals(PackingOptions.PASS)) { + passCurrentClass(); + } else if (action.equals(PackingOptions.ERROR)) { + throw new Error("Unknown attribute encountered"); + } // else skip + } + classBands.addFieldAttribute(newAttribute); + } else { + throw new RuntimeException("Unexpected attribute encountered: " + attribute.type); + } + } + } + + public void visitEnd() { + } + } + + public boolean lastConstantHadWideIndex() { + return currentClassReader.lastConstantHadWideIndex(); + } + + public CpBands getCpBands() { + return cpBands; + } + + public SegmentHeader getSegmentHeader() { + return segmentHeader; + } + + public AttributeDefinitionBands getAttrBands() { + return attributeDefinitionBands; + } + + public IcBands getIcBands() { + return icBands; + } + + public Pack200ClassReader getCurrentClassReader() { + return currentClassReader; + } + + private void passCurrentClass() { + throw new PassException(); + } + + /** + * Exception indicating that the class currently being visited contains an + * unknown attribute, which means that by default the class file needs to be + * passed through as-is in the file_bands rather than being packed with + * pack200. + */ + public class PassException extends RuntimeException { + + } +} diff --git a/src/main/java/org/apache/harmony/pack200/SegmentHeader.java b/src/main/java/org/apache/harmony/pack200/SegmentHeader.java new file mode 100644 index 0000000..c06f985 --- /dev/null +++ b/src/main/java/org/apache/harmony/pack200/SegmentHeader.java @@ -0,0 +1,392 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * SegmentHeader is the header band of a {@link Segment}. Corresponds to + * segment_header in the pack200 specification. + */ +public class SegmentHeader extends BandSet { + + /** + * Create a new SegmentHeader + */ + public SegmentHeader() { + super(1, null); // Pass 1 for effort because bands in the segment header + // should always use the default encoding + } + + private static final int[] magic = { 0xCA, 0xFE, 0xD0, 0x0D }; + private static final int archive_minver = 7; + private static final int archive_majver = 150; + + private int archive_options; + + private int cp_Utf8_count; + private int cp_Int_count; + private int cp_Float_count; + private int cp_Long_count; + private int cp_Double_count; + private int cp_String_count; + private int cp_Class_count; + private int cp_Signature_count; + private int cp_Descr_count; + private int cp_Field_count; + private int cp_Method_count; + private int cp_Imethod_count; + + private int attribute_definition_count; + private final IntList band_headers = new IntList(); + + private boolean have_all_code_flags = true; // true by default + + private int archive_size_hi; + private int archive_size_lo; + private int archive_next_count; + private int archive_modtime; + private int file_count; + + private boolean deflate_hint; + private boolean have_file_modtime = true; + private boolean have_file_options = true; + private boolean have_file_size_hi; + private boolean have_class_flags_hi; + private boolean have_field_flags_hi; + private boolean have_method_flags_hi; + private boolean have_code_flags_hi; + + private int ic_count; + private int class_count; + private final Counter majverCounter = new Counter(); + + /** + * Encode and write the SegmentHeader bands to the OutputStream + */ + public void pack(OutputStream out) throws IOException, Pack200Exception { + out.write(encodeScalar(magic, Codec.BYTE1)); + out.write(encodeScalar(archive_minver, Codec.UNSIGNED5)); + out.write(encodeScalar(archive_majver, Codec.UNSIGNED5)); + calculateArchiveOptions(); + out.write(encodeScalar(archive_options, Codec.UNSIGNED5)); + writeArchiveFileCounts(out); + writeArchiveSpecialCounts(out); + writeCpCounts(out); + writeClassCounts(out); + if (band_headers.size()> 0) { + out.write(encodeScalar(band_headers.toArray(), BHSDCodec.BYTE1)); + } + } + + private void calculateArchiveOptions() { + if (attribute_definition_count > 0 || band_headers.size() > 0) { + archive_options |= 1; + } + if (cp_Int_count > 0 || cp_Float_count > 0 || cp_Long_count > 0 + || cp_Double_count > 0) { + archive_options |= (1 << 1); + } + if (have_all_code_flags) { + archive_options |= (1 << 2); + } + if (file_count > 0) { + archive_options |= (1 << 4); + } + if (deflate_hint) { + archive_options |= (1 << 5); + } + if (have_file_modtime) { + archive_options |= (1 << 6); + } + if (have_file_options) { + archive_options |= (1 << 7); + } + if (have_file_size_hi) { + archive_options |= (1 << 8); + } + if (have_class_flags_hi) { + archive_options |= (1 << 9); + } + if (have_field_flags_hi) { + archive_options |= (1 << 10); + } + if (have_method_flags_hi) { + archive_options |= (1 << 11); + } + if (have_code_flags_hi) { + archive_options |= (1 << 12); + } + } + + public void setCp_Utf8_count(int count) { + cp_Utf8_count = count; + } + + public void setCp_Int_count(int count) { + cp_Int_count = count; + } + + public void setCp_Float_count(int count) { + cp_Float_count = count; + } + + public void setCp_Long_count(int count) { + cp_Long_count = count; + } + + public void setCp_Double_count(int count) { + cp_Double_count = count; + } + + public void setCp_String_count(int count) { + cp_String_count = count; + } + + public void setCp_Class_count(int count) { + cp_Class_count = count; + } + + public void setCp_Signature_count(int count) { + cp_Signature_count = count; + } + + public void setCp_Descr_count(int count) { + cp_Descr_count = count; + } + + public void setCp_Field_count(int count) { + cp_Field_count = count; + } + + public void setCp_Method_count(int count) { + cp_Method_count = count; + } + + public void setCp_Imethod_count(int count) { + cp_Imethod_count = count; + } + + public void setAttribute_definition_count(int attribute_definition_count) { + this.attribute_definition_count = attribute_definition_count; + } + + public void setHave_all_code_flags(boolean have_all_code_flags) { + this.have_all_code_flags = have_all_code_flags; + } + + public void setArchive_size_hi(int archive_size_hi) { + this.archive_size_hi = archive_size_hi; + } + + public void setArchive_size_lo(int archive_size_lo) { + this.archive_size_lo = archive_size_lo; + } + + public void setArchive_next_count(int archive_next_count) { + this.archive_next_count = archive_next_count; + } + + public void setArchive_modtime(int archive_modtime) { + this.archive_modtime = archive_modtime; + } + + public int getArchive_modtime() { + return archive_modtime; + } + + public void setFile_count(int file_count) { + this.file_count = file_count; + } + + public void setDeflate_hint(boolean deflate_hint) { + this.deflate_hint = deflate_hint; + } + + public void setHave_file_modtime(boolean have_file_modtime) { + this.have_file_modtime = have_file_modtime; + } + + public void setHave_file_options(boolean have_file_options) { + this.have_file_options = have_file_options; + } + + public void setHave_file_size_hi(boolean have_file_size_hi) { + this.have_file_size_hi = have_file_size_hi; + } + + public void setHave_class_flags_hi(boolean have_class_flags_hi) { + this.have_class_flags_hi = have_class_flags_hi; + } + + public void setHave_field_flags_hi(boolean have_field_flags_hi) { + this.have_field_flags_hi = have_field_flags_hi; + } + + public void setHave_method_flags_hi(boolean have_method_flags_hi) { + this.have_method_flags_hi = have_method_flags_hi; + } + + public void setHave_code_flags_hi(boolean have_code_flags_hi) { + this.have_code_flags_hi = have_code_flags_hi; + } + + public boolean have_class_flags_hi() { + return have_class_flags_hi; + } + + public boolean have_field_flags_hi() { + return have_field_flags_hi; + } + + public boolean have_method_flags_hi() { + return have_method_flags_hi; + } + + public boolean have_code_flags_hi() { + return have_code_flags_hi; + } + + public void setIc_count(int ic_count) { + this.ic_count = ic_count; + } + + public void setClass_count(int class_count) { + this.class_count = class_count; + } + + private void writeCpCounts(OutputStream out) throws IOException, + Pack200Exception { + out.write(encodeScalar(cp_Utf8_count, Codec.UNSIGNED5)); + if ((archive_options & (1 << 1)) != 0) { // have_cp_numbers + out.write(encodeScalar(cp_Int_count, Codec.UNSIGNED5)); + out.write(encodeScalar(cp_Float_count, Codec.UNSIGNED5)); + out.write(encodeScalar(cp_Long_count, Codec.UNSIGNED5)); + out.write(encodeScalar(cp_Double_count, Codec.UNSIGNED5)); + } + out.write(encodeScalar(cp_String_count, Codec.UNSIGNED5)); + out.write(encodeScalar(cp_Class_count, Codec.UNSIGNED5)); + out.write(encodeScalar(cp_Signature_count, Codec.UNSIGNED5)); + out.write(encodeScalar(cp_Descr_count, Codec.UNSIGNED5)); + out.write(encodeScalar(cp_Field_count, Codec.UNSIGNED5)); + out.write(encodeScalar(cp_Method_count, Codec.UNSIGNED5)); + out.write(encodeScalar(cp_Imethod_count, Codec.UNSIGNED5)); + } + + private void writeClassCounts(OutputStream out) throws IOException, + Pack200Exception { + int default_class_minver = 0; + int default_class_majver = majverCounter.getMostCommon(); + out.write(encodeScalar(ic_count, Codec.UNSIGNED5)); + out.write(encodeScalar(default_class_minver, Codec.UNSIGNED5)); + out.write(encodeScalar(default_class_majver, Codec.UNSIGNED5)); + out.write(encodeScalar(class_count, Codec.UNSIGNED5)); + } + + private void writeArchiveSpecialCounts(OutputStream out) + throws IOException, Pack200Exception { + if ((archive_options & 1) > 0) { // have_special_formats + out.write(encodeScalar(band_headers.size(), Codec.UNSIGNED5)); + out.write(encodeScalar(attribute_definition_count, + Codec.UNSIGNED5)); + } + } + + private void writeArchiveFileCounts(OutputStream out) throws IOException, + Pack200Exception { + if ((archive_options & (1 << 4)) > 0) { // have_file_headers + out.write(encodeScalar(archive_size_hi, Codec.UNSIGNED5)); + out.write(encodeScalar(archive_size_lo, Codec.UNSIGNED5)); + out.write(encodeScalar(archive_next_count, Codec.UNSIGNED5)); + out.write(encodeScalar(archive_modtime, Codec.UNSIGNED5)); + out.write(encodeScalar(file_count, Codec.UNSIGNED5)); + } + } + + public void addMajorVersion(int major) { + majverCounter.add(major); + } + + /** + * Counter for major/minor class file numbers so we can work out the default + */ + private class Counter { + + private final int[] objs = new int[8]; + private final int[] counts = new int[8]; + private int length; + + public void add(int obj) { + boolean found = false; + for (int i = 0; i < length; i++) { + if (objs[i] == obj) { + counts[i]++; + found = true; + } + } + if (!found) { + objs[length] = obj; + counts[length] = 1; + length++; + if (length > objs.length - 1) { + Object[] newArray = new Object[objs.length + 8]; + System.arraycopy(objs, 0, newArray, 0, length); + } + } + } + + public int getMostCommon() { + int returnIndex = 0; + for (int i = 0; i < length; i++) { + if (counts[i] > counts[returnIndex]) { + returnIndex = i; + } + } + return objs[returnIndex]; + } + } + + public int getDefaultMinorVersion() { + return 0; + } + + public int getDefaultMajorVersion() { + return majverCounter.getMostCommon(); + } + + public boolean have_file_size_hi() { + return have_file_size_hi; + } + + public boolean have_file_modtime() { + return have_file_modtime; + } + + public boolean have_file_options() { + return have_file_options; + } + + public boolean have_all_code_flags() { + return have_all_code_flags; + } + + public void appendBandCodingSpecifier(int specifier) { + band_headers.add(specifier); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/Archive.java b/src/main/java/org/apache/harmony/unpack200/Archive.java new file mode 100644 index 0000000..a9a4933 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/Archive.java @@ -0,0 +1,245 @@ +/* + * 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.unpack200; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.zip.GZIPInputStream; + +import org.apache.harmony.pack200.Pack200Exception; + +/** + * Archive is the main entry point to unpack200. An archive is constructed with + * either two file names, a pack file and an output file name or an input stream + * and an output streams. Then unpack() is called, to unpack the + * pack200 archive. + */ +public class Archive { + + private InputStream inputStream; + + private final JarOutputStream outputStream; + + private boolean removePackFile; + + private int logLevel = Segment.LOG_LEVEL_STANDARD; + + private FileOutputStream logFile; + + private boolean overrideDeflateHint; + + private boolean deflateHint; + + private String inputFileName; + + private String outputFileName; + + /** + * Creates an Archive with the given input and output file names. + * + * @param inputFile + * @param outputFile + * @throws FileNotFoundException + * if the input file does not exist + * @throws IOException + */ + public Archive(String inputFile, String outputFile) + throws FileNotFoundException, IOException { + this.inputFileName = inputFile; + this.outputFileName = outputFile; + inputStream = new FileInputStream(inputFile); + outputStream = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(outputFile))); + } + + /** + * Creates an Archive with streams for the input and output files. Note: If + * you use this method then calling {@link #setRemovePackFile(boolean)} will + * have no effect. + * + * @param inputStream + * @param outputStream + * @throws IOException + */ + public Archive(InputStream inputStream, JarOutputStream outputStream) + throws IOException { + this.inputStream = inputStream; + this.outputStream = outputStream; + } + + /** + * Unpacks the Archive from the input file to the output file + * + * @throws Pack200Exception + * @throws IOException + */ + public void unpack() throws Pack200Exception, IOException { + outputStream.setComment("PACK200"); + try { + if (!inputStream.markSupported()) { + inputStream = new BufferedInputStream(inputStream); + if (!inputStream.markSupported()) + throw new IllegalStateException(); + } + inputStream.mark(2); + if (((inputStream.read() & 0xFF) | (inputStream.read() & 0xFF) << 8) == GZIPInputStream.GZIP_MAGIC) { + inputStream.reset(); + inputStream = new BufferedInputStream(new GZIPInputStream( + inputStream)); + } else { + inputStream.reset(); + } + inputStream.mark(4); + int[] magic = { 0xCA, 0xFE, 0xD0, 0x0D }; // Magic word for + // pack200 + int word[] = new int[4]; + for (int i = 0; i < word.length; i++) { + word[i] = inputStream.read(); + } + boolean compressedWithE0 = false; + for (int m = 0; m < magic.length; m++) { + if (word[m] != magic[m]) { + compressedWithE0 = true; + } + } + inputStream.reset(); + if (compressedWithE0) { // The original Jar was not packed, so just + // copy it across + JarInputStream jarInputStream = new JarInputStream(inputStream); + JarEntry jarEntry; + while ((jarEntry = jarInputStream.getNextJarEntry()) != null) { + outputStream.putNextEntry(jarEntry); + byte[] bytes = new byte[16384]; + int bytesRead = jarInputStream.read(bytes); + while (bytesRead != -1) { + outputStream.write(bytes, 0, bytesRead); + bytesRead = jarInputStream.read(bytes); + } + outputStream.closeEntry(); + } + } else { + int i = 0; + while (available(inputStream)) { + i++; + Segment segment = new Segment(); + segment.setLogLevel(logLevel); + segment + .setLogStream(logFile != null ? (OutputStream) logFile + : (OutputStream) System.out); + segment.setPreRead(false); + + if (i == 1) { + segment.log(Segment.LOG_LEVEL_VERBOSE, + "Unpacking from " + inputFileName + " to " + + outputFileName); + } + segment.log(Segment.LOG_LEVEL_VERBOSE, "Reading segment " + + i); + if (overrideDeflateHint) { + segment.overrideDeflateHint(deflateHint); + } + segment.unpack(inputStream, outputStream); + outputStream.flush(); + + if (inputStream instanceof FileInputStream) { + inputFileName = ((FileInputStream) inputStream).getFD() + .toString(); + } + } + } + } finally { + try { + inputStream.close(); + } catch (Exception e) { + } + try { + outputStream.close(); + } catch (Exception e) { + } + if (logFile != null) { + try { + logFile.close(); + } catch (Exception e) { + } + } + } + if (removePackFile) { + File file = new File(inputFileName); + boolean deleted = file.delete(); + if (!deleted) { + throw new Pack200Exception("Failed to delete the input file."); + } + } + } + + private boolean available(InputStream inputStream) throws IOException { + inputStream.mark(1); + int check = inputStream.read(); + inputStream.reset(); + return check != -1; + } + + /** + * If removePackFile is set to true, the input file is deleted after + * unpacking + * + * @param removePackFile + */ + public void setRemovePackFile(boolean removePackFile) { + this.removePackFile = removePackFile; + } + + public void setVerbose(boolean verbose) { + if (verbose) { + logLevel = Segment.LOG_LEVEL_VERBOSE; + } else if (logLevel == Segment.LOG_LEVEL_VERBOSE) { + logLevel = Segment.LOG_LEVEL_STANDARD; + } + } + + public void setQuiet(boolean quiet) { + if (quiet) { + logLevel = Segment.LOG_LEVEL_QUIET; + } else if (logLevel == Segment.LOG_LEVEL_QUIET) { + logLevel = Segment.LOG_LEVEL_QUIET; + } + } + + public void setLogFile(String logFileName) throws FileNotFoundException { + this.logFile = new FileOutputStream(logFileName); + } + + public void setLogFile(String logFileName, boolean append) + throws FileNotFoundException { + logFile = new FileOutputStream(logFileName, true); + } + + public void setDeflateHint(boolean deflateHint) { + overrideDeflateHint = true; + this.deflateHint = deflateHint; + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/AttrDefinitionBands.java b/src/main/java/org/apache/harmony/unpack200/AttrDefinitionBands.java new file mode 100644 index 0000000..709cd24 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/AttrDefinitionBands.java @@ -0,0 +1,121 @@ +/* + * 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.unpack200; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.bytecode.AnnotationDefaultAttribute; +import org.apache.harmony.unpack200.bytecode.CodeAttribute; +import org.apache.harmony.unpack200.bytecode.ConstantValueAttribute; +import org.apache.harmony.unpack200.bytecode.DeprecatedAttribute; +import org.apache.harmony.unpack200.bytecode.EnclosingMethodAttribute; +import org.apache.harmony.unpack200.bytecode.ExceptionsAttribute; +import org.apache.harmony.unpack200.bytecode.InnerClassesAttribute; +import org.apache.harmony.unpack200.bytecode.LineNumberTableAttribute; +import org.apache.harmony.unpack200.bytecode.LocalVariableTableAttribute; +import org.apache.harmony.unpack200.bytecode.LocalVariableTypeTableAttribute; +import org.apache.harmony.unpack200.bytecode.SignatureAttribute; +import org.apache.harmony.unpack200.bytecode.SourceFileAttribute; + +/** + * Attribute definition bands are the set of bands used to define extra + * attributes transmitted in the archive. + */ +public class AttrDefinitionBands extends BandSet { + + private int[] attributeDefinitionHeader; + + private String[] attributeDefinitionLayout; + + private String[] attributeDefinitionName; + + private AttributeLayoutMap attributeDefinitionMap; + + private final String[] cpUTF8; + + public AttrDefinitionBands(Segment segment) { + super(segment); + this.cpUTF8 = segment.getCpBands().getCpUTF8(); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.BandSet#unpack(java.io.InputStream) + */ + public void read(InputStream in) throws IOException, Pack200Exception { + int attributeDefinitionCount = header.getAttributeDefinitionCount(); + attributeDefinitionHeader = decodeBandInt("attr_definition_headers", + in, Codec.BYTE1, attributeDefinitionCount); + attributeDefinitionName = parseReferences("attr_definition_name", in, + Codec.UNSIGNED5, attributeDefinitionCount, cpUTF8); + attributeDefinitionLayout = parseReferences("attr_definition_layout", + in, Codec.UNSIGNED5, attributeDefinitionCount, cpUTF8); + + attributeDefinitionMap = new AttributeLayoutMap(); + + int overflowIndex = 32; + if (segment.getSegmentHeader().getOptions().hasClassFlagsHi()) { + overflowIndex = 63; + } + for (int i = 0; i < attributeDefinitionCount; i++) { + int context = attributeDefinitionHeader[i] & 0x03; + int index = (attributeDefinitionHeader[i] >> 2) - 1; + if (index == -1) { + index = overflowIndex++; + } + AttributeLayout layout = new AttributeLayout( + attributeDefinitionName[i], context, + attributeDefinitionLayout[i], index, false); + NewAttributeBands newBands = new NewAttributeBands(segment, layout); + attributeDefinitionMap.add(layout, newBands); + } + attributeDefinitionMap.checkMap(); + setupDefaultAttributeNames(); + } + + public void unpack() throws Pack200Exception, IOException { + + } + + private void setupDefaultAttributeNames() { + AnnotationDefaultAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("AnnotationDefault")); //$NON-NLS-1$ + CodeAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("Code")); //$NON-NLS-1$ + ConstantValueAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("ConstantValue")); //$NON-NLS-1$ + DeprecatedAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("Deprecated")); //$NON-NLS-1$ + EnclosingMethodAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("EnclosingMethod")); //$NON-NLS-1$ + ExceptionsAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("Exceptions")); //$NON-NLS-1$ + InnerClassesAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("InnerClasses")); //$NON-NLS-1$ + LineNumberTableAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("LineNumberTable")); //$NON-NLS-1$ + LocalVariableTableAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("LocalVariableTable")); //$NON-NLS-1$ + LocalVariableTypeTableAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("LocalVariableTypeTable")); //$NON-NLS-1$ + SignatureAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("Signature")); //$NON-NLS-1$ + SourceFileAttribute.setAttributeName(segment.getCpBands().cpUTF8Value("SourceFile")); //$NON-NLS-1$ + MetadataBandGroup.setRvaAttributeName(segment.getCpBands().cpUTF8Value("RuntimeVisibleAnnotations")); + MetadataBandGroup.setRiaAttributeName(segment.getCpBands().cpUTF8Value("RuntimeInvisibleAnnotations")); + MetadataBandGroup.setRvpaAttributeName(segment.getCpBands().cpUTF8Value("RuntimeVisibleParameterAnnotations")); + MetadataBandGroup.setRipaAttributeName(segment.getCpBands().cpUTF8Value("RuntimeInvisibleParameterAnnotations")); + } + + public AttributeLayoutMap getAttributeDefinitionMap() { + return attributeDefinitionMap; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/AttributeLayout.java b/src/main/java/org/apache/harmony/unpack200/AttributeLayout.java new file mode 100644 index 0000000..c263963 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/AttributeLayout.java @@ -0,0 +1,286 @@ +/* + * 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.unpack200; + +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; + +/** + * AttributeLayout defines a layout that describes how an attribute will be + * transmitted. + */ +public class AttributeLayout implements IMatcher { + + public static final String ACC_ABSTRACT = "ACC_ABSTRACT"; //$NON-NLS-1$ + public static final String ACC_ANNOTATION = "ACC_ANNOTATION"; //$NON-NLS-1$ + public static final String ACC_ENUM = "ACC_ENUM"; //$NON-NLS-1$ + public static final String ACC_FINAL = "ACC_FINAL"; //$NON-NLS-1$ + public static final String ACC_INTERFACE = "ACC_INTERFACE"; //$NON-NLS-1$ + public static final String ACC_NATIVE = "ACC_NATIVE"; //$NON-NLS-1$ + public static final String ACC_PRIVATE = "ACC_PRIVATE"; //$NON-NLS-1$ + public static final String ACC_PROTECTED = "ACC_PROTECTED"; //$NON-NLS-1$ + public static final String ACC_PUBLIC = "ACC_PUBLIC"; //$NON-NLS-1$ + public static final String ACC_STATIC = "ACC_STATIC"; //$NON-NLS-1$ + public static final String ACC_STRICT = "ACC_STRICT"; //$NON-NLS-1$ + public static final String ACC_SYNCHRONIZED = "ACC_SYNCHRONIZED"; //$NON-NLS-1$ + public static final String ACC_SYNTHETIC = "ACC_SYNTHETIC"; //$NON-NLS-1$ + public static final String ACC_TRANSIENT = "ACC_TRANSIENT"; //$NON-NLS-1$ + public static final String ACC_VOLATILE = "ACC_VOLATILE"; //$NON-NLS-1$ + public static final String ATTRIBUTE_ANNOTATION_DEFAULT = "AnnotationDefault"; //$NON-NLS-1$ + public static final String ATTRIBUTE_CLASS_FILE_VERSION = "class-file version"; //$NON-NLS-1$ + public static final String ATTRIBUTE_CODE = "Code"; //$NON-NLS-1$ + public static final String ATTRIBUTE_CONSTANT_VALUE = "ConstantValue"; //$NON-NLS-1$ + public static final String ATTRIBUTE_DEPRECATED = "Deprecated"; //$NON-NLS-1$ + public static final String ATTRIBUTE_ENCLOSING_METHOD = "EnclosingMethod"; //$NON-NLS-1$ + public static final String ATTRIBUTE_EXCEPTIONS = "Exceptions"; //$NON-NLS-1$ + public static final String ATTRIBUTE_INNER_CLASSES = "InnerClasses"; //$NON-NLS-1$ + public static final String ATTRIBUTE_LINE_NUMBER_TABLE = "LineNumberTable"; //$NON-NLS-1$ + public static final String ATTRIBUTE_LOCAL_VARIABLE_TABLE = "LocalVariableTable"; //$NON-NLS-1$ + public static final String ATTRIBUTE_LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable"; //$NON-NLS-1$ + public static final String ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations"; //$NON-NLS-1$ + public static final String ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS = "RuntimeInvisibleParameterAnnotations"; //$NON-NLS-1$ + public static final String ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations"; //$NON-NLS-1$ + public static final String ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = "RuntimeVisibleParameterAnnotations"; //$NON-NLS-1$ + public static final String ATTRIBUTE_SIGNATURE = "Signature"; //$NON-NLS-1$ + public static final String ATTRIBUTE_SOURCE_FILE = "SourceFile"; //$NON-NLS-1$ + public static final int CONTEXT_CLASS = 0; + public static final int CONTEXT_CODE = 3; + public static final int CONTEXT_FIELD = 1; + public static final int CONTEXT_METHOD = 2; + public static final String[] contextNames = { "Class", "Field", "Method", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + "Code", }; //$NON-NLS-1$ + + private static ClassFileEntry getValue(String layout, long value, + SegmentConstantPool pool) throws Pack200Exception { + if (layout.startsWith("R")) { //$NON-NLS-1$ + // references + if (layout.indexOf('N') != -1) + value--; + if (layout.startsWith("RU")) { //$NON-NLS-1$ + return pool.getValue(SegmentConstantPool.UTF_8, value); + } else if (layout.startsWith("RS")) { //$NON-NLS-1$ + return pool.getValue(SegmentConstantPool.SIGNATURE, value); + } + } else if (layout.startsWith("K")) { //$NON-NLS-1$ + 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); + } + + private final int context; + + private final int index; + + private final String layout; + + private long mask; + + private final String name; + private final boolean isDefault; + private int backwardsCallCount; + + /** + * Construct a default AttributeLayout (equivalent to + * new AttributeLayout(name, context, layout, index, true);) + * + * @param name + * @param context + * @param layout + * @param index + * @throws Pack200Exception + */ + public AttributeLayout(String name, int context, String layout, int index) + throws Pack200Exception { + this(name, context, layout, index, true); + } + + public AttributeLayout(String name, int context, String layout, int index, + boolean isDefault) throws Pack200Exception { + super(); + this.index = index; + this.context = context; + if (index >= 0) { + this.mask = 1L << index; + } else { + this.mask = 0; + } + if (context != CONTEXT_CLASS && context != CONTEXT_CODE + && context != CONTEXT_FIELD && context != CONTEXT_METHOD) + throw new Pack200Exception("Attribute context out of range: " + + context); + if (layout == null) // || layout.length() == 0) + throw new Pack200Exception("Cannot have a null layout"); + if (name == null || name.length() == 0) + throw new Pack200Exception("Cannot have an unnamed layout"); + this.name = name; + this.layout = layout; + this.isDefault = isDefault; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final AttributeLayout other = (AttributeLayout) obj; + if (layout == null) { + if (other.layout != null) + return false; + } else if (!layout.equals(other.layout)) + return false; + if (index != other.index) + return false; + if (context != other.context) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + + public Codec getCodec() { + if (layout.indexOf('O') >= 0) { + return Codec.BRANCH5; + } else if (layout.indexOf('P') >= 0) { + return Codec.BCI5; + } else if (layout.indexOf('S') >= 0 && layout.indexOf("KS") < 0 //$NON-NLS-1$ + && layout.indexOf("RS") < 0) { //$NON-NLS-1$ + return Codec.SIGNED5; + } else if (layout.indexOf('B') >= 0) { + return Codec.BYTE1; + } else { + return Codec.UNSIGNED5; + } + } + + public String getLayout() { + return layout; + } + + public ClassFileEntry getValue(long value, SegmentConstantPool pool) + throws Pack200Exception { + return getValue(layout, value, pool); + } + + public ClassFileEntry getValue(long value, String type, SegmentConstantPool pool) + 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")) { //$NON-NLS-1$ + if (type.equals("Ljava/lang/String;")) { //$NON-NLS-1$ + ClassFileEntry value2 = getValue("KS", value, pool); //$NON-NLS-1$ + return value2; + } else { + return getValue("K" + type + layout.substring(2), value, //$NON-NLS-1$ + pool); + } + } else { + return getValue(layout, value, pool); + } + } + + public int hashCode() { + int PRIME = 31; + int r = 1; + if (name != null) { + r = r * PRIME + name.hashCode(); + } + if (layout != null) { + r = r * PRIME + layout.hashCode(); + } + r = r * PRIME + index; + r = r * PRIME + context; + return r; + } + + public boolean isClass() { + return context == CONTEXT_CLASS; + } + + public boolean isCode() { + return context == CONTEXT_CODE; + } + + public boolean isField() { + return context == CONTEXT_FIELD; + } + + public boolean isMethod() { + return context == CONTEXT_METHOD; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.IMatches#matches(long) + */ + public boolean matches(long value) { + return (value & mask) != 0; + } + + public String toString() { + return contextNames[context] + ": " + name; + } + + public int getContext() { + return context; + } + + public int getIndex() { + return index; + } + + public String getName() { + return name; + } + + public int numBackwardsCallables() { + if (layout == "*") { + return 1; + } else { + return backwardsCallCount; + } + } + + public boolean isDefaultLayout() { + return isDefault; + } + + public void setBackwardsCallCount(int backwardsCallCount) { + this.backwardsCallCount = backwardsCallCount; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/AttributeLayoutMap.java b/src/main/java/org/apache/harmony/unpack200/AttributeLayoutMap.java new file mode 100644 index 0000000..bc8c839 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/AttributeLayoutMap.java @@ -0,0 +1,284 @@ +/* + * 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.unpack200; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.harmony.pack200.Pack200Exception; + +/** + * Stores a mapping from attribute names to their corresponding layout types. + * Note that names of attribute layouts and their formats are not + * internationalized, and should not be translated. + */ +public class AttributeLayoutMap { + + // Create all the default AttributeLayouts here + private static AttributeLayout[] getDefaultAttributeLayouts() + throws Pack200Exception { + return new AttributeLayout[] { + new AttributeLayout(AttributeLayout.ACC_PUBLIC, + AttributeLayout.CONTEXT_CLASS, "", 0), + new AttributeLayout(AttributeLayout.ACC_PUBLIC, + AttributeLayout.CONTEXT_FIELD, "", 0), + new AttributeLayout(AttributeLayout.ACC_PUBLIC, + AttributeLayout.CONTEXT_METHOD, "", 0), + new AttributeLayout(AttributeLayout.ACC_PRIVATE, + AttributeLayout.CONTEXT_CLASS, "", 1), + new AttributeLayout(AttributeLayout.ACC_PRIVATE, + AttributeLayout.CONTEXT_FIELD, "", 1), + new AttributeLayout(AttributeLayout.ACC_PRIVATE, + AttributeLayout.CONTEXT_METHOD, "", 1), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_LINE_NUMBER_TABLE, + AttributeLayout.CONTEXT_CODE, "NH[PHH]", 1), + + new AttributeLayout(AttributeLayout.ACC_PROTECTED, + AttributeLayout.CONTEXT_CLASS, "", 2), + new AttributeLayout(AttributeLayout.ACC_PROTECTED, + AttributeLayout.CONTEXT_FIELD, "", 2), + new AttributeLayout(AttributeLayout.ACC_PROTECTED, + AttributeLayout.CONTEXT_METHOD, "", 2), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_LOCAL_VARIABLE_TABLE, + AttributeLayout.CONTEXT_CODE, "NH[PHOHRUHRSHH]", 2), + + new AttributeLayout(AttributeLayout.ACC_STATIC, + AttributeLayout.CONTEXT_CLASS, "", 3), + new AttributeLayout(AttributeLayout.ACC_STATIC, + AttributeLayout.CONTEXT_FIELD, "", 3), + new AttributeLayout(AttributeLayout.ACC_STATIC, + AttributeLayout.CONTEXT_METHOD, "", 3), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_LOCAL_VARIABLE_TYPE_TABLE, + AttributeLayout.CONTEXT_CODE, "NH[PHOHRUHRSHH]", 3), + + new AttributeLayout(AttributeLayout.ACC_FINAL, + AttributeLayout.CONTEXT_CLASS, "", 4), + new AttributeLayout(AttributeLayout.ACC_FINAL, + AttributeLayout.CONTEXT_FIELD, "", 4), + new AttributeLayout(AttributeLayout.ACC_FINAL, + AttributeLayout.CONTEXT_METHOD, "", 4), + new AttributeLayout(AttributeLayout.ACC_SYNCHRONIZED, + AttributeLayout.CONTEXT_CLASS, "", 5), + new AttributeLayout(AttributeLayout.ACC_SYNCHRONIZED, + AttributeLayout.CONTEXT_FIELD, "", 5), + new AttributeLayout(AttributeLayout.ACC_SYNCHRONIZED, + AttributeLayout.CONTEXT_METHOD, "", 5), + new AttributeLayout(AttributeLayout.ACC_VOLATILE, + AttributeLayout.CONTEXT_CLASS, "", 6), + new AttributeLayout(AttributeLayout.ACC_VOLATILE, + AttributeLayout.CONTEXT_FIELD, "", 6), + new AttributeLayout(AttributeLayout.ACC_VOLATILE, + AttributeLayout.CONTEXT_METHOD, "", 6), + new AttributeLayout(AttributeLayout.ACC_TRANSIENT, + AttributeLayout.CONTEXT_CLASS, "", 7), + new AttributeLayout(AttributeLayout.ACC_TRANSIENT, + AttributeLayout.CONTEXT_FIELD, "", 7), + new AttributeLayout(AttributeLayout.ACC_TRANSIENT, + AttributeLayout.CONTEXT_METHOD, "", 7), + new AttributeLayout(AttributeLayout.ACC_NATIVE, + AttributeLayout.CONTEXT_CLASS, "", 8), + new AttributeLayout(AttributeLayout.ACC_NATIVE, + AttributeLayout.CONTEXT_FIELD, "", 8), + new AttributeLayout(AttributeLayout.ACC_NATIVE, + AttributeLayout.CONTEXT_METHOD, "", 8), + new AttributeLayout(AttributeLayout.ACC_INTERFACE, + AttributeLayout.CONTEXT_CLASS, "", 9), + new AttributeLayout(AttributeLayout.ACC_INTERFACE, + AttributeLayout.CONTEXT_FIELD, "", 9), + new AttributeLayout(AttributeLayout.ACC_INTERFACE, + AttributeLayout.CONTEXT_METHOD, "", 9), + new AttributeLayout(AttributeLayout.ACC_ABSTRACT, + AttributeLayout.CONTEXT_CLASS, "", 10), + new AttributeLayout(AttributeLayout.ACC_ABSTRACT, + AttributeLayout.CONTEXT_FIELD, "", 10), + new AttributeLayout(AttributeLayout.ACC_ABSTRACT, + AttributeLayout.CONTEXT_METHOD, "", 10), + new AttributeLayout(AttributeLayout.ACC_STRICT, + AttributeLayout.CONTEXT_CLASS, "", 11), + new AttributeLayout(AttributeLayout.ACC_STRICT, + AttributeLayout.CONTEXT_FIELD, "", 11), + new AttributeLayout(AttributeLayout.ACC_STRICT, + AttributeLayout.CONTEXT_METHOD, "", 11), + new AttributeLayout(AttributeLayout.ACC_SYNTHETIC, + AttributeLayout.CONTEXT_CLASS, "", 12), + new AttributeLayout(AttributeLayout.ACC_SYNTHETIC, + AttributeLayout.CONTEXT_FIELD, "", 12), + new AttributeLayout(AttributeLayout.ACC_SYNTHETIC, + AttributeLayout.CONTEXT_METHOD, "", 12), + new AttributeLayout(AttributeLayout.ACC_ANNOTATION, + AttributeLayout.CONTEXT_CLASS, "", 13), + new AttributeLayout(AttributeLayout.ACC_ANNOTATION, + AttributeLayout.CONTEXT_FIELD, "", 13), + new AttributeLayout(AttributeLayout.ACC_ANNOTATION, + AttributeLayout.CONTEXT_METHOD, "", 13), + new AttributeLayout(AttributeLayout.ACC_ENUM, + AttributeLayout.CONTEXT_CLASS, "", 14), + new AttributeLayout(AttributeLayout.ACC_ENUM, + AttributeLayout.CONTEXT_FIELD, "", 14), + new AttributeLayout(AttributeLayout.ACC_ENUM, + AttributeLayout.CONTEXT_METHOD, "", 14), + new AttributeLayout(AttributeLayout.ATTRIBUTE_SOURCE_FILE, + AttributeLayout.CONTEXT_CLASS, "RUNH", 17), + new AttributeLayout(AttributeLayout.ATTRIBUTE_CONSTANT_VALUE, + AttributeLayout.CONTEXT_FIELD, "KQH", 17), + new AttributeLayout(AttributeLayout.ATTRIBUTE_CODE, + AttributeLayout.CONTEXT_METHOD, "", 17), + new AttributeLayout(AttributeLayout.ATTRIBUTE_ENCLOSING_METHOD, + AttributeLayout.CONTEXT_CLASS, "RCHRDNH", 18), + new AttributeLayout(AttributeLayout.ATTRIBUTE_EXCEPTIONS, + AttributeLayout.CONTEXT_METHOD, "NH[RCH]", 18), + new AttributeLayout(AttributeLayout.ATTRIBUTE_SIGNATURE, + AttributeLayout.CONTEXT_CLASS, "RSH", 19), + new AttributeLayout(AttributeLayout.ATTRIBUTE_SIGNATURE, + AttributeLayout.CONTEXT_FIELD, "RSH", 19), + new AttributeLayout(AttributeLayout.ATTRIBUTE_SIGNATURE, + AttributeLayout.CONTEXT_METHOD, "RSH", 19), + new AttributeLayout(AttributeLayout.ATTRIBUTE_DEPRECATED, + AttributeLayout.CONTEXT_CLASS, "", 20), + new AttributeLayout(AttributeLayout.ATTRIBUTE_DEPRECATED, + AttributeLayout.CONTEXT_FIELD, "", 20), + new AttributeLayout(AttributeLayout.ATTRIBUTE_DEPRECATED, + AttributeLayout.CONTEXT_METHOD, "", 20), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_CLASS, "*", 21), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_FIELD, "*", 21), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_METHOD, "*", 21), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_CLASS, "*", 22), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_FIELD, "*", 22), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_METHOD, "*", 22), + new AttributeLayout(AttributeLayout.ATTRIBUTE_INNER_CLASSES, + AttributeLayout.CONTEXT_CLASS, "", 23), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, + AttributeLayout.CONTEXT_METHOD, "*", 23), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_CLASS_FILE_VERSION, + AttributeLayout.CONTEXT_CLASS, "", 24), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, + AttributeLayout.CONTEXT_METHOD, "*", 24), + new AttributeLayout( + AttributeLayout.ATTRIBUTE_ANNOTATION_DEFAULT, + AttributeLayout.CONTEXT_METHOD, "*", 25) }; + } + + private final Map classLayouts = new HashMap(); + private final Map fieldLayouts = new HashMap(); + private final Map methodLayouts = new HashMap(); + private final Map codeLayouts = new HashMap(); + + // The order of the maps in this array should not be changed as their + // indices correspond to + // the value of their context constants (AttributeLayout.CONTEXT_CLASS etc.) + private final Map[] layouts = new Map[] { classLayouts, fieldLayouts, + methodLayouts, codeLayouts }; + + private final Map layoutsToBands = new HashMap(); + + public AttributeLayoutMap() throws Pack200Exception { + AttributeLayout[] defaultAttributeLayouts = getDefaultAttributeLayouts(); + for (int i = 0; i < defaultAttributeLayouts.length; i++) { + add(defaultAttributeLayouts[i]); + } + } + + public void add(AttributeLayout layout) { + layouts[layout.getContext()] + .put(new Integer(layout.getIndex()), layout); + } + + public void add(AttributeLayout layout, NewAttributeBands newBands) { + add(layout); + layoutsToBands.put(layout, newBands); + } + + public AttributeLayout getAttributeLayout(String name, int context) + throws Pack200Exception { + Map map = layouts[context]; + for (Iterator iter = map.values().iterator(); iter.hasNext();) { + AttributeLayout layout = (AttributeLayout) iter.next(); + if (layout.getName().equals(name)) { + return layout; + } + } + return null; + } + + public AttributeLayout getAttributeLayout(int index, int context) + throws Pack200Exception { + Map map = layouts[context]; + return (AttributeLayout) map.get(new Integer(index)); + } + + /** + * The map should not contain the same layout and name combination more than + * once for each context. + * + * @throws Pack200Exception + * + */ + public void checkMap() throws Pack200Exception { + for (int i = 0; i < layouts.length; i++) { + Map map = layouts[i]; + Collection c = map.values(); + if (!(c instanceof List)) { + c = new ArrayList(c); + } + List l = (List) c; + for (int j = 0; j < l.size(); j++) { + AttributeLayout layout1 = (AttributeLayout) l.get(j); + for (int j2 = j + 1; j2 < l.size(); j2++) { + AttributeLayout layout2 = (AttributeLayout) l.get(j2); + if (layout1.getName().equals(layout2.getName()) + && layout1.getLayout().equals(layout2.getLayout())) { + throw new Pack200Exception( + "Same layout/name combination: " + + layout1.getLayout() + + "/" + + layout1.getName() + + " exists twice for context: " + + AttributeLayout.contextNames[layout1 + .getContext()]); + } + } + } + } + } + + public NewAttributeBands getAttributeBands(AttributeLayout layout) { + return (NewAttributeBands) layoutsToBands.get(layout); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/BandSet.java b/src/main/java/org/apache/harmony/unpack200/BandSet.java new file mode 100644 index 0000000..1037100 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/BandSet.java @@ -0,0 +1,551 @@ +/* + * 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.unpack200; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import org.apache.harmony.pack200.BHSDCodec; +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.CodecEncoding; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.pack200.PopulationCodec; +import org.apache.harmony.unpack200.bytecode.CPClass; +import org.apache.harmony.unpack200.bytecode.CPDouble; +import org.apache.harmony.unpack200.bytecode.CPFieldRef; +import org.apache.harmony.unpack200.bytecode.CPFloat; +import org.apache.harmony.unpack200.bytecode.CPInteger; +import org.apache.harmony.unpack200.bytecode.CPInterfaceMethodRef; +import org.apache.harmony.unpack200.bytecode.CPLong; +import org.apache.harmony.unpack200.bytecode.CPMethodRef; +import org.apache.harmony.unpack200.bytecode.CPNameAndType; +import org.apache.harmony.unpack200.bytecode.CPString; +import org.apache.harmony.unpack200.bytecode.CPUTF8; + +/** + * Abstract superclass for a set of bands + */ +public abstract class BandSet { + + public abstract void read(InputStream inputStream) throws IOException, + Pack200Exception; + + public abstract void unpack() throws IOException, Pack200Exception; + + public void unpack(InputStream in) throws IOException, Pack200Exception { + read(in); + unpack(); + } + + protected Segment segment; + + protected SegmentHeader header; + + public BandSet(Segment segment) { + this.segment = segment; + this.header = segment.getSegmentHeader(); + } + + /** + * Decode a band and return an array of int values + * + * @param name + * the name of the band (primarily for logging/debugging + * purposes) + * @param in + * the InputStream to decode from + * @param codec + * the default Codec for this band + * @param count + * the number of elements to read + * @return an array of decoded int values + * @throws IOException + * if there is a problem reading from the underlying input + * stream + * @throws Pack200Exception + * if there is a problem decoding the value or that the value is + * invalid + */ + public int[] decodeBandInt(String name, InputStream in, BHSDCodec codec, + int count) throws IOException, Pack200Exception { + int[] band; + // Useful for debugging +// if(count > 0) { +// System.out.println("decoding " + name + " " + count); +// } + Codec codecUsed = codec; + if (codec.getB() == 1 || count == 0) { + return codec.decodeInts(count, in); + } + int[] getFirst = codec.decodeInts(1, in); + if (getFirst.length == 0) { + return getFirst; + } + int first = getFirst[0]; + if (codec.isSigned() && first >= -256 && first <= -1) { + // Non-default codec should be used + codecUsed = CodecEncoding.getCodec((-1 - first), header + .getBandHeadersInputStream(), codec); + band = codecUsed.decodeInts(count, in); + } else if (!codec.isSigned() && first >= codec.getL() + && first <= codec.getL() + 255) { + // Non-default codec should be used + codecUsed = CodecEncoding.getCodec(first - codec.getL(), header + .getBandHeadersInputStream(), codec); + band = codecUsed.decodeInts(count, in); + } else { + // First element should not be discarded + band = codec.decodeInts(count - 1, in, first); + } + // Useful for debugging -E options: + //if(!codecUsed.equals(codec)) { + // int bytes = codecUsed.lastBandLength; + // System.out.println(count + " " + name + " encoded with " + codecUsed + " " + bytes); + //} + if (codecUsed instanceof PopulationCodec) { + PopulationCodec popCodec = (PopulationCodec) codecUsed; + int[] favoured = (int[]) popCodec.getFavoured().clone(); + Arrays.sort(favoured); + for (int i = 0; i < band.length; i++) { + boolean favouredValue = Arrays.binarySearch(favoured, band[i]) > -1; + Codec theCodec = favouredValue ? popCodec.getFavouredCodec() + : popCodec.getUnfavouredCodec(); + if (theCodec instanceof BHSDCodec + && ((BHSDCodec) theCodec).isDelta()) { + BHSDCodec bhsd = (BHSDCodec) theCodec; + long cardinality = bhsd.cardinality(); + while (band[i] > bhsd.largest()) { + band[i] -= cardinality; + } + while (band[i] < bhsd.smallest()) { + band[i] += cardinality; + } + } + } + } + return band; + } + + /** + * Decode a band and return an array of int[] values + * + * @param name + * the name of the band (primarily for logging/debugging + * purposes) + * @param in + * the InputStream to decode from + * @param defaultCodec + * the default codec for this band + * @param counts + * the numbers of elements to read for each int array within the + * array to be returned + * @return an array of decoded int[] values + * @throws IOException + * if there is a problem reading from the underlying input + * stream + * @throws Pack200Exception + * if there is a problem decoding the value or that the value is + * invalid + */ + public int[][] decodeBandInt(String name, InputStream in, + BHSDCodec defaultCodec, int[] counts) throws IOException, + Pack200Exception { + int[][] result = new int[counts.length][]; + int totalCount = 0; + for (int i = 0; i < counts.length; i++) { + totalCount += counts[i]; + } + int[] twoDResult = decodeBandInt(name, in, defaultCodec, totalCount); + int index = 0; + for (int i = 0; i < result.length; i++) { + result[i] = new int[counts[i]]; + for (int j = 0; j < result[i].length; j++) { + result[i][j] = twoDResult[index]; + index++; + } + } + return result; + } + + public long[] parseFlags(String name, InputStream in, int count, + BHSDCodec codec, boolean hasHi) throws IOException, + Pack200Exception { + return parseFlags(name, in, new int[] { count }, + (hasHi ? codec : null), codec)[0]; + } + + public long[][] parseFlags(String name, InputStream in, int counts[], + BHSDCodec codec, boolean hasHi) throws IOException, + Pack200Exception { + return parseFlags(name, in, counts, (hasHi ? codec : null), codec); + } + + public long[] parseFlags(String name, InputStream in, int count, + BHSDCodec hiCodec, BHSDCodec loCodec) throws IOException, + Pack200Exception { + return parseFlags(name, in, new int[] { count }, hiCodec, loCodec)[0]; + } + + public long[][] parseFlags(String name, InputStream in, int counts[], + BHSDCodec hiCodec, BHSDCodec loCodec) throws IOException, + Pack200Exception { + int count = counts.length; + if (count == 0) { + return new long[][] { {} }; + } + int sum = 0; + long[][] result = new long[count][]; + for (int i = 0; i < count; i++) { + result[i] = new long[counts[i]]; + sum += counts[i]; + } + int[] hi = null; + int[] lo; + if (hiCodec != null) { + hi = decodeBandInt(name, in, hiCodec, sum); + lo = decodeBandInt(name, in, loCodec, sum); + } else { + lo = decodeBandInt(name, in, loCodec, sum); + } + + int index = 0; + for (int i = 0; i < result.length; i++) { + for (int j = 0; j < result[i].length; j++) { + if (hi != null) { + result[i][j] = ((long) hi[index] << 32) + | (lo[index] & 4294967295L); + } else { + result[i][j] = lo[index]; + } + index++; + } + } + 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 + * the band name + * @param in + * the input stream to read from + * @param codec + * the BHSDCodec to use for decoding + * @param count + * the number of references to decode + * @param reference + * the array of values to use for the references + * + * @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 + */ + public String[] parseReferences(String name, InputStream in, + BHSDCodec codec, int count, String[] reference) throws IOException, + Pack200Exception { + return parseReferences(name, in, codec, new int[] { count }, reference)[0]; + } + + /** + * 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]. Unlike the other parseReferences, this + * post-processes the result into an array of results. + * + * @param name + * TODO + * @param in + * the input stream to read from + * @param codec + * the BHSDCodec to use for decoding + * @param counts + * the numbers of references to decode for each array entry + * @param reference + * the array of values to use for the references + * + * @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 + */ + public String[][] parseReferences(String name, InputStream in, + BHSDCodec codec, int counts[], String[] reference) + throws IOException, Pack200Exception { + int count = counts.length; + if (count == 0) { + return new String[][] { {} }; + } + String[][] result = new String[count][]; + int sum = 0; + for (int i = 0; i < count; 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[] indices = decodeBandInt(name, in, codec, sum); + for (int i1 = 0; i1 < sum; i1++) { + int index = indices[i1]; + if (index < 0 || index >= reference.length) + throw new Pack200Exception( + "Something has gone wrong during parsing references, index = " + + index + ", array size = " + reference.length); + 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; + } + + public CPInteger[] parseCPIntReferences(String name, InputStream in, + BHSDCodec codec, int count) throws IOException, Pack200Exception { + int[] reference = segment.getCpBands().getCpInt(); + int[] indices = decodeBandInt(name, in, codec, count); + CPInteger[] result = new CPInteger[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + if (index < 0 || index >= reference.length) + throw new Pack200Exception( + "Something has gone wrong during parsing references, index = " + + index + ", array size = " + reference.length); + result[i1] = segment.getCpBands().cpIntegerValue(index); + } + return result; + } + + public CPDouble[] parseCPDoubleReferences(String name, InputStream in, + BHSDCodec codec, int count) throws IOException, Pack200Exception { + int[] indices = decodeBandInt(name, in, codec, count); + CPDouble[] result = new CPDouble[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + result[i1] = segment.getCpBands().cpDoubleValue(index); + } + return result; + } + + public CPFloat[] parseCPFloatReferences(String name, InputStream in, + BHSDCodec codec, int count) throws IOException, Pack200Exception { + int[] indices = decodeBandInt(name, in, codec, count); + CPFloat[] result = new CPFloat[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + result[i1] = segment.getCpBands().cpFloatValue(index); + } + return result; + } + + public CPLong[] parseCPLongReferences(String name, InputStream in, + BHSDCodec codec, int count) throws IOException, Pack200Exception { + long[] reference = segment.getCpBands().getCpLong(); + int[] indices = decodeBandInt(name, in, codec, count); + CPLong[] result = new CPLong[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + if (index < 0 || index >= reference.length) + throw new Pack200Exception( + "Something has gone wrong during parsing references, index = " + + index + ", array size = " + reference.length); + result[i1] = segment.getCpBands().cpLongValue(index); + } + return result; + } + + public CPUTF8[] parseCPUTF8References(String name, InputStream in, + BHSDCodec codec, int count) throws IOException, Pack200Exception { + int[] indices = decodeBandInt(name, in, codec, count); + CPUTF8[] result = new CPUTF8[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + result[i1] = segment.getCpBands().cpUTF8Value(index); + } + return result; + } + + public CPUTF8[][] parseCPUTF8References(String name, InputStream in, + BHSDCodec codec, int[] counts) throws IOException, Pack200Exception { + CPUTF8[][] result = new CPUTF8[counts.length][]; + int sum = 0; + for (int i = 0; i < counts.length; i++) { + result[i] = new CPUTF8[counts[i]]; + sum += counts[i]; + } + CPUTF8[] result1 = new CPUTF8[sum]; + int[] indices = decodeBandInt(name, in, codec, sum); + for (int i1 = 0; i1 < sum; i1++) { + int index = indices[i1]; + result1[i1] = segment.getCpBands().cpUTF8Value(index); + } + CPUTF8[] refs = result1; + int pos = 0; + for (int i = 0; i < counts.length; i++) { + int num = counts[i]; + result[i] = new CPUTF8[num]; + System.arraycopy(refs, pos, result[i], 0, num); + pos += num; + } + return result; + } + + public CPString[] parseCPStringReferences(String name, InputStream in, + BHSDCodec codec, int count) throws IOException, Pack200Exception { + int[] indices = decodeBandInt(name, in, codec, count); + CPString[] result = new CPString[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + result[i1] = segment.getCpBands().cpStringValue(index); + } + return result; + } + + public CPInterfaceMethodRef[] parseCPInterfaceMethodRefReferences( + String name, InputStream in, BHSDCodec codec, int count) + throws IOException, Pack200Exception { + CpBands cpBands = segment.getCpBands(); + int[] indices = decodeBandInt(name, in, codec, count); + CPInterfaceMethodRef[] result = new CPInterfaceMethodRef[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + result[i1] = cpBands.cpIMethodValue(index); + } + return result; + } + + public CPMethodRef[] parseCPMethodRefReferences(String name, + InputStream in, BHSDCodec codec, int count) throws IOException, + Pack200Exception { + CpBands cpBands = segment.getCpBands(); + int[] indices = decodeBandInt(name, in, codec, count); + CPMethodRef[] result = new CPMethodRef[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + result[i1] = cpBands.cpMethodValue(index); + } + return result; + } + + public CPFieldRef[] parseCPFieldRefReferences(String name, InputStream in, + BHSDCodec codec, int count) throws IOException, Pack200Exception { + CpBands cpBands = segment.getCpBands(); + int[] indices = decodeBandInt(name, in, codec, count); + CPFieldRef[] result = new CPFieldRef[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + result[i1] = cpBands.cpFieldValue(index); + } + return result; + } + + public CPNameAndType[] parseCPDescriptorReferences(String name, + InputStream in, BHSDCodec codec, int count) throws IOException, + Pack200Exception { + CpBands cpBands = segment.getCpBands(); + int[] indices = decodeBandInt(name, in, codec, count); + CPNameAndType[] result = new CPNameAndType[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + result[i1] = cpBands.cpNameAndTypeValue(index); + } + return result; + } + + public CPUTF8[] parseCPSignatureReferences(String name, InputStream in, + BHSDCodec codec, int count) throws IOException, Pack200Exception { + int[] indices = decodeBandInt(name, in, codec, count); + CPUTF8[] result = new CPUTF8[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + result[i1] = segment.getCpBands().cpSignatureValue(index); + } + return result; + } + + protected CPUTF8[][] parseCPSignatureReferences(String name, InputStream in, + BHSDCodec codec, int[] counts) throws IOException, Pack200Exception { + CPUTF8[][] result = new CPUTF8[counts.length][]; + int sum = 0; + for (int i = 0; i < counts.length; i++) { + result[i] = new CPUTF8[counts[i]]; + sum += counts[i]; + } + CPUTF8[] result1 = new CPUTF8[sum]; + int[] indices = decodeBandInt(name, in, codec, sum); + for (int i1 = 0; i1 < sum; i1++) { + int index = indices[i1]; + result1[i1] = segment.getCpBands().cpSignatureValue(index); + } + CPUTF8[] refs = result1; + int pos = 0; + for (int i = 0; i < counts.length; i++) { + int num = counts[i]; + result[i] = new CPUTF8[num]; + System.arraycopy(refs, pos, result[i], 0, num); + pos += num; + } + return result; + } + + public CPClass[] parseCPClassReferences(String name, InputStream in, + BHSDCodec codec, int count) throws IOException, Pack200Exception { + int[] indices = decodeBandInt(name, in, codec, count); + CPClass[] result = new CPClass[indices.length]; + for (int i1 = 0; i1 < count; i1++) { + int index = indices[i1]; + result[i1] = segment.getCpBands().cpClassValue(index); + } + return result; + } + + protected String[] getReferences(int[] ints, String[] reference) { + String[] result = new String[ints.length]; + for (int i = 0; i < result.length; i++) { + result[i] = reference[ints[i]]; + } + return result; + } + + protected String[][] getReferences(int[][] ints, String[] reference) { + String[][] result = new String[ints.length][]; + for (int i = 0; i < result.length; i++) { + result[i] = new String[ints[i].length]; + for (int j = 0; j < result[i].length; j++) { + result[i][j] = reference[ints[i][j]]; + + } + } + return result; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/BcBands.java b/src/main/java/org/apache/harmony/unpack200/BcBands.java new file mode 100644 index 0000000..3806173 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/BcBands.java @@ -0,0 +1,606 @@ +/* + * 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.unpack200; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.bytecode.Attribute; +import org.apache.harmony.unpack200.bytecode.BCIRenumberedAttribute; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.CPClass; +import org.apache.harmony.unpack200.bytecode.CodeAttribute; +import org.apache.harmony.unpack200.bytecode.ExceptionTableEntry; +import org.apache.harmony.unpack200.bytecode.NewAttribute; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * Bytecode bands + */ +public class BcBands extends BandSet { + + // The bytecodes for each method in each class as they come (i.e. in their + // packed format) + private byte[][][] methodByteCodePacked; + + // The bands + // TODO: Haven't resolved references yet. Do we want to? + private int[] bcCaseCount; + private int[] bcCaseValue; + private int[] bcByte; + private int[] bcLocal; + private int[] bcShort; + private int[] bcLabel; + private int[] bcIntRef; + private int[] bcFloatRef; + private int[] bcLongRef; + private int[] bcDoubleRef; + private int[] bcStringRef; + private int[] bcClassRef; + private int[] bcFieldRef; + private int[] bcMethodRef; + private int[] bcIMethodRef; + private int[] bcThisField; + private int[] bcSuperField; + private int[] bcThisMethod; + private int[] bcSuperMethod; + private int[] bcInitRef; + private int[] bcEscRef; + private int[] bcEscRefSize; + private int[] bcEscSize; + private int[][] bcEscByte; + + private List wideByteCodes; + + /** + * @param segment + */ + public BcBands(Segment segment) { + super(segment); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.BandSet#unpack(java.io.InputStream) + */ + public void read(InputStream in) throws IOException, Pack200Exception { + + AttributeLayoutMap attributeDefinitionMap = segment + .getAttrDefinitionBands().getAttributeDefinitionMap(); + int classCount = header.getClassCount(); + long[][] methodFlags = segment.getClassBands().getMethodFlags(); + + int bcCaseCountCount = 0; + int bcByteCount = 0; + int bcShortCount = 0; + int bcLocalCount = 0; + int bcLabelCount = 0; + int bcIntRefCount = 0; + int bcFloatRefCount = 0; + int bcLongRefCount = 0; + int bcDoubleRefCount = 0; + int bcStringRefCount = 0; + int bcClassRefCount = 0; + int bcFieldRefCount = 0; + int bcMethodRefCount = 0; + int bcIMethodRefCount = 0; + int bcThisFieldCount = 0; + int bcSuperFieldCount = 0; + int bcThisMethodCount = 0; + int bcSuperMethodCount = 0; + int bcInitRefCount = 0; + int bcEscCount = 0; + int bcEscRefCount = 0; + + AttributeLayout abstractModifier = attributeDefinitionMap + .getAttributeLayout(AttributeLayout.ACC_ABSTRACT, + AttributeLayout.CONTEXT_METHOD); + AttributeLayout nativeModifier = attributeDefinitionMap + .getAttributeLayout(AttributeLayout.ACC_NATIVE, + AttributeLayout.CONTEXT_METHOD); + + methodByteCodePacked = new byte[classCount][][]; + int bcParsed = 0; + + List switchIsTableSwitch = new ArrayList(); + wideByteCodes = new ArrayList(); + for (int c = 0; c < classCount; c++) { + int numberOfMethods = methodFlags[c].length; + methodByteCodePacked[c] = new byte[numberOfMethods][]; + for (int m = 0; m < numberOfMethods; m++) { + long methodFlag = methodFlags[c][m]; + if (!abstractModifier.matches(methodFlag) + && !nativeModifier.matches(methodFlag)) { + ByteArrayOutputStream codeBytes = new ByteArrayOutputStream(); + byte code; + while ((code = (byte) (0xff & in.read())) != -1) + codeBytes.write(code); + methodByteCodePacked[c][m] = codeBytes.toByteArray(); + bcParsed += methodByteCodePacked[c][m].length; + int[] codes = new int[methodByteCodePacked[c][m].length]; + for (int i = 0; i < codes.length; i++) { + codes[i] = methodByteCodePacked[c][m][i] & 0xff; + } + for (int i = 0; i < methodByteCodePacked[c][m].length; i++) { + int codePacked = 0xff & methodByteCodePacked[c][m][i]; + switch (codePacked) { + case 16: // bipush + case 188: // newarray + bcByteCount++; + break; + case 17: // sipush + bcShortCount++; + break; + case 18: // (a)ldc + case 19: // aldc_w + bcStringRefCount++; + break; + case 234: // ildc + case 237: // ildc_w + bcIntRefCount++; + break; + case 235: // fldc + case 238: // fldc_w + bcFloatRefCount++; + break; + case 197: // multianewarray + bcByteCount++; + // fallthrough intended + case 233: // cldc + case 236: // cldc_w + case 187: // new + case 189: // anewarray + case 192: // checkcast + case 193: // instanceof + bcClassRefCount++; + break; + case 20: // lldc2_w + bcLongRefCount++; + break; + case 239: // dldc2_w + bcDoubleRefCount++; + break; + case 169: // ret + bcLocalCount++; + break; + case 167: // goto + case 168: // jsr + case 200: // goto_w + case 201: // jsr_w + bcLabelCount++; + break; + case 170: // tableswitch + switchIsTableSwitch.add(new Boolean(true)); + bcCaseCountCount++; + bcLabelCount++; + break; + case 171: // lookupswitch + switchIsTableSwitch.add(new Boolean(false)); + bcCaseCountCount++; + bcLabelCount++; + break; + case 178: // getstatic + case 179: // putstatic + case 180: // getfield + case 181: // putfield + bcFieldRefCount++; + break; + case 182: // invokevirtual + case 183: // invokespecial + case 184: // invokestatic + bcMethodRefCount++; + break; + case 185: // invokeinterface + bcIMethodRefCount++; + break; + case 202: // getstatic_this + case 203: // putstatic_this + case 204: // getfield_this + case 205: // putfield_this + case 209: // aload_0_getstatic_this + case 210: // aload_0_putstatic_this + case 211: // aload_0_putfield_this + case 212: // aload_0_putfield_this + bcThisFieldCount++; + break; + case 206: // invokevirtual_this + case 207: // invokespecial_this + case 208: // invokestatic_this + case 213: // aload_0_invokevirtual_this + case 214: // aload_0_invokespecial_this + case 215: // aload_0_invokestatic_this + bcThisMethodCount++; + break; + case 216: // getstatic_super + case 217: // putstatic_super + case 218: // getfield_super + case 219: // putfield_super + case 223: // aload_0_getstatic_super + case 224: // aload_0_putstatic_super + case 225: // aload_0_getfield_super + case 226: // aload_0_putfield_super + bcSuperFieldCount++; + break; + case 220: // invokevirtual_super + case 221: // invokespecial_super + case 222: // invokestatic_super + case 227: // aload_0_invokevirtual_super + case 228: // aload_0_invokespecial_super + case 229: // aload_0_invokestatic_super + bcSuperMethodCount++; + break; + case 132: // iinc + bcLocalCount++; + bcByteCount++; + break; + case 196: // wide + int nextInstruction = 0xff & methodByteCodePacked[c][m][i + 1]; + wideByteCodes.add(new Integer(nextInstruction)); + if (nextInstruction == 132) { // iinc + bcLocalCount++; + bcShortCount++; + } else if (endsWithLoad(nextInstruction) + || endsWithStore(nextInstruction) + || nextInstruction == 169) { + bcLocalCount++; + } else { + segment + .log( + Segment.LOG_LEVEL_VERBOSE, + "Found unhandled " + + ByteCode + .getByteCode(nextInstruction)); + } + i++; + break; + case 230: // invokespecial_this_init + case 231: // invokespecial_super_init + case 232: // invokespecial_new_init + bcInitRefCount++; + break; + case 253: // ref_escape + bcEscRefCount++; + break; + case 254: // byte_escape + bcEscCount++; + break; + default: + if (endsWithLoad(codePacked) + || endsWithStore(codePacked)) { + bcLocalCount++; + } else if (startsWithIf(codePacked)) { + bcLabelCount++; + } + } + } + } + } + } + // other bytecode bands + bcCaseCount = decodeBandInt("bc_case_count", in, Codec.UNSIGNED5, + bcCaseCountCount); + int bcCaseValueCount = 0; + for (int i = 0; i < bcCaseCount.length; i++) { + boolean isTableSwitch = ((Boolean) switchIsTableSwitch.get(i)) + .booleanValue(); + if (isTableSwitch) { + bcCaseValueCount += 1; + } else { + bcCaseValueCount += bcCaseCount[i]; + } + } + bcCaseValue = decodeBandInt("bc_case_value", in, Codec.DELTA5, + bcCaseValueCount); + // Every case value needs a label. We weren't able to count these + // above, because we didn't know how many cases there were. + // Have to correct it now. + for (int index = 0; index < bcCaseCountCount; index++) { + bcLabelCount += bcCaseCount[index]; + } + bcByte = decodeBandInt("bc_byte", in, Codec.BYTE1, bcByteCount); + bcShort = decodeBandInt("bc_short", in, Codec.DELTA5, bcShortCount); + bcLocal = decodeBandInt("bc_local", in, Codec.UNSIGNED5, bcLocalCount); + bcLabel = decodeBandInt("bc_label", in, Codec.BRANCH5, bcLabelCount); + bcIntRef = decodeBandInt("bc_intref", in, Codec.DELTA5, bcIntRefCount); + bcFloatRef = decodeBandInt("bc_floatref", in, Codec.DELTA5, + bcFloatRefCount); + bcLongRef = decodeBandInt("bc_longref", in, Codec.DELTA5, + bcLongRefCount); + bcDoubleRef = decodeBandInt("bc_doubleref", in, Codec.DELTA5, + bcDoubleRefCount); + bcStringRef = decodeBandInt("bc_stringref", in, Codec.DELTA5, + bcStringRefCount); + bcClassRef = decodeBandInt("bc_classref", in, Codec.UNSIGNED5, + bcClassRefCount); + bcFieldRef = decodeBandInt("bc_fieldref", in, Codec.DELTA5, + bcFieldRefCount); + bcMethodRef = decodeBandInt("bc_methodref", in, Codec.UNSIGNED5, + bcMethodRefCount); + bcIMethodRef = decodeBandInt("bc_imethodref", in, Codec.DELTA5, + bcIMethodRefCount); + bcThisField = decodeBandInt("bc_thisfield", in, Codec.UNSIGNED5, + bcThisFieldCount); + bcSuperField = decodeBandInt("bc_superfield", in, Codec.UNSIGNED5, + bcSuperFieldCount); + bcThisMethod = decodeBandInt("bc_thismethod", in, Codec.UNSIGNED5, + bcThisMethodCount); + bcSuperMethod = decodeBandInt("bc_supermethod", in, Codec.UNSIGNED5, + bcSuperMethodCount); + bcInitRef = decodeBandInt("bc_initref", in, Codec.UNSIGNED5, + bcInitRefCount); + bcEscRef = decodeBandInt("bc_escref", in, Codec.UNSIGNED5, + bcEscRefCount); + bcEscRefSize = decodeBandInt("bc_escrefsize", in, Codec.UNSIGNED5, + bcEscRefCount); + bcEscSize = decodeBandInt("bc_escsize", in, Codec.UNSIGNED5, bcEscCount); + bcEscByte = decodeBandInt("bc_escbyte", in, Codec.BYTE1, bcEscSize); + } + + public void unpack() throws Pack200Exception { + int classCount = header.getClassCount(); + long[][] methodFlags = segment.getClassBands().getMethodFlags(); + int[] codeMaxNALocals = segment.getClassBands().getCodeMaxNALocals(); + int[] codeMaxStack = segment.getClassBands().getCodeMaxStack(); + ArrayList[][] methodAttributes = segment.getClassBands() + .getMethodAttributes(); + String[][] methodDescr = segment.getClassBands().getMethodDescr(); + + AttributeLayoutMap attributeDefinitionMap = segment + .getAttrDefinitionBands().getAttributeDefinitionMap(); + + AttributeLayout abstractModifier = attributeDefinitionMap + .getAttributeLayout(AttributeLayout.ACC_ABSTRACT, + AttributeLayout.CONTEXT_METHOD); + AttributeLayout nativeModifier = attributeDefinitionMap + .getAttributeLayout(AttributeLayout.ACC_NATIVE, + AttributeLayout.CONTEXT_METHOD); + AttributeLayout staticModifier = attributeDefinitionMap + .getAttributeLayout(AttributeLayout.ACC_STATIC, + AttributeLayout.CONTEXT_METHOD); + + int[] wideByteCodeArray = new int[wideByteCodes.size()]; + for (int index = 0; index < wideByteCodeArray.length; index++) { + wideByteCodeArray[index] = ((Integer) wideByteCodes.get(index)) + .intValue(); + } + OperandManager operandManager = new OperandManager(bcCaseCount, + bcCaseValue, bcByte, bcShort, bcLocal, bcLabel, bcIntRef, + bcFloatRef, bcLongRef, bcDoubleRef, bcStringRef, bcClassRef, + bcFieldRef, bcMethodRef, bcIMethodRef, bcThisField, + bcSuperField, bcThisMethod, bcSuperMethod, bcInitRef, + wideByteCodeArray); + operandManager.setSegment(segment); + + int i = 0; + ArrayList orderedCodeAttributes = segment.getClassBands() + .getOrderedCodeAttributes(); + int codeAttributeIndex = 0; + + // Exception table fields + int[] handlerCount = segment.getClassBands().getCodeHandlerCount(); + int[][] handlerStartPCs = segment.getClassBands() + .getCodeHandlerStartP(); + int[][] handlerEndPCs = segment.getClassBands().getCodeHandlerEndPO(); + int[][] handlerCatchPCs = segment.getClassBands() + .getCodeHandlerCatchPO(); + int[][] handlerClassTypes = segment.getClassBands() + .getCodeHandlerClassRCN(); + + boolean allCodeHasFlags = segment.getSegmentHeader().getOptions().hasAllCodeFlags(); + boolean[] codeHasFlags = segment.getClassBands().getCodeHasAttributes(); + + for (int c = 0; c < classCount; c++) { + int numberOfMethods = methodFlags[c].length; + for (int m = 0; m < numberOfMethods; m++) { + long methodFlag = methodFlags[c][m]; + if (!abstractModifier.matches(methodFlag) + && !nativeModifier.matches(methodFlag)) { + int maxStack = codeMaxStack[i]; + int maxLocal = codeMaxNALocals[i]; + if (!staticModifier.matches(methodFlag)) + maxLocal++; // one for 'this' parameter + // I believe this has to take wide arguments into account + maxLocal += SegmentUtils + .countInvokeInterfaceArgs(methodDescr[c][m]); + String[] cpClass = segment.getCpBands().getCpClass(); + operandManager.setCurrentClass(cpClass[segment + .getClassBands().getClassThisInts()[c]]); + operandManager.setSuperClass(cpClass[segment + .getClassBands().getClassSuperInts()[c]]); + List exceptionTable = new ArrayList(); + if (handlerCount != null) { + for (int j = 0; j < handlerCount[i]; j++) { + int handlerClass = handlerClassTypes[i][j] - 1; + CPClass cpHandlerClass = null; + if (handlerClass != -1) { + // The handlerClass will be null if the + // catch is a finally (that is, the + // exception table catch_type should be 0 + cpHandlerClass = segment.getCpBands() + .cpClassValue(handlerClass); + } + ExceptionTableEntry entry = new ExceptionTableEntry( + handlerStartPCs[i][j], handlerEndPCs[i][j], + handlerCatchPCs[i][j], cpHandlerClass); + exceptionTable.add(entry); + } + } + CodeAttribute codeAttr = new CodeAttribute(maxStack, + maxLocal, methodByteCodePacked[c][m], segment, + operandManager, exceptionTable); + ArrayList methodAttributesList = methodAttributes[c][m]; + // Make sure we add the code attribute in the right place + int indexForCodeAttr = 0; + for (int index = 0; index < methodAttributesList.size(); index++) { + Attribute attribute = (Attribute) methodAttributesList.get(index); + if((attribute instanceof NewAttribute && ((NewAttribute)attribute).getLayoutIndex() < 15)) { + indexForCodeAttr ++; + } else { + break; + } + } + methodAttributesList.add(indexForCodeAttr, codeAttr); + codeAttr.renumber(codeAttr.byteCodeOffsets); + List currentAttributes; + if (allCodeHasFlags) { + currentAttributes = (List) orderedCodeAttributes.get(i); + } else { + if (codeHasFlags[i]) { + currentAttributes = (List) orderedCodeAttributes + .get(codeAttributeIndex); + codeAttributeIndex++; + } else { + currentAttributes = Collections.EMPTY_LIST; + } + } + for (int index = 0; index < currentAttributes.size(); index++) { + Attribute currentAttribute = (Attribute) currentAttributes + .get(index); + codeAttr.addAttribute(currentAttribute); + // Fix up the line numbers if needed + if (currentAttribute.hasBCIRenumbering()) { + ((BCIRenumberedAttribute) currentAttribute) + .renumber(codeAttr.byteCodeOffsets); + } + } + i++; + } + } + } + } + + private boolean startsWithIf(int codePacked) { + return (codePacked >= 153 && codePacked <= 166) || (codePacked == 198) + || (codePacked == 199); + } + + private boolean endsWithLoad(int codePacked) { + return (codePacked >= 21 && codePacked <= 25); + } + + private boolean endsWithStore(int codePacked) { + return (codePacked >= 54 && codePacked <= 58); + } + + public byte[][][] getMethodByteCodePacked() { + return methodByteCodePacked; + } + + public int[] getBcCaseCount() { + return bcCaseCount; + } + + public int[] getBcCaseValue() { + return bcCaseValue; + } + + public int[] getBcByte() { + return bcByte; + } + + public int[] getBcClassRef() { + return bcClassRef; + } + + public int[] getBcDoubleRef() { + return bcDoubleRef; + } + + public int[] getBcFieldRef() { + return bcFieldRef; + } + + public int[] getBcFloatRef() { + return bcFloatRef; + } + + public int[] getBcIMethodRef() { + return bcIMethodRef; + } + + public int[] getBcInitRef() { + return bcInitRef; + } + + public int[] getBcIntRef() { + return bcIntRef; + } + + public int[] getBcLabel() { + return bcLabel; + } + + public int[] getBcLocal() { + return bcLocal; + } + + public int[] getBcLongRef() { + return bcLongRef; + } + + public int[] getBcMethodRef() { + return bcMethodRef; + } + + public int[] getBcShort() { + return bcShort; + } + + public int[] getBcStringRef() { + return bcStringRef; + } + + public int[] getBcSuperField() { + return bcSuperField; + } + + public int[] getBcSuperMethod() { + return bcSuperMethod; + } + + public int[] getBcThisField() { + return bcThisField; + } + + public int[] getBcThisMethod() { + return bcThisMethod; + } + + public int[] getBcEscRef() { + return bcEscRef; + } + + public int[] getBcEscRefSize() { + return bcEscRefSize; + } + + public int[] getBcEscSize() { + return bcEscSize; + } + + public int[][] getBcEscByte() { + return bcEscByte; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/ClassBands.java b/src/main/java/org/apache/harmony/unpack200/ClassBands.java new file mode 100644 index 0000000..d11ba69 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/ClassBands.java @@ -0,0 +1,1418 @@ +/* + * 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.unpack200; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.bytecode.Attribute; +import org.apache.harmony.unpack200.bytecode.CPClass; +import org.apache.harmony.unpack200.bytecode.CPNameAndType; +import org.apache.harmony.unpack200.bytecode.CPUTF8; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; +import org.apache.harmony.unpack200.bytecode.ConstantValueAttribute; +import org.apache.harmony.unpack200.bytecode.DeprecatedAttribute; +import org.apache.harmony.unpack200.bytecode.EnclosingMethodAttribute; +import org.apache.harmony.unpack200.bytecode.ExceptionsAttribute; +import org.apache.harmony.unpack200.bytecode.LineNumberTableAttribute; +import org.apache.harmony.unpack200.bytecode.LocalVariableTableAttribute; +import org.apache.harmony.unpack200.bytecode.LocalVariableTypeTableAttribute; +import org.apache.harmony.unpack200.bytecode.SignatureAttribute; +import org.apache.harmony.unpack200.bytecode.SourceFileAttribute; + +/** + * Class Bands + */ +public class ClassBands extends BandSet { + + private int[] classFieldCount; + + private long[] classFlags; + + private long[] classAccessFlags; // Access flags for writing to the class + // file + + private int[][] classInterfacesInts; + + private int[] classMethodCount; + + private int[] classSuperInts; + + private String[] classThis; + + private int[] classThisInts; + + private ArrayList[] classAttributes; + + private int[] classVersionMajor; + + private int[] classVersionMinor; + + private IcTuple[][] icLocal; + + private List[] codeAttributes; + + private int[] codeHandlerCount; + + private int[] codeMaxNALocals; + + private int[] codeMaxStack; + + private ArrayList[][] fieldAttributes; + + private String[][] fieldDescr; + + private int[][] fieldDescrInts; + + private long[][] fieldFlags; + + private long[][] fieldAccessFlags; + + private ArrayList[][] methodAttributes; + + private String[][] methodDescr; + + private int[][] methodDescrInts; + + private long[][] methodFlags; + + private long[][] methodAccessFlags; + + private final AttributeLayoutMap attrMap; + + private final CpBands cpBands; + + private final SegmentOptions options; + + private final int classCount; + + private int[] methodAttrCalls; + + private int[][] codeHandlerStartP; + + private int[][] codeHandlerEndPO; + + private int[][] codeHandlerCatchPO; + + private int[][] codeHandlerClassRCN; + + private boolean [] codeHasAttributes; + + /** + * @param segment + */ + public ClassBands(Segment segment) { + super(segment); + this.attrMap = segment.getAttrDefinitionBands() + .getAttributeDefinitionMap(); + this.cpBands = segment.getCpBands(); + this.classCount = header.getClassCount(); + this.options = header.getOptions(); + + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.BandSet#unpack(java.io.InputStream) + */ + public void read(InputStream in) throws IOException, Pack200Exception { + int classCount = header.getClassCount(); + classThisInts = decodeBandInt("class_this", in, Codec.DELTA5, classCount); + classThis = getReferences(classThisInts, cpBands.getCpClass()); + classSuperInts = decodeBandInt("class_super", in, Codec.DELTA5, + classCount); + int[] classInterfaceLengths = decodeBandInt("class_interface_count", + in, Codec.DELTA5, classCount); + classInterfacesInts = decodeBandInt("class_interface", in, Codec.DELTA5, + classInterfaceLengths); + classFieldCount = decodeBandInt("class_field_count", in, Codec.DELTA5, + classCount); + classMethodCount = decodeBandInt("class_method_count", in, + Codec.DELTA5, classCount); + parseFieldBands(in); + parseMethodBands(in); + parseClassAttrBands(in); + parseCodeBands(in); + + } + + public void unpack() { + + } + + private void parseFieldBands(InputStream in) throws IOException, + Pack200Exception { + fieldDescrInts = decodeBandInt("field_descr", in, Codec.DELTA5, + classFieldCount); + fieldDescr = getReferences(fieldDescrInts, cpBands.getCpDescriptor()); + parseFieldAttrBands(in); + } + + private void parseFieldAttrBands(InputStream in) throws IOException, + Pack200Exception { + fieldFlags = parseFlags("field_flags", in, classFieldCount, + Codec.UNSIGNED5, options.hasFieldFlagsHi()); + int fieldAttrCount = SegmentUtils.countBit16(fieldFlags); + int[] fieldAttrCounts = decodeBandInt("field_attr_count", in, + Codec.UNSIGNED5, fieldAttrCount); + int[][] fieldAttrIndexes = decodeBandInt("field_attr_indexes", in, + Codec.UNSIGNED5, fieldAttrCounts); + int callCount = getCallCount(fieldAttrIndexes, fieldFlags, + AttributeLayout.CONTEXT_FIELD); + int[] fieldAttrCalls = decodeBandInt("field_attr_calls", in, + Codec.UNSIGNED5, callCount); + + // Assign empty field attributes + 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(); + } + } + + AttributeLayout constantValueLayout = attrMap.getAttributeLayout( + "ConstantValue", AttributeLayout.CONTEXT_FIELD); + int constantCount = SegmentUtils.countMatches(fieldFlags, + constantValueLayout); + int[] field_constantValue_KQ = decodeBandInt("field_ConstantValue_KQ", + in, Codec.UNSIGNED5, constantCount); + int constantValueIndex = 0; + + AttributeLayout signatureLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_SIGNATURE, + AttributeLayout.CONTEXT_FIELD); + int signatureCount = SegmentUtils.countMatches(fieldFlags, + signatureLayout); + int[] fieldSignatureRS = decodeBandInt("field_Signature_RS", in, + Codec.UNSIGNED5, signatureCount); + int signatureIndex = 0; + + AttributeLayout deprecatedLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_DEPRECATED, + AttributeLayout.CONTEXT_FIELD); + + for (int i = 0; i < classCount; i++) { + for (int j = 0; j < fieldFlags[i].length; j++) { + long flag = fieldFlags[i][j]; + if (deprecatedLayout.matches(flag)) { + fieldAttributes[i][j].add(new DeprecatedAttribute()); + } + if (constantValueLayout.matches(flag)) { + // we've got a value to read + long result = field_constantValue_KQ[constantValueIndex]; + String desc = fieldDescr[i][j]; + int colon = desc.indexOf(':'); + String type = desc.substring(colon + 1); + if (type.equals("B") || type.equals("S") + || type.equals("C") || type.equals("Z")) + type = "I"; + ClassFileEntry value = constantValueLayout.getValue(result, + type, cpBands.getConstantPool()); + fieldAttributes[i][j] + .add(new ConstantValueAttribute(value)); + constantValueIndex++; + } + if (signatureLayout.matches(flag)) { + // we've got a signature attribute + long result = fieldSignatureRS[signatureIndex]; + String desc = fieldDescr[i][j]; + int colon = desc.indexOf(':'); + String type = desc.substring(colon + 1); + CPUTF8 value = (CPUTF8) signatureLayout.getValue(result, + type, cpBands.getConstantPool()); + fieldAttributes[i][j].add(new SignatureAttribute(value)); + signatureIndex++; + } + } + } + + int backwardsCallsUsed = parseFieldMetadataBands(in, fieldAttrCalls); + + // Parse non-predefined attribute bands + int backwardsCallIndex = backwardsCallsUsed; + int limit = options.hasFieldFlagsHi() ? 62 : 31; + AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1]; + int[] counts = new int[limit + 1]; + List[] otherAttributes = new List[limit + 1]; + for (int i = 0; i < limit; i++) { + AttributeLayout layout = attrMap.getAttributeLayout(i, + AttributeLayout.CONTEXT_FIELD); + if (layout != null && !(layout.isDefaultLayout())) { + otherLayouts[i] = layout; + counts[i] = SegmentUtils.countMatches(fieldFlags, layout); + } + } + for (int i = 0; i < counts.length; i++) { + if (counts[i] > 0) { + NewAttributeBands bands = attrMap + .getAttributeBands(otherLayouts[i]); + otherAttributes[i] = bands.parseAttributes(in, counts[i]); + int numBackwardsCallables = otherLayouts[i] + .numBackwardsCallables(); + if (numBackwardsCallables > 0) { + int[] backwardsCalls = new int[numBackwardsCallables]; + System.arraycopy(fieldAttrCalls, backwardsCallIndex, + backwardsCalls, 0, numBackwardsCallables); + bands.setBackwardsCalls(backwardsCalls); + backwardsCallIndex += numBackwardsCallables; + } + } + } + + // Non-predefined attributes + for (int i = 0; i < classCount; i++) { + for (int j = 0; j < fieldFlags[i].length; j++) { + long flag = fieldFlags[i][j]; + int othersAddedAtStart = 0; + for (int k = 0; k < otherLayouts.length; k++) { + if (otherLayouts[k] != null + && otherLayouts[k].matches(flag)) { + // Add the next attribute + if(otherLayouts[k].getIndex()<15) { + fieldAttributes[i][j].add(othersAddedAtStart++, otherAttributes[k].get(0)); + } else { + fieldAttributes[i][j].add(otherAttributes[k].get(0)); + } + otherAttributes[k].remove(0); + } + } + } + } + } + + private void parseMethodBands(InputStream in) throws IOException, + Pack200Exception { + methodDescrInts = decodeBandInt("method_descr", in, Codec.MDELTA5, + classMethodCount); + methodDescr = getReferences(methodDescrInts, cpBands.getCpDescriptor()); + parseMethodAttrBands(in); + } + + private void parseMethodAttrBands(InputStream in) throws IOException, + Pack200Exception { + methodFlags = parseFlags("method_flags", in, classMethodCount, + Codec.UNSIGNED5, options.hasMethodFlagsHi()); + int methodAttrCount = SegmentUtils.countBit16(methodFlags); + int[] methodAttrCounts = decodeBandInt("method_attr_count", in, + Codec.UNSIGNED5, methodAttrCount); + int[][] methodAttrIndexes = decodeBandInt("method_attr_indexes", in, + Codec.UNSIGNED5, methodAttrCounts); + int callCount = getCallCount(methodAttrIndexes, methodFlags, + AttributeLayout.CONTEXT_METHOD); + methodAttrCalls = decodeBandInt("method_attr_calls", in, Codec.UNSIGNED5, + callCount); + + // 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(); + } + } + + // Parse method exceptions attributes + AttributeLayout methodExceptionsLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_EXCEPTIONS, + AttributeLayout.CONTEXT_METHOD); + int count = SegmentUtils.countMatches(methodFlags, + methodExceptionsLayout); + int[] numExceptions = decodeBandInt("method_Exceptions_n", in, + Codec.UNSIGNED5, count); + int[][] methodExceptionsRS = decodeBandInt("method_Exceptions_RC", + in, Codec.UNSIGNED5, numExceptions); + + // Parse method signature attributes + AttributeLayout methodSignatureLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_SIGNATURE, + AttributeLayout.CONTEXT_METHOD); + int count1 = SegmentUtils.countMatches(methodFlags, + methodSignatureLayout); + int[] methodSignatureRS = decodeBandInt("method_signature_RS", in, + Codec.UNSIGNED5, count1); + + AttributeLayout deprecatedLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_DEPRECATED, + AttributeLayout.CONTEXT_METHOD); + + // Add attributes to the attribute arrays + int methodExceptionsIndex = 0; + int methodSignatureIndex = 0; + for (int i = 0; i < methodAttributes.length; i++) { + for (int j = 0; j < methodAttributes[i].length; j++) { + long flag = methodFlags[i][j]; + if (methodExceptionsLayout.matches(flag)) { + int n = numExceptions[methodExceptionsIndex]; + int[] exceptions = methodExceptionsRS[methodExceptionsIndex]; + CPClass[] exceptionClasses = new CPClass[n]; + for (int k = 0; k < n; k++) { + exceptionClasses[k] = cpBands + .cpClassValue(exceptions[k]); + } + methodAttributes[i][j].add(new ExceptionsAttribute( + exceptionClasses)); + methodExceptionsIndex++; + } + if (methodSignatureLayout.matches(flag)) { + // We've got a signature attribute + long result = methodSignatureRS[methodSignatureIndex]; + String desc = methodDescr[i][j]; + int colon = desc.indexOf(':'); + 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"; + CPUTF8 value = (CPUTF8) methodSignatureLayout.getValue( + result, type, cpBands.getConstantPool()); + methodAttributes[i][j] + .add(new SignatureAttribute(value)); + methodSignatureIndex++; + } + if (deprecatedLayout.matches(flag)) { + methodAttributes[i][j].add(new DeprecatedAttribute()); + } + } + } + + // Parse method metadata bands + int backwardsCallsUsed = parseMethodMetadataBands(in, methodAttrCalls); + + // Parse non-predefined attribute bands + int backwardsCallIndex = backwardsCallsUsed; + int limit = options.hasMethodFlagsHi() ? 62 : 31; + AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1]; + int[] counts = new int[limit + 1]; + List[] otherAttributes = new List[limit + 1]; + for (int i = 0; i < limit; i++) { + AttributeLayout layout = attrMap.getAttributeLayout(i, + AttributeLayout.CONTEXT_METHOD); + if (layout != null && !(layout.isDefaultLayout())) { + otherLayouts[i] = layout; + counts[i] = SegmentUtils.countMatches(methodFlags, layout); + } + } + for (int i = 0; i < counts.length; i++) { + if (counts[i] > 0) { + NewAttributeBands bands = attrMap + .getAttributeBands(otherLayouts[i]); + otherAttributes[i] = bands.parseAttributes(in, counts[i]); + int numBackwardsCallables = otherLayouts[i] + .numBackwardsCallables(); + if (numBackwardsCallables > 0) { + int[] backwardsCalls = new int[numBackwardsCallables]; + System.arraycopy(methodAttrCalls, backwardsCallIndex, + backwardsCalls, 0, numBackwardsCallables); + bands.setBackwardsCalls(backwardsCalls); + backwardsCallIndex += numBackwardsCallables; + } + } + } + + // Non-predefined attributes + for (int i = 0; i < methodAttributes.length; i++) { + for (int j = 0; j < methodAttributes[i].length; j++) { + long flag = methodFlags[i][j]; + int othersAddedAtStart = 0; + for (int k = 0; k < otherLayouts.length; k++) { + if (otherLayouts[k] != null + && otherLayouts[k].matches(flag)) { + // Add the next attribute + if(otherLayouts[k].getIndex() < 15) { + methodAttributes[i][j].add(othersAddedAtStart++, otherAttributes[k].get(0)); + } else { + methodAttributes[i][j].add(otherAttributes[k].get(0)); + } + otherAttributes[k].remove(0); + } + } + } + } + } + + private int getCallCount(int[][] methodAttrIndexes, long[][] flags, + int context) throws Pack200Exception { + int callCount = 0; + for (int i = 0; i < methodAttrIndexes.length; i++) { + for (int j = 0; j < methodAttrIndexes[i].length; j++) { + int index = methodAttrIndexes[i][j]; + AttributeLayout layout = attrMap.getAttributeLayout(index, + context); + callCount += layout.numBackwardsCallables(); + } + } + int layoutsUsed = 0; + for (int i = 0; i < flags.length; i++) { + for (int j = 0; j < flags[i].length; j++) { + layoutsUsed |= flags[i][j]; + } + } + for (int i = 0; i < 26; i++) { + if ((layoutsUsed & 1 << i) != 0) { + AttributeLayout layout = attrMap.getAttributeLayout(i, context); + callCount += layout.numBackwardsCallables(); + } + } + return callCount; + } + + private void parseClassAttrBands(InputStream in) throws IOException, + Pack200Exception { + String[] cpUTF8 = cpBands.getCpUTF8(); + String[] cpClass = cpBands.getCpClass(); + + // Prepare empty attribute lists + classAttributes = new ArrayList[classCount]; + for (int i = 0; i < classCount; i++) { + classAttributes[i] = new ArrayList(); + } + + classFlags = parseFlags("class_flags", in, classCount, Codec.UNSIGNED5, + options.hasClassFlagsHi()); + int classAttrCount = SegmentUtils.countBit16(classFlags); + int[] classAttrCounts = decodeBandInt("class_attr_count", in, + Codec.UNSIGNED5, classAttrCount); + int[][] classAttrIndexes = decodeBandInt("class_attr_indexes", in, + Codec.UNSIGNED5, classAttrCounts); + int callCount = getCallCount(classAttrIndexes, + new long[][] { classFlags }, AttributeLayout.CONTEXT_CLASS); + int[] classAttrCalls = decodeBandInt("class_attr_calls", in, + Codec.UNSIGNED5, callCount); + + AttributeLayout deprecatedLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_DEPRECATED, + AttributeLayout.CONTEXT_CLASS); + + AttributeLayout sourceFileLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_SOURCE_FILE, + AttributeLayout.CONTEXT_CLASS); + int sourceFileCount = SegmentUtils.countMatches(classFlags, + sourceFileLayout); + int[] classSourceFile = decodeBandInt("class_SourceFile_RUN", in, + Codec.UNSIGNED5, sourceFileCount); + + AttributeLayout enclosingMethodLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_ENCLOSING_METHOD, + AttributeLayout.CONTEXT_CLASS); + int enclosingMethodCount = SegmentUtils.countMatches(classFlags, + enclosingMethodLayout); + int[] enclosingMethodRC = decodeBandInt( + "class_EnclosingMethod_RC", in, Codec.UNSIGNED5, + enclosingMethodCount); + int[] enclosingMethodRDN = decodeBandInt( + "class_EnclosingMethod_RDN", in, Codec.UNSIGNED5, + enclosingMethodCount); + + AttributeLayout signatureLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_SIGNATURE, + AttributeLayout.CONTEXT_CLASS); + int signatureCount = SegmentUtils.countMatches(classFlags, + signatureLayout); + int[] classSignature = decodeBandInt("class_Signature_RS", in, + Codec.UNSIGNED5, signatureCount); + + int backwardsCallsUsed = parseClassMetadataBands(in, classAttrCalls); + + AttributeLayout innerClassLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_INNER_CLASSES, + AttributeLayout.CONTEXT_CLASS); + int innerClassCount = SegmentUtils.countMatches(classFlags, + innerClassLayout); + int[] classInnerClassesN = decodeBandInt("class_InnerClasses_N", in, + Codec.UNSIGNED5, innerClassCount); + int[][] classInnerClassesRC = decodeBandInt("class_InnerClasses_RC", + in, Codec.UNSIGNED5, classInnerClassesN); + int[][] classInnerClassesF = decodeBandInt("class_InnerClasses_F", in, + Codec.UNSIGNED5, classInnerClassesN); + int flagsCount = 0; + for (int i = 0; i < classInnerClassesF.length; i++) { + for (int j = 0; j < classInnerClassesF[i].length; j++) { + if (classInnerClassesF[i][j] != 0) { + flagsCount++; + } + } + } + int[] classInnerClassesOuterRCN = decodeBandInt( + "class_InnerClasses_outer_RCN", in, Codec.UNSIGNED5, flagsCount); + int[] classInnerClassesNameRUN = decodeBandInt( + "class_InnerClasses_name_RUN", in, Codec.UNSIGNED5, flagsCount); + + AttributeLayout versionLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_CLASS_FILE_VERSION, + AttributeLayout.CONTEXT_CLASS); + int versionCount = SegmentUtils.countMatches(classFlags, versionLayout); + int[] classFileVersionMinorH = decodeBandInt( + "class_file_version_minor_H", in, Codec.UNSIGNED5, versionCount); + int[] classFileVersionMajorH = decodeBandInt( + "class_file_version_major_H", in, Codec.UNSIGNED5, versionCount); + if (versionCount > 0) { + classVersionMajor = new int[classCount]; + classVersionMinor = new int[classCount]; + } + int defaultVersionMajor = header.getDefaultClassMajorVersion(); + int defaultVersionMinor = header.getDefaultClassMinorVersion(); + + // Parse non-predefined attribute bands + int backwardsCallIndex = backwardsCallsUsed; + int limit = options.hasClassFlagsHi() ? 62 : 31; + AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1]; + int[] counts = new int[limit + 1]; + List[] otherAttributes = new List[limit + 1]; + for (int i = 0; i < limit; i++) { + AttributeLayout layout = attrMap.getAttributeLayout(i, + AttributeLayout.CONTEXT_CLASS); + if (layout != null && !(layout.isDefaultLayout())) { + otherLayouts[i] = layout; + counts[i] = SegmentUtils.countMatches(classFlags, layout); + } + } + for (int i = 0; i < counts.length; i++) { + if (counts[i] > 0) { + NewAttributeBands bands = attrMap + .getAttributeBands(otherLayouts[i]); + otherAttributes[i] = bands.parseAttributes(in, counts[i]); + int numBackwardsCallables = otherLayouts[i] + .numBackwardsCallables(); + if (numBackwardsCallables > 0) { + int[] backwardsCalls = new int[numBackwardsCallables]; + System.arraycopy(classAttrCalls, backwardsCallIndex, + backwardsCalls, 0, numBackwardsCallables); + bands.setBackwardsCalls(backwardsCalls); + backwardsCallIndex += numBackwardsCallables; + } + } + } + + // Now process the attribute bands we have parsed + int sourceFileIndex = 0; + int enclosingMethodIndex = 0; + int signatureIndex = 0; + int innerClassIndex = 0; + int innerClassC2NIndex = 0; + int versionIndex = 0; + icLocal = new IcTuple[classCount][]; + for (int i = 0; i < classCount; i++) { + long flag = classFlags[i]; + if (deprecatedLayout.matches(classFlags[i])) { + classAttributes[i].add(new DeprecatedAttribute()); + } + if (sourceFileLayout.matches(flag)) { + long result = classSourceFile[sourceFileIndex]; + ClassFileEntry value = sourceFileLayout.getValue(result, + cpBands.getConstantPool()); + if (value == null) { + // Remove package prefix + String className = classThis[i].substring(classThis[i] + .lastIndexOf('/') + 1); + className = className + .substring(className.lastIndexOf('.') + 1); + + // Remove mangled nested class names + char[] chars = className.toCharArray(); + int index = -1; + for (int j = 0; j < chars.length; j++) { + if (chars[j] <= 0x2D) { + index = j; + break; + } + } + if (index > -1) { + className = className.substring(0, index); + } + // Add .java to the end + value = cpBands.cpUTF8Value(className + ".java", true); + } + classAttributes[i].add(new SourceFileAttribute((CPUTF8)value)); + sourceFileIndex++; + } + if (enclosingMethodLayout.matches(flag)) { + CPClass theClass = cpBands + .cpClassValue(enclosingMethodRC[enclosingMethodIndex]); + CPNameAndType theMethod = null; + if(enclosingMethodRDN[enclosingMethodIndex] != 0) { + theMethod = cpBands + .cpNameAndTypeValue(enclosingMethodRDN[enclosingMethodIndex] - 1); + } + classAttributes[i].add(new EnclosingMethodAttribute(theClass, + theMethod)); + enclosingMethodIndex++; + } + if (signatureLayout.matches(flag)) { + long result = classSignature[signatureIndex]; + CPUTF8 value = (CPUTF8) signatureLayout.getValue(result, cpBands + .getConstantPool()); + classAttributes[i].add(new SignatureAttribute(value)); + signatureIndex++; + } + if (innerClassLayout.matches(flag)) { + // Just create the tuples for now because the attributes are + // decided at the end when creating class constant pools + icLocal[i] = new IcTuple[classInnerClassesN[innerClassIndex]]; + for (int j = 0; j < icLocal[i].length; j++) { + int icTupleCIndex = classInnerClassesRC[innerClassIndex][j]; + int icTupleC2Index = -1; + int icTupleNIndex = -1; + + String icTupleC = cpClass[icTupleCIndex]; + int icTupleF = classInnerClassesF[innerClassIndex][j]; + String icTupleC2 = null; + String icTupleN = null; + + if (icTupleF != 0) { + icTupleC2Index = classInnerClassesOuterRCN[innerClassC2NIndex]; + icTupleNIndex = classInnerClassesNameRUN[innerClassC2NIndex]; + icTupleC2 = cpClass[icTupleC2Index]; + icTupleN = cpUTF8[icTupleNIndex]; + innerClassC2NIndex++; + } else { + // Get from icBands + IcBands icBands = segment.getIcBands(); + IcTuple[] icAll = icBands.getIcTuples(); + for (int k = 0; k < icAll.length; k++) { + if (icAll[k].getC().equals(icTupleC)) { + icTupleF = icAll[k].getF(); + icTupleC2 = icAll[k].getC2(); + icTupleN = icAll[k].getN(); + break; + } + } + } + + IcTuple icTuple = new IcTuple(icTupleC, icTupleF, + icTupleC2, icTupleN, icTupleCIndex, icTupleC2Index, icTupleNIndex, j); + icLocal[i][j] = icTuple; + } + innerClassIndex++; + } + if (versionLayout.matches(flag)) { + classVersionMajor[i] = classFileVersionMajorH[versionIndex]; + classVersionMinor[i] = classFileVersionMinorH[versionIndex]; + versionIndex++; + } else if (classVersionMajor != null) { + // Fill in with defaults + classVersionMajor[i] = defaultVersionMajor; + classVersionMinor[i] = defaultVersionMinor; + } + // Non-predefined attributes + for (int j = 0; j < otherLayouts.length; j++) { + if (otherLayouts[j] != null && otherLayouts[j].matches(flag)) { + // Add the next attribute + classAttributes[i].add(otherAttributes[j].get(0)); + otherAttributes[j].remove(0); + } + } + } + } + + private void parseCodeBands(InputStream in) throws Pack200Exception, + IOException { + AttributeLayout layout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_CODE, AttributeLayout.CONTEXT_METHOD); + + int codeCount = SegmentUtils.countMatches(methodFlags, layout); + int[] codeHeaders = decodeBandInt("code_headers", in, Codec.BYTE1, + codeCount); + + boolean allCodeHasFlags = segment.getSegmentHeader().getOptions().hasAllCodeFlags(); + if(!allCodeHasFlags) { + codeHasAttributes = new boolean[codeCount]; + } + int codeSpecialHeader = 0; + for (int i = 0; i < codeCount; i++) { + if (codeHeaders[i] == 0) { + codeSpecialHeader++; + if(!allCodeHasFlags) { + codeHasAttributes[i] = true; + } + } + } + int[] codeMaxStackSpecials = decodeBandInt("code_max_stack", in, + Codec.UNSIGNED5, codeSpecialHeader); + int[] codeMaxNALocalsSpecials = decodeBandInt("code_max_na_locals", in, + Codec.UNSIGNED5, codeSpecialHeader); + int[] codeHandlerCountSpecials = decodeBandInt("code_handler_count", + in, Codec.UNSIGNED5, codeSpecialHeader); + + codeMaxStack = new int[codeCount]; + codeMaxNALocals = new int[codeCount]; + codeHandlerCount = new int[codeCount]; + int special = 0; + for (int i = 0; i < codeCount; i++) { + int header = 0xff & codeHeaders[i]; + if (header < 0) { + throw new IllegalStateException("Shouldn't get here"); + } else if (header == 0) { + codeMaxStack[i] = codeMaxStackSpecials[special]; + codeMaxNALocals[i] = codeMaxNALocalsSpecials[special]; + codeHandlerCount[i] = codeHandlerCountSpecials[special]; + special++; + } else if (header <= 144) { + codeMaxStack[i] = (header - 1) % 12; + codeMaxNALocals[i] = (header - 1) / 12; + codeHandlerCount[i] = 0; + } else if (header <= 208) { + codeMaxStack[i] = (header - 145) % 8; + codeMaxNALocals[i] = (header - 145) / 8; + codeHandlerCount[i] = 1; + } else if (header <= 255) { + codeMaxStack[i] = (header - 209) % 7; + codeMaxNALocals[i] = (header - 209) / 7; + codeHandlerCount[i] = 2; + } else { + throw new IllegalStateException("Shouldn't get here either"); + } + } + codeHandlerStartP = decodeBandInt("code_handler_start_P", in, + Codec.BCI5, codeHandlerCount); + codeHandlerEndPO = decodeBandInt("code_handler_end_PO", in, + Codec.BRANCH5, codeHandlerCount); + codeHandlerCatchPO = decodeBandInt("code_handler_catch_PO", in, + Codec.BRANCH5, codeHandlerCount); + codeHandlerClassRCN = decodeBandInt( + "code_handler_class_RCN", in, Codec.UNSIGNED5, codeHandlerCount); + + int codeFlagsCount = allCodeHasFlags ? codeCount : codeSpecialHeader; + + codeAttributes = new List[codeFlagsCount]; + for (int i = 0; i < codeAttributes.length; i++) { + codeAttributes[i] = new ArrayList(); + } + parseCodeAttrBands(in, codeFlagsCount); + } + + private void parseCodeAttrBands(InputStream in, int codeFlagsCount) + throws IOException, Pack200Exception { + long[] codeFlags = parseFlags("code_flags", in, codeFlagsCount, + Codec.UNSIGNED5, segment.getSegmentHeader().getOptions() + .hasCodeFlagsHi()); + int codeAttrCount = SegmentUtils.countBit16(codeFlags); + int[] codeAttrCounts = decodeBandInt("code_attr_count", in, + Codec.UNSIGNED5, codeAttrCount); + int[][] codeAttrIndexes = decodeBandInt("code_attr_indexes", in, + Codec.UNSIGNED5, codeAttrCounts); + int callCount = 0; + for (int i = 0; i < codeAttrIndexes.length; i++) { + for (int j = 0; j < codeAttrIndexes[i].length; j++) { + int index = codeAttrIndexes[i][j]; + AttributeLayout layout = attrMap.getAttributeLayout(index, + AttributeLayout.CONTEXT_CODE); + callCount += layout.numBackwardsCallables(); + } + } + int[] codeAttrCalls = decodeBandInt("code_attr_calls", in, + Codec.UNSIGNED5, callCount); + + AttributeLayout lineNumberTableLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_LINE_NUMBER_TABLE, + AttributeLayout.CONTEXT_CODE); + int lineNumberTableCount = SegmentUtils.countMatches(codeFlags, + lineNumberTableLayout); + int[] lineNumberTableN = decodeBandInt("code_LineNumberTable_N", in, + Codec.UNSIGNED5, lineNumberTableCount); + int[][] lineNumberTableBciP = decodeBandInt( + "code_LineNumberTable_bci_P", in, Codec.BCI5, lineNumberTableN); + int[][] lineNumberTableLine = decodeBandInt( + "code_LineNumberTable_line", in, Codec.UNSIGNED5, + lineNumberTableN); + + AttributeLayout localVariableTableLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_LOCAL_VARIABLE_TABLE, + AttributeLayout.CONTEXT_CODE); + AttributeLayout localVariableTypeTableLayout = attrMap + .getAttributeLayout( + AttributeLayout.ATTRIBUTE_LOCAL_VARIABLE_TYPE_TABLE, + AttributeLayout.CONTEXT_CODE); + + int lengthLocalVariableNBand = SegmentUtils.countMatches(codeFlags, + localVariableTableLayout); + int[] localVariableTableN = decodeBandInt("code_LocalVariableTable_N", + in, Codec.UNSIGNED5, lengthLocalVariableNBand); + int[][] localVariableTableBciP = decodeBandInt( + "code_LocalVariableTable_bci_P", in, Codec.BCI5, + localVariableTableN); + int[][] localVariableTableSpanO = decodeBandInt( + "code_LocalVariableTable_span_O", in, Codec.BRANCH5, + localVariableTableN); + CPUTF8[][] localVariableTableNameRU = parseCPUTF8References( + "code_LocalVariableTable_name_RU", in, Codec.UNSIGNED5, + localVariableTableN); + CPUTF8[][] localVariableTableTypeRS = parseCPSignatureReferences( + "code_LocalVariableTable_type_RS", in, Codec.UNSIGNED5, + localVariableTableN); + int[][] localVariableTableSlot = decodeBandInt( + "code_LocalVariableTable_slot", in, Codec.UNSIGNED5, + localVariableTableN); + + int lengthLocalVariableTypeTableNBand = SegmentUtils.countMatches( + codeFlags, localVariableTypeTableLayout); + int[] localVariableTypeTableN = decodeBandInt( + "code_LocalVariableTypeTable_N", in, Codec.UNSIGNED5, + lengthLocalVariableTypeTableNBand); + int[][] localVariableTypeTableBciP = decodeBandInt( + "code_LocalVariableTypeTable_bci_P", in, Codec.BCI5, + localVariableTypeTableN); + int[][] localVariableTypeTableSpanO = decodeBandInt( + "code_LocalVariableTypeTable_span_O", in, Codec.BRANCH5, + localVariableTypeTableN); + CPUTF8[][] localVariableTypeTableNameRU = parseCPUTF8References( + "code_LocalVariableTypeTable_name_RU", in, Codec.UNSIGNED5, + localVariableTypeTableN); + CPUTF8[][] localVariableTypeTableTypeRS = parseCPSignatureReferences( + "code_LocalVariableTypeTable_type_RS", in, Codec.UNSIGNED5, + localVariableTypeTableN); + int[][] localVariableTypeTableSlot = decodeBandInt( + "code_LocalVariableTypeTable_slot", in, Codec.UNSIGNED5, + localVariableTypeTableN); + + // Parse non-predefined attribute bands + int backwardsCallIndex = 0; + int limit = options.hasCodeFlagsHi() ? 62 : 31; + AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1]; + int[] counts = new int[limit + 1]; + List[] otherAttributes = new List[limit + 1]; + for (int i = 0; i < limit; i++) { + AttributeLayout layout = attrMap.getAttributeLayout(i, + AttributeLayout.CONTEXT_CODE); + if (layout != null && !(layout.isDefaultLayout())) { + otherLayouts[i] = layout; + counts[i] = SegmentUtils.countMatches(codeFlags, layout); + } + } + for (int i = 0; i < counts.length; i++) { + if (counts[i] > 0) { + NewAttributeBands bands = attrMap + .getAttributeBands(otherLayouts[i]); + otherAttributes[i] = bands.parseAttributes(in, counts[i]); + int numBackwardsCallables = otherLayouts[i] + .numBackwardsCallables(); + if (numBackwardsCallables > 0) { + int[] backwardsCalls = new int[numBackwardsCallables]; + System.arraycopy(codeAttrCalls, backwardsCallIndex, + backwardsCalls, 0, numBackwardsCallables); + bands.setBackwardsCalls(backwardsCalls); + backwardsCallIndex += numBackwardsCallables; + } + } + } + + int lineNumberIndex = 0; + int lvtIndex = 0; + int lvttIndex = 0; + for (int i = 0; i < codeFlagsCount; i++) { + if (lineNumberTableLayout.matches(codeFlags[i])) { + LineNumberTableAttribute lnta = new LineNumberTableAttribute( + lineNumberTableN[lineNumberIndex], + lineNumberTableBciP[lineNumberIndex], + lineNumberTableLine[lineNumberIndex]); + lineNumberIndex++; + codeAttributes[i].add(lnta); + } + if (localVariableTableLayout.matches(codeFlags[i])) { + LocalVariableTableAttribute lvta = new LocalVariableTableAttribute( + localVariableTableN[lvtIndex], + localVariableTableBciP[lvtIndex], + localVariableTableSpanO[lvtIndex], + localVariableTableNameRU[lvtIndex], + localVariableTableTypeRS[lvtIndex], + localVariableTableSlot[lvtIndex]); + lvtIndex++; + codeAttributes[i].add(lvta); + } + if (localVariableTypeTableLayout.matches(codeFlags[i])) { + LocalVariableTypeTableAttribute lvtta = new LocalVariableTypeTableAttribute( + localVariableTypeTableN[lvttIndex], + localVariableTypeTableBciP[lvttIndex], + localVariableTypeTableSpanO[lvttIndex], + localVariableTypeTableNameRU[lvttIndex], + localVariableTypeTableTypeRS[lvttIndex], + localVariableTypeTableSlot[lvttIndex]); + lvttIndex++; + codeAttributes[i].add(lvtta); + } + // Non-predefined attributes + for (int j = 0; j < otherLayouts.length; j++) { + if (otherLayouts[j] != null + && otherLayouts[j].matches(codeFlags[i])) { + // Add the next attribute + codeAttributes[i].add(otherAttributes[j].get(0)); + otherAttributes[j].remove(0); + } + } + } + + } + + private int parseFieldMetadataBands(InputStream in, int[] fieldAttrCalls) + throws Pack200Exception, IOException { + int backwardsCallsUsed = 0; + String[] RxA = new String[] { "RVA", "RIA" }; + + AttributeLayout rvaLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_FIELD); + AttributeLayout riaLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_FIELD); + + int rvaCount = SegmentUtils.countMatches(fieldFlags, rvaLayout); + int riaCount = SegmentUtils.countMatches(fieldFlags, riaLayout); + int[] RxACount = new int[] { rvaCount, riaCount }; + int[] backwardsCalls = new int[] { 0, 0 }; + if (rvaCount > 0) { + backwardsCalls[0] = fieldAttrCalls[0]; + backwardsCallsUsed++; + if (riaCount > 0) { + backwardsCalls[1] = fieldAttrCalls[1]; + backwardsCallsUsed++; + } + } else if (riaCount > 0) { + backwardsCalls[1] = fieldAttrCalls[0]; + backwardsCallsUsed++; + } + MetadataBandGroup[] mb = parseMetadata(in, RxA, RxACount, + backwardsCalls, "field"); + List rvaAttributes = mb[0].getAttributes(); + List riaAttributes = mb[1].getAttributes(); + int rvaAttributesIndex = 0; + int riaAttributesIndex = 0; + for (int i = 0; i < fieldFlags.length; i++) { + for (int j = 0; j < fieldFlags[i].length; j++) { + if (rvaLayout.matches(fieldFlags[i][j])) { + fieldAttributes[i][j].add(rvaAttributes.get(rvaAttributesIndex++)); + } + if (riaLayout.matches(fieldFlags[i][j])) { + fieldAttributes[i][j].add(riaAttributes.get(riaAttributesIndex++)); + } + } + } + return backwardsCallsUsed; + } + + private MetadataBandGroup[] parseMetadata(InputStream in, String[] RxA, + int[] RxACount, int[] backwardsCallCounts, String contextName) + throws IOException, Pack200Exception { + MetadataBandGroup[] mbg = new MetadataBandGroup[RxA.length]; + for (int i = 0; i < RxA.length; i++) { + mbg[i] = new MetadataBandGroup(RxA[i], cpBands); + String rxa = RxA[i]; + if (rxa.indexOf('P') >= 0) { + mbg[i].param_NB = decodeBandInt(contextName + "_" + rxa + + "_param_NB", in, Codec.BYTE1, RxACount[i]); + } + int pairCount = 0; + if (!rxa.equals("AD")) { + mbg[i].anno_N = decodeBandInt(contextName + "_" + rxa + + "_anno_N", in, Codec.UNSIGNED5, RxACount[i]); + mbg[i].type_RS = parseCPSignatureReferences(contextName + + "_" + rxa + "_type_RS", in, Codec.UNSIGNED5, + mbg[i].anno_N); + mbg[i].pair_N = decodeBandInt(contextName + "_" + rxa + + "_pair_N", in, Codec.UNSIGNED5, mbg[i].anno_N); + for (int j = 0; j < mbg[i].pair_N.length; j++) { + for (int k = 0; k < mbg[i].pair_N[j].length; k++) { + pairCount += mbg[i].pair_N[j][k]; + } + } + + mbg[i].name_RU = parseCPUTF8References(contextName + + "_" + rxa + "_name_RU", in, Codec.UNSIGNED5, + pairCount); + } + mbg[i].T = decodeBandInt(contextName + "_" + rxa + "_T", in, + Codec.BYTE1, pairCount + backwardsCallCounts[i]); + int ICount = 0, DCount = 0, FCount = 0, JCount = 0, cCount = 0, eCount = 0, sCount = 0, arrayCount = 0, atCount = 0; + for (int j = 0; j < mbg[i].T.length; j++) { + char c = (char) mbg[i].T[j]; + switch (c) { + case 'B': + case 'C': + case 'I': + case 'S': + case 'Z': + ICount++; + break; + case 'D': + DCount++; + break; + case 'F': + FCount++; + break; + case 'J': + JCount++; + break; + case 'c': + cCount++; + break; + case 'e': + eCount++; + break; + case 's': + sCount++; + break; + case '[': + arrayCount++; + break; + case '@': + atCount++; + break; + } + } + mbg[i].caseI_KI = parseCPIntReferences(contextName + "_" + rxa + + "_caseI_KI", in, Codec.UNSIGNED5, ICount); + mbg[i].caseD_KD = parseCPDoubleReferences(contextName + "_" + rxa + + "_caseD_KD", in, Codec.UNSIGNED5, DCount); + mbg[i].caseF_KF = parseCPFloatReferences(contextName + "_" + rxa + + "_caseF_KF", in, Codec.UNSIGNED5, FCount); + mbg[i].caseJ_KJ = parseCPLongReferences(contextName + "_" + rxa + + "_caseJ_KJ", in, Codec.UNSIGNED5, JCount); + mbg[i].casec_RS = parseCPUTF8References(contextName + "_" + rxa + + "_casec_RS", in, Codec.UNSIGNED5, cCount); + mbg[i].caseet_RS = parseReferences(contextName + "_" + rxa + + "_caseet_RS", in, Codec.UNSIGNED5, eCount, cpBands + .getCpSignature()); + mbg[i].caseec_RU = parseReferences(contextName + "_" + rxa + + "_caseec_RU", in, Codec.UNSIGNED5, eCount, cpBands + .getCpUTF8()); + mbg[i].cases_RU = parseCPUTF8References(contextName + "_" + rxa + + "_cases_RU", in, Codec.UNSIGNED5, sCount); + mbg[i].casearray_N = decodeBandInt(contextName + "_" + rxa + + "_casearray_N", in, Codec.UNSIGNED5, arrayCount); + mbg[i].nesttype_RS = parseCPUTF8References(contextName + "_" + rxa + + "_nesttype_RS", in, Codec.UNSIGNED5, atCount); + mbg[i].nestpair_N = decodeBandInt(contextName + "_" + rxa + + "_nestpair_N", in, Codec.UNSIGNED5, atCount); + int nestPairCount = 0; + for (int j = 0; j < mbg[i].nestpair_N.length; j++) { + nestPairCount += mbg[i].nestpair_N[j]; + } + mbg[i].nestname_RU = parseCPUTF8References(contextName + "_" + rxa + + "_nestname_RU", in, Codec.UNSIGNED5, nestPairCount); + } + return mbg; + } + + private int parseMethodMetadataBands(InputStream in, int[] methodAttrCalls) + throws Pack200Exception, IOException { + int backwardsCallsUsed = 0; + String[] RxA = new String[] { "RVA", "RIA", "RVPA", "RIPA", "AD" }; + int[] rxaCounts = new int[] { 0, 0, 0, 0, 0 }; + int[] backwardsCalls = new int[5]; + int methodAttrIndex = 0; + for (int i = 0; i < backwardsCalls.length; i++) { + if (rxaCounts[i] > 0) { + backwardsCallsUsed++; + backwardsCalls[i] = methodAttrCalls[methodAttrIndex]; + methodAttrIndex++; + } else { + backwardsCalls[i] = 0; + } + } + AttributeLayout rvaLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_METHOD); + AttributeLayout riaLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_METHOD); + AttributeLayout rvpaLayout = attrMap + .getAttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, + AttributeLayout.CONTEXT_METHOD); + AttributeLayout ripaLayout = attrMap + .getAttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, + AttributeLayout.CONTEXT_METHOD); + AttributeLayout adLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_ANNOTATION_DEFAULT, + AttributeLayout.CONTEXT_METHOD); + AttributeLayout[] rxaLayouts = new AttributeLayout[] { rvaLayout, + riaLayout, rvpaLayout, ripaLayout, adLayout }; + + for (int i = 0; i < rxaLayouts.length; i++) { + rxaCounts[i] = SegmentUtils + .countMatches(methodFlags, rxaLayouts[i]); + } + MetadataBandGroup[] mbgs = parseMetadata(in, RxA, rxaCounts, + backwardsCalls, "method"); + List[] attributeLists = new List[RxA.length]; + int[] attributeListIndexes = new int[RxA.length]; + for (int i = 0; i < mbgs.length; i++) { + attributeLists[i] = mbgs[i].getAttributes(); + attributeListIndexes[i] = 0; + } + for (int i = 0; i < methodFlags.length; i++) { + for (int j = 0; j < methodFlags[i].length; j++) { + for (int k = 0; k < rxaLayouts.length; k++) { + if (rxaLayouts[k].matches(methodFlags[i][j])) { + methodAttributes[i][j] + .add(attributeLists[k].get(attributeListIndexes[k]++)); + } + } + } + } + return backwardsCallsUsed; + } + + /** + * Parse the class metadata bands and return the number of backwards + * callables + * + * @param in + * @param classAttrCalls + * @return + * @throws Pack200Exception + * @throws IOException + */ + private int parseClassMetadataBands(InputStream in, int[] classAttrCalls) + throws Pack200Exception, IOException { + int numBackwardsCalls = 0; + String[] RxA = new String[] { "RVA", "RIA" }; + + AttributeLayout rvaLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_CLASS); + AttributeLayout riaLayout = attrMap.getAttributeLayout( + AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS, + AttributeLayout.CONTEXT_CLASS); + int rvaCount = SegmentUtils.countMatches(classFlags, rvaLayout); + int riaCount = SegmentUtils.countMatches(classFlags, riaLayout); + int[] RxACount = new int[] { rvaCount, riaCount }; + int[] backwardsCalls = new int[] { 0, 0 }; + if (rvaCount > 0) { + numBackwardsCalls++; + backwardsCalls[0] = classAttrCalls[0]; + if (riaCount > 0) { + numBackwardsCalls++; + backwardsCalls[1] = classAttrCalls[1]; + } + } else if (riaCount > 0) { + numBackwardsCalls++; + backwardsCalls[1] = classAttrCalls[0]; + } + MetadataBandGroup[] mbgs = parseMetadata(in, RxA, RxACount, + backwardsCalls, "class"); + List rvaAttributes = mbgs[0].getAttributes(); + List riaAttributes = mbgs[1].getAttributes(); + int rvaAttributesIndex = 0; + int riaAttributesIndex = 0; + for (int i = 0; i < classFlags.length; i++) { + if (rvaLayout.matches(classFlags[i])) { + classAttributes[i].add(rvaAttributes.get(rvaAttributesIndex++)); + } + if (riaLayout.matches(classFlags[i])) { + classAttributes[i].add(riaAttributes.get(riaAttributesIndex++)); + } + } + return numBackwardsCalls; + } + + public ArrayList[] getClassAttributes() { + return classAttributes; + } + + public int[] getClassFieldCount() { + return classFieldCount; + } + + public long[] getRawClassFlags() { + return classFlags; + } + + public long[] getClassFlags() throws Pack200Exception { + if (classAccessFlags == null) { + long mask = 0x7FFF; + for (int i = 0; i < 16; i++) { + AttributeLayout layout = attrMap.getAttributeLayout(i, + AttributeLayout.CONTEXT_CLASS); + if (layout != null && !layout.isDefaultLayout()) { + mask &= ~(1 << i); + } + } + classAccessFlags = new long[classFlags.length]; + for (int i = 0; i < classFlags.length; i++) { + classAccessFlags[i] = classFlags[i] & mask; + } + } + return classAccessFlags; + } + + public int[][] getClassInterfacesInts() { + return classInterfacesInts; + } + + public int[] getClassMethodCount() { + return classMethodCount; + } + + public int[] getClassSuperInts() { + return classSuperInts; + } + + public int[] getClassThisInts() { + return classThisInts; + } + + public int[] getCodeMaxNALocals() { + return codeMaxNALocals; + } + + public int[] getCodeMaxStack() { + return codeMaxStack; + } + + public ArrayList[][] getFieldAttributes() { + return fieldAttributes; + } + + public int[][] getFieldDescrInts() { + return fieldDescrInts; + } + + public int[][] getMethodDescrInts() { + return methodDescrInts; + } + + public long[][] getFieldFlags() throws Pack200Exception { + if (fieldAccessFlags == null) { + long mask = 0x7FFF; + for (int i = 0; i < 16; i++) { + AttributeLayout layout = attrMap.getAttributeLayout(i, + AttributeLayout.CONTEXT_FIELD); + if (layout != null && !layout.isDefaultLayout()) { + mask &= ~(1 << i); + } + } + fieldAccessFlags = new long[fieldFlags.length][]; + for (int i = 0; i < fieldFlags.length; i++) { + fieldAccessFlags[i] = new long[fieldFlags[i].length]; + for (int j = 0; j < fieldFlags[i].length; j++) { + fieldAccessFlags[i][j] = fieldFlags[i][j] & mask; + } + } + } + return fieldAccessFlags; + } + + /** + * Answer an ArrayList of ArrayLists which hold the the code attributes + * corresponding to all classes in order. + * + * If a class doesn't have any attributes, the corresponding element in this + * list will be an empty ArrayList. + * + * @return ArrayList + */ + public ArrayList getOrderedCodeAttributes() { + ArrayList orderedAttributeList = new ArrayList(codeAttributes.length); + for (int classIndex = 0; classIndex < codeAttributes.length; classIndex++) { + ArrayList currentAttributes = new ArrayList(codeAttributes[classIndex].size()); + for (int attributeIndex = 0; attributeIndex < codeAttributes[classIndex] + .size(); attributeIndex++) { + Attribute attribute = (Attribute) codeAttributes[classIndex] + .get(attributeIndex); + currentAttributes.add(attribute); + } + orderedAttributeList.add(currentAttributes); + } + return orderedAttributeList; + } + + public ArrayList[][] getMethodAttributes() { + return methodAttributes; + } + + public String[][] getMethodDescr() { + return methodDescr; + } + + public long[][] getMethodFlags() throws Pack200Exception { + if (methodAccessFlags == null) { + long mask = 0x7FFF; + for (int i = 0; i < 16; i++) { + AttributeLayout layout = attrMap.getAttributeLayout(i, + AttributeLayout.CONTEXT_METHOD); + if (layout != null && !layout.isDefaultLayout()) { + mask &= ~(1 << i); + } + } + methodAccessFlags = new long[methodFlags.length][]; + for (int i = 0; i < methodFlags.length; i++) { + methodAccessFlags[i] = new long[methodFlags[i].length]; + for (int j = 0; j < methodFlags[i].length; j++) { + methodAccessFlags[i][j] = methodFlags[i][j] & mask; + } + } + } + return methodAccessFlags; + } + + /** + * Returns null if all classes should use the default major and minor + * version or an array of integers containing the major version numberss to + * use for each class in the segment + * + * @return Class file major version numbers, or null if none specified + */ + public int[] getClassVersionMajor() { + return classVersionMajor; + } + + /** + * Returns null if all classes should use the default major and minor + * version or an array of integers containing the minor version numberss to + * use for each class in the segment + * + * @return Class file minor version numbers, or null if none specified + */ + public int[] getClassVersionMinor() { + return classVersionMinor; + } + + public int[] getCodeHandlerCount() { + return codeHandlerCount; + } + + public int[][] getCodeHandlerCatchPO() { + return codeHandlerCatchPO; + } + + public int[][] getCodeHandlerClassRCN() { + return codeHandlerClassRCN; + } + + public int[][] getCodeHandlerEndPO() { + return codeHandlerEndPO; + } + + public int[][] getCodeHandlerStartP() { + return codeHandlerStartP; + } + + public IcTuple[][] getIcLocal() { + return icLocal; + } + + public boolean[] getCodeHasAttributes() { + return codeHasAttributes; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/CpBands.java b/src/main/java/org/apache/harmony/unpack200/CpBands.java new file mode 100644 index 0000000..b5b6918 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/CpBands.java @@ -0,0 +1,724 @@ +/* + * 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.unpack200; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.bytecode.CPClass; +import org.apache.harmony.unpack200.bytecode.CPDouble; +import org.apache.harmony.unpack200.bytecode.CPFieldRef; +import org.apache.harmony.unpack200.bytecode.CPFloat; +import org.apache.harmony.unpack200.bytecode.CPInteger; +import org.apache.harmony.unpack200.bytecode.CPInterfaceMethodRef; +import org.apache.harmony.unpack200.bytecode.CPLong; +import org.apache.harmony.unpack200.bytecode.CPMethodRef; +import org.apache.harmony.unpack200.bytecode.CPNameAndType; +import org.apache.harmony.unpack200.bytecode.CPString; +import org.apache.harmony.unpack200.bytecode.CPUTF8; + +/** + * Constant Pool bands + */ +public class CpBands extends BandSet { + + public SegmentConstantPool getConstantPool() { + return pool; + } + + private final SegmentConstantPool pool = new SegmentConstantPool(this); + + private String[] cpClass; + private int[] cpClassInts; + private int[] cpDescriptorNameInts; + private int[] cpDescriptorTypeInts; + private String[] cpDescriptor; + private double[] cpDouble; + private String[] cpFieldClass; + private String[] cpFieldDescriptor; + private int[] cpFieldClassInts; + private int[] cpFieldDescriptorInts; + private float[] cpFloat; + private String[] cpIMethodClass; + private String[] cpIMethodDescriptor; + private int[] cpIMethodClassInts; + private int[] cpIMethodDescriptorInts; + private int[] cpInt; + private long[] cpLong; + private String[] cpMethodClass; + private String[] cpMethodDescriptor; + private int[] cpMethodClassInts; + private int[] cpMethodDescriptorInts; + private String[] cpSignature; + private int[] cpSignatureInts; + private String[] cpString; + private int[] cpStringInts; + private String[] cpUTF8; + + private final Map stringsToCPUTF8 = new HashMap(); + private final Map stringsToCPStrings = new HashMap(); + private final Map longsToCPLongs = new HashMap(); + private final Map integersToCPIntegers = new HashMap(); + private final Map floatsToCPFloats = new HashMap(); + private final Map stringsToCPClass = new HashMap(); + private final Map doublesToCPDoubles = new HashMap(); + private final Map descriptorsToCPNameAndTypes = new HashMap(); + + private Map mapClass; + private Map mapDescriptor; + private Map mapUTF8; + +// TODO: Not used +// private Map mapSignature; + + private int intOffset; + private int floatOffset; + private int longOffset; + private int doubleOffset; + private int stringOffset; + private int classOffset; + private int signatureOffset; + private int descrOffset; + private int fieldOffset; + private int methodOffset; + private int imethodOffset; + + public CpBands(Segment segment) { + super(segment); + } + + public void read(InputStream in) throws IOException, Pack200Exception { + parseCpUtf8(in); + parseCpInt(in); + parseCpFloat(in); + parseCpLong(in); + parseCpDouble(in); + parseCpString(in); + parseCpClass(in); + parseCpSignature(in); + parseCpDescriptor(in); + parseCpField(in); + parseCpMethod(in); + parseCpIMethod(in); + + intOffset = cpUTF8.length; + floatOffset = intOffset + cpInt.length; + longOffset = floatOffset + cpFloat.length; + doubleOffset = longOffset + cpLong.length; + stringOffset = doubleOffset + cpDouble.length; + classOffset = stringOffset + cpString.length; + signatureOffset = classOffset + cpClass.length; + descrOffset = signatureOffset + cpSignature.length; + fieldOffset = descrOffset + cpDescriptor.length; + methodOffset = fieldOffset + cpFieldClass.length; + imethodOffset = methodOffset + cpMethodClass.length; + } + + public void unpack() { + + } + + /** + * Parses the constant pool class names, using {@link #cpClassCount} to + * populate {@link #cpClass} from {@link #cpUTF8}. + * + * @param in + * the input stream to read from + * @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 void parseCpClass(InputStream in) throws IOException, + Pack200Exception { + int cpClassCount = header.getCpClassCount(); + cpClassInts = decodeBandInt("cp_Class", in, Codec.UDELTA5, cpClassCount); + cpClass = new String[cpClassCount]; + mapClass = new HashMap(cpClassCount); + for (int i = 0; i < cpClassCount; i++) { + cpClass[i] = cpUTF8[cpClassInts[i]]; + mapClass.put(cpClass[i], new Integer(i)); + } + } + + /** + * Parses the constant pool descriptor definitions, using + * {@link #cpDescriptorCount} to populate {@link #cpDescriptor}. For ease + * of use, the cpDescriptor is stored as a string of the form name:type, + * largely to make it easier for representing field and method descriptors + * (e.g. out:java.lang.PrintStream) in a way that is + * compatible with passing String arrays. + * + * @param in + * the input stream to read from + * @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 void parseCpDescriptor(InputStream in) throws IOException, + Pack200Exception { + int cpDescriptorCount = header.getCpDescriptorCount(); + cpDescriptorNameInts = decodeBandInt("cp_Descr_name", in, Codec.DELTA5, + cpDescriptorCount); + cpDescriptorTypeInts = decodeBandInt("cp_Descr_type", in, + Codec.UDELTA5, cpDescriptorCount); + String[] cpDescriptorNames = getReferences(cpDescriptorNameInts, cpUTF8); + String[] cpDescriptorTypes = getReferences(cpDescriptorTypeInts, + cpSignature); + cpDescriptor = new String[cpDescriptorCount]; + mapDescriptor = new HashMap(cpDescriptorCount); + for (int i = 0; i < cpDescriptorCount; i++) { + cpDescriptor[i] = cpDescriptorNames[i] + ":" + cpDescriptorTypes[i]; //$NON-NLS-1$ + mapDescriptor.put(cpDescriptor[i], new Integer(i)); + } + } + + private void parseCpDouble(InputStream in) throws IOException, + Pack200Exception { + int cpDoubleCount = header.getCpDoubleCount(); + long[] band = parseFlags("cp_Double", in, cpDoubleCount, Codec.UDELTA5, + Codec.DELTA5); + cpDouble = new double[band.length]; + for (int i = 0; i < band.length; i++) { + cpDouble[i] = Double.longBitsToDouble(band[i]); + } + } + + /** + * Parses the constant pool field definitions, using {@link #cpFieldCount} + * to populate {@link #cpFieldClass} and {@link #cpFieldDescriptor}. + * + * @param in + * the input stream to read from + * @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 void parseCpField(InputStream in) throws IOException, + Pack200Exception { + int cpFieldCount = header.getCpFieldCount(); + cpFieldClassInts = decodeBandInt("cp_Field_class", in, Codec.DELTA5, + cpFieldCount); + cpFieldDescriptorInts = decodeBandInt("cp_Field_desc", in, Codec.UDELTA5, + cpFieldCount); + cpFieldClass = new String[cpFieldCount]; + cpFieldDescriptor = new String[cpFieldCount]; + for (int i = 0; i < cpFieldCount; i++) { + cpFieldClass[i] = cpClass[cpFieldClassInts[i]]; + cpFieldDescriptor[i] = cpDescriptor[cpFieldDescriptorInts[i]]; + } + } + + private void parseCpFloat(InputStream in) throws IOException, + Pack200Exception { + int cpFloatCount = header.getCpFloatCount(); + cpFloat = new float[cpFloatCount]; + int floatBits[] = decodeBandInt("cp_Float", in, Codec.UDELTA5, + cpFloatCount); + for (int i = 0; i < cpFloatCount; i++) { + cpFloat[i] = Float.intBitsToFloat(floatBits[i]); + } + } + + /** + * Parses the constant pool interface method definitions, using + * {@link #cpIMethodCount} to populate {@link #cpIMethodClass} and + * {@link #cpIMethodDescriptor}. + * + * @param in + * the input stream to read from + * @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 void parseCpIMethod(InputStream in) throws IOException, + Pack200Exception { + int cpIMethodCount = header.getCpIMethodCount(); + cpIMethodClassInts = decodeBandInt("cp_Imethod_class", in, Codec.DELTA5, + cpIMethodCount); + cpIMethodDescriptorInts = decodeBandInt("cp_Imethod_desc", in, + Codec.UDELTA5, cpIMethodCount); + cpIMethodClass = new String[cpIMethodCount]; + cpIMethodDescriptor = new String[cpIMethodCount]; + for (int i = 0; i < cpIMethodCount; i++) { + cpIMethodClass[i] = cpClass[cpIMethodClassInts[i]]; + cpIMethodDescriptor[i] = cpDescriptor[cpIMethodDescriptorInts[i]]; + } + } + + private void parseCpInt(InputStream in) throws IOException, + Pack200Exception { + int cpIntCount = header.getCpIntCount(); + cpInt = decodeBandInt("cpInt", in, Codec.UDELTA5, cpIntCount); + } + + private void parseCpLong(InputStream in) throws IOException, + Pack200Exception { + int cpLongCount = header.getCpLongCount(); + cpLong = parseFlags("cp_Long", in, cpLongCount, Codec.UDELTA5, + Codec.DELTA5); + } + + /** + * Parses the constant pool method definitions, using {@link #cpMethodCount} + * to populate {@link #cpMethodClass} and {@link #cpMethodDescriptor}. + * + * @param in + * the input stream to read from + * @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 void parseCpMethod(InputStream in) throws IOException, + Pack200Exception { + int cpMethodCount = header.getCpMethodCount(); + cpMethodClassInts = decodeBandInt("cp_Method_class", in, Codec.DELTA5, + cpMethodCount); + cpMethodDescriptorInts = decodeBandInt("cp_Method_desc", in, + Codec.UDELTA5, cpMethodCount); + cpMethodClass = new String[cpMethodCount]; + cpMethodDescriptor = new String[cpMethodCount]; + for (int i = 0; i < cpMethodCount; i++) { + cpMethodClass[i] = cpClass[cpMethodClassInts[i]]; + cpMethodDescriptor[i] = cpDescriptor[cpMethodDescriptorInts[i]]; + } + } + + /** + * Parses the constant pool signature classes, using + * {@link #cpSignatureCount} to populate {@link #cpSignature}. A signature + * form is akin to the bytecode representation of a class; Z for boolean, I + * for int, [ for array etc. However, although classes are started with L, + * the classname does not follow the form; instead, there is a separate + * array of classes. So an array corresponding to + * public static void main(String args[]) has a form of + * [L(V) and a classes array of + * [java.lang.String]. The {@link #cpSignature} is a string + * representation identical to the bytecode equivalent + * [Ljava/lang/String;(V) TODO Check that the form is as + * above and update other types e.g. J + * + * @param in + * the input stream to read from + * @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 void parseCpSignature(InputStream in) throws IOException, + Pack200Exception { + int cpSignatureCount = header.getCpSignatureCount(); + cpSignatureInts = decodeBandInt("cp_Signature_form", in, + Codec.DELTA5, cpSignatureCount); + String[] cpSignatureForm = getReferences(cpSignatureInts, cpUTF8); + cpSignature = new String[cpSignatureCount]; + int lCount = 0; + for (int i = 0; i < cpSignatureCount; i++) { + String form = cpSignatureForm[i]; + char[] chars = form.toCharArray(); + for (int j = 0; j < chars.length; j++) { + if (chars[j] == 'L') { + cpSignatureInts[i] = -1; + lCount++; + } + } + } + String[] cpSignatureClasses = parseReferences("cp_Signature_classes", + in, Codec.UDELTA5, lCount, cpClass); + int index = 0; + for (int i = 0; i < cpSignatureCount; i++) { + String form = cpSignatureForm[i]; + int len = form.length(); + StringBuffer signature = new StringBuffer(64); + ArrayList list = new ArrayList(); + for (int j = 0; j < len; j++) { + char c = form.charAt(j); + signature.append(c); + if (c == 'L') { + String className = cpSignatureClasses[index]; + list.add(className); + signature.append(className); + index++; + } + } + cpSignature[i] = signature.toString(); + } +// for (int i = 0; i < cpSignatureInts.length; i++) { +// if(cpSignatureInts[i] == -1) { +// cpSignatureInts[i] = search(cpUTF8, cpSignature[i]); +// } +// } + } + + /** + * Parses the constant pool strings, using {@link #cpStringCount} to + * populate {@link #cpString} from indexes into {@link #cpUTF8}. + * + * @param in + * the input stream to read from + * @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 void parseCpString(InputStream in) throws IOException, + Pack200Exception { + int cpStringCount = header.getCpStringCount(); + cpStringInts = decodeBandInt("cp_String", in, Codec.UDELTA5, + cpStringCount); + cpString = new String[cpStringCount]; + for (int i = 0; i < cpStringCount; i++) { + cpString[i] = cpUTF8[cpStringInts[i]]; + } + } + + private void parseCpUtf8(InputStream in) throws IOException, + Pack200Exception { + int cpUTF8Count = header.getCpUTF8Count(); + cpUTF8 = new String[cpUTF8Count]; + mapUTF8 = new HashMap(cpUTF8Count+1); + cpUTF8[0] = ""; //$NON-NLS-1$ + mapUTF8.put("", new Integer(0)); + int[] prefix = decodeBandInt("cpUTF8Prefix", in, Codec.DELTA5, + cpUTF8Count - 2); + int charCount = 0; + int bigSuffixCount = 0; + int[] suffix = decodeBandInt("cpUTF8Suffix", in, Codec.UNSIGNED5, + cpUTF8Count - 1); + + for (int i = 0; i < suffix.length; i++) { + if (suffix[i] == 0) { + bigSuffixCount++; + } else { + charCount += suffix[i]; + } + } + char[] data = new char[charCount]; + int[] dataBand = decodeBandInt("cp_Utf8_chars", in, Codec.CHAR3, + charCount); + for (int i = 0; i < data.length; i++) { + data[i] = (char) dataBand[i]; + } + + // Read in the big suffix data + int[] bigSuffixCounts = decodeBandInt("cp_Utf8_big_suffix", in, + Codec.DELTA5, bigSuffixCount); + int[][] bigSuffixDataBand = new int[bigSuffixCount][]; + for (int i = 0; i < bigSuffixDataBand.length; i++) { + bigSuffixDataBand[i] = decodeBandInt("cp_Utf8_big_chars " + i, in, + Codec.DELTA5, bigSuffixCounts[i]); + } + + // Convert big suffix data to characters + char bigSuffixData[][] = new char[bigSuffixCount][]; + for (int i = 0; i < bigSuffixDataBand.length; i++) { + bigSuffixData[i] = new char[bigSuffixDataBand[i].length]; + for (int j = 0; j < bigSuffixDataBand[i].length; j++) { + bigSuffixData[i][j] = (char) bigSuffixDataBand[i][j]; + } + } + // Go through the strings + charCount = 0; + bigSuffixCount = 0; + for (int i = 1; i < cpUTF8Count; i++) { + String lastString = cpUTF8[i - 1]; + if (suffix[i - 1] == 0) { + // The big suffix stuff hasn't been tested, and I'll be + // surprised if it works first time w/o errors ... + cpUTF8[i] = lastString.substring(0, i > 1 ? prefix[i - 2] : 0) + + new String(bigSuffixData[bigSuffixCount++]); + mapUTF8.put(cpUTF8[i], new Integer(i)); + } else { + cpUTF8[i] = lastString.substring(0, i > 1 ? prefix[i - 2] : 0) + + new String(data, charCount, suffix[i - 1]); + charCount += suffix[i - 1]; + mapUTF8.put(cpUTF8[i], new Integer(i)); + } + } + } + + public String[] getCpClass() { + return cpClass; + } + + public String[] getCpDescriptor() { + return cpDescriptor; + } + + public double[] getCpDouble() { + return cpDouble; + } + + public String[] getCpFieldClass() { + return cpFieldClass; + } + + public String[] getCpFieldDescriptor() { + return cpFieldDescriptor; + } + + public float[] getCpFloat() { + return cpFloat; + } + + public String[] getCpIMethodClass() { + return cpIMethodClass; + } + + public String[] getCpIMethodDescriptor() { + return cpIMethodDescriptor; + } + + public int[] getCpInt() { + return cpInt; + } + + public long[] getCpLong() { + return cpLong; + } + + public String[] getCpMethodClass() { + return cpMethodClass; + } + + public String[] getCpMethodDescriptor() { + return cpMethodDescriptor; + } + + public String[] getCpSignature() { + return cpSignature; + } + + public String[] getCpString() { + return cpString; + } + + public String[] getCpUTF8() { + return cpUTF8; + } + + public CPUTF8 cpUTF8Value(int index) { + String string = cpUTF8[index]; + CPUTF8 cputf8 = (CPUTF8) stringsToCPUTF8.get(string); + if (cputf8 == null) { + cputf8 = new CPUTF8(string, index); + stringsToCPUTF8.put(string, cputf8); + } else if(cputf8.getGlobalIndex() > index) { + cputf8.setGlobalIndex(index); + } + return cputf8; + } + + public CPUTF8 cpUTF8Value(String string) { + return cpUTF8Value(string, true); + } + + public CPUTF8 cpUTF8Value(String string, boolean searchForIndex) { + CPUTF8 cputf8 = (CPUTF8) stringsToCPUTF8.get(string); + if (cputf8 == null) { + Integer index = null; + if(searchForIndex) { + index = (Integer)mapUTF8.get(string); + } + if(index != null) { + return cpUTF8Value(index.intValue()); + } + /* TODO: mapSignature is not filled anywhere + if(searchForIndex) { + index = (Integer)mapSignature.get(string); + } + if(index != null) { + return cpSignatureValue(index.intValue()); + }*/ + cputf8 = new CPUTF8(string, -1); + stringsToCPUTF8.put(string, cputf8); + } + return cputf8; + } + + public CPString cpStringValue(int index) { + String string = cpString[index]; + int utf8Index = cpStringInts[index]; + int globalIndex = stringOffset + index; + CPString cpString = (CPString) stringsToCPStrings.get(string); + if (cpString == null) { + cpString = new CPString(cpUTF8Value(utf8Index), globalIndex); + stringsToCPStrings.put(string, cpString); + } + return cpString; + } + + public CPLong cpLongValue(int index) { + Long l = new Long(cpLong[index]); + CPLong cpLong = (CPLong) longsToCPLongs.get(l); + if (cpLong == null) { + cpLong = new CPLong(l, index + longOffset); + longsToCPLongs.put(l, cpLong); + } + return cpLong; + } + + public CPInteger cpIntegerValue(int index) { + Integer i = new Integer(cpInt[index]); + CPInteger cpInteger = (CPInteger) integersToCPIntegers.get(i); + if (cpInteger == null) { + cpInteger = new CPInteger(i, index + intOffset); + integersToCPIntegers.put(i, cpInteger); + } + return cpInteger; + } + + public CPFloat cpFloatValue(int index) { + Float f = new Float(cpFloat[index]); + CPFloat cpFloat = (CPFloat) floatsToCPFloats.get(f); + if (cpFloat == null) { + cpFloat = new CPFloat(f, index + floatOffset); + floatsToCPFloats.put(f, cpFloat); + } + return cpFloat; + } + + public CPClass cpClassValue(int index) { + String string = cpClass[index]; + int utf8Index = cpClassInts[index]; + int globalIndex = classOffset + index; + CPClass cpString = (CPClass) stringsToCPClass.get(string); + if (cpString == null) { + cpString = new CPClass(cpUTF8Value(utf8Index), globalIndex); + stringsToCPClass.put(string, cpString); + } + return cpString; + } + + public CPClass cpClassValue(String string) { + CPClass cpString = (CPClass) stringsToCPClass.get(string); + if (cpString == null) { + Integer index = (Integer)mapClass.get(string); + if(index != null) { + return cpClassValue(index.intValue()); + } + cpString = new CPClass(cpUTF8Value(string, false), -1); + stringsToCPClass.put(string, cpString); + } + return cpString; + } + + public CPDouble cpDoubleValue(int index) { + Double dbl = new Double(cpDouble[index]); + CPDouble cpDouble = (CPDouble) doublesToCPDoubles.get(dbl); + if (cpDouble == null) { + cpDouble = new CPDouble(dbl, index + doubleOffset); + doublesToCPDoubles.put(dbl, cpDouble); + } + return cpDouble; + } + + public CPNameAndType cpNameAndTypeValue(int index) { + String descriptor = cpDescriptor[index]; + CPNameAndType cpNameAndType = (CPNameAndType) descriptorsToCPNameAndTypes + .get(descriptor); + if (cpNameAndType == null) { + int nameIndex = cpDescriptorNameInts[index]; + int descriptorIndex = cpDescriptorTypeInts[index]; + + CPUTF8 name = cpUTF8Value(nameIndex); + CPUTF8 descriptorU = cpSignatureValue(descriptorIndex); + cpNameAndType = new CPNameAndType(name, descriptorU, index + descrOffset); + descriptorsToCPNameAndTypes.put(descriptor, cpNameAndType); + } + return cpNameAndType; + } + + public CPInterfaceMethodRef cpIMethodValue(int index) { + return new CPInterfaceMethodRef(cpClassValue(cpIMethodClassInts[index]), + cpNameAndTypeValue(cpIMethodDescriptorInts[index]), index + + imethodOffset); + } + + public CPMethodRef cpMethodValue(int index) { + return new CPMethodRef(cpClassValue(cpMethodClassInts[index]), + cpNameAndTypeValue(cpMethodDescriptorInts[index]), index + + methodOffset); + } + + public CPFieldRef cpFieldValue(int index) { + return new CPFieldRef(cpClassValue(cpFieldClassInts[index]), + cpNameAndTypeValue(cpFieldDescriptorInts[index]), index + + fieldOffset); + } + + public CPUTF8 cpSignatureValue(int index) { + int globalIndex; + if(cpSignatureInts[index] != -1) { + globalIndex = cpSignatureInts[index]; + } else { + globalIndex = index + signatureOffset; + } + String string = cpSignature[index]; + CPUTF8 cpUTF8 = (CPUTF8) stringsToCPUTF8.get(string); + if(cpUTF8 == null) { + cpUTF8 = new CPUTF8(string, globalIndex); + stringsToCPUTF8.put(string, cpUTF8); + } + return cpUTF8; + } + + public CPNameAndType cpNameAndTypeValue(String descriptor) { + CPNameAndType cpNameAndType = (CPNameAndType) descriptorsToCPNameAndTypes + .get(descriptor); + if (cpNameAndType == null) { + Integer index = (Integer)mapDescriptor.get(descriptor); + if(index != null) { + return cpNameAndTypeValue(index.intValue()); + } + int colon = descriptor.indexOf(':'); + String nameString = descriptor.substring(0, colon); + String descriptorString = descriptor.substring(colon + 1); + + CPUTF8 name = cpUTF8Value(nameString, true); + CPUTF8 descriptorU = cpUTF8Value(descriptorString, true); + cpNameAndType = new CPNameAndType(name, descriptorU, -1 + descrOffset); + descriptorsToCPNameAndTypes.put(descriptor, cpNameAndType); + } + return cpNameAndType; + } + + public int[] getCpDescriptorNameInts() { + return cpDescriptorNameInts; + } + + public int[] getCpDescriptorTypeInts() { + return cpDescriptorTypeInts; + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/FileBands.java b/src/main/java/org/apache/harmony/unpack200/FileBands.java new file mode 100644 index 0000000..ab42efd --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/FileBands.java @@ -0,0 +1,127 @@ +/* + * 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.unpack200; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; + +/** + * Parses the file band headers (not including the actual bits themselves). At + * the end of this parse call, the input stream will be positioned at the start + * of the file_bits themselves, and there will be Sum(file_size) bits remaining + * in the stream with BYTE1 compression. A decent implementation will probably + * just stream the bytes out to the reconstituted Jar rather than caching them. + */ +public class FileBands extends BandSet { + + private byte[][] fileBits; + + private int[] fileModtime; + + private String[] fileName; + + private int[] fileOptions; + + private long[] fileSize; + + private final String[] cpUTF8; + + private InputStream in; + + /** + * @param segment + */ + public FileBands(Segment segment) { + super(segment); + this.cpUTF8 = segment.getCpBands().getCpUTF8(); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.BandSet#unpack(java.io.InputStream) + */ + public void read(InputStream in) throws IOException, Pack200Exception { + int numberOfFiles = header.getNumberOfFiles(); + SegmentOptions options = header.getOptions(); + + fileName = parseReferences("file_name", in, Codec.UNSIGNED5, + numberOfFiles, cpUTF8); + fileSize = parseFlags("file_size", in, numberOfFiles, Codec.UNSIGNED5, + options.hasFileSizeHi()); + if (options.hasFileModtime()) { + fileModtime = decodeBandInt("file_modtime", in, Codec.DELTA5, + numberOfFiles); + } else { + fileModtime = new int[numberOfFiles]; + } + if (options.hasFileOptions()) { + fileOptions = decodeBandInt("file_options", in, Codec.UNSIGNED5, + numberOfFiles); + } else { + fileOptions = new int[numberOfFiles]; + } + this.in = in; // store for use by processFileBits(), which is called + // later + } + + // TODO: stream the file bits directly somehow + public void processFileBits() throws IOException, Pack200Exception { + // now read in the bytes + int numberOfFiles = header.getNumberOfFiles(); + fileBits = new byte[numberOfFiles][]; + for (int i = 0; i < numberOfFiles; i++) { + int size = (int) fileSize[i]; + // TODO This breaks if file_size > 2^32. Probably an array is + // not the right choice, and we should just serialize it here? + fileBits[i] = new byte[size]; + int read = in.read(fileBits[i]); + if (size != 0 && read < size) { + throw new Pack200Exception("Expected to read " + size + + " bytes but read " + read); + } + } + } + + public void unpack() { + + } + + public byte[][] getFileBits() { + return fileBits; + } + + public int[] getFileModtime() { + return fileModtime; + } + + public String[] getFileName() { + return fileName; + } + + public int[] getFileOptions() { + return fileOptions; + } + + public long[] getFileSize() { + return fileSize; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/IMatcher.java b/src/main/java/org/apache/harmony/unpack200/IMatcher.java new file mode 100644 index 0000000..0d35735 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/IMatcher.java @@ -0,0 +1,26 @@ +/* + * 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.unpack200; + +/** + * Interface for a class that can perform matching on flag values. + */ +public interface IMatcher { + + public abstract boolean matches(long value); + +} diff --git a/src/main/java/org/apache/harmony/unpack200/IcBands.java b/src/main/java/org/apache/harmony/unpack200/IcBands.java new file mode 100644 index 0000000..6a5008b --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/IcBands.java @@ -0,0 +1,249 @@ +/* + * 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.unpack200; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.bytecode.CPClass; +import org.apache.harmony.unpack200.bytecode.ClassConstantPool; +import org.apache.harmony.unpack200.bytecode.ConstantPoolEntry; + +/** + * Inner Class Bands + */ +public class IcBands extends BandSet { + + private IcTuple[] icAll; + + private final String[] cpUTF8; + + private final String[] cpClass; + + private Map thisClassToTuple; + private Map outerClassToTuples; + + /** + * @param segment + */ + public IcBands(Segment segment) { + super(segment); + this.cpClass = segment.getCpBands().getCpClass(); + this.cpUTF8 = segment.getCpBands().getCpUTF8(); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.BandSet#unpack(java.io.InputStream) + */ + public void read(InputStream in) throws IOException, Pack200Exception { + // Read IC bands + int innerClassCount = header.getInnerClassCount(); + int[] icThisClassInts = decodeBandInt("ic_this_class", in, + Codec.UDELTA5, innerClassCount); + String[] icThisClass = getReferences(icThisClassInts, cpClass); + int[] icFlags = decodeBandInt("ic_flags", in, Codec.UNSIGNED5, + innerClassCount); + int outerClasses = SegmentUtils.countBit16(icFlags); + int[] icOuterClassInts = decodeBandInt("ic_outer_class", in, + Codec.DELTA5, outerClasses); + String[] icOuterClass = new String[outerClasses]; + for (int i = 0; i < icOuterClass.length; i++) { + if (icOuterClassInts[i] == 0) { + icOuterClass[i] = null; + } else { + icOuterClass[i] = cpClass[icOuterClassInts[i] - 1]; + } + } + int[] icNameInts = decodeBandInt("ic_name", in, Codec.DELTA5, + outerClasses); + String[] icName = new String[outerClasses]; + for (int i = 0; i < icName.length; i++) { + if (icNameInts[i] == 0) { + icName[i] = null; + } else { + icName[i] = cpUTF8[icNameInts[i] - 1]; + } + } + + // Construct IC tuples + icAll = new IcTuple[icThisClass.length]; + int index = 0; + for (int i = 0; i < icThisClass.length; i++) { + String icTupleC = icThisClass[i]; + int icTupleF = icFlags[i]; + String icTupleC2 = null; + String icTupleN = null; + int cIndex = icThisClassInts[i]; + int c2Index = -1; + int nIndex = -1; + if ((icFlags[i] & 1 << 16) != 0) { + icTupleC2 = icOuterClass[index]; + icTupleN = icName[index]; + c2Index = icOuterClassInts[index] - 1; + nIndex = icNameInts[index] - 1; + index++; + } + icAll[i] = new IcTuple(icTupleC, icTupleF, icTupleC2, icTupleN, cIndex, c2Index, nIndex, i); + } + } + + public void unpack() throws IOException, Pack200Exception { + IcTuple[] allTuples = getIcTuples(); + thisClassToTuple = new HashMap(allTuples.length); + outerClassToTuples = new HashMap(allTuples.length); + for(int index = 0; index < allTuples.length; index++) { + + IcTuple tuple = allTuples[index]; + + // generate mapping thisClassString -> IcTuple + // presumably this relation is 1:1 + // + Object result = thisClassToTuple.put(tuple.thisClassString(), tuple); + if (result != null) { + throw new Error("Collision detected in mapping. " + + "There are at least two inner clases with the same name."); + } + + // generate mapping outerClassString -> IcTuple + // this relation is 1:M + + // If it's not anon and the outer is not anon, it could be relevant + if ( (!tuple.isAnonymous() && !tuple.outerIsAnonymous()) || (tuple.nestedExplicitFlagSet()) ) { + + // add tuple to corresponding bucket + String key = tuple.outerClassString(); + List bucket = (List)outerClassToTuples.get(key); + if (bucket == null) { + bucket = new ArrayList(); + outerClassToTuples.put(key, bucket); + } + bucket.add(tuple); + } + } + } + + + public IcTuple[] getIcTuples() { + return icAll; + } + + /** + * Answer the relevant IcTuples for the specified className and class + * constant pool. + * + * @param className + * String name of the class X for ic_relevant(X) + * @param cp + * ClassConstantPool used to generate ic_relevant(X) + * @return array of IcTuple + */ + public IcTuple[] getRelevantIcTuples(String className, ClassConstantPool cp) { + Set relevantTuplesContains = new HashSet(); + List relevantTuples = new ArrayList(); + + List relevantCandidates = (List) outerClassToTuples.get(className); + if (relevantCandidates != null) { + for (int index = 0; index < relevantCandidates.size(); index++) { + IcTuple tuple = (IcTuple) relevantCandidates.get(index); + relevantTuplesContains.add(tuple); + relevantTuples.add(tuple); + } + } + + List entries = cp.entries(); + + // For every class constant in both ic_this_class and cp, + // add it to ic_relevant. Repeat until no more + // changes to ic_relevant. + + for (int eIndex = 0; eIndex < entries.size(); eIndex++) { + ConstantPoolEntry entry = (ConstantPoolEntry) entries.get(eIndex); + if (entry instanceof CPClass) { + CPClass clazz = (CPClass) entry; + IcTuple relevant = (IcTuple) thisClassToTuple.get(clazz.name); + if (relevant != null && relevantTuplesContains.add(relevant)) { + relevantTuples.add(relevant); + } + } + } + + // Not part of spec: fix up by adding to relevantTuples the parents + // of inner classes which are themselves inner classes. + // i.e., I think that if Foo$Bar$Baz gets added, Foo$Bar needs to be + // added + // as well. + + ArrayList tuplesToScan = new ArrayList(relevantTuples); + ArrayList tuplesToAdd = new ArrayList(); + + while (tuplesToScan.size() > 0) { + + tuplesToAdd.clear(); + for (int index = 0; index < tuplesToScan.size(); index++) { + IcTuple aRelevantTuple = (IcTuple) tuplesToScan.get(index); + IcTuple relevant = (IcTuple) thisClassToTuple + .get(aRelevantTuple.outerClassString()); + if (relevant != null && !aRelevantTuple.outerIsAnonymous()) { + tuplesToAdd.add(relevant); + } + } + + tuplesToScan.clear(); + for (int index = 0; index < tuplesToAdd.size(); index++) { + IcTuple tuple = (IcTuple) tuplesToAdd.get(index); + if (relevantTuplesContains.add(tuple)) { + relevantTuples.add(tuple); + tuplesToScan.add(tuple); + } + } + + } + + // End not part of the spec. Ugh. + + // Now order the result as a subsequence of ic_all + Collections.sort(relevantTuples, new Comparator() { + + public int compare(Object arg0, Object arg1) { + Integer index1 = new Integer(((IcTuple)arg0).getTupleIndex()); + Integer index2 = new Integer(((IcTuple)arg1).getTupleIndex()); + return index1.compareTo(index2); + } + + }); + + IcTuple[] relevantTuplesArray = new IcTuple[relevantTuples.size()]; + for(int i = 0; i < relevantTuplesArray.length; i++) { + relevantTuplesArray[i] = (IcTuple)relevantTuples.get(i); + } + + return relevantTuplesArray; + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/IcTuple.java b/src/main/java/org/apache/harmony/unpack200/IcTuple.java new file mode 100644 index 0000000..fe15724 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/IcTuple.java @@ -0,0 +1,370 @@ +/* + * 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.unpack200; + +import java.util.ArrayList; + +/** + * An IcTuple is the set of information that describes an inner class. + * + * C is the fully qualified class name
+ * F is the flags
+ * C2 is the outer class name, or null if it can be inferred from C
+ * N is the inner class name, or null if it can be inferred from C
+ */ +public class IcTuple { + + private final int cIndex; + private final int c2Index; + private final int nIndex; + private final int tIndex; + + /** + * + * @param C + * @param F + * @param C2 + * @param N + * @param cIndex the index of C in cpClass + * @param c2Index the index of C2 in cpClass, or -1 if C2 is null + * @param nIndex the index of N in cpUTF8, or -1 if N is null + */ + public IcTuple(String C, int F, String C2, String N, int cIndex, int c2Index, int nIndex, int tIndex) { + this.C = C; + this.F = F; + this.C2 = C2; + this.N = N; + this.cIndex = cIndex; + this.c2Index = c2Index; + this.nIndex = nIndex; + this.tIndex = tIndex; + if (null == N) { + predictSimple = true; + } + if (null == C2) { + predictOuter = true; + } + initializeClassStrings(); + } + + public IcTuple(String C, int F, int cIndex) { + this(C, F, null, null, cIndex, -1, -1, -1); + } + + public static final int NESTED_CLASS_FLAG = 0x00010000; + protected String C; // this class + protected int F; // flags + protected String C2; // outer class + protected String N; // name + + private boolean predictSimple; + private boolean predictOuter; + private String cachedOuterClassString; + private String cachedSimpleClassName; + private boolean initialized; + private boolean anonymous; + private boolean outerIsAnonymous; + private boolean member = true; + private int cachedOuterClassIndex = -1; + private int cachedSimpleClassNameIndex = -1; + + /** + * Answer true if the receiver is predicted; answer false if the receiver is + * specified explicitly in the outer and name fields. + */ + public boolean predicted() { + return predictOuter || predictSimple; + } + + + /** + * Answer true if the receiver's bit 16 is set (indicating + * that explicit outer class and name fields are set). + * @return boolean + */ + public boolean nestedExplicitFlagSet() { + return (F & NESTED_CLASS_FLAG) == NESTED_CLASS_FLAG; + } + + /** + * Break the receiver into components at $ boundaries. + */ + public String[] innerBreakAtDollar(String className) { + ArrayList resultList = new ArrayList(); + int start = 0; + int index = 0; + while (index < className.length()) { + if (className.charAt(index) <= '$') { + resultList.add(className.substring(start, index)); + start = index + 1; + } + index++; + if (index >= className.length()) { + // Add the last element + resultList.add(className.substring(start, className.length())); + } + } + String[] result = new String[resultList.size()]; + for (int i = 0; i < resultList.size(); i++) { + result[i] = (String) resultList.get(i); + } + return result; + } + + /** + * Answer the outer class name for the receiver. This may either be + * specified or inferred from inner class name. + * + * @return String name of outer class + */ + public String outerClassString() { + return cachedOuterClassString; + } + + /** + * Answer the inner class name for the receiver. + * + * @return String name of inner class + */ + public String simpleClassName() { + return cachedSimpleClassName; + } + + /** + * Answer the full name of the inner class represented by this tuple + * (including its outer component) + * + * @return String full name of inner class + */ + public String thisClassString() { + if (predicted()) { + return C; + } else { + // TODO: this may not be right. What if I + // get a class like Foo#Bar$Baz$Bug? + return C2 + "$" + N; + } + } + + public boolean isMember() { + return member; + } + + public boolean isAnonymous() { + return anonymous; + } + + + public boolean outerIsAnonymous() { + return outerIsAnonymous; + } + + private boolean computeOuterIsAnonymous() { + String[] result = innerBreakAtDollar(cachedOuterClassString); + if (result.length == 0) { + throw new Error( + "Should have an outer before checking if it's anonymous"); + } + + for (int index = 0; index < result.length; index++) { + if (isAllDigits(result[index])) { + return true; + } + } + return false; + } + + private void initializeClassStrings() { + if (initialized) { + return; + } + initialized = true; + + if (!predictSimple) { + cachedSimpleClassName = N; + } + if (!predictOuter) { + cachedOuterClassString = C2; + } + // Class names must be calculated from + // this class name. + String nameComponents[] = innerBreakAtDollar(C); + if (nameComponents.length == 0) { + // Unable to predict outer class + // throw new Error("Unable to predict outer class name: " + C); + } + if (nameComponents.length == 1) { + // Unable to predict simple class name + // throw new Error("Unable to predict inner class name: " + C); + } + if (nameComponents.length < 2) { + // If we get here, we hope cachedSimpleClassName + // and cachedOuterClassString were caught by the + // predictSimple / predictOuter code above. + return; + } + + // If we get to this point, nameComponents.length must be >=2 + int lastPosition = nameComponents.length - 1; + cachedSimpleClassName = nameComponents[lastPosition]; + cachedOuterClassString = ""; + for (int index = 0; index < lastPosition; index++) { + cachedOuterClassString += nameComponents[index]; + if (isAllDigits(nameComponents[index])) { + member = false; + } + if (index + 1 != lastPosition) { + // TODO: might need more logic to handle + // classes with separators of non-$ characters + // (ie Foo#Bar) + cachedOuterClassString += '$'; + } + } + // TODO: these two blocks are the same as blocks + // above. Can we eliminate some by reworking the logic? + if (!predictSimple) { + cachedSimpleClassName = N; + cachedSimpleClassNameIndex = nIndex; + } + if (!predictOuter) { + cachedOuterClassString = C2; + cachedOuterClassIndex = c2Index; + } + if (isAllDigits(cachedSimpleClassName)) { + anonymous = true; + member = false; + if (nestedExplicitFlagSet()) { + // Predicted class - marking as member + member = true; + } + } + + outerIsAnonymous = computeOuterIsAnonymous(); + } + + private boolean isAllDigits(String nameString) { + // Answer true if the receiver is all digits; otherwise answer false. + if (null == nameString) { + return false; + } + for (int index = 0; index < nameString.length(); index++) { + if (!Character.isDigit(nameString.charAt(index))) { + return false; + } + } + return true; + } + + public String toString() { + StringBuffer result = new StringBuffer(); + result.append("IcTuple "); + result.append('('); + result.append(simpleClassName()); + result.append(" in "); + result.append(outerClassString()); + result.append(')'); + return result.toString(); + } + + public boolean nullSafeEquals(String stringOne, String stringTwo) { + if (null == stringOne) { + return null == stringTwo; + } + return stringOne.equals(stringTwo); + } + + public boolean equals(Object object) { + if ((object == null) || (object.getClass() != this.getClass())) { + return false; + } + IcTuple compareTuple = (IcTuple) object; + + if (!nullSafeEquals(this.C, compareTuple.C)) { + return false; + } + + if (!nullSafeEquals(this.C2, compareTuple.C2)) { + return false; + } + + if (!nullSafeEquals(this.N, compareTuple.N)) { + return false; + } + return true; + } + + private boolean hashcodeComputed; + private int cachedHashCode; + + private void generateHashCode() { + hashcodeComputed = true; + cachedHashCode = 17; + if(C != null) { cachedHashCode =+ C.hashCode(); } + if(C2 != null) { cachedHashCode =+ C2.hashCode(); } + if(N != null) { cachedHashCode =+ N.hashCode(); } + } + + public int hashCode() { + if (!hashcodeComputed) + generateHashCode(); + return cachedHashCode; + } + + public String getC() { + return C; + } + + public int getF() { + return F; + } + + public String getC2() { + return C2; + } + + public String getN() { + return N; + } + + public int getTupleIndex() { + return tIndex; + } + + public String realOuterClassString() { + int firstDollarPosition = cachedOuterClassString.indexOf('$'); + if (firstDollarPosition <= 0) { + return cachedOuterClassString; + } + return cachedOuterClassString.substring(0, firstDollarPosition); + } + + public int thisClassIndex() { + if(predicted()) { + return cIndex; + } else { + return -1; + } + } + + public int outerClassIndex() { + return cachedOuterClassIndex; + } + + public int simpleClassNameIndex() { + return cachedSimpleClassNameIndex; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/MetadataBandGroup.java b/src/main/java/org/apache/harmony/unpack200/MetadataBandGroup.java new file mode 100644 index 0000000..49ae802 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/MetadataBandGroup.java @@ -0,0 +1,252 @@ +/* + * 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.unpack200; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.apache.harmony.unpack200.bytecode.AnnotationDefaultAttribute; +import org.apache.harmony.unpack200.bytecode.Attribute; +import org.apache.harmony.unpack200.bytecode.CPDouble; +import org.apache.harmony.unpack200.bytecode.CPFloat; +import org.apache.harmony.unpack200.bytecode.CPInteger; +import org.apache.harmony.unpack200.bytecode.CPLong; +import org.apache.harmony.unpack200.bytecode.CPUTF8; +import org.apache.harmony.unpack200.bytecode.RuntimeVisibleorInvisibleAnnotationsAttribute; +import org.apache.harmony.unpack200.bytecode.RuntimeVisibleorInvisibleParameterAnnotationsAttribute; +import org.apache.harmony.unpack200.bytecode.AnnotationsAttribute.Annotation; +import org.apache.harmony.unpack200.bytecode.AnnotationsAttribute.ElementValue; +import org.apache.harmony.unpack200.bytecode.RuntimeVisibleorInvisibleParameterAnnotationsAttribute.ParameterAnnotation; + +/** + * A group of metadata bands, such as class_RVA_bands, method_AD_bands etc. + */ +public class MetadataBandGroup { + + private final String type; + private final CpBands cpBands; + + private static CPUTF8 rvaUTF8; + private static CPUTF8 riaUTF8; + private static CPUTF8 rvpaUTF8; + private static CPUTF8 ripaUTF8; + + public static void setRvaAttributeName(CPUTF8 cpUTF8Value) { + rvaUTF8 = cpUTF8Value; + } + + public static void setRiaAttributeName(CPUTF8 cpUTF8Value) { + riaUTF8 = cpUTF8Value; + } + + public static void setRvpaAttributeName(CPUTF8 cpUTF8Value) { + rvpaUTF8 = cpUTF8Value; + } + + public static void setRipaAttributeName(CPUTF8 cpUTF8Value) { + ripaUTF8 = cpUTF8Value; + } + + public MetadataBandGroup(String type, CpBands cpBands) { + this.type = type; + this.cpBands = cpBands; + } + + private List attributes; + + public int[] param_NB; + public int[] anno_N; + public CPUTF8[][] type_RS; + public int[][] pair_N; + public CPUTF8[] name_RU; + public int[] T; + public CPInteger[] caseI_KI; + public CPDouble[] caseD_KD; + public CPFloat[] caseF_KF; + public CPLong[] caseJ_KJ; + public CPUTF8[] casec_RS; + public String[] caseet_RS; + public String[] caseec_RU; + public CPUTF8[] cases_RU; + public int[] casearray_N; + public CPUTF8[] nesttype_RS; + public int[] nestpair_N; + public CPUTF8[] nestname_RU; + + private int caseI_KI_Index; + + private int caseD_KD_Index; + + private int caseF_KF_Index; + + private int caseJ_KJ_Index; + + private int casec_RS_Index; + + private int caseet_RS_Index; + + private int caseec_RU_Index; + + private int cases_RU_Index; + + private int casearray_N_Index; + + private int T_index; + + private int nesttype_RS_Index; + + private int nestpair_N_Index; + + private Iterator nestname_RU_Iterator; + + private int anno_N_Index; + + private Iterator type_RS_Iterator; + + private int pair_N_Index; + + public List getAttributes() { + // TODO: Optimize iterators! + if (attributes == null) { + attributes = new ArrayList(); + if (name_RU != null) { + Iterator name_RU_Iterator = Arrays.asList(name_RU).iterator(); + if (!type.equals("AD")) { + T_index = 0; + } + caseI_KI_Index = 0; + caseD_KD_Index = 0; + caseF_KF_Index = 0; + caseJ_KJ_Index = 0; + casec_RS_Index = 0; + caseet_RS_Index = 0; + caseec_RU_Index = 0; + cases_RU_Index = 0; + casearray_N_Index = 0; + nesttype_RS_Index = 0; + nestpair_N_Index = 0; + nestname_RU_Iterator = Arrays.asList(nestname_RU).iterator(); + if (type.equals("RVA") || type.equals("RIA")) { + for (int i = 0; i < anno_N.length; i++) { + attributes.add(getAttribute(anno_N[i], type_RS[i], + pair_N[i], name_RU_Iterator)); + } + } else if (type.equals("RVPA") || type.equals("RIPA")) { + anno_N_Index = 0; + type_RS_Iterator = Arrays.asList(type_RS).iterator(); + pair_N_Index = 0; + for (int i = 0; i < param_NB.length; i++) { + attributes.add(getParameterAttribute(param_NB[i], + name_RU_Iterator)); + } + } else { // type.equals("AD") + for (int i = 0; i < T.length; i++) { + attributes.add(new AnnotationDefaultAttribute( + new ElementValue(T[i], getNextValue(T[i])))); + } + } + } + } + return attributes; + } + + private Attribute getAttribute(int numAnnotations, CPUTF8[] types, + int[] pairCounts, Iterator namesIterator) { + Annotation[] annotations = new Annotation[numAnnotations]; + for (int i = 0; i < numAnnotations; i++) { + annotations[i] = getAnnotation(types[i], pairCounts[i], + namesIterator); + } + return new RuntimeVisibleorInvisibleAnnotationsAttribute(type + .equals("RVA") ? rvaUTF8 : riaUTF8, annotations); + } + + private Attribute getParameterAttribute(int numParameters, + Iterator namesIterator) { + ParameterAnnotation[] parameter_annotations = new ParameterAnnotation[numParameters]; + for (int i = 0; i < numParameters; i++) { + int numAnnotations = anno_N[anno_N_Index++]; + int[] pairCounts = pair_N[pair_N_Index++]; + Annotation[] annotations = new Annotation[numAnnotations]; + for (int j = 0; j < annotations.length; j++) { + annotations[j] = getAnnotation( + (CPUTF8) type_RS_Iterator.next(), pairCounts[j], + namesIterator); + } + parameter_annotations[i] = new ParameterAnnotation(annotations); + } + return new RuntimeVisibleorInvisibleParameterAnnotationsAttribute(type + .equals("RVPA") ? rvpaUTF8 : ripaUTF8, + parameter_annotations); + } + + private Annotation getAnnotation(CPUTF8 type, int pairCount, + Iterator namesIterator) { + CPUTF8[] elementNames = new CPUTF8[pairCount]; + ElementValue[] elementValues = new ElementValue[pairCount]; + for (int j = 0; j < elementNames.length; j++) { + elementNames[j] = (CPUTF8) namesIterator.next(); + int t = T[T_index++]; + elementValues[j] = new ElementValue(t, getNextValue(t)); + } + return new Annotation(pairCount, type, elementNames, elementValues); + } + + private Object getNextValue(int t) { + switch (t) { + case 'B': + case 'C': + case 'I': + case 'S': + case 'Z': + return caseI_KI[caseI_KI_Index++]; + case 'D': + return caseD_KD[caseD_KD_Index++]; + case 'F': + return caseF_KF[caseF_KF_Index++]; + case 'J': + return caseJ_KJ[caseJ_KJ_Index++]; + case 'c': + return casec_RS[casec_RS_Index++]; + case 'e': + // TODO: check this - it may not work if the first string already + // has a colon in it + String enumString = caseet_RS[caseet_RS_Index++] + ":" + + caseec_RU[caseec_RU_Index++]; + return cpBands.cpNameAndTypeValue(enumString); + case 's': + return cases_RU[cases_RU_Index++]; + case '[': + int arraySize = casearray_N[casearray_N_Index++]; + ElementValue[] nestedArray = new ElementValue[arraySize]; + for (int i = 0; i < arraySize; i++) { + int nextT = T[T_index++]; + nestedArray[i] = new ElementValue(nextT, getNextValue(nextT)); + } + return nestedArray; + case '@': + CPUTF8 type = nesttype_RS[nesttype_RS_Index++]; + int numPairs = nestpair_N[nestpair_N_Index++]; + + return getAnnotation(type, numPairs, nestname_RU_Iterator); + } + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/NewAttributeBands.java b/src/main/java/org/apache/harmony/unpack200/NewAttributeBands.java new file mode 100644 index 0000000..c27425d --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/NewAttributeBands.java @@ -0,0 +1,995 @@ +/* + * 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.unpack200; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.harmony.pack200.BHSDCodec; +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.bytecode.Attribute; +import org.apache.harmony.unpack200.bytecode.CPClass; +import org.apache.harmony.unpack200.bytecode.CPDouble; +import org.apache.harmony.unpack200.bytecode.CPFieldRef; +import org.apache.harmony.unpack200.bytecode.CPFloat; +import org.apache.harmony.unpack200.bytecode.CPInteger; +import org.apache.harmony.unpack200.bytecode.CPInterfaceMethodRef; +import org.apache.harmony.unpack200.bytecode.CPLong; +import org.apache.harmony.unpack200.bytecode.CPMethodRef; +import org.apache.harmony.unpack200.bytecode.CPNameAndType; +import org.apache.harmony.unpack200.bytecode.CPString; +import org.apache.harmony.unpack200.bytecode.CPUTF8; +import org.apache.harmony.unpack200.bytecode.NewAttribute; + +/** + * Set of bands relating to a non-predefined attribute + */ +public class NewAttributeBands extends BandSet { + + private final AttributeLayout attributeLayout; + + private int backwardsCallCount; + + private List attributeLayoutElements; + + public NewAttributeBands(Segment segment, AttributeLayout attributeLayout) + throws IOException { + super(segment); + this.attributeLayout = attributeLayout; + parseLayout(); + attributeLayout.setBackwardsCallCount(backwardsCallCount); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.BandSet#unpack(java.io.InputStream) + */ + public void read(InputStream in) throws IOException, Pack200Exception { + // does nothing - use parseAttributes instead + } + + /** + * Parse the bands relating to this AttributeLayout and return the correct + * class file attributes as a List of {@link Attribute} + * + * @throws Pack200Exception + */ + public List parseAttributes(InputStream in, int occurrenceCount) + throws IOException, Pack200Exception { + for(int i = 0; i < attributeLayoutElements.size(); i++) { + AttributeLayoutElement element = (AttributeLayoutElement) attributeLayoutElements.get(i); + element.readBands(in, occurrenceCount); + } + + List attributes = new ArrayList(occurrenceCount); + for (int i = 0; i < occurrenceCount; i++) { + attributes.add(getOneAttribute(i, attributeLayoutElements)); + } + return attributes; + } + + /** + * Get one attribute at the given index from the various bands. The correct + * bands must have already been read in. + * + * @param index + * @param elements + * @return + */ + private Attribute getOneAttribute(int index, List elements) { + NewAttribute attribute = new NewAttribute(segment.getCpBands() + .cpUTF8Value(attributeLayout.getName()), + attributeLayout.getIndex()); + for(int i = 0; i < elements.size(); i++) { + AttributeLayoutElement element = (AttributeLayoutElement) elements.get(i); + element.addToAttribute(index, attribute); + } + return attribute; + } + + /** + * Tokenise the layout into AttributeElements + * + * @throws IOException + */ + private void parseLayout() throws IOException { + if (attributeLayoutElements == null) { + attributeLayoutElements = new ArrayList(); + StringReader stream = new StringReader(attributeLayout.getLayout()); + AttributeLayoutElement e; + while ((e = readNextAttributeElement(stream)) != null) { + attributeLayoutElements.add(e); + } + resolveCalls(); + } + } + + /** + * Resolve calls in the attribute layout and returns the number of backwards + * calls + */ + private void resolveCalls() { + int backwardsCalls = 0; + for (int i = 0; i < attributeLayoutElements.size(); i++) { + AttributeLayoutElement element = (AttributeLayoutElement) attributeLayoutElements + .get(i); + if (element instanceof Callable) { + Callable callable = (Callable) element; + if(i == 0) { + callable.setFirstCallable(true); + } + List body = callable.body; // Look for calls in the body + for(int iIndex = 0; iIndex < body.size(); iIndex++) { + LayoutElement layoutElement = (LayoutElement) body + .get(iIndex); + // Set the callable for each call + backwardsCalls += resolveCallsForElement(i, callable, + layoutElement); + } + } + } + backwardsCallCount = backwardsCalls; + } + + private int resolveCallsForElement(int i, + Callable currentCallable, LayoutElement layoutElement) { + int backwardsCalls = 0; + if (layoutElement instanceof Call) { + Call call = (Call) layoutElement; + int index = call.callableIndex; + if (index == 0) { // Calls the parent callable + backwardsCalls++; + call.setCallable(currentCallable); + } else if (index > 0) { // Forwards call + for (int k = i + 1; k < attributeLayoutElements.size(); k++) { + AttributeLayoutElement el = (AttributeLayoutElement) attributeLayoutElements + .get(k); + if (el instanceof Callable) { + index--; + if (index == 0) { + call.setCallable((Callable) el); + break; + } + } + } + } else { // Backwards call + backwardsCalls++; + for (int k = i; k >= 0; k--) { + AttributeLayoutElement el = (AttributeLayoutElement) attributeLayoutElements + .get(k); + if (el instanceof Callable) { + index++; + if (index == 0) { + call.setCallable((Callable) el); + break; + } + } + } + } + } else if (layoutElement instanceof Replication) { + List children = ((Replication)layoutElement).layoutElements; + for (Iterator iterator = children.iterator(); iterator.hasNext();) { + LayoutElement object = (LayoutElement) iterator.next(); + backwardsCalls += resolveCallsForElement(i, currentCallable, object); + } + } + return backwardsCalls; + } + + private AttributeLayoutElement readNextAttributeElement(StringReader stream) + throws IOException { + stream.mark(1); + int nextChar = stream.read(); + if (nextChar == -1) { + return null; + } + if (nextChar == '[') { + List body = readBody(getStreamUpToMatchingBracket(stream)); + return new Callable(body); + } else { + stream.reset(); + return readNextLayoutElement(stream); + } + } + + private LayoutElement readNextLayoutElement(StringReader stream) + throws IOException { + int nextChar = stream.read(); + if (nextChar == -1) { + return null; + } + switch (nextChar) { + // Integrals + case 'B': + case 'H': + case 'I': + case 'V': + return new Integral(new String(new char[] { (char)nextChar })); + case 'S': + case 'F': + return new Integral(new String(new char[] { (char)nextChar, + (char) stream.read() })); + case 'P': + stream.mark(1); + if (stream.read() != 'O') { + stream.reset(); + return new Integral("P" + (char) stream.read()); + } else { + return new Integral("PO" + (char) stream.read()); + } + case 'O': + stream.mark(1); + if (stream.read() != 'S') { + stream.reset(); + return new Integral("O" + (char) stream.read()); + } else { + return new Integral("OS" + (char) stream.read()); + } + + // Replication + case 'N': + char uint_type = (char) stream.read(); + stream.read(); // '[' + String str = readUpToMatchingBracket(stream); + return new Replication("" + uint_type, str); + + // Union + case 'T': + String int_type = "" + (char) stream.read(); + if (int_type.equals("S")) { + int_type += (char) stream.read(); + } + List unionCases = new ArrayList(); + UnionCase c; + while ((c = readNextUnionCase(stream)) != null) { + unionCases.add(c); + } + stream.read(); // '(' + stream.read(); // ')' + stream.read(); // '[' + List body = null; + stream.mark(1); + char next = (char) stream.read(); + if (next != ']') { + stream.reset(); + body = readBody(getStreamUpToMatchingBracket(stream)); + } + return new Union(int_type, unionCases, body); + + // Call + case '(': + int number = readNumber(stream).intValue(); + stream.read(); // ')' + return new Call(number); + // Reference + case 'K': + case 'R': + String string = "" + (char)nextChar + (char) stream.read(); + char nxt = (char) stream.read(); + string += nxt; + if (nxt == 'N') { + string += (char) stream.read(); + } + return new Reference(string); + } + return null; + } + + /** + * Read a UnionCase from the stream + * + * @param stream + * @return + * @throws IOException + */ + private UnionCase readNextUnionCase(StringReader stream) throws IOException { + stream.mark(2); + stream.read(); // '(' + char next = (char) stream.read(); + if (next == ')') { + stream.reset(); + return null; + } else { + stream.reset(); + stream.read(); // '(' + } + List tags = new ArrayList(); + Integer nextTag; + do { + nextTag = readNumber(stream); + if(nextTag != null) { + tags.add(nextTag); + stream.read(); // ',' or ')' + } + } while (nextTag != null); + stream.read(); // '[' + stream.mark(1); + next = (char) stream.read(); + if (next == ']') { + return new UnionCase(tags); + } else { + stream.reset(); + return new UnionCase(tags, + readBody(getStreamUpToMatchingBracket(stream))); + } + } + + /** + * An AttributeLayoutElement is a part of an attribute layout and has one or + * more bands associated with it, which transmit the AttributeElement data + * for successive Attributes of this type. + */ + private interface AttributeLayoutElement { + + /** + * Read the bands associated with this part of the layout + * + * @param in + * @param count + * @throws Pack200Exception + * @throws IOException + */ + public void readBands(InputStream in, int count) throws IOException, + Pack200Exception; + + /** + * Add the band data for this element at the given index to the + * attribute + * + * @param index + * @param attribute + */ + public void addToAttribute(int index, NewAttribute attribute); + + } + + private abstract class LayoutElement implements AttributeLayoutElement { + + protected int getLength(char uint_type) { + int length = 0; + switch (uint_type) { + case 'B': + length = 1; + break; + case 'H': + length = 2; + break; + case 'I': + length = 4; + break; + case 'V': + length = 0; + break; + } + return length; + } + } + + private class Integral extends LayoutElement { + + private final String tag; + private int[] band; + + public Integral(String tag) { + this.tag = tag; + } + + public void readBands(InputStream in, int count) throws IOException, + Pack200Exception { + band = decodeBandInt(attributeLayout.getName() + "_" + tag, in, + getCodec(tag), count); + } + + public void addToAttribute(int n, NewAttribute attribute) { + long value = band[n]; + if (tag.equals("B") || tag.equals("FB")) { + attribute.addInteger(1, value); + } else if (tag.equals("SB")) { + attribute.addInteger(1, (byte) value); + } else if (tag.equals("H") || tag.equals("FH")) { + attribute.addInteger(2, value); + } else if (tag.equals("SH")) { + attribute.addInteger(2, (short) value); + } else if (tag.equals("I") || tag.equals("FI")) { + attribute.addInteger(4, value); + } else if (tag.equals("SI")) { + attribute.addInteger(4, (int) value); + } else if (tag.equals("V") || tag.equals("FV") || tag.equals("SV")) { + // Don't add V's - they shouldn't be written out to the class + // file + } else if (tag.startsWith("PO")) { + char uint_type = tag.substring(2).toCharArray()[0]; + int length = getLength(uint_type); + attribute.addBCOffset(length, (int) value); + } else if (tag.startsWith("P")) { + char uint_type = tag.substring(1).toCharArray()[0]; + int length = getLength(uint_type); + attribute.addBCIndex(length, (int) value); + } else if (tag.startsWith("OS")) { + char uint_type = tag.substring(2).toCharArray()[0]; + int length = getLength(uint_type); + if (length == 1) { + value = (byte) value; + } else if (length == 2) { + value = (short) value; + } else if (length == 4) { + value = (int) value; + } + attribute.addBCLength(length, (int) value); + } else if (tag.startsWith("O")) { + char uint_type = tag.substring(1).toCharArray()[0]; + int length = getLength(uint_type); + attribute.addBCLength(length, (int) value); + } + } + + long getValue(int index) { + return band[index]; + } + + } + + /** + * A replication is an array of layout elements, with an associated count + */ + private class Replication extends LayoutElement { + + private final Integral countElement; + + private final List layoutElements = new ArrayList(); + + public Replication(String tag, String contents) throws IOException { + this.countElement = new Integral(tag); + StringReader stream = new StringReader(contents); + LayoutElement e; + while ((e = readNextLayoutElement(stream)) != null) { + layoutElements.add(e); + } + } + + public void readBands(InputStream in, int count) throws IOException, + Pack200Exception { + countElement.readBands(in, count); + int arrayCount = 0; + for (int i = 0; i < count; i++) { + arrayCount += countElement.getValue(i); + } + for(int i = 0; i < layoutElements.size(); i++) { + LayoutElement element = (LayoutElement) layoutElements.get(i); + element.readBands(in, arrayCount); + } + } + + public void addToAttribute(int index, NewAttribute attribute) { + // Add the count value + countElement.addToAttribute(index, attribute); + + // Add the corresponding array values + int offset = 0; + for (int i = 0; i < index; i++) { + offset += countElement.getValue(i); + } + long numElements = countElement.getValue(index); + for (int i = offset; i < offset + numElements; i++) { + for (int it = 0; it < layoutElements.size(); it++) { + LayoutElement element = (LayoutElement) layoutElements.get(it); + element.addToAttribute(i, attribute); + } + } + } + } + + /** + * A Union is a type of layout element where the tag value acts as a + * selector for one of the union cases + */ + private class Union extends LayoutElement { + + private final Integral unionTag; + private final List unionCases; + private final List defaultCaseBody; + private int[] caseCounts; + private int defaultCount; + + public Union(String tag, List unionCases, List body) { + this.unionTag = new Integral(tag); + this.unionCases = unionCases; + this.defaultCaseBody = body; + } + + public void readBands(InputStream in, int count) throws IOException, + Pack200Exception { + unionTag.readBands(in, count); + int[] values = unionTag.band; + // Count the band size for each union case then read the bands + caseCounts = new int[unionCases.size()]; + for (int i = 0; i < caseCounts.length; i++) { + UnionCase unionCase = (UnionCase) unionCases.get(i); + for (int j = 0; j < values.length; j++) { + if (unionCase.hasTag(values[j])) { + caseCounts[i]++; + } + } + unionCase.readBands(in, caseCounts[i]); + } + // Count number of default cases then read the default bands + for (int i = 0; i < values.length; i++) { + boolean found = false; + for(int it = 0; it < unionCases.size(); it++) { + UnionCase unionCase = (UnionCase) unionCases.get(it); + if (unionCase.hasTag(values[i])) { + found = true; + } + } + if (!found) { + defaultCount++; + } + } + if (defaultCaseBody != null) { + for(int i = 0; i < defaultCaseBody.size(); i++) { + LayoutElement element = (LayoutElement) defaultCaseBody.get(i); + element.readBands(in, defaultCount); + } + } + } + + public void addToAttribute(int n, NewAttribute attribute) { + unionTag.addToAttribute(n, attribute); + int offset = 0; + int[] tagBand = unionTag.band; + long tag = unionTag.getValue(n); + boolean defaultCase = true; + for(int i = 0; i < unionCases.size(); i++) { + UnionCase element = (UnionCase) unionCases.get(i); + if (element.hasTag(tag)) { + defaultCase = false; + for (int j = 0; j < n; j++) { + if (element.hasTag(tagBand[j])) { + offset++; + } + } + element.addToAttribute(offset, attribute); + } + } + if (defaultCase) { + // default case + int defaultOffset = 0; + for (int j = 0; j < n; j++) { + boolean found = false; + for(int i = 0; i < unionCases.size(); i++) { + UnionCase element = (UnionCase) unionCases.get(i); + if (element.hasTag(tagBand[j])) { + found = true; + } + } + if (!found) { + defaultOffset++; + } + } + if (defaultCaseBody != null) { + for(int i = 0; i < defaultCaseBody.size(); i++) { + LayoutElement element = (LayoutElement) defaultCaseBody.get(i); + element.addToAttribute(defaultOffset, attribute); + } + } + } + } + + } + + private class Call extends LayoutElement { + + private final int callableIndex; + private Callable callable; + + public Call(int callableIndex) { + this.callableIndex = callableIndex; + } + + public void setCallable(Callable callable) { + this.callable = callable; + if (callableIndex < 1) { + callable.setBackwardsCallable(); + } + } + + public void readBands(InputStream in, int count) { + /* + * We don't read anything here, but we need to pass the extra count + * to the callable if it's a forwards call. For backwards callables + * the count is transmitted directly in the attribute bands and so + * it is added later. + */ + if (callableIndex > 0) { + callable.addCount(count); + } + } + + public void addToAttribute(int n, NewAttribute attribute) { + callable.addNextToAttribute(attribute); + } + } + + /** + * Constant Pool Reference + */ + private class Reference extends LayoutElement { + + private final String tag; + + private Object band; + + private final int length; + + public Reference(String tag) { + this.tag = tag; + length = getLength(tag.charAt(tag.length() - 1)); + } + + public void readBands(InputStream in, int count) throws IOException, + Pack200Exception { + if (tag.startsWith("KI")) { // Integer + band = parseCPIntReferences(attributeLayout.getName(), in, + Codec.UNSIGNED5, count); + } else if (tag.startsWith("KJ")) { // Long + band = parseCPLongReferences(attributeLayout.getName(), in, + Codec.UNSIGNED5, count); + } else if (tag.startsWith("KF")) { // Float + band = parseCPFloatReferences(attributeLayout.getName(), in, + Codec.UNSIGNED5, count); + } else if (tag.startsWith("KD")) { // Double + band = parseCPDoubleReferences(attributeLayout.getName(), in, + Codec.UNSIGNED5, count); + } else if (tag.startsWith("KS")) { // String + band = parseCPStringReferences(attributeLayout.getName(), in, + Codec.UNSIGNED5, count); + } else if (tag.startsWith("RC")) { // Class + band = parseCPClassReferences(attributeLayout.getName(), in, + Codec.UNSIGNED5, count); + } else if (tag.startsWith("RS")) { // Signature + band = parseCPSignatureReferences(attributeLayout.getName(), + in, Codec.UNSIGNED5, count); + } else if (tag.startsWith("RD")) { // Descriptor + band = parseCPDescriptorReferences(attributeLayout.getName(), + in, Codec.UNSIGNED5, count); + } else if (tag.startsWith("RF")) { // Field Reference + band = parseCPFieldRefReferences(attributeLayout.getName(), in, + Codec.UNSIGNED5, count); + } else if (tag.startsWith("RM")) { // Method Reference + band = parseCPMethodRefReferences(attributeLayout.getName(), + in, Codec.UNSIGNED5, count); + } else if (tag.startsWith("RI")) { // Interface Method Reference + band = parseCPInterfaceMethodRefReferences(attributeLayout + .getName(), in, Codec.UNSIGNED5, count); + } else if (tag.startsWith("RU")) { // UTF8 String + band = parseCPUTF8References(attributeLayout.getName(), in, + Codec.UNSIGNED5, count); + } + } + + public void addToAttribute(int n, NewAttribute attribute) { + if (tag.startsWith("KI")) { // Integer + attribute.addCPConstant(length, ((CPInteger[]) band)[n]); + } else if (tag.startsWith("KJ")) { // Long + attribute.addCPConstant(length, ((CPLong[]) band)[n]); + } else if (tag.startsWith("KF")) { // Float + attribute.addCPConstant(length, ((CPFloat[]) band)[n]); + } else if (tag.startsWith("KD")) { // Double + attribute.addCPConstant(length, ((CPDouble[]) band)[n]); + } else if (tag.startsWith("KS")) { // String + attribute.addCPConstant(length, ((CPString[]) band)[n]); + } else if (tag.startsWith("RC")) { // Class + attribute.addCPClass(length, ((CPClass[]) band)[n]); + } else if (tag.startsWith("RS")) { // Signature + attribute.addCPUTF8(length, ((CPUTF8[]) band)[n]); + } else if (tag.startsWith("RD")) { // Descriptor + attribute.addCPNameAndType(length, ((CPNameAndType[]) band)[n]); + } else if (tag.startsWith("RF")) { // Field Reference + attribute.addCPFieldRef(length, ((CPFieldRef[]) band)[n]); + } else if (tag.startsWith("RM")) { // Method Reference + attribute.addCPMethodRef(length, ((CPMethodRef[]) band)[n]); + } else if (tag.startsWith("RI")) { // Interface Method Reference + attribute.addCPIMethodRef(length, + ((CPInterfaceMethodRef[]) band)[n]); + } else if (tag.startsWith("RU")) { // UTF8 String + attribute.addCPUTF8(length, ((CPUTF8[]) band)[n]); + } + } + + } + + private static class Callable implements AttributeLayoutElement { + + private final List body; + + private boolean isBackwardsCallable; + + private boolean isFirstCallable; + + public Callable(List body) throws IOException { + this.body = body; + } + + private int count; + private int index; + + /** + * Used by calls when adding band contents to attributes so they don't + * have to keep track of the internal index of the callable + * + * @param attribute + */ + public void addNextToAttribute(NewAttribute attribute) { + for(int i = 0; i < body.size(); i++) { + LayoutElement element = (LayoutElement) body.get(i); + element.addToAttribute(index, attribute); + } + index++; + } + + /** + * Adds the count of a call to this callable (ie the number of calls) + * + * @param count + */ + public void addCount(int count) { + this.count += count; + } + + public void readBands(InputStream in, int count) throws IOException, + Pack200Exception { + if(isFirstCallable) { + count += this.count; + } else { + count = this.count; + } + for(int i = 0; i < body.size(); i++) { + LayoutElement element = (LayoutElement) body.get(i); + element.readBands(in, count); + } + } + + public void addToAttribute(int n, NewAttribute attribute) { + if(isFirstCallable) { + // Ignore n because bands also contain element parts from calls + for(int i = 0; i < body.size(); i++) { + LayoutElement element = (LayoutElement) body.get(i); + element.addToAttribute(index, attribute); + } + index++; + } + } + + public boolean isBackwardsCallable() { + return isBackwardsCallable; + } + + /** + * Tells this Callable that it is a backwards callable + */ + public void setBackwardsCallable() { + this.isBackwardsCallable = true; + } + + public void setFirstCallable(boolean isFirstCallable) { + this.isFirstCallable = isFirstCallable; + } + } + + /** + * A Union case + */ + private class UnionCase extends LayoutElement { + + private List body; + + private final List tags; + + public UnionCase(List tags) { + this.tags = tags; + } + + public boolean hasTag(long l) { + return tags.contains(new Integer((int) l)); + } + + public UnionCase(List tags, List body) throws IOException { + this.tags = tags; + this.body = body; + } + + public void readBands(InputStream in, int count) throws IOException, + Pack200Exception { + if (body != null) { + for(int i = 0; i < body.size(); i++) { + LayoutElement element = (LayoutElement) body.get(i); + element.readBands(in, count); + } + } + } + + public void addToAttribute(int index, NewAttribute attribute) { + if (body != null) { + for(int i = 0; i < body.size(); i++) { + LayoutElement element = (LayoutElement) body.get(i); + element.addToAttribute(index, attribute); + } + } + } + } + + /** + * Utility method to get the contents of the given stream, up to the next + * ']', (ignoring pairs of brackets '[' and ']') + * + * @param stream + * @return + * @throws IOException + */ + private StringReader getStreamUpToMatchingBracket(StringReader stream) + throws IOException { + StringBuffer sb = new StringBuffer(); + int foundBracket = -1; + while (foundBracket != 0) { + char c = (char) stream.read(); + if (c == ']') { + foundBracket++; + } + if (c == '[') { + foundBracket--; + } + if (!(foundBracket == 0)) { + sb.append(c); + } + } + return new StringReader(sb.toString()); + } + + /** + * Returns the {@link BHSDCodec} that should be used for the given layout + * element + * + * @param layoutElement + */ + public BHSDCodec getCodec(String layoutElement) { + if (layoutElement.indexOf('O') >= 0) { + return Codec.BRANCH5; + } else if (layoutElement.indexOf('P') >= 0) { + return Codec.BCI5; + } else if (layoutElement.indexOf('S') >= 0 && layoutElement.indexOf("KS") < 0 //$NON-NLS-1$ + && layoutElement.indexOf("RS") < 0) { //$NON-NLS-1$ + return Codec.SIGNED5; + } else if (layoutElement.indexOf('B') >= 0) { + return Codec.BYTE1; + } else { + return Codec.UNSIGNED5; + } + } + + /** + * Utility method to get the contents of the given stream, up to the next + * ']', (ignoring pairs of brackets '[' and ']') + * + * @param stream + * @return + * @throws IOException + */ + private String readUpToMatchingBracket(StringReader stream) + throws IOException { + StringBuffer sb = new StringBuffer(); + int foundBracket = -1; + while (foundBracket != 0) { + char c = (char) stream.read(); + if (c == ']') { + foundBracket++; + } + if (c == '[') { + foundBracket--; + } + if (!(foundBracket == 0)) { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Read a number from the stream and return it + * + * @param stream + * @return + * @throws IOException + */ + private Integer readNumber(StringReader stream) throws IOException { + stream.mark(1); + char first = (char) stream.read(); + boolean negative = first == '-'; + if (!negative) { + stream.reset(); + } + stream.mark(100); + int i; + int length = 0; + while ((i = (stream.read())) != -1 && Character.isDigit((char) i)) { + length++; + } + stream.reset(); + if(length == 0) { + return null; + } + char[] digits = new char[length]; + int read = stream.read(digits); + if (read != digits.length) { + throw new IOException("Error reading from the input stream"); + } + return new Integer(Integer.parseInt((negative ? "-" : "") + new String(digits))); + } + + /** + * Read a 'body' section of the layout from the given stream + * + * @param stream + * @return List of LayoutElements + * @throws IOException + */ + private List readBody(StringReader stream) throws IOException { + List layoutElements = new ArrayList(); + LayoutElement e; + while ((e = readNextLayoutElement(stream)) != null) { + layoutElements.add(e); + } + return layoutElements; + } + + public int getBackwardsCallCount() { + return backwardsCallCount; + } + + /** + * Once the attribute bands have been read the callables can be informed + * about the number of times each is subject to a backwards call. This + * method is used to set this information. + * + * @param backwardsCalls + * one int for each backwards callable, which contains the number + * of times that callable is subject to a backwards call. + * @throws IOException + */ + public void setBackwardsCalls(int[] backwardsCalls) throws IOException { + int index = 0; + parseLayout(); + for(int i = 0; i < attributeLayoutElements.size(); i++) { + AttributeLayoutElement element = (AttributeLayoutElement) attributeLayoutElements.get(i); + if (element instanceof Callable + && ((Callable) element).isBackwardsCallable()) { + ((Callable) element).addCount(backwardsCalls[index]); + index++; + } + } + } + + public void unpack() throws IOException, Pack200Exception { + + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/Segment.java b/src/main/java/org/apache/harmony/unpack200/Segment.java new file mode 100644 index 0000000..85c95d0 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/Segment.java @@ -0,0 +1,640 @@ +/* + * 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.unpack200; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TimeZone; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.zip.CRC32; +import java.util.zip.GZIPInputStream; +import java.util.zip.ZipEntry; + +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.bytecode.Attribute; +import org.apache.harmony.unpack200.bytecode.CPClass; +import org.apache.harmony.unpack200.bytecode.CPField; +import org.apache.harmony.unpack200.bytecode.CPMethod; +import org.apache.harmony.unpack200.bytecode.CPUTF8; +import org.apache.harmony.unpack200.bytecode.ClassConstantPool; +import org.apache.harmony.unpack200.bytecode.ClassFile; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; +import org.apache.harmony.unpack200.bytecode.InnerClassesAttribute; +import org.apache.harmony.unpack200.bytecode.SourceFileAttribute; + +/** + * A Pack200 archive consists of one or more segments. Each segment is + * stand-alone, in the sense that every segment has the magic number header; + * thus, every segment is also a valid archive. However, it is possible to + * combine (non-GZipped) archives into a single large archive by concatenation + * alone. Thus all the hard work in unpacking an archive falls to understanding + * a segment. + * + * The first component of a segment is the header; this contains (amongst other + * things) the expected counts of constant pool entries, which in turn defines + * how many values need to be read from the stream. Because values are variable + * width (see {@link Codec}), it is not possible to calculate the start of the + * next segment, although one of the header values does hint at the size of the + * segment if non-zero, which can be used for buffering purposes. + * + * Note that this does not perform any buffering of the input stream; each value + * will be read on a byte-by-byte basis. It does not perform GZip decompression + * automatically; both of these are expected to be done by the caller if the + * stream has the magic header for GZip streams ({@link GZIPInputStream#GZIP_MAGIC}). + * In any case, if GZip decompression is being performed the input stream will + * be buffered at a higher level, and thus this can read on a byte-oriented + * basis. + */ +public class Segment { + + public static final int LOG_LEVEL_VERBOSE = 2; + + public static final int LOG_LEVEL_STANDARD = 1; + + public static final int LOG_LEVEL_QUIET = 0; + + private SegmentHeader header; + + private CpBands cpBands; + + private AttrDefinitionBands attrDefinitionBands; + + private IcBands icBands; + + private ClassBands classBands; + + private BcBands bcBands; + + private FileBands fileBands; + + private boolean overrideDeflateHint; + + private boolean deflateHint; + + private boolean doPreRead; + + private int logLevel; + + private PrintWriter logStream; + + private byte[][] classFilesContents; + + private boolean[] fileDeflate; + + private boolean[] fileIsClass; + + private InputStream internalBuffer; + + private ClassFile buildClassFile(int classNum) throws Pack200Exception { + ClassFile classFile = new ClassFile(); + int[] major = classBands.getClassVersionMajor(); + int[] minor = classBands.getClassVersionMinor(); + if(major != null) { + classFile.major = major[classNum]; + classFile.minor = minor[classNum]; + } else { + classFile.major = header.getDefaultClassMajorVersion(); + classFile.minor = header.getDefaultClassMinorVersion(); + } + // build constant pool + ClassConstantPool cp = classFile.pool; + int fullNameIndexInCpClass = classBands.getClassThisInts()[classNum]; + String fullName = cpBands.getCpClass()[fullNameIndexInCpClass]; + // SourceFile attribute + int i = fullName.lastIndexOf("/") + 1; // if lastIndexOf==-1, then + // -1+1=0, so str.substring(0) + // == str + + // Get the source file attribute + ArrayList classAttributes = classBands.getClassAttributes()[classNum]; + SourceFileAttribute sourceFileAttribute = null; + for (int index = 0; index < classAttributes.size(); index++) { + if (((Attribute) classAttributes.get(index)) + .isSourceFileAttribute()) { + sourceFileAttribute = ((SourceFileAttribute) classAttributes + .get(index)); + } + } + + if (sourceFileAttribute == null) { + // If we don't have a source file attribute yet, we need + // to infer it from the class. + AttributeLayout SOURCE_FILE = attrDefinitionBands + .getAttributeDefinitionMap().getAttributeLayout( + AttributeLayout.ATTRIBUTE_SOURCE_FILE, + AttributeLayout.CONTEXT_CLASS); + if (SOURCE_FILE.matches(classBands.getRawClassFlags()[classNum])) { + int firstDollar = SegmentUtils.indexOfFirstDollar(fullName); + String fileName = null; + + if (firstDollar > -1 && (i <= firstDollar)) { + fileName = fullName.substring(i, firstDollar) + ".java"; + } else { + fileName = fullName.substring(i) + ".java"; + } + sourceFileAttribute = new SourceFileAttribute(cpBands + .cpUTF8Value(fileName, false)); + classFile.attributes = new Attribute[] { (Attribute) cp + .add(sourceFileAttribute) }; + } else { + classFile.attributes = new Attribute[] {}; + } + } else { + classFile.attributes = new Attribute[] { (Attribute) cp + .add(sourceFileAttribute) }; + } + + // If we see any class attributes, add them to the class's attributes + // that will + // be written out. Keep SourceFileAttributes out since we just + // did them above. + ArrayList classAttributesWithoutSourceFileAttribute = new ArrayList(classAttributes.size()); + for (int index = 0; index < classAttributes.size(); index++) { + Attribute attrib = (Attribute) classAttributes.get(index); + if (!attrib.isSourceFileAttribute()) { + classAttributesWithoutSourceFileAttribute.add(attrib); + } + } + Attribute[] originalAttributes = classFile.attributes; + classFile.attributes = new Attribute[originalAttributes.length + + classAttributesWithoutSourceFileAttribute.size()]; + System.arraycopy(originalAttributes, 0, classFile.attributes, 0, + originalAttributes.length); + for (int index = 0; index < classAttributesWithoutSourceFileAttribute + .size(); index++) { + Attribute attrib = ((Attribute) classAttributesWithoutSourceFileAttribute + .get(index)); + cp.add(attrib); + classFile.attributes[originalAttributes.length + index] = attrib; + } + + // this/superclass + ClassFileEntry cfThis = cp.add(cpBands.cpClassValue(fullNameIndexInCpClass)); + ClassFileEntry cfSuper = cp.add(cpBands.cpClassValue(classBands + .getClassSuperInts()[classNum])); + // add interfaces + ClassFileEntry cfInterfaces[] = new ClassFileEntry[classBands + .getClassInterfacesInts()[classNum].length]; + for (i = 0; i < cfInterfaces.length; i++) { + cfInterfaces[i] = cp.add(cpBands.cpClassValue(classBands + .getClassInterfacesInts()[classNum][i])); + } + // add fields + ClassFileEntry cfFields[] = new ClassFileEntry[classBands + .getClassFieldCount()[classNum]]; + // fieldDescr and fieldFlags used to create this + for (i = 0; i < cfFields.length; i++) { + int descriptorIndex = classBands.getFieldDescrInts()[classNum][i]; + int nameIndex = cpBands.getCpDescriptorNameInts()[descriptorIndex]; + int typeIndex = cpBands.getCpDescriptorTypeInts()[descriptorIndex]; + CPUTF8 name = cpBands.cpUTF8Value(nameIndex); + CPUTF8 descriptor = cpBands.cpSignatureValue(typeIndex); + cfFields[i] = cp.add(new CPField(name, descriptor, classBands + .getFieldFlags()[classNum][i], classBands + .getFieldAttributes()[classNum][i])); + } + // add methods + ClassFileEntry cfMethods[] = new ClassFileEntry[classBands + .getClassMethodCount()[classNum]]; + // methodDescr and methodFlags used to create this + for (i = 0; i < cfMethods.length; i++) { + int descriptorIndex = classBands.getMethodDescrInts()[classNum][i]; +// int colon = descriptorStr.indexOf(':'); + int nameIndex = cpBands.getCpDescriptorNameInts()[descriptorIndex]; + int typeIndex = cpBands.getCpDescriptorTypeInts()[descriptorIndex]; + CPUTF8 name = cpBands.cpUTF8Value(nameIndex); + CPUTF8 descriptor = cpBands.cpSignatureValue(typeIndex); + cfMethods[i] = cp.add(new CPMethod(name, descriptor, classBands + .getMethodFlags()[classNum][i], classBands + .getMethodAttributes()[classNum][i])); + } + cp.addNestedEntries(); + + // add inner class attribute (if required) + boolean addInnerClassesAttr = false; + IcTuple[] ic_local = getClassBands().getIcLocal()[classNum]; + boolean ic_local_sent = ic_local != null; + InnerClassesAttribute innerClassesAttribute = new InnerClassesAttribute( + "InnerClasses"); + IcTuple[] ic_relevant = getIcBands().getRelevantIcTuples(fullName, cp); + List ic_stored = computeIcStored(ic_local, ic_relevant); + for (int index = 0; index < ic_stored.size(); index++) { + IcTuple icStored = (IcTuple)ic_stored.get(index); + int innerClassIndex = icStored.thisClassIndex(); + int outerClassIndex = icStored.outerClassIndex(); + int simpleClassNameIndex = icStored.simpleClassNameIndex(); + + String innerClassString = icStored.thisClassString(); + String outerClassString = icStored.outerClassString(); + String simpleClassName = icStored.simpleClassName(); + + CPClass innerClass = null; + CPUTF8 innerName = null; + CPClass outerClass = null; + + innerClass = innerClassIndex != -1 ? cpBands + .cpClassValue(innerClassIndex) : cpBands + .cpClassValue(innerClassString); + if (!icStored.isAnonymous()) { + innerName = simpleClassNameIndex != -1 ? cpBands.cpUTF8Value( + simpleClassNameIndex) : cpBands + .cpUTF8Value(simpleClassName); + } + + if (icStored.isMember()) { + outerClass = outerClassIndex != -1 ? cpBands + .cpClassValue(outerClassIndex) : cpBands + .cpClassValue(outerClassString); + } + int flags = icStored.F; + innerClassesAttribute.addInnerClassesEntry(innerClass, outerClass, + innerName, flags); + addInnerClassesAttr = true; + } + // If ic_local is sent and it's empty, don't add + // the inner classes attribute. + if (ic_local_sent && (ic_local.length == 0)) { + addInnerClassesAttr = false; + } + + // If ic_local is not sent and ic_relevant is empty, + // don't add the inner class attribute. + if (!ic_local_sent && (ic_relevant.length == 0)) { + addInnerClassesAttr = false; + } + + if (addInnerClassesAttr) { + // Need to add the InnerClasses attribute to the + // existing classFile attributes. + Attribute[] originalAttrs = classFile.attributes; + Attribute[] newAttrs = new Attribute[originalAttrs.length + 1]; + for (int index = 0; index < originalAttrs.length; index++) { + newAttrs[index] = originalAttrs[index]; + } + newAttrs[newAttrs.length - 1] = innerClassesAttribute; + classFile.attributes = newAttrs; + cp.addWithNestedEntries(innerClassesAttribute); + } + // sort CP according to cp_All + cp.resolve(this); + // NOTE the indexOf is only valid after the cp.resolve() + // build up remainder of file + classFile.accessFlags = (int) classBands.getClassFlags()[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; + } + + /** + * Given an ic_local and an ic_relevant, use them to calculate what should + * be added as ic_stored. + * + * @param ic_local + * IcTuple[] array of local transmitted tuples + * @param ic_relevant + * IcTuple[] array of relevant tuples + * @return List of tuples to be stored. If ic_local is null or + * empty, the values returned may not be correct. The caller will + * have to determine if this is the case. + */ + private List computeIcStored(IcTuple[] ic_local, IcTuple[] ic_relevant) { + List result = new ArrayList(ic_relevant.length); + List duplicates = new ArrayList(ic_relevant.length); + Set isInResult = new HashSet(ic_relevant.length); + + // need to compute: + // result = ic_local XOR ic_relevant + + // add ic_local + if (ic_local != null) { + for(int index = 0; index < ic_local.length; index++) { + if (isInResult.add(ic_local[index])) { + result.add(ic_local[index]); + } + } + } + + // add ic_relevant + for(int index = 0; index < ic_relevant.length; index++) { + if (isInResult.add(ic_relevant[index])) { + result.add(ic_relevant[index]); + } else { + duplicates.add(ic_relevant[index]); + } + } + + // eliminate "duplicates" + for(int index = 0; index < duplicates.size(); index++) { + IcTuple tuple = (IcTuple)duplicates.get(index); + result.remove(tuple); + } + + return result; + } + + /** + * This performs reading the data from the stream into non-static instance of + * Segment. After the completion of this method stream can be freed. + * + * @param in + * the input stream to read from + * @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 void readSegment(InputStream in) throws IOException, + Pack200Exception { + log(LOG_LEVEL_VERBOSE, "-------"); + cpBands = new CpBands(this); + cpBands.read(in); + attrDefinitionBands = new AttrDefinitionBands(this); + attrDefinitionBands.read(in); + icBands = new IcBands(this); + icBands.read(in); + classBands = new ClassBands(this); + classBands.read(in); + bcBands = new BcBands(this); + bcBands.read(in); + fileBands = new FileBands(this); + fileBands.read(in); + + fileBands.processFileBits(); + } + + /** + * This performs the actual work of parsing against a non-static instance of + * Segment. This method is intended to run concurrently for multiple segments. + * + * @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 void parseSegment() throws IOException, Pack200Exception { + + header.unpack(); + cpBands.unpack(); + attrDefinitionBands.unpack(); + icBands.unpack(); + classBands.unpack(); + bcBands.unpack(); + fileBands.unpack(); + + int classNum = 0; + int numberOfFiles = header.getNumberOfFiles(); + String[] fileName = fileBands.getFileName(); + int[] fileOptions = fileBands.getFileOptions(); + SegmentOptions options = header.getOptions(); + + classFilesContents = new byte[numberOfFiles][]; + fileDeflate = new boolean[numberOfFiles]; + fileIsClass = new boolean[numberOfFiles]; + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + + for (int i = 0; i < numberOfFiles; i++) { + String name = fileName[i]; + + boolean nameIsEmpty = (name == null) || name.equals(""); + boolean isClass = (fileOptions[i] & 2) == 2 || nameIsEmpty; + if (isClass && nameIsEmpty) { + name = cpBands.getCpClass()[classBands.getClassThisInts()[classNum]] + ".class"; + fileName[i] = name; + } + + if (!overrideDeflateHint) { + fileDeflate[i] = (fileOptions[i] & 1) == 1 || options.shouldDeflate(); + } else { + fileDeflate[i] = deflateHint; + } + + fileIsClass[i] = isClass; + + if (isClass) { + ClassFile classFile = buildClassFile(classNum); + classFile.write(dos); + dos.flush(); + + classFilesContents[classNum] = bos.toByteArray(); + bos.reset(); + + classNum++; + } + } + } + + /** + * Unpacks a packed stream (either .pack. or .pack.gz) into a corresponding + * JarOuputStream. + * + * @throws Pack200Exception + * if there is a problem unpacking + * @throws IOException + * if there is a problem with I/O during unpacking + */ + public void unpack(InputStream in, JarOutputStream out) throws IOException, + Pack200Exception { + unpackRead(in); + unpackProcess(); + unpackWrite(out); + } + + /* + * Package-private accessors for unpacking stages + */ + void unpackRead(InputStream in) throws IOException, Pack200Exception { + if (!in.markSupported()) + in = new BufferedInputStream(in); + + header = new SegmentHeader(this); + header.read(in); + + int size = (int)header.getArchiveSize() - header.getArchiveSizeOffset(); + + if (doPreRead && header.getArchiveSize() != 0) { + byte[] data = new byte[size]; + in.read(data); + internalBuffer = new BufferedInputStream(new ByteArrayInputStream(data)); + } else { + readSegment(in); + } + } + + void unpackProcess() throws IOException, Pack200Exception { + if(internalBuffer != null) { + readSegment(internalBuffer); + } + parseSegment(); + } + + void unpackWrite(JarOutputStream out) throws IOException, Pack200Exception { + writeJar(out); + } + + /** + * 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 + * @throws IOException + * if an error occurs while reading or writing to the streams + * @throws Pack200Exception + * if an error occurs while processing data + */ + public void writeJar(JarOutputStream out) throws IOException, + Pack200Exception { + String[] fileName = fileBands.getFileName(); + int[] fileModtime = fileBands.getFileModtime(); + long[] fileSize = fileBands.getFileSize(); + byte[][] fileBits = fileBands.getFileBits(); + + // now write the files out + int classNum = 0; + int numberOfFiles = header.getNumberOfFiles(); + long archiveModtime = header.getArchiveModtime(); + + for (int i = 0; i < numberOfFiles; i++) { + String name = fileName[i]; + // For Pack200 archives, modtime is in seconds + // from the epoch. JarEntries need it to be in + // milliseconds from the epoch. + // Even though we're adding two longs and multiplying + // by 1000, we won't overflow because both longs are + // always under 2^32. + long modtime = 1000 * (archiveModtime + fileModtime[i]); + boolean deflate = fileDeflate[i]; + + JarEntry entry = new JarEntry(name); + if (deflate) { + entry.setMethod(ZipEntry.DEFLATED); + } else { + entry.setMethod(ZipEntry.STORED); + CRC32 crc = new CRC32(); + if(fileIsClass[i]) { + crc.update(classFilesContents[classNum]); + entry.setSize(classFilesContents[classNum].length); + } else { + crc.update(fileBits[i]); + entry.setSize(fileSize[i]); + } + entry.setCrc(crc.getValue()); + } + // On Windows at least, need to correct for timezone + entry.setTime(modtime - TimeZone.getDefault().getRawOffset()); + out.putNextEntry(entry); + + // write to output stream + if (fileIsClass[i]) { + entry.setSize(classFilesContents[classNum].length); + out.write(classFilesContents[classNum]); + classNum++; + } else { + entry.setSize(fileSize[i]); + out.write(fileBits[i]); + } + } + } + + public SegmentConstantPool getConstantPool() { + return cpBands.getConstantPool(); + } + + public SegmentHeader getSegmentHeader() { + return header; + } + + public void setPreRead(boolean value) { + doPreRead = value; + } + + protected AttrDefinitionBands getAttrDefinitionBands() { + return attrDefinitionBands; + } + + protected BcBands getBcBands() { + return bcBands; + } + + protected ClassBands getClassBands() { + return classBands; + } + + protected CpBands getCpBands() { + return cpBands; + } + + protected FileBands getFileBands() { + return fileBands; + } + + protected IcBands getIcBands() { + return icBands; + } + + public void setLogLevel(int logLevel) { + this.logLevel = logLevel; + } + + public void setLogStream(OutputStream logStream) { + this.logStream = new PrintWriter(logStream); + } + + public void log(int logLevel, String message) { + if (this.logLevel >= logLevel) { + logStream.println(message); + } + } + + /** + * Override the archive's deflate hint with the given boolean + * + * @param deflateHint - + * the deflate hint to use + */ + public void overrideDeflateHint(boolean deflateHint) { + this.overrideDeflateHint = true; + this.deflateHint = deflateHint; + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/SegmentConstantPool.java b/src/main/java/org/apache/harmony/unpack200/SegmentConstantPool.java new file mode 100644 index 0000000..8b91dcf --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/SegmentConstantPool.java @@ -0,0 +1,317 @@ +/* + * 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.unpack200; + +import java.util.List; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; +import org.apache.harmony.unpack200.bytecode.ConstantPoolEntry; + +/** + * SegmentConstantPool manages the constant pool used for re-creating class + * files. + */ +public class SegmentConstantPool { + + private final CpBands bands; + private final SegmentConstantPoolArrayCache arrayCache = new SegmentConstantPoolArrayCache(); + /** + * @param bands + */ + public SegmentConstantPool(CpBands bands) { + this.bands = bands; + } + + // define in archive order + + public static final int ALL = 0; + public static final int UTF_8 = 1; + public static final int CP_INT = 2; + public static final int CP_FLOAT = 3; + public static final int CP_LONG = 4; + public static final int CP_DOUBLE = 5; + public static final int CP_STRING = 6; + public static final int CP_CLASS = 7; + public static final int SIGNATURE = 8; // TODO and more to come -- + public static final int CP_DESCR = 9; + public static final int CP_FIELD = 10; + public static final int CP_METHOD = 11; + public static final int CP_IMETHOD = 12; + + protected static final String REGEX_MATCH_ALL = ".*"; + protected static final String INITSTRING = ""; + protected static final String REGEX_MATCH_INIT = "^" + INITSTRING + ".*"; + + public ClassFileEntry getValue(int cp, long value) throws Pack200Exception { + int index = (int) value; + if (index == -1) { + return null; + } else if (index < 0) { + throw new Pack200Exception("Cannot have a negative range"); + } else if (cp == UTF_8) { + return bands.cpUTF8Value(index); + } else if (cp == CP_INT) { + return bands.cpIntegerValue(index); + } else if (cp == CP_FLOAT) { + return bands.cpFloatValue(index); + } else if (cp == CP_LONG) { + return bands.cpLongValue(index); + } else if (cp == CP_DOUBLE) { + return bands.cpDoubleValue(index); + } else if (cp == CP_STRING) { + return bands.cpStringValue(index); + } else if (cp == CP_CLASS) { + return bands.cpClassValue(index); + } else if (cp == SIGNATURE) { + return bands.cpSignatureValue(index); + } else if (cp == CP_DESCR) { + return bands.cpNameAndTypeValue(index); + } else { + throw new Error("Tried to get a value I don't know about: " + cp); + } + } + + /** + * Subset the constant pool of the specified type to be just that which has + * the specified class name. Answer the ConstantPoolEntry at the + * desiredIndex of the subsetted pool. + * + * @param cp + * type of constant pool array to search + * @param desiredIndex + * index of the constant pool + * @param desiredClassName + * class to use to generate a subset of the pool + * @return ConstantPoolEntry + * @throws Pack200Exception + */ + public ConstantPoolEntry getClassSpecificPoolEntry(int cp, + long desiredIndex, String desiredClassName) throws Pack200Exception { + int index = (int) desiredIndex; + int realIndex = -1; + String array[] = null; + if (cp == CP_FIELD) { + array = bands.getCpFieldClass(); + } else if (cp == CP_METHOD) { + array = bands.getCpMethodClass(); + } else if (cp == CP_IMETHOD) { + array = bands.getCpIMethodClass(); + } else { + throw new Error("Don't know how to handle " + cp); + } + realIndex = matchSpecificPoolEntryIndex(array, desiredClassName, index); + return getConstantPoolEntry(cp, realIndex); + } + + /** + * Given the name of a class, answer the CPClass associated with that class. + * Answer null if the class doesn't exist. + * + * @param name + * Class name to look for (form: java/lang/Object) + * @return CPClass for that class name, or null if not found. + */ + public ConstantPoolEntry getClassPoolEntry(String name) { + String classes[] = bands.getCpClass(); + int index = matchSpecificPoolEntryIndex(classes, name, 0); + if (index == -1) { + return null; + } + try { + return getConstantPoolEntry(CP_CLASS, index); + } catch (Pack200Exception ex) { + throw new Error("Error getting class pool entry"); + } + } + + /** + * Answer the init method for the specified class. + * + * @param cp + * constant pool to search (must be CP_METHOD) + * @param value + * index of init method + * @param desiredClassName + * String class name of the init method + * @return CPMethod init method + * @throws Pack200Exception + */ + public ConstantPoolEntry getInitMethodPoolEntry(int cp, long value, + String desiredClassName) throws Pack200Exception { + int realIndex = -1; + String desiredRegex = REGEX_MATCH_INIT; + if (cp == CP_METHOD) { + realIndex = matchSpecificPoolEntryIndex(bands.getCpMethodClass(), + bands.getCpMethodDescriptor(), desiredClassName, + desiredRegex, (int) value); + } else { + throw new Error("Nothing but CP_METHOD can be an "); + } + return getConstantPoolEntry(cp, realIndex); + } + + /** + * A number of things make use of subsets of structures. In one particular + * example, _super bytecodes will use a subset of method or field classes + * which have just those methods / fields defined in the superclass. + * Similarly, _this bytecodes use just those methods/fields defined in this + * class, and _init bytecodes use just those methods that start with . + * + * This method takes an array of names, a String to match for, an index and + * a boolean as parameters, and answers the array position in the array of + * the indexth element which matches (or equals) the String (depending on + * the state of the boolean) + * + * In other words, if the class array consists of: Object [position 0, 0th + * instance of Object] String [position 1, 0th instance of String] String + * [position 2, 1st instance of String] Object [position 3, 1st instance of + * Object] Object [position 4, 2nd instance of Object] then + * matchSpecificPoolEntryIndex(..., "Object", 2, false) will answer 4. + * matchSpecificPoolEntryIndex(..., "String", 0, false) will answer 1. + * + * @param nameArray + * Array of Strings against which the compareString is tested + * @param compareString + * String for which to search + * @param desiredIndex + * nth element with that match (counting from 0) + * @return int index into nameArray, or -1 if not found. + */ + protected int matchSpecificPoolEntryIndex(String[] nameArray, + String compareString, int desiredIndex) { + return matchSpecificPoolEntryIndex(nameArray, nameArray, compareString, + REGEX_MATCH_ALL, desiredIndex); + } + + /** + * This method's function is to look through pairs of arrays. It keeps track + * of the number of hits it finds using the following basis of comparison + * for a hit: - the primaryArray[index] must be .equals() to the + * primaryCompareString - the secondaryArray[index] .matches() the + * secondaryCompareString. When the desiredIndex number of hits + * has been reached, the index into the original two arrays of + * the element hit is returned. + * + * @param primaryArray + * The first array to search + * @param secondaryArray + * The second array (must be same .length as primaryArray) + * @param primaryCompareString + * The String to compare against primaryArray using .equals() + * @param secondaryCompareRegex + * The String to compare against secondaryArray using .matches() + * @param desiredIndex + * The nth hit whose position we're seeking + * @return int index that represents the position of the nth hit in + * primaryArray and secondaryArray + */ + protected int matchSpecificPoolEntryIndex(String[] primaryArray, + String[] secondaryArray, String primaryCompareString, + String secondaryCompareRegex, int desiredIndex) { + int instanceCount = -1; + List indexList = arrayCache.indexesForArrayKey(primaryArray, primaryCompareString); + if(indexList.isEmpty()) { + // Primary key not found, no chance of finding secondary + return -1; + } + + for(int index=0; index < indexList.size(); index++) { + int arrayIndex = ((Integer)indexList.get(index)).intValue(); + if(regexMatches(secondaryCompareRegex, secondaryArray[arrayIndex])) { + instanceCount++; + if(instanceCount == desiredIndex) { + return arrayIndex; + } + } + } + // We didn't return in the for loop, so the desiredMatch + // with desiredIndex must not exist in the arrays. + return -1; + } + + /** + * We don't want a dependency on regex in Pack200. The only place one exists + * is in matchSpecificPoolEntryIndex(). To eliminate this dependency, we've + * implemented the world's stupidest regexMatch. It knows about the two + * forms we care about: .* (aka REGEX_MATCH_ALL) ^.* (aka + * REGEX_MATCH_INIT) and will answer correctly if those are passed as the + * regexString. + * + * @param regexString + * String against which the compareString will be matched + * @param compareString + * String to match against the regexString + * @return boolean true if the compareString matches the regexString; + * otherwise false. + */ + protected static boolean regexMatches(String regexString, + String compareString) { + if (REGEX_MATCH_ALL.equals(regexString)) { + return true; + } + if (REGEX_MATCH_INIT.equals(regexString)) { + if (compareString.length() < (INITSTRING.length())) { + return false; + } + return (INITSTRING.equals(compareString.substring(0, INITSTRING + .length()))); + } + throw new Error("regex trying to match a pattern I don't know: " + + regexString); + } + + public ConstantPoolEntry getConstantPoolEntry(int cp, long value) + throws Pack200Exception { + int index = (int) value; + if (index == -1) { + return null; + } else if (index < 0) { + throw new Pack200Exception("Cannot have a negative range"); + } else if (cp == UTF_8) { + return bands.cpUTF8Value(index); + } else if (cp == CP_INT) { + return bands.cpIntegerValue(index); + } else if (cp == CP_FLOAT) { + return bands.cpFloatValue(index); + } else if (cp == CP_LONG) { + return bands.cpLongValue(index); + } else if (cp == CP_DOUBLE) { + return bands.cpDoubleValue(index); + } else if (cp == CP_STRING) { + return bands.cpStringValue(index); + } else if (cp == CP_CLASS) { + return bands.cpClassValue(index); + } else if (cp == SIGNATURE) { + throw new Error("I don't know what to do with signatures yet"); + // return null /* new CPSignature(bands.getCpSignature()[index]) */; + } else if (cp == CP_DESCR) { + throw new Error("I don't know what to do with descriptors yet"); + // return null /* new CPDescriptor(bands.getCpDescriptor()[index]) + // */; + } else if (cp == CP_FIELD) { + return bands.cpFieldValue(index); + } else if (cp == CP_METHOD) { + return bands.cpMethodValue(index); + } else if (cp == CP_IMETHOD) { + return bands.cpIMethodValue(index); + } else { + // etc + throw new Error("Get value incomplete"); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/SegmentConstantPoolArrayCache.java b/src/main/java/org/apache/harmony/unpack200/SegmentConstantPoolArrayCache.java new file mode 100644 index 0000000..50df8f7 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/SegmentConstantPoolArrayCache.java @@ -0,0 +1,182 @@ +/* + * 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.unpack200; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; + +/** + * The SegmentConstantPool spends a lot of time searching + * through large arrays of Strings looking for matches. + * This can be sped up by caching the arrays in HashMaps + * so the String keys are looked up and resolve to positions + * in the array rather than iterating through the arrays + * each time. + * + * Because the arrays only grow (never shrink or change) + * we can use the last known size as a way to determine if + * the array has changed. + * + * Note that this cache must be synchronized externally + * if it is shared. + */ +public class SegmentConstantPoolArrayCache { + + protected IdentityHashMap knownArrays = new IdentityHashMap(1000); + + protected List lastIndexes; + protected String[] lastArray; + protected String lastKey; + + /** + * Answer the indices for the given key in the given + * array. If no such key exists in the cached array, + * answer -1. + * @param array String[] array to search for the value + * @param key String value for which to search + * @return List collection of index positions in the array + */ + public List indexesForArrayKey(String[] array, String key) { + if(!arrayIsCached(array)) { + cacheArray(array); + } + + // If the search is one we've just done, don't even + // bother looking and return the last indices. This + // is a second cache within the cache. This is + // efficient because we usually are looking for + // several secondary elements with the same primary + // key. + if((lastArray == array) && (lastKey == key)) { + return lastIndexes; + } + + // Remember the last thing we found. + lastArray = array; + lastKey = key; + lastIndexes = ((CachedArray)knownArrays.get(array)).indexesForKey(key); + + return lastIndexes; + } + + /** + * Given a String array, answer true if the + * array is correctly cached. Answer false + * if the array is not cached, or if the + * array cache is outdated. + * + * @param array of String + * @return boolean true if up-to-date cache, + * otherwise false. + */ + protected boolean arrayIsCached(String[] array) { + if(!knownArrays.containsKey(array)) { + return false; + } + CachedArray cachedArray = (CachedArray)knownArrays.get(array); + if(cachedArray.lastKnownSize() != array.length) { + return false; + } + return true; + } + + /** + * Cache the array passed in as the argument + * @param array String[] to cache + */ + protected void cacheArray(String[] array) { + if(arrayIsCached(array)) { + throw new IllegalArgumentException("Trying to cache an array that already exists"); + } + knownArrays.put(array, new CachedArray(array)); + // Invalidate the cache-within-a-cache + lastArray = null; + } + + /** + * CachedArray keeps track of the last known size of + * an array as well as a HashMap that knows the mapping + * from element values to the indices of the array + * which contain that value. + */ + protected class CachedArray { + String[] primaryArray; + int lastKnownSize; + HashMap primaryTable; + + public CachedArray(String[] array) { + super(); + this.primaryArray = array; + this.lastKnownSize = array.length; + this.primaryTable = new HashMap(lastKnownSize); + cacheIndexes(); + } + + /** + * Answer the last known size of the array cached. + * If the last known size is not the same as the + * current size, the array must have changed. + * @return int last known size of the cached array + */ + public int lastKnownSize() { + return lastKnownSize; + } + + /** + * Given a particular key, answer a List of + * index locations in the array which contain that + * key. + * + * If no elements are found, answer an empty list. + * + * @param key String element of the array + * @return List of indexes containing that key + * in the array. + */ + public List indexesForKey(String key) { + if(!primaryTable.containsKey(key)) { + return Collections.EMPTY_LIST; + } + return (List)primaryTable.get(key); + } + + /** + * Given a primaryArray, cache its values in a HashMap + * to provide a backwards mapping from element values + * to element indexes. For instance, a primaryArray + * of: + * {"Zero", "Foo", "Two", "Foo"} + * would yield a HashMap of: + * "Zero" -> 0 + * "Foo" -> 1, 3 + * "Two" -> 2 + * which is then cached. + */ + protected void cacheIndexes() { + for(int index=0; index < primaryArray.length; index++) { + String key = primaryArray[index]; + if(!primaryTable.containsKey(key)) { + primaryTable.put(key, new ArrayList()); + } + ((ArrayList)primaryTable.get(key)).add(new Integer(index)); + } + } + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/SegmentHeader.java b/src/main/java/org/apache/harmony/unpack200/SegmentHeader.java new file mode 100644 index 0000000..d48e104 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/SegmentHeader.java @@ -0,0 +1,450 @@ +/* + * 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.unpack200; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.harmony.pack200.BHSDCodec; +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; + +/** + * SegmentHeader is the header band of a {@link Segment} + */ +public class SegmentHeader { + + private int archiveMajor; + + private int archiveMinor; + + private long archiveModtime; + + private long archiveSize; + + private int attributeDefinitionCount; + + private InputStream bandHeadersInputStream; + + private int bandHeadersSize; + + private int classCount; + + private int cpClassCount; + + private int cpDescriptorCount; + + private int cpDoubleCount; + + private int cpFieldCount; + + private int cpFloatCount; + + private int cpIMethodCount; + + private int cpIntCount; + + private int cpLongCount; + + private int cpMethodCount; + + private int cpSignatureCount; + + private int cpStringCount; + + private int cpUTF8Count; + + private int defaultClassMajorVersion; + + private int defaultClassMinorVersion; + + private int innerClassCount; + + private int numberOfFiles; + + private int segmentsRemaining; + + private SegmentOptions options; + + private final Segment segment; + + /** + * The magic header for a Pack200 Segment is 0xCAFED00D. I wonder where they + * get their inspiration from ... + */ + private static final int[] magic = { 0xCA, 0xFE, 0xD0, 0x0D }; + + public SegmentHeader(Segment segment) { + this.segment = segment; + } + + public int getArchiveSizeOffset() { + return archiveSizeOffset; + } + + private int archiveSizeOffset; + + public void read(InputStream in) throws IOException, Pack200Exception, + Error, Pack200Exception { + + int word[] = decodeScalar("archive_magic_word", in, Codec.BYTE1, + magic.length); + for (int m = 0; m < magic.length; m++) + if (word[m] != magic[m]) + throw new Error("Bad header"); + setArchiveMinorVersion(decodeScalar("archive_minver", in, + Codec.UNSIGNED5)); + setArchiveMajorVersion(decodeScalar("archive_majver", in, + Codec.UNSIGNED5)); + options = new SegmentOptions(decodeScalar("archive_options", in, + Codec.UNSIGNED5)); + parseArchiveFileCounts(in); + parseArchiveSpecialCounts(in); + parseCpCounts(in); + parseClassCounts(in); + + if (getBandHeadersSize() > 0) { + byte[] bandHeaders = new byte[getBandHeadersSize()]; + readFully(in, bandHeaders); + setBandHeadersData(bandHeaders); + } + + archiveSizeOffset = archiveSizeOffset - in.available(); + } + + public void unpack() { + + } + + /** + * 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; + } + + /** + * 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: " + + version); + archiveMajor = version; + } + + public long getArchiveModtime() { + return archiveModtime; + } + + public int getArchiveMajor() { + return archiveMajor; + } + + public int getArchiveMinor() { + return archiveMinor; + } + + public int getAttributeDefinitionCount() { + return attributeDefinitionCount; + } + + public int getClassCount() { + return classCount; + } + + public int getCpClassCount() { + return cpClassCount; + } + + public int getCpDescriptorCount() { + return cpDescriptorCount; + } + + public int getCpDoubleCount() { + return cpDoubleCount; + } + + public int getCpFieldCount() { + return cpFieldCount; + } + + public int getCpFloatCount() { + return cpFloatCount; + } + + public int getCpIMethodCount() { + return cpIMethodCount; + } + + public int getCpIntCount() { + return cpIntCount; + } + + public int getCpLongCount() { + return cpLongCount; + } + + public int getCpMethodCount() { + return cpMethodCount; + } + + public int getCpSignatureCount() { + return cpSignatureCount; + } + + public int getCpStringCount() { + return cpStringCount; + } + + public int getCpUTF8Count() { + return cpUTF8Count; + } + + public int getDefaultClassMajorVersion() { + return defaultClassMajorVersion; + } + + public int getDefaultClassMinorVersion() { + return defaultClassMinorVersion; + } + + public int getInnerClassCount() { + return innerClassCount; + } + + public void setNumberOfFiles(int numberOfFiles) { + this.numberOfFiles = numberOfFiles; + } + + public long getArchiveSize() { + return archiveSize; + } + + /** + * Obtain the band headers data as an input stream. If no band headers are + * present, this will return an empty input stream to prevent any further + * reads taking place. + * + * Note that as a stream, data consumed from this input stream can't be + * re-used. Data is only read from this stream if the encoding is such that + * additional information needs to be decoded from the stream itself. + * + * @return the band headers input stream + */ + public InputStream getBandHeadersInputStream() { + if (bandHeadersInputStream == null) { + bandHeadersInputStream = new ByteArrayInputStream(new byte[0]); + } + return bandHeadersInputStream; + + } + + public int getNumberOfFiles() { + return numberOfFiles; + } + + public int getSegmentsRemaining() { + return segmentsRemaining; + } + + public SegmentOptions getOptions() { + return options; + } + + private void parseArchiveFileCounts(InputStream in) throws IOException, + Pack200Exception { + if (options.hasArchiveFileCounts()) { + setArchiveSize((long)decodeScalar("archive_size_hi", in, Codec.UNSIGNED5) << 32 + | decodeScalar("archive_size_lo", in, Codec.UNSIGNED5)); + archiveSizeOffset = in.available(); + setSegmentsRemaining(decodeScalar("archive_next_count", in, + Codec.UNSIGNED5)); + setArchiveModtime(decodeScalar("archive_modtime", in, + Codec.UNSIGNED5)); + numberOfFiles = decodeScalar("file_count", in, + Codec.UNSIGNED5); + } + } + + private void parseArchiveSpecialCounts(InputStream in) throws IOException, + Pack200Exception { + if (getOptions().hasSpecialFormats()) { + bandHeadersSize = decodeScalar("band_headers_size", in, + Codec.UNSIGNED5); + setAttributeDefinitionCount(decodeScalar("attr_definition_count", + in, Codec.UNSIGNED5)); + } + } + + private void parseClassCounts(InputStream in) throws IOException, + Pack200Exception { + innerClassCount = decodeScalar("ic_count", in, Codec.UNSIGNED5); + defaultClassMinorVersion = decodeScalar("default_class_minver", + in, Codec.UNSIGNED5); + defaultClassMajorVersion = decodeScalar("default_class_majver", + in, Codec.UNSIGNED5); + classCount = decodeScalar("class_count", in, Codec.UNSIGNED5); + } + + private void parseCpCounts(InputStream in) throws IOException, + Pack200Exception { + cpUTF8Count = decodeScalar("cp_Utf8_count", in, Codec.UNSIGNED5); + if (getOptions().hasCPNumberCounts()) { + cpIntCount = decodeScalar("cp_Int_count", in, Codec.UNSIGNED5); + cpFloatCount = decodeScalar("cp_Float_count", in, + Codec.UNSIGNED5); + cpLongCount = decodeScalar("cp_Long_count", in, + Codec.UNSIGNED5); + cpDoubleCount = decodeScalar("cp_Double_count", in, + Codec.UNSIGNED5); + } + cpStringCount = decodeScalar("cp_String_count", in, + Codec.UNSIGNED5); + cpClassCount = decodeScalar("cp_Class_count", in, Codec.UNSIGNED5); + cpSignatureCount = decodeScalar("cp_Signature_count", in, + Codec.UNSIGNED5); + cpDescriptorCount = decodeScalar("cp_Descr_count", in, + Codec.UNSIGNED5); + cpFieldCount = decodeScalar("cp_Field_count", in, Codec.UNSIGNED5); + cpMethodCount = decodeScalar("cp_Method_count", in, + Codec.UNSIGNED5); + cpIMethodCount = decodeScalar("cp_Imethod_count", in, + Codec.UNSIGNED5); + } + + /** + * Decode a number of scalars from the band file. A scalar is like a band, + * but does not perform any band code switching. + * + * @param name + * the name of the scalar (primarily for logging/debugging + * purposes) + * @param in + * the input stream to read from + * @param codec + * the codec for this scalar + * @return an array of decoded long[] values + * @throws IOException + * if there is a problem reading from the underlying input + * stream + * @throws Pack200Exception + * if there is a problem decoding the value or that the value is + * invalid + */ + private int[] decodeScalar(String name, InputStream in, BHSDCodec codec, + int n) throws IOException, Pack200Exception { + segment.log(Segment.LOG_LEVEL_VERBOSE, "Parsed #" + name + " (" + n + + ")"); + return codec.decodeInts(n, in); + } + + /** + * Decode a scalar from the band file. A scalar is like a band, but does not + * perform any band code switching. + * + * @param name + * the name of the scalar (primarily for logging/debugging + * purposes) + * @param in + * the input stream to read from + * @param codec + * the codec for this scalar + * @return the decoded value + * @throws IOException + * if there is a problem reading from the underlying input + * stream + * @throws Pack200Exception + * if there is a problem decoding the value or that the value is + * invalid + */ + private int decodeScalar(String name, InputStream in, BHSDCodec codec) + throws IOException, Pack200Exception { + int ret = codec.decode(in); + segment + .log(Segment.LOG_LEVEL_VERBOSE, "Parsed #" + name + " as " + + ret); + return ret; + } + + public void setArchiveModtime(long archiveModtime) { + this.archiveModtime = archiveModtime; + } + + public void setArchiveSize(long archiveSize) { + this.archiveSize = archiveSize; + } + + private void setAttributeDefinitionCount(long valuie) { + this.attributeDefinitionCount = (int) valuie; + } + + private void setBandHeadersData(byte[] bandHeaders) { + this.bandHeadersInputStream = new ByteArrayInputStream(bandHeaders); + } + + public void setSegmentsRemaining(long value) { + segmentsRemaining = (int) value; + } + + /** + * Completely reads in a byte array, akin to the implementation in + * {@link java.lang.DataInputStream}. TODO Refactor out into a separate + * InputStream handling class + * + * @param in + * the input stream to read from + * @param data + * the byte array to read into + * @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 void readFully(InputStream in, byte[] data) throws IOException, + Pack200Exception { + int total = in.read(data); + if (total == -1) + throw new EOFException("Failed to read any data from input stream"); + while (total < data.length) { + int delta = in.read(data, total, data.length - total); + if (delta == -1) + throw new EOFException( + "Failed to read some data from input stream"); + total += delta; + } + } + + public int getBandHeadersSize() { + return bandHeadersSize; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/SegmentOptions.java b/src/main/java/org/apache/harmony/unpack200/SegmentOptions.java new file mode 100644 index 0000000..37858a3 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/SegmentOptions.java @@ -0,0 +1,126 @@ +/* + * 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.unpack200; + +import org.apache.harmony.pack200.Pack200Exception; + +/** + * Stores the combinations of bit flags that can be used in the segment header + * options. Whilst this could be defined in {@link Segment}, it's cleaner to + * pull it out into a separate class, not least because methods can then be used + * to determine the semantic meaning of the flags. In languages with a + * pre-processor, these may be defined by macros that do bitflag manipulation + * instead. + */ +public class SegmentOptions { + + private static final int DEFLATE_HINT = 1 << 5; + + private static final int HAVE_ALL_CODE_FLAGS = 1 << 2; + + private static final int HAVE_CLASS_FLAGS_HI = 1 << 9; + + // private static final int UNUSED_3 = 2^3; + + private static final int HAVE_CODE_FLAGS_HI = 1 << 10; + + private static final int HAVE_CP_NUMBERS = 1 << 1; + + private static final int HAVE_FIELD_FLAGS_HI = 1 << 10; + + private static final int HAVE_FILE_HEADERS = 1 << 4; + + private static final int HAVE_FILE_MODTIME = 1 << 6; + + private static final int HAVE_FILE_OPTIONS = 1 << 7; + + private static final int HAVE_FILE_SIZE_HI = 1 << 8; + + private static final int HAVE_METHOD_FLAGS_HI = 1 << 11; + + private static final int HAVE_SPECIAL_FORMATS = 1 << 0; + + /** + * The bit flags that are defined as unused by the specification; + * specifically, every bit above bit 13 and bit 3. + */ + private static final int UNUSED = -1 << 13 | 1 << 3; + + private int options; + + /** + * Creates a new segment options with the given integer value. + * + * @param options + * the integer value to use as the flags + * @throws Pack200Exception + * if an unused bit (bit 3 or bit 13+) is non-zero + */ + public SegmentOptions(int options) throws Pack200Exception { + if ((options & UNUSED) != 0) + throw new Pack200Exception("Some unused flags are non-zero"); + this.options = options; + } + + public boolean hasAllCodeFlags() { + return (options & HAVE_ALL_CODE_FLAGS) != 0; + } + + public boolean hasArchiveFileCounts() { + return (options & HAVE_FILE_HEADERS) != 0; + } + + public boolean hasClassFlagsHi() { + return (options & HAVE_CLASS_FLAGS_HI) != 0; + } + + public boolean hasCodeFlagsHi() { + return (options & HAVE_CODE_FLAGS_HI) != 0; + } + + public boolean hasCPNumberCounts() { + return (options & HAVE_CP_NUMBERS) != 0; + } + + public boolean hasFieldFlagsHi() { + return (options & HAVE_FIELD_FLAGS_HI) != 0; + } + + public boolean hasFileModtime() { + return (options & HAVE_FILE_MODTIME) != 0; + } + + public boolean hasFileOptions() { + return (options & HAVE_FILE_OPTIONS) != 0; + } + + public boolean hasFileSizeHi() { + return (options & HAVE_FILE_SIZE_HI) != 0; + } + + public boolean hasMethodFlagsHi() { + return (options & HAVE_METHOD_FLAGS_HI) != 0; + } + + public boolean hasSpecialFormats() { + return (options & HAVE_SPECIAL_FORMATS) != 0; + } + + public boolean shouldDeflate() { + return (options & DEFLATE_HINT) != 0; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/SegmentUtils.java b/src/main/java/org/apache/harmony/unpack200/SegmentUtils.java new file mode 100644 index 0000000..c47a790 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/SegmentUtils.java @@ -0,0 +1,164 @@ +/* + * 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.unpack200; + +/** + * Utility class for unpack200 + */ +public final class SegmentUtils { + + public static int countArgs(String descriptor) { + return countArgs(descriptor, 1); + } + + public static int countInvokeInterfaceArgs(String descriptor) { + return countArgs(descriptor, 2); + } + + /** + * Count the number of arguments in the descriptor. Each long or double + * counts as widthOfLongsAndDoubles; all other arguments count as 1. + * + * @param descriptor + * String for which arguments are counted + * @param widthOfLongsAndDoubles + * int increment to apply for longs doubles. This is typically 1 + * when counting arguments alone, or 2 when counting arguments + * for invokeinterface. + * @return integer count + */ + protected static int countArgs(String descriptor, int widthOfLongsAndDoubles) { + int bra = descriptor.indexOf('('); + int ket = descriptor.indexOf(')'); + if (bra == -1 || ket == -1 || ket < bra) + throw new IllegalArgumentException("No arguments"); + + boolean inType = false; + boolean consumingNextType = false; + int count = 0; + for (int i = bra + 1; i < ket; i++) { + char charAt = descriptor.charAt(i); + if (inType && charAt == ';') { + inType = false; + consumingNextType = false; + } else if (!inType && charAt == 'L') { + inType = true; + count++; + } else if (charAt == '[') { + consumingNextType = true; + } else if (inType) { + // NOP + } else { + if (consumingNextType) { + count++; + consumingNextType = false; + } else { + if (charAt == 'D' || charAt == 'J') { + count += widthOfLongsAndDoubles; + } else { + count++; + } + } + } + } + return count; + } + + public static int countMatches(long[] flags, IMatcher matcher) { + int count = 0; + for (int i = 0; i < flags.length; i++) { + if (matcher.matches(flags[i])) + count++; + } + return count; + } + + public static int countBit16(int[] flags) { + int count = 0; + for (int i = 0; i < flags.length; i++) { + if ((flags[i] & (1 << 16)) != 0) + count++; + } + return count; + } + + public static int countBit16(long[] flags) { + int count = 0; + for (int i = 0; i < flags.length; i++) { + if ((flags[i] & (1 << 16)) != 0) + count++; + } + return count; + } + + public static int countBit16(long[][] flags) { + int count = 0; + for (int i = 0; i < flags.length; i++) { + for (int j = 0; j < flags[i].length; j++) { + if ((flags[i][j] & (1 << 16)) != 0) + count++; + } + } + return count; + } + + public static int countMatches(long[][] flags, IMatcher matcher) { + int count = 0; + for (int i = 0; i < flags.length; i++) { + count += countMatches(flags[i], matcher); + } + return count; + } + + /** + * Answer the index of the first character <= '$' in the parameter. This is + * used instead of indexOf('$') because inner classes may be separated by + * any character <= '$' (in other words, Foo#Bar is as valid as Foo$Bar). If + * no $ character is found, answer -1. + * + * @param string + * String to search for $ + * @return first index of $ character, or -1 if not found + */ + public static int indexOfFirstDollar(String string) { + for (int index = 0; index < string.length(); index++) { + if (string.charAt(index) <= '$') { + return index; + } + } + return -1; + } + + private SegmentUtils() { + // Intended to be a helper class + } + + /** + * This is a debugging message to aid the developer in writing this class. + * If the property 'debug.unpack200' is set, this will generate messages to + * stderr; otherwise, it will be silent. + * + * @param message + * @deprecated this may be removed from production code + */ + public static void debug(String message) { + if (System.getProperty("debug.unpack200") != null) { + System.err.println(message); + } + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/AnnotationDefaultAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/AnnotationDefaultAttribute.java new file mode 100644 index 0000000..bd94809 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/AnnotationDefaultAttribute.java @@ -0,0 +1,73 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * AnnotationDefault class file attribute + */ +public class AnnotationDefaultAttribute extends AnnotationsAttribute { + + private final ElementValue element_value; + + private static CPUTF8 attributeName; + + public static void setAttributeName(CPUTF8 cpUTF8Value) { + attributeName = cpUTF8Value; + } + public AnnotationDefaultAttribute(ElementValue element_value) { + super(attributeName); + this.element_value = element_value; + } + + protected int getLength() { + return element_value.getLength(); + } + + protected void writeBody(DataOutputStream dos) throws IOException { + element_value.writeBody(dos); + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + element_value.resolve(pool); + } + + public String toString() { + return "AnnotationDefault: " + element_value; + } + + public boolean equals(Object obj) { + return this == obj; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + List nested = new ArrayList(); + nested.add(attributeName); + nested.addAll(element_value.getClassFileEntries()); + ClassFileEntry[] nestedEntries = new ClassFileEntry[nested.size()]; + for (int i = 0; i < nestedEntries.length; i++) { + nestedEntries[i] = (ClassFileEntry) nested.get(i); + } + return nestedEntries; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/AnnotationsAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/AnnotationsAttribute.java new file mode 100644 index 0000000..529cdbb --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/AnnotationsAttribute.java @@ -0,0 +1,190 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Abstract superclass for Annotations attributes + */ +public abstract class AnnotationsAttribute extends Attribute { + + /** + * Class to represent the annotation structure for class file attributes + */ + public static class Annotation { + + private final int num_pairs; + private final CPUTF8[] element_names; + private final ElementValue[] element_values; + private final CPUTF8 type; + + // Resolved values + private int type_index; + private int[] name_indexes; + + public Annotation(int num_pairs, CPUTF8 type, CPUTF8[] element_names, + ElementValue[] element_values) { + this.num_pairs = num_pairs; + this.type = type; + this.element_names = element_names; + this.element_values = element_values; + } + + public int getLength() { + int length = 4; + for (int i = 0; i < num_pairs; i++) { + length += 2; + length += element_values[i].getLength(); + } + return length; + } + + public void resolve(ClassConstantPool pool) { + type.resolve(pool); + type_index = pool.indexOf(type); + name_indexes = new int[num_pairs]; + for (int i = 0; i < element_names.length; i++) { + element_names[i].resolve(pool); + name_indexes[i] = pool.indexOf(element_names[i]); + element_values[i].resolve(pool); + } + } + + public void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(type_index); + dos.writeShort(num_pairs); + for (int i = 0; i < num_pairs; i++) { + dos.writeShort(name_indexes[i]); + element_values[i].writeBody(dos); + } + } + + public List getClassFileEntries() { + List entries = new ArrayList(); + for (int i = 0; i < element_names.length; i++) { + entries.add(element_names[i]); + entries.addAll(element_values[i].getClassFileEntries()); + } + entries.add(type); + return entries; + } + } + + public static class ElementValue { + + private final Object value; + private final int tag; + + // resolved value index if it's a constant + private int constant_value_index = -1; + + public ElementValue(int tag, Object value) { + this.tag = tag; + this.value = value; + } + + public List getClassFileEntries() { + List entries = new ArrayList(1); + if(value instanceof CPNameAndType) { + // used to represent enum, so don't include the actual CPNameAndType + entries.add(((CPNameAndType)value).name); + entries.add(((CPNameAndType)value).descriptor); + } else if(value instanceof ClassFileEntry) { + entries.add(value); + } else if (value instanceof ElementValue[]) { + ElementValue[] values = (ElementValue[]) value; + for (int i = 0; i < values.length; i++) { + entries.addAll(values[i].getClassFileEntries()); + } + } else if (value instanceof Annotation) { + entries.addAll(((Annotation)value).getClassFileEntries()); + } + return entries; + } + + public void resolve(ClassConstantPool pool) { + if (value instanceof CPConstant) { + ((CPConstant) value).resolve(pool); + constant_value_index = pool.indexOf((CPConstant) value); + } else if (value instanceof CPClass) { + ((CPClass) value).resolve(pool); + constant_value_index = pool.indexOf((CPClass) value); + } else if (value instanceof CPNameAndType) { + ((CPNameAndType) value).resolve(pool); + } else if (value instanceof Annotation) { + ((Annotation) value).resolve(pool); + } else if (value instanceof ElementValue[]) { + ElementValue[] nestedValues = (ElementValue[]) value; + for (int i = 0; i < nestedValues.length; i++) { + nestedValues[i].resolve(pool); + } + } + } + + public void writeBody(DataOutputStream dos) throws IOException { + dos.writeByte(tag); + if (constant_value_index != -1) { + dos.writeShort(constant_value_index); + } else if (value instanceof CPNameAndType) { + ((CPNameAndType) value).writeBody(dos); + } else if (value instanceof Annotation) { + ((Annotation) value).writeBody(dos); + } else if (value instanceof ElementValue[]) { + ElementValue[] nestedValues = (ElementValue[]) value; + for (int i = 0; i < nestedValues.length; i++) { + nestedValues[i].writeBody(dos); + } + } + } + + public int getLength() { + switch (tag) { + case 'B': + case 'C': + case 'D': + case 'F': + case 'I': + case 'J': + case 'S': + case 'Z': + case 'c': + return 3; + case 'e': + return 5; + case '[': + int length = 3; + ElementValue[] nestedValues = (ElementValue[]) value; + for (int i = 0; i < nestedValues.length; i++) { + length += nestedValues[i].getLength(); + } + return length; + case '@': + return (1 + ((Annotation) value).getLength()); + } + return 0; + } + } + + public AnnotationsAttribute(CPUTF8 attributeName) { + super(attributeName); + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/Attribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/Attribute.java new file mode 100644 index 0000000..6e68a1f --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/Attribute.java @@ -0,0 +1,115 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Abstract superclass for class file attributes + */ +public abstract class Attribute extends ClassFileEntry { + + protected final CPUTF8 attributeName; + + private int attributeNameIndex; + + public Attribute(CPUTF8 attributeName) { + this.attributeName = attributeName; + } + + protected void doWrite(DataOutputStream dos) throws IOException { + dos.writeShort(attributeNameIndex); + dos.writeInt(getLength()); + writeBody(dos); + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (this.getClass() != obj.getClass()) + return false; + final Attribute other = (Attribute) obj; + if (attributeName == null) { + if (other.attributeName != null) + return false; + } else if (!attributeName.equals(other.attributeName)) + return false; + return true; + } + + protected CPUTF8 getAttributeName() { + return attributeName; + } + + protected abstract int getLength(); + + /** + * Answer the length of the receiver including its header (the u2 for the + * attribute name and the u4 for the attribute length). This is relevant + * when attributes are nested within other attributes - the outer attribute + * needs to take the inner attribute headers into account when calculating + * its length. + * + * @return int adjusted length + */ + protected int getLengthIncludingHeader() { + return getLength() + 2 + 4; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + return new ClassFileEntry[] { getAttributeName() }; + } + + /** + * Answer true if the receiver needs to have BCI renumbering applied to it; + * otherwise answer false. + * + * @return boolean BCI renumbering required + */ + public boolean hasBCIRenumbering() { + return false; + } + + /** + * Answer true if the receiver is a source file attribute (which gets + * special handling when the class is built); otherwise answer false. + * + * @return boolean source file attribute + */ + public boolean isSourceFileAttribute() { + return false; + } + + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + + ((attributeName == null) ? 0 : attributeName.hashCode()); + return result; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + attributeNameIndex = pool.indexOf(attributeName); + } + + protected abstract void writeBody(DataOutputStream dos) throws IOException; + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/BCIRenumberedAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/BCIRenumberedAttribute.java new file mode 100644 index 0000000..526f593 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/BCIRenumberedAttribute.java @@ -0,0 +1,78 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.List; + +import org.apache.harmony.pack200.Pack200Exception; + +/** + * Abstract superclass for attributes that have some part encoded with a BCI + * renumbering + */ +public abstract class BCIRenumberedAttribute extends Attribute { + + protected boolean renumbered; + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#hasBCIRenumbering() + */ + public boolean hasBCIRenumbering() { + return true; + } + + public BCIRenumberedAttribute(CPUTF8 attributeName) { + super(attributeName); + } + + protected abstract int getLength(); + + protected abstract void writeBody(DataOutputStream dos) throws IOException; + + public abstract String toString(); + + protected abstract int[] getStartPCs(); + + /** + * In Pack200, line number tables are BCI renumbered. This method takes the + * byteCodeOffsets (which is a List of Integers specifying the offset in the + * byte code array of each instruction) and updates the start_pcs so that it + * points to the instruction index itself, not the BCI renumbering of the + * instruction. + * + * @param byteCodeOffsets + * List of Integer offsets of the bytecode array + * @throws Pack200Exception + */ + public void renumber(List byteCodeOffsets) throws Pack200Exception { + if (renumbered) { + throw new Error( + "Trying to renumber a line number table that has already been renumbered"); + } + renumbered = true; + int[] startPCs = getStartPCs(); + for (int index = 0; index < startPCs.length; index++) { + startPCs[index] = ((Integer) byteCodeOffsets.get(startPCs[index])) + .intValue(); + } + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/ByteCode.java b/src/main/java/org/apache/harmony/unpack200/bytecode/ByteCode.java new file mode 100644 index 0000000..cc4d0e1 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/ByteCode.java @@ -0,0 +1,383 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +import org.apache.harmony.unpack200.Segment; +import org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm; + +/** + * A bytecode class file entry. + */ +public class ByteCode extends ClassFileEntry { + + public static ByteCode getByteCode(int opcode) { + int byteOpcode = 0xFF & opcode; + if(ByteCodeForm.get(byteOpcode).hasNoOperand()) { + if(null == noArgByteCodes[byteOpcode]) { + noArgByteCodes[byteOpcode] = new ByteCode(byteOpcode); + } + return noArgByteCodes[byteOpcode]; + } + return new ByteCode(byteOpcode); + } + + private static ByteCode[] noArgByteCodes = new ByteCode[255]; + + private final ByteCodeForm byteCodeForm; + + private ClassFileEntry[] nested; + private int[][] nestedPositions; + private int[] rewrite; + + private int byteCodeOffset = -1; + private int[] byteCodeTargets; + + protected ByteCode(int opcode) { + this(opcode, ClassFileEntry.NONE); + } + + protected ByteCode(int opcode, ClassFileEntry[] nested) { + this.byteCodeForm = ByteCodeForm.get(opcode); + this.rewrite = byteCodeForm.getRewriteCopy(); + this.nested = nested; + } + + protected void doWrite(DataOutputStream dos) throws IOException { + for (int i = 0; i < rewrite.length; i++) { + dos.writeByte(rewrite[i]); + } + } + + public boolean equals(Object obj) { + return this == obj; + } + + public void extractOperands(OperandManager operandManager, Segment segment, + int codeLength) { + // Given an OperandTable, figure out which operands + // the receiver needs and stuff them in operands. + // Later on the operands can be rewritten (But that's + // later, not now). + ByteCodeForm currentByteCodeForm = getByteCodeForm(); + currentByteCodeForm.setByteCodeOperands(this, operandManager, + codeLength); + } + + protected ByteCodeForm getByteCodeForm() { + return byteCodeForm; + } + + public int getLength() { + return rewrite.length; + } + + public String getName() { + return getByteCodeForm().getName(); + } + + public ClassFileEntry[] getNestedClassFileEntries() { + return nested; + } + + public int getOpcode() { + return getByteCodeForm().getOpcode(); + } + + public int getOperandType() { + return getByteCodeForm().getOperandType(); + } + + private boolean hashcodeComputed; + private int cachedHashCode; + + private void generateHashCode() { + cachedHashCode = objectHashCode(); + } + + public int hashCode() { + if (!hashcodeComputed) + generateHashCode(); + return cachedHashCode; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.ClassFileEntry#resolve(org.apache.harmony.unpack200.bytecode.ClassConstantPool) + */ + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + if (nested.length > 0) { + // Update the bytecode rewrite array so that it points + // to the elements of the nested array. + for (int index = 0; index < nested.length; index++) { + int argLength = getNestedPosition(index)[1]; + switch (argLength) { + + case 1: + setOperandByte(pool.indexOf(nested[index]), + getNestedPosition(index)[0]); + break; + + case 2: + setOperand2Bytes(pool.indexOf(nested[index]), + getNestedPosition(index)[0]); + break; + + default: + throw new Error("Unhandled resolve " + this); + } + } + } + } + + /** + * Given an array of ints which correspond to bytes in the operand of the + * bytecode, set the rewrite bytes of the operand to be the appropriate + * values. All values in operands[] will be masked with 0xFF so they fit + * into a byte. + * + * @param operands + * int[] rewrite operand bytes + */ + public void setOperandBytes(int[] operands) { + int firstOperandIndex = getByteCodeForm().firstOperandIndex(); + int byteCodeFormLength = getByteCodeForm().operandLength(); + if (firstOperandIndex < 1) { + // No operand rewriting permitted for this bytecode + throw new Error("Trying to rewrite " + this + + " that has no rewrite"); + } + + if (byteCodeFormLength != operands.length) { + throw new Error("Trying to rewrite " + this + " with " + + operands.length + " but bytecode has length " + + byteCodeForm.operandLength()); + } + + for (int index = 0; index < byteCodeFormLength; index++) { + rewrite[index + firstOperandIndex] = operands[index] & 0xFF; + } + } + + /** + * Given an int operand, set the rewrite bytes for that position and the one + * immediately following it to a high-byte, low-byte encoding of the + * operand. + * + * @param operand + * int to set the rewrite bytes to + * @param position + * int position in the operands of the rewrite bytes. For a + * rewrite array of {100, -1, -1, -1} position 0 is the first -1, + * position 1 is the second -1, etc. + */ + public void setOperand2Bytes(int operand, int position) { + int firstOperandIndex = getByteCodeForm().firstOperandIndex(); + int byteCodeFormLength = getByteCodeForm().getRewrite().length; + if (firstOperandIndex < 1) { + // No operand rewriting permitted for this bytecode + throw new Error("Trying to rewrite " + this + + " that has no rewrite"); + } + + if (firstOperandIndex + position + 1 > byteCodeFormLength) { + throw new Error("Trying to rewrite " + this + + " with an int at position " + position + + " but this won't fit in the rewrite array"); + } + + rewrite[firstOperandIndex + position] = (operand & 0xFF00) >> 8; + rewrite[firstOperandIndex + position + 1] = operand & 0xFF; + } + + /** + * This is just like setOperandInt, but takes special care when the operand + * is less than 0 to make sure it's written correctly. + * + * @param operand + * int to set the rewrite bytes to + * @param position + * int position of the operands in the rewrite bytes + */ + public void setOperandSigned2Bytes(int operand, int position) { + if (operand >= 0) { + setOperand2Bytes(operand, position); + } else { + int twosComplementOperand = 0x10000 + operand; + setOperand2Bytes(twosComplementOperand, position); + } + } + + /** + * Given an int operand, treat it as a byte and set the rewrite byte for + * that position to that value. Mask of anything beyond 0xFF. + * + * @param operand + * int to set the rewrite byte to (unsigned) + * @param position + * int position in the operands of the rewrite bytes. For a + * rewrite array of {100, -1, -1, -1} position 0 is the first -1, + * position 1 is the second -1, etc. + */ + public void setOperandByte(int operand, int position) { + int firstOperandIndex = getByteCodeForm().firstOperandIndex(); + int byteCodeFormLength = getByteCodeForm().operandLength(); + if (firstOperandIndex < 1) { + // No operand rewriting permitted for this bytecode + throw new Error("Trying to rewrite " + this + + " that has no rewrite"); + } + + if (firstOperandIndex + position > byteCodeFormLength) { + throw new Error("Trying to rewrite " + this + + " with an byte at position " + position + + " but this won't fit in the rewrite array"); + } + + rewrite[firstOperandIndex + position] = operand & 0xFF; + } + + public String toString() { + return getByteCodeForm().getName(); + } + + public void setNested(ClassFileEntry[] nested) { + this.nested = nested; + } + + /** + * nestedPositions is an array of arrays of ints. Each subarray specifies a + * position of a nested element (from the nested[] array) and the length of + * that element. + * + * For instance, one might have a nested of: {CPClass java/lang/Foo, CPFloat + * 3.14} The nestedPositions would then be: {{0,2},{2,2}} In other words, + * when the bytecode is resolved, the CPClass will be resolved to an int and + * inserted at position 0 and 1 of the rewrite arguments (the first + * occurrences of -1). The CPFloat will be resolved to an int position and + * inserted at positions 2 and 3 of the rewrite arguments. + * + * @param nestedPositions + */ + public void setNestedPositions(int[][] nestedPositions) { + this.nestedPositions = nestedPositions; + } + + public int[][] getNestedPositions() { + return nestedPositions; + } + + public int[] getNestedPosition(int index) { + return getNestedPositions()[index]; + } + + /** + * This method will answer true if the receiver is a multi-bytecode + * instruction (such as aload0_putfield_super); otherwise, it will answer + * false. + * + * @return boolean true if multibytecode, false otherwise + */ + public boolean hasMultipleByteCodes() { + return getByteCodeForm().hasMultipleByteCodes(); + } + + /** + * ByteCodes may need to know their position in the code array (in + * particular, label byte codes need to know where they are in order to + * calculate their targets). This method lets the CodeAttribute specify + * where the byte code is. + * + * Since there are no aload0+label instructions, this method doesn't worry + * about multioperation bytecodes. + * + * @param byteCodeOffset + * int position in code array. + */ + public void setByteCodeIndex(int byteCodeOffset) { + this.byteCodeOffset = byteCodeOffset; + } + + public int getByteCodeIndex() { + return byteCodeOffset; + } + + /** + * Some ByteCodes (in particular, those with labels) have to keep track of + * byteCodeTargets. These are initially offsets in the CodeAttribute array + * relative to the byteCodeOffset, but later get fixed up to point to the + * absolute position in the CodeAttribute array. This method sets the + * targets. + * + * @param byteCodeTargets + * int index in array + */ + public void setByteCodeTargets(int[] byteCodeTargets) { + this.byteCodeTargets = byteCodeTargets; + } + + public int[] getByteCodeTargets() { + return byteCodeTargets; + } + + /** + * Some ByteCodes (in particular, those with labels need to be fixed up + * after all the bytecodes in the CodeAttribute have been added. (This can't + * be done beforehand because the CodeAttribute needs to be complete before + * targets can be assigned.) + * + * @param codeAttribute + * the code attribute + */ + public void applyByteCodeTargetFixup(CodeAttribute codeAttribute) { + getByteCodeForm().fixUpByteCodeTargets(this, codeAttribute); + } + + /** + * Some bytecodes (the ones with variable lengths) can't have a static + * rewrite array - they need the ability to update the array. This method + * permits that. + * + * Note that this should not be called from bytecodes which have a static + * rewrite; use the table in ByteCodeForm instead to specify those rewrites. + * + * @param rewrite + */ + public void setRewrite(int[] rewrite) { + this.rewrite = rewrite; + } + + /** + * Some bytecodes (the ones with variable lengths) can't have a static + * rewrite array - they need the ability to update the array. This method + * permits their associated bytecode formst to query their rewrite array. + * + * Note that this should not be called from bytecodes which have a static + * rewrite; use the table in ByteCodeForm instead to specify those rewrites. + */ + public int[] getRewrite() { + return rewrite; + } + + public boolean nestedMustStartClassPool() { + return byteCodeForm.nestedMustStartClassPool(); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPClass.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPClass.java new file mode 100644 index 0000000..5587209 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPClass.java @@ -0,0 +1,96 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Constant pool entry for a class + */ +public class CPClass extends ConstantPoolEntry { + + private int index; + + public String name; + + private final CPUTF8 utf8; + + /** + * Creates a new CPClass + * + * @param name + * @param globalIndex + * index in CpBands + * @throws NullPointerException + * if name is null + */ + public CPClass(CPUTF8 name, int globalIndex) { + super(ConstantPoolEntry.CP_Class, globalIndex); + if (name == null) { + throw new NullPointerException("Null arguments are not allowed"); + } + this.name = name.underlyingString(); + this.utf8 = name; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (this.getClass() != obj.getClass()) + return false; + final CPClass other = (CPClass) obj; + return utf8.equals(other.utf8); + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + return new ClassFileEntry[] { utf8, }; + } + + private boolean hashcodeComputed; + private int cachedHashCode; + + private void generateHashCode() { + hashcodeComputed = true; + cachedHashCode = utf8.hashCode(); + } + + public int hashCode() { + if (!hashcodeComputed) + generateHashCode(); + return cachedHashCode; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + index = pool.indexOf(utf8); + } + + public String toString() { + return "Class: " + getName(); + } + + public String getName() { + return name; + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(index); + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPConstant.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPConstant.java new file mode 100644 index 0000000..ab4be3a --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPConstant.java @@ -0,0 +1,71 @@ +/* + * 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.unpack200.bytecode; + +/** + * Abstract superclass for constant pool constant entries such as numbers or + * Strings + */ +public abstract class CPConstant extends ConstantPoolEntry { + + private final Object value; + + /** + * Create a new CPConstant + * + * @param tag + * @param value + * @param globalIndex + * index in CpBands + * @throws NullPointerException + * if value is null + */ + public CPConstant(byte tag, Object value, int globalIndex) { + super(tag, globalIndex); + this.value = value; + if (value == null) { + throw new NullPointerException("Null arguments are not allowed"); + } + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (this.getClass() != obj.getClass()) + return false; + final CPConstant other = (CPConstant) obj; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + return true; + } + + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + protected Object getValue() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPConstantNumber.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPConstantNumber.java new file mode 100644 index 0000000..9b9d203 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPConstantNumber.java @@ -0,0 +1,32 @@ +/* + * 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.unpack200.bytecode; + +/** + * Abstract superclass for constant pool entries that are numbers. + */ +public abstract class CPConstantNumber extends CPConstant { + + public CPConstantNumber(byte tag, Object value, int globalIndex) { + super(tag, value, globalIndex); + } + + protected Number getNumber() { + return (Number) getValue(); + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPDouble.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPDouble.java new file mode 100644 index 0000000..b7d0910 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPDouble.java @@ -0,0 +1,39 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Double constant pool entry. + */ +public class CPDouble extends CPConstantNumber { + + public CPDouble(Double value, int globalIndex) { + super(ConstantPoolEntry.CP_Double, value, globalIndex); + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeDouble(getNumber().doubleValue()); + } + + public String toString() { + return "Double: " + getValue(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPField.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPField.java new file mode 100644 index 0000000..c05f1fc --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPField.java @@ -0,0 +1,34 @@ +/* + * 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.unpack200.bytecode; + +import java.util.List; + +/** + * Field constant pool entry. + */ +public class CPField extends CPMember { + + public CPField(CPUTF8 name, CPUTF8 descriptor, long flags, List attributes) { + super(name, descriptor, flags, attributes); + } + + public String toString() { + return "Field: " + name + "(" + descriptor + ")"; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPFieldRef.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPFieldRef.java new file mode 100644 index 0000000..adba84f --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPFieldRef.java @@ -0,0 +1,98 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Field reference constant pool entry. + */ +public class CPFieldRef extends ConstantPoolEntry { + + CPClass className; + transient int classNameIndex; + private final CPNameAndType nameAndType; + transient int nameAndTypeIndex; + + public CPFieldRef(CPClass className, CPNameAndType descriptor, int globalIndex) { + super(ConstantPoolEntry.CP_Fieldref, globalIndex); + this.className = className; + this.nameAndType = descriptor; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + return new ClassFileEntry[] { className, nameAndType }; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + nameAndTypeIndex = pool.indexOf(nameAndType); + classNameIndex = pool.indexOf(className); + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(classNameIndex); + dos.writeShort(nameAndTypeIndex); + } + + public String toString() { + return "FieldRef: " + className + "#" + nameAndType; + } + + private boolean hashcodeComputed; + private int cachedHashCode; + + private void generateHashCode() { + hashcodeComputed = true; + final int PRIME = 31; + int result = 1; + result = PRIME * result + + ((className == null) ? 0 : className.hashCode()); + result = PRIME * result + + ((nameAndType == null) ? 0 : nameAndType.hashCode()); + cachedHashCode = result; + } + + public int hashCode() { + if (!hashcodeComputed) + generateHashCode(); + return cachedHashCode; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final CPFieldRef other = (CPFieldRef) obj; + if (className == null) { + if (other.className != null) + return false; + } else if (!className.equals(other.className)) + return false; + if (nameAndType == null) { + if (other.nameAndType != null) + return false; + } else if (!nameAndType.equals(other.nameAndType)) + return false; + return true; + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPFloat.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPFloat.java new file mode 100644 index 0000000..dedcb32 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPFloat.java @@ -0,0 +1,39 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Float constant pool entry. + */ +public class CPFloat extends CPConstantNumber { + + public CPFloat(Float value, int globalIndex) { + super(ConstantPoolEntry.CP_Float, value, globalIndex); + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeFloat(getNumber().floatValue()); + } + + public String toString() { + return "Float: " + getValue(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPInteger.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPInteger.java new file mode 100644 index 0000000..531d95e --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPInteger.java @@ -0,0 +1,39 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Integer constant pool entry. + */ +public class CPInteger extends CPConstantNumber { + + public CPInteger(Integer value, int globalIndex) { + super(ConstantPoolEntry.CP_Integer, value, globalIndex); + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeInt(getNumber().intValue()); + } + + public String toString() { + return "Integer: " + getValue(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPInterfaceMethodRef.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPInterfaceMethodRef.java new file mode 100644 index 0000000..5ead087 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPInterfaceMethodRef.java @@ -0,0 +1,54 @@ +/* + * 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.unpack200.bytecode; + +/** + * Interface method reference constant pool entry. + */ +public class CPInterfaceMethodRef extends CPRef { + + public CPInterfaceMethodRef(CPClass className, CPNameAndType descriptor, int globalIndex) { + super(ConstantPoolEntry.CP_InterfaceMethodref, className, descriptor, globalIndex); + } + + /** + * This method answers the value this method will use for an invokeinterface + * call. This is equal to 1 + the count of all the args, where longs and + * doubles count for 2 and all others count for 1. + * + * @return integer count + */ + public int invokeInterfaceCount() { + return nameAndType.invokeInterfaceCount(); + } + + private boolean hashcodeComputed; + private int cachedHashCode; + + private void generateHashCode() { + hashcodeComputed = true; + final int PRIME = 31; + int result = 1; + result = PRIME * result + className.hashCode(); + result = PRIME * result + nameAndType.hashCode(); + cachedHashCode = result; + } + + public int hashCode() { + if (!hashcodeComputed) + generateHashCode(); + return cachedHashCode; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPLong.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPLong.java new file mode 100644 index 0000000..288f91d --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPLong.java @@ -0,0 +1,39 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Long constant pool entry. + */ +public class CPLong extends CPConstantNumber { + + public CPLong(Long value, int globalIndex) { + super(ConstantPoolEntry.CP_Long, value, globalIndex); + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeLong(getNumber().longValue()); + } + + public String toString() { + return "Long: " + getValue(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPMember.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPMember.java new file mode 100644 index 0000000..a390299 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPMember.java @@ -0,0 +1,123 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +/** + * Superclass for member constant pool entries, such as fields or methods. + */ +public class CPMember extends ClassFileEntry { + + List attributes; + short flags; + CPUTF8 name; + transient int nameIndex; + protected final CPUTF8 descriptor; + transient int descriptorIndex; + + /** + * Create a new CPMember + * + * @param name + * @param descriptor + * @param flags + * @param attributes + * @throws NullPointerException + * if name or descriptor is null + */ + public CPMember(CPUTF8 name, CPUTF8 descriptor, long flags, List attributes) { + this.name = name; + this.descriptor = descriptor; + this.flags = (short) flags; + this.attributes = (attributes == null ? Collections.EMPTY_LIST + : attributes); + if (name == null || descriptor == null) { + throw new NullPointerException("Null arguments are not allowed"); + } + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + int attributeCount = attributes.size(); + ClassFileEntry[] entries = new ClassFileEntry[attributeCount + 2]; + entries[0] = name; + entries[1] = descriptor; + for (int i = 0; i < attributeCount; i++) { + entries[i + 2] = (Attribute) attributes.get(i); + } + return entries; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + nameIndex = pool.indexOf(name); + descriptorIndex = pool.indexOf(descriptor); + for(int it = 0; it < attributes.size(); it++) { + Attribute attribute = (Attribute) attributes.get(it); + attribute.resolve(pool); + } + } + + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = PRIME * result + attributes.hashCode(); + result = PRIME * result + descriptor.hashCode(); + result = PRIME * result + flags; + result = PRIME * result + name.hashCode(); + return result; + } + + public String toString() { + return "CPMember: " + name + "(" + descriptor + ")"; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final CPMember other = (CPMember) obj; + if (!attributes.equals(other.attributes)) + return false; + if (!descriptor.equals(other.descriptor)) + return false; + if (flags != other.flags) + return false; + if (!name.equals(other.name)) + return false; + return true; + } + + protected void doWrite(DataOutputStream dos) throws IOException { + dos.writeShort(flags); + dos.writeShort(nameIndex); + dos.writeShort(descriptorIndex); + int attributeCount = attributes.size(); + dos.writeShort(attributeCount); + for (int i = 0; i < attributeCount; i++) { + Attribute attribute = (Attribute) attributes.get(i); + attribute.doWrite(dos); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPMethod.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPMethod.java new file mode 100644 index 0000000..b76d75a --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPMethod.java @@ -0,0 +1,52 @@ +/* + * 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.unpack200.bytecode; + +import java.util.List; + +/** + * Method constant pool entry. + */ +public class CPMethod extends CPMember { + + public CPMethod(CPUTF8 name, CPUTF8 descriptor, long flags, List attributes) { + super(name, descriptor, flags, attributes); + } + + public String toString() { + return "Method: " + name + "(" + descriptor + ")"; + } + + private boolean hashcodeComputed; + private int cachedHashCode; + + private void generateHashCode() { + hashcodeComputed = true; + final int PRIME = 31; + int result = 1; + result = PRIME * result + name.hashCode(); + result = PRIME * result + descriptor.hashCode(); + cachedHashCode = result; + } + + public int hashCode() { + if (!hashcodeComputed) + generateHashCode(); + return cachedHashCode; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPMethodRef.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPMethodRef.java new file mode 100644 index 0000000..86226ac --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPMethodRef.java @@ -0,0 +1,51 @@ +/* + * 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.unpack200.bytecode; + +/** + * Method reference constant pool entry. + */ +public class CPMethodRef extends CPRef { + + public CPMethodRef(CPClass className, CPNameAndType descriptor, int globalIndex) { + super(ConstantPoolEntry.CP_Methodref, className, descriptor, globalIndex); + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + return new ClassFileEntry[] { className, nameAndType }; + } + + + private boolean hashcodeComputed; + private int cachedHashCode; + + private void generateHashCode() { + hashcodeComputed = true; + final int PRIME = 31; + int result = 1; + result = PRIME * result + className.hashCode(); + result = PRIME * result + nameAndType.hashCode(); + cachedHashCode = result; + } + + public int hashCode() { + if (!hashcodeComputed) + generateHashCode(); + return cachedHashCode; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPNameAndType.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPNameAndType.java new file mode 100644 index 0000000..b57da6c --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPNameAndType.java @@ -0,0 +1,123 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +import org.apache.harmony.unpack200.SegmentUtils; + +/** + * Name and Type pair constant pool entry. + */ +public class CPNameAndType extends ConstantPoolEntry { + + CPUTF8 descriptor; + + transient int descriptorIndex; + + CPUTF8 name; + + transient int nameIndex; + + /** + * Create a new CPNameAndType + * + * @param name + * @param descriptor + * @param globalIndex - index in CpBands + * @throws NullPointerException + * if name or descriptor is null + */ + public CPNameAndType(CPUTF8 name, CPUTF8 descriptor, int globalIndex) { + super(ConstantPoolEntry.CP_NameAndType, globalIndex); + this.name = name; + this.descriptor = descriptor; + if (name == null || descriptor == null) { + throw new NullPointerException("Null arguments are not allowed"); + } + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + return new ClassFileEntry[] { name, descriptor }; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + descriptorIndex = pool.indexOf(descriptor); + nameIndex = pool.indexOf(name); + } + + /* + * field_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 + * attributes_count; attribute_info attributes[attributes_count]; } + */ + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(nameIndex); + dos.writeShort(descriptorIndex); + } + + public String toString() { + return "NameAndType: " + name + "(" + descriptor + ")"; + } + + private boolean hashcodeComputed; + private int cachedHashCode; + + private void generateHashCode() { + hashcodeComputed = true; + final int PRIME = 31; + int result = 1; + result = PRIME * result + descriptor.hashCode(); + result = PRIME * result + name.hashCode(); + cachedHashCode = result; + } + + public int hashCode() { + if (!hashcodeComputed) + generateHashCode(); + return cachedHashCode; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final CPNameAndType other = (CPNameAndType) obj; + if (!descriptor.equals(other.descriptor)) + return false; + if (!name.equals(other.name)) + return false; + return true; + } + + /** + * Answers the invokeinterface count argument when the receiver is treated + * as an invokeinterface target. This value is not meaningful if the + * receiver is not an invokeinterface target. + * + * @return count + */ + public int invokeInterfaceCount() { + return 1 + SegmentUtils.countInvokeInterfaceArgs(descriptor + .underlyingString()); + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPRef.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPRef.java new file mode 100644 index 0000000..bcdcbe3 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPRef.java @@ -0,0 +1,114 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Abstract superclass for reference constant pool entries, such as a method or + * field reference. + */ +public abstract class CPRef extends ConstantPoolEntry { + + CPClass className; + transient int classNameIndex; + + protected CPNameAndType nameAndType; + transient int nameAndTypeIndex; + + /** + * Create a new CPRef + * + * @param type + * @param className + * @param descriptor + * @param globalIndex + * index in CpBands + * @throws NullPointerException + * if descriptor or className is null + */ + public CPRef(byte type, CPClass className, CPNameAndType descriptor, int globalIndex) { + super(type, globalIndex); + this.className = className; + this.nameAndType = descriptor; + if (descriptor == null || className == null) { + throw new NullPointerException("Null arguments are not allowed"); + } + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + if (this.hashCode() != obj.hashCode()) { + return false; + } + final CPRef other = (CPRef) obj; + if (!className.equals(other.className)) + return false; + if (!nameAndType.equals(other.nameAndType)) + return false; + return true; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + ClassFileEntry[] entries = new ClassFileEntry[2]; + entries[0] = className; + entries[1] = nameAndType; + return entries; + } + + public int hashCode() { + final int PRIME = 37; + return (PRIME * className.hashCode()) + nameAndType.hashCode(); + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + nameAndTypeIndex = pool.indexOf(nameAndType); + classNameIndex = pool.indexOf(className); + } + + protected String cachedToString; + + public String toString() { + if (cachedToString == null) { + String type; + if (getTag() == ConstantPoolEntry.CP_Fieldref) { + type = "FieldRef"; //$NON-NLS-1$ + } else if (getTag() == ConstantPoolEntry.CP_Methodref) { + type = "MethoddRef"; //$NON-NLS-1$ + } else if (getTag() == ConstantPoolEntry.CP_InterfaceMethodref) { + type = "InterfaceMethodRef"; //$NON-NLS-1$ + } else { + type = "unknown"; //$NON-NLS-1$ + } + cachedToString = type + ": " + className + "#" + nameAndType; //$NON-NLS-1$ //$NON-NLS-2$ + } + return cachedToString; + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(classNameIndex); + dos.writeShort(nameAndTypeIndex); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPString.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPString.java new file mode 100644 index 0000000..ca2548a --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPString.java @@ -0,0 +1,73 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * String constant pool entry. + */ +public class CPString extends CPConstant { + + private transient int nameIndex; + private final CPUTF8 name; + + public CPString(CPUTF8 value, int globalIndex) { + super(ConstantPoolEntry.CP_String, value, globalIndex); + this.name = value; + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(nameIndex); + } + + public String toString() { + return "String: " + getValue(); + } + + /** + * Allows the constant pool entries to resolve their nested entries + * + * @param pool + */ + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + nameIndex = pool.indexOf(name); + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + return new ClassFileEntry[] { name }; + } + + private boolean hashcodeComputed; + private int cachedHashCode; + + private void generateHashCode() { + hashcodeComputed = true; + final int PRIME = 31; + int result = 1; + result = PRIME * result + name.hashCode(); + cachedHashCode = result; + } + + public int hashCode() { + if (!hashcodeComputed) + generateHashCode(); + return cachedHashCode; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CPUTF8.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CPUTF8.java new file mode 100644 index 0000000..8aa4239 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CPUTF8.java @@ -0,0 +1,90 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * UTF8 constant pool entry, used for storing long Strings. + */ +public class CPUTF8 extends ConstantPoolEntry { + + private final String utf8; + + /** + * Creates a new CPUTF8 instance + * + * @param utf8 + * @param globalIndex - index in CpBands + * @throws NullPointerException + * if utf8 is null + */ + public CPUTF8(String utf8, int globalIndex) { + super(ConstantPoolEntry.CP_UTF8, globalIndex); + this.utf8 = utf8; + if (utf8 == null) { + throw new NullPointerException("Null arguments are not allowed"); + } + } + + public CPUTF8(String string) { + this(string, -1); + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (this.getClass() != obj.getClass()) + return false; + final CPUTF8 other = (CPUTF8) obj; + return utf8.equals(other.utf8); + } + + private boolean hashcodeComputed; + private int cachedHashCode; + + private void generateHashCode() { + hashcodeComputed = true; + final int PRIME = 31; + cachedHashCode = PRIME + utf8.hashCode(); + } + + public int hashCode() { + if (!hashcodeComputed) + generateHashCode(); + return cachedHashCode; + } + + public String toString() { + return "UTF8: " + utf8; + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeUTF(utf8); + } + + public String underlyingString() { + return utf8; + } + + public void setGlobalIndex(int index) { + globalIndex = index; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/ClassConstantPool.java b/src/main/java/org/apache/harmony/unpack200/bytecode/ClassConstantPool.java new file mode 100644 index 0000000..a9b00f6 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/ClassConstantPool.java @@ -0,0 +1,277 @@ +/* + * 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.unpack200.bytecode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; + +import org.apache.harmony.unpack200.Segment; + +/** + * The Class constant pool + */ +public class ClassConstantPool { + + protected HashSet entriesContainsSet = new HashSet(); + protected HashSet othersContainsSet = new HashSet(); + + private final HashSet mustStartClassPool = new HashSet(); + + protected Map indexCache; + + public String toString() { + return entries.toString(); + } + + private final List others = new ArrayList(500); + private final List entries = new ArrayList(500); + + private boolean resolved; + + public ClassFileEntry add(ClassFileEntry entry) { + if (entry instanceof ByteCode) { + return null; + } + if (entry instanceof ConstantPoolEntry) { + if (entriesContainsSet.add(entry)) { + entries.add(entry); + } + } else { + if (othersContainsSet.add(entry)) { + others.add(entry); + } + } + + return entry; + } + + public void addNestedEntries() { + boolean added = true; + + // initial assignment + ArrayList parents = new ArrayList(512); + ArrayList children = new ArrayList(512); + + // adding old entries + parents.addAll(entries); + parents.addAll(others); + + // while there any parents to traverse and at least one change in target + // storage was made + while(added || parents.size() > 0) { + + children.clear(); + + int entriesOriginalSize = entries.size(); + int othersOriginalSize = others.size(); + + // get the parents' children and add them to buffer + // concurrently add parents to target storage + for(int indexParents = 0; indexParents < parents.size(); indexParents++) { + ClassFileEntry entry = (ClassFileEntry) parents.get(indexParents); + + // traverse children + ClassFileEntry[] entryChildren = entry.getNestedClassFileEntries(); + for(int c = 0; c < entryChildren.length; c++) { + children.add(entryChildren[c]); + } + + boolean isAtStart = (entry instanceof ByteCode) + && ((ByteCode) entry).nestedMustStartClassPool(); + + if (isAtStart) { + for (int c = 0; c < entryChildren.length; c++) { + mustStartClassPool.add(entryChildren[c]); + } + } + + // add parent + add(entry); + } + + added = !(entries.size() == entriesOriginalSize && others.size() == othersOriginalSize); + + // parents are not needed anymore + // children now become parents + parents.clear(); + parents.addAll(children); + + + } + } + + public int indexOf(ClassFileEntry entry) { + if (!resolved) + throw new IllegalStateException( + "Constant pool is not yet resolved; this does not make any sense"); + if (null == indexCache) { + throw new IllegalStateException("Index cache is not initialized!"); + } + Integer entryIndex = ((Integer) indexCache.get(entry)); + // If the entry isn't found, answer -1. Otherwise answer the entry. + if (entryIndex != null) { + return entryIndex.intValue() + 1; + } + return -1; + } + + public int size() { + return entries.size(); + } + + public ClassFileEntry get(int i) { + if (!resolved) + throw new IllegalStateException( + "Constant pool is not yet resolved; this does not make any sense"); + return (ClassFileEntry) entries.get(--i); + } + + public void resolve(Segment segment) { + initialSort(); + sortClassPool(); + + resolved = true; + + for(int it = 0; it < entries.size(); it++) { + ClassFileEntry entry = (ClassFileEntry) entries.get(it); + entry.resolve(this); + } + + for(int it = 0; it < others.size(); it++) { + ClassFileEntry entry = (ClassFileEntry) others.get(it); + entry.resolve(this); + } + + } + + private void initialSort() { + TreeSet inCpAll = new TreeSet(new Comparator() { + public int compare(Object arg0, Object arg1) { + return ((ConstantPoolEntry)arg0).getGlobalIndex() - ((ConstantPoolEntry)arg1).getGlobalIndex(); + } + }); + TreeSet cpUtf8sNotInCpAll = new TreeSet(new Comparator() { + + public int compare(Object arg0, Object arg1) { + return ((CPUTF8)arg0).underlyingString().compareTo(((CPUTF8)arg1).underlyingString()); + } + + }); + TreeSet cpClassesNotInCpAll = new TreeSet(new Comparator() { + + public int compare(Object arg0, Object arg1) { + return ((CPClass)arg0).getName().compareTo(((CPClass)arg1).getName()); + } + + }); + + + for(int index = 0; index < entries.size(); index++) { + ConstantPoolEntry entry = (ConstantPoolEntry) entries.get(index); + if(entry.getGlobalIndex() == -1) { + if (entry instanceof CPUTF8) { + cpUtf8sNotInCpAll.add(entry); + } else if (entry instanceof CPClass) { + cpClassesNotInCpAll.add(entry); + } else { + throw new Error("error"); + } + } else { + inCpAll.add(entry); + } + } + entries.clear(); + entries.addAll(inCpAll); + entries.addAll(cpUtf8sNotInCpAll); + entries.addAll(cpClassesNotInCpAll); + } + + public List entries() { + return Collections.unmodifiableList(entries); + } + + protected void sortClassPool() { + // Now that everything has been resolved, do one + // final sort of the class pool. This fixes up + // references to objects which need to be at the + // start of the class pool + + + ArrayList startOfPool = new ArrayList(entries.size()); + ArrayList finalSort = new ArrayList(entries.size()); + + for(int i = 0; i < entries.size(); i++) { + ClassFileEntry nextEntry = (ClassFileEntry) entries.get(i); + if (mustStartClassPool.contains(nextEntry)) { + startOfPool.add(nextEntry); + } else { + finalSort.add(nextEntry); + } + } + + // copy over and rebuild the cache + // + indexCache = new HashMap(entries.size()); + int index = 0; + + entries.clear(); + + for(int itIndex = 0; itIndex < startOfPool.size(); itIndex++) { + ClassFileEntry entry = (ClassFileEntry) startOfPool.get(itIndex); + indexCache.put(entry, new Integer(index)); + + if (entry instanceof CPLong || entry instanceof CPDouble) { + entries.add(entry); // these get 2 slots because of their size + entries.add(entry); + index += 2; + } else { + entries.add(entry); + index += 1; + } + } + + for(int itFinal = 0; itFinal < finalSort.size(); itFinal++) { + ClassFileEntry entry = (ClassFileEntry) finalSort.get(itFinal); + indexCache.put(entry, new Integer(index)); + + if (entry instanceof CPLong || entry instanceof CPDouble) { + entries.add(entry); // these get 2 slots because of their size + entries.add(entry); + index += 2; + } else { + entries.add(entry); + index += 1; + } + } + + } + + public ClassFileEntry addWithNestedEntries(ClassFileEntry entry) { + add(entry); + ClassFileEntry[] nestedEntries = entry.getNestedClassFileEntries(); + for (int i = 0; i < nestedEntries.length; i++) { + addWithNestedEntries(nestedEntries[i]); + } + return entry; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/ClassFile.java b/src/main/java/org/apache/harmony/unpack200/bytecode/ClassFile.java new file mode 100644 index 0000000..e8d4bc6 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/ClassFile.java @@ -0,0 +1,73 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * ClassFile is used to represent and write out Java class files. + */ +public class ClassFile { + + public int major; + public int minor; + private final int magic = 0xCAFEBABE; + public ClassConstantPool pool = new ClassConstantPool(); + public int accessFlags; + public int thisClass; + public int superClass; + public int[] interfaces; + public ClassFileEntry[] fields; + public ClassFileEntry[] methods; + public 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)).doWrite(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 < interfaces.length; i++) { + dos.writeShort(interfaces[i]); + } + dos.writeShort(fields.length); + for (int i = 0; i < fields.length; i++) { + fields[i].write(dos); + } + dos.writeShort(methods.length); + for (int i = 0; i < methods.length; i++) { + methods[i].write(dos); + } + dos.writeShort(attributes.length); + for (int i = 0; i < attributes.length; i++) { + attributes[i].write(dos); + } + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/ClassFileEntry.java b/src/main/java/org/apache/harmony/unpack200/bytecode/ClassFileEntry.java new file mode 100644 index 0000000..440ed6b --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/ClassFileEntry.java @@ -0,0 +1,65 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * The abstract superclass for all types of class file entries. + */ +public abstract class ClassFileEntry { + + protected static final ClassFileEntry[] NONE = new ClassFileEntry[0]; + private boolean resolved; + + protected abstract void doWrite(DataOutputStream dos) throws IOException; + + public abstract boolean equals(Object arg0); + + protected ClassFileEntry[] getNestedClassFileEntries() { + return NONE; + } + + public abstract int hashCode(); + + /** + * Allows the constant pool entries to resolve their nested entries + * + * @param pool + */ + protected void resolve(ClassConstantPool pool) { + resolved = true; + } + + protected boolean isResolved() { + return resolved; + } + + protected int objectHashCode() { + return super.hashCode(); + } + + public abstract String toString(); + + public final void write(DataOutputStream dos) throws IOException { + if (!resolved) + throw new IllegalStateException("Entry has not been resolved"); + doWrite(dos); + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/CodeAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/CodeAttribute.java new file mode 100644 index 0000000..42bba52 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/CodeAttribute.java @@ -0,0 +1,199 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.harmony.unpack200.Segment; + +public class CodeAttribute extends BCIRenumberedAttribute { + + public List attributes = new ArrayList(); + // instances + public List byteCodeOffsets = new ArrayList(); + public List byteCodes = new ArrayList(); + public int codeLength; + public List exceptionTable; // of ExceptionTableEntry + public int maxLocals; + public int maxStack; + private static CPUTF8 attributeName; + + public CodeAttribute(int maxStack, int maxLocals, byte codePacked[], + Segment segment, OperandManager operandManager, List exceptionTable) { + super(attributeName); + this.maxLocals = maxLocals; + this.maxStack = maxStack; + this.codeLength = 0; + this.exceptionTable = exceptionTable; + byteCodeOffsets.add(new Integer(0)); + int byteCodeIndex = 0; + for (int i = 0; i < codePacked.length; i++) { + ByteCode byteCode = ByteCode.getByteCode(codePacked[i] & 0xff); + // Setting the offset must happen before extracting operands + // because label bytecodes need to know their offsets. + byteCode.setByteCodeIndex(byteCodeIndex); + byteCodeIndex++; + byteCode.extractOperands(operandManager, segment, codeLength); + byteCodes.add(byteCode); + codeLength += byteCode.getLength(); + int lastBytecodePosition = ((Integer) byteCodeOffsets + .get(byteCodeOffsets.size() - 1)).intValue(); + // This code assumes all multiple byte bytecodes are + // replaced by a single-byte bytecode followed by + // another bytecode. + if (byteCode.hasMultipleByteCodes()) { + byteCodeOffsets.add(new Integer(lastBytecodePosition + 1)); + byteCodeIndex++; + } + // I've already added the first element (at 0) before + // entering this loop, so make sure I don't add one + // after the last element. + if (i < (codePacked.length - 1)) { + byteCodeOffsets.add(new Integer(lastBytecodePosition + + byteCode.getLength())); + } + if (byteCode.getOpcode() == 0xC4) { + // Special processing for wide bytecode - it knows what its + // instruction is from the opcode manager, so ignore the + // next instruction + i++; + } + } + // Now that all the bytecodes know their positions and + // sizes, fix up the byte code targets + // At this point, byteCodes may be a different size than + // codePacked because of wide bytecodes. + for (int i = 0; i < byteCodes.size(); i++) { + ByteCode byteCode = (ByteCode) byteCodes.get(i); + byteCode.applyByteCodeTargetFixup(this); + } + } + + protected int getLength() { + int attributesSize = 0; + for(int it = 0; it < attributes.size(); it++) { + Attribute attribute = (Attribute) attributes.get(it); + attributesSize += attribute.getLengthIncludingHeader(); + } + return 2 + 2 + 4 + codeLength + 2 + exceptionTable.size() + * (2 + 2 + 2 + 2) + 2 + attributesSize; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + ArrayList nestedEntries = new ArrayList(attributes.size() + byteCodes.size() + 10); + nestedEntries.add(getAttributeName()); + nestedEntries.addAll(byteCodes); + nestedEntries.addAll(attributes); + // Don't forget to add the ExceptionTable catch_types + for(int iter = 0; iter < exceptionTable.size(); iter++) { + ExceptionTableEntry entry = (ExceptionTableEntry) exceptionTable.get(iter); + CPClass catchType = entry.getCatchType(); + // If the catch type is null, this is a finally + // block. If it's not null, we need to add the + // CPClass to the list of nested class file entries. + if (catchType != null) { + nestedEntries.add(catchType); + } + } + ClassFileEntry[] nestedEntryArray = new ClassFileEntry[nestedEntries + .size()]; + nestedEntries.toArray(nestedEntryArray); + return nestedEntryArray; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + for(int it = 0; it < attributes.size(); it++) { + Attribute attribute = (Attribute) attributes.get(it); + attribute.resolve(pool); + } + + for(int it = 0; it < byteCodes.size(); it++) { + ByteCode byteCode = (ByteCode) byteCodes.get(it); + byteCode.resolve(pool); + } + + for(int it = 0; it < exceptionTable.size(); it++) { + ExceptionTableEntry entry = (ExceptionTableEntry) exceptionTable.get(it); + entry.resolve(pool); + } + } + + public String toString() { + return "Code: " + getLength() + " bytes"; + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(maxStack); + dos.writeShort(maxLocals); + + dos.writeInt(codeLength); + for(int it = 0; it < byteCodes.size(); it++) { + ByteCode byteCode = (ByteCode) byteCodes.get(it); + byteCode.write(dos); + } + + dos.writeShort(exceptionTable.size()); + for(int it = 0; it < exceptionTable.size(); it++) { + ExceptionTableEntry entry = (ExceptionTableEntry) exceptionTable.get(it); + entry.write(dos); + } + + dos.writeShort(attributes.size()); + for(int it = 0; it < attributes.size(); it++) { + Attribute attribute = (Attribute) attributes.get(it); + attribute.write(dos); + } + } + + public void addAttribute(Attribute attribute) { + attributes.add(attribute); + if(attribute instanceof LocalVariableTableAttribute) { + ((LocalVariableTableAttribute)attribute).setCodeLength(codeLength); + } + if(attribute instanceof LocalVariableTypeTableAttribute) { + ((LocalVariableTypeTableAttribute)attribute).setCodeLength(codeLength); + } + } + + public List attributes() { + return attributes; + } + + public boolean equals(Object obj) { + return this == obj; + } + + protected int[] getStartPCs() { + // Do nothing here as we've overriden renumber + return null; + } + + public void renumber(List byteCodeOffsets) { + for(int iter = 0; iter < exceptionTable.size(); iter++) { + ExceptionTableEntry entry = (ExceptionTableEntry) exceptionTable.get(iter); + entry.renumber(byteCodeOffsets); + } + } + + public static void setAttributeName(CPUTF8 attributeName) { + CodeAttribute.attributeName = attributeName; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/ConstantPoolEntry.java b/src/main/java/org/apache/harmony/unpack200/bytecode/ConstantPoolEntry.java new file mode 100644 index 0000000..cde2749 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/ConstantPoolEntry.java @@ -0,0 +1,83 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Abstract superclass for constant pool entries + */ +public abstract class ConstantPoolEntry extends ClassFileEntry { + + 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; + + protected int globalIndex; + + ConstantPoolEntry(byte tag, int globalIndex) { + this.tag = tag; + this.globalIndex = globalIndex; + } + + public abstract boolean equals(Object obj); + + public byte getTag() { + return tag; + } + + public abstract int hashCode(); + + public void doWrite(DataOutputStream dos) throws IOException { + dos.writeByte(tag); + writeBody(dos); + } + + protected abstract void writeBody(DataOutputStream dos) throws IOException; + + public int getGlobalIndex() { + return globalIndex; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/ConstantValueAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/ConstantValueAttribute.java new file mode 100644 index 0000000..13b9aa8 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/ConstantValueAttribute.java @@ -0,0 +1,90 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * An {@link Attribute} representing a constant. + */ +public class ConstantValueAttribute extends Attribute { + + private int constantIndex; + + private final ClassFileEntry entry; + + private static CPUTF8 attributeName; + + public static void setAttributeName(CPUTF8 cpUTF8Value) { + attributeName = cpUTF8Value; + } + + public ConstantValueAttribute(ClassFileEntry entry) { + super(attributeName); + if (entry == null) { + throw new NullPointerException(); + } + this.entry = entry; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (this.getClass() != obj.getClass()) + return false; + final ConstantValueAttribute other = (ConstantValueAttribute) obj; + if (entry == null) { + if (other.entry != null) + return false; + } else if (!entry.equals(other.entry)) + return false; + return true; + } + + protected int getLength() { + return 2; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + return new ClassFileEntry[] { getAttributeName(), entry }; + } + + public int hashCode() { + final int PRIME = 31; + int result = super.hashCode(); + result = PRIME * result + ((entry == null) ? 0 : entry.hashCode()); + return result; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + entry.resolve(pool); + this.constantIndex = pool.indexOf(entry); + } + + public String toString() { + return "Constant:" + entry; + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(constantIndex); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/DeprecatedAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/DeprecatedAttribute.java new file mode 100644 index 0000000..e302565 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/DeprecatedAttribute.java @@ -0,0 +1,68 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Deprecated class file attribute. + */ +public class DeprecatedAttribute extends Attribute { + + private static CPUTF8 attributeName; + + public static void setAttributeName(CPUTF8 cpUTF8Value) { + attributeName = cpUTF8Value; + } + + public DeprecatedAttribute() { + super(attributeName); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#getLength() + */ + protected int getLength() { + return 0; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#writeBody(java.io.DataOutputStream) + */ + protected void writeBody(DataOutputStream dos) throws IOException { + // No body + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.ClassFileEntry#toString() + */ + public String toString() { + return "Deprecated Attribute"; + } + + public boolean equals(Object obj) { + return this == obj; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/EnclosingMethodAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/EnclosingMethodAttribute.java new file mode 100644 index 0000000..9f9b0cf --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/EnclosingMethodAttribute.java @@ -0,0 +1,95 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Enclosing method class file attribute. + */ +public class EnclosingMethodAttribute extends Attribute { + + private int class_index; + private int method_index; + private final CPClass cpClass; + private final CPNameAndType method; + private static CPUTF8 attributeName; + + public static void setAttributeName(CPUTF8 cpUTF8Value) { + attributeName = cpUTF8Value; + } + + public EnclosingMethodAttribute(CPClass cpClass, CPNameAndType method) { + super(attributeName); + this.cpClass = cpClass; + this.method = method; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + if(method != null) { + return new ClassFileEntry[] {attributeName, cpClass, method}; + } else { + return new ClassFileEntry[] {attributeName, cpClass}; + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#getLength() + */ + protected int getLength() { + return 4; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + cpClass.resolve(pool); + class_index = pool.indexOf(cpClass); + if(method != null) { + method.resolve(pool); + method_index = pool.indexOf(method); + } else { + method_index = 0; + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#writeBody(java.io.DataOutputStream) + */ + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(class_index); + dos.writeShort(method_index); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.ClassFileEntry#toString() + */ + public String toString() { + return "EnclosingMethod"; + } + + public boolean equals(Object obj) { + return this == obj; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/ExceptionTableEntry.java b/src/main/java/org/apache/harmony/unpack200/bytecode/ExceptionTableEntry.java new file mode 100644 index 0000000..2611ec8 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/ExceptionTableEntry.java @@ -0,0 +1,96 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.List; + +/** + * An entry in an exception table. + */ +public class ExceptionTableEntry { + + private final int startPC; + private final int endPC; + private final int handlerPC; + private final CPClass catchType; + + private int startPcRenumbered; + private int endPcRenumbered; + private int handlerPcRenumbered; + private int catchTypeIndex; + + /** + * Create a new ExceptionTableEntry. Exception tables are of two kinds: + * either a normal one (with a Throwable as the catch_type) or a finally + * clause (which has no catch_type). In the class file, the finally clause + * is represented as catch_type == 0. + * + * To create a finally clause with this method, pass in null for the + * catchType. + * + * @param startPC + * int + * @param endPC + * int + * @param handlerPC + * int + * @param catchType + * CPClass (if it's a normal catch) or null (if it's a finally + * clause). + */ + public ExceptionTableEntry(int startPC, int endPC, int handlerPC, + CPClass catchType) { + this.startPC = startPC; + this.endPC = endPC; + this.handlerPC = handlerPC; + this.catchType = catchType; + } + + public void write(DataOutputStream dos) throws IOException { + dos.writeShort(startPcRenumbered); + dos.writeShort(endPcRenumbered); + dos.writeShort(handlerPcRenumbered); + dos.writeShort(catchTypeIndex); + } + + public void renumber(List byteCodeOffsets) { + startPcRenumbered = ((Integer) byteCodeOffsets.get(startPC)).intValue(); + int endPcIndex = startPC + endPC; + endPcRenumbered = ((Integer) byteCodeOffsets.get(endPcIndex)) + .intValue(); + int handlerPcIndex = endPcIndex + handlerPC; + handlerPcRenumbered = ((Integer) byteCodeOffsets.get(handlerPcIndex)) + .intValue(); + } + + public CPClass getCatchType() { + return catchType; + } + + public void resolve(ClassConstantPool pool) { + if (catchType == null) { + // If the catch type is a finally clause + // the index is always 0. + catchTypeIndex = 0; + return; + } + catchType.resolve(pool); + catchTypeIndex = pool.indexOf(catchType); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/ExceptionsAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/ExceptionsAttribute.java new file mode 100644 index 0000000..39d26a0 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/ExceptionsAttribute.java @@ -0,0 +1,114 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Arrays; + +/** + * Exceptions class file attribute + */ +public class ExceptionsAttribute extends Attribute { + + private static CPUTF8 attributeName; + + private static int hashCode(Object[] array) { + final int prime = 31; + if (array == null) + return 0; + int result = 1; + for (int index = 0; index < array.length; index++) { + result = prime * result + + (array[index] == null ? 0 : array[index].hashCode()); + } + return result; + } + + private transient int[] exceptionIndexes; + + private final CPClass[] exceptions; + + public ExceptionsAttribute(CPClass[] exceptions) { + super(attributeName); + this.exceptions = exceptions; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + final ExceptionsAttribute other = (ExceptionsAttribute) obj; + if (!Arrays.equals(exceptions, other.exceptions)) + return false; + return true; + } + + protected int getLength() { + return 2 + 2 * exceptions.length; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + ClassFileEntry[] result = new ClassFileEntry[exceptions.length + 1]; + for (int i = 0; i < exceptions.length; i++) { + result[i] = exceptions[i]; + } + result[exceptions.length] = getAttributeName(); + return result; + } + + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ExceptionsAttribute.hashCode(exceptions); + return result; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + exceptionIndexes = new int[exceptions.length]; + for (int i = 0; i < exceptions.length; i++) { + exceptions[i].resolve(pool); + exceptionIndexes[i] = pool.indexOf(exceptions[i]); + } + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("Exceptions: "); + for (int i = 0; i < exceptions.length; i++) { + sb.append(exceptions[i]); + sb.append(' '); + } + return sb.toString(); + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(exceptionIndexes.length); + for (int i = 0; i < exceptionIndexes.length; i++) { + dos.writeShort(exceptionIndexes[i]); + } + } + + public static void setAttributeName(CPUTF8 cpUTF8Value) { + attributeName = cpUTF8Value; + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/InnerClassesAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/InnerClassesAttribute.java new file mode 100644 index 0000000..0c9e1c3 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/InnerClassesAttribute.java @@ -0,0 +1,185 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Inner classes class file attribute + */ +public class InnerClassesAttribute extends Attribute { + + private static CPUTF8 attributeName; + + public static void setAttributeName(CPUTF8 cpUTF8Value) { + attributeName = cpUTF8Value; + } + + private static class InnerClassesEntry { + + CPClass inner_class_info; + CPClass outer_class_info; + CPUTF8 inner_class_name; + + int inner_class_info_index = -1; + int outer_class_info_index = -1; + int inner_name_index = -1; + int inner_class_access_flags = -1; + + public InnerClassesEntry(CPClass innerClass, CPClass outerClass, + CPUTF8 innerName, int flags) { + this.inner_class_info = innerClass; + this.outer_class_info = outerClass; + this.inner_class_name = innerName; + this.inner_class_access_flags = flags; + } + + /** + * Determine the indices of the things in the receiver which point to + * elements of the ClassConstantPool + * + * @param pool + * ClassConstantPool which holds the CPClass and CPUTF8 + * objects. + */ + public void resolve(ClassConstantPool pool) { + if (inner_class_info != null) { + inner_class_info.resolve(pool); + inner_class_info_index = pool.indexOf(inner_class_info); + } else { + inner_class_info_index = 0; + } + + if (inner_class_name != null) { + inner_class_name.resolve(pool); + inner_name_index = pool.indexOf(inner_class_name); + } else { + inner_name_index = 0; + } + + if (outer_class_info != null) { + outer_class_info.resolve(pool); + outer_class_info_index = pool.indexOf(outer_class_info); + } else { + outer_class_info_index = 0; + } + } + + public void write(DataOutputStream dos) throws IOException { + dos.writeShort(inner_class_info_index); + dos.writeShort(outer_class_info_index); + dos.writeShort(inner_name_index); + dos.writeShort(inner_class_access_flags); + } + + } + + private final List innerClasses = new ArrayList(); + private final List nestedClassFileEntries = new ArrayList(); + + public InnerClassesAttribute(String name) { + super(attributeName); + nestedClassFileEntries.add(getAttributeName()); + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (this.getClass() != obj.getClass()) + return false; + final InnerClassesAttribute other = (InnerClassesAttribute) obj; + if (getAttributeName() == null) { + if (other.getAttributeName() != null) + return false; + } else if (!getAttributeName().equals(other.getAttributeName())) + return false; + return true; + } + + protected int getLength() { + return 2 + ((2 + 2 + 2 + 2) * innerClasses.size()); + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + ClassFileEntry[] result = new ClassFileEntry[nestedClassFileEntries + .size()]; + for (int index = 0; index < result.length; index++) { + result[index] = (ClassFileEntry) nestedClassFileEntries.get(index); + } + return result; + } + + public int hashCode() { + final int PRIME = 31; + int result = super.hashCode(); + result = PRIME + * result + + ((getAttributeName() == null) ? 0 : getAttributeName() + .hashCode()); + return result; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + for(int it = 0; it < innerClasses.size(); it++) { + InnerClassesEntry entry = (InnerClassesEntry) innerClasses.get(it); + entry.resolve(pool); + } + } + + public String toString() { + return "InnerClasses: " + getAttributeName(); + } + + protected void doWrite(DataOutputStream dos) throws IOException { + // Hack so I can see what's being written. + super.doWrite(dos); + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(innerClasses.size()); + + for(int it = 0; it < innerClasses.size(); it++) { + InnerClassesEntry entry = (InnerClassesEntry) innerClasses.get(it); + entry.write(dos); + } + } + + public void addInnerClassesEntry(CPClass innerClass, CPClass outerClass, + CPUTF8 innerName, int flags) { + if (innerClass != null) { + nestedClassFileEntries.add(innerClass); + } + if (outerClass != null) { + nestedClassFileEntries.add(outerClass); + } + if (innerName != null) { + nestedClassFileEntries.add(innerName); + } + addInnerClassesEntry(new InnerClassesEntry(innerClass, outerClass, + innerName, flags)); + } + + private void addInnerClassesEntry(InnerClassesEntry innerClassesEntry) { + innerClasses.add(innerClassesEntry); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/LineNumberTableAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/LineNumberTableAttribute.java new file mode 100644 index 0000000..57e5ec1 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/LineNumberTableAttribute.java @@ -0,0 +1,91 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Line number table + */ +public class LineNumberTableAttribute extends BCIRenumberedAttribute { + + private final int line_number_table_length; + private final int[] start_pcs; + private final int[] line_numbers; + private static CPUTF8 attributeName; + + public static void setAttributeName(CPUTF8 cpUTF8Value) { + attributeName = cpUTF8Value; + } + + public LineNumberTableAttribute(int line_number_table_length, + int[] start_pcs, int[] line_numbers) { + super(attributeName); + this.line_number_table_length = line_number_table_length; + this.start_pcs = start_pcs; + this.line_numbers = line_numbers; + } + + protected int getLength() { + return 2 + (4 * line_number_table_length); + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(line_number_table_length); + for (int i = 0; i < line_number_table_length; i++) { + dos.writeShort(start_pcs[i]); + dos.writeShort(line_numbers[i]); + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.ClassFileEntry#toString() + */ + public String toString() { + return "LineNumberTable: " + line_number_table_length + " lines"; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#getNestedClassFileEntries() + */ + protected ClassFileEntry[] getNestedClassFileEntries() { + return new ClassFileEntry[] { getAttributeName() }; + } + + + public boolean equals(Object obj) { + return this == obj; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#resolve(org.apache.harmony.unpack200.bytecode.ClassConstantPool) + */ + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + } + + protected int[] getStartPCs() { + return start_pcs; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/LocalVariableTableAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/LocalVariableTableAttribute.java new file mode 100644 index 0000000..f8156c4 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/LocalVariableTableAttribute.java @@ -0,0 +1,171 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.harmony.pack200.Pack200Exception; + +/** + * Local variable table + */ +public class LocalVariableTableAttribute extends BCIRenumberedAttribute { + + private int local_variable_table_length; + private final int[] start_pcs; + private final int[] lengths; + private int[] name_indexes; + private int[] descriptor_indexes; + private final int[] indexes; + private final CPUTF8[] names; + private final CPUTF8[] descriptors; + private int codeLength; + private static CPUTF8 attributeName; + + public static void setAttributeName(CPUTF8 cpUTF8Value) { + attributeName = cpUTF8Value; + } + + public LocalVariableTableAttribute(int local_variable_table_length, + int[] start_pcs, int[] lengths, CPUTF8[] names, + CPUTF8[] descriptors, int[] indexes) { + super(attributeName); + this.local_variable_table_length = local_variable_table_length; + this.start_pcs = start_pcs; + this.lengths = lengths; + this.names = names; + this.descriptors = descriptors; + this.indexes = indexes; + } + + + public void setCodeLength(int length) { + codeLength = length; + } + + protected int getLength() { + return 2 + (10 * local_variable_table_length); + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(local_variable_table_length); + for (int i = 0; i < local_variable_table_length; i++) { + dos.writeShort(start_pcs[i]); + dos.writeShort(lengths[i]); + dos.writeShort(name_indexes[i]); + dos.writeShort(descriptor_indexes[i]); + dos.writeShort(indexes[i]); + } + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + ArrayList nestedEntries = new ArrayList(); + nestedEntries.add(getAttributeName()); + for (int i = 0; i < local_variable_table_length; i++) { + nestedEntries.add(names[i]); + nestedEntries.add(descriptors[i]); + } + ClassFileEntry[] nestedEntryArray = new ClassFileEntry[nestedEntries + .size()]; + nestedEntries.toArray(nestedEntryArray); + return nestedEntryArray; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + name_indexes = new int[local_variable_table_length]; + descriptor_indexes = new int[local_variable_table_length]; + for (int i = 0; i < local_variable_table_length; i++) { + names[i].resolve(pool); + descriptors[i].resolve(pool); + name_indexes[i] = pool.indexOf(names[i]); + descriptor_indexes[i] = pool.indexOf(descriptors[i]); + } + } + + public String toString() { + return "LocalVariableTable: " + +local_variable_table_length + + " variables"; + } + + protected int[] getStartPCs() { + return start_pcs; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.BCIRenumberedAttribute#renumber(java.util.List) + */ + public void renumber(List byteCodeOffsets) throws Pack200Exception { + // Remember the unrenumbered start_pcs, since that's used later + // to calculate end position. + int[] unrenumbered_start_pcs = new int[start_pcs.length]; + System.arraycopy(start_pcs, 0, unrenumbered_start_pcs, 0, + start_pcs.length); + + // Next renumber start_pcs in place + super.renumber(byteCodeOffsets); + + // lengths are BRANCH5 encoded, not BCI-encoded. + // In other words: + // start_pc is BCI5 start_pc + // end_pc is byteCodeOffset[(index of start_pc in byteCodeOffset) + + // (encoded length)] + // real length = end_pc - start_pc + // special case if end_pc is beyond end of bytecode array + + int maxSize = codeLength; + + // Iterate through the lengths and update each in turn. + // This is done in place in the lengths array. + for (int index = 0; index < lengths.length; index++) { + int start_pc = start_pcs[index]; + int revisedLength = -1; + int encodedLength = lengths[index]; + + // First get the index of the start_pc in the byteCodeOffsets + int indexOfStartPC = unrenumbered_start_pcs[index]; + // Given the index of the start_pc, we can now add + // the encodedLength to it to get the stop index. + int stopIndex = indexOfStartPC + encodedLength; + if (stopIndex < 0) { + throw new Pack200Exception("Error renumbering bytecode indexes"); + } + // Length can either be an index into the byte code offsets, or one + // beyond the + // end of the byte code offsets. Need to determine which this is. + if (stopIndex == byteCodeOffsets.size()) { + // Pointing to one past the end of the byte code array + revisedLength = maxSize - start_pc; + } else { + // We're indexed into the byte code array + int stopValue = ((Integer) byteCodeOffsets.get(stopIndex)) + .intValue(); + revisedLength = stopValue - start_pc; + } + lengths[index] = revisedLength; + } + } + + public boolean equals(Object obj) { + return this == obj; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/LocalVariableTypeTableAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/LocalVariableTypeTableAttribute.java new file mode 100644 index 0000000..e6662ce --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/LocalVariableTypeTableAttribute.java @@ -0,0 +1,172 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.harmony.pack200.Pack200Exception; + +/** + * Local variable type table. + */ +public class LocalVariableTypeTableAttribute extends BCIRenumberedAttribute { + + private int local_variable_type_table_length; + private final int[] start_pcs; + private final int[] lengths; + private int[] name_indexes; + private int[] signature_indexes; + private final int[] indexes; + private final CPUTF8[] names; + private final CPUTF8[] signatures; + private int codeLength; + private static CPUTF8 attributeName; + + public static void setAttributeName(CPUTF8 cpUTF8Value) { + attributeName = cpUTF8Value; + } + + public LocalVariableTypeTableAttribute( + int local_variable_type_table_length, int[] start_pcs, + int[] lengths, CPUTF8[] names, CPUTF8[] signatures, int[] indexes) { + super(attributeName); + this.local_variable_type_table_length = local_variable_type_table_length; + this.start_pcs = start_pcs; + this.lengths = lengths; + this.names = names; + this.signatures = signatures; + this.indexes = indexes; + } + + public void setCodeLength(int length) { + codeLength = length; + } + + protected int getLength() { + return 2 + (10 * local_variable_type_table_length); + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(local_variable_type_table_length); + for (int i = 0; i < local_variable_type_table_length; i++) { + dos.writeShort(start_pcs[i]); + dos.writeShort(lengths[i]); + dos.writeShort(name_indexes[i]); + dos.writeShort(signature_indexes[i]); + dos.writeShort(indexes[i]); + } + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + name_indexes = new int[local_variable_type_table_length]; + signature_indexes = new int[local_variable_type_table_length]; + for (int i = 0; i < local_variable_type_table_length; i++) { + names[i].resolve(pool); + signatures[i].resolve(pool); + name_indexes[i] = pool.indexOf(names[i]); + signature_indexes[i] = pool.indexOf(signatures[i]); + } + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + ArrayList nestedEntries = new ArrayList(); + nestedEntries.add(getAttributeName()); + for (int i = 0; i < local_variable_type_table_length; i++) { + nestedEntries.add(names[i]); + nestedEntries.add(signatures[i]); + } + ClassFileEntry[] nestedEntryArray = new ClassFileEntry[nestedEntries + .size()]; + nestedEntries.toArray(nestedEntryArray); + return nestedEntryArray; + } + + protected int[] getStartPCs() { + return start_pcs; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.BCIRenumberedAttribute#renumber(java.util.List) + */ + public void renumber(List byteCodeOffsets) throws Pack200Exception { + // Remember the unrenumbered start_pcs, since that's used later + // to calculate end position. + int[] unrenumbered_start_pcs = new int[start_pcs.length]; + System.arraycopy(start_pcs, 0, unrenumbered_start_pcs, 0, + start_pcs.length); + + // Next renumber start_pcs in place + super.renumber(byteCodeOffsets); + + // lengths are BRANCH5 encoded, not BCI-encoded. + // In other words: + // start_pc is BCI5 start_pc + // end_pc is byteCodeOffset[(index of start_pc in byteCodeOffset) + + // (encoded length)] + // real length = end_pc - start_pc + // special case if end_pc is beyond end of bytecode array + + int maxSize = codeLength; + + // Iterate through the lengths and update each in turn. + // This is done in place in the lengths array. + for (int index = 0; index < lengths.length; index++) { + int start_pc = start_pcs[index]; + int revisedLength = -1; + int encodedLength = lengths[index]; + + // First get the index of the start_pc in the byteCodeOffsets + int indexOfStartPC = unrenumbered_start_pcs[index]; + // Given the index of the start_pc, we can now add + // the encodedLength to it to get the stop index. + int stopIndex = indexOfStartPC + encodedLength; + if (stopIndex < 0) { + throw new Pack200Exception("Error renumbering bytecode indexes"); + } + // Length can either be an index into the byte code offsets, or one + // beyond the + // end of the byte code offsets. Need to determine which this is. + if (stopIndex == byteCodeOffsets.size()) { + // Pointing to one past the end of the byte code array + revisedLength = maxSize - start_pc; + } else { + // We're indexed into the byte code array + int stopValue = ((Integer) byteCodeOffsets.get(stopIndex)) + .intValue(); + revisedLength = stopValue - start_pc; + } + lengths[index] = revisedLength; + } + } + + + public String toString() { + return "LocalVariableTypeTable: " + +local_variable_type_table_length + + " varaibles"; + } + + public boolean equals(Object obj) { + return this == obj; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/NewAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/NewAttribute.java new file mode 100644 index 0000000..2642d61 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/NewAttribute.java @@ -0,0 +1,276 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * A compressor-defined class file attribute. + */ +public class NewAttribute extends BCIRenumberedAttribute { + + private final List lengths = new ArrayList(); // List of Integers + private final List body = new ArrayList(); + private ClassConstantPool pool; + private final int layoutIndex; + + public NewAttribute(CPUTF8 attributeName, int layoutIndex) { + super(attributeName); + this.layoutIndex = layoutIndex; + } + + public int getLayoutIndex() { + return layoutIndex; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#getLength() + */ + protected int getLength() { + int length = 0; + for(int iter = 0; iter < lengths.size(); iter++) { + length += ((Integer)lengths.get(iter)).intValue(); + } + return length; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#writeBody(java.io.DataOutputStream) + */ + protected void writeBody(DataOutputStream dos) throws IOException { + for (int i = 0; i < lengths.size(); i++) { + int length = ((Integer) lengths.get(i)).intValue(); + Object obj = body.get(i); + long value = 0; + if (obj instanceof Long) { + value = ((Long) obj).longValue(); + } else if (obj instanceof ClassFileEntry) { + value = pool.indexOf(((ClassFileEntry) obj)); + } else if (obj instanceof BCValue) { + value = ((BCValue) obj).actualValue; + } + // Write + if (length == 1) { + dos.writeByte((int) value); + } else if (length == 2) { + dos.writeShort((int) value); + } else if (length == 4) { + dos.writeInt((int) value); + } else if (length == 8) { + dos.writeLong(value); + } + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.ClassFileEntry#toString() + */ + public String toString() { + return attributeName.underlyingString(); + } + + public void addInteger(int length, long value) { + lengths.add(new Integer(length)); + body.add(new Long(value)); + } + + public void addBCOffset(int length, int value) { + lengths.add(new Integer(length)); + body.add(new BCOffset(value)); + } + + public void addBCIndex(int length, int value) { + lengths.add(new Integer(length)); + body.add(new BCIndex(value)); + } + + public void addBCLength(int length, int value) { + lengths.add(new Integer(length)); + body.add(new BCLength(value)); + } + + public void addCPConstant(int length, CPConstant constant) { + lengths.add(new Integer(length)); + body.add(constant); + } + + public void addCPClass(int length, CPClass class1) { + lengths.add(new Integer(length)); + body.add(class1); + } + + public void addCPUTF8(int length, CPUTF8 cputf8) { + lengths.add(new Integer(length)); + body.add(cputf8); + } + + public void addCPNameAndType(int length, CPNameAndType type) { + lengths.add(new Integer(length)); + body.add(type); + } + + public void addCPFieldRef(int length, CPFieldRef ref) { + lengths.add(new Integer(length)); + body.add(ref); + } + + public void addCPMethodRef(int length, CPMethodRef ref) { + lengths.add(new Integer(length)); + body.add(ref); + } + + public void addCPIMethodRef(int length, CPInterfaceMethodRef ref) { + lengths.add(new Integer(length)); + body.add(ref); + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + for(int iter = 0; iter < body.size(); iter++) { + Object element = body.get(iter); + if (element instanceof ClassFileEntry) { + ((ClassFileEntry) element).resolve(pool); + } + } + this.pool = pool; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + int total = 1; + for(int iter = 0; iter < body.size(); iter++) { + Object element = body.get(iter); + if (element instanceof ClassFileEntry) { + total++; + } + } + ClassFileEntry[] nested = new ClassFileEntry[total]; + nested[0] = getAttributeName(); + int i = 1; + for(int iter = 0; iter < body.size(); iter++) { + Object element = body.get(iter); + if (element instanceof ClassFileEntry) { + nested[i] = (ClassFileEntry) element; + i++; + } + } + return nested; + } + + private static class BCOffset extends BCValue { + + private final int offset; + private int index; + + public BCOffset(int offset) { + this.offset = offset; + } + + public void setIndex(int index) { + this.index = index; + } + + } + + private static class BCIndex extends BCValue { + + private final int index; + + public BCIndex(int index) { + this.index = index; + } + } + + private static class BCLength extends BCValue { + + private final int length; + + public BCLength(int length) { + this.length = length; + } + } + + // Bytecode-related value (either a bytecode index or a length) + private static abstract class BCValue { + + int actualValue; + + public void setActualValue(int value) { + this.actualValue = value; + } + + } + + protected int[] getStartPCs() { + // Don't need to return anything here as we've overridden renumber + return null; + } + + public void renumber(List byteCodeOffsets) { + if (!renumbered) { + Object previous = null; + for (Iterator iter = body.iterator(); iter.hasNext();) { + Object obj = iter.next(); + if (obj instanceof BCIndex) { + BCIndex bcIndex = (BCIndex) obj; + bcIndex.setActualValue(((Integer) byteCodeOffsets + .get(bcIndex.index)).intValue()); + } else if (obj instanceof BCOffset) { + BCOffset bcOffset = (BCOffset) obj; + if (previous instanceof BCIndex) { + int index = ((BCIndex) previous).index + + bcOffset.offset; + bcOffset.setIndex(index); + bcOffset.setActualValue(((Integer) byteCodeOffsets + .get(index)).intValue()); + } else if (previous instanceof BCOffset) { + int index = ((BCOffset) previous).index + + bcOffset.offset; + bcOffset.setIndex(index); + bcOffset.setActualValue(((Integer) byteCodeOffsets + .get(index)).intValue()); + } else { + // Not sure if this should be able to happen + bcOffset.setActualValue(((Integer) byteCodeOffsets + .get(bcOffset.offset)).intValue()); + } + } else if (obj instanceof BCLength) { + // previous must be a BCIndex + BCLength bcLength = (BCLength) obj; + BCIndex prevIndex = (BCIndex) previous; + int index = prevIndex.index + bcLength.length; + int actualLength = ((Integer) byteCodeOffsets.get(index)) + .intValue() + - prevIndex.actualValue; + bcLength.setActualValue(actualLength); + } + previous = obj; + } + renumbered = true; + } + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/OperandManager.java b/src/main/java/org/apache/harmony/unpack200/bytecode/OperandManager.java new file mode 100644 index 0000000..d4a0acb --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/OperandManager.java @@ -0,0 +1,242 @@ +/* + * 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.unpack200.bytecode; + +import org.apache.harmony.unpack200.Segment; +import org.apache.harmony.unpack200.SegmentConstantPool; + +/** + * This class keeps track of operands used. It provides API to let other classes + * get next elements, and also knows about which classes have been used recently + * in super, this and new references. + */ +public class OperandManager { + + int[] bcCaseCount; + int[] bcCaseValue; + int[] bcByte; + int[] bcShort; + int[] bcLocal; + int[] bcLabel; + int[] bcIntRef; + int[] bcFloatRef; + int[] bcLongRef; + int[] bcDoubleRef; + int[] bcStringRef; + int[] bcClassRef; + int[] bcFieldRef; + int[] bcMethodRef; + int[] bcIMethodRef; + int[] bcThisField; + int[] bcSuperField; + int[] bcThisMethod; + int[] bcSuperMethod; + int[] bcInitRef; + int[] wideByteCodes; + + int bcCaseCountIndex; + int bcCaseValueIndex; + int bcByteIndex; + int bcShortIndex; + int bcLocalIndex; + int bcLabelIndex; + int bcIntRefIndex; + int bcFloatRefIndex; + int bcLongRefIndex; + int bcDoubleRefIndex; + int bcStringRefIndex; + int bcClassRefIndex; + int bcFieldRefIndex; + int bcMethodRefIndex; + int bcIMethodRefIndex; + int bcThisFieldIndex; + int bcSuperFieldIndex; + int bcThisMethodIndex; + int bcSuperMethodIndex; + int bcInitRefIndex; + int wideByteCodeIndex; + + Segment segment; + + String currentClass; + String superClass; + String newClass; + + public OperandManager(int[] bcCaseCount, int[] bcCaseValue, int[] bcByte, + int[] bcShort, int[] bcLocal, int[] bcLabel, int[] bcIntRef, + int[] bcFloatRef, int[] bcLongRef, int[] bcDoubleRef, + int[] bcStringRef, int[] bcClassRef, int[] bcFieldRef, + int[] bcMethodRef, int[] bcIMethodRef, int[] bcThisField, + int[] bcSuperField, int[] bcThisMethod, int[] bcSuperMethod, + int[] bcInitRef, int[] wideByteCodes) { + this.bcCaseCount = bcCaseCount; + this.bcCaseValue = bcCaseValue; + this.bcByte = bcByte; + this.bcShort = bcShort; + this.bcLocal = bcLocal; + this.bcLabel = bcLabel; + this.bcIntRef = bcIntRef; + this.bcFloatRef = bcFloatRef; + this.bcLongRef = bcLongRef; + this.bcDoubleRef = bcDoubleRef; + this.bcStringRef = bcStringRef; + this.bcClassRef = bcClassRef; + this.bcFieldRef = bcFieldRef; + this.bcMethodRef = bcMethodRef; + this.bcIMethodRef = bcIMethodRef; + + this.bcThisField = bcThisField; + this.bcSuperField = bcSuperField; + this.bcThisMethod = bcThisMethod; + this.bcSuperMethod = bcSuperMethod; + this.bcInitRef = bcInitRef; + this.wideByteCodes = wideByteCodes; + } + + public int nextCaseCount() { + return bcCaseCount[bcCaseCountIndex++]; + } + + public int nextCaseValues() { + return bcCaseValue[bcCaseValueIndex++]; + } + + public int nextByte() { + return bcByte[bcByteIndex++]; + } + + public int nextShort() { + return bcShort[bcShortIndex++]; + } + + public int nextLocal() { + return bcLocal[bcLocalIndex++]; + } + + public int nextLabel() { + return bcLabel[bcLabelIndex++]; + } + + public int nextIntRef() { + return bcIntRef[bcIntRefIndex++]; + } + + public int nextFloatRef() { + return bcFloatRef[bcFloatRefIndex++]; + } + + public int nextLongRef() { + return bcLongRef[bcLongRefIndex++]; + } + + public int nextDoubleRef() { + return bcDoubleRef[bcDoubleRefIndex++]; + } + + public int nextStringRef() { + return bcStringRef[bcStringRefIndex++]; + } + + public int nextClassRef() { + return bcClassRef[bcClassRefIndex++]; + } + + public int nextFieldRef() { + return bcFieldRef[bcFieldRefIndex++]; + } + + public int nextMethodRef() { + return bcMethodRef[bcMethodRefIndex++]; + } + + public int nextIMethodRef() { + return bcIMethodRef[bcIMethodRefIndex++]; + } + + public int nextThisFieldRef() { + return bcThisField[bcThisFieldIndex++]; + } + + public int nextSuperFieldRef() { + return bcSuperField[bcSuperFieldIndex++]; + } + + public int nextThisMethodRef() { + return bcThisMethod[bcThisMethodIndex++]; + } + + public int nextSuperMethodRef() { + return bcSuperMethod[bcSuperMethodIndex++]; + } + + public int nextInitRef() { + return bcInitRef[bcInitRefIndex++]; + } + + public int nextWideByteCode() { + return wideByteCodes[wideByteCodeIndex++]; + } + + public void setSegment(Segment segment) { + this.segment = segment; + } + + public Segment getSegment() { + return segment; + } + + public SegmentConstantPool globalConstantPool() { + return segment.getConstantPool(); + } + + public void setCurrentClass(String string) { + currentClass = string; + } + + public void setSuperClass(String string) { + superClass = string; + } + + public void setNewClass(String string) { + newClass = string; + } + + public String getCurrentClass() { + if (null == currentClass) { + throw new Error("Current class not set yet"); + } else { + return currentClass; + } + } + + public String getSuperClass() { + if (null == superClass) { + throw new Error("SuperClass not set yet"); + } else { + return superClass; + } + } + + public String getNewClass() { + if (null == newClass) { + throw new Error("New class not set yet"); + } else { + return newClass; + } + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/RuntimeVisibleorInvisibleAnnotationsAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/RuntimeVisibleorInvisibleAnnotationsAttribute.java new file mode 100644 index 0000000..6d7daa5 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/RuntimeVisibleorInvisibleAnnotationsAttribute.java @@ -0,0 +1,84 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Annotations class file attribute, either a RuntimeVisibleAnnotations + * attribute or a RuntimeInvisibleAnnotations attribute. + */ +public class RuntimeVisibleorInvisibleAnnotationsAttribute extends + AnnotationsAttribute { + + private final int num_annotations; + private final Annotation[] annotations; + + public RuntimeVisibleorInvisibleAnnotationsAttribute(CPUTF8 name, + Annotation[] annotations) { + super(name); + this.num_annotations = annotations.length; + this.annotations = annotations; + } + + protected int getLength() { + int length = 2; + for (int i = 0; i < num_annotations; i++) { + length += annotations[i].getLength(); + } + return length; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + for (int i = 0; i < annotations.length; i++) { + annotations[i].resolve(pool); + } + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(num_annotations); + for (int i = 0; i < num_annotations; i++) { + annotations[i].writeBody(dos); + } + } + + public String toString() { + return attributeName.underlyingString() + ": " + num_annotations + + " annotations"; + } + + public boolean equals(Object obj) { + return this == obj; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + List nested = new ArrayList(); + nested.add(attributeName); + for (int i = 0; i < annotations.length; i++) { + nested.addAll(annotations[i].getClassFileEntries()); + } + ClassFileEntry[] nestedEntries = new ClassFileEntry[nested.size()]; + for (int i = 0; i < nestedEntries.length; i++) { + nestedEntries[i] = (ClassFileEntry) nested.get(i); + } + return nestedEntries; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/RuntimeVisibleorInvisibleParameterAnnotationsAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/RuntimeVisibleorInvisibleParameterAnnotationsAttribute.java new file mode 100644 index 0000000..02ccd3e --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/RuntimeVisibleorInvisibleParameterAnnotationsAttribute.java @@ -0,0 +1,130 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Parameter annotations class file attribute, either a + * RuntimeVisibleParameterAnnotations attribute or a + * RuntimeInvisibleParameterAnnotations attribute. + */ +public class RuntimeVisibleorInvisibleParameterAnnotationsAttribute extends + AnnotationsAttribute { + + private final int num_parameters; + private final ParameterAnnotation[] parameter_annotations; + + public RuntimeVisibleorInvisibleParameterAnnotationsAttribute(CPUTF8 name, + ParameterAnnotation[] parameter_annotations) { + super(name); + this.num_parameters = parameter_annotations.length; + this.parameter_annotations = parameter_annotations; + } + + protected int getLength() { + int length = 1; + for (int i = 0; i < num_parameters; i++) { + length += parameter_annotations[i].getLength(); + } + return length; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + for (int i = 0; i < parameter_annotations.length; i++) { + parameter_annotations[i].resolve(pool); + } + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeByte(num_parameters); + for (int i = 0; i < num_parameters; i++) { + parameter_annotations[i].writeBody(dos); + } + } + + public String toString() { + return attributeName.underlyingString() + ": " + num_parameters + + " parameter annotations"; + } + + /** + * ParameterAnnotation represents the annotations on a single parameter. + */ + public static class ParameterAnnotation { + + private final Annotation[] annotations; + private final int num_annotations; + + public ParameterAnnotation(Annotation[] annotations) { + this.num_annotations = annotations.length; + this.annotations = annotations; + } + + public void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(num_annotations); + for (int i = 0; i < annotations.length; i++) { + annotations[i].writeBody(dos); + } + } + + public void resolve(ClassConstantPool pool) { + for (int i = 0; i < annotations.length; i++) { + annotations[i].resolve(pool); + } + } + + public int getLength() { + int length = 2; + for (int i = 0; i < annotations.length; i++) { + length += annotations[i].getLength(); + } + return length; + } + + public List getClassFileEntries() { + List nested = new ArrayList(); + for (int i = 0; i < annotations.length; i++) { + nested.addAll(annotations[i].getClassFileEntries()); + } + return nested; + } + + } + + public boolean equals(Object obj) { + return this == obj; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + List nested = new ArrayList(); + nested.add(attributeName); + for (int i = 0; i < parameter_annotations.length; i++) { + nested.addAll(parameter_annotations[i].getClassFileEntries()); + } + ClassFileEntry[] nestedEntries = new ClassFileEntry[nested.size()]; + for (int i = 0; i < nestedEntries.length; i++) { + nestedEntries[i] = (ClassFileEntry) nested.get(i); + } + return nestedEntries; + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/SignatureAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/SignatureAttribute.java new file mode 100644 index 0000000..02bdb43 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/SignatureAttribute.java @@ -0,0 +1,83 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Signature class file attribute + */ +public class SignatureAttribute extends Attribute { + + private int signature_index; + private final CPUTF8 signature; + + private static CPUTF8 attributeName; + + public static void setAttributeName(CPUTF8 cpUTF8Value) { + attributeName = cpUTF8Value; + } + + public SignatureAttribute(CPUTF8 value) { + super(attributeName); + this.signature = value; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#getLength() + */ + protected int getLength() { + return 2; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + return new ClassFileEntry[] { getAttributeName(), signature }; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + signature.resolve(pool); + signature_index = pool.indexOf(signature); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#writeBody(java.io.DataOutputStream) + */ + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(signature_index); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.ClassFileEntry#toString() + */ + public String toString() { + // TODO Auto-generated method stub + return "Signature: " + signature; + } + + + public boolean equals(Object obj) { + return this == obj; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/SourceFileAttribute.java b/src/main/java/org/apache/harmony/unpack200/bytecode/SourceFileAttribute.java new file mode 100644 index 0000000..e01224a --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/SourceFileAttribute.java @@ -0,0 +1,92 @@ +/* + * 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.unpack200.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Source file class file attribute + */ +public class SourceFileAttribute extends Attribute { + + private final CPUTF8 name; + private int nameIndex; + private static CPUTF8 attributeName; + + public static void setAttributeName(CPUTF8 cpUTF8Value) { + attributeName = cpUTF8Value; + } + + public SourceFileAttribute(CPUTF8 name) { + super(attributeName); + this.name = name; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (this.getClass() != obj.getClass()) + return false; + final SourceFileAttribute other = (SourceFileAttribute) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.Attribute#isSourceFileAttribute() + */ + public boolean isSourceFileAttribute() { + return true; + } + + protected int getLength() { + return 2; + } + + protected ClassFileEntry[] getNestedClassFileEntries() { + return new ClassFileEntry[] { getAttributeName(), name }; + } + + public int hashCode() { + final int PRIME = 31; + int result = super.hashCode(); + result = PRIME * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + protected void resolve(ClassConstantPool pool) { + super.resolve(pool); + nameIndex = pool.indexOf(name); + } + + public String toString() { + return "SourceFile: " + name; + } + + protected void writeBody(DataOutputStream dos) throws IOException { + dos.writeShort(nameIndex); + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ByteCodeForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ByteCodeForm.java new file mode 100644 index 0000000..62250d0 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ByteCodeForm.java @@ -0,0 +1,684 @@ +/* + * 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.unpack200.bytecode.forms; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.CodeAttribute; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +public class ByteCodeForm { + + // Operand types. Those with no operands and + // those with operands on the operand stack are + // treated similarly. These types are used to + // distinguish different kinds of non-stack + // bytecode operands + protected static final int TYPE_NONE = 0; + protected static final int TYPE_BYTE = 1; + protected static final int TYPE_SHORT = 2; + protected static final int TYPE_INTREF = 3; + protected static final int TYPE_FLOATREF = 4; + protected static final int TYPE_STRINGREF = 5; + protected static final int TYPE_CLASSREF = 6; + protected static final int TYPE_LONG = 7; + protected static final int TYPE_DOUBLE = 8; + protected static final int TYPE_LOCAL = 9; + protected static final int TYPE_LABEL = 10; + protected static final int TYPE_FIELDREF = 11; + protected static final int TYPE_METHODREF = 12; + protected static final int TYPE_IMETHODREF = 13; + protected static final int TYPE_WIDE = 14; + protected static final int TYPE_IINC = 15; + protected static final int TYPE_SWITCH = 16; + protected static final int TYPE_MULTIANEWARRAY = 17; + protected static final int TYPE_THISFIELDREF = 18; + protected static final int TYPE_THISMETHODREF = 19; + protected static final int TYPE_SUPERFIELDREF = 20; + protected static final int TYPE_SUPERMETHODREF = 21; + protected static final int TYPE_THISINITMETHODREF = 22; + protected static final int TYPE_SUPERINITMETHODREF = 23; + protected static final int TYPE_NEWINITMETHODREF = 24; + protected static final int TYPE_NEWCLASSREF = 25; + + protected static final boolean WIDENED = true; + + protected static final ByteCodeForm[] byteCodeArray = new ByteCodeForm[256]; + protected static final Map byteCodesByName = new HashMap(256); + static { + byteCodeArray[0] = new NoArgumentForm(0, "nop"); + byteCodeArray[1] = new NoArgumentForm(1, "aconst_null"); + byteCodeArray[2] = new NoArgumentForm(2, "iconst_m1"); + byteCodeArray[3] = new NoArgumentForm(3, "iconst_0"); + byteCodeArray[4] = new NoArgumentForm(4, "iconst_1"); + byteCodeArray[5] = new NoArgumentForm(5, "iconst_2"); + byteCodeArray[6] = new NoArgumentForm(6, "iconst_3"); + byteCodeArray[7] = new NoArgumentForm(7, "iconst_4"); + byteCodeArray[8] = new NoArgumentForm(8, "iconst_5"); + byteCodeArray[9] = new NoArgumentForm(9, "lconst_0"); + byteCodeArray[10] = new NoArgumentForm(10, "lconst_1"); + byteCodeArray[11] = new NoArgumentForm(11, "fconst_0"); + byteCodeArray[12] = new NoArgumentForm(12, "fconst_1"); + byteCodeArray[13] = new NoArgumentForm(13, "fconst_2"); + byteCodeArray[14] = new NoArgumentForm(14, "dconst_0"); + byteCodeArray[15] = new NoArgumentForm(15, "dconst_1"); + byteCodeArray[16] = new ByteForm(16, "bipush", new int[] { 16, -1 }); + byteCodeArray[17] = new ShortForm(17, "sipush", + new int[] { 17, -1, -1 }); + byteCodeArray[18] = new StringRefForm(18, "ldc", new int[] { 18, -1 }); + byteCodeArray[19] = new StringRefForm(19, "ldc_w", new int[] { 19, -1, + -1 }, WIDENED); + byteCodeArray[20] = new LongForm(20, "ldc2_w", new int[] { 20, -1, -1 }); + byteCodeArray[21] = new LocalForm(21, "iload", new int[] { 21, -1 }); + byteCodeArray[22] = new LocalForm(22, "lload", new int[] { 22, -1 }); + byteCodeArray[23] = new LocalForm(23, "fload", new int[] { 23, -1 }); + byteCodeArray[24] = new LocalForm(24, "dload", new int[] { 24, -1 }); + byteCodeArray[25] = new LocalForm(25, "aload", new int[] { 25, -1 }); + byteCodeArray[26] = new NoArgumentForm(26, "iload_0"); + byteCodeArray[27] = new NoArgumentForm(27, "iload_1"); + byteCodeArray[28] = new NoArgumentForm(28, "iload_2"); + byteCodeArray[29] = new NoArgumentForm(29, "iload_3"); + byteCodeArray[30] = new NoArgumentForm(30, "lload_0"); + byteCodeArray[31] = new NoArgumentForm(31, "lload_1"); + byteCodeArray[32] = new NoArgumentForm(32, "lload_2"); + byteCodeArray[33] = new NoArgumentForm(33, "lload_3"); + byteCodeArray[34] = new NoArgumentForm(34, "fload_0"); + byteCodeArray[35] = new NoArgumentForm(35, "fload_1"); + byteCodeArray[36] = new NoArgumentForm(36, "fload_2"); + byteCodeArray[37] = new NoArgumentForm(37, "fload_3"); + byteCodeArray[38] = new NoArgumentForm(38, "dload_0"); + byteCodeArray[39] = new NoArgumentForm(39, "dload_1"); + byteCodeArray[40] = new NoArgumentForm(40, "dload_2"); + byteCodeArray[41] = new NoArgumentForm(41, "dload_3"); + byteCodeArray[42] = new NoArgumentForm(42, "aload_0"); + byteCodeArray[43] = new NoArgumentForm(43, "aload_1"); + byteCodeArray[44] = new NoArgumentForm(44, "aload_2"); + byteCodeArray[45] = new NoArgumentForm(45, "aload_3"); + byteCodeArray[46] = new NoArgumentForm(46, "iaload"); + byteCodeArray[47] = new NoArgumentForm(47, "laload"); + byteCodeArray[48] = new NoArgumentForm(48, "faload"); + byteCodeArray[49] = new NoArgumentForm(49, "daload"); + byteCodeArray[50] = new NoArgumentForm(50, "aaload"); + byteCodeArray[51] = new NoArgumentForm(51, "baload"); + byteCodeArray[52] = new NoArgumentForm(52, "caload"); + byteCodeArray[53] = new NoArgumentForm(53, "saload"); + byteCodeArray[54] = new LocalForm(54, "istore", new int[] { 54, -1 }); + byteCodeArray[55] = new LocalForm(55, "lstore", new int[] { 55, -1 }); + byteCodeArray[56] = new LocalForm(56, "fstore", new int[] { 56, -1 }); + byteCodeArray[57] = new LocalForm(57, "dstore", new int[] { 57, -1 }); + byteCodeArray[58] = new LocalForm(58, "astore", new int[] { 58, -1 }); + byteCodeArray[59] = new NoArgumentForm(59, "istore_0"); + byteCodeArray[60] = new NoArgumentForm(60, "istore_1"); + byteCodeArray[61] = new NoArgumentForm(61, "istore_2"); + byteCodeArray[62] = new NoArgumentForm(62, "istore_3"); + byteCodeArray[63] = new NoArgumentForm(63, "lstore_0"); + byteCodeArray[64] = new NoArgumentForm(64, "lstore_1"); + byteCodeArray[65] = new NoArgumentForm(65, "lstore_2"); + byteCodeArray[66] = new NoArgumentForm(66, "lstore_3"); + byteCodeArray[67] = new NoArgumentForm(67, "fstore_0"); + byteCodeArray[68] = new NoArgumentForm(68, "fstore_1"); + byteCodeArray[69] = new NoArgumentForm(69, "fstore_2"); + byteCodeArray[70] = new NoArgumentForm(70, "fstore_3"); + byteCodeArray[71] = new NoArgumentForm(71, "dstore_0"); + byteCodeArray[72] = new NoArgumentForm(72, "dstore_1"); + byteCodeArray[73] = new NoArgumentForm(73, "dstore_2"); + byteCodeArray[74] = new NoArgumentForm(74, "dstore_3"); + byteCodeArray[75] = new NoArgumentForm(75, "astore_0"); + byteCodeArray[76] = new NoArgumentForm(76, "astore_1"); + byteCodeArray[77] = new NoArgumentForm(77, "astore_2"); + byteCodeArray[78] = new NoArgumentForm(78, "astore_3"); + byteCodeArray[79] = new NoArgumentForm(79, "iastore"); + byteCodeArray[80] = new NoArgumentForm(80, "lastore"); + byteCodeArray[81] = new NoArgumentForm(81, "fastore"); + byteCodeArray[82] = new NoArgumentForm(82, "dastore"); + byteCodeArray[83] = new NoArgumentForm(83, "aastore"); + byteCodeArray[84] = new NoArgumentForm(84, "bastore"); + byteCodeArray[85] = new NoArgumentForm(85, "castore"); + byteCodeArray[86] = new NoArgumentForm(86, "sastore"); + byteCodeArray[87] = new NoArgumentForm(87, "pop"); + byteCodeArray[88] = new NoArgumentForm(88, "pop2"); + byteCodeArray[89] = new NoArgumentForm(89, "dup"); + byteCodeArray[90] = new NoArgumentForm(90, "dup_x1"); + byteCodeArray[91] = new NoArgumentForm(91, "dup_x2"); + byteCodeArray[92] = new NoArgumentForm(92, "dup2"); + byteCodeArray[93] = new NoArgumentForm(93, "dup2_x1"); + byteCodeArray[94] = new NoArgumentForm(94, "dup2_x2"); + byteCodeArray[95] = new NoArgumentForm(95, "swap"); + byteCodeArray[96] = new NoArgumentForm(96, "iadd"); + byteCodeArray[97] = new NoArgumentForm(97, "ladd"); + byteCodeArray[98] = new NoArgumentForm(98, "fadd"); + byteCodeArray[99] = new NoArgumentForm(99, "dadd"); + byteCodeArray[100] = new NoArgumentForm(100, "isub"); + byteCodeArray[101] = new NoArgumentForm(101, "lsub"); + byteCodeArray[102] = new NoArgumentForm(102, "fsub"); + byteCodeArray[103] = new NoArgumentForm(103, "dsub"); + byteCodeArray[104] = new NoArgumentForm(104, "imul"); + byteCodeArray[105] = new NoArgumentForm(105, "lmul"); + byteCodeArray[106] = new NoArgumentForm(106, "fmul"); + byteCodeArray[107] = new NoArgumentForm(107, "dmul"); + byteCodeArray[108] = new NoArgumentForm(108, "idiv"); + byteCodeArray[109] = new NoArgumentForm(109, "ldiv"); + byteCodeArray[110] = new NoArgumentForm(110, "fdiv"); + byteCodeArray[111] = new NoArgumentForm(111, "ddiv"); + byteCodeArray[112] = new NoArgumentForm(112, "irem"); + byteCodeArray[113] = new NoArgumentForm(113, "lrem"); + byteCodeArray[114] = new NoArgumentForm(114, "frem"); + byteCodeArray[115] = new NoArgumentForm(115, "drem"); + byteCodeArray[116] = new NoArgumentForm(116, ""); + byteCodeArray[117] = new NoArgumentForm(117, "lneg"); + byteCodeArray[118] = new NoArgumentForm(118, "fneg"); + byteCodeArray[119] = new NoArgumentForm(119, "dneg"); + byteCodeArray[120] = new NoArgumentForm(120, "ishl"); + byteCodeArray[121] = new NoArgumentForm(121, "lshl"); + byteCodeArray[122] = new NoArgumentForm(122, "ishr"); + byteCodeArray[123] = new NoArgumentForm(123, "lshr"); + byteCodeArray[124] = new NoArgumentForm(124, "iushr"); + byteCodeArray[125] = new NoArgumentForm(125, "lushr"); + byteCodeArray[126] = new NoArgumentForm(126, "iand"); + byteCodeArray[127] = new NoArgumentForm(127, "land"); + byteCodeArray[128] = new NoArgumentForm(128, "ior"); + byteCodeArray[129] = new NoArgumentForm(129, "lor"); + byteCodeArray[130] = new NoArgumentForm(130, "ixor"); + byteCodeArray[131] = new NoArgumentForm(131, "lxor"); + byteCodeArray[132] = new IincForm(132, "iinc", + new int[] { 132, -1, -1 }); + byteCodeArray[133] = new NoArgumentForm(133, "i2l"); + byteCodeArray[134] = new NoArgumentForm(134, "i2f"); + byteCodeArray[135] = new NoArgumentForm(135, "i2d"); + byteCodeArray[136] = new NoArgumentForm(136, "l2i"); + byteCodeArray[137] = new NoArgumentForm(137, "l2f"); + byteCodeArray[138] = new NoArgumentForm(138, "l2d"); + byteCodeArray[139] = new NoArgumentForm(139, "f2i"); + byteCodeArray[140] = new NoArgumentForm(140, "f2l"); + byteCodeArray[141] = new NoArgumentForm(141, "f2d"); + byteCodeArray[142] = new NoArgumentForm(142, "d2i"); + byteCodeArray[143] = new NoArgumentForm(143, "d2l"); + byteCodeArray[144] = new NoArgumentForm(144, "d2f"); + byteCodeArray[145] = new NoArgumentForm(145, "i2b"); + byteCodeArray[146] = new NoArgumentForm(146, "i2c"); + byteCodeArray[147] = new NoArgumentForm(147, "i2s"); + byteCodeArray[148] = new NoArgumentForm(148, "lcmp"); + byteCodeArray[149] = new NoArgumentForm(149, "fcmpl"); + byteCodeArray[150] = new NoArgumentForm(150, "fcmpg"); + byteCodeArray[151] = new NoArgumentForm(151, "dcmpl"); + byteCodeArray[152] = new NoArgumentForm(152, "dcmpg"); + byteCodeArray[153] = new LabelForm(153, "ifeq", + new int[] { 153, -1, -1 }); + byteCodeArray[154] = new LabelForm(154, "ifne", + new int[] { 154, -1, -1 }); + byteCodeArray[155] = new LabelForm(155, "iflt", + new int[] { 155, -1, -1 }); + byteCodeArray[156] = new LabelForm(156, "ifge", + new int[] { 156, -1, -1 }); + byteCodeArray[157] = new LabelForm(157, "ifgt", + new int[] { 157, -1, -1 }); + byteCodeArray[158] = new LabelForm(158, "ifle", + new int[] { 158, -1, -1 }); + byteCodeArray[159] = new LabelForm(159, "if_icmpeq", new int[] { 159, + -1, -1 }); + byteCodeArray[160] = new LabelForm(160, "if_icmpne", new int[] { 160, + -1, -1 }); + byteCodeArray[161] = new LabelForm(161, "if_icmplt", new int[] { 161, + -1, -1 }); + byteCodeArray[162] = new LabelForm(162, "if_icmpge", new int[] { 162, + -1, -1 }); + byteCodeArray[163] = new LabelForm(163, "if_icmpgt", new int[] { 163, + -1, -1 }); + byteCodeArray[164] = new LabelForm(164, "if_icmple", new int[] { 164, + -1, -1 }); + byteCodeArray[165] = new LabelForm(165, "if_acmpeq", new int[] { 165, + -1, -1 }); + byteCodeArray[166] = new LabelForm(166, "if_acmpne", new int[] { 166, + -1, -1 }); + byteCodeArray[167] = new LabelForm(167, "goto", + new int[] { 167, -1, -1 }); + byteCodeArray[168] = new LabelForm(168, "jsr", + new int[] { 168, -1, -1 }); + byteCodeArray[169] = new LocalForm(169, "ret", new int[] { 169, -1 }); + byteCodeArray[170] = new TableSwitchForm(170, "tableswitch"); + byteCodeArray[171] = new LookupSwitchForm(171, "lookupswitch"); + byteCodeArray[172] = new NoArgumentForm(172, "ireturn"); + byteCodeArray[173] = new NoArgumentForm(173, "lreturn"); + byteCodeArray[174] = new NoArgumentForm(174, "freturn"); + byteCodeArray[175] = new NoArgumentForm(175, "dreturn"); + byteCodeArray[176] = new NoArgumentForm(176, "areturn"); + byteCodeArray[177] = new NoArgumentForm(177, "return"); + byteCodeArray[178] = new FieldRefForm(178, "getstatic", new int[] { + 178, -1, -1 }); + byteCodeArray[179] = new FieldRefForm(179, "putstatic", new int[] { + 179, -1, -1 }); + byteCodeArray[180] = new FieldRefForm(180, "getfield", new int[] { 180, + -1, -1 }); + byteCodeArray[181] = new FieldRefForm(181, "putfield", new int[] { 181, + -1, -1 }); + byteCodeArray[182] = new MethodRefForm(182, "invokevirtual", new int[] { + 182, -1, -1 }); + byteCodeArray[183] = new MethodRefForm(183, "invokespecial", new int[] { + 183, -1, -1 }); + byteCodeArray[184] = new MethodRefForm(184, "invokestatic", new int[] { + 184, -1, -1 }); + byteCodeArray[185] = new IMethodRefForm(185, "invokeinterface", + new int[] { 185, -1, -1, /* count */-1, 0 }); + byteCodeArray[186] = new NoArgumentForm(186, "xxxunusedxxx"); + byteCodeArray[187] = new NewClassRefForm(187, "new", new int[] { 187, + -1, -1 }); + byteCodeArray[188] = new ByteForm(188, "newarray", + new int[] { 188, -1 }); + byteCodeArray[189] = new ClassRefForm(189, "anewarray", new int[] { + 189, -1, -1 }); + byteCodeArray[190] = new NoArgumentForm(190, "arraylength"); + byteCodeArray[191] = new NoArgumentForm(191, "athrow"); + byteCodeArray[192] = new ClassRefForm(192, "checkcast", new int[] { + 192, -1, -1 }); + byteCodeArray[193] = new ClassRefForm(193, "instanceof", new int[] { + 193, -1, -1 }); + byteCodeArray[194] = new NoArgumentForm(194, "monitorenter"); + byteCodeArray[195] = new NoArgumentForm(195, "monitorexit"); + byteCodeArray[196] = new WideForm(196, "wide"); + byteCodeArray[197] = new MultiANewArrayForm(197, "multianewarray", + new int[] { 197, -1, -1, -1 }); + byteCodeArray[198] = new LabelForm(198, "ifnull", new int[] { 198, -1, + -1 }); + byteCodeArray[199] = new LabelForm(199, "ifnonnull", new int[] { 199, + -1, -1 }); + byteCodeArray[200] = new LabelForm(200, "goto_w", new int[] { 200, -1, + -1, -1, -1 }, WIDENED); + byteCodeArray[201] = new LabelForm(201, "jsr_w", new int[] { 201, -1, + -1, -1, -1 }, WIDENED); + + // Extra ones defined by pack200 + byteCodeArray[202] = new ThisFieldRefForm(202, "getstatic_this", + new int[] { 178, -1, -1 }); + byteCodeArray[203] = new ThisFieldRefForm(203, "putstatic_this", + new int[] { 179, -1, -1 }); + byteCodeArray[204] = new ThisFieldRefForm(204, "getfield_this", + new int[] { 180, -1, -1 }); + byteCodeArray[205] = new ThisFieldRefForm(205, "putfield_this", + new int[] { 181, -1, -1 }); + byteCodeArray[206] = new ThisMethodRefForm(206, "invokevirtual_this", + new int[] { 182, -1, -1 }); + byteCodeArray[207] = new ThisMethodRefForm(207, "invokespecial_this", + new int[] { 183, -1, -1 }); + byteCodeArray[208] = new ThisMethodRefForm(208, "invokestatic_this", + new int[] { 184, -1, -1 }); + byteCodeArray[209] = new ThisFieldRefForm(209, + "aload_0_getstatic_this", new int[] { 42, 178, -1, -1 }); + byteCodeArray[210] = new ThisFieldRefForm(210, + "aload_0_putstatic_this", new int[] { 42, 179, -1, -1 }); + byteCodeArray[211] = new ThisFieldRefForm(211, "aload_0_getfield_this", + new int[] { 42, 180, -1, -1 }); + byteCodeArray[212] = new ThisFieldRefForm(212, "aload_0_putfield_this", + new int[] { 42, 181, -1, -1 }); + byteCodeArray[213] = new ThisMethodRefForm(213, + "aload_0_invokevirtual_this", new int[] { 42, 182, -1, -1 }); + byteCodeArray[214] = new ThisMethodRefForm(214, + "aload_0_invokespecial_this", new int[] { 42, 183, -1, -1 }); + byteCodeArray[215] = new ThisMethodRefForm(215, + "aload_0_invokestatic_this", new int[] { 42, 184, -1, -1 }); + byteCodeArray[216] = new SuperFieldRefForm(216, "getstatic_super", + new int[] { 178, -1, -1 }); + byteCodeArray[217] = new SuperFieldRefForm(217, "putstatic_super", + new int[] { 179, -1, -1 }); + byteCodeArray[218] = new SuperFieldRefForm(218, "getfield_super", + new int[] { 180, -1, -1 }); + byteCodeArray[219] = new SuperFieldRefForm(219, "putfield_super", + new int[] { 181, -1, -1 }); + byteCodeArray[220] = new SuperMethodRefForm(220, "invokevirtual_super", + new int[] { 182, -1, -1 }); + byteCodeArray[221] = new SuperMethodRefForm(221, "invokespecial_super", + new int[] { 183, -1, -1 }); + byteCodeArray[222] = new SuperMethodRefForm(222, "invokestatic_super", + new int[] { 184, -1, -1 }); + byteCodeArray[223] = new SuperFieldRefForm(223, + "aload_0_getstatic_super", new int[] { 42, 178, -1, -1 }); + byteCodeArray[224] = new SuperFieldRefForm(224, + "aload_0_putstatic_super", new int[] { 42, 179, -1, -1 }); + byteCodeArray[225] = new SuperFieldRefForm(225, + "aload_0_getfield_super", new int[] { 42, 180, -1, -1 }); + byteCodeArray[226] = new SuperFieldRefForm(226, + "aload_0_putfield_super", new int[] { 42, 181, -1, -1 }); + byteCodeArray[227] = new SuperMethodRefForm(227, + "aload_0_invokevirtual_super", new int[] { 42, 182, -1, -1 }); + byteCodeArray[228] = new SuperMethodRefForm(228, + "aload_0_invokespecial_super", new int[] { 42, 183, -1, -1 }); + byteCodeArray[229] = new SuperMethodRefForm(229, + "aload_0_invokestatic_super", new int[] { 42, 184, -1, -1 }); + byteCodeArray[230] = new ThisInitMethodRefForm(230, + "invokespecial_this_init", new int[] { 183, -1, -1 }); + byteCodeArray[231] = new SuperInitMethodRefForm(231, + "invokespecial_super_init", new int[] { 183, -1, -1 }); + byteCodeArray[232] = new NewInitMethodRefForm(232, + "invokespecial_new_init", new int[] { 183, -1, -1 }); + byteCodeArray[233] = new NarrowClassRefForm(233, "cldc", new int[] { + 18, -1 }); + byteCodeArray[234] = new IntRefForm(234, "ildc", new int[] { 18, -1 }); + byteCodeArray[235] = new FloatRefForm(235, "fldc", new int[] { 18, -1 }); + byteCodeArray[236] = new NarrowClassRefForm(236, "cldc_w", new int[] { + 19, -1, -1 }, WIDENED); + byteCodeArray[237] = new IntRefForm(237, "ildc_w", new int[] { 19, -1, + -1 }, WIDENED); + byteCodeArray[238] = new FloatRefForm(238, "fldc_w", new int[] { 19, + -1, -1 }, WIDENED); + byteCodeArray[239] = new DoubleForm(239, "dldc2_w", new int[] { 20, -1, + -1 }); + + // Reserved bytecodes + byteCodeArray[254] = new NoArgumentForm(254, "impdep1"); + byteCodeArray[255] = new NoArgumentForm(255, "impdep2"); + + // Bytecodes that aren't defined in the spec but are useful when + // unpacking (all must be >255) + // maybe wide versions of the others? etc. + + // Put all the bytecodes in a HashMap so we can + // get them by either name or number + for (int i = 0; i < byteCodeArray.length; i++) { + final ByteCodeForm byteCode = byteCodeArray[i]; + if (byteCode != null) { + byteCodesByName.put(byteCode.getName(), byteCode); + } + } + } + + private final int opcode; + private final String name; + private final int[] rewrite; + private int firstOperandIndex; + private int operandLength; + + /** + * Answer a new instance of this class with the specified opcode and name. + * Assume no rewrite. + * + * @param opcode + * int corresponding to the opcode's value + * @param name + * String printable name of the opcode + */ + public ByteCodeForm(int opcode, String name) { + this(opcode, name, new int[] { opcode }); + } + + /** + * Answer a new instance of this class with the specified opcode, name, + * operandType and rewrite + * + * @param opcode + * int corresponding to the opcode's value + * @param name + * String printable name of the opcode + * @param rewrite + * int[] Array of ints. Operand positions (which will later be + * rewritten in ByteCodes) are indicated by -1. + */ + public ByteCodeForm(int opcode, String name, int[] rewrite) { + this.opcode = opcode; + this.name = name; + this.rewrite = rewrite; + calculateOperandPosition(); + } + + protected void calculateOperandPosition() { + firstOperandIndex = -1; + operandLength = -1; + + // Find the first negative number in the rewrite array + int iterationIndex = 0; + while (iterationIndex < rewrite.length) { + if (rewrite[iterationIndex] < 0) { + // Found the first opcode to substitute + firstOperandIndex = iterationIndex; + iterationIndex = rewrite.length; + } else { + iterationIndex++; + } + } + + if (firstOperandIndex == -1) { + // Nothing more to do since the opcode has no operands + return; + } + + // Find the last negative number in the rewrite array + int lastOperandIndex = -1; + iterationIndex = firstOperandIndex; + while (iterationIndex < rewrite.length) { + if (rewrite[iterationIndex] < 0) { + lastOperandIndex = iterationIndex; + } + iterationIndex++; + } + + // Now we have the first index and the last index. + final int difference = lastOperandIndex - firstOperandIndex; + + // If last < first, something is wrong. + if (difference < 0) { + throw new Error( + "Logic error: not finding rewrite operands correctly"); + } + operandLength = difference + 1; + } + + public static ByteCodeForm get(int opcode) { + return (ByteCodeForm) byteCodeArray[opcode]; + } + + public static ByteCodeForm get(String name) { + return (ByteCodeForm) byteCodesByName.get(name); + } + + public String toString() { + return this.getClass().getName() + "(" + getName() + ":" + + getOperandType() + ")"; + } + + public int getOpcode() { + return opcode; + } + + public String getName() { + return name; + } + + public int getOperandType() { + return -1; + } + + public int[] getRewrite() { + return rewrite; + } + + public int[] getRewriteCopy() { + final int[] result = new int[rewrite.length]; + System.arraycopy(rewrite, 0, result, 0, rewrite.length); + return result; + } + + public int firstOperandIndex() { + return firstOperandIndex; + } + + public int operandLength() { + return operandLength; + } + + public boolean hasNoOperand() { + return false; + } + + public boolean hasByteOperand() { + return false; + } + + public boolean hasShortOperand() { + return false; + } + + public boolean hasIntRefOperand() { + return false; + } + + public boolean hasFloatRefOperand() { + return false; + } + + public boolean hasStringRefOperand() { + return false; + } + + public boolean hasClassRefOperand() { + return false; + } + + public boolean hasLongOperand() { + return false; + } + + public boolean hasDoubleOperand() { + return false; + } + + public boolean hasLocalOperand() { + return false; + } + + public boolean hasLabelOperand() { + return false; + } + + public boolean hasFieldRefOperand() { + return false; + } + + public boolean hasMethodRefOperand() { + return false; + } + + public boolean hasIMethodRefOperand() { + return false; + } + + public boolean hasWideOperand() { + return false; + } + + public boolean hasIincOperand() { + return false; + } + + public boolean hasSwitchOperand() { + return false; + } + + public boolean hasMultianewarrayOperand() { + return false; + } + + public boolean hasThisFieldRefOperand() { + return false; + } + + public boolean hasThisMethodRefOperand() { + return false; + } + + public boolean hasSuperFieldRefOperand() { + return false; + } + + public boolean hasSuperMethodRefOperand() { + return false; + } + + public boolean hasThisInitMethodRefOperand() { + return false; + } + + public boolean hasSuperInitMethodRefOperand() { + return false; + } + + public boolean hasNewInitMethodRefOperand() { + return false; + } + + public boolean hasInitMethodRefOperand() { + return false; + } + + /** + * This method will answer true if the receiver is a multi-bytecode + * instruction (such as aload0_putfield_super); otherwise, it will answer + * false. + * + * @return boolean true if multibytecode, false otherwise + */ + public boolean hasMultipleByteCodes() { + if (rewrite.length > 1) { + // Currently, all multi-bytecode instructions + // begin with aload_0, so this is how we test. + if (rewrite[0] == 42) { + // If there's an instruction (not a negative + // number, which is an operand) after the + // aload_0, it's a multibytecode instruction. + return (rewrite[1] > 0); + } + } + return false; + } + + /** + * When passed a byteCode, an OperandTable and a SegmentConstantPool, this + * method will set the rewrite of the byteCode appropriately. + * + * @param byteCode + * ByteCode to be updated (!) + * @param operandManager + * OperandTable from which to draw info + * @param codeLength + * Length of bytes (excluding this bytecode) from the beginning + * of the method. Used in calculating padding for some + * variable-length bytecodes (such as lookupswitch, tableswitch). + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + throw new Error("My subclass should have implemented this"); + } + + /** + * The ByteCodeForm knows how to fix up a bytecode if it needs to be fixed + * up because it holds a Label bytecode. + * + * @param byteCode + * a ByteCode to be fixed up + * @param codeAttribute + * a CodeAttribute used to determine how the ByteCode should be + * fixed up. + */ + public void fixUpByteCodeTargets(ByteCode byteCode, + CodeAttribute codeAttribute) { + // Most ByteCodeForms don't have any fixing up to do. + return; + } + + public boolean nestedMustStartClassPool() { + return false; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ByteForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ByteForm.java new file mode 100644 index 0000000..07a3872 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ByteForm.java @@ -0,0 +1,50 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the form for bytecodes which have single byte operands. + */ +public class ByteForm extends ByteCodeForm { + + public ByteForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_BYTE; + } + + public boolean hasByteOperand() { + return true; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandTable, + * org.apache.harmony.unpack200.SegmentConstantPool) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + byteCode.setOperandByte(operandManager.nextByte() & 0xFF, 0); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ClassRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ClassRefForm.java new file mode 100644 index 0000000..71c1486 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ClassRefForm.java @@ -0,0 +1,79 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for those bytecodes which have class + * references (and only class references). + */ +public class ClassRefForm extends ReferenceForm { + + protected boolean widened; + + public ClassRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public ClassRefForm(int opcode, String name, int[] rewrite, boolean widened) { + this(opcode, name, rewrite); + this.widened = widened; + } + + protected void setNestedEntries(ByteCode byteCode, + OperandManager operandManager, int offset) throws Pack200Exception { + // If the offset is not zero, proceed normally. + if (offset != 0) { + super.setNestedEntries(byteCode, operandManager, offset - 1); + return; + } + // If the offset is 0, ClassRefForms refer to + // the current class. Add that as the nested class. + // (This is true for all bc_classref forms in + // the spec except for multianewarray, which has + // its own form.) + final SegmentConstantPool globalPool = operandManager + .globalConstantPool(); + ClassFileEntry[] nested = null; + // How do I get this class? + nested = new ClassFileEntry[] { globalPool + .getClassPoolEntry(operandManager.getCurrentClass()) }; + byteCode.setNested(nested); + byteCode.setNestedPositions(new int[][] { { 0, 2 } }); + } + + public int getOperandType() { + return TYPE_CLASSREF; + } + + public boolean hasClassRefOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextClassRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_CLASS; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ClassSpecificReferenceForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ClassSpecificReferenceForm.java new file mode 100644 index 0000000..a3f11a3 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ClassSpecificReferenceForm.java @@ -0,0 +1,55 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * Abstract superclass of all classes that have class-specific references to + * constant pool information. These classes have a context (a string + * representing a pack200 class) i.e., they send getClassSpecificPoolEntry + * instead of getConstantPoolEntry. + * + */ +public abstract class ClassSpecificReferenceForm extends ReferenceForm { + + public ClassSpecificReferenceForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + protected abstract int getOffset(OperandManager operandManager); + + protected abstract int getPoolID(); + + protected abstract String context(OperandManager operandManager); + + protected void setNestedEntries(ByteCode byteCode, + OperandManager operandManager, int offset) throws Pack200Exception { + final SegmentConstantPool globalPool = operandManager + .globalConstantPool(); + ClassFileEntry[] nested = null; + nested = new ClassFileEntry[] { globalPool.getClassSpecificPoolEntry( + getPoolID(), offset, context(operandManager)) }; + byteCode.setNested(nested); + byteCode.setNestedPositions(new int[][] { { 0, 2 } }); + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/DoubleForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/DoubleForm.java new file mode 100644 index 0000000..30431f0 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/DoubleForm.java @@ -0,0 +1,47 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for those bytecodes which have + * double references (and only double references). + */ +public class DoubleForm extends ReferenceForm { + + public DoubleForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_DOUBLE; + } + + public boolean hasDoubleOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextDoubleRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_DOUBLE; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/FieldRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/FieldRefForm.java new file mode 100644 index 0000000..e95544f --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/FieldRefForm.java @@ -0,0 +1,47 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for those bytecodes which have field + * references (and only field references). + */ +public class FieldRefForm extends ReferenceForm { + + public FieldRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_FIELDREF; + } + + public boolean hasFieldRefOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextFieldRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_FIELD; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/FloatRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/FloatRefForm.java new file mode 100644 index 0000000..8b1690a --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/FloatRefForm.java @@ -0,0 +1,51 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for those bytecodes which have float + * references (and only float references). + */ +public class FloatRefForm extends SingleByteReferenceForm { + + public FloatRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public FloatRefForm(int opcode, String name, int[] rewrite, boolean widened) { + this(opcode, name, rewrite); + this.widened = widened; + } + + public int getOperandType() { + return TYPE_FLOATREF; + } + + public boolean hasFloatRefOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextFloatRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_FLOAT; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/IMethodRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/IMethodRefForm.java new file mode 100644 index 0000000..9bb9be1 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/IMethodRefForm.java @@ -0,0 +1,64 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.CPInterfaceMethodRef; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for those bytecodes which have + * IMethod references (and only IMethod references). + */ +public class IMethodRefForm extends ReferenceForm { + + public IMethodRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_IMETHODREF; + } + + public boolean hasIMethodRefOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextIMethodRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_IMETHOD; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandTable, + * org.apache.harmony.unpack200.Segment) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + super.setByteCodeOperands(byteCode, operandManager, codeLength); + final int count = ((CPInterfaceMethodRef) byteCode + .getNestedClassFileEntries()[0]).invokeInterfaceCount(); + byteCode.getRewrite()[3] = count; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/IincForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/IincForm.java new file mode 100644 index 0000000..619bb95 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/IincForm.java @@ -0,0 +1,53 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for the iinc instruction. It has a + * local reference and a byte operand. + */ +public class IincForm extends ByteCodeForm { + + public IincForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_IINC; + } + + public boolean hasIincOperand() { + return true; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandTable, + * org.apache.harmony.unpack200.SegmentConstantPool) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + final int local = operandManager.nextLocal(); + final int constant = operandManager.nextByte(); + byteCode.setOperandBytes(new int[] { local, constant }); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/InitMethodReferenceForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/InitMethodReferenceForm.java new file mode 100644 index 0000000..4d02331 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/InitMethodReferenceForm.java @@ -0,0 +1,60 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * Abstract superclass of those classes which look up init methods (these are + * class specific methods). They use getInitMethodPoolEntry to find the methods. + */ +public abstract class InitMethodReferenceForm extends + ClassSpecificReferenceForm { + + public InitMethodReferenceForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + protected abstract String context(OperandManager operandManager); + + public boolean hasInitMethodRefOperand() { + return true; + } + + protected int getPoolID() { + return SegmentConstantPool.CP_METHOD; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextInitRef(); + } + + protected void setNestedEntries(ByteCode byteCode, + OperandManager operandManager, int offset) throws Pack200Exception { + final SegmentConstantPool globalPool = operandManager + .globalConstantPool(); + ClassFileEntry[] nested = null; + nested = new ClassFileEntry[] { globalPool.getInitMethodPoolEntry( + SegmentConstantPool.CP_METHOD, offset, context(operandManager)) }; + byteCode.setNested(nested); + byteCode.setNestedPositions(new int[][] { { 0, 2 } }); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/IntRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/IntRefForm.java new file mode 100644 index 0000000..7ebca59 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/IntRefForm.java @@ -0,0 +1,52 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for those bytecodes which have int + * references (and only int references). + */ +public class IntRefForm extends SingleByteReferenceForm { + + public IntRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public IntRefForm(int opcode, String name, int[] rewrite, boolean widened) { + this(opcode, name, rewrite); + this.widened = widened; + } + + public int getOperandType() { + return TYPE_INTREF; + } + + public boolean hasIntRefOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextIntRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_INT; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LabelForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LabelForm.java new file mode 100644 index 0000000..66f621e --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LabelForm.java @@ -0,0 +1,88 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.CodeAttribute; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for those bytecodes which have label + * references (and only label references). + */ +public class LabelForm extends ByteCodeForm { + + protected boolean widened; + + public LabelForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public LabelForm(int opcode, String name, int[] rewrite, boolean widened) { + this(opcode, name, rewrite); + this.widened = widened; + } + + public int getOperandType() { + return TYPE_LABEL; + } + + public boolean hasLabelOperand() { + return true; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#fixUpByteCodeTarget(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.CodeAttribute) + */ + public void fixUpByteCodeTargets(ByteCode byteCode, + CodeAttribute codeAttribute) { + // LabelForms need to fix up the target of label operations + final int originalTarget = byteCode.getByteCodeTargets()[0]; + final int sourceIndex = byteCode.getByteCodeIndex(); + final int absoluteInstructionTargetIndex = sourceIndex + originalTarget; + final int targetValue = ((Integer) codeAttribute.byteCodeOffsets + .get(absoluteInstructionTargetIndex)).intValue(); + final int sourceValue = ((Integer) codeAttribute.byteCodeOffsets + .get(sourceIndex)).intValue(); + // The operand is the difference between the source instruction + // and the destination instruction. + byteCode.setOperandSigned2Bytes(targetValue - sourceValue, 0); + if (widened) { + byteCode.setNestedPositions(new int[][] { { 0, 4 } }); + } else { + byteCode.setNestedPositions(new int[][] { { 0, 2 } }); + } + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandTable, + * org.apache.harmony.unpack200.SegmentConstantPool) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + byteCode.setByteCodeTargets(new int[] { operandManager.nextLabel() }); + // The byte code operands actually get set later - + // once we have all the bytecodes - in fixUpByteCodeTarget(). + return; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LocalForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LocalForm.java new file mode 100644 index 0000000..40515ca --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LocalForm.java @@ -0,0 +1,56 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for those bytecodes which have float + * references (and only float references). This excludes iinc (which has its own + * form, IincForm). + */ +public class LocalForm extends ByteCodeForm { + + public LocalForm(int opcode, String name) { + super(opcode, name); + } + + public LocalForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_LOCAL; + } + + public boolean hasLocalOperand() { + return true; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandTable, + * org.apache.harmony.unpack200.SegmentConstantPool) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + byteCode.setOperandBytes(new int[] { operandManager.nextLocal() }); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LongForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LongForm.java new file mode 100644 index 0000000..63c1b17 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LongForm.java @@ -0,0 +1,47 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for those bytecodes which have long + * references (and only long references). + */ +public class LongForm extends ReferenceForm { + + public LongForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_LONG; + } + + public boolean hasLongOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextLongRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_LONG; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LookupSwitchForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LookupSwitchForm.java new file mode 100644 index 0000000..af8757f --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/LookupSwitchForm.java @@ -0,0 +1,114 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +public class LookupSwitchForm extends SwitchForm { + + public LookupSwitchForm(int opcode, String name) { + super(opcode, name); + } + + public LookupSwitchForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.SwitchForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandManager, int) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + final int case_count = operandManager.nextCaseCount(); + final int default_pc = operandManager.nextLabel(); + final int case_values[] = new int[case_count]; + for (int index = 0; index < case_count; index++) { + case_values[index] = operandManager.nextCaseValues(); + } + final int case_pcs[] = new int[case_count]; + for (int index = 0; index < case_count; index++) { + case_pcs[index] = operandManager.nextLabel(); + } + + final int[] labelsArray = new int[case_count + 1]; + labelsArray[0] = default_pc; + for (int index = 1; index < case_count + 1; index++) { + labelsArray[index] = case_pcs[index - 1]; + } + byteCode.setByteCodeTargets(labelsArray); + + // All this gets dumped into the rewrite bytes of the + // poor bytecode. + + // Unlike most byte codes, the LookupSwitch is a + // variable-sized bytecode. Because of this, the + // rewrite array has to be defined here individually + // for each bytecode, rather than in the ByteCodeForm + // class. + + // First, there's the bytecode. Then there are 0-3 + // bytes of padding so that the first (default) + // label is on a 4-byte offset. + final int padLength = 3 - (codeLength % 4); + final int rewriteSize = 1 + padLength + 4 // defaultbytes + + 4 // npairs + + (4 * case_values.length) + (4 * case_pcs.length); + + final int[] newRewrite = new int[rewriteSize]; + int rewriteIndex = 0; + + // Fill in what we can now + // opcode + newRewrite[rewriteIndex++] = byteCode.getOpcode(); + + // padding + for (int index = 0; index < padLength; index++) { + newRewrite[rewriteIndex++] = 0; + } + + // defaultbyte + // This gets overwritten by fixUpByteCodeTargets + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + + // npairs + final int npairsIndex = rewriteIndex; + setRewrite4Bytes(case_values.length, npairsIndex, newRewrite); + rewriteIndex += 4; + + // match-offset pairs + // The case_values aren't overwritten, but the + // case_pcs will get overwritten by fixUpByteCodeTargets + for (int index = 0; index < case_values.length; index++) { + // match + setRewrite4Bytes(case_values[index], rewriteIndex, newRewrite); + rewriteIndex += 4; + // offset + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + } + byteCode.setRewrite(newRewrite); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/MethodRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/MethodRefForm.java new file mode 100644 index 0000000..916141b --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/MethodRefForm.java @@ -0,0 +1,49 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for those bytecodes which have + * regular method references (and only regular method references). These are: + * invokevirtual invokespecial invokestatic Class-specific references to methods + * are subclasses of ClassSpecificReferenceForm instead. + */ +public class MethodRefForm extends ReferenceForm { + + public MethodRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_METHODREF; + } + + public boolean hasMethodRefOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextMethodRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_METHOD; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/MultiANewArrayForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/MultiANewArrayForm.java new file mode 100644 index 0000000..90b0924 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/MultiANewArrayForm.java @@ -0,0 +1,63 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for the multianewarray instruction. + * It has a class reference and a byte operand. + * + * MultiANewArrayForms (like other anewarray forms) do not track the last new(). + */ +public class MultiANewArrayForm extends ClassRefForm { + + public MultiANewArrayForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_MULTIANEWARRAY; + } + + public boolean hasMultianewarrayOperand() { + return true; + } + + public boolean hasClassRefOperand() { + return false; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandTable, + * org.apache.harmony.unpack200.SegmentConstantPool) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + // multianewarray has a class ref and a dimension. + // The superclass handles the class ref. + super.setByteCodeOperands(byteCode, operandManager, codeLength); + + // We have to handle the dimension. + final int dimension = operandManager.nextByte(); + byteCode.setOperandByte(dimension, 2); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NarrowClassRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NarrowClassRefForm.java new file mode 100644 index 0000000..1fafef1 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NarrowClassRefForm.java @@ -0,0 +1,49 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class is used for representations of cldc and cldc_w. In these cases, a + * narrow class ref has one byte and a wide class ref has two bytes. + */ +public class NarrowClassRefForm extends ClassRefForm { + + public NarrowClassRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public NarrowClassRefForm(int opcode, String name, int[] rewrite, + boolean widened) { + super(opcode, name, rewrite, widened); + } + + protected void setNestedEntries(ByteCode byteCode, + OperandManager operandManager, int offset) throws Pack200Exception { + super.setNestedEntries(byteCode, operandManager, offset); + if (!widened) { + byteCode.setNestedPositions(new int[][] { { 0, 1 } }); + } + } + + public boolean nestedMustStartClassPool() { + return !widened; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NewClassRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NewClassRefForm.java new file mode 100644 index 0000000..9c441af --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NewClassRefForm.java @@ -0,0 +1,69 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.CPClass; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class is an extension of the ClassRefForm. It has two purposes: 1. To + * keep track of the last type used in a new() instruction in the current class. + * 2. To allow the sender to create instances of either a specified class (which + * then becomes the new class) or the last used new class. + */ +public class NewClassRefForm extends ClassRefForm { + + public NewClassRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ReferenceForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandManager) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + ClassFileEntry[] nested = null; + final int offset = getOffset(operandManager); + if (offset == 0) { + // Use current class + final SegmentConstantPool globalPool = operandManager + .globalConstantPool(); + nested = new ClassFileEntry[] { globalPool + .getClassPoolEntry(operandManager.getCurrentClass()) }; + byteCode.setNested(nested); + byteCode.setNestedPositions(new int[][] { { 0, 2 } }); + } else { + // Look up the class in the classpool + try { + // Parent takes care of subtracting one from offset + // to adjust for 1-based global pool + setNestedEntries(byteCode, operandManager, offset); + } catch (final Pack200Exception ex) { + throw new Error("Got a pack200 exception. What to do?"); + } + } + operandManager.setNewClass(((CPClass) byteCode + .getNestedClassFileEntries()[0]).getName()); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NewInitMethodRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NewInitMethodRefForm.java new file mode 100644 index 0000000..e0e80fc --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NewInitMethodRefForm.java @@ -0,0 +1,58 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class is used to determine which init method should be called, based on + * the last class which was sent a constructor message. + */ +public class NewInitMethodRefForm extends InitMethodReferenceForm { + + public NewInitMethodRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_NEWINITMETHODREF; + } + + public boolean hasNewInitMethodRefOperand() { + return true; + } + + protected String context(OperandManager operandManager) { + final String result = operandManager.getNewClass(); + return result; + } + + protected void setNestedEntries(ByteCode byteCode, + OperandManager operandManager, int offset) throws Pack200Exception { + final SegmentConstantPool globalPool = operandManager + .globalConstantPool(); + ClassFileEntry[] nested = null; + nested = new ClassFileEntry[] { globalPool.getInitMethodPoolEntry( + SegmentConstantPool.CP_METHOD, offset, context(operandManager)) }; + byteCode.setNested(nested); + byteCode.setNestedPositions(new int[][] { { 0, 2 } }); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NoArgumentForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NoArgumentForm.java new file mode 100644 index 0000000..e3a9a09 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/NoArgumentForm.java @@ -0,0 +1,52 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form of all bytecodes which either have + * no operands (such as nop) or have all their operands passed on the stack (not + * encoded as bytes in the bytecode streams). + */ +public class NoArgumentForm extends ByteCodeForm { + + public NoArgumentForm(int opcode, String name) { + super(opcode, name); + } + + public int getOperandType() { + return TYPE_NONE; + } + + public boolean hasNoOperand() { + return true; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandTable, + * org.apache.harmony.unpack200.SegmentConstantPool) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + // Nothing to do for no-argument form + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ReferenceForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ReferenceForm.java new file mode 100644 index 0000000..d121e16 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ReferenceForm.java @@ -0,0 +1,69 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * Abstract class of all ByteCodeForms which add a nested entry from the + * globalConstantPool. + */ +public abstract class ReferenceForm extends ByteCodeForm { + + public ReferenceForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + protected abstract int getPoolID(); + + protected abstract int getOffset(OperandManager operandManager); + + protected void setNestedEntries(ByteCode byteCode, + OperandManager operandManager, int offset) throws Pack200Exception { + final SegmentConstantPool globalPool = operandManager + .globalConstantPool(); + ClassFileEntry[] nested = null; + nested = new ClassFileEntry[] { globalPool.getConstantPoolEntry( + getPoolID(), offset) }; + if(nested[0] == null) { + throw new NullPointerException("Null nested entries are not allowed"); + } + byteCode.setNested(nested); + byteCode.setNestedPositions(new int[][] { { 0, 2 } }); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandTable, + * org.apache.harmony.unpack200.Segment) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + final int offset = getOffset(operandManager); + try { + setNestedEntries(byteCode, operandManager, offset); + } catch (final Pack200Exception ex) { + throw new Error("Got a pack200 exception. What to do?"); + } + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ShortForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ShortForm.java new file mode 100644 index 0000000..aab12ef --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ShortForm.java @@ -0,0 +1,50 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the form for bytecodes which have short operands only. + */ +public class ShortForm extends ByteCodeForm { + + public ShortForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_SHORT; + } + + public boolean hasShortOperand() { + return true; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandTable, + * org.apache.harmony.unpack200.SegmentConstantPool) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + byteCode.setOperand2Bytes(operandManager.nextShort(), 0); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SingleByteReferenceForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SingleByteReferenceForm.java new file mode 100644 index 0000000..01e88a1 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SingleByteReferenceForm.java @@ -0,0 +1,52 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * Some bytecodes (such as (a)ldc, fldc and ildc) have single- byte references + * to the class pool. This class is the abstract superclass of those classes. + */ +public abstract class SingleByteReferenceForm extends ReferenceForm { + + protected boolean widened; + + public SingleByteReferenceForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + protected abstract int getOffset(OperandManager operandManager); + + protected abstract int getPoolID(); + + protected void setNestedEntries(ByteCode byteCode, + OperandManager operandManager, int offset) throws Pack200Exception { + super.setNestedEntries(byteCode, operandManager, offset); + if (widened) { + byteCode.setNestedPositions(new int[][] { { 0, 2 } }); + } else { + byteCode.setNestedPositions(new int[][] { { 0, 1 } }); + } + } + + public boolean nestedMustStartClassPool() { + return !widened; + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/StringRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/StringRefForm.java new file mode 100644 index 0000000..f696a22 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/StringRefForm.java @@ -0,0 +1,71 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.CPString; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for those bytecodes which have + * string references (and only string references). + */ +public class StringRefForm extends SingleByteReferenceForm { + + public StringRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public StringRefForm(int opcode, String name, int[] rewrite, boolean widened) { + this(opcode, name, rewrite); + this.widened = widened; + } + + public int getOperandType() { + return TYPE_STRINGREF; + } + + public boolean hasStringRefOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextStringRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_STRING; + } + + protected void setNestedEntries(ByteCode byteCode, + OperandManager operandManager, int offset) throws Pack200Exception { + final SegmentConstantPool globalPool = operandManager + .globalConstantPool(); + ClassFileEntry[] nested = null; + nested = new ClassFileEntry[] { ((CPString) globalPool.getValue( + getPoolID(), offset)) }; + byteCode.setNested(nested); + if (widened) { + byteCode.setNestedPositions(new int[][] { { 0, 2 } }); + } else { + byteCode.setNestedPositions(new int[][] { { 0, 1 } }); + } + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SuperFieldRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SuperFieldRefForm.java new file mode 100644 index 0000000..5d2b38c --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SuperFieldRefForm.java @@ -0,0 +1,53 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements references to fields defined in the superclass, which + * is set by this class in the OperandManager. Pack200 allows the superclass to + * be inferred from context; this class tracks previous field reference + * superclasses to allow this. + */ +public class SuperFieldRefForm extends ClassSpecificReferenceForm { + + public SuperFieldRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_SUPERFIELDREF; + } + + public boolean hasSuperFieldRefOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextSuperFieldRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_FIELD; + } + + protected String context(OperandManager operandManager) { + return operandManager.getSuperClass(); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SuperInitMethodRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SuperInitMethodRefForm.java new file mode 100644 index 0000000..e3a2c39 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SuperInitMethodRefForm.java @@ -0,0 +1,41 @@ +/* + * 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.unpack200.bytecode.forms; + +/** + * This class is used to determine which init method should be called, based on + * the last superclass reference. + */ +public class SuperInitMethodRefForm extends InitMethodReferenceForm { + + public SuperInitMethodRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_SUPERINITMETHODREF; + } + + public boolean hasSuperInitMethodRefOperand() { + return true; + } + + protected String context( + org.apache.harmony.unpack200.bytecode.OperandManager operandManager) { + return operandManager.getSuperClass(); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SuperMethodRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SuperMethodRefForm.java new file mode 100644 index 0000000..d6e9227 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SuperMethodRefForm.java @@ -0,0 +1,53 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements references to methods defined in the superclass, which + * is set by this class in the OperandManager. Pack200 allows the superclass to + * be inferred from context; this class tracks previous method reference + * superclasses to allow this. + */ +public class SuperMethodRefForm extends ClassSpecificReferenceForm { + + public SuperMethodRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_SUPERMETHODREF; + } + + public boolean hasSuperMethodRefOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextSuperMethodRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_METHOD; + } + + protected String context(OperandManager operandManager) { + return operandManager.getSuperClass(); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SwitchForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SwitchForm.java new file mode 100644 index 0000000..486eb42 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/SwitchForm.java @@ -0,0 +1,81 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.CodeAttribute; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +public abstract class SwitchForm extends VariableInstructionForm { + + public SwitchForm(int opcode, String name) { + super(opcode, name); + } + + public SwitchForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_SWITCH; + } + + public boolean hasSwitchOperand() { + return true; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandTable, + * org.apache.harmony.unpack200.SegmentConstantPool) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#fixUpByteCodeTargets(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.CodeAttribute) + */ + public void fixUpByteCodeTargets(ByteCode byteCode, + CodeAttribute codeAttribute) { + // SwitchForms need to fix up the target of label operations + final int[] originalTargets = byteCode.getByteCodeTargets(); + final int numberOfLabels = originalTargets.length; + final int[] replacementTargets = new int[numberOfLabels]; + + final int sourceIndex = byteCode.getByteCodeIndex(); + final int sourceValue = ((Integer) codeAttribute.byteCodeOffsets + .get(sourceIndex)).intValue(); + for (int index = 0; index < numberOfLabels; index++) { + final int absoluteInstructionTargetIndex = sourceIndex + + originalTargets[index]; + final int targetValue = ((Integer) codeAttribute.byteCodeOffsets + .get(absoluteInstructionTargetIndex)).intValue(); + replacementTargets[index] = targetValue - sourceValue; + } + final int[] rewriteArray = byteCode.getRewrite(); + for (int index = 0; index < numberOfLabels; index++) { + setRewrite4Bytes(replacementTargets[index], rewriteArray); + } + } + +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/TableSwitchForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/TableSwitchForm.java new file mode 100644 index 0000000..7265bad --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/TableSwitchForm.java @@ -0,0 +1,117 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +public class TableSwitchForm extends SwitchForm { + + public TableSwitchForm(int opcode, String name) { + super(opcode, name); + } + + public TableSwitchForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.SwitchForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandManager, int) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + final int case_count = operandManager.nextCaseCount(); + final int default_pc = operandManager.nextLabel(); + int case_value = -1; + case_value = operandManager.nextCaseValues(); + + final int case_pcs[] = new int[case_count]; + for (int index = 0; index < case_count; index++) { + case_pcs[index] = operandManager.nextLabel(); + } + + final int[] labelsArray = new int[case_count + 1]; + labelsArray[0] = default_pc; + for (int index = 1; index < case_count + 1; index++) { + labelsArray[index] = case_pcs[index - 1]; + } + byteCode.setByteCodeTargets(labelsArray); + + final int lowValue = case_value; + final int highValue = lowValue + case_count - 1; + // All this gets dumped into the rewrite bytes of the + // poor bytecode. + + // Unlike most byte codes, the TableSwitch is a + // variable-sized bytecode. Because of this, the + // rewrite array has to be defined here individually + // for each bytecode, rather than in the ByteCodeForm + // class. + + // First, there's the bytecode. Then there are 0-3 + // bytes of padding so that the first (default) + // label is on a 4-byte offset. + final int padLength = 3 - (codeLength % 4); + final int rewriteSize = 1 + padLength + 4 // defaultbytes + + 4 // lowbyte + + 4 // highbyte + + (4 * case_pcs.length); + + final int[] newRewrite = new int[rewriteSize]; + int rewriteIndex = 0; + + // Fill in what we can now + // opcode + newRewrite[rewriteIndex++] = byteCode.getOpcode(); + + // padding + for (int index = 0; index < padLength; index++) { + newRewrite[rewriteIndex++] = 0; + } + + // defaultbyte + // This gets overwritten by fixUpByteCodeTargets + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + + // lowbyte + final int lowbyteIndex = rewriteIndex; + setRewrite4Bytes(lowValue, lowbyteIndex, newRewrite); + rewriteIndex += 4; + + // highbyte + final int highbyteIndex = rewriteIndex; + setRewrite4Bytes(highValue, highbyteIndex, newRewrite); + rewriteIndex += 4; + + // jump offsets + // The case_pcs will get overwritten by fixUpByteCodeTargets + for (int index = 0; index < case_count; index++) { + // offset + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + newRewrite[rewriteIndex++] = -1; + } + byteCode.setRewrite(newRewrite); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ThisFieldRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ThisFieldRefForm.java new file mode 100644 index 0000000..a297a9f --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ThisFieldRefForm.java @@ -0,0 +1,53 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements references to fields defined in the current class, + * which is set by this class in the OperandManager. Pack200 allows the current + * class to be inferred from context; this class tracks previous field reference + * classes to allow this. + */ +public class ThisFieldRefForm extends ClassSpecificReferenceForm { + + public ThisFieldRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_THISFIELDREF; + } + + public boolean hasThisFieldRefOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextThisFieldRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_FIELD; + } + + protected String context(OperandManager operandManager) { + return operandManager.getCurrentClass(); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ThisInitMethodRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ThisInitMethodRefForm.java new file mode 100644 index 0000000..6796c58 --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ThisInitMethodRefForm.java @@ -0,0 +1,42 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class is used to determine which init method should be called, based on + * the last current class reference. + */ +public class ThisInitMethodRefForm extends InitMethodReferenceForm { + + public ThisInitMethodRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_THISINITMETHODREF; + } + + public boolean hasThisInitMethodRefOperand() { + return true; + } + + protected String context(OperandManager operandManager) { + return operandManager.getCurrentClass(); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ThisMethodRefForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ThisMethodRefForm.java new file mode 100644 index 0000000..fa38efd --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/ThisMethodRefForm.java @@ -0,0 +1,53 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements references to methods defined in the current class, + * which is set by this class in the OperandManager. Pack200 allows the current + * class to be inferred from context; this class tracks previous method + * reference current classes to allow this. + */ +public class ThisMethodRefForm extends ClassSpecificReferenceForm { + + public ThisMethodRefForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_THISMETHODREF; + } + + public boolean hasThisMethodRefOperand() { + return true; + } + + protected int getOffset(OperandManager operandManager) { + return operandManager.nextThisMethodRef(); + } + + protected int getPoolID() { + return SegmentConstantPool.CP_METHOD; + } + + protected String context(OperandManager operandManager) { + return operandManager.getCurrentClass(); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/VariableInstructionForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/VariableInstructionForm.java new file mode 100644 index 0000000..1d1be2f --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/VariableInstructionForm.java @@ -0,0 +1,162 @@ +/* + * 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.unpack200.bytecode.forms; + +/** + * This abstract class implements the common code for instructions which have + * variable lengths. This is currently the *switch instructions and some wide + * (_w) instructions. + */ +public abstract class VariableInstructionForm extends ByteCodeForm { + + public VariableInstructionForm(int opcode, String name) { + super(opcode, name); + } + + public VariableInstructionForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + /** + * Given an int operand, set the rewrite bytes for the next available + * operand position and the three immediately following it to a + * highest-byte, mid-high, mid-low, low-byte encoding of the operand. + * + * Note that unlike the ByteCode setOperand* operations, this starts with an + * actual bytecode rewrite array (rather than a ByteCodeForm prototype + * rewrite array). Also, this method overwrites -1 values in the rewrite + * array - so if you start with an array that looks like: {100, -1, -1, -1, + * -1, 200, -1, -1, -1, -1} then calling setRewrite4Bytes(0, rewrite) the + * first time will convert it to: {100, 0, 0, 0, 0, 200, -1, -1, -1, -1} + * Calling setRewrite4Bytes(0, rewrite) a second time will convert it to: + * {100, 0, 0, 0, 0, 200, 0, 0, 0, 0} + * + * @param operand + * int to set the rewrite bytes to + * @param rewrite + * int[] bytes to rewrite + */ + public void setRewrite4Bytes(int operand, int[] rewrite) { + int firstOperandPosition = -1; + + // Find the first -1 in the rewrite array + for (int index = 0; index < rewrite.length - 3; index++) { + if ((rewrite[index] == -1) && (rewrite[index + 1] == -1) + && (rewrite[index + 2] == -1) && (rewrite[index + 3] == -1)) { + firstOperandPosition = index; + break; + } + } + setRewrite4Bytes(operand, firstOperandPosition, rewrite); + } + + /** + * Given an int operand, set the rewrite bytes for the next available + * operand position and the byte immediately following it to a high-byte, + * low-byte encoding of the operand. + * + * Note that unlike the ByteCode setOperand* operations, this starts with an + * actual bytecode rewrite array (rather than a ByteCodeForm prototype + * rewrite array). Also, this method overwrites -1 values in the rewrite + * array - so if you start with an array that looks like: {100, -1, -1, -1, + * -1, 200, -1, -1, -1, -1} then calling setRewrite2Bytes(0, rewrite) the + * first time will convert it to: {100, 0, 0, -1, -1, 200, -1, -1, -1, -1} + * Calling setRewrite2Bytes(0, rewrite) a second time will convert it to: + * {100, 0, 0, 0, 0, 200, -1, -1, -1, -1} + * + * @param operand + * int to set the rewrite bytes to + * @param rewrite + * int[] bytes to rewrite + */ + public void setRewrite2Bytes(int operand, int[] rewrite) { + int firstOperandPosition = -1; + + // Find the first -1 in the rewrite array + for (int index = 0; index < rewrite.length - 3; index++) { + if ((rewrite[index] == -1) && (rewrite[index + 1] == -1)) { + firstOperandPosition = index; + break; + } + } + setRewrite2Bytes(operand, firstOperandPosition, rewrite); + } + + /** + * This method writes operand directly into the rewrite array at index + * position specified. + * + * @param operand + * value to write + * @param absPosition + * position in array to write. Note that this is absolute + * position in the array, so one can overwrite the bytecode if + * one isn't careful. + * @param rewrite + * array to write into + */ + public void setRewrite4Bytes(int operand, int absPosition, int[] rewrite) { + if (absPosition < 0) { + throw new Error("Trying to rewrite " + this + + " but there is no room for 4 bytes"); + } + + final int byteCodeRewriteLength = rewrite.length; + + if (absPosition + 3 > byteCodeRewriteLength) { + throw new Error("Trying to rewrite " + this + + " with an int at position " + absPosition + + " but this won't fit in the rewrite array"); + } + + rewrite[absPosition] = ((0xFF000000) & operand) >> 24; + rewrite[absPosition + 1] = ((0x00FF0000) & operand) >> 16; + rewrite[absPosition + 2] = ((0x0000FF00) & operand) >> 8; + rewrite[absPosition + 3] = ((0x000000FF) & operand); + } + + /** + * This method writes operand directly into the rewrite array at index + * position specified. + * + * @param operand + * value to write + * @param absPosition + * position in array to write. Note that this is absolute + * position in the array, so one can overwrite the bytecode if + * one isn't careful. + * @param rewrite + * array to write into + */ + public void setRewrite2Bytes(int operand, int absPosition, int[] rewrite) { + if (absPosition < 0) { + throw new Error("Trying to rewrite " + this + + " but there is no room for 4 bytes"); + } + + final int byteCodeRewriteLength = rewrite.length; + + if (absPosition + 1 > byteCodeRewriteLength) { + throw new Error("Trying to rewrite " + this + + " with an int at position " + absPosition + + " but this won't fit in the rewrite array"); + } + + rewrite[absPosition] = ((0xFF00) & operand) >> 8; + rewrite[absPosition + 1] = ((0x00FF) & operand); + } +} diff --git a/src/main/java/org/apache/harmony/unpack200/bytecode/forms/WideForm.java b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/WideForm.java new file mode 100644 index 0000000..eaa1c6f --- /dev/null +++ b/src/main/java/org/apache/harmony/unpack200/bytecode/forms/WideForm.java @@ -0,0 +1,160 @@ +/* + * 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.unpack200.bytecode.forms; + +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * This class implements the byte code form for the wide instruction. Unlike + * other instructions, it can take multiple forms, depending on what is being + * widened. + */ +public class WideForm extends VariableInstructionForm { + + public WideForm(int opcode, String name) { + super(opcode, name); + } + + public WideForm(int opcode, String name, int[] rewrite) { + super(opcode, name, rewrite); + } + + public int getOperandType() { + return TYPE_WIDE; + } + + public boolean hasWideOperand() { + return true; + } + + /* + * (non-Javadoc) + * + * @see org.apache.harmony.unpack200.bytecode.forms.ByteCodeForm#setByteCodeOperands(org.apache.harmony.unpack200.bytecode.ByteCode, + * org.apache.harmony.unpack200.bytecode.OperandTable, + * org.apache.harmony.unpack200.SegmentConstantPool) + */ + public void setByteCodeOperands(ByteCode byteCode, + OperandManager operandManager, int codeLength) { + final int instruction = operandManager.nextWideByteCode(); + if (instruction == 132) { + setByteCodeOperandsFormat2(instruction, byteCode, operandManager, + codeLength); + } else { + setByteCodeOperandsFormat1(instruction, byteCode, operandManager, + codeLength); + } + } + + /** + * This method sets the rewrite array for the bytecode using Format 1 of the + * JVM spec: an opcode and two index bytes. This is used for + * ?load/?store/ret + * + * @param instruction + * should be 132 + * @param byteCode + * the byte code whose rewrite array should be updated + * @param operandManager + * the source of the operands + * @param codeLength + * ignored + */ + protected void setByteCodeOperandsFormat1(int instruction, + ByteCode byteCode, OperandManager operandManager, int codeLength) { + + // Even though this code is really similar to the + // code for setByteCodeOperandsFormat2, I've left it + // distinct here. This is so changing one will + // not change the other - if there is a need to change, + // there's a good chance that the formats will + // differ, so an updater will not have to disentangle + // it. + final int local = operandManager.nextLocal(); + + // Unlike most byte codes, the wide bytecode is a + // variable-sized bytecode. Because of this, the + // rewrite array has to be defined here individually + // for each bytecode, rather than in the ByteCodeForm + // class. + + final int[] newRewrite = new int[4]; + int rewriteIndex = 0; + + // Fill in what we can now + // wide opcode + newRewrite[rewriteIndex++] = byteCode.getOpcode(); + + // "real" instruction that is widened + newRewrite[rewriteIndex++] = instruction; + + // Index bytes + setRewrite2Bytes(local, rewriteIndex, newRewrite); + rewriteIndex += 2; + + byteCode.setRewrite(newRewrite); + } + + /** + * This method sets the rewrite array for the bytecode using Format 2 of the + * JVM spec: an opcode, two index bytes, and two constant bytes. This is + * used for iinc. + * + * @param instruction + * int should be 132 + * @param byteCode + * ByteCode whose rewrite array should be updated + * @param operandManager + * OperandManager source of the operands + * @param codeLength + * ignored + */ + protected void setByteCodeOperandsFormat2(int instruction, + ByteCode byteCode, OperandManager operandManager, int codeLength) { + + final int local = operandManager.nextLocal(); + final int constWord = operandManager.nextShort(); + + // Unlike most byte codes, the wide bytecode is a + // variable-sized bytecode. Because of this, the + // rewrite array has to be defined here individually + // for each bytecode, rather than in the ByteCodeForm + // class. + + final int[] newRewrite = new int[6]; + int rewriteIndex = 0; + + // Fill in what we can now + // wide opcode + newRewrite[rewriteIndex++] = byteCode.getOpcode(); + + // "real" instruction that is widened + newRewrite[rewriteIndex++] = instruction; + + // Index bytes + setRewrite2Bytes(local, rewriteIndex, newRewrite); + rewriteIndex += 2; + + // constant bytes + setRewrite2Bytes(constWord, rewriteIndex, newRewrite); + rewriteIndex += 2; // not strictly necessary, but just in case + // something comes along later + + byteCode.setRewrite(newRewrite); + } +} diff --git a/src/main/java5/org/apache/harmony/pack200/Pack200Adapter.java b/src/main/java5/org/apache/harmony/pack200/Pack200Adapter.java new file mode 100644 index 0000000..e2b284c --- /dev/null +++ b/src/main/java5/org/apache/harmony/pack200/Pack200Adapter.java @@ -0,0 +1,59 @@ +/* + * 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.pack200; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * Provides generic JavaBeans support for the Pack/UnpackAdapters + */ +public abstract class Pack200Adapter { + + protected static final int DEFAULT_BUFFER_SIZE = 8192; + + private final PropertyChangeSupport support = new PropertyChangeSupport(this); + + private final SortedMap properties = new TreeMap(); + + public SortedMap properties() { + return properties; + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + support.addPropertyChangeListener(listener); + } + + protected void firePropertyChange(String propertyName, Object oldValue, + Object newValue) { + support.firePropertyChange(propertyName, oldValue, newValue); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + support.removePropertyChangeListener(listener); + } + + /** + * Completion between 0..1 + * @param value + */ + protected void completed(double value) { + firePropertyChange("pack.progress", null, String.valueOf((int)(100*value))); //$NON-NLS-1$ + } +} diff --git a/src/main/java5/org/apache/harmony/pack200/Pack200PackerAdapter.java b/src/main/java5/org/apache/harmony/pack200/Pack200PackerAdapter.java new file mode 100644 index 0000000..7059a3f --- /dev/null +++ b/src/main/java5/org/apache/harmony/pack200/Pack200PackerAdapter.java @@ -0,0 +1,102 @@ +/* + * 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.pack200; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.Pack200.Packer; + + +/** + * This class provides the binding between the standard Pack200 interface and the + * internal interface for (un)packing. As this uses generics for the SortedMap, + * this class must be compiled and run on a Java 1.5 system. However, Java 1.5 + * is not necessary to use the internal libraries for unpacking. + */ +public class Pack200PackerAdapter extends Pack200Adapter implements Packer { + + private final PackingOptions options = new PackingOptions(); + + public void pack(JarFile file, OutputStream out) throws IOException { + if (file == null || out == null) + throw new IllegalArgumentException( + "Must specify both input and output streams"); + completed(0); + try { + new org.apache.harmony.pack200.Archive(file, out, options).pack(); + } catch (Pack200Exception e) { + throw new IOException("Failed to pack Jar:" + String.valueOf(e)); + } + completed(1); + } + + public void pack(JarInputStream in, OutputStream out) throws IOException { + if (in == null || out == null) + throw new IllegalArgumentException( + "Must specify both input and output streams"); + completed(0); + PackingOptions options = new PackingOptions(); + + try { + new org.apache.harmony.pack200.Archive(in, out, options).pack(); + } catch (Pack200Exception e) { + throw new IOException("Failed to pack Jar:" + String.valueOf(e)); + } + completed(1); + in.close(); + } + + protected void firePropertyChange(String propertyName, Object oldValue, + Object newValue) { + super.firePropertyChange(propertyName, oldValue, newValue); + if(newValue != null && !newValue.equals(oldValue)) { + if (propertyName.startsWith(CLASS_ATTRIBUTE_PFX)) { + String attributeName = propertyName.substring(CLASS_ATTRIBUTE_PFX.length()); + options.addClassAttributeAction(attributeName, (String)newValue); + } else if (propertyName.startsWith(CODE_ATTRIBUTE_PFX)) { + String attributeName = propertyName.substring(CODE_ATTRIBUTE_PFX.length()); + options.addCodeAttributeAction(attributeName, (String)newValue); + } else if (propertyName.equals(DEFLATE_HINT)) { + options.setDeflateHint((String) newValue); + } else if (propertyName.equals(EFFORT)) { + options.setEffort(Integer.parseInt((String)newValue)); + } else if (propertyName.startsWith(FIELD_ATTRIBUTE_PFX)) { + String attributeName = propertyName.substring(FIELD_ATTRIBUTE_PFX.length()); + options.addFieldAttributeAction(attributeName, (String)newValue); + } else if (propertyName.equals(KEEP_FILE_ORDER)) { + options.setKeepFileOrder(Boolean.parseBoolean((String)newValue)); + } else if (propertyName.startsWith(METHOD_ATTRIBUTE_PFX)) { + String attributeName = propertyName.substring(METHOD_ATTRIBUTE_PFX.length()); + options.addMethodAttributeAction(attributeName, (String)newValue); + } else if (propertyName.equals(MODIFICATION_TIME)) { + options.setModificationTime((String)newValue); + } else if (propertyName.startsWith(PASS_FILE_PFX)) { + if(oldValue != null && !oldValue.equals("")) { + options.removePassFile((String)oldValue); + } + options.addPassFile((String) newValue); + } else if (propertyName.equals(SEGMENT_LIMIT)) { + options.setSegmentLimit(Long.parseLong((String)newValue)); + } else if (propertyName.equals(UNKNOWN_ATTRIBUTE)) { + options.setUnknownAttributeAction((String)newValue); + } + } + } + +} diff --git a/src/main/java5/org/apache/harmony/unpack200/Pack200UnpackerAdapter.java b/src/main/java5/org/apache/harmony/unpack200/Pack200UnpackerAdapter.java new file mode 100644 index 0000000..b51bd85 --- /dev/null +++ b/src/main/java5/org/apache/harmony/unpack200/Pack200UnpackerAdapter.java @@ -0,0 +1,74 @@ +/* + * 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.unpack200; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.jar.JarOutputStream; +import java.util.jar.Pack200.Unpacker; + +import org.apache.harmony.pack200.Pack200Adapter; +import org.apache.harmony.pack200.Pack200Exception; + +/** + * This class provides the binding between the standard Pack200 interface and + * the internal interface for (un)packing. As this uses generics for the + * SortedMap, this class must be compiled and run on a Java 1.5 system. However, + * Java 1.5 is not necessary to use the internal libraries for unpacking. + */ +public class Pack200UnpackerAdapter extends Pack200Adapter implements Unpacker { + /* + * (non-Javadoc) + * + * @see java.util.jar.Pack200.Unpacker#unpack(java.io.InputStream, + * java.util.jar.JarOutputStream) + */ + public void unpack(InputStream in, JarOutputStream out) throws IOException { + if (in == null || out == null) + throw new IllegalArgumentException( + "Must specify both input and output streams"); + completed(0); + try { + new Archive(in, out).unpack(); + } catch (Pack200Exception e) { + throw new IOException("Failed to unpack Jar:" + String.valueOf(e)); + } + completed(1); + in.close(); + } + + /* + * (non-Javadoc) + * + * @see java.util.jar.Pack200.Unpacker#unpack(java.io.File, + * java.util.jar.JarOutputStream) + */ + public void unpack(File file, JarOutputStream out) throws IOException { + if (file == null || out == null) + throw new IllegalArgumentException( + "Must specify both input and output streams"); + int size = (int) file.length(); + int bufferSize = (size > 0 && size < DEFAULT_BUFFER_SIZE ? size + : DEFAULT_BUFFER_SIZE); + InputStream in = new BufferedInputStream(new FileInputStream(file), + bufferSize); + unpack(in, out); + } +} diff --git a/src/test/java/org/apache/harmony/pack200/tests/ArchiveTest.java b/src/test/java/org/apache/harmony/pack200/tests/ArchiveTest.java new file mode 100644 index 0000000..02a69fa --- /dev/null +++ b/src/test/java/org/apache/harmony/pack200/tests/ArchiveTest.java @@ -0,0 +1,456 @@ +/* + * 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.pack200.tests; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.URISyntaxException; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +import junit.framework.TestCase; + +import org.apache.harmony.pack200.Archive; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.pack200.PackingOptions; +import org.apache.harmony.unpack200.Segment; + +public class ArchiveTest extends TestCase { + + JarFile in; + OutputStream out; + File file; + + public void testHelloWorld() throws IOException, Pack200Exception, URISyntaxException { + in = new JarFile(new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/hw.jar").toURI())); + file = File.createTempFile("helloworld", ".pack.gz"); + out = new FileOutputStream(file); + new Archive(in, out, null).pack(); + in.close(); + out.close(); + + // now unpack + InputStream in2 = new FileInputStream(file); + File file2 = File.createTempFile("helloworld", ".jar"); + JarOutputStream out2 = new JarOutputStream(new FileOutputStream(file2)); + org.apache.harmony.unpack200.Archive archive = new org.apache.harmony.unpack200.Archive( + in2, out2); + archive.unpack(); + out2.close(); + in2.close(); + + JarFile jarFile = new JarFile(file2); + file2.deleteOnExit(); + JarEntry entry = jarFile + .getJarEntry("org/apache/harmony/archive/tests/internal/pack200/HelloWorld.class"); + assertNotNull(entry); + InputStream ours = jarFile.getInputStream(entry); + + JarFile jarFile2 = new JarFile(new File(Segment.class.getResource( + "/org/apache/harmony/pack200/tests/hw.jar").toURI())); + JarEntry entry2 = jarFile2 + .getJarEntry("org/apache/harmony/archive/tests/internal/pack200/HelloWorld.class"); + assertNotNull(entry2); + + InputStream expected = jarFile2.getInputStream(entry2); + + BufferedReader reader1 = new BufferedReader(new InputStreamReader(ours)); + BufferedReader reader2 = new BufferedReader(new InputStreamReader( + expected)); + String line1 = reader1.readLine(); + String line2 = reader2.readLine(); + int i = 1; + while (line1 != null || line2 != null) { + assertEquals("Unpacked class files differ", line2, line1); + line1 = reader1.readLine(); + line2 = reader2.readLine(); + i++; + } + reader1.close(); + reader2.close(); + } + + public void testSQL() throws IOException, Pack200Exception, URISyntaxException { + in = new JarFile(new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/sqlUnpacked.jar").toURI())); + file = File.createTempFile("sql", ".pack"); + out = new FileOutputStream(file); + PackingOptions options = new PackingOptions(); + options.setGzip(false); + Archive ar = new Archive(in, out, options); + ar.pack(); + in.close(); + out.close(); + + // now unpack + InputStream in2 = new FileInputStream(file); + File file2 = File.createTempFile("sqlout", ".jar"); + JarOutputStream out2 = new JarOutputStream(new FileOutputStream(file2)); + org.apache.harmony.unpack200.Archive archive = new org.apache.harmony.unpack200.Archive(in2, out2); + archive.unpack(); + JarFile jarFile = new JarFile(file2); + file2.deleteOnExit(); + + File compareFile = new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/sqlUnpacked.jar").toURI()); + JarFile jarFile2 = new JarFile(compareFile); + + assertEquals(jarFile2.size(), jarFile.size()); + + compareFiles(jarFile, jarFile2); + } + + public void testLargeClass() throws IOException, Pack200Exception, URISyntaxException { + in = new JarFile(new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/largeClassUnpacked.jar") + .toURI())); + file = File.createTempFile("largeClass", ".pack"); + out = new FileOutputStream(file); + PackingOptions options = new PackingOptions(); + options.setGzip(false); + new Archive(in, out, options).pack(); + in.close(); + out.close(); + + // now unpack + InputStream in2 = new FileInputStream(file); + File file2 = File.createTempFile("largeClassOut", ".jar"); + JarOutputStream out2 = new JarOutputStream(new FileOutputStream(file2)); + org.apache.harmony.unpack200.Archive archive = new org.apache.harmony.unpack200.Archive(in2, out2); + archive.unpack(); + JarFile jarFile = new JarFile(file2); + file2.deleteOnExit(); + + File compareFile = new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/largeClassUnpacked.jar").toURI()); + JarFile jarFile2 = new JarFile(compareFile); + + assertEquals(jarFile2.size(), jarFile.size()); + + compareFiles(jarFile, jarFile2); + } + + public void testJNDI() throws IOException, Pack200Exception, URISyntaxException { + in = new JarFile(new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/jndi.jar").toURI())); + file = File.createTempFile("jndi", ".pack"); + out = new FileOutputStream(file); + PackingOptions options = new PackingOptions(); + options.setGzip(false); + new Archive(in, out, options).pack(); + in.close(); + out.close(); + + // now unpack + InputStream in2 = new FileInputStream(file); + File file2 = File.createTempFile("jndiout", ".jar"); + JarOutputStream out2 = new JarOutputStream(new FileOutputStream(file2)); + org.apache.harmony.unpack200.Archive archive = new org.apache.harmony.unpack200.Archive(in2, out2); + archive.unpack(); + JarFile jarFile = new JarFile(file2); + file2.deleteOnExit(); + JarFile jarFile2 = new JarFile(new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/jndiUnpacked.jar").toURI())); + + compareFiles(jarFile, jarFile2); + } + + public void testSegmentLimits() throws IOException, Pack200Exception, + URISyntaxException { + in = new JarFile(new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/hw.jar").toURI())); + file = File.createTempFile("helloworld", ".pack.gz"); + out = new FileOutputStream(file); + PackingOptions options = new PackingOptions(); + options.setSegmentLimit(0); + Archive archive = new Archive(in, out, options); + archive.pack(); + in.close(); + out.close(); + + in = new JarFile(new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/hw.jar").toURI())); + file = File.createTempFile("helloworld", ".pack.gz"); + out = new FileOutputStream(file); + options = new PackingOptions(); + options.setSegmentLimit(-1); + archive = new Archive(in, out, options); + archive.pack(); + in.close(); + out.close(); + + in = new JarFile(new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/hw.jar").toURI())); + file = File.createTempFile("helloworld", ".pack.gz"); + out = new FileOutputStream(file); + options = new PackingOptions(); + options.setSegmentLimit(5000); + archive = new Archive(in, out, options); + archive.pack(); + in.close(); + out.close(); + } + + public void testStripDebug() throws IOException, Pack200Exception, URISyntaxException { + in = new JarFile(new File(Archive.class + .getResource("/org/apache/harmony/pack200/tests/sqlUnpacked.jar").toURI())); + file = File.createTempFile("sql", ".pack"); + out = new FileOutputStream(file); + PackingOptions options = new PackingOptions(); + options.setGzip(false); + options.setStripDebug(true); + Archive archive = new Archive(in, out, options); + archive.pack(); + in.close(); + out.close(); + + // now unpack + InputStream in2 = new FileInputStream(file); + File file2 = File.createTempFile("sqloutNoDebug", ".jar"); + JarOutputStream out2 = new JarOutputStream(new FileOutputStream(file2)); + org.apache.harmony.unpack200.Archive u2archive = new org.apache.harmony.unpack200.Archive(in2, out2); + u2archive.unpack(); + + File compareFile = new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/sqlUnpackedNoDebug.jar").toURI()); + JarFile jarFile = new JarFile(file2); + assertTrue(file2.length() < 250000); + file2.deleteOnExit(); + + JarFile jarFile2 = new JarFile(compareFile); + + compareFiles(jarFile, jarFile2); + } + + public void testPassFiles() throws IOException, URISyntaxException, Pack200Exception { + // Don't pass any + in = new JarFile(new File(Archive.class + .getResource("/org/apache/harmony/pack200/tests/sqlUnpacked.jar").toURI())); + File file0 = File.createTempFile("sql", ".pack"); + out = new FileOutputStream(file0); + PackingOptions options = new PackingOptions(); + options.setGzip(false); + Archive archive = new Archive(in, out, options); + archive.pack(); + in.close(); + out.close(); + + // Pass one file + in = new JarFile(new File(Archive.class + .getResource("/org/apache/harmony/pack200/tests/sqlUnpacked.jar").toURI())); + file = File.createTempFile("sql", ".pack"); + out = new FileOutputStream(file); + options = new PackingOptions(); + options.setGzip(false); + options.addPassFile("bin/test/org/apache/harmony/sql/tests/java/sql/DatabaseMetaDataTest.class"); + assertTrue(options.isPassFile("bin/test/org/apache/harmony/sql/tests/java/sql/DatabaseMetaDataTest.class")); + archive = new Archive(in, out, options); + archive.pack(); + in.close(); + out.close(); + + // Pass a whole directory + in = new JarFile(new File(Archive.class + .getResource("/org/apache/harmony/pack200/tests/sqlUnpacked.jar").toURI())); + File file2 = File.createTempFile("sql", ".pack"); + out = new FileOutputStream(file2); + options = new PackingOptions(); + options.setGzip(false); + options.addPassFile("bin/test/org/apache/harmony/sql/tests/java/sql"); + assertTrue(options.isPassFile("bin/test/org/apache/harmony/sql/tests/java/sql/DatabaseMetaDataTest.class")); + assertFalse(options.isPassFile("bin/test/org/apache/harmony/sql/tests/java/sqldata/SqlData.class")); + archive = new Archive(in, out, options); + archive.pack(); + in.close(); + out.close(); + + assertTrue("If files are passed then the pack file should be larger", file.length() > file0.length()); + assertTrue("If more files are passed then the pack file should be larger", file2.length() > file.length()); + + // now unpack + InputStream in2 = new FileInputStream(file); + File file3 = File.createTempFile("sql", ".jar"); + JarOutputStream out2 = new JarOutputStream(new FileOutputStream(file3)); + org.apache.harmony.unpack200.Archive u2archive = new org.apache.harmony.unpack200.Archive(in2, out2); + u2archive.unpack(); + + File compareFile = new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/sqlUnpacked.jar").toURI()); + JarFile jarFile = new JarFile(file3); + file2.deleteOnExit(); + + JarFile jarFile2 = new JarFile(compareFile); + // Check that both jars have the same entries + compareJarEntries(jarFile, jarFile2); + + // now unpack the file with lots of passed files + InputStream in3 = new FileInputStream(file2); + File file4 = File.createTempFile("sql", ".jar"); + JarOutputStream out3 = new JarOutputStream(new FileOutputStream(file4)); + u2archive = new org.apache.harmony.unpack200.Archive(in3, out3); + u2archive.unpack(); + jarFile = new JarFile(file4); + jarFile2 = new JarFile(compareFile); + compareJarEntries(jarFile, jarFile2); + } + + public void testAnnotations() throws IOException, Pack200Exception, + URISyntaxException { + in = new JarFile(new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/annotationsUnpacked.jar") + .toURI())); + file = File.createTempFile("annotations", ".pack"); + out = new FileOutputStream(file); + PackingOptions options = new PackingOptions(); + options.setGzip(false); + new Archive(in, out, options).pack(); + in.close(); + out.close(); + + // now unpack + InputStream in2 = new FileInputStream(file); + File file2 = File.createTempFile("annotationsout", ".jar"); + JarOutputStream out2 = new JarOutputStream(new FileOutputStream(file2)); + org.apache.harmony.unpack200.Archive archive = new org.apache.harmony.unpack200.Archive( + in2, out2); + archive.unpack(); + JarFile jarFile = new JarFile(file2); + file2.deleteOnExit(); + JarFile jarFile2 = new JarFile(new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/annotationsUnpacked.jar").toURI())); + + compareFiles(jarFile, jarFile2); + } + + public void testE0() throws Pack200Exception, IOException, URISyntaxException { + File f1 = new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/jndi.jar").toURI()); + in = new JarFile(f1); + file = File.createTempFile("jndiE0", ".pack"); + out = new FileOutputStream(file); + PackingOptions options = new PackingOptions(); + options.setGzip(false); + options.setEffort(0); + Archive archive = new Archive(in, out, options); + archive.pack(); + in.close(); + out.close(); + compareFiles(new JarFile(f1), new JarFile(file)); + + } + +// public void testE0again() throws IOException, Pack200Exception, URISyntaxException { +// JarInputStream inputStream = new JarInputStream(Archive.class.getResourceAsStream("/org/apache/harmony/pack200/tests/jndi.jar")); +// file = File.createTempFile("jndiE0", ".pack"); +// out = new FileOutputStream(file); +// Archive archive = new Archive(inputStream, out, false); +// archive.setEffort(0); +// archive.pack(); +// inputStream.close(); +// out.close(); +// in = new JarFile(new File(Archive.class.getResource( +// "/org/apache/harmony/pack200/tests/jndi.jar").toURI())); +// compareFiles(in, new JarFile(file)); +// } + + public void testMultipleJars() throws URISyntaxException, IOException, Pack200Exception { + File folder = new File(Archive.class + .getResource("/org/apache/harmony/pack200/tests/jars").toURI()); + String[] children = folder.list(); + for (int i = 0; i < children.length; i++) { + if(children[i].endsWith(".jar") && !children[i].endsWith("Unpacked.jar")) { + File inputFile = new File(folder, children[i]); + in = new JarFile(inputFile); + file = File.createTempFile("temp", ".pack.gz"); + out = new FileOutputStream(file); +// System.out.println("packing " + children[i]); + new Archive(in, out, null).pack(); + in.close(); + out.close(); + + // unpack and compare + + } + } + } + + private void compareJarEntries(JarFile jarFile, JarFile jarFile2) + throws IOException { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + + JarEntry entry = (JarEntry) entries.nextElement(); + assertNotNull(entry); + + String name = entry.getName(); + JarEntry entry2 = jarFile2.getJarEntry(name); + assertNotNull("Missing Entry: " + name, entry2); + } + } + + private void compareFiles(JarFile jarFile, JarFile jarFile2) + throws IOException { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + + JarEntry entry = (JarEntry) entries.nextElement(); + assertNotNull(entry); + + String name = entry.getName(); + JarEntry entry2 = jarFile2.getJarEntry(name); + assertNotNull("Missing Entry: " + name, entry2); +// assertEquals(entry.getTime(), entry2.getTime()); + if (!name.equals("META-INF/MANIFEST.MF")) { // Manifests aren't + // necessarily + // byte-for-byte + // identical + + InputStream ours = jarFile.getInputStream(entry); + InputStream expected = jarFile2.getInputStream(entry2); + + BufferedReader reader1 = new BufferedReader( + new InputStreamReader(ours)); + BufferedReader reader2 = new BufferedReader( + new InputStreamReader(expected)); + String line1 = reader1.readLine(); + String line2 = reader2.readLine(); + int i = 1; + while (line1 != null || line2 != null) { + assertEquals("Unpacked files differ for " + name, line2, + line1); + line1 = reader1.readLine(); + line2 = reader2.readLine(); + i++; + } + reader1.close(); + reader2.close(); + } + } + jarFile.close(); + jarFile2.close(); + } + +} diff --git a/src/test/java/org/apache/harmony/pack200/tests/BHSDCodecTest.java b/src/test/java/org/apache/harmony/pack200/tests/BHSDCodecTest.java new file mode 100644 index 0000000..90afcf2 --- /dev/null +++ b/src/test/java/org/apache/harmony/pack200/tests/BHSDCodecTest.java @@ -0,0 +1,82 @@ +/* + * 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.pack200.tests; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.apache.harmony.pack200.BHSDCodec; +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.CodecEncoding; +import org.apache.harmony.pack200.Pack200Exception; + +/** + * Tests for BHSDCodec + */ +public class BHSDCodecTest extends TestCase { + + public void testEncodeDecode() throws IOException, Pack200Exception { + for (int i = 1; i < 116; i++) { + + BHSDCodec codec = (BHSDCodec) CodecEncoding.getCodec(i, null, null); + + if (!codec.isDelta()) { + // Test encode-decode with a selection of numbers within the + // range of the codec + long largest = codec.largest(); + long smallest = codec.isSigned() ? codec.smallest() : 0; + if(smallest < Integer.MIN_VALUE) smallest = Integer.MIN_VALUE; + long difference = (largest - smallest) / 4; + for (long j = smallest; j <= largest; j += difference) { + if(j > Integer.MAX_VALUE) { + break; + } + byte[] encoded = codec.encode((int)j, 0); + long decoded = 0; + try { + decoded = codec.decode( + new ByteArrayInputStream(encoded), 0); + } catch (EOFException e) { + System.out.println(e); + } + if (j != decoded) { + fail("Failed with codec: " + i + ", " + codec + + " expected: " + j + ", got: " + decoded); + } + } + } + + // Test encode-decode with 0 + assertEquals(0, codec.decode(new ByteArrayInputStream(codec.encode( + 0, 0)), 0)); + } + } + + public void testDeltaEncodings() throws IOException, Pack200Exception { + Codec c = Codec.UDELTA5; + int[] sequence = new int[] {0, 2, 4, 2, 2, 4}; + byte[] encoded = c.encode(sequence); + int[] decoded = c.decodeInts(6, new ByteArrayInputStream(encoded)); + for (int i = 0; i < decoded.length; i++) { + assertEquals(sequence[i], decoded[i]); + } + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/harmony/pack200/tests/CodecEncodingTest.java b/src/test/java/org/apache/harmony/pack200/tests/CodecEncodingTest.java new file mode 100644 index 0000000..94f61e1 --- /dev/null +++ b/src/test/java/org/apache/harmony/pack200/tests/CodecEncodingTest.java @@ -0,0 +1,311 @@ +/* + * 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.pack200.tests; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.harmony.pack200.BHSDCodec; +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.CodecEncoding; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.pack200.PopulationCodec; +import org.apache.harmony.pack200.RunCodec; + +/** + * + */ +public class CodecEncodingTest extends TestCase { + + public void testCanonicalEncodings() throws IOException, Pack200Exception { + Codec defaultCodec = new BHSDCodec(2, 16, 0, 0); + assertEquals(defaultCodec, CodecEncoding + .getCodec(0, null, defaultCodec)); + Map map = new HashMap(); + // These are the canonical encodings specified by the Pack200 spec + map.put(new Integer(1), "(1,256)"); + map.put(new Integer(2), "(1,256,1)"); + map.put(new Integer(3), "(1,256,0,1)"); + map.put(new Integer(4), "(1,256,1,1)"); + map.put(new Integer(5), "(2,256)"); + map.put(new Integer(6), "(2,256,1)"); + map.put(new Integer(7), "(2,256,0,1)"); + map.put(new Integer(8), "(2,256,1,1)"); + map.put(new Integer(9), "(3,256)"); + map.put(new Integer(10), "(3,256,1)"); + map.put(new Integer(11), "(3,256,0,1)"); + map.put(new Integer(12), "(3,256,1,1)"); + map.put(new Integer(13), "(4,256)"); + map.put(new Integer(14), "(4,256,1)"); + map.put(new Integer(15), "(4,256,0,1)"); + map.put(new Integer(16), "(4,256,1,1)"); + map.put(new Integer(17), "(5,4)"); + map.put(new Integer(18), "(5,4,1)"); + map.put(new Integer(19), "(5,4,2)"); + map.put(new Integer(20), "(5,16)"); + map.put(new Integer(21), "(5,16,1)"); + map.put(new Integer(22), "(5,16,2)"); + map.put(new Integer(23), "(5,32)"); + map.put(new Integer(24), "(5,32,1)"); + map.put(new Integer(25), "(5,32,2)"); + map.put(new Integer(26), "(5,64)"); + map.put(new Integer(27), "(5,64,1)"); + map.put(new Integer(28), "(5,64,2)"); + map.put(new Integer(29), "(5,128)"); + map.put(new Integer(30), "(5,128,1)"); + map.put(new Integer(31), "(5,128,2)"); + map.put(new Integer(32), "(5,4,0,1)"); + map.put(new Integer(33), "(5,4,1,1)"); + map.put(new Integer(34), "(5,4,2,1)"); + map.put(new Integer(35), "(5,16,0,1)"); + map.put(new Integer(36), "(5,16,1,1)"); + map.put(new Integer(37), "(5,16,2,1)"); + map.put(new Integer(38), "(5,32,0,1)"); + map.put(new Integer(39), "(5,32,1,1)"); + map.put(new Integer(40), "(5,32,2,1)"); + map.put(new Integer(41), "(5,64,0,1)"); + map.put(new Integer(42), "(5,64,1,1)"); + map.put(new Integer(43), "(5,64,2,1)"); + map.put(new Integer(44), "(5,128,0,1)"); + map.put(new Integer(45), "(5,128,1,1)"); + map.put(new Integer(46), "(5,128,2,1)"); + map.put(new Integer(47), "(2,192)"); + map.put(new Integer(48), "(2,224)"); + map.put(new Integer(49), "(2,240)"); + map.put(new Integer(50), "(2,248)"); + map.put(new Integer(51), "(2,252)"); + map.put(new Integer(52), "(2,8,0,1)"); + map.put(new Integer(53), "(2,8,1,1)"); + map.put(new Integer(54), "(2,16,0,1)"); + map.put(new Integer(55), "(2,16,1,1)"); + map.put(new Integer(56), "(2,32,0,1)"); + map.put(new Integer(57), "(2,32,1,1)"); + map.put(new Integer(58), "(2,64,0,1)"); + map.put(new Integer(59), "(2,64,1,1)"); + map.put(new Integer(60), "(2,128,0,1)"); + map.put(new Integer(61), "(2,128,1,1)"); + map.put(new Integer(62), "(2,192,0,1)"); + map.put(new Integer(63), "(2,192,1,1)"); + map.put(new Integer(64), "(2,224,0,1)"); + map.put(new Integer(65), "(2,224,1,1)"); + map.put(new Integer(66), "(2,240,0,1)"); + map.put(new Integer(67), "(2,240,1,1)"); + map.put(new Integer(68), "(2,248,0,1)"); + map.put(new Integer(69), "(2,248,1,1)"); + map.put(new Integer(70), "(3,192)"); + map.put(new Integer(71), "(3,224)"); + map.put(new Integer(72), "(3,240)"); + map.put(new Integer(73), "(3,248)"); + map.put(new Integer(74), "(3,252)"); + map.put(new Integer(75), "(3,8,0,1)"); + map.put(new Integer(76), "(3,8,1,1)"); + map.put(new Integer(77), "(3,16,0,1)"); + map.put(new Integer(78), "(3,16,1,1)"); + map.put(new Integer(79), "(3,32,0,1)"); + map.put(new Integer(80), "(3,32,1,1)"); + map.put(new Integer(81), "(3,64,0,1)"); + map.put(new Integer(82), "(3,64,1,1)"); + map.put(new Integer(83), "(3,128,0,1)"); + map.put(new Integer(84), "(3,128,1,1)"); + map.put(new Integer(85), "(3,192,0,1)"); + map.put(new Integer(86), "(3,192,1,1)"); + map.put(new Integer(87), "(3,224,0,1)"); + map.put(new Integer(88), "(3,224,1,1)"); + map.put(new Integer(89), "(3,240,0,1)"); + map.put(new Integer(90), "(3,240,1,1)"); + map.put(new Integer(91), "(3,248,0,1)"); + map.put(new Integer(92), "(3,248,1,1)"); + map.put(new Integer(93), "(4,192)"); + map.put(new Integer(94), "(4,224)"); + map.put(new Integer(95), "(4,240)"); + map.put(new Integer(96), "(4,248)"); + map.put(new Integer(97), "(4,252)"); + map.put(new Integer(98), "(4,8,0,1)"); + map.put(new Integer(99), "(4,8,1,1)"); + map.put(new Integer(100), "(4,16,0,1)"); + map.put(new Integer(101), "(4,16,1,1)"); + map.put(new Integer(102), "(4,32,0,1)"); + map.put(new Integer(103), "(4,32,1,1)"); + map.put(new Integer(104), "(4,64,0,1)"); + map.put(new Integer(105), "(4,64,1,1)"); + map.put(new Integer(106), "(4,128,0,1)"); + map.put(new Integer(107), "(4,128,1,1)"); + map.put(new Integer(108), "(4,192,0,1)"); + map.put(new Integer(109), "(4,192,1,1)"); + map.put(new Integer(110), "(4,224,0,1)"); + map.put(new Integer(111), "(4,224,1,1)"); + map.put(new Integer(112), "(4,240,0,1)"); + map.put(new Integer(113), "(4,240,1,1)"); + map.put(new Integer(114), "(4,248,0,1)"); + map.put(new Integer(115), "(4,248,1,1)"); + for (int i = 1; i <= 115; i++) { + assertEquals(map.get(new Integer(i)), CodecEncoding.getCodec(i, + null, null).toString()); + } + } + + public void testArbitraryCodec() throws IOException, Pack200Exception { + assertEquals("(1,256)", CodecEncoding.getCodec(116, + new ByteArrayInputStream(new byte[] { 0x00, (byte) 0xFF }), + null).toString()); + assertEquals("(5,128,2,1)", CodecEncoding.getCodec(116, + new ByteArrayInputStream(new byte[] { 0x25, (byte) 0x7F }), + null).toString()); + assertEquals("(2,128,1,1)", CodecEncoding.getCodec(116, + new ByteArrayInputStream(new byte[] { 0x0B, (byte) 0x7F }), + null).toString()); + } + + public void testGetSpecifier() throws IOException, Pack200Exception { + // Test canonical codecs + for (int i = 1; i <= 115; i++) { + assertEquals(i, CodecEncoding.getSpecifier(CodecEncoding.getCodec(i, null, null), null)[0]); + } + + // Test a range of non-canonical codecs + Codec c1 = new BHSDCodec(2, 125, 0, 1); + int[] specifiers = CodecEncoding.getSpecifier(c1, null); + assertEquals(3, specifiers.length); + assertEquals(116, specifiers[0]); + byte[] bytes = new byte[] {(byte) specifiers[1], (byte) specifiers[2]}; + InputStream in = new ByteArrayInputStream(bytes); + assertEquals(c1, CodecEncoding.getCodec(116, in, null)); + + c1 = new BHSDCodec(3, 125, 2, 1); + specifiers = CodecEncoding.getSpecifier(c1, null); + assertEquals(3, specifiers.length); + assertEquals(116, specifiers[0]); + bytes = new byte[] {(byte) specifiers[1], (byte) specifiers[2]}; + in = new ByteArrayInputStream(bytes); + assertEquals(c1, CodecEncoding.getCodec(116, in, null)); + + c1 = new BHSDCodec(4, 125); + specifiers = CodecEncoding.getSpecifier(c1, null); + assertEquals(3, specifiers.length); + assertEquals(116, specifiers[0]); + bytes = new byte[] {(byte) specifiers[1], (byte) specifiers[2]}; + in = new ByteArrayInputStream(bytes); + assertEquals(c1, CodecEncoding.getCodec(116, in, null)); + + c1 = new BHSDCodec(5, 125, 2, 0); + specifiers = CodecEncoding.getSpecifier(c1, null); + assertEquals(3, specifiers.length); + assertEquals(116, specifiers[0]); + bytes = new byte[] {(byte) specifiers[1], (byte) specifiers[2]}; + in = new ByteArrayInputStream(bytes); + assertEquals(c1, CodecEncoding.getCodec(116, in, null)); + + c1 = new BHSDCodec(3, 5, 2, 1); + specifiers = CodecEncoding.getSpecifier(c1, null); + assertEquals(3, specifiers.length); + assertEquals(116, specifiers[0]); + bytes = new byte[] {(byte) specifiers[1], (byte) specifiers[2]}; + in = new ByteArrayInputStream(bytes); + assertEquals(c1, CodecEncoding.getCodec(116, in, null)); + } + + public void testGetSpeciferForRunCodec() throws Pack200Exception, IOException { + RunCodec runCodec = new RunCodec(25, Codec.DELTA5, Codec.BYTE1); + int[] specifiers = CodecEncoding.getSpecifier(runCodec, null); + assertTrue(specifiers[0] > 116); + assertTrue(specifiers[0] < 141); + byte[] bytes = new byte[specifiers.length - 1]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) specifiers[i+1]; + } + InputStream in = new ByteArrayInputStream(bytes); + RunCodec runCodec2 = (RunCodec) CodecEncoding.getCodec(specifiers[0], in, null); + assertEquals(runCodec.getK(), runCodec2.getK()); + assertEquals(runCodec.getACodec(), runCodec2.getACodec()); + assertEquals(runCodec.getBCodec(), runCodec2.getBCodec()); + + // One codec is the same as the default + runCodec = new RunCodec(4096, Codec.DELTA5, Codec.BYTE1); + specifiers = CodecEncoding.getSpecifier(runCodec, Codec.DELTA5); + assertTrue(specifiers[0] > 116); + assertTrue(specifiers[0] < 141); + bytes = new byte[specifiers.length - 1]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) specifiers[i+1]; + } + in = new ByteArrayInputStream(bytes); + runCodec2 = (RunCodec) CodecEncoding.getCodec(specifiers[0], in, Codec.DELTA5); + assertEquals(runCodec.getK(), runCodec2.getK()); + assertEquals(runCodec.getACodec(), runCodec2.getACodec()); + assertEquals(runCodec.getBCodec(), runCodec2.getBCodec()); + + // Nested run codecs + runCodec = new RunCodec(64, Codec.SIGNED5, new RunCodec(25, Codec.UDELTA5, Codec.DELTA5)); + specifiers = CodecEncoding.getSpecifier(runCodec, null); + assertTrue(specifiers[0] > 116); + assertTrue(specifiers[0] < 141); + bytes = new byte[specifiers.length - 1]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) specifiers[i+1]; + } + in = new ByteArrayInputStream(bytes); + runCodec2 = (RunCodec) CodecEncoding.getCodec(specifiers[0], in, null); + assertEquals(runCodec.getK(), runCodec2.getK()); + assertEquals(runCodec.getACodec(), runCodec2.getACodec()); + RunCodec bCodec = (RunCodec) runCodec.getBCodec(); + RunCodec bCodec2 = (RunCodec) runCodec2.getBCodec(); + assertEquals(bCodec.getK(), bCodec2.getK()); + assertEquals(bCodec.getACodec(), bCodec2.getACodec()); + assertEquals(bCodec.getBCodec(), bCodec2.getBCodec()); + + // Nested with one the same as the default + runCodec = new RunCodec(64, Codec.SIGNED5, new RunCodec(25, Codec.UDELTA5, Codec.DELTA5)); + specifiers = CodecEncoding.getSpecifier(runCodec, Codec.UDELTA5); + assertTrue(specifiers[0] > 116); + assertTrue(specifiers[0] < 141); + bytes = new byte[specifiers.length - 1]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) specifiers[i+1]; + } + in = new ByteArrayInputStream(bytes); + runCodec2 = (RunCodec) CodecEncoding.getCodec(specifiers[0], in, Codec.UDELTA5); + assertEquals(runCodec.getK(), runCodec2.getK()); + assertEquals(runCodec.getACodec(), runCodec2.getACodec()); + bCodec = (RunCodec) runCodec.getBCodec(); + bCodec2 = (RunCodec) runCodec2.getBCodec(); + assertEquals(bCodec.getK(), bCodec2.getK()); + assertEquals(bCodec.getACodec(), bCodec2.getACodec()); + assertEquals(bCodec.getBCodec(), bCodec2.getBCodec()); + } + + public void testGetSpeciferForPopulationCodec() throws IOException, Pack200Exception { + PopulationCodec pCodec = new PopulationCodec(Codec.BYTE1, Codec.CHAR3, Codec.UNSIGNED5); + int[] specifiers = CodecEncoding.getSpecifier(pCodec, null); + assertTrue(specifiers[0] > 140); + assertTrue(specifiers[0] < 189); + byte[] bytes = new byte[specifiers.length - 1]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) specifiers[i+1]; + } + InputStream in = new ByteArrayInputStream(bytes); + PopulationCodec pCodec2 = (PopulationCodec) CodecEncoding.getCodec(specifiers[0], in, null); + assertEquals(pCodec.getFavouredCodec(), pCodec2.getFavouredCodec()); + assertEquals(pCodec.getTokenCodec(), pCodec2.getTokenCodec()); + assertEquals(pCodec.getUnfavouredCodec(), pCodec2.getUnfavouredCodec()); + } + +} diff --git a/src/test/java/org/apache/harmony/pack200/tests/CodecTest.java b/src/test/java/org/apache/harmony/pack200/tests/CodecTest.java new file mode 100644 index 0000000..25818bd --- /dev/null +++ b/src/test/java/org/apache/harmony/pack200/tests/CodecTest.java @@ -0,0 +1,253 @@ +/* + * 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.pack200.tests; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.apache.harmony.pack200.BHSDCodec; +import org.apache.harmony.pack200.CanonicalCodecFamilies; +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.pack200.RunCodec; + +/** + * + */ +public class CodecTest extends TestCase { + + public void testInvalidCodings() { + for (int i = 0; i < 256; i++) { + try { + new BHSDCodec(1, i); + fail("b=1 -> h=256"); + } catch (IllegalArgumentException e) { + assertTrue(true); + } + } + for (int i = 1; i <= 5; i++) { + try { + new BHSDCodec(i, 256); + if (i == 5) + fail("h=256 -> b!=5"); + } catch (IllegalArgumentException e) { + assertTrue(true); + } + } + + } + + public void testCodecToString() { + assertEquals("(1,256)", Codec.BYTE1.toString()); + assertEquals("(3,128)", Codec.CHAR3.toString()); + assertEquals("(5,4)", Codec.BCI5.toString()); + assertEquals("(5,4,2)", Codec.BRANCH5.toString()); + assertEquals("(5,64)", Codec.UNSIGNED5.toString()); + assertEquals("(5,64,1)", Codec.SIGNED5.toString()); + assertEquals("(5,64,0,1)", Codec.UDELTA5.toString()); + assertEquals("(5,64,1,1)", Codec.DELTA5.toString()); + assertEquals("(5,64,2,1)", Codec.MDELTA5.toString()); + assertEquals("(5,64)", Codec.UNSIGNED5.toString()); + assertEquals("(5,64,1)", Codec.SIGNED5.toString()); + assertEquals("(5,64,1,1)", Codec.DELTA5.toString()); + assertEquals("(5,64,2,1)", Codec.MDELTA5.toString()); + } + + public void testByte1() throws Exception { + for (int i = 0; i < 255; i++) + decode(Codec.BYTE1, new byte[] { (byte) i }, i, 0); + } + + public void testByte1Delta() throws Exception { + Codec BYTE1D = new BHSDCodec(1, 256, 0, 1); + long last = 0; + for (int i = 1; i < 255; i++) + last = decode(BYTE1D, new byte[] { (byte) 1 }, i, last); + } + + public void testByte1DeltaException() throws Exception { + Codec BYTE1D = new BHSDCodec(1, 256, 0, 1); + try { + BYTE1D.decode(new ByteArrayInputStream(new byte[] { (byte) 1 })); + fail("Decoding with a delta stream and not passing a last value should throw exception"); + } catch (Pack200Exception e) { + assertTrue(true); + } + } + + public void testByte1Signed() throws Exception { + Codec BYTE1S2 = new BHSDCodec(1, 256, 2); + decode(BYTE1S2, new byte[] { 0 }, 0, 0); + decode(BYTE1S2, new byte[] { 1 }, 1, 0); + decode(BYTE1S2, new byte[] { 2 }, 2, 0); + decode(BYTE1S2, new byte[] { 3 }, -1, 0); + decode(BYTE1S2, new byte[] { 4 }, 3, 0); + decode(BYTE1S2, new byte[] { 5 }, 4, 0); + decode(BYTE1S2, new byte[] { 6 }, 5, 0); + decode(BYTE1S2, new byte[] { 7 }, -2, 0); + decode(BYTE1S2, new byte[] { 8 }, 6, 0); + decode(BYTE1S2, new byte[] { 9 }, 7, 0); + decode(BYTE1S2, new byte[] { 10 }, 8, 0); + decode(BYTE1S2, new byte[] { 11 }, -3, 0); + } + + public void testCardinality() throws Exception { + BHSDCodec byte1 = Codec.BYTE1; + assertEquals(256, byte1.cardinality()); + assertEquals(0, byte1.smallest()); + assertEquals(255, byte1.largest()); + assertFalse(byte1.encodes(-257)); + assertFalse(byte1.encodes(-256)); + assertFalse(byte1.encodes(-255)); + assertFalse(byte1.encodes(-129)); + assertFalse(byte1.encodes(-128)); + assertFalse(byte1.encodes(-127)); + assertFalse(byte1.encodes(-1)); + assertTrue(byte1.encodes(0)); + assertTrue(byte1.encodes(1)); + assertTrue(byte1.encodes(255)); + assertFalse(byte1.encodes(256)); + BHSDCodec byte1s = new BHSDCodec(1, 256, 1); + assertEquals(256, byte1s.cardinality()); + assertEquals(-128, byte1s.smallest()); + assertEquals(127, byte1s.largest()); + assertFalse(byte1s.encodes(-257)); + assertFalse(byte1s.encodes(-256)); + assertFalse(byte1s.encodes(-255)); + assertFalse(byte1s.encodes(-129)); + assertTrue(byte1s.encodes(-128)); + assertTrue(byte1s.encodes(-127)); + assertTrue(byte1s.encodes(-1)); + assertTrue(byte1s.encodes(0)); + assertTrue(byte1s.encodes(1)); + assertTrue(byte1s.encodes(127)); + assertFalse(byte1s.encodes(128)); + assertFalse(byte1s.encodes(129)); + assertFalse(byte1s.encodes(255)); + assertFalse(byte1s.encodes(256)); + BHSDCodec byte2s = new BHSDCodec(1, 256, 2); + assertEquals(256, byte2s.cardinality()); + assertEquals(-64, byte2s.smallest()); + assertEquals(191, byte2s.largest()); + assertFalse(byte2s.encodes(-257)); + assertFalse(byte2s.encodes(-256)); + assertFalse(byte2s.encodes(-255)); + assertFalse(byte2s.encodes(-129)); + assertFalse(byte2s.encodes(-128)); + assertFalse(byte2s.encodes(-127)); + assertFalse(byte2s.encodes(-65)); + assertTrue(byte2s.encodes(-64)); + assertTrue(byte2s.encodes(-64)); + assertTrue(byte2s.encodes(-1)); + assertTrue(byte2s.encodes(0)); + assertTrue(byte2s.encodes(1)); + assertTrue(byte2s.encodes(127)); + assertTrue(byte2s.encodes(128)); + assertTrue(byte2s.encodes(191)); + assertFalse(byte2s.encodes(192)); + assertFalse(byte2s.encodes(256)); + } + + public void testRunCodec() throws Exception { + RunCodec runCodec = new RunCodec(1, Codec.UNSIGNED5, Codec.BYTE1); + ByteArrayInputStream bais = new ByteArrayInputStream(new byte[] { + (byte) 192, 0, (byte) 192, 0 }); + assertEquals(192, runCodec.decode(bais)); + assertEquals(192, runCodec.decode(bais)); + assertEquals(0, runCodec.decode(bais)); + assertEquals(0, bais.available()); + runCodec = new RunCodec(1, Codec.BYTE1, Codec.UNSIGNED5); + bais = new ByteArrayInputStream(new byte[] { (byte) 192, 0, (byte) 192, + 0 }); + assertEquals(192, runCodec.decode(bais)); + assertEquals(0, runCodec.decode(bais)); + assertEquals(192, runCodec.decode(bais)); + assertEquals(0, bais.available()); + } + + public void testUnsigned5() throws Exception { + decode(Codec.UNSIGNED5, new byte[] { 1 }, 1, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 191 }, 191, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 192, 0 }, 192, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 193, 0 }, 193, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 255, 0 }, 255, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 192, 1 }, 256, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 192, 5 }, 512, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 192, 13 }, 1024, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 192, 29 }, 2048, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 255, (byte) 191 }, 12479, 0); + + decode(Codec.UNSIGNED5, new byte[] { (byte) 192, (byte) 192, 0 }, + 12480, 0); + decode(Codec.UNSIGNED5, + new byte[] { (byte) 255, (byte) 255, (byte) 191 }, 798911, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 192, (byte) 192, + (byte) 192, 0 }, 798912, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 255, (byte) 255, + (byte) 255, (byte) 191 }, 51130559, 0); + decode(Codec.UNSIGNED5, new byte[] { (byte) 192, (byte) 192, + (byte) 192, (byte) 192, 0 }, 51130560, 0); + decodeFail(Codec.UNSIGNED5, new byte[] { (byte) 192 }); + decodeFail(Codec.UNSIGNED5, new byte[] { (byte) 192, (byte) 192 }); + decodeFail(Codec.UNSIGNED5, new byte[] { (byte) 192, (byte) 192, + (byte) 192 }); + decodeFail(Codec.UNSIGNED5, new byte[] { (byte) 192, (byte) 192, + (byte) 192, (byte) 192 }); + } + public void testCodecFamilies() { + checkAscendingCardinalities(CanonicalCodecFamilies.nonDeltaUnsignedCodecs1); + checkAscendingCardinalities(CanonicalCodecFamilies.nonDeltaUnsignedCodecs2); + checkAscendingCardinalities(CanonicalCodecFamilies.nonDeltaUnsignedCodecs3); + checkAscendingCardinalities(CanonicalCodecFamilies.nonDeltaUnsignedCodecs4); + checkAscendingCardinalities(CanonicalCodecFamilies.nonDeltaUnsignedCodecs5); + checkAscendingCardinalities(CanonicalCodecFamilies.deltaUnsignedCodecs1); + checkAscendingCardinalities(CanonicalCodecFamilies.deltaUnsignedCodecs2); + checkAscendingCardinalities(CanonicalCodecFamilies.deltaUnsignedCodecs3); + checkAscendingCardinalities(CanonicalCodecFamilies.deltaUnsignedCodecs4); + checkAscendingCardinalities(CanonicalCodecFamilies.deltaUnsignedCodecs5); + } + + private void checkAscendingCardinalities(BHSDCodec[] family) { + for (int i = 1; i < family.length; i++) { + BHSDCodec previous = family[i-1]; + BHSDCodec codec = family[i]; + assertTrue(codec.largest() >= previous.largest()); + assertTrue(codec.smallest() <= previous.smallest()); + } + } + + private void decodeFail(final Codec codec, final byte[] data) + throws IOException, Pack200Exception { + try { + decode(codec, data, 0, 0); + fail("Should have detected an EOFException"); + } catch (EOFException e) { + assertTrue(true); + } + } + + private long decode(final Codec codec, final byte[] data, final long value, + final long last) throws IOException, Pack200Exception { + final ByteArrayInputStream in = new ByteArrayInputStream(data); + assertEquals(value, codec.decode(in, last)); + assertEquals(-1, in.read()); + return (value); + } +} diff --git a/src/test/java/org/apache/harmony/pack200/tests/HelloWorld.java b/src/test/java/org/apache/harmony/pack200/tests/HelloWorld.java new file mode 100644 index 0000000..76ba7b0 --- /dev/null +++ b/src/test/java/org/apache/harmony/pack200/tests/HelloWorld.java @@ -0,0 +1,36 @@ +/* + * 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.pack200.tests; + +/** + * This is intended to be used as a test class for unpacking a packed Jar file. + */ +public class HelloWorld { + + int i = 97, j = 42, k = 12345; + float f = 3.142f, g = 2.718f; + long l = 299792458; + double d = 4.0d; + + public static void main(String[] args) { + System.out.println("Hello world"); + } + + public HelloWorld[][] method(int a, int b, int c) { + return null; + } +} diff --git a/src/test/java/org/apache/harmony/pack200/tests/NewAttributeBandsTest.java b/src/test/java/org/apache/harmony/pack200/tests/NewAttributeBandsTest.java new file mode 100644 index 0000000..51ff186 --- /dev/null +++ b/src/test/java/org/apache/harmony/pack200/tests/NewAttributeBandsTest.java @@ -0,0 +1,263 @@ +/* + * 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.pack200.tests; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.harmony.pack200.AttributeDefinitionBands; +import org.apache.harmony.pack200.CPUTF8; +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.CpBands; +import org.apache.harmony.pack200.NewAttribute; +import org.apache.harmony.pack200.NewAttributeBands; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.pack200.SegmentHeader; +import org.apache.harmony.pack200.AttributeDefinitionBands.AttributeDefinition; +import org.apache.harmony.pack200.NewAttributeBands.Call; +import org.apache.harmony.pack200.NewAttributeBands.Callable; +import org.apache.harmony.pack200.NewAttributeBands.Integral; +import org.apache.harmony.pack200.NewAttributeBands.Reference; +import org.apache.harmony.pack200.NewAttributeBands.Replication; +import org.apache.harmony.pack200.NewAttributeBands.Union; +import org.apache.harmony.pack200.NewAttributeBands.UnionCase; + +/** + * Tests for pack200 support for non-predefined attributes + */ +public class NewAttributeBandsTest extends TestCase { + + public void testEmptyLayout() throws IOException { + CPUTF8 name = new CPUTF8("TestAttribute"); + CPUTF8 layout = new CPUTF8(""); + MockNewAttributeBands newAttributeBands = new MockNewAttributeBands(1, + null, null, new AttributeDefinition(35, + AttributeDefinitionBands.CONTEXT_CLASS, name, layout)); + List layoutElements = newAttributeBands.getLayoutElements(); + assertEquals(0, layoutElements.size()); + } + + public void testIntegralLayouts() throws IOException { + tryIntegral("B"); + tryIntegral("FB"); + tryIntegral("SB"); + tryIntegral("H"); + tryIntegral("FH"); + tryIntegral("SH"); + tryIntegral("I"); + tryIntegral("FI"); + tryIntegral("SI"); + tryIntegral("PB"); + tryIntegral("OB"); + tryIntegral("OSB"); + tryIntegral("POB"); + tryIntegral("PH"); + tryIntegral("OH"); + tryIntegral("OSH"); + tryIntegral("POH"); + tryIntegral("PI"); + tryIntegral("OI"); + tryIntegral("OSI"); + tryIntegral("POI"); + } + + private void tryIntegral(String layoutStr) throws IOException { + CPUTF8 name = new CPUTF8("TestAttribute"); + CPUTF8 layout = new CPUTF8(layoutStr); + MockNewAttributeBands newAttributeBands = new MockNewAttributeBands(1, + null, null, new AttributeDefinition(35, + AttributeDefinitionBands.CONTEXT_CLASS, name, layout)); + List layoutElements = newAttributeBands.getLayoutElements(); + assertEquals(1, layoutElements.size()); + Integral element = (Integral) layoutElements.get(0); + assertEquals(layoutStr, element.getTag()); + } + + public void testReplicationLayouts() throws IOException { + CPUTF8 name = new CPUTF8("TestAttribute"); + CPUTF8 layout = new CPUTF8("NH[PHOHRUHRSHH]"); + MockNewAttributeBands newAttributeBands = new MockNewAttributeBands(1, + null, null, new AttributeDefinition(35, + AttributeDefinitionBands.CONTEXT_CLASS, name, layout)); + List layoutElements = newAttributeBands.getLayoutElements(); + assertEquals(1, layoutElements.size()); + Replication element = (Replication) layoutElements.get(0); + Integral countElement = element.getCountElement(); + assertEquals("H", countElement.getTag()); + List replicatedElements = element.getLayoutElements(); + assertEquals(5, replicatedElements.size()); + Integral firstElement = (Integral) replicatedElements.get(0); + assertEquals("PH", firstElement.getTag()); + Integral secondElement = (Integral) replicatedElements.get(1); + assertEquals("OH", secondElement.getTag()); + Reference thirdElement = (Reference) replicatedElements.get(2); + assertEquals("RUH", thirdElement.getTag()); + Reference fourthElement = (Reference) replicatedElements.get(3); + assertEquals("RSH", fourthElement.getTag()); + Integral fifthElement = (Integral) replicatedElements.get(4); + assertEquals("H", fifthElement.getTag()); + } + + public void testReferenceLayouts() throws IOException { + tryReference("KIB"); + tryReference("KIH"); + tryReference("KII"); + tryReference("KINH"); + tryReference("KJH"); + tryReference("KDH"); + tryReference("KSH"); + tryReference("KQH"); + tryReference("RCH"); + tryReference("RSH"); + tryReference("RDH"); + tryReference("RFH"); + tryReference("RMH"); + tryReference("RIH"); + tryReference("RUH"); + tryReference("RQH"); + tryReference("RQNH"); + tryReference("RQNI"); + } + + private void tryReference(String layoutStr) throws IOException { + CPUTF8 name = new CPUTF8("TestAttribute"); + CPUTF8 layout = new CPUTF8(layoutStr); + MockNewAttributeBands newAttributeBands = new MockNewAttributeBands(1, + null, null, new AttributeDefinition(35, + AttributeDefinitionBands.CONTEXT_CLASS, name, layout)); + List layoutElements = newAttributeBands.getLayoutElements(); + assertEquals(1, layoutElements.size()); + Reference element = (Reference) layoutElements.get(0); + assertEquals(layoutStr, element.getTag()); + } + + public void testUnionLayout() throws IOException { + CPUTF8 name = new CPUTF8("TestAttribute"); + CPUTF8 layout = new CPUTF8("TB(55)[FH](23)[]()[RSH]"); + MockNewAttributeBands newAttributeBands = new MockNewAttributeBands(1, + null, null, new AttributeDefinition(35, + AttributeDefinitionBands.CONTEXT_CLASS, name, layout)); + List layoutElements = newAttributeBands.getLayoutElements(); + assertEquals(1, layoutElements.size()); + Union element = (Union) layoutElements.get(0); + Integral tag = element.getUnionTag(); + assertEquals("B", tag.getTag()); + List unionCases = element.getUnionCases(); + assertEquals(2, unionCases.size()); + UnionCase firstCase = (UnionCase) unionCases.get(0); + assertTrue(firstCase.hasTag(55)); + assertFalse(firstCase.hasTag(23)); + List body = firstCase.getBody(); + assertEquals(1, body.size()); + Integral bodyElement = (Integral) body.get(0); + assertEquals("FH", bodyElement.getTag()); + UnionCase secondCase = (UnionCase) unionCases.get(1); + assertTrue(secondCase.hasTag(23)); + assertFalse(secondCase.hasTag(55)); + body = secondCase.getBody(); + assertEquals(0, body.size()); + List defaultBody = element.getDefaultCaseBody(); + assertEquals(1, defaultBody.size()); + Reference ref = (Reference) defaultBody.get(0); + assertEquals("RSH", ref.getTag()); + } + + public void testLayoutWithCalls() throws IOException { + CPUTF8 name = new CPUTF8("TestAttribute"); + CPUTF8 layout = new CPUTF8( + "[NH[(1)]][RSH NH[RUH(1)]][TB(66,67,73,83,90)[KIH](68)[KDH](70)[KFH](74)[KJH](99)[RSH](101)[RSH RUH](115)[RUH](91)[NH[(0)]](64)[RSH[RUH(0)]]()[]]"); + MockNewAttributeBands newAttributeBands = new MockNewAttributeBands(1, + null, null, new AttributeDefinition(35, + AttributeDefinitionBands.CONTEXT_CLASS, name, layout)); + List layoutElements = newAttributeBands.getLayoutElements(); + assertEquals(3, layoutElements.size()); + Callable firstCallable = (Callable) layoutElements.get(0); + Callable secondCallable = (Callable) layoutElements.get(1); + Callable thirdCallable = (Callable) layoutElements.get(2); + List firstBody = firstCallable.getBody(); + assertEquals(1, firstBody.size()); + Replication rep = (Replication) firstBody.get(0); + List repBody = rep.getLayoutElements(); + assertEquals(1, repBody.size()); + Call call = (Call) repBody.get(0); + assertEquals(1, call.getCallableIndex()); + assertEquals(secondCallable, call.getCallable()); + } + + public void testAddAttributes() throws IOException, Pack200Exception { + CPUTF8 name = new CPUTF8("TestAttribute"); + CPUTF8 layout = new CPUTF8("B"); + MockNewAttributeBands newAttributeBands = new MockNewAttributeBands(1, + null, null, new AttributeDefinition(35, + AttributeDefinitionBands.CONTEXT_CLASS, name, layout)); + newAttributeBands.addAttribute(new NewAttribute(null, "TestAttribute", "B", new byte[] {27}, null, 0, null)); + newAttributeBands.addAttribute(new NewAttribute(null, "TestAttribute", "B", new byte[] {56}, null, 0, null)); + newAttributeBands.addAttribute(new NewAttribute(null, "TestAttribute", "B", new byte[] {3}, null, 0, null)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + newAttributeBands.pack(out); + // BYTE1 is used for B layouts so we don't need to unpack to test the results + byte[] bytes = out.toByteArray(); + assertEquals(3, bytes.length); + assertEquals(27, bytes[0]); + assertEquals(56, bytes[1]); + assertEquals(3, bytes[2]); + } + + public void testAddAttributesWithReplicationLayout() throws IOException, Pack200Exception { + CPUTF8 name = new CPUTF8("TestAttribute"); + CPUTF8 layout = new CPUTF8("NB[SH]"); + MockNewAttributeBands newAttributeBands = new MockNewAttributeBands(1, + null, null, new AttributeDefinition(35, + AttributeDefinitionBands.CONTEXT_CLASS, name, layout)); + newAttributeBands.addAttribute(new NewAttribute(null, "TestAttribute", "B", new byte[] {1, 0, 100}, null, 0, null)); + short s = -50; + byte b1 = (byte)(s>>>8); + byte b2 = (byte)s; + newAttributeBands.addAttribute(new NewAttribute(null, "TestAttribute", "B", new byte[] {3, 0, 5, 0, 25, b1, b2}, null, 0, null)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + newAttributeBands.pack(out); + byte[] bytes = out.toByteArray(); + assertEquals(1, bytes[0]); + assertEquals(3, bytes[1]); + byte[] band = new byte[bytes.length - 2]; + System.arraycopy(bytes, 2, band, 0, band.length); + int[] decoded = Codec.SIGNED5.decodeInts(4, new ByteArrayInputStream(band)); + assertEquals(4, decoded.length); + assertEquals(100, decoded[0]); + assertEquals(5, decoded[1]); + assertEquals(25, decoded[2]); + assertEquals(-50, decoded[3]); + } + + private class MockNewAttributeBands extends NewAttributeBands { + + public MockNewAttributeBands(int effort, CpBands cpBands, + SegmentHeader header, AttributeDefinition def) + throws IOException { + super(effort, cpBands, header, def); + } + + public List getLayoutElements() { + return attributeLayoutElements; + } + } + +} diff --git a/src/test/java/org/apache/harmony/pack200/tests/PopulationCodecTest.java b/src/test/java/org/apache/harmony/pack200/tests/PopulationCodecTest.java new file mode 100644 index 0000000..06a07a5 --- /dev/null +++ b/src/test/java/org/apache/harmony/pack200/tests/PopulationCodecTest.java @@ -0,0 +1,67 @@ +/* + * 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.pack200.tests; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.pack200.PopulationCodec; + +public class PopulationCodecTest extends TestCase { + + public void testPopulationCodec() throws IOException, Pack200Exception { + checkDecode(new byte[] { 4, 5, 6, 4, 2, 1, 3, 0, 7 }, new long[] { 5, + 4, 6, 7 }, Codec.BYTE1); + // Codec.SIGNED5 can be trivial for small n, because the encoding is 2n + // if even, 2n-1 if odd + // Therefore they're left here to explain what the values are :-) + checkDecode(new byte[] { 4 * 2, 4 * 2 - 1, 6 * 2, 4 * 2, 2 * 2, 1 * 2, + 3 * 2, 0, 7 * 2 }, new long[] { -4, 4, 6, 7 }, Codec.SIGNED5); + checkDecode(new byte[] { 4 * 2 - 1, 4 * 2, 6 * 2, 4 * 2, 2 * 2, 1 * 2, + 3 * 2, 0, 7 * 2 }, new long[] { 4, -4, 6, 7 }, Codec.SIGNED5); + checkDecode(new byte[] { 1, 1, 1 }, new long[] { 1 }, Codec.BYTE1); + checkDecode(new byte[] { 2, 2, 1 }, new long[] { 2 }, Codec.BYTE1); + checkDecode(new byte[] { 1, 1, 2 }, new long[] { -1 }, Codec.SIGNED5); + checkDecode(new byte[] { 2, 2, 0, 1, 3 }, new long[] { 3, 2 }, + Codec.BYTE1); + checkDecode(new byte[] { 1, 2, 3, 4, 4, 2, 3, 4, 0, 1 }, new long[] { + 2, 3, 4, 1 }, Codec.BYTE1); + checkDecode(new byte[] { 3, 2, 1, 4, 4, 2, 3, 4, 0, 1 }, new long[] { + 2, 1, 4, 1 }, Codec.BYTE1); + checkDecode(new byte[] { 3, 2, 1, 4, 1, 2, 3, 4, 0, 1 }, new long[] { + 2, 1, 4, 1 }, Codec.BYTE1); + } + + private void checkDecode(byte[] data, long[] expectedResult, Codec codec) + throws IOException, Pack200Exception { + InputStream in = new ByteArrayInputStream(data); + + int[] result = new PopulationCodec(codec, codec, codec).decodeInts( + expectedResult.length, in); + assertEquals(expectedResult.length, result.length); + for (int i = 0; i < expectedResult.length; i++) { + assertEquals(expectedResult[i], result[i]); + } + assertEquals(0, in.available()); + } + +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/AbstractBandsTestCase.java b/src/test/java/org/apache/harmony/unpack200/tests/AbstractBandsTestCase.java new file mode 100644 index 0000000..63626b6 --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/AbstractBandsTestCase.java @@ -0,0 +1,83 @@ +/* + * 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.unpack200.tests; + +import junit.framework.TestCase; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.AttrDefinitionBands; +import org.apache.harmony.unpack200.AttributeLayoutMap; +import org.apache.harmony.unpack200.Segment; +import org.apache.harmony.unpack200.SegmentHeader; +import org.apache.harmony.unpack200.SegmentOptions; + +/** + * + */ +public abstract class AbstractBandsTestCase extends TestCase { + + protected int numClasses = 1; + protected int[] numMethods = { 1 }; + + public class MockSegmentHeader extends SegmentHeader { + + public MockSegmentHeader(Segment segment) { + super(segment); + } + + public int getClassCount() { + return numClasses; + } + + public SegmentOptions getOptions() { + try { + return new SegmentOptions(0); + } catch (Pack200Exception e) { + return null; + } + } + } + + public class MockAttributeDefinitionBands extends AttrDefinitionBands { + + public MockAttributeDefinitionBands(Segment segment) { + super(segment); + } + + public AttributeLayoutMap getAttributeDefinitionMap() { + try { + return new AttributeLayoutMap(); + } catch (Pack200Exception e) { + fail(e.getLocalizedMessage()); + } + return null; + } + + } + + public class MockSegment extends Segment { + + protected AttrDefinitionBands getAttrDefinitionBands() { + return new MockAttributeDefinitionBands(this); + } + + public SegmentHeader getSegmentHeader() { + return new MockSegmentHeader(this); + } + } + +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/ArchiveTest.java b/src/test/java/org/apache/harmony/unpack200/tests/ArchiveTest.java new file mode 100644 index 0000000..3fbf93f --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/ArchiveTest.java @@ -0,0 +1,199 @@ +/* + * 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.unpack200.tests; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.Archive; + +/** + * Tests for org.apache.harmony.unpack200.Archive, which is the main class for + * unpack200. + */ +public class ArchiveTest extends TestCase { + + InputStream in; + JarOutputStream out; + File file; + + public void testJustResourcesGZip() throws Exception { + in = Archive.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/JustResources.pack.gz"); + file = File.createTempFile("Just", "ResourcesGz.jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Archive archive = new Archive(in, out); + archive.unpack(); + } + + // Test with an archive containing Harmony's SQL module, packed with -E1 + public void testWithSqlE1() throws Exception { + in = Archive.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/sql-e1.pack.gz"); + file = File.createTempFile("sql-e1", ".jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Archive archive = new Archive(in, out); + archive.unpack(); + } + + // Test with an archive containing Harmony's SQL module + public void testWithSql() throws Exception { + in = Archive.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/sql.pack.gz"); + file = File.createTempFile("sql", ".jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Archive archive = new Archive(in, out); + archive.unpack(); + JarFile jarFile = new JarFile(file); + file.deleteOnExit(); + + File compareFile = new File(Archive.class.getResource( + "/org/apache/harmony/pack200/tests/sqlUnpacked.jar").toURI()); + + JarFile jarFile2 = new JarFile(compareFile); + + long differenceInJarSizes = Math.abs(compareFile.length() + - file.length()); + + assertTrue("Expected jar files to be a similar size, difference was " + + differenceInJarSizes + " bytes", differenceInJarSizes < 100); + + Enumeration entries = jarFile.entries(); + Enumeration entries2 = jarFile2.entries(); + while(entries.hasMoreElements() && entries2.hasMoreElements()) { + + JarEntry entry = (JarEntry) entries.nextElement(); + assertNotNull(entry); + String name = entry.getName(); + + JarEntry entry2 = (JarEntry) entries2.nextElement(); + assertNotNull(entry2); + String name2 = entry2.getName(); + + assertEquals(name, name2); + + InputStream ours = jarFile.getInputStream(entry); + InputStream expected = jarFile2.getInputStream(entry2); + + BufferedReader reader1 = new BufferedReader(new InputStreamReader(ours)); + BufferedReader reader2 = new BufferedReader(new InputStreamReader( + expected)); + String line1 = reader1.readLine(); + String line2 = reader2.readLine(); + int i = 1; + while (line1 != null || line2 != null) { + assertEquals("Unpacked class files differ for " + name, line2, line1); + line1 = reader1.readLine(); + line2 = reader2.readLine(); + i++; + } + reader1.close(); + reader2.close(); + } + } + + // Test with an archive containing Harmony's Pack200 module, packed with -E1 + public void testWithPack200E1() throws Exception { + in = Archive.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/pack200-e1.pack.gz"); + file = File.createTempFile("p200-e1", ".jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Archive archive = new Archive(in, out); + archive.unpack(); + } + + // Test with an archive containing Harmony's Pack200 module + public void testWithPack200() throws Exception { + in = Archive.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/pack200.pack.gz"); + file = File.createTempFile("p200", ".jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Archive archive = new Archive(in, out); + archive.unpack(); + } + + // Test with an archive containing Harmony's JNDI module + public void testWithJNDIE1() throws Exception { + in = Archive.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/jndi-e1.pack.gz"); + file = File.createTempFile("jndi-e1", ".jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Archive archive = new Archive(in, out); + archive.unpack(); + } + + // Test with an archive containing Annotations + public void testWithAnnotations() throws Exception { + in = Archive.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/annotations.pack.gz"); + file = File.createTempFile("annotations", ".jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Archive archive = new Archive(in, out); + archive.unpack(); + } + + // Test with an archive packed with the -E0 option + public void testWithE0() throws Exception { + in = Archive.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/simple-E0.pack.gz"); + file = File.createTempFile("simple-e0", ".jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Archive archive = new Archive(in, out); + archive.unpack(); + } + + // Test with a class containing lots of local variables (regression test for + // HARMONY-5470) + public void testWithLargeClass() throws Exception { + in = Archive.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/LargeClass.pack.gz"); + file = File.createTempFile("largeClass", ".jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Archive archive = new Archive(in, out); + archive.unpack(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + try { + if (out != null) { + out.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + file.delete(); + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/harmony/unpack200/tests/AttributeLayoutMapTest.java b/src/test/java/org/apache/harmony/unpack200/tests/AttributeLayoutMapTest.java new file mode 100644 index 0000000..c0f4c6e --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/AttributeLayoutMapTest.java @@ -0,0 +1,53 @@ +/* + * 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.unpack200.tests; + +import junit.framework.TestCase; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.AttributeLayout; +import org.apache.harmony.unpack200.AttributeLayoutMap; + +public class AttributeLayoutMapTest extends TestCase { + + public void testRepeatable() throws Pack200Exception { + // Check we can retrieve a default layout + AttributeLayoutMap a = new AttributeLayoutMap(); + AttributeLayout layout = a.getAttributeLayout("SourceFile", + AttributeLayout.CONTEXT_CLASS); + assertNotNull(layout); + assertEquals("RUNH", layout.getLayout()); + // and that we can change it + a.add(new AttributeLayout("SourceFile", AttributeLayout.CONTEXT_CLASS, + "FROG", 15)); + layout = a.getAttributeLayout("SourceFile", + AttributeLayout.CONTEXT_CLASS); + assertNotNull(layout); + assertEquals("FROG", layout.getLayout()); + assertTrue(layout.matches(1 << 15)); + assertFalse(layout.matches(1 << 16)); + assertTrue(layout.matches(-1)); + assertFalse(layout.matches(0)); + // and that changes don't affect subsequent defaults + AttributeLayoutMap b = new AttributeLayoutMap(); + layout = b.getAttributeLayout("SourceFile", + AttributeLayout.CONTEXT_CLASS); + assertNotNull(layout); + assertEquals("RUNH", layout.getLayout()); + + } +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/AttributeLayoutTest.java b/src/test/java/org/apache/harmony/unpack200/tests/AttributeLayoutTest.java new file mode 100644 index 0000000..97b4fdc --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/AttributeLayoutTest.java @@ -0,0 +1,142 @@ +/* + * 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.unpack200.tests; + +import junit.framework.TestCase; + +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.AttributeLayout; +import org.apache.harmony.unpack200.Segment; +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.CPUTF8; +import org.apache.harmony.unpack200.bytecode.ClassFileEntry; + +public class AttributeLayoutTest extends TestCase { + + public class TestSegment extends Segment { + + public SegmentConstantPool getConstantPool() { + final ClassFileEntry[][] data = new ClassFileEntry[][] { + {}, // ALL + { entry("Zero"), entry("One"), entry("Two"), + entry("Three"), entry("Four"), entry("Five"), + entry("Six"), entry("Seven"), entry("Eight"), + entry("Nine") }, // UTF-8 + {}, + {}, + {}, + {}, + {}, + {}, + { entry("Eins"), entry("Zwei"), entry("Drei"), + entry("Vier"), entry("Funf"), entry("Sechs"), + entry("Sieben"), entry("Acht"), entry("Neun") }, // Signature + }; + return new SegmentConstantPool(null) { + + public ClassFileEntry getValue(int cp, long index) { + if (index == -1) + return null; + return data[cp][(int) index]; + } + + }; + } + + private ClassFileEntry entry(String string) { + return new CPUTF8(string); + } + } + + public void testBadData() { + assertTrue(throwsException(null, AttributeLayout.CONTEXT_CLASS, "")); + assertTrue(throwsException("", AttributeLayout.CONTEXT_CLASS, "")); + assertTrue(!throwsException("name", AttributeLayout.CONTEXT_CLASS, "")); + assertTrue(!throwsException("name", AttributeLayout.CONTEXT_METHOD, "")); + assertTrue(!throwsException("name", AttributeLayout.CONTEXT_FIELD, "")); + assertTrue(!throwsException("name", AttributeLayout.CONTEXT_CODE, "")); + assertTrue(throwsException("name", -1, "")); + assertTrue(throwsException("name", 1234, "")); + } + + public void testLayoutRU() throws Pack200Exception { + AttributeLayout layout = new AttributeLayout("RU", + AttributeLayout.CONTEXT_CLASS, "RU", 1); + Segment segment = new TestSegment(); + assertNull(layout.getValue(-1, segment.getConstantPool())); + assertEquals("Zero", ((CPUTF8)layout.getValue(0, segment.getConstantPool())).underlyingString()); + assertEquals("One", ((CPUTF8)layout.getValue(1, segment.getConstantPool())).underlyingString()); + } + + public void testLayoutRUN() throws Pack200Exception { + AttributeLayout layout = new AttributeLayout("RUN", + AttributeLayout.CONTEXT_CLASS, "RUN", 1); + Segment segment = new TestSegment(); + assertNull(layout.getValue(0, segment.getConstantPool())); + assertEquals("Zero", ((CPUTF8)layout.getValue(1, segment.getConstantPool())).underlyingString()); + assertEquals("One", ((CPUTF8)layout.getValue(2, segment.getConstantPool())).underlyingString()); + } + + public void testLayoutRS() throws Pack200Exception { + AttributeLayout layout = new AttributeLayout("RS", + AttributeLayout.CONTEXT_CLASS, "RS", 1); + Segment segment = new TestSegment(); + assertNull(layout.getValue(-1, segment.getConstantPool())); + assertEquals("Eins", ((CPUTF8)layout.getValue(0, segment.getConstantPool())).underlyingString()); + assertEquals("Zwei", ((CPUTF8)layout.getValue(1, segment.getConstantPool())).underlyingString()); + } + + public void testLayoutRSN() throws Pack200Exception { + AttributeLayout layout = new AttributeLayout("RSN", + AttributeLayout.CONTEXT_CLASS, "RSN", 1); + Segment segment = new TestSegment(); + assertNull(layout.getValue(0, segment.getConstantPool())); + assertEquals("Eins", ((CPUTF8)layout.getValue(1, segment.getConstantPool())).underlyingString()); + assertEquals("Zwei", ((CPUTF8)layout.getValue(2, segment.getConstantPool())).underlyingString()); + } + + public void testGetCodec() throws Pack200Exception { + AttributeLayout layout = new AttributeLayout("O", + AttributeLayout.CONTEXT_CLASS, "HOBS", 1); + assertEquals(Codec.BRANCH5, layout.getCodec()); + layout = new AttributeLayout("P", AttributeLayout.CONTEXT_METHOD, + "PIN", 1); + assertEquals(Codec.BCI5, layout.getCodec()); + layout = new AttributeLayout("S", AttributeLayout.CONTEXT_FIELD, "HS", + 1); + assertEquals(Codec.SIGNED5, layout.getCodec()); + layout = new AttributeLayout("RS", AttributeLayout.CONTEXT_CODE, + "RRRS", 1); + assertEquals(Codec.UNSIGNED5, layout.getCodec()); + layout = new AttributeLayout("KS", AttributeLayout.CONTEXT_CLASS, + "RKS", 1); + assertEquals(Codec.UNSIGNED5, layout.getCodec()); + layout = new AttributeLayout("B", AttributeLayout.CONTEXT_CLASS, + "TRKSB", 1); + assertEquals(Codec.BYTE1, layout.getCodec()); + } + + public boolean throwsException(String name, int context, String layout) { + try { + new AttributeLayout(name, context, layout, -1); + return false; + } catch (Pack200Exception e) { + return true; + } + } +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/BandSetTest.java b/src/test/java/org/apache/harmony/unpack200/tests/BandSetTest.java new file mode 100644 index 0000000..af04f02 --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/BandSetTest.java @@ -0,0 +1,82 @@ +/* + * 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.unpack200.tests; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.apache.harmony.pack200.BHSDCodec; +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.BandSet; +import org.apache.harmony.unpack200.Segment; +import org.apache.harmony.unpack200.SegmentHeader; + +public class BandSetTest extends TestCase { + + public class MockSegment extends Segment { + + public SegmentHeader getSegmentHeader() { + return new SegmentHeader(this); + } + } + + private final BandSet bandSet = new BandSet(new MockSegment()) { + + public void read(InputStream inputStream) throws IOException, + Pack200Exception { + } + + public void unpack() throws IOException, Pack200Exception { + } + + }; + + public void testDecodeBandInt() throws IOException, Pack200Exception { + BHSDCodec codec = Codec.BYTE1; + byte[] bytes = new byte[] { (byte) 3, (byte) 56, (byte) 122, (byte) 78 }; + InputStream in = new ByteArrayInputStream(bytes); + int[] ints = bandSet.decodeBandInt("Test Band", in, codec, 4); + for (int i = 0; i < ints.length; i++) { + assertEquals("Wrong value in position " + i, ints[i], bytes[i]); + } + } + + public void testParseFlags1() { + + } + + public void testParseFlags2() { + + } + + public void testParseFlags3() { + + } + + public void testParseReferences1() { + + } + + public void testParseReferences2() { + + } + +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/BcBandsTest.java b/src/test/java/org/apache/harmony/unpack200/tests/BcBandsTest.java new file mode 100644 index 0000000..0ce8edf --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/BcBandsTest.java @@ -0,0 +1,672 @@ +/* + * 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.unpack200.tests; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.AttrDefinitionBands; +import org.apache.harmony.unpack200.BcBands; +import org.apache.harmony.unpack200.ClassBands; +import org.apache.harmony.unpack200.CpBands; +import org.apache.harmony.unpack200.Segment; +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.CPClass; +import org.apache.harmony.unpack200.bytecode.CPDouble; +import org.apache.harmony.unpack200.bytecode.CPFieldRef; +import org.apache.harmony.unpack200.bytecode.CPFloat; +import org.apache.harmony.unpack200.bytecode.CPInteger; +import org.apache.harmony.unpack200.bytecode.CPInterfaceMethodRef; +import org.apache.harmony.unpack200.bytecode.CPLong; +import org.apache.harmony.unpack200.bytecode.CPMethodRef; +import org.apache.harmony.unpack200.bytecode.CPNameAndType; +import org.apache.harmony.unpack200.bytecode.CPString; +import org.apache.harmony.unpack200.bytecode.CPUTF8; + +/** + * Tests for Pack200 bytecode bands + */ + +/* + * TODO: The number 8 is used in most of the tests in this class as a low + * (non-zero) number that is not likely to indicate a multiple byte number, but + * should be replaced with properly encoded byte arrays when encoding is + * implemented. + */ +public class BcBandsTest extends AbstractBandsTestCase { + + public class MockCpBands extends CpBands { + + private final CPUTF8 cpUTF8 = new CPUTF8("java/lang/String"); + private final CPClass cpClass = new CPClass(cpUTF8, -1); + private final CPNameAndType descriptor = new CPNameAndType(new CPUTF8( + "Hello"), new CPUTF8("(a, b, c)"), -1); + + public MockCpBands(Segment segment) { + super(segment); + } + + public CPString cpStringValue(int index) { + return new CPString(cpUTF8, index); + } + + public CPInteger cpIntegerValue(int index) { + return new CPInteger(new Integer(21), index); + } + + public CPClass cpClassValue(int index) { + return cpClass; + } + + public CPFloat cpFloatValue(int index) { + return new CPFloat(new Float(2.5F), index); + } + + public CPLong cpLongValue(int index) { + return new CPLong(new Long(21L), index); + } + + public CPDouble cpDoubleValue(int index) { + return new CPDouble(new Double(2.5D), index); + } + + public CPFieldRef cpFieldValue(int index) { + return new CPFieldRef(cpClass, descriptor, index); + } + + public CPMethodRef cpMethodValue(int index) { + return new CPMethodRef(cpClass, descriptor, index); + } + + public CPInterfaceMethodRef cpIMethodValue(int index) { + return new CPInterfaceMethodRef(cpClass, descriptor, index); + } + + public String[] getCpClass() { + return new String[] {"Hello"}; + } + + public String[] getCpFieldClass() { + return new String[]{}; + } + + public String[] getCpMethodClass() { + return new String[]{}; + } + + public String[] getCpIMethodClass() { + return new String[]{}; + } + } + + public class MockClassBands extends ClassBands { + + public MockClassBands(Segment segment) { + super(segment); + } + + public long[][] getMethodFlags() { + long[][] flags = new long[numClasses][]; + for (int i = 0; i < flags.length; i++) { + flags[i] = new long[numMethods[i]]; + } + return flags; + } + + public int[] getCodeMaxStack() { + int totalMethods = 0; + for (int i = 0; i < numClasses; i++) { + totalMethods += numMethods[i]; + } + return new int[totalMethods]; + } + + public int[] getCodeMaxNALocals() { + int totalMethods = 0; + for (int i = 0; i < numClasses; i++) { + totalMethods += numMethods[i]; + } + return new int[totalMethods]; + } + + public boolean[] getCodeHasAttributes() { + int totalMethods = 0; + for (int i = 0; i < numClasses; i++) { + totalMethods += numMethods[i]; + } + return new boolean[totalMethods]; + } + + public String[][] getMethodDescr() { + String[][] descr = new String[numClasses][]; + for (int i = 0; i < descr.length; i++) { + descr[i] = new String[numMethods[i]]; + for (int j = 0; j < descr[i].length; j++) { + descr[i][j] = "hello()"; + } + } + return descr; + } + + public int[] getClassThisInts() { + int[] thisClasses = new int[numClasses]; + for (int index = 0; index < numClasses; index++) { + thisClasses[index] = 0; + } + return thisClasses; + } + + public int[] getClassSuperInts() { + int[] superClasses = new int[numClasses]; + for (int index = 0; index < numClasses; index++) { + superClasses[index] = 0; + } + return superClasses; + } + + public ArrayList[][] getMethodAttributes() { + ArrayList[][] attributes = new ArrayList[numClasses][]; + for (int i = 0; i < attributes.length; i++) { + attributes[i] = new ArrayList[numMethods[i]]; + for (int j = 0; j < attributes[i].length; j++) { + attributes[i][j] = new ArrayList(); + } + } + return attributes; + } + + public ArrayList getOrderedCodeAttributes() { + int totalMethods = 0; + for (int classIndex = 0; classIndex < numMethods.length; classIndex++) { + totalMethods = totalMethods + numMethods[classIndex]; + } + ArrayList orderedAttributeList = new ArrayList(); + for (int classIndex = 0; classIndex < totalMethods; classIndex++) { + ArrayList currentAttributes = new ArrayList(); + orderedAttributeList.add(currentAttributes); + } + return orderedAttributeList; + } + } + + public class MockSegment extends AbstractBandsTestCase.MockSegment { + + public CpBands cpBands; + + protected AttrDefinitionBands getAttrDefinitionBands() { + return new MockAttributeDefinitionBands(this); + } + + protected CpBands getCpBands() { + if (null == cpBands) { + cpBands = new MockCpBands(this); + } + return cpBands; + } + + protected ClassBands getClassBands() { + return new MockClassBands(this); + } + + public SegmentConstantPool getConstantPool() { + return cpBands.getConstantPool(); + } + } + + BcBands bcBands = new BcBands(new MockSegment()); + + /** + * Test with single byte instructions that mean all other bands apart from + * bc_codes will be empty. + * + * @throws IOException + * @throws Pack200Exception + */ + public void testSimple() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, (byte) 128, + (byte) 129, (byte) 130, (byte) 131, (byte) 133, (byte) 134, + (byte) 135, (byte) 136, (byte) 137, (byte) 138, (byte) 139, + (byte) 140, (byte) 141, (byte) 142, (byte) 143, (byte) 144, + (byte) 145, (byte) 146, (byte) 147, (byte) 148, (byte) 149, + (byte) 150, (byte) 151, (byte) 172, (byte) 173, (byte) 174, + (byte) 175, (byte) 176, (byte) 177, (byte) 190, (byte) 191, + (byte) 194, (byte) 195, (byte) 255 }; + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(bytes.length - 1, + bcBands.getMethodByteCodePacked()[0][0].length); + } + + /** + * Test with multiple classes but single byte instructions + * + * @throws IOException + * @throws Pack200Exception + */ + public void testMultipleClassesSimple() throws IOException, + Pack200Exception { + numClasses = 2; + numMethods = new int[] { 1, 1 }; + byte[] bytes = new byte[] { 50, 50, (byte) 255, 50, 50, (byte) 255 }; + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(2, bcBands.getMethodByteCodePacked()[0][0].length); + assertEquals(2, bcBands.getMethodByteCodePacked()[0][0].length); + + numClasses = 1; + numMethods = new int[] { 1 }; + } + + /** + * Test with multiple classes and multiple methods but single byte + * instructions + * + * @throws IOException + * @throws Pack200Exception + */ + public void testMultipleMethodsSimple() throws IOException, + Pack200Exception { + numClasses = 2; + numMethods = new int[] { 3, 1 }; + byte[] bytes = new byte[] { 50, 50, (byte) 255, 50, 50, (byte) 255, 50, + 50, (byte) 255, 50, 50, (byte) 255 }; + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(2, bcBands.getMethodByteCodePacked()[0][0].length); + assertEquals(2, bcBands.getMethodByteCodePacked()[0][0].length); + + numClasses = 1; + numMethods = new int[] { 1 }; + } + + /** + * Test with codes that require entries in the bc_case_count and + * bc_case_value bands + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcCaseBands() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 170, (byte) 171, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 255, 2, 5, // bc_case_count + 0, 0, 0, 0, 0, 0, 0, // bc_case_value + 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // bc_label + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(18, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_case_count = bcBands.getBcCaseCount(); + assertEquals(2, bc_case_count.length); + assertEquals(2, bc_case_count[0]); + assertEquals(5, bc_case_count[1]); + int[] bc_case_value = bcBands.getBcCaseValue(); + assertEquals(0, bc_case_value[0]); + assertEquals(0, bc_case_value[1]); + assertEquals(9, bcBands.getBcLabel().length); + } + + /** + * Test with codes that should require entries in the bc_byte band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcByteBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { 16, (byte) 132, (byte) 188, (byte) 197, + (byte) 255, 8, 8, 8, 8, // bc_byte band + 8, // bc_locals band (required by iinc (132)) + 8 }; // bc_class band (required by multianewarray (197)) + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(4, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_byte = bcBands.getBcByte(); + assertEquals(4, bc_byte.length); + for (int i = 0; i < bc_byte.length; i++) { + assertEquals(8, bc_byte[i]); + } + assertEquals(1, bcBands.getBcLocal().length); + assertEquals(1, bcBands.getBcClassRef().length); + } + + /** + * Test with codes that should require entries in the bc_short band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcShortBand() throws IOException, Pack200Exception { + // TODO: Need to fix this testcase so it has enough data to pass. + byte[] bytes = new byte[] { 17, (byte) 196, (byte) 132, (byte) 255, 8, + 8,// bc_short band + 8 }; // bc_locals band (required by wide iinc (196, 132)) + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(3, bcBands.getMethodByteCodePacked()[0][0].length); + assertEquals(2, bcBands.getBcShort().length); + assertEquals(1, bcBands.getBcLocal().length); + } + + /** + * Test with codes that should require entries in the bc_local band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcLocalBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { 21, 22, 23, 24, 25, 54, 55, 56, 57, 58, + (byte) 169, (byte) 255, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }; // bc_local + // band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(11, bcBands.getMethodByteCodePacked()[0][0].length); + assertEquals(11, bcBands.getBcLocal().length); + } + + /** + * Test with codes that should require entries in the bc_label band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcLabelBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 159, (byte) 160, (byte) 161, + (byte) 162, (byte) 163, (byte) 164, (byte) 165, (byte) 166, + (byte) 167, (byte) 168, (byte) 170, (byte) 171, (byte) 198, + (byte) 199, (byte) 200, (byte) 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 255, 2, 2, // bc_case_count + // (required + // by + // tableswitch + // (170) and + // lookupswitch + // (171)) + 0, 0, 0, 0, // bc_case_value + // Now that we're actually doing real label lookup, need valid + // labels + // 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }; // bc_label + // band + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // bc_label + // band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(36, bcBands.getMethodByteCodePacked()[0][0].length); + assertEquals(20, bcBands.getBcLabel().length); + } + + public void testWideForms() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 196, (byte) 54, // wide istore + (byte) 196, (byte) 132, // wide iinc + (byte) 255, 0, // bc_short band + 0, 1 }; // bc_locals band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(4, bcBands.getMethodByteCodePacked()[0][0].length); + assertEquals(2, bcBands.getBcLocal().length); + assertEquals(1, bcBands.getBcShort().length); + } + + /** + * Test with codes that should require entries in the bc_intref band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcIntRefBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 234, (byte) 237, (byte) 255, 8, 8 }; // bc_intref + // band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(2, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_intref = bcBands.getBcIntRef(); + assertEquals(2, bc_intref.length); + } + + /** + * Test with codes that should require entries in the bc_floatref band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcFloatRefBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 235, (byte) 238, (byte) 255, 8, 8 }; // bc_floatref + // band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(2, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_floatref = bcBands.getBcFloatRef(); + assertEquals(2, bc_floatref.length); + } + + /** + * Test with codes that should require entries in the bc_longref band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcLongRefBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { 20, (byte) 255, 8 }; // bc_longref band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(1, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_longref = bcBands.getBcLongRef(); + assertEquals(1, bc_longref.length); + } + + /** + * Test with codes that should require entries in the bc_doubleref band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcDoubleRefBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 239, (byte) 255, 8 }; // bc_doubleref + // band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(1, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_doubleref = bcBands.getBcDoubleRef(); + assertEquals(1, bc_doubleref.length); + } + + /** + * Test with codes that should require entries in the bc_stringref band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcStringRefBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { 18, 19, (byte) 255, 8, 8 }; // bc_stringref + // band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(2, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_stringref = bcBands.getBcStringRef(); + assertEquals(2, bc_stringref.length); + } + + /** + * Test with codes that should require entries in the bc_classref band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcClassRefBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 233, (byte) 236, (byte) 255, 8, 8 }; // bc_classref + // band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(2, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_classref = bcBands.getBcClassRef(); + assertEquals(2, bc_classref.length); + } + + /** + * Test with codes that should require entries in the bc_fieldref band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcFieldRefBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 178, (byte) 179, (byte) 180, + (byte) 181, (byte) 255, 8, 8, 8, 8 }; // bc_fieldref band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(4, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_fieldref = bcBands.getBcFieldRef(); + assertEquals(4, bc_fieldref.length); + } + + /** + * Test with codes that should require entries in the bc_methodref band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcMethodRefBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 182, (byte) 183, (byte) 184, + (byte) 255, 8, 8, 8 }; // bc_methodref band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(3, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_methodref = bcBands.getBcMethodRef(); + assertEquals(3, bc_methodref.length); + } + + /** + * Test with codes that should require entries in the bc_imethodref band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcIMethodRefBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 185, (byte) 255, 8 }; // bc_imethodref + // band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(1, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_imethodref = bcBands.getBcIMethodRef(); + assertEquals(1, bc_imethodref.length); + } + + /** + * Test with codes that should require entries in the bc_thisfieldref band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcThisFieldBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 202, (byte) 203, (byte) 204, + (byte) 205, (byte) 209, (byte) 210, (byte) 211, (byte) 212, + (byte) 255, 8, 8, 8, 8, 8, 8, 8, 8 }; // bc_thisfieldref band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(8, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_thisfield = bcBands.getBcThisField(); + assertEquals(8, bc_thisfield.length); + } + + /** + * Test with codes that should require entries in the bc_superfield band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcSuperFieldBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 216, (byte) 217, (byte) 218, + (byte) 219, (byte) 223, (byte) 224, (byte) 225, (byte) 226, + (byte) 255, 8, 8, 8, 8, 8, 8, 8, 8 }; // bc_superfield band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(8, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_superfield = bcBands.getBcSuperField(); + assertEquals(8, bc_superfield.length); + } + + /** + * Test with codes that should require entries in the bc_thismethod band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcThisMethodBand() throws IOException, Pack200Exception { + byte[] bytes = new byte[] { (byte) 206, (byte) 207, (byte) 208, + (byte) 213, (byte) 214, (byte) 215, (byte) 255, 8, 8, 8, 8, 8, + 8 }; // bc_thismethod band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(6, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_thismethod = bcBands.getBcThisMethod(); + assertEquals(6, bc_thismethod.length); + } + + /** + * Test with codes that should require entries in the bc_supermethod band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcSuperMethodBand() throws IOException, Pack200Exception { + // TODO: Need to fix this testcase so it has enough data to pass. + if (true) + return; + byte[] bytes = new byte[] { (byte) 220, (byte) 221, (byte) 222, + (byte) 227, (byte) 228, (byte) 229, (byte) 255, 8, 8, 8, 8, 8, + 8 }; // bc_supermethod band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(6, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_supermethod = bcBands.getBcSuperMethod(); + assertEquals(6, bc_supermethod.length); + } + + /** + * Test with codes that should require entries in the bc_initrefref band + * + * @throws Pack200Exception + * @throws IOException + */ + public void testBcInitRefRefBand() throws IOException, Pack200Exception { + // TODO: Need to fix this testcase so it has enough data to pass. + if (true) + return; + byte[] bytes = new byte[] { (byte) 230, (byte) 231, (byte) 232, + (byte) 255, 8, 8, 8 }; // bc_initrefref band + InputStream in = new ByteArrayInputStream(bytes); + bcBands.unpack(in); + assertEquals(3, bcBands.getMethodByteCodePacked()[0][0].length); + int[] bc_initrefref = bcBands.getBcInitRef(); + assertEquals(3, bc_initrefref.length); + } + + public void testBcEscRefBands() throws IOException, Pack200Exception { + // TODO + } + + public void testBcEscBands() throws IOException, Pack200Exception { + // TODO + } + +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/CPUTF8Test.java b/src/test/java/org/apache/harmony/unpack200/tests/CPUTF8Test.java new file mode 100644 index 0000000..ee6980a --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/CPUTF8Test.java @@ -0,0 +1,37 @@ +/* + * 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.unpack200.tests; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.bytecode.CPUTF8; + +public class CPUTF8Test extends TestCase { + + public void testEquality() { + CPUTF8 one = new CPUTF8("(III)V", 1); + CPUTF8 two = new CPUTF8("((I[II)V", 2); + CPUTF8 three = new CPUTF8("([III)V", 3); + assertFalse(one.equals(two)); + assertFalse(one.equals(three)); + assertFalse(two.equals(three)); + + assertFalse(two.equals(one)); + assertFalse(three.equals(one)); + assertFalse(three.equals(two)); + } +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/ClassBandsTest.java b/src/test/java/org/apache/harmony/unpack200/tests/ClassBandsTest.java new file mode 100644 index 0000000..893336c --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/ClassBandsTest.java @@ -0,0 +1,179 @@ +/* + * 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.unpack200.tests; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.apache.harmony.pack200.BHSDCodec; +import org.apache.harmony.pack200.Codec; +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.ClassBands; +import org.apache.harmony.unpack200.CpBands; +import org.apache.harmony.unpack200.Segment; + +/** + * + */ +public class ClassBandsTest extends AbstractBandsTestCase { + + private String[] cpClasses; + private String[] cpDescriptor; + private String[] cpUTF8; + + public class MockCpBands extends CpBands { + + public MockCpBands(Segment segment) { + super(segment); + } + + public String[] getCpClass() { + return cpClasses; + } + + public String[] getCpDescriptor() { + return cpDescriptor; + } + + public String[] getCpUTF8() { + return cpUTF8; + } + + public int[] getCpInt() { + return new int[0]; + } + + public double[] getCpDouble() { + return new double[0]; + } + + public long[] getCpLong() { + return new long[0]; + } + + public float[] getCpFloat() { + return new float[0]; + } + + public String[] getCpSignature() { + return new String[0]; + } + } + + public class MockSegment extends AbstractBandsTestCase.MockSegment { + + protected CpBands getCpBands() { + return new MockCpBands(this); + } + } + + ClassBands classBands = new ClassBands(new MockSegment()); + + public void testSimple() throws IOException, Pack200Exception { + cpClasses = new String[] { "Class1", "Class2", "Class3", "Interface1", + "Interface2" }; + cpDescriptor = new String[0]; + cpUTF8 = new String[0]; + byte[] classThis = Codec.DELTA5.encode(1, 0); + byte[] classSuper = Codec.DELTA5.encode(2, 0); + byte[] classInterfaceCount = Codec.DELTA5.encode(2, 0); + byte[] classInterfaceRef1 = encodeBandInt( + new int[] { 3, 4 }, Codec.DELTA5); + byte[] classFieldCount = Codec.DELTA5.encode(0, 0); + byte[] classMethodCount = Codec.DELTA5.encode(0, 0); + byte[] classFlags = Codec.UNSIGNED5.encode(0, 0); + byte[][] allArrays = new byte[][] { classThis, classSuper, + classInterfaceCount, classInterfaceRef1, classFieldCount, + classMethodCount, classFlags }; + int total = classThis.length + classSuper.length + + classInterfaceCount.length + classInterfaceRef1.length + + classFieldCount.length + classMethodCount.length + + classFlags.length; + byte[] bytes = new byte[total]; + int index = 0; + for (int i = 0; i < allArrays.length; i++) { + for (int j = 0; j < allArrays[i].length; j++) { + bytes[index] = allArrays[i][j]; + index++; + } + } + ByteArrayInputStream in = new ByteArrayInputStream(bytes); + classBands.unpack(in); + assertEquals(1, classBands.getClassThisInts()[0]); + assertEquals(2, classBands.getClassSuperInts()[0]); + assertEquals(1, classBands.getClassInterfacesInts().length); + assertEquals(2, classBands.getClassInterfacesInts()[0].length); + assertEquals(3, classBands.getClassInterfacesInts()[0][0]); + assertEquals(4, classBands.getClassInterfacesInts()[0][1]); + cpClasses = null; + } + + public void testWithMethods() throws Pack200Exception, IOException { + cpClasses = new String[] { "Class1", "Class2", "Class3" }; + cpDescriptor = new String[] { "method1", "method2", "method3" }; + cpUTF8 = new String[0]; + byte[] classThis = Codec.DELTA5.encode(1, 0); + byte[] classSuper = Codec.DELTA5.encode(2, 0); + byte[] classInterfaceCount = Codec.DELTA5.encode(0, 0); + byte[] classFieldCount = Codec.DELTA5.encode(0, 0); + byte[] classMethodCount = Codec.DELTA5.encode(3, 0); + byte[] methodDescr = encodeBandInt(new int[] { 0, 1, 2 }, + Codec.MDELTA5); + byte[] methodFlagsLo = encodeBandInt( + new int[] { 0, 0, 0 }, Codec.UNSIGNED5); + byte[] classFlags = Codec.UNSIGNED5.encode(0, 0); + byte[][] allArrays = new byte[][] { classThis, classSuper, + classInterfaceCount, classFieldCount, classMethodCount, + methodDescr, methodFlagsLo, classFlags, }; + int total = 0; + for (int i = 0; i < allArrays.length; i++) { + total += allArrays[i].length; + } + byte[] bytes = new byte[total]; + int index = 0; + for (int i = 0; i < allArrays.length; i++) { + for (int j = 0; j < allArrays[i].length; j++) { + bytes[index] = allArrays[i][j]; + index++; + } + } + ByteArrayInputStream in = new ByteArrayInputStream(bytes); + classBands.unpack(in); + assertEquals(1, classBands.getClassThisInts()[0]); + assertEquals(2, classBands.getClassSuperInts()[0]); + assertEquals(1, classBands.getMethodDescr().length); + assertEquals(3, classBands.getMethodDescr()[0].length); + assertEquals(cpDescriptor[0], classBands.getMethodDescr()[0][0]); + assertEquals(cpDescriptor[1], classBands.getMethodDescr()[0][1]); + assertEquals(cpDescriptor[2], classBands.getMethodDescr()[0][2]); + + cpClasses = null; + cpDescriptor = null; + } + + public byte[] encodeBandInt(int[] data, BHSDCodec codec) + throws IOException, Pack200Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int i = 0; i < data.length; i++) { + baos.write(codec.encode(data[i], i == 0 ? 0 : data[i - 1])); + } + return baos.toByteArray(); + } + + +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/ClassVersionTest.java b/src/test/java/org/apache/harmony/unpack200/tests/ClassVersionTest.java new file mode 100644 index 0000000..520071e --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/ClassVersionTest.java @@ -0,0 +1,70 @@ +/* + * 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.unpack200.tests; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.Segment; + +public class ClassVersionTest extends TestCase { + + private static final int JAVA_15 = 49; + + public void testCorrectVersionOfSegment() throws IOException { + InputStream in = Segment.class + .getResourceAsStream("/org/apache/harmony/unpack200/Segment.class"); + DataInputStream din = new DataInputStream(in); + + assertEquals(0xCAFEBABE, din.readInt()); + din.readShort(); // MINOR -- don't care + assertTrue("Class file has been compiled with Java 1.5 compatibility" + + " instead of 1.4 or lower", din.readShort() < JAVA_15); + } + + public void testCorrectVersionOfTest() throws IOException { + InputStream in = Segment.class + .getResourceAsStream("/org/apache/harmony/unpack200/tests/ClassVersionTest.class"); + DataInputStream din = new DataInputStream(in); + + assertEquals(0xCAFEBABE, din.readInt()); + din.readShort(); // MINOR -- don't care + assertTrue("Class file has been compiled with Java 1.5 compatibility" + + " instead of 1.4 or lower", din.readShort() < JAVA_15); + din.close(); + } + + public void testCorrectVersionOfAdapter() throws IOException { + // tests that both the file is on the classpath and that it's been + // compiled correctly, but without actually loading the class + InputStream in = Segment.class + .getResourceAsStream("/org/apache/harmony/unpack200/Pack200Adapter.class"); + if (in != null) { // If running in Eclipse and Java5 stuff not + // built/available + DataInputStream din = new DataInputStream(in); + + assertEquals(0xCAFEBABE, din.readInt()); + din.readShort(); // MINOR -- don't care + assertTrue("Class file needs 1.5 compatibility", + din.readShort() >= JAVA_15); + din.close(); + } + } +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/CodeAttributeTest.java b/src/test/java/org/apache/harmony/unpack200/tests/CodeAttributeTest.java new file mode 100644 index 0000000..0589fd8 --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/CodeAttributeTest.java @@ -0,0 +1,203 @@ +/* + * 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.unpack200.tests; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.CpBands; +import org.apache.harmony.unpack200.Segment; +import org.apache.harmony.unpack200.SegmentConstantPool; +import org.apache.harmony.unpack200.bytecode.ByteCode; +import org.apache.harmony.unpack200.bytecode.CPFieldRef; +import org.apache.harmony.unpack200.bytecode.CPMethodRef; +import org.apache.harmony.unpack200.bytecode.CPString; +import org.apache.harmony.unpack200.bytecode.CPUTF8; +import org.apache.harmony.unpack200.bytecode.CodeAttribute; +import org.apache.harmony.unpack200.bytecode.LocalVariableTableAttribute; +import org.apache.harmony.unpack200.bytecode.OperandManager; + +/** + * Tests for CodeAttribute + */ +public class CodeAttributeTest extends TestCase { + + public class MockCodeAttribute extends CodeAttribute { + + public MockCodeAttribute(int maxStack, int maxLocals, + byte[] codePacked, Segment segment, + OperandManager operandManager, List exceptionTable) { + super(maxStack, maxLocals, codePacked, segment, operandManager, + exceptionTable); + } + + public int getLength() { + return super.getLength(); + } + } + + public class MockCpBands extends CpBands { + + public MockCpBands(Segment segment) { + super(segment); + } + + public CPFieldRef cpFieldValue(int index) { + return null; + } + + public CPString cpStringValue(int index) { + return new CPString(new CPUTF8("Hello"), -1); + } + + public CPMethodRef cpMethodValue(int index) { + return null; + } + + } + + public class MockOperandManager extends OperandManager { + + public MockOperandManager() { + super(new int[] {}, // bcCaseCount + new int[] {}, // bcCaseValues + new int[] {}, // bcByte + new int[] {}, // bcShort + new int[] {}, // bcLocal + new int[] {}, // bcLabel + new int[] {}, // bcIntRef + new int[] {}, // bcFloatRef + new int[] {}, // bcLongRef + new int[] {}, // bcDoubleRef + new int[] { 0, 1, 2, 3, 4 }, // bcStringRef + new int[] {}, // bcClassRef + new int[] {}, // bcFieldRef + new int[] {}, // bcMethodRef + new int[] {}, // bcIMethodRef + new int[] { 0, 0, 0, 0, 0, 0 }, // bcThisField + new int[] {}, // bcSuperField + new int[] { 0 }, // bcThisMethod + new int[] {}, // bcSuperMethod + new int[] {} // bcInitRef + , null); + } + } + + public class MockSegment extends Segment { + + public SegmentConstantPool getConstantPool() { + return new MockSegmentConstantPool(cpBands); + } + } + + public class MockSegmentConstantPool extends SegmentConstantPool { + + public MockSegmentConstantPool(CpBands bands) { + super(bands); + } + + protected int matchSpecificPoolEntryIndex(String[] nameArray, + String compareString, int desiredIndex) { + return 1; + } + } + + Segment segment = new MockSegment(); + CpBands cpBands = new MockCpBands(segment); + + public byte[] mixedByteArray = { -47, // aload_0_getstatic_this 0, 1 + -46, // aload_0_putstatic_this 4, 5 + 1, // aconst_null 8 + -45, // aload_0_getfield_this 9, 10 + // Should always end with a multibyte + // instruction + -44, // aload_0_putfield_this (int) 13, 14 + }; + + public byte[] singleByteArray = { 42, // aload_0 0 + 1, // aconst_null 1 + 18, // ldc 2 + -49, // return 4 + }; + + public void testLength() { + OperandManager operandManager = new MockOperandManager(); + operandManager.setSegment(segment); + operandManager.setCurrentClass("java/lang/Foo"); + + MockCodeAttribute attribute = new MockCodeAttribute(3, // maxStack + 2, // maxLocals + mixedByteArray, // codePacked + segment, // segment + operandManager, // operandManager + new ArrayList()); + assertEquals(29, attribute.getLength()); + + attribute.attributes.add(new LocalVariableTableAttribute(0, null, null, + null, null, null)); + assertEquals(37, attribute.getLength()); + } + + public void testMixedByteCodes() { + OperandManager operandManager = new MockOperandManager(); + operandManager.setSegment(segment); + operandManager.setCurrentClass("java/lang/Foo"); + + CodeAttribute attribute = new CodeAttribute(3, // maxStack + 2, // maxLocals + mixedByteArray, // codePacked + segment, // segment + operandManager, // operandManager + new ArrayList()); + assertEquals(2, attribute.maxLocals); + assertEquals(3, attribute.maxStack); + assertEquals("aload_0_putfield_this", ((ByteCode) attribute.byteCodes + .get(4)).toString()); + + int expectedLabels[] = new int[] { 0, 1, 4, 5, 8, 9, 10, 13, 14 }; + for (int index = 0; index < expectedLabels.length; index++) { + assertEquals(expectedLabels[index], + ((Integer) attribute.byteCodeOffsets.get(index)).intValue()); + } + } + + public void testSingleByteCodes() { + OperandManager operandManager = new MockOperandManager(); + operandManager.setSegment(segment); + operandManager.setCurrentClass("java/lang/Foo"); + + CodeAttribute attribute = new CodeAttribute(4, // maxStack + 3, // maxLocals + singleByteArray, // codePacked + segment, // segment + operandManager, // operandManager + new ArrayList()); + assertEquals(3, attribute.maxLocals); + assertEquals(4, attribute.maxStack); + assertEquals("invokespecial_this", ((ByteCode) attribute.byteCodes + .get(3)).toString()); + + int expectedLabels[] = new int[] { 0, 1, 2, 4 }; + for (int index = 0; index < expectedLabels.length; index++) { + assertEquals(expectedLabels[index], + ((Integer) attribute.byteCodeOffsets.get(index)).intValue()); + } + } + +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/ICTupleTest.java b/src/test/java/org/apache/harmony/unpack200/tests/ICTupleTest.java new file mode 100644 index 0000000..fae5c69 --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/ICTupleTest.java @@ -0,0 +1,64 @@ +/* + * 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.unpack200.tests; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.IcTuple; + +public class ICTupleTest extends TestCase { + + public void testPredictedClassTupleParsing() { + IcTuple tuple = new IcTuple( + "orw/SimpleHelloWorld$SimpleHelloWorldInner", 0, null, null, + -1, -1, -1, -1); + assertEquals("SimpleHelloWorldInner", tuple.simpleClassName()); + assertEquals("orw/SimpleHelloWorld", tuple.outerClassString()); + + tuple = new IcTuple("java/util/AbstractList$2$Local", 0, null, null, + -1, -1, -1, -1); + assertEquals("Local", tuple.simpleClassName()); + assertEquals("java/util/AbstractList$2", tuple.outerClassString()); + + tuple = new IcTuple("java/util/AbstractList#2#Local", 0, null, null, + -1, -1, -1, -1); + assertEquals("Local", tuple.simpleClassName()); + assertEquals("java/util/AbstractList$2", tuple.outerClassString()); + + tuple = new IcTuple("java/util/AbstractList$1", 0, null, null, -1, -1, + -1, -1); + assertEquals("1", tuple.simpleClassName()); + assertEquals("java/util/AbstractList", tuple.outerClassString()); + } + + public void testExplicitClassTupleParsing() { + IcTuple tuple = new IcTuple("Foo$$2$Local", IcTuple.NESTED_CLASS_FLAG, + null, "$2$Local", -1, -1, -1, -1); + assertEquals("$2$Local", tuple.simpleClassName()); + assertEquals("Foo$$2", tuple.outerClassString()); + + tuple = new IcTuple("Red$Herring", IcTuple.NESTED_CLASS_FLAG, + "Red$Herring", null, -1, -1, -1, -1); + assertEquals("Herring", tuple.simpleClassName()); + assertEquals("Red$Herring", tuple.outerClassString()); + + tuple = new IcTuple("X$1$Q", IcTuple.NESTED_CLASS_FLAG, "X$1", "Q", -1, + -1, -1, -1); + assertEquals("Q", tuple.simpleClassName()); + assertEquals("X$1", tuple.outerClassString()); + } +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/SegmentConstantPoolArrayCacheTest.java b/src/test/java/org/apache/harmony/unpack200/tests/SegmentConstantPoolArrayCacheTest.java new file mode 100644 index 0000000..8823195 --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/SegmentConstantPoolArrayCacheTest.java @@ -0,0 +1,78 @@ +/* + * 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.unpack200.tests; + +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.SegmentConstantPoolArrayCache; + +public class SegmentConstantPoolArrayCacheTest extends TestCase { + + public void testSingleSimpleArray() { + SegmentConstantPoolArrayCache arrayCache = new SegmentConstantPoolArrayCache(); + String array[] = {"Zero", "One", "Two", "Three", "Four"}; + List list = arrayCache.indexesForArrayKey(array, "Three"); + assertEquals(1, list.size()); + assertEquals(3, ((Integer)list.get(0)).intValue()); + } + + public void testSingleMultipleHitArray() { + SegmentConstantPoolArrayCache arrayCache = new SegmentConstantPoolArrayCache(); + String array[] = {"Zero", "OneThreeFour", "Two", "OneThreeFour", "OneThreeFour"}; + List list = arrayCache.indexesForArrayKey(array, "OneThreeFour"); + assertEquals(3, list.size()); + assertEquals(1, ((Integer)list.get(0)).intValue()); + assertEquals(3, ((Integer)list.get(1)).intValue()); + assertEquals(4, ((Integer)list.get(2)).intValue()); + } + + public void testMultipleArrayMultipleHit() { + SegmentConstantPoolArrayCache arrayCache = new SegmentConstantPoolArrayCache(); + String arrayOne[] = {"Zero", "Shared", "Two", "Shared", "Shared"}; + String arrayTwo[] = {"Shared", "One", "Shared", "Shared", "Shared"}; + + List listOne = arrayCache.indexesForArrayKey(arrayOne, "Shared"); + List listTwo = arrayCache.indexesForArrayKey(arrayTwo, "Shared"); + // Make sure we're using the cached values. First trip + // through builds the cache. + listOne = arrayCache.indexesForArrayKey(arrayOne, "Two"); + listTwo = arrayCache.indexesForArrayKey(arrayTwo, "Shared"); + + assertEquals(1, listOne.size()); + assertEquals(2, ((Integer)listOne.get(0)).intValue()); + + // Now look for a different element in list one + listOne = arrayCache.indexesForArrayKey(arrayOne, "Shared"); + assertEquals(3, listOne.size()); + assertEquals(1, ((Integer)listOne.get(0)).intValue()); + assertEquals(3, ((Integer)listOne.get(1)).intValue()); + assertEquals(4, ((Integer)listOne.get(2)).intValue()); + + assertEquals(4, listTwo.size()); + assertEquals(0, ((Integer)listTwo.get(0)).intValue()); + assertEquals(2, ((Integer)listTwo.get(1)).intValue()); + assertEquals(3, ((Integer)listTwo.get(2)).intValue()); + assertEquals(4, ((Integer)listTwo.get(3)).intValue()); + + List listThree = arrayCache.indexesForArrayKey(arrayOne, "Not found"); + assertEquals(0, listThree.size()); + } + +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/SegmentConstantPoolTest.java b/src/test/java/org/apache/harmony/unpack200/tests/SegmentConstantPoolTest.java new file mode 100644 index 0000000..c443d0a --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/SegmentConstantPoolTest.java @@ -0,0 +1,121 @@ +/* + * 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.unpack200.tests; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.CpBands; +import org.apache.harmony.unpack200.Segment; +import org.apache.harmony.unpack200.SegmentConstantPool; + +/** + * Tests for org.apache.harmony.unpack200.SegmentConstantPool. + */ +public class SegmentConstantPoolTest extends TestCase { + + public class MockSegmentConstantPool extends SegmentConstantPool { + + public MockSegmentConstantPool() { + super(new CpBands(new Segment())); + }; + + public int matchSpecificPoolEntryIndex(String[] classNameArray, + String desiredClassName, int desiredIndex) { + return super.matchSpecificPoolEntryIndex(classNameArray, + desiredClassName, desiredIndex); + }; + + public int matchSpecificPoolEntryIndex(String[] classNameArray, + String[] methodNameArray, String desiredClassName, + String desiredMethodRegex, int desiredIndex) { + return super.matchSpecificPoolEntryIndex(classNameArray, + methodNameArray, desiredClassName, desiredMethodRegex, + desiredIndex); + }; + + public boolean regexMatchesVisible(String regexString, + String compareString) { + return SegmentConstantPool.regexMatches(regexString, compareString); + } + } + + String[] testClassArray = { "Object", "Object", "java/lang/String", + "java/lang/String", "Object", "Other" }; + String[] testMethodArray = { "()", "clone()", "equals()", "", + "isNull()", "Other" }; + + public void testMatchSpecificPoolEntryIndex_SingleArray() throws Exception { + MockSegmentConstantPool mockInstance = new MockSegmentConstantPool(); + // Elements should be found at the proper position. + assertEquals(0, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, "Object", 0)); + assertEquals(1, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, "Object", 1)); + assertEquals(2, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, "java/lang/String", 0)); + assertEquals(3, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, "java/lang/String", 1)); + assertEquals(4, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, "Object", 2)); + assertEquals(5, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, "Other", 0)); + + // Elements that don't exist shouldn't be found + assertEquals(-1, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, "NotThere", 0)); + + // Elements that exist but don't have the requisite number + // of hits shouldn't be found. + assertEquals(-1, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, "java/lang/String", 2)); + } + + public void testMatchSpecificPoolEntryIndex_DoubleArray() throws Exception { + MockSegmentConstantPool mockInstance = new MockSegmentConstantPool(); + // Elements should be found at the proper position. + assertEquals(0, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, testMethodArray, "Object", "^.*", 0)); + assertEquals(2, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, testMethodArray, "java/lang/String", ".*", 0)); + assertEquals(3, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, testMethodArray, "java/lang/String", + "^.*", 0)); + assertEquals(5, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, testMethodArray, "Other", ".*", 0)); + + // Elements that don't exist shouldn't be found + assertEquals(-1, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, testMethodArray, "NotThere", "^.*", 0)); + + // Elements that exist but don't have the requisite number + // of hits shouldn't be found. + assertEquals(-1, mockInstance.matchSpecificPoolEntryIndex( + testClassArray, testMethodArray, "java/lang/String", + "^.*", 1)); + } + + public void testRegexReplacement() { + MockSegmentConstantPool mockPool = new MockSegmentConstantPool(); + assertTrue(mockPool.regexMatchesVisible(".*", "anything")); + assertTrue(mockPool.regexMatchesVisible(".*", "")); + assertTrue(mockPool.regexMatchesVisible("^.*", "")); + assertTrue(mockPool.regexMatchesVisible("^.*", "stuff")); + assertFalse(mockPool.regexMatchesVisible("^.*", "init>stuff")); + assertFalse(mockPool.regexMatchesVisible("^.*", ".*", "")); + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/harmony/unpack200/tests/SegmentOptionsTest.java b/src/test/java/org/apache/harmony/unpack200/tests/SegmentOptionsTest.java new file mode 100644 index 0000000..30c36af --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/SegmentOptionsTest.java @@ -0,0 +1,43 @@ +/* + * 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.unpack200.tests; + +import junit.framework.TestCase; + +import org.apache.harmony.pack200.Pack200Exception; +import org.apache.harmony.unpack200.SegmentOptions; + +/** + * + */ +public class SegmentOptionsTest extends TestCase { + + public void testUnused() { + int[] unused = new int[] { 3, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31 }; + for (int i = 0; i < unused.length; i++) { + try { + new SegmentOptions(1 << unused[i]); + fail("Bit " + + unused[i] + + " should be unused, but it's not caught during construction"); + } catch (Pack200Exception e) { + assertTrue(true); + } + } + } +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/SegmentTest.java b/src/test/java/org/apache/harmony/unpack200/tests/SegmentTest.java new file mode 100644 index 0000000..25c7c24 --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/SegmentTest.java @@ -0,0 +1,119 @@ +/* + * 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.unpack200.tests; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.Segment; + +/** + * Tests for org.apache.harmony.unpack200.Segment. + */ +public class SegmentTest extends TestCase { + + InputStream in; + JarOutputStream out; + File file; + + protected void tearDown() throws Exception { + super.tearDown(); + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + try { + if (out != null) { + out.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + file.delete(); + } + + public void testJustResources() throws Exception { + in = Segment.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/JustResources.pack"); + file = File.createTempFile("just", "resources.jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Segment segment = new Segment(); + segment.unpack(in, out); + } + + public void testInterfaceOnly() throws Exception { + in = Segment.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/InterfaceOnly.pack"); + file = File.createTempFile("Interface", "Only.jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Segment segment = new Segment(); + segment.unpack(in, out); + } + + public void testHelloWorld() throws Exception { + in = Segment.class + .getResourceAsStream("/org/apache/harmony/pack200/tests/HelloWorld.pack"); + file = File.createTempFile("hello", "world.jar"); + out = new JarOutputStream(new FileOutputStream(file)); + Segment segment = new Segment(); + segment.unpack(in, out); + out.close(); + out = null; + JarFile jarFile = new JarFile(file); + file.deleteOnExit(); + + JarEntry entry = jarFile + .getJarEntry("org/apache/harmony/archive/tests/internal/pack200/HelloWorld.class"); + assertNotNull(entry); + InputStream ours = jarFile.getInputStream(entry); + + JarFile jarFile2 = new JarFile(new File(Segment.class.getResource( + "/org/apache/harmony/pack200/tests/hw.jar").toURI())); + JarEntry entry2 = jarFile2 + .getJarEntry("org/apache/harmony/archive/tests/internal/pack200/HelloWorld.class"); + assertNotNull(entry2); + + InputStream expected = jarFile2.getInputStream(entry2); + + BufferedReader reader1 = new BufferedReader(new InputStreamReader(ours)); + BufferedReader reader2 = new BufferedReader(new InputStreamReader(expected)); + String line1 = reader1.readLine(); + String line2 = reader2.readLine(); + int i = 1; + while (line1 != null || line2 != null) { + assertEquals("Unpacked class files differ", line2, line1); + line1 = reader1.readLine(); + line2 = reader2.readLine(); + i++; + } + reader1.close(); + reader2.close(); + } + +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/SegmentUtilsTest.java b/src/test/java/org/apache/harmony/unpack200/tests/SegmentUtilsTest.java new file mode 100644 index 0000000..caf304e --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/SegmentUtilsTest.java @@ -0,0 +1,82 @@ +/* + * 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.unpack200.tests; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.IMatcher; +import org.apache.harmony.unpack200.SegmentUtils; + +public class SegmentUtilsTest extends TestCase { + + private static class MultipleMatches implements IMatcher { + + private final int divisor; + + public MultipleMatches(int divisor) { + this.divisor = divisor; + } + + public boolean matches(long value) { + return value % divisor == 0; + } + + } + + public static final IMatcher even = new MultipleMatches(2); + public static final IMatcher five = new MultipleMatches(5); + + public void testCountArgs() { + assertEquals(0, SegmentUtils.countArgs("()V")); + assertEquals(1, SegmentUtils.countArgs("(D)V")); + assertEquals(1, SegmentUtils.countArgs("([D)V")); + assertEquals(1, SegmentUtils.countArgs("([[D)V")); + assertEquals(2, SegmentUtils.countArgs("(DD)V")); + assertEquals(3, SegmentUtils.countArgs("(DDD)V")); + assertEquals(2, SegmentUtils.countArgs("(Lblah/blah;D)V")); + assertEquals(3, SegmentUtils.countArgs("(Lblah/blah;DLbLah;)V")); + } + + public void testCountInvokeInterfaceArgs() { + assertEquals(1, SegmentUtils.countInvokeInterfaceArgs("(Z)V")); + assertEquals(2, SegmentUtils.countInvokeInterfaceArgs("(D)V")); + assertEquals(2, SegmentUtils.countInvokeInterfaceArgs("(J)V")); + assertEquals(1, SegmentUtils.countInvokeInterfaceArgs("([D)V")); + assertEquals(1, SegmentUtils.countInvokeInterfaceArgs("([[D)V")); + assertEquals(4, SegmentUtils.countInvokeInterfaceArgs("(DD)V")); + assertEquals(3, SegmentUtils + .countInvokeInterfaceArgs("(Lblah/blah;D)V")); + assertEquals(4, SegmentUtils + .countInvokeInterfaceArgs("(Lblah/blah;DLbLah;)V")); + assertEquals(4, SegmentUtils + .countInvokeInterfaceArgs("([Lblah/blah;DLbLah;)V")); + } + + public void testMatches() { + long[] oneToTen = new long[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + assertEquals(6, SegmentUtils.countMatches(new long[][] { oneToTen, + new long[] { 5, 6, 7 } }, even)); + assertEquals(5, SegmentUtils.countMatches(new long[][] { oneToTen }, + even)); + assertEquals(5, SegmentUtils.countMatches(oneToTen, even)); + assertEquals(3, SegmentUtils.countMatches(new long[][] { oneToTen, + new long[] { 5, 6, 7 } }, five)); + assertEquals(2, SegmentUtils.countMatches(new long[][] { oneToTen }, + five)); + assertEquals(2, SegmentUtils.countMatches(oneToTen, five)); + } +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/bytecode/ByteCodeTest.java b/src/test/java/org/apache/harmony/unpack200/tests/bytecode/ByteCodeTest.java new file mode 100644 index 0000000..3d3e33f --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/bytecode/ByteCodeTest.java @@ -0,0 +1,30 @@ +/* + * 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.unpack200.tests.bytecode; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.bytecode.ByteCode; + +public class ByteCodeTest extends TestCase { + + public void testByteCode() { + assertEquals("nop", ByteCode.getByteCode(0).getName()); + assertEquals("return", ByteCode.getByteCode(-79).getName()); + assertEquals("return", ByteCode.getByteCode(177).getName()); + } +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/bytecode/ClassFileEntryTest.java b/src/test/java/org/apache/harmony/unpack200/tests/bytecode/ClassFileEntryTest.java new file mode 100644 index 0000000..bbe808a --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/bytecode/ClassFileEntryTest.java @@ -0,0 +1,108 @@ +/* + * 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.unpack200.tests.bytecode; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.bytecode.CPDouble; +import org.apache.harmony.unpack200.bytecode.CPFloat; +import org.apache.harmony.unpack200.bytecode.CPInteger; +import org.apache.harmony.unpack200.bytecode.CPLong; +import org.apache.harmony.unpack200.bytecode.CPMember; +import org.apache.harmony.unpack200.bytecode.CPString; +import org.apache.harmony.unpack200.bytecode.CPUTF8; +import org.apache.harmony.unpack200.bytecode.SourceFileAttribute; + +public class ClassFileEntryTest extends TestCase { + + public void testUTF8() { + CPUTF8 u1 = new CPUTF8(new String("thing"), 1); //$NON-NLS-1$ + CPUTF8 u2 = new CPUTF8(new String("thing"), 1); //$NON-NLS-1$ + CPUTF8 u3 = new CPUTF8(new String("otherthing"), 2); //$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); + assertFalse(equal1.equals(unequal)); + assertFalse(equal2.equals(unequal)); + assertFalse(unequal.equals(equal1)); + assertFalse(unequal.equals(equal2)); + } + + public void testSourceAttribute() { + SourceFileAttribute sfa1 = new SourceFileAttribute(new CPUTF8( + new String("Thing.java"), 1)); //$NON-NLS-1$ + SourceFileAttribute sfa2 = new SourceFileAttribute(new CPUTF8( + new String("Thing.java"), 1)); //$NON-NLS-1$ + SourceFileAttribute sfa3 = new SourceFileAttribute(new CPUTF8( + new String("OtherThing.java"), 2)); //$NON-NLS-1$ + checkEquality(sfa1, sfa2, "Thing.java", sfa3); //$NON-NLS-1$ + } + + public void testCPInteger() { + CPInteger cp1 = new CPInteger(new Integer(3), 3); + CPInteger cp2 = new CPInteger(new Integer(3), 3); + CPInteger cp3 = new CPInteger(new Integer(5), 5); + checkEquality(cp1, cp2, "3", cp3); //$NON-NLS-1$ + } + + public void testCPLong() { + CPLong cp1 = new CPLong(new Long(3), 3); + CPLong cp2 = new CPLong(new Long(3), 3); + CPLong cp3 = new CPLong(new Long(5), 5); + checkEquality(cp1, cp2, "3", cp3); //$NON-NLS-1$ + } + + public void testCPDouble() { + CPDouble cp1 = new CPDouble(new Double(3), 3); + CPDouble cp2 = new CPDouble(new Double(3), 3); + CPDouble cp3 = new CPDouble(new Double(5), 5); + checkEquality(cp1, cp2, "3", cp3); //$NON-NLS-1$ + } + + public void testCPFloat() { + CPFloat cp1 = new CPFloat(new Float(3), 3); + CPFloat cp2 = new CPFloat(new Float(3), 3); + CPFloat cp3 = new CPFloat(new Float(5), 5); + checkEquality(cp1, cp2, "3", cp3); //$NON-NLS-1$ + } + + public void testCPString() { + CPString cp1 = new CPString(new CPUTF8(new String("3"), 3), 3); + CPString cp2 = new CPString(new CPUTF8(new String("3"), 3), 3); + CPString cp3 = new CPString(new CPUTF8(new String("5"), 5), 5); + checkEquality(cp1, cp2, "3", cp3); //$NON-NLS-1$ + } + + public void testCPField() { + CPMember cp1 = new CPMember(new CPUTF8("Name", 3), new CPUTF8("I", 4), + 0, null); + CPMember cp2 = new CPMember(new CPUTF8("Name", 3), new CPUTF8("I", 4), + 0, null); + CPMember cp3 = new CPMember(new CPUTF8("Name", 3), new CPUTF8("Z", 5), + 0, null); + CPMember cp4 = new CPMember(new CPUTF8("GName", 6), new CPUTF8("I", 4), + 0, null); + checkEquality(cp1, cp2, "Name", cp3); //$NON-NLS-1$ + checkEquality(cp1, cp2, "I", cp4); //$NON-NLS-1$ + } + +} diff --git a/src/test/java/org/apache/harmony/unpack200/tests/bytecode/ConstantPoolTest.java b/src/test/java/org/apache/harmony/unpack200/tests/bytecode/ConstantPoolTest.java new file mode 100644 index 0000000..283d3e9 --- /dev/null +++ b/src/test/java/org/apache/harmony/unpack200/tests/bytecode/ConstantPoolTest.java @@ -0,0 +1,69 @@ +/* + * 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.unpack200.tests.bytecode; + +import junit.framework.TestCase; + +import org.apache.harmony.unpack200.Segment; +import org.apache.harmony.unpack200.bytecode.CPClass; +import org.apache.harmony.unpack200.bytecode.CPMember; +import org.apache.harmony.unpack200.bytecode.CPUTF8; +import org.apache.harmony.unpack200.bytecode.ClassConstantPool; + +public class ConstantPoolTest extends TestCase { + + private ClassConstantPool pool; + + public void setUp() { + pool = new ClassConstantPool(); + } + + public void testDuplicateUTF8() { + CPUTF8 u1 = new CPUTF8("thing", 1); + CPUTF8 u2 = new CPUTF8("thing", 1); + pool.add(u1); + pool.add(u2); + assertEquals(1, pool.size()); + } + + public void testDuplicateField() { + CPMember cp1 = new CPMember(new CPUTF8("name", 1), new CPUTF8("I", 2), + 0, null); + pool.add(cp1); + pool.addNestedEntries(); + assertEquals(2, pool.size()); + CPMember cp2 = new CPMember(new CPUTF8("name", 1), new CPUTF8("I", 2), + 0, null); + pool.add(cp2); + pool.addNestedEntries(); + assertEquals(2, pool.size()); + } + + public void testIndex() { + pool.add(new CPUTF8("OtherThing", 1)); + CPUTF8 u1 = new CPUTF8("thing", 2); + pool.add(u1); + pool.resolve(new Segment()); + assertTrue(pool.indexOf(u1) > 0); + } + + public void testEntries() { + pool.add(new CPClass(new CPUTF8("RandomClass", 1), 10)); + pool.add(new CPClass(new CPUTF8("RandomClass2", 2), 20)); + assertEquals(2, pool.entries().size()); + } +} diff --git a/src/test/resources/org/apache/harmony/pack200/tests/HelloWorld.pack b/src/test/resources/org/apache/harmony/pack200/tests/HelloWorld.pack new file mode 100644 index 0000000..7445d85 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/HelloWorld.pack differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/InterfaceOnly.pack b/src/test/resources/org/apache/harmony/pack200/tests/InterfaceOnly.pack new file mode 100644 index 0000000..a2e0770 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/InterfaceOnly.pack differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/JustResources.pack b/src/test/resources/org/apache/harmony/pack200/tests/JustResources.pack new file mode 100755 index 0000000..3691467 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/JustResources.pack differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/JustResources.pack.gz b/src/test/resources/org/apache/harmony/pack200/tests/JustResources.pack.gz new file mode 100644 index 0000000..3eda6a0 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/JustResources.pack.gz differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/LargeClass.pack.gz b/src/test/resources/org/apache/harmony/pack200/tests/LargeClass.pack.gz new file mode 100644 index 0000000..0504773 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/LargeClass.pack.gz differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/annotations.pack.gz b/src/test/resources/org/apache/harmony/pack200/tests/annotations.pack.gz new file mode 100644 index 0000000..ac7bb9e Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/annotations.pack.gz differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/annotationsUnpacked.jar b/src/test/resources/org/apache/harmony/pack200/tests/annotationsUnpacked.jar new file mode 100644 index 0000000..0476d2f Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/annotationsUnpacked.jar differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/hw.jar b/src/test/resources/org/apache/harmony/pack200/tests/hw.jar new file mode 100644 index 0000000..ab58dd5 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/hw.jar differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/jars/ant.jar b/src/test/resources/org/apache/harmony/pack200/tests/jars/ant.jar new file mode 100644 index 0000000..cf01339 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/jars/ant.jar differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/jndi-e1.pack.gz b/src/test/resources/org/apache/harmony/pack200/tests/jndi-e1.pack.gz new file mode 100644 index 0000000..f88af2b Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/jndi-e1.pack.gz differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/jndi.jar b/src/test/resources/org/apache/harmony/pack200/tests/jndi.jar new file mode 100644 index 0000000..b5cc329 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/jndi.jar differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/jndiUnpacked.jar b/src/test/resources/org/apache/harmony/pack200/tests/jndiUnpacked.jar new file mode 100644 index 0000000..2fbd397 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/jndiUnpacked.jar differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/largeClassUnpacked.jar b/src/test/resources/org/apache/harmony/pack200/tests/largeClassUnpacked.jar new file mode 100644 index 0000000..1acefe9 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/largeClassUnpacked.jar differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/pack200-e1.pack.gz b/src/test/resources/org/apache/harmony/pack200/tests/pack200-e1.pack.gz new file mode 100644 index 0000000..6a54f8d Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/pack200-e1.pack.gz differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/pack200.pack.gz b/src/test/resources/org/apache/harmony/pack200/tests/pack200.pack.gz new file mode 100644 index 0000000..75f3f3c Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/pack200.pack.gz differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/simple-E0.pack.gz b/src/test/resources/org/apache/harmony/pack200/tests/simple-E0.pack.gz new file mode 100644 index 0000000..35c6050 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/simple-E0.pack.gz differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/sql-e1.pack.gz b/src/test/resources/org/apache/harmony/pack200/tests/sql-e1.pack.gz new file mode 100644 index 0000000..d1c51fc Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/sql-e1.pack.gz differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/sql.pack.gz b/src/test/resources/org/apache/harmony/pack200/tests/sql.pack.gz new file mode 100644 index 0000000..cc7cbc1 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/sql.pack.gz differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/sqlUnpacked.jar b/src/test/resources/org/apache/harmony/pack200/tests/sqlUnpacked.jar new file mode 100644 index 0000000..8472a00 Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/sqlUnpacked.jar differ diff --git a/src/test/resources/org/apache/harmony/pack200/tests/sqlUnpackedNoDebug.jar b/src/test/resources/org/apache/harmony/pack200/tests/sqlUnpackedNoDebug.jar new file mode 100644 index 0000000..9e8a04e Binary files /dev/null and b/src/test/resources/org/apache/harmony/pack200/tests/sqlUnpackedNoDebug.jar differ