Fork of Apache Harmony's Pack200 implementation
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pack200/src/main/java/org/apache/harmony/pack200/Segment.java

724 lines
27 KiB

/*
* 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.Opcodes;
import org.objectweb.asm.Type;
/**
* A Pack200 archive consists of one or more Segments.
*/
public class Segment extends 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;
public Segment() {
super(Opcodes.ASM9);
}
/**
* 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, nonStandardAttributePrototypes);
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...");
int finalNumberOfClasses = classBands.numClassesProcessed();
segmentHeader.setClass_count(finalNumberOfClasses);
cpBands.pack(bandsOutputStream);
if(finalNumberOfClasses > 0) {
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, Attribute[] attributes) 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, attributes, 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();
options.addPassFile(name);
cpBands.addCPUtf8(name);
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 extends MethodVisitor {
public SegmentMethodVisitor() {
super(Opcodes.ASM9);
}
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, boolean isInterface) {
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 <code>AnnotationVisitor</code> to
* visit Annotations found in a class file.
*/
public class SegmentAnnotationVisitor extends 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) {
super(Opcodes.ASM9);
this.context = context;
this.desc = desc;
this.visible = visible;
}
public SegmentAnnotationVisitor(int context) {
super(Opcodes.ASM9);
this.context = context;
}
public SegmentAnnotationVisitor(int context, int parameter,
String desc, boolean visible) {
super(Opcodes.ASM9);
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);
addValueAndTag(value, T, values);
}
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(Opcodes.ASM9) {
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);
addValueAndTag(value, T, values);
}
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 ArrayVisitor(caseArrayN, T, nameRU, values);
}
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);
}
}
public class ArrayVisitor extends AnnotationVisitor {
private int indexInCaseArrayN;
private List caseArrayN;
private List values;
private List nameRU;
private List T;
public ArrayVisitor(List caseArrayN, List T, List nameRU, List values) {
super(Opcodes.ASM9);
this.caseArrayN = caseArrayN;
this.T = T;
this.nameRU = nameRU;
this.values = values;
this.indexInCaseArrayN = caseArrayN.size() - 1;
}
public void visit(String name, Object value) {
Integer numCases = (Integer) caseArrayN.remove(indexInCaseArrayN);
caseArrayN.add(indexInCaseArrayN, new Integer(numCases.intValue() + 1));
if (name == null) {
name = "";
}
addValueAndTag(value, T, values);
}
public AnnotationVisitor visitAnnotation(String arg0,
String arg1) {
throw new RuntimeException("Not yet supported");
}
public AnnotationVisitor visitArray(String name) {
T.add("[");
if (name == null) {
name = "";
}
nameRU.add(name);
caseArrayN.add(new Integer(0));
return new ArrayVisitor(caseArrayN, T, nameRU, values);
}
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");
values.add(desc);
values.add(value);
}
}
/**
* SegmentFieldVisitor implements <code>FieldVisitor</code> to visit the
* metadata relating to fields in a class file.
*/
public class SegmentFieldVisitor extends FieldVisitor {
public SegmentFieldVisitor() {
super(Opcodes.ASM9);
}
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() {
}
}
// helper method for annotation visitors
private void addValueAndTag(Object value, List T, List values) {
if(value instanceof Integer) {
T.add("I");
values.add(value);
} else if (value instanceof Double) {
T.add("D");
values.add(value);
} else if (value instanceof Float) {
T.add("F");
values.add(value);
} else if (value instanceof Long) {
T.add("J");
values.add(value);
} else if (value instanceof Byte) {
T.add("B");
values.add(new Integer(((Byte)value).intValue()));
} else if (value instanceof Character) {
T.add("C");
values.add(new Integer(((Character)value).charValue()));
} else if (value instanceof Short) {
T.add("S");
values.add(new Integer(((Short)value).intValue()));
} else if (value instanceof Boolean) {
T.add("Z");
values.add(new Integer(((Boolean)value).booleanValue() ? 1 : 0));
} else if (value instanceof String) {
T.add("s");
values.add(value);
} else if (value instanceof Type) {
T.add("c");
values.add(((Type)value).toString());
}
}
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 static class PassException extends RuntimeException {
}
}