Segment.java
/*
* 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.commons.compress.harmony.pack200;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.compress.harmony.pack200.Archive.PackingFile;
import org.apache.commons.compress.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 {
public class ArrayVisitor extends AnnotationVisitor {
private final int indexInCaseArrayN;
private final List<Integer> caseArrayN;
private final List<Object> values;
private final List<String> nameRU;
private final List<String> tags;
public ArrayVisitor(final List<Integer> caseArrayN, final List<String> tags, final List<String> nameRU, final List<Object> values) {
super(ASM_API);
this.caseArrayN = caseArrayN;
this.tags = tags;
this.nameRU = nameRU;
this.values = values;
this.indexInCaseArrayN = caseArrayN.size() - 1;
}
@Override
public void visit(String name, final Object value) {
final Integer numCases = caseArrayN.remove(indexInCaseArrayN);
caseArrayN.add(indexInCaseArrayN, Integer.valueOf(numCases.intValue() + 1));
if (name == null) {
name = "";
}
addValueAndTag(value, tags, values);
}
@Override
public AnnotationVisitor visitAnnotation(final String arg0, final String arg1) {
throw new UnsupportedOperationException("Not yet supported");
}
@Override
public AnnotationVisitor visitArray(String name) {
tags.add("[");
if (name == null) {
name = "";
}
nameRU.add(name);
caseArrayN.add(Integer.valueOf(0));
return new ArrayVisitor(caseArrayN, tags, nameRU, values);
}
@Override
public void visitEnd() {
// override to noop
}
@Override
public void visitEnum(final String name, final String desc, final String value) {
final Integer numCases = caseArrayN.remove(caseArrayN.size() - 1);
caseArrayN.add(Integer.valueOf(numCases.intValue() + 1));
tags.add("e");
values.add(desc);
values.add(value);
}
}
/**
* 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 {
private static final long serialVersionUID = 1L;
}
/**
* SegmentAnnotationVisitor implements {@code AnnotationVisitor} 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<String> nameRU = new ArrayList<>();
private final List<String> tags = new ArrayList<>(); // tags
private final List<Object> values = new ArrayList<>();
private final List<Integer> caseArrayN = new ArrayList<>();
private final List<String> nestTypeRS = new ArrayList<>();
private final List<String> nestNameRU = new ArrayList<>();
private final List<Integer> nestPairN = new ArrayList<>();
public SegmentAnnotationVisitor(final int context) {
super(ASM_API);
this.context = context;
}
public SegmentAnnotationVisitor(final int context, final int parameter, final String desc, final boolean visible) {
super(ASM_API);
this.context = context;
this.parameter = parameter;
this.desc = desc;
this.visible = visible;
}
public SegmentAnnotationVisitor(final int context, final String desc, final boolean visible) {
super(ASM_API);
this.context = context;
this.desc = desc;
this.visible = visible;
}
@Override
public void visit(String name, final Object value) {
if (name == null) {
name = "";
}
nameRU.add(name);
addValueAndTag(value, tags, values);
}
@Override
public AnnotationVisitor visitAnnotation(String name, final String desc) {
tags.add("@");
if (name == null) {
name = "";
}
nameRU.add(name);
nestTypeRS.add(desc);
nestPairN.add(Integer.valueOf(0));
return new AnnotationVisitor(context, av) {
@Override
public void visit(final String name, final Object value) {
final Integer numPairs = nestPairN.remove(nestPairN.size() - 1);
nestPairN.add(Integer.valueOf(numPairs.intValue() + 1));
nestNameRU.add(name);
addValueAndTag(value, tags, values);
}
@Override
public AnnotationVisitor visitAnnotation(final String arg0, final String arg1) {
throw new UnsupportedOperationException("Not yet supported");
// return null;
}
@Override
public AnnotationVisitor visitArray(final String arg0) {
throw new UnsupportedOperationException("Not yet supported");
// return null;
}
@Override
public void visitEnd() {
}
@Override
public void visitEnum(final String name, final String desc, final String value) {
final Integer numPairs = nestPairN.remove(nestPairN.size() - 1);
nestPairN.add(Integer.valueOf(numPairs.intValue() + 1));
tags.add("e");
nestNameRU.add(name);
values.add(desc);
values.add(value);
}
};
}
@Override
public AnnotationVisitor visitArray(String name) {
tags.add("[");
if (name == null) {
name = "";
}
nameRU.add(name);
caseArrayN.add(Integer.valueOf(0));
return new ArrayVisitor(caseArrayN, tags, nameRU, values);
}
@Override
public void visitEnd() {
if (desc == null) {
Segment.this.classBands.addAnnotationDefault(nameRU, tags, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
} else if (parameter != -1) {
Segment.this.classBands.addParameterAnnotation(parameter, desc, visible, nameRU, tags, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
} else {
Segment.this.classBands.addAnnotation(context, desc, visible, nameRU, tags, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
}
}
@Override
public void visitEnum(String name, final String desc, final String value) {
tags.add("e");
if (name == null) {
name = "";
}
nameRU.add(name);
values.add(desc);
values.add(value);
}
}
/**
* SegmentFieldVisitor implements {@code FieldVisitor} to visit the metadata relating to fields in a class file.
*/
public class SegmentFieldVisitor extends FieldVisitor {
public SegmentFieldVisitor() {
super(ASM_API);
}
@Override
public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
return new SegmentAnnotationVisitor(MetadataBandGroup.CONTEXT_FIELD, desc, visible);
}
@Override
public void visitAttribute(final Attribute attribute) {
if (attribute.isUnknown()) {
final 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) {
final NewAttribute newAttribute = (NewAttribute) attribute;
if (newAttribute.isUnknown(AttributeDefinitionBands.CONTEXT_FIELD)) {
final 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 IllegalArgumentException("Unexpected attribute encountered: " + attribute.type);
}
}
@Override
public void visitEnd() {
}
}
/**
* 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(ASM_API);
}
@Override
public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
return new SegmentAnnotationVisitor(MetadataBandGroup.CONTEXT_METHOD, desc, visible);
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
return new SegmentAnnotationVisitor(MetadataBandGroup.CONTEXT_METHOD);
}
@Override
public void visitAttribute(final Attribute attribute) {
if (attribute.isUnknown()) {
final 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) {
final NewAttribute newAttribute = (NewAttribute) attribute;
if (attribute.isCodeAttribute()) {
if (newAttribute.isUnknown(AttributeDefinitionBands.CONTEXT_CODE)) {
final 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)) {
final 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 IllegalArgumentException("Unexpected attribute encountered: " + attribute.type);
}
}
@Override
public void visitCode() {
classBands.addCode();
}
@Override
public void visitEnd() {
classBands.endOfMethod();
bcBands.visitEnd();
}
@Override
public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
bcBands.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitFrame(final int arg0, final int arg1, final Object[] arg2, final int arg3, final Object[] arg4) {
// TODO: Java 6 - implement support for this
}
@Override
public void visitIincInsn(final int var, final int increment) {
bcBands.visitIincInsn(var, increment);
}
@Override
public void visitInsn(final int opcode) {
bcBands.visitInsn(opcode);
}
@Override
public void visitIntInsn(final int opcode, final int operand) {
bcBands.visitIntInsn(opcode, operand);
}
@Override
public void visitJumpInsn(final int opcode, final Label label) {
bcBands.visitJumpInsn(opcode, label);
}
@Override
public void visitLabel(final Label label) {
bcBands.visitLabel(label);
}
@Override
public void visitLdcInsn(final Object cst) {
bcBands.visitLdcInsn(cst);
}
@Override
public void visitLineNumber(final int line, final Label start) {
if (!stripDebug) {
classBands.addLineNumber(line, start);
}
}
@Override
public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, final int index) {
if (!stripDebug) {
classBands.addLocalVariable(name, desc, signature, start, end, index);
}
}
@Override
public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
bcBands.visitLookupSwitchInsn(dflt, keys, labels);
}
@Override
public void visitMaxs(final int maxStack, final int maxLocals) {
classBands.addMaxStack(maxStack, maxLocals);
}
@Override
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
bcBands.visitMethodInsn(opcode, owner, name, desc);
}
@Override
public void visitMultiANewArrayInsn(final String desc, final int dimensions) {
bcBands.visitMultiANewArrayInsn(desc, dimensions);
}
@Override
public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) {
return new SegmentAnnotationVisitor(MetadataBandGroup.CONTEXT_METHOD, parameter, desc, visible);
}
@Override
public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) {
bcBands.visitTableSwitchInsn(min, max, dflt, labels);
}
@Override
public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) {
classBands.addHandler(start, end, handler, type);
}
@Override
public void visitTypeInsn(final int opcode, final String type) {
bcBands.visitTypeInsn(opcode, type);
}
@Override
public void visitVarInsn(final int opcode, final int var) {
bcBands.visitVarInsn(opcode, var);
}
}
/** See https://asm.ow2.io/Javadoc/org/objectweb/asm/Opcodes.html#ASM4 */
public static int ASM_API = Opcodes.ASM4;
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(ASM_API);
}
// helper method for annotation visitors
private void addValueAndTag(final Object value, final List<String> tags, final List<Object> values) {
if (value instanceof Integer) {
tags.add("I");
values.add(value);
} else if (value instanceof Double) {
tags.add("D");
values.add(value);
} else if (value instanceof Float) {
tags.add("F");
values.add(value);
} else if (value instanceof Long) {
tags.add("J");
values.add(value);
} else if (value instanceof Byte) {
tags.add("B");
values.add(Integer.valueOf(((Byte) value).intValue()));
} else if (value instanceof Character) {
tags.add("C");
values.add(Integer.valueOf(((Character) value).charValue()));
} else if (value instanceof Short) {
tags.add("S");
values.add(Integer.valueOf(((Short) value).intValue()));
} else if (value instanceof Boolean) {
tags.add("Z");
values.add(Integer.valueOf(((Boolean) value).booleanValue() ? 1 : 0));
} else if (value instanceof String) {
tags.add("s");
values.add(value);
} else if (value instanceof Type) {
tags.add("c");
values.add(((Type) value).toString());
}
}
public AttributeDefinitionBands getAttrBands() {
return attributeDefinitionBands;
}
public ClassBands getClassBands() {
return classBands;
}
public CpBands getCpBands() {
return cpBands;
}
public Pack200ClassReader getCurrentClassReader() {
return currentClassReader;
}
public IcBands getIcBands() {
return icBands;
}
public SegmentHeader getSegmentHeader() {
return segmentHeader;
}
public boolean lastConstantHadWideIndex() {
return currentClassReader.lastConstantHadWideIndex();
}
/**
* 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 segmentUnit TODO
* @param out the OutputStream to write the packed Segment to
* @param options packing options
* @throws IOException If an I/O error occurs.
* @throws Pack200Exception TODO
*/
public void pack(final SegmentUnit segmentUnit, final OutputStream out, final PackingOptions options) throws IOException, Pack200Exception {
this.options = options;
this.stripDebug = options.isStripDebug();
final 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(Boolean.parseBoolean(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.
final ByteArrayOutputStream bandsOutputStream = new ByteArrayOutputStream();
PackingUtils.log("Packing...");
final 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);
final 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 passCurrentClass() {
throw new PassException();
}
private void processClasses(final SegmentUnit segmentUnit, final Attribute[] attributes) throws Pack200Exception {
segmentHeader.setClass_count(segmentUnit.classListSize());
for (final Pack200ClassReader classReader : segmentUnit.getClassList()) {
currentClassReader = classReader;
int flags = 0;
if (stripDebug) {
flags |= ClassReader.SKIP_DEBUG;
}
try {
classReader.accept(this, attributes, flags);
} catch (final PassException pe) {
// Pass this class through as-is rather than packing it
// TODO: probably need to deal with any inner classes
classBands.removeCurrentClass();
final String name = classReader.getFileName();
options.addPassFile(name);
cpBands.addCPUtf8(name);
boolean found = false;
for (final PackingFile file : segmentUnit.getFileList()) {
if (file.getName().equals(name)) {
found = true;
file.setContents(classReader.b);
break;
}
}
if (!found) {
throw new Pack200Exception("Error passing file " + name);
}
}
}
}
@Override
public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
bcBands.setCurrentClass(name, superName);
segmentHeader.addMajorVersion(version);
classBands.addClass(version, access, name, signature, superName, interfaces);
}
@Override
public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
return new SegmentAnnotationVisitor(MetadataBandGroup.CONTEXT_CLASS, desc, visible);
}
@Override
public void visitAttribute(final Attribute attribute) {
if (attribute.isUnknown()) {
final 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) {
final NewAttribute newAttribute = (NewAttribute) attribute;
if (newAttribute.isUnknown(AttributeDefinitionBands.CONTEXT_CLASS)) {
final 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 IllegalArgumentException("Unexpected attribute encountered: " + attribute.type);
}
}
@Override
public void visitEnd() {
classBands.endOfClass();
}
@Override
public FieldVisitor visitField(final int flags, final String name, final String desc, final String signature, final Object value) {
classBands.addField(flags, name, desc, signature, value);
return fieldVisitor;
}
@Override
public void visitInnerClass(final String name, final String outerName, final String innerName, final int flags) {
icBands.addInnerClass(name, outerName, innerName, flags);
}
@Override
public MethodVisitor visitMethod(final int flags, final String name, final String desc, final String signature, final String[] exceptions) {
classBands.addMethod(flags, name, desc, signature, exceptions);
return methodVisitor;
}
@Override
public void visitOuterClass(final String owner, final String name, final String desc) {
classBands.addEnclosingMethod(owner, name, desc);
}
@Override
public void visitSource(final String source, final String debug) {
if (!stripDebug) {
classBands.addSourceFile(source);
}
}
}