diff --git a/src/main/java/net/minecraftforge/gradle/patcher/TaskGenBinPatches.java b/src/main/java/net/minecraftforge/gradle/patcher/TaskGenBinPatches.java index 6093d7e23..d3f2e46b0 100644 --- a/src/main/java/net/minecraftforge/gradle/patcher/TaskGenBinPatches.java +++ b/src/main/java/net/minecraftforge/gradle/patcher/TaskGenBinPatches.java @@ -35,11 +35,11 @@ import java.util.jar.JarFile; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; -import java.util.jar.Pack200; -import java.util.jar.Pack200.Packer; import lzma.streams.LzmaOutputStream; +import net.minecraftforge.gradle.util.pack200.Pack200; +import net.minecraftforge.gradle.util.pack200.Pack200.Packer; import net.minecraftforge.gradle.util.patching.BinPatches; import org.gradle.api.DefaultTask; import org.gradle.api.file.FileCollection; diff --git a/src/main/java/net/minecraftforge/gradle/user/patcherUser/TaskApplyBinPatches.java b/src/main/java/net/minecraftforge/gradle/user/patcherUser/TaskApplyBinPatches.java index 219a7c517..e8a393dc9 100644 --- a/src/main/java/net/minecraftforge/gradle/user/patcherUser/TaskApplyBinPatches.java +++ b/src/main/java/net/minecraftforge/gradle/user/patcherUser/TaskApplyBinPatches.java @@ -32,7 +32,6 @@ import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; -import java.util.jar.Pack200; import java.util.regex.Pattern; import java.util.zip.Adler32; import java.util.zip.ZipEntry; @@ -40,6 +39,7 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; +import net.minecraftforge.gradle.util.pack200.Pack200; import org.gradle.api.file.FileVisitDetails; import org.gradle.api.file.FileVisitor; import org.gradle.api.tasks.InputFile; diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/AdaptiveCoding.java b/src/main/java/net/minecraftforge/gradle/util/pack200/AdaptiveCoding.java new file mode 100644 index 000000000..a60fe36c7 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/AdaptiveCoding.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import static net.minecraftforge.gradle.util.pack200.Constants.*; + +/** + * Adaptive coding. + * See the section "Adaptive Encodings" in the Pack200 spec. + * @author John Rose + */ +class AdaptiveCoding implements CodingMethod { + CodingMethod headCoding; + int headLength; + CodingMethod tailCoding; + + public AdaptiveCoding(int headLength, CodingMethod headCoding, CodingMethod tailCoding) { + assert(isCodableLength(headLength)); + this.headLength = headLength; + this.headCoding = headCoding; + this.tailCoding = tailCoding; + } + + public void setHeadCoding(CodingMethod headCoding) { + this.headCoding = headCoding; + } + public void setHeadLength(int headLength) { + assert(isCodableLength(headLength)); + this.headLength = headLength; + } + public void setTailCoding(CodingMethod tailCoding) { + this.tailCoding = tailCoding; + } + + public boolean isTrivial() { + return headCoding == tailCoding; + } + + // CodingMethod methods. + public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException { + writeArray(this, out, a, start, end); + } + // writeArrayTo must be coded iteratively, not recursively: + private static void writeArray(AdaptiveCoding run, OutputStream out, int[] a, int start, int end) throws IOException { + for (;;) { + int mid = start+run.headLength; + assert(mid <= end); + run.headCoding.writeArrayTo(out, a, start, mid); + start = mid; + if (run.tailCoding instanceof AdaptiveCoding) { + run = (AdaptiveCoding) run.tailCoding; + continue; + } + break; + } + run.tailCoding.writeArrayTo(out, a, start, end); + } + + public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException { + readArray(this, in, a, start, end); + } + private static void readArray(AdaptiveCoding run, InputStream in, int[] a, int start, int end) throws IOException { + for (;;) { + int mid = start+run.headLength; + assert(mid <= end); + run.headCoding.readArrayFrom(in, a, start, mid); + start = mid; + if (run.tailCoding instanceof AdaptiveCoding) { + run = (AdaptiveCoding) run.tailCoding; + continue; + } + break; + } + run.tailCoding.readArrayFrom(in, a, start, end); + } + + public static final int KX_MIN = 0; + public static final int KX_MAX = 3; + public static final int KX_LG2BASE = 4; + public static final int KX_BASE = 16; + + public static final int KB_MIN = 0x00; + public static final int KB_MAX = 0xFF; + public static final int KB_OFFSET = 1; + public static final int KB_DEFAULT = 3; + + static int getKXOf(int K) { + for (int KX = KX_MIN; KX <= KX_MAX; KX++) { + if (((K - KB_OFFSET) & ~KB_MAX) == 0) + return KX; + K >>>= KX_LG2BASE; + } + return -1; + } + + static int getKBOf(int K) { + int KX = getKXOf(K); + if (KX < 0) return -1; + K >>>= (KX * KX_LG2BASE); + return K-1; + } + + static int decodeK(int KX, int KB) { + assert(KX_MIN <= KX && KX <= KX_MAX); + assert(KB_MIN <= KB && KB <= KB_MAX); + return (KB+KB_OFFSET) << (KX * KX_LG2BASE); + } + + static int getNextK(int K) { + if (K <= 0) return 1; // 1st K value + int KX = getKXOf(K); + if (KX < 0) return Integer.MAX_VALUE; + // This is the increment we expect to apply: + int unit = 1 << (KX * KX_LG2BASE); + int mask = KB_MAX << (KX * KX_LG2BASE); + int K1 = K + unit; + K1 &= ~(unit-1); // cut off stray low-order bits + if (((K1 - unit) & ~mask) == 0) { + assert(getKXOf(K1) == KX); + return K1; + } + if (KX == KX_MAX) return Integer.MAX_VALUE; + KX += 1; + int mask2 = KB_MAX << (KX * KX_LG2BASE); + K1 |= (mask & ~mask2); + K1 += unit; + assert(getKXOf(K1) == KX); + return K1; + } + + // Is K of the form ((KB:[0..255])+1) * 16^(KX:{0..3])? + public static boolean isCodableLength(int K) { + int KX = getKXOf(K); + if (KX < 0) return false; + int unit = 1 << (KX * KX_LG2BASE); + int mask = KB_MAX << (KX * KX_LG2BASE); + return ((K - unit) & ~mask) == 0; + } + + public byte[] getMetaCoding(Coding dflt) { + //assert(!isTrivial()); // can happen + // See the isCodableLength restriction in CodingChooser. + ByteArrayOutputStream bytes = new ByteArrayOutputStream(10); + try { + makeMetaCoding(this, dflt, bytes); + } catch (IOException ee) { + throw new RuntimeException(ee); + } + return bytes.toByteArray(); + } + private static void makeMetaCoding(AdaptiveCoding run, Coding dflt, + ByteArrayOutputStream bytes) + throws IOException { + for (;;) { + CodingMethod headCoding = run.headCoding; + int headLength = run.headLength; + CodingMethod tailCoding = run.tailCoding; + int K = headLength; + assert(isCodableLength(K)); + int ADef = (headCoding == dflt)?1:0; + int BDef = (tailCoding == dflt)?1:0; + if (ADef+BDef > 1) BDef = 0; // arbitrary choice + int ABDef = 1*ADef + 2*BDef; + assert(ABDef < 3); + int KX = getKXOf(K); + int KB = getKBOf(K); + assert(decodeK(KX, KB) == K); + int KBFlag = (KB != KB_DEFAULT)?1:0; + bytes.write(_meta_run + KX + 4*KBFlag + 8*ABDef); + if (KBFlag != 0) bytes.write(KB); + if (ADef == 0) bytes.write(headCoding.getMetaCoding(dflt)); + if (tailCoding instanceof AdaptiveCoding) { + run = (AdaptiveCoding) tailCoding; + continue; // tail call, to avoid deep stack recursion + } + if (BDef == 0) bytes.write(tailCoding.getMetaCoding(dflt)); + break; + } + } + public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod res[]) { + int op = bytes[pos++] & 0xFF; + if (op < _meta_run || op >= _meta_pop) return pos-1; // backup + AdaptiveCoding prevc = null; + for (boolean keepGoing = true; keepGoing; ) { + keepGoing = false; + assert(op >= _meta_run); + op -= _meta_run; + int KX = op % 4; + int KBFlag = (op / 4) % 2; + int ABDef = (op / 8); + assert(ABDef < 3); + int ADef = (ABDef & 1); + int BDef = (ABDef & 2); + CodingMethod[] ACode = {dflt}, BCode = {dflt}; + int KB = KB_DEFAULT; + if (KBFlag != 0) + KB = bytes[pos++] & 0xFF; + if (ADef == 0) { + pos = BandStructure.parseMetaCoding(bytes, pos, dflt, ACode); + } + if (BDef == 0 && + ((op = bytes[pos] & 0xFF) >= _meta_run) && op < _meta_pop) { + pos++; + keepGoing = true; + } else if (BDef == 0) { + pos = BandStructure.parseMetaCoding(bytes, pos, dflt, BCode); + } + AdaptiveCoding newc = new AdaptiveCoding(decodeK(KX, KB), + ACode[0], BCode[0]); + if (prevc == null) { + res[0] = newc; + } else { + prevc.tailCoding = newc; + } + prevc = newc; + } + return pos; + } + + private String keyString(CodingMethod m) { + if (m instanceof Coding) + return ((Coding)m).keyString(); + return m.toString(); + } + public String toString() { + StringBuilder res = new StringBuilder(20); + AdaptiveCoding run = this; + res.append("run("); + for (;;) { + res.append(run.headLength).append("*"); + res.append(keyString(run.headCoding)); + if (run.tailCoding instanceof AdaptiveCoding) { + run = (AdaptiveCoding) run.tailCoding; + res.append(" "); + continue; + } + break; + } + res.append(" **").append(keyString(run.tailCoding)); + res.append(")"); + return res.toString(); + } + +/* + public static void main(String av[]) { + int[][] samples = { + {1,2,3,4,5}, + {254,255,256,256+1*16,256+2*16}, + {0xfd,0xfe,0xff,0x100,0x110,0x120,0x130}, + {0xfd0,0xfe0,0xff0,0x1000,0x1100,0x1200,0x1300}, + {0xfd00,0xfe00,0xff00,0x10000,0x11000,0x12000,0x13000}, + {0xfd000,0xfe000,0xff000,0x100000} + }; + for (int i = 0; i < samples.length; i++) { + for (int j = 0; j < samples[i].length; j++) { + int K = samples[i][j]; + int KX = getKXOf(K); + int KB = getKBOf(K); + System.out.println("K="+Integer.toHexString(K)+ + " KX="+KX+" KB="+KB); + assert(isCodableLength(K)); + assert(K == decodeK(KX, KB)); + if (j == 0) continue; + int K1 = samples[i][j-1]; + assert(K == getNextK(K1)); + } + } + } +//*/ + +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/Attribute.java b/src/main/java/net/minecraftforge/gradle/util/pack200/Attribute.java new file mode 100644 index 000000000..5cada39f2 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/Attribute.java @@ -0,0 +1,1698 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import net.minecraftforge.gradle.util.pack200.ConstantPool.Entry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.Index; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import static net.minecraftforge.gradle.util.pack200.Constants.*; + +/** + * Represents an attribute in a class-file. + * Takes care to remember where constant pool indexes occur. + * Implements the "little language" of Pack200 for describing + * attribute layouts. + * @author John Rose + */ +class Attribute implements Comparable { + // Attribute instance fields. + + Layout def; // the name and format of this attr + byte[] bytes; // the actual bytes + Object fixups; // reference relocations, if any are required + + public String name() { return def.name(); } + public Layout layout() { return def; } + public byte[] bytes() { return bytes; } + public int size() { return bytes.length; } + public Entry getNameRef() { return def.getNameRef(); } + + private Attribute(Attribute old) { + this.def = old.def; + this.bytes = old.bytes; + this.fixups = old.fixups; + } + + public Attribute(Layout def, byte[] bytes, Object fixups) { + this.def = def; + this.bytes = bytes; + this.fixups = fixups; + Fixups.setBytes(fixups, bytes); + } + public Attribute(Layout def, byte[] bytes) { + this(def, bytes, null); + } + + public Attribute addContent(byte[] bytes, Object fixups) { + assert(isCanonical()); + if (bytes.length == 0 && fixups == null) + return this; + Attribute res = new Attribute(this); + res.bytes = bytes; + res.fixups = fixups; + Fixups.setBytes(fixups, bytes); + return res; + } + public Attribute addContent(byte[] bytes) { + return addContent(bytes, null); + } + + public void finishRefs(Index ix) { + if (fixups != null) { + Fixups.finishRefs(fixups, bytes, ix); + fixups = null; + } + } + + public boolean isCanonical() { + return this == def.canon; + } + + @Override + public int compareTo(Attribute that) { + return this.def.compareTo(that.def); + } + + private static final Map, List> canonLists = new HashMap<>(); + private static final Map attributes = new HashMap<>(); + private static final Map standardDefs = new HashMap<>(); + + // Canonicalized lists of trivial attrs (Deprecated, etc.) + // are used by trimToSize, in order to reduce footprint + // of some common cases. (Note that Code attributes are + // always zero size.) + public static List getCanonList(List al) { + synchronized (canonLists) { + List cl = canonLists.get(al); + if (cl == null) { + cl = new ArrayList<>(al.size()); + cl.addAll(al); + cl = Collections.unmodifiableList(cl); + canonLists.put(al, cl); + } + return cl; + } + } + + // Find the canonical empty attribute with the given ctype, name, layout. + public static Attribute find(int ctype, String name, String layout) { + Layout key = Layout.makeKey(ctype, name, layout); + synchronized (attributes) { + Attribute a = attributes.get(key); + if (a == null) { + a = new Layout(ctype, name, layout).canonicalInstance(); + attributes.put(key, a); + } + return a; + } + } + + public static Layout keyForLookup(int ctype, String name) { + return Layout.makeKey(ctype, name); + } + + // Find canonical empty attribute with given ctype and name, + // and with the standard layout. + public static Attribute lookup(Map defs, int ctype, + String name) { + if (defs == null) { + defs = standardDefs; + } + return defs.get(Layout.makeKey(ctype, name)); + } + + public static Attribute define(Map defs, int ctype, + String name, String layout) { + Attribute a = find(ctype, name, layout); + defs.put(Layout.makeKey(ctype, name), a); + return a; + } + + static { + Map sd = standardDefs; + define(sd, ATTR_CONTEXT_CLASS, "Signature", "RSH"); + define(sd, ATTR_CONTEXT_CLASS, "Synthetic", ""); + define(sd, ATTR_CONTEXT_CLASS, "Deprecated", ""); + define(sd, ATTR_CONTEXT_CLASS, "SourceFile", "RUH"); + define(sd, ATTR_CONTEXT_CLASS, "EnclosingMethod", "RCHRDNH"); + define(sd, ATTR_CONTEXT_CLASS, "InnerClasses", "NH[RCHRCNHRUNHFH]"); + define(sd, ATTR_CONTEXT_CLASS, "BootstrapMethods", "NH[RMHNH[KLH]]"); + + define(sd, ATTR_CONTEXT_FIELD, "Signature", "RSH"); + define(sd, ATTR_CONTEXT_FIELD, "Synthetic", ""); + define(sd, ATTR_CONTEXT_FIELD, "Deprecated", ""); + define(sd, ATTR_CONTEXT_FIELD, "ConstantValue", "KQH"); + + define(sd, ATTR_CONTEXT_METHOD, "Signature", "RSH"); + define(sd, ATTR_CONTEXT_METHOD, "Synthetic", ""); + define(sd, ATTR_CONTEXT_METHOD, "Deprecated", ""); + define(sd, ATTR_CONTEXT_METHOD, "Exceptions", "NH[RCH]"); + define(sd, ATTR_CONTEXT_METHOD, "MethodParameters", "NB[RUNHFH]"); + //define(sd, ATTR_CONTEXT_METHOD, "Code", "HHNI[B]NH[PHPOHPOHRCNH]NH[RUHNI[B]]"); + + define(sd, ATTR_CONTEXT_CODE, "StackMapTable", + ("[NH[(1)]]" + + "[TB" + + "(64-127)[(2)]" + + "(247)[(1)(2)]" + + "(248-251)[(1)]" + + "(252)[(1)(2)]" + + "(253)[(1)(2)(2)]" + + "(254)[(1)(2)(2)(2)]" + + "(255)[(1)NH[(2)]NH[(2)]]" + + "()[]" + + "]" + + "[H]" + + "[TB(7)[RCH](8)[PH]()[]]")); + + define(sd, ATTR_CONTEXT_CODE, "LineNumberTable", "NH[PHH]"); + define(sd, ATTR_CONTEXT_CODE, "LocalVariableTable", "NH[PHOHRUHRSHH]"); + define(sd, ATTR_CONTEXT_CODE, "LocalVariableTypeTable", "NH[PHOHRUHRSHH]"); + //define(sd, ATTR_CONTEXT_CODE, "CharacterRangeTable", "NH[PHPOHIIH]"); + //define(sd, ATTR_CONTEXT_CODE, "CoverageTable", "NH[PHHII]"); + + // Note: Code and InnerClasses are special-cased elsewhere. + // Their layout specs. are given here for completeness. + // The Code spec is incomplete, in that it does not distinguish + // bytecode bytes or locate CP references. + // The BootstrapMethods attribute is also special-cased + // elsewhere as an appendix to the local constant pool. + } + + // Metadata. + // + // We define metadata using similar layouts + // for all five kinds of metadata attributes and 2 type metadata attributes + // + // Regular annotations are a counted list of [RSHNH[RUH(1)]][...] + // pack.method.attribute.RuntimeVisibleAnnotations=[NH[(1)]][RSHNH[RUH(1)]][TB...] + // + // Parameter annotations are a counted list of regular annotations. + // pack.method.attribute.RuntimeVisibleParameterAnnotations=[NB[(1)]][NH[(1)]][RSHNH[RUH(1)]][TB...] + // + // RuntimeInvisible annotations are defined similarly... + // Non-method annotations are defined similarly... + // + // Annotation are a simple tagged value [TB...] + // pack.attribute.method.AnnotationDefault=[TB...] + + static { + String mdLayouts[] = { + Attribute.normalizeLayoutString + ("" + +"\n # parameter_annotations :=" + +"\n [ NB[(1)] ] # forward call to annotations" + ), + Attribute.normalizeLayoutString + ("" + +"\n # annotations :=" + +"\n [ NH[(1)] ] # forward call to annotation" + +"\n " + ), + Attribute.normalizeLayoutString + ("" + +"\n # annotation :=" + +"\n [RSH" + +"\n NH[RUH (1)] # forward call to value" + +"\n ]" + ), + Attribute.normalizeLayoutString + ("" + +"\n # value :=" + +"\n [TB # Callable 2 encodes one tagged value." + +"\n (\\B,\\C,\\I,\\S,\\Z)[KIH]" + +"\n (\\D)[KDH]" + +"\n (\\F)[KFH]" + +"\n (\\J)[KJH]" + +"\n (\\c)[RSH]" + +"\n (\\e)[RSH RUH]" + +"\n (\\s)[RUH]" + +"\n (\\[)[NH[(0)]] # backward self-call to value" + +"\n (\\@)[RSH NH[RUH (0)]] # backward self-call to value" + +"\n ()[] ]" + ) + }; + /* + * RuntimeVisibleTypeAnnotation and RuntimeInvisibleTypeAnnotatation are + * similar to RuntimeVisibleAnnotation and RuntimeInvisibleAnnotation, + * a type-annotation union and a type-path structure precedes the + * annotation structure + */ + String typeLayouts[] = { + Attribute.normalizeLayoutString + ("" + +"\n # type-annotations :=" + +"\n [ NH[(1)(2)(3)] ] # forward call to type-annotations" + ), + Attribute.normalizeLayoutString + ( "" + +"\n # type-annotation :=" + +"\n [TB" + +"\n (0-1) [B] # {CLASS, METHOD}_TYPE_PARAMETER" + +"\n (16) [FH] # CLASS_EXTENDS" + +"\n (17-18) [BB] # {CLASS, METHOD}_TYPE_PARAMETER_BOUND" + +"\n (19-21) [] # FIELD, METHOD_RETURN, METHOD_RECEIVER" + +"\n (22) [B] # METHOD_FORMAL_PARAMETER" + +"\n (23) [H] # THROWS" + +"\n (64-65) [NH[PHOHH]] # LOCAL_VARIABLE, RESOURCE_VARIABLE" + +"\n (66) [H] # EXCEPTION_PARAMETER" + +"\n (67-70) [PH] # INSTANCEOF, NEW, {CONSTRUCTOR, METHOD}_REFERENCE_RECEIVER" + +"\n (71-75) [PHB] # CAST, {CONSTRUCTOR,METHOD}_INVOCATION_TYPE_ARGUMENT, {CONSTRUCTOR, METHOD}_REFERENCE_TYPE_ARGUMENT" + +"\n ()[] ]" + ), + Attribute.normalizeLayoutString + ("" + +"\n # type-path" + +"\n [ NB[BB] ]" + ) + }; + Map sd = standardDefs; + String defaultLayout = mdLayouts[3]; + String annotationsLayout = mdLayouts[1] + mdLayouts[2] + mdLayouts[3]; + String paramsLayout = mdLayouts[0] + annotationsLayout; + String typesLayout = typeLayouts[0] + typeLayouts[1] + + typeLayouts[2] + mdLayouts[2] + mdLayouts[3]; + + for (int ctype = 0; ctype < ATTR_CONTEXT_LIMIT; ctype++) { + if (ctype != ATTR_CONTEXT_CODE) { + define(sd, ctype, + "RuntimeVisibleAnnotations", annotationsLayout); + define(sd, ctype, + "RuntimeInvisibleAnnotations", annotationsLayout); + + if (ctype == ATTR_CONTEXT_METHOD) { + define(sd, ctype, + "RuntimeVisibleParameterAnnotations", paramsLayout); + define(sd, ctype, + "RuntimeInvisibleParameterAnnotations", paramsLayout); + define(sd, ctype, + "AnnotationDefault", defaultLayout); + } + } + define(sd, ctype, + "RuntimeVisibleTypeAnnotations", typesLayout); + define(sd, ctype, + "RuntimeInvisibleTypeAnnotations", typesLayout); + } + } + + public static String contextName(int ctype) { + switch (ctype) { + case ATTR_CONTEXT_CLASS: return "class"; + case ATTR_CONTEXT_FIELD: return "field"; + case ATTR_CONTEXT_METHOD: return "method"; + case ATTR_CONTEXT_CODE: return "code"; + } + return null; + } + + /** Base class for any attributed object (Class, Field, Method, Code). + * Flags are included because they are used to help transmit the + * presence of attributes. That is, flags are a mix of modifier + * bits and attribute indicators. + */ + public abstract static + class Holder { + + // We need this abstract method to interpret embedded CP refs. + protected abstract Entry[] getCPMap(); + + protected int flags; // defined here for convenience + protected List attributes; + + public int attributeSize() { + return (attributes == null) ? 0 : attributes.size(); + } + + public void trimToSize() { + if (attributes == null) { + return; + } + if (attributes.isEmpty()) { + attributes = null; + return; + } + if (attributes instanceof ArrayList) { + ArrayList al = (ArrayList)attributes; + al.trimToSize(); + boolean allCanon = true; + for (Attribute a : al) { + if (!a.isCanonical()) { + allCanon = false; + } + if (a.fixups != null) { + assert(!a.isCanonical()); + a.fixups = Fixups.trimToSize(a.fixups); + } + } + if (allCanon) { + // Replace private writable attribute list + // with only trivial entries by public unique + // immutable attribute list with the same entries. + attributes = getCanonList(al); + } + } + } + + public void addAttribute(Attribute a) { + if (attributes == null) + attributes = new ArrayList<>(3); + else if (!(attributes instanceof ArrayList)) + attributes = new ArrayList<>(attributes); // unfreeze it + attributes.add(a); + } + + public Attribute removeAttribute(Attribute a) { + if (attributes == null) return null; + if (!attributes.contains(a)) return null; + if (!(attributes instanceof ArrayList)) + attributes = new ArrayList<>(attributes); // unfreeze it + attributes.remove(a); + return a; + } + + public Attribute getAttribute(int n) { + return attributes.get(n); + } + + protected void visitRefs(int mode, Collection refs) { + if (attributes == null) return; + for (Attribute a : attributes) { + a.visitRefs(this, mode, refs); + } + } + + static final List noAttributes = Arrays.asList(new Attribute[0]); + + public List getAttributes() { + if (attributes == null) + return noAttributes; + return attributes; + } + + public void setAttributes(List attrList) { + if (attrList.isEmpty()) + attributes = null; + else + attributes = attrList; + } + + public Attribute getAttribute(String attrName) { + if (attributes == null) return null; + for (Attribute a : attributes) { + if (a.name().equals(attrName)) + return a; + } + return null; + } + + public Attribute getAttribute(Layout attrDef) { + if (attributes == null) return null; + for (Attribute a : attributes) { + if (a.layout() == attrDef) + return a; + } + return null; + } + + public Attribute removeAttribute(String attrName) { + return removeAttribute(getAttribute(attrName)); + } + + public Attribute removeAttribute(Layout attrDef) { + return removeAttribute(getAttribute(attrDef)); + } + + public void strip(String attrName) { + removeAttribute(getAttribute(attrName)); + } + } + + // Lightweight interface to hide details of band structure. + // Also used for testing. + public abstract static + class ValueStream { + public int getInt(int bandIndex) { throw undef(); } + public void putInt(int bandIndex, int value) { throw undef(); } + public Entry getRef(int bandIndex) { throw undef(); } + public void putRef(int bandIndex, Entry ref) { throw undef(); } + // Note: decodeBCI goes w/ getInt/Ref; encodeBCI goes w/ putInt/Ref + public int decodeBCI(int bciCode) { throw undef(); } + public int encodeBCI(int bci) { throw undef(); } + public void noteBackCall(int whichCallable) { /* ignore by default */ } + private RuntimeException undef() { + return new UnsupportedOperationException("ValueStream method"); + } + } + + // Element kinds: + static final byte EK_INT = 1; // B H I SH etc. + static final byte EK_BCI = 2; // PH POH etc. + static final byte EK_BCO = 3; // OH etc. + static final byte EK_FLAG = 4; // FH etc. + static final byte EK_REPL = 5; // NH[...] etc. + static final byte EK_REF = 6; // RUH, RUNH, KQH, etc. + static final byte EK_UN = 7; // TB(...)[...] etc. + static final byte EK_CASE = 8; // (...)[...] etc. + static final byte EK_CALL = 9; // (0), (1), etc. + static final byte EK_CBLE = 10; // [...][...] etc. + static final byte EF_SIGN = 1<<0; // INT is signed + static final byte EF_DELTA = 1<<1; // BCI/BCI value is diff'ed w/ previous + static final byte EF_NULL = 1<<2; // null REF is expected/allowed + static final byte EF_BACK = 1<<3; // call, callable, case is backward + static final int NO_BAND_INDEX = -1; + + /** A "class" of attributes, characterized by a context-type, name + * and format. The formats are specified in a "little language". + */ + public static + class Layout implements Comparable { + int ctype; // attribute context type, e.g., ATTR_CONTEXT_CODE + String name; // name of attribute + boolean hasRefs; // this kind of attr contains CP refs? + String layout; // layout specification + int bandCount; // total number of elems + Element[] elems; // tokenization of layout + Attribute canon; // canonical instance of this layout + + public int ctype() { return ctype; } + public String name() { return name; } + public String layout() { return layout; } + public Attribute canonicalInstance() { return canon; } + + public Entry getNameRef() { + return ConstantPool.getUtf8Entry(name()); + } + + public boolean isEmpty() { + return layout.isEmpty(); + } + + public Layout(int ctype, String name, String layout) { + this.ctype = ctype; + this.name = name.intern(); + this.layout = layout.intern(); + assert(ctype < ATTR_CONTEXT_LIMIT); + boolean hasCallables = layout.startsWith("["); + try { + if (!hasCallables) { + this.elems = tokenizeLayout(this, -1, layout); + } else { + String[] bodies = splitBodies(layout); + // Make the callables now, so they can be linked immediately. + Element[] lelems = new Element[bodies.length]; + this.elems = lelems; + for (int i = 0; i < lelems.length; i++) { + Element ce = this.new Element(); + ce.kind = EK_CBLE; + ce.removeBand(); + ce.bandIndex = NO_BAND_INDEX; + ce.layout = bodies[i]; + lelems[i] = ce; + } + // Next fill them in. + for (int i = 0; i < lelems.length; i++) { + Element ce = lelems[i]; + ce.body = tokenizeLayout(this, i, bodies[i]); + } + //System.out.println(Arrays.asList(elems)); + } + } catch (StringIndexOutOfBoundsException ee) { + // simplest way to catch syntax errors... + throw new RuntimeException("Bad attribute layout: "+layout, ee); + } + // Some uses do not make a fresh one for each occurrence. + // For example, if layout == "", we only need one attr to share. + canon = new Attribute(this, noBytes); + } + private Layout() {} + static Layout makeKey(int ctype, String name, String layout) { + Layout def = new Layout(); + def.ctype = ctype; + def.name = name.intern(); + def.layout = layout.intern(); + assert(ctype < ATTR_CONTEXT_LIMIT); + return def; + } + static Layout makeKey(int ctype, String name) { + return makeKey(ctype, name, ""); + } + + public Attribute addContent(byte[] bytes, Object fixups) { + return canon.addContent(bytes, fixups); + } + public Attribute addContent(byte[] bytes) { + return canon.addContent(bytes, null); + } + + @Override + public boolean equals(Object x) { + return ( x != null) && ( x.getClass() == Layout.class ) && + equals((Layout)x); + } + public boolean equals(Layout that) { + return this.name.equals(that.name) + && this.layout.equals(that.layout) + && this.ctype == that.ctype; + } + @Override + public int hashCode() { + return (((17 + name.hashCode()) + * 37 + layout.hashCode()) + * 37 + ctype); + } + @Override + public int compareTo(Layout that) { + int r; + r = this.name.compareTo(that.name); + if (r != 0) return r; + r = this.layout.compareTo(that.layout); + if (r != 0) return r; + return this.ctype - that.ctype; + } + @Override + public String toString() { + String str = contextName(ctype)+"."+name+"["+layout+"]"; + // If -ea, print out more informative strings! + assert((str = stringForDebug()) != null); + return str; + } + private String stringForDebug() { + return contextName(ctype)+"."+name+Arrays.asList(elems); + } + + public + class Element { + String layout; // spelling in the little language + byte flags; // EF_SIGN, etc. + byte kind; // EK_UINT, etc. + byte len; // scalar length of element + byte refKind; // CONSTANT_String, etc. + int bandIndex; // which band does this element govern? + int value; // extra parameter + Element[] body; // extra data (for replications, unions, calls) + + boolean flagTest(byte mask) { return (flags & mask) != 0; } + + Element() { + bandIndex = bandCount++; + } + + void removeBand() { + --bandCount; + assert(bandIndex == bandCount); + bandIndex = NO_BAND_INDEX; + } + + public boolean hasBand() { + return bandIndex >= 0; + } + public String toString() { + String str = layout; + // If -ea, print out more informative strings! + assert((str = stringForDebug()) != null); + return str; + } + private String stringForDebug() { + Element[] lbody = this.body; + switch (kind) { + case EK_CALL: + lbody = null; + break; + case EK_CASE: + if (flagTest(EF_BACK)) + lbody = null; + break; + } + return layout + + (!hasBand()?"":"#"+bandIndex) + + "<"+ (flags==0?"":""+flags)+kind+len + + (refKind==0?"":""+refKind) + ">" + + (value==0?"":"("+value+")") + + (lbody==null?"": ""+Arrays.asList(lbody)); + } + } + + public boolean hasCallables() { + return (elems.length > 0 && elems[0].kind == EK_CBLE); + } + private static final Element[] noElems = {}; + public Element[] getCallables() { + if (hasCallables()) { + Element[] nelems = Arrays.copyOf(elems, elems.length); + return nelems; + } else + return noElems; // no callables at all + } + public Element[] getEntryPoint() { + if (hasCallables()) + return elems[0].body; // body of first callable + else { + Element[] nelems = Arrays.copyOf(elems, elems.length); + return nelems; // no callables; whole body + } + } + + /** Return a sequence of tokens from the given attribute bytes. + * Sequence elements will be 1-1 correspondent with my layout tokens. + */ + public void parse(Holder holder, + byte[] bytes, int pos, int len, ValueStream out) { + int end = parseUsing(getEntryPoint(), + holder, bytes, pos, len, out); + if (end != pos + len) + throw new InternalError("layout parsed "+(end-pos)+" out of "+len+" bytes"); + } + /** Given a sequence of tokens, return the attribute bytes. + * Sequence elements must be 1-1 correspondent with my layout tokens. + * The returned object is a cookie for Fixups.finishRefs, which + * must be used to harden any references into integer indexes. + */ + public Object unparse(ValueStream in, ByteArrayOutputStream out) { + Object[] fixups = { null }; + unparseUsing(getEntryPoint(), fixups, in, out); + return fixups[0]; // return ref-bearing cookie, if any + } + + public String layoutForClassVersion(Package.Version vers) { + if (vers.lessThan(JAVA6_MAX_CLASS_VERSION)) { + // Disallow layout syntax in the oldest protocol version. + return expandCaseDashNotation(layout); + } + return layout; + } + } + + public static + class FormatException extends IOException { + private static final long serialVersionUID = -2542243830788066513L; + + private int ctype; + private String name; + String layout; + public FormatException(String message, + int ctype, String name, String layout) { + super(ATTR_CONTEXT_NAME[ctype]+ " attribute \"" + name + "\"" + + (message == null? "" : (": " + message))); + this.ctype = ctype; + this.name = name; + this.layout = layout; + } + public FormatException(String message, + int ctype, String name) { + this(message, ctype, name, null); + } + } + + void visitRefs(Holder holder, int mode, final Collection refs) { + if (mode == VRM_CLASSIC) { + refs.add(getNameRef()); + } + // else the name is owned by the layout, and is processed elsewhere + if (bytes.length == 0) return; // quick exit + if (!def.hasRefs) return; // quick exit + if (fixups != null) { + Fixups.visitRefs(fixups, refs); + return; + } + // References (to a local cpMap) are embedded in the bytes. + def.parse(holder, bytes, 0, bytes.length, + new ValueStream() { + @Override + public void putInt(int bandIndex, int value) { + } + @Override + public void putRef(int bandIndex, Entry ref) { + refs.add(ref); + } + @Override + public int encodeBCI(int bci) { + return bci; + } + }); + } + + public void parse(Holder holder, byte[] bytes, int pos, int len, ValueStream out) { + def.parse(holder, bytes, pos, len, out); + } + public Object unparse(ValueStream in, ByteArrayOutputStream out) { + return def.unparse(in, out); + } + + @Override + public String toString() { + return def + +"{"+(bytes == null ? -1 : size())+"}" + +(fixups == null? "": fixups.toString()); + } + + /** Remove any informal "pretty printing" from the layout string. + * Removes blanks and control chars. + * Removes '#' comments (to end of line). + * Replaces '\c' by the decimal code of the character c. + * Replaces '0xNNN' by the decimal code of the hex number NNN. + */ + public static + String normalizeLayoutString(String layout) { + StringBuilder buf = new StringBuilder(); + for (int i = 0, len = layout.length(); i < len; ) { + char ch = layout.charAt(i++); + if (ch <= ' ') { + // Skip whitespace and control chars + continue; + } else if (ch == '#') { + // Skip to end of line. + int end1 = layout.indexOf('\n', i); + int end2 = layout.indexOf('\r', i); + if (end1 < 0) end1 = len; + if (end2 < 0) end2 = len; + i = Math.min(end1, end2); + } else if (ch == '\\') { + // Map a character reference to its decimal code. + buf.append((int) layout.charAt(i++)); + } else if (ch == '0' && layout.startsWith("0x", i-1)) { + // Map a hex numeral to its decimal code. + int start = i-1; + int end = start+2; + while (end < len) { + int dig = layout.charAt(end); + if ((dig >= '0' && dig <= '9') || + (dig >= 'a' && dig <= 'f')) + ++end; + else + break; + } + if (end > start) { + String num = layout.substring(start, end); + buf.append(Integer.decode(num)); + i = end; + } else { + buf.append(ch); + } + } else { + buf.append(ch); + } + } + String result = buf.toString(); + if (false && !result.equals(layout)) { + Utils.log.info("Normalizing layout string"); + Utils.log.info(" From: "+layout); + Utils.log.info(" To: "+result); + } + return result; + } + + /// Subroutines for parsing and unparsing: + + /** Parse the attribute layout language. +
+  attribute_layout:
+        ( layout_element )* | ( callable )+
+  layout_element:
+        ( integral | replication | union | call | reference )
+
+  callable:
+        '[' body ']'
+  body:
+        ( layout_element )+
+
+  integral:
+        ( unsigned_int | signed_int | bc_index | bc_offset | flag )
+  unsigned_int:
+        uint_type
+  signed_int:
+        'S' uint_type
+  any_int:
+        ( unsigned_int | signed_int )
+  bc_index:
+        ( 'P' uint_type | 'PO' uint_type )
+  bc_offset:
+        'O' any_int
+  flag:
+        'F' uint_type
+  uint_type:
+        ( 'B' | 'H' | 'I' | 'V' )
+
+  replication:
+        'N' uint_type '[' body ']'
+
+  union:
+        'T' any_int (union_case)* '(' ')' '[' (body)? ']'
+  union_case:
+        '(' union_case_tag (',' union_case_tag)* ')' '[' (body)? ']'
+  union_case_tag:
+        ( numeral | numeral '-' numeral )
+  call:
+        '(' numeral ')'
+
+  reference:
+        reference_type ( 'N' )? uint_type
+  reference_type:
+        ( constant_ref | schema_ref | utf8_ref | untyped_ref )
+  constant_ref:
+        ( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' | 'KM' | 'KT' | 'KL' )
+  schema_ref:
+        ( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' | 'RY' | 'RB' | 'RN' )
+  utf8_ref:
+        'RU'
+  untyped_ref:
+        'RQ'
+
+  numeral:
+        '(' ('-')? (digit)+ ')'
+  digit:
+        ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' )
+ 
+ */ + static //private + Layout.Element[] tokenizeLayout(Layout self, int curCble, String layout) { + List col = new ArrayList<>(layout.length()); + tokenizeLayout(self, curCble, layout, col); + Layout.Element[] res = new Layout.Element[col.size()]; + col.toArray(res); + return res; + } + static //private + void tokenizeLayout(Layout self, int curCble, String layout, List col) { + boolean prevBCI = false; + for (int len = layout.length(), i = 0; i < len; ) { + int start = i; + int body; + Layout.Element e = self.new Element(); + byte kind; + //System.out.println("at "+i+": ..."+layout.substring(i)); + // strip a prefix + switch (layout.charAt(i++)) { + /// layout_element: integral + case 'B': case 'H': case 'I': case 'V': // unsigned_int + kind = EK_INT; + --i; // reparse + i = tokenizeUInt(e, layout, i); + break; + case 'S': // signed_int + kind = EK_INT; + --i; // reparse + i = tokenizeSInt(e, layout, i); + break; + case 'P': // bc_index + kind = EK_BCI; + if (layout.charAt(i++) == 'O') { + // bc_index: 'PO' tokenizeUInt + e.flags |= EF_DELTA; + // must follow P or PO: + if (!prevBCI) + { i = -i; continue; } // fail + i++; // move forward + } + --i; // reparse + i = tokenizeUInt(e, layout, i); + break; + case 'O': // bc_offset + kind = EK_BCO; + e.flags |= EF_DELTA; + // must follow P or PO: + if (!prevBCI) + { i = -i; continue; } // fail + i = tokenizeSInt(e, layout, i); + break; + case 'F': // flag + kind = EK_FLAG; + i = tokenizeUInt(e, layout, i); + break; + case 'N': // replication: 'N' uint '[' elem ... ']' + kind = EK_REPL; + i = tokenizeUInt(e, layout, i); + if (layout.charAt(i++) != '[') + { i = -i; continue; } // fail + i = skipBody(layout, body = i); + e.body = tokenizeLayout(self, curCble, + layout.substring(body, i++)); + break; + case 'T': // union: 'T' any_int union_case* '(' ')' '[' body ']' + kind = EK_UN; + i = tokenizeSInt(e, layout, i); + List cases = new ArrayList<>(); + for (;;) { + // Keep parsing cases until we hit the default case. + if (layout.charAt(i++) != '(') + { i = -i; break; } // fail + int beg = i; + i = layout.indexOf(')', i); + String cstr = layout.substring(beg, i++); + int cstrlen = cstr.length(); + if (layout.charAt(i++) != '[') + { i = -i; break; } // fail + // Check for duplication. + if (layout.charAt(i) == ']') + body = i; // missing body, which is legal here + else + i = skipBody(layout, body = i); + Layout.Element[] cbody + = tokenizeLayout(self, curCble, + layout.substring(body, i++)); + if (cstrlen == 0) { + Layout.Element ce = self.new Element(); + ce.body = cbody; + ce.kind = EK_CASE; + ce.removeBand(); + cases.add(ce); + break; // done with the whole union + } else { + // Parse a case string. + boolean firstCaseNum = true; + for (int cp = 0, endp;; cp = endp+1) { + // Look for multiple case tags: + endp = cstr.indexOf(',', cp); + if (endp < 0) endp = cstrlen; + String cstr1 = cstr.substring(cp, endp); + if (cstr1.length() == 0) + cstr1 = "empty"; // will fail parse + int value0, value1; + // Check for a case range (new in 1.6). + int dash = findCaseDash(cstr1, 0); + if (dash >= 0) { + value0 = parseIntBefore(cstr1, dash); + value1 = parseIntAfter(cstr1, dash); + if (value0 >= value1) + { i = -i; break; } // fail + } else { + value0 = value1 = Integer.parseInt(cstr1); + } + // Add a case for each value in value0..value1 + for (;; value0++) { + Layout.Element ce = self.new Element(); + ce.body = cbody; // all cases share one body + ce.kind = EK_CASE; + ce.removeBand(); + if (!firstCaseNum) + // "backward case" repeats a body + ce.flags |= EF_BACK; + firstCaseNum = false; + ce.value = value0; + cases.add(ce); + if (value0 == value1) break; + } + if (endp == cstrlen) { + break; // done with this case + } + } + } + } + e.body = new Layout.Element[cases.size()]; + cases.toArray(e.body); + e.kind = kind; + for (int j = 0; j < e.body.length-1; j++) { + Layout.Element ce = e.body[j]; + if (matchCase(e, ce.value) != ce) { + // Duplicate tag. + { i = -i; break; } // fail + } + } + break; + case '(': // call: '(' '-'? digit+ ')' + kind = EK_CALL; + e.removeBand(); + i = layout.indexOf(')', i); + String cstr = layout.substring(start+1, i++); + int offset = Integer.parseInt(cstr); + int target = curCble + offset; + if (!(offset+"").equals(cstr) || + self.elems == null || + target < 0 || + target >= self.elems.length) + { i = -i; continue; } // fail + Layout.Element ce = self.elems[target]; + assert(ce.kind == EK_CBLE); + e.value = target; + e.body = new Layout.Element[]{ ce }; + // Is it a (recursive) backward call? + if (offset <= 0) { + // Yes. Mark both caller and callee backward. + e.flags |= EF_BACK; + ce.flags |= EF_BACK; + } + break; + case 'K': // reference_type: constant_ref + kind = EK_REF; + switch (layout.charAt(i++)) { + case 'I': e.refKind = CONSTANT_Integer; break; + case 'J': e.refKind = CONSTANT_Long; break; + case 'F': e.refKind = CONSTANT_Float; break; + case 'D': e.refKind = CONSTANT_Double; break; + case 'S': e.refKind = CONSTANT_String; break; + case 'Q': e.refKind = CONSTANT_FieldSpecific; break; + + // new in 1.7: + case 'M': e.refKind = CONSTANT_MethodHandle; break; + case 'T': e.refKind = CONSTANT_MethodType; break; + case 'L': e.refKind = CONSTANT_LoadableValue; break; + default: { i = -i; continue; } // fail + } + break; + case 'R': // schema_ref + kind = EK_REF; + switch (layout.charAt(i++)) { + case 'C': e.refKind = CONSTANT_Class; break; + case 'S': e.refKind = CONSTANT_Signature; break; + case 'D': e.refKind = CONSTANT_NameandType; break; + case 'F': e.refKind = CONSTANT_Fieldref; break; + case 'M': e.refKind = CONSTANT_Methodref; break; + case 'I': e.refKind = CONSTANT_InterfaceMethodref; break; + + case 'U': e.refKind = CONSTANT_Utf8; break; //utf8_ref + case 'Q': e.refKind = CONSTANT_All; break; //untyped_ref + + // new in 1.7: + case 'Y': e.refKind = CONSTANT_InvokeDynamic; break; + case 'B': e.refKind = CONSTANT_BootstrapMethod; break; + case 'N': e.refKind = CONSTANT_AnyMember; break; + + default: { i = -i; continue; } // fail + } + break; + default: { i = -i; continue; } // fail + } + + // further parsing of refs + if (kind == EK_REF) { + // reference: reference_type -><- ( 'N' )? tokenizeUInt + if (layout.charAt(i++) == 'N') { + e.flags |= EF_NULL; + i++; // move forward + } + --i; // reparse + i = tokenizeUInt(e, layout, i); + self.hasRefs = true; + } + + prevBCI = (kind == EK_BCI); + + // store the new element + e.kind = kind; + e.layout = layout.substring(start, i); + col.add(e); + } + } + static //private + String[] splitBodies(String layout) { + List bodies = new ArrayList<>(); + // Parse several independent layout bodies: "[foo][bar]...[baz]" + for (int i = 0; i < layout.length(); i++) { + if (layout.charAt(i++) != '[') + layout.charAt(-i); // throw error + int body; + i = skipBody(layout, body = i); + bodies.add(layout.substring(body, i)); + } + String[] res = new String[bodies.size()]; + bodies.toArray(res); + return res; + } + private static + int skipBody(String layout, int i) { + assert(layout.charAt(i-1) == '['); + if (layout.charAt(i) == ']') + // No empty bodies, please. + return -i; + // skip balanced [...[...]...] + for (int depth = 1; depth > 0; ) { + switch (layout.charAt(i++)) { + case '[': depth++; break; + case ']': depth--; break; + } + } + --i; // get before bracket + assert(layout.charAt(i) == ']'); + return i; // return closing bracket + } + private static + int tokenizeUInt(Layout.Element e, String layout, int i) { + switch (layout.charAt(i++)) { + case 'V': e.len = 0; break; + case 'B': e.len = 1; break; + case 'H': e.len = 2; break; + case 'I': e.len = 4; break; + default: return -i; + } + return i; + } + private static + int tokenizeSInt(Layout.Element e, String layout, int i) { + if (layout.charAt(i) == 'S') { + e.flags |= EF_SIGN; + ++i; + } + return tokenizeUInt(e, layout, i); + } + + private static + boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + + /** Find an occurrence of hyphen '-' between two numerals. */ + static //private + int findCaseDash(String layout, int fromIndex) { + if (fromIndex <= 0) fromIndex = 1; // minimum dash pos + int lastDash = layout.length() - 2; // maximum dash pos + for (;;) { + int dash = layout.indexOf('-', fromIndex); + if (dash < 0 || dash > lastDash) return -1; + if (isDigit(layout.charAt(dash-1))) { + char afterDash = layout.charAt(dash+1); + if (afterDash == '-' && dash+2 < layout.length()) + afterDash = layout.charAt(dash+2); + if (isDigit(afterDash)) { + // matched /[0-9]--?[0-9]/; return position of dash + return dash; + } + } + fromIndex = dash+1; + } + } + static + int parseIntBefore(String layout, int dash) { + int end = dash; + int beg = end; + while (beg > 0 && isDigit(layout.charAt(beg-1))) { + --beg; + } + if (beg == end) return Integer.parseInt("empty"); + // skip backward over a sign + if (beg >= 1 && layout.charAt(beg-1) == '-') --beg; + assert(beg == 0 || !isDigit(layout.charAt(beg-1))); + return Integer.parseInt(layout.substring(beg, end)); + } + static + int parseIntAfter(String layout, int dash) { + int beg = dash+1; + int end = beg; + int limit = layout.length(); + if (end < limit && layout.charAt(end) == '-') ++end; + while (end < limit && isDigit(layout.charAt(end))) { + ++end; + } + if (beg == end) return Integer.parseInt("empty"); + return Integer.parseInt(layout.substring(beg, end)); + } + /** For compatibility with 1.5 pack, expand 1-5 into 1,2,3,4,5. */ + static + String expandCaseDashNotation(String layout) { + int dash = findCaseDash(layout, 0); + if (dash < 0) return layout; // no dashes (the common case) + StringBuilder result = new StringBuilder(layout.length() * 3); + int sofar = 0; // how far have we processed the layout? + for (;;) { + // for each dash, collect everything up to the dash + result.append(layout, sofar, dash); + sofar = dash+1; // skip the dash + // then collect intermediate values + int value0 = parseIntBefore(layout, dash); + int value1 = parseIntAfter(layout, dash); + assert(value0 < value1); + result.append(","); // close off value0 numeral + for (int i = value0+1; i < value1; i++) { + result.append(i); + result.append(","); // close off i numeral + } + dash = findCaseDash(layout, sofar); + if (dash < 0) break; + } + result.append(layout, sofar, layout.length()); // collect the rest + return result.toString(); + } + static { + assert(expandCaseDashNotation("1-5").equals("1,2,3,4,5")); + assert(expandCaseDashNotation("-2--1").equals("-2,-1")); + assert(expandCaseDashNotation("-2-1").equals("-2,-1,0,1")); + assert(expandCaseDashNotation("-1-0").equals("-1,0")); + } + + // Parse attribute bytes, putting values into bands. Returns new pos. + // Used when reading a class file (local refs resolved with local cpMap). + // Also used for ad hoc scanning. + static + int parseUsing(Layout.Element[] elems, Holder holder, + byte[] bytes, int pos, int len, ValueStream out) { + int prevBCI = 0; + int prevRBCI = 0; + int end = pos + len; + int[] buf = { 0 }; // for calls to parseInt, holds 2nd result + for (int i = 0; i < elems.length; i++) { + Layout.Element e = elems[i]; + int bandIndex = e.bandIndex; + int value; + int BCI, RBCI; + switch (e.kind) { + case EK_INT: + pos = parseInt(e, bytes, pos, buf); + value = buf[0]; + out.putInt(bandIndex, value); + break; + case EK_BCI: // PH, POH + pos = parseInt(e, bytes, pos, buf); + BCI = buf[0]; + RBCI = out.encodeBCI(BCI); + if (!e.flagTest(EF_DELTA)) { + // PH: transmit R(bci), store bci + value = RBCI; + } else { + // POH: transmit D(R(bci)), store bci + value = RBCI - prevRBCI; + } + prevBCI = BCI; + prevRBCI = RBCI; + out.putInt(bandIndex, value); + break; + case EK_BCO: // OH + assert(e.flagTest(EF_DELTA)); + // OH: transmit D(R(bci)), store D(bci) + pos = parseInt(e, bytes, pos, buf); + BCI = prevBCI + buf[0]; + RBCI = out.encodeBCI(BCI); + value = RBCI - prevRBCI; + prevBCI = BCI; + prevRBCI = RBCI; + out.putInt(bandIndex, value); + break; + case EK_FLAG: + pos = parseInt(e, bytes, pos, buf); + value = buf[0]; + out.putInt(bandIndex, value); + break; + case EK_REPL: + pos = parseInt(e, bytes, pos, buf); + value = buf[0]; + out.putInt(bandIndex, value); + for (int j = 0; j < value; j++) { + pos = parseUsing(e.body, holder, bytes, pos, end-pos, out); + } + break; // already transmitted the scalar value + case EK_UN: + pos = parseInt(e, bytes, pos, buf); + value = buf[0]; + out.putInt(bandIndex, value); + Layout.Element ce = matchCase(e, value); + pos = parseUsing(ce.body, holder, bytes, pos, end-pos, out); + + break; // already transmitted the scalar value + case EK_CALL: + // Adjust band offset if it is a backward call. + assert(e.body.length == 1); + assert(e.body[0].kind == EK_CBLE); + if (e.flagTest(EF_BACK)) + out.noteBackCall(e.value); + pos = parseUsing(e.body[0].body, holder, bytes, pos, end-pos, out); + break; // no additional scalar value to transmit + case EK_REF: + pos = parseInt(e, bytes, pos, buf); + int localRef = buf[0]; + Entry globalRef; + if (localRef == 0) { + globalRef = null; // N.B. global null reference is -1 + } else { + Entry[] cpMap = holder.getCPMap(); + globalRef = (localRef >= 0 && localRef < cpMap.length + ? cpMap[localRef] + : null); + byte tag = e.refKind; + if (globalRef != null && tag == CONSTANT_Signature + && globalRef.getTag() == CONSTANT_Utf8) { + // Cf. ClassReader.readSignatureRef. + String typeName = globalRef.stringValue(); + globalRef = ConstantPool.getSignatureEntry(typeName); + } + String got = (globalRef == null + ? "invalid CP index" + : "type=" + ConstantPool.tagName(globalRef.tag)); + if (globalRef == null || !globalRef.tagMatches(tag)) { + throw new IllegalArgumentException( + "Bad constant, expected type=" + + ConstantPool.tagName(tag) + " got " + got); + } + } + out.putRef(bandIndex, globalRef); + break; + default: assert(false); + } + } + return pos; + } + + static + Layout.Element matchCase(Layout.Element e, int value) { + assert(e.kind == EK_UN); + int lastj = e.body.length-1; + for (int j = 0; j < lastj; j++) { + Layout.Element ce = e.body[j]; + assert(ce.kind == EK_CASE); + if (value == ce.value) + return ce; + } + return e.body[lastj]; + } + + private static + int parseInt(Layout.Element e, byte[] bytes, int pos, int[] buf) { + int value = 0; + int loBits = e.len * 8; + // Read in big-endian order: + for (int bitPos = loBits; (bitPos -= 8) >= 0; ) { + value += (bytes[pos++] & 0xFF) << bitPos; + } + if (loBits < 32 && e.flagTest(EF_SIGN)) { + // sign-extend subword value + int hiBits = 32 - loBits; + value = (value << hiBits) >> hiBits; + } + buf[0] = value; + return pos; + } + + // Format attribute bytes, drawing values from bands. + // Used when emptying attribute bands into a package model. + // (At that point CP refs. are not yet assigned indexes.) + static + void unparseUsing(Layout.Element[] elems, Object[] fixups, + ValueStream in, ByteArrayOutputStream out) { + int prevBCI = 0; + int prevRBCI = 0; + for (int i = 0; i < elems.length; i++) { + Layout.Element e = elems[i]; + int bandIndex = e.bandIndex; + int value; + int BCI, RBCI; // "RBCI" is R(BCI), BCI's coded representation + switch (e.kind) { + case EK_INT: + value = in.getInt(bandIndex); + unparseInt(e, value, out); + break; + case EK_BCI: // PH, POH + value = in.getInt(bandIndex); + if (!e.flagTest(EF_DELTA)) { + // PH: transmit R(bci), store bci + RBCI = value; + } else { + // POH: transmit D(R(bci)), store bci + RBCI = prevRBCI + value; + } + assert(prevBCI == in.decodeBCI(prevRBCI)); + BCI = in.decodeBCI(RBCI); + unparseInt(e, BCI, out); + prevBCI = BCI; + prevRBCI = RBCI; + break; + case EK_BCO: // OH + value = in.getInt(bandIndex); + assert(e.flagTest(EF_DELTA)); + // OH: transmit D(R(bci)), store D(bci) + assert(prevBCI == in.decodeBCI(prevRBCI)); + RBCI = prevRBCI + value; + BCI = in.decodeBCI(RBCI); + unparseInt(e, BCI - prevBCI, out); + prevBCI = BCI; + prevRBCI = RBCI; + break; + case EK_FLAG: + value = in.getInt(bandIndex); + unparseInt(e, value, out); + break; + case EK_REPL: + value = in.getInt(bandIndex); + unparseInt(e, value, out); + for (int j = 0; j < value; j++) { + unparseUsing(e.body, fixups, in, out); + } + break; + case EK_UN: + value = in.getInt(bandIndex); + unparseInt(e, value, out); + Layout.Element ce = matchCase(e, value); + unparseUsing(ce.body, fixups, in, out); + break; + case EK_CALL: + assert(e.body.length == 1); + assert(e.body[0].kind == EK_CBLE); + unparseUsing(e.body[0].body, fixups, in, out); + break; + case EK_REF: + Entry globalRef = in.getRef(bandIndex); + int localRef; + if (globalRef != null) { + // It's a one-element array, really an lvalue. + fixups[0] = Fixups.addRefWithLoc(fixups[0], out.size(), globalRef); + localRef = 0; // placeholder for fixups + } else { + localRef = 0; // fixed null value + } + unparseInt(e, localRef, out); + break; + default: assert(false); continue; + } + } + } + + private static + void unparseInt(Layout.Element e, int value, ByteArrayOutputStream out) { + int loBits = e.len * 8; + if (loBits == 0) { + // It is not stored at all ('V' layout). + return; + } + if (loBits < 32) { + int hiBits = 32 - loBits; + int codedValue; + if (e.flagTest(EF_SIGN)) + codedValue = (value << hiBits) >> hiBits; + else + codedValue = (value << hiBits) >>> hiBits; + if (codedValue != value) + throw new InternalError("cannot code in "+e.len+" bytes: "+value); + } + // Write in big-endian order: + for (int bitPos = loBits; (bitPos -= 8) >= 0; ) { + out.write((byte)(value >>> bitPos)); + } + } + +/* + /// Testing. + public static void main(String av[]) { + int maxVal = 12; + int iters = 0; + boolean verbose; + int ap = 0; + while (ap < av.length) { + if (!av[ap].startsWith("-")) break; + if (av[ap].startsWith("-m")) + maxVal = Integer.parseInt(av[ap].substring(2)); + else if (av[ap].startsWith("-i")) + iters = Integer.parseInt(av[ap].substring(2)); + else + throw new RuntimeException("Bad option: "+av[ap]); + ap++; + } + verbose = (iters == 0); + if (iters <= 0) iters = 1; + if (ap == av.length) { + av = new String[] { + "HH", // ClassFile.version + "RUH", // SourceFile + "RCHRDNH", // EnclosingMethod + "KQH", // ConstantValue + "NH[RCH]", // Exceptions + "NH[PHH]", // LineNumberTable + "NH[PHOHRUHRSHH]", // LocalVariableTable + "NH[PHPOHIIH]", // CharacterRangeTable + "NH[PHHII]", // CoverageTable + "NH[RCHRCNHRUNHFH]", // InnerClasses + "NH[RMHNH[KLH]]", // BootstrapMethods + "HHNI[B]NH[PHPOHPOHRCNH]NH[RUHNI[B]]", // Code + "=AnnotationDefault", + // Like metadata, but with a compact tag set: + "[NH[(1)]]" + +"[NH[(1)]]" + +"[RSHNH[RUH(1)]]" + +"[TB(0,1,3)[KIH](2)[KDH](5)[KFH](4)[KJH](7)[RSH](8)[RSHRUH](9)[RUH](10)[(-1)](6)[NH[(0)]]()[]]", + "" + }; + ap = 0; + } + Utils.currentInstance.set(new PackerImpl()); + final int[][] counts = new int[2][3]; // int bci ref + final Entry[] cpMap = new Entry[maxVal+1]; + for (int i = 0; i < cpMap.length; i++) { + if (i == 0) continue; // 0 => null + cpMap[i] = ConstantPool.getLiteralEntry(new Integer(i)); + } + Package.Class cls = new Package().new Class(""); + cls.cpMap = cpMap; + class TestValueStream extends ValueStream { + java.util.Random rand = new java.util.Random(0); + ArrayList history = new ArrayList(); + int ckidx = 0; + int maxVal; + boolean verbose; + void reset() { history.clear(); ckidx = 0; } + public int getInt(int bandIndex) { + counts[0][0]++; + int value = rand.nextInt(maxVal+1); + history.add(new Integer(bandIndex)); + history.add(new Integer(value)); + return value; + } + public void putInt(int bandIndex, int token) { + counts[1][0]++; + if (verbose) + System.out.print(" "+bandIndex+":"+token); + // Make sure this put parallels a previous get: + int check0 = ((Integer)history.get(ckidx+0)).intValue(); + int check1 = ((Integer)history.get(ckidx+1)).intValue(); + if (check0 != bandIndex || check1 != token) { + if (!verbose) + System.out.println(history.subList(0, ckidx)); + System.out.println(" *** Should be "+check0+":"+check1); + throw new RuntimeException("Failed test!"); + } + ckidx += 2; + } + public Entry getRef(int bandIndex) { + counts[0][2]++; + int value = getInt(bandIndex); + if (value < 0 || value > maxVal) { + System.out.println(" *** Unexpected ref code "+value); + return ConstantPool.getLiteralEntry(new Integer(value)); + } + return cpMap[value]; + } + public void putRef(int bandIndex, Entry ref) { + counts[1][2]++; + if (ref == null) { + putInt(bandIndex, 0); + return; + } + Number refValue = null; + if (ref instanceof ConstantPool.NumberEntry) + refValue = ((ConstantPool.NumberEntry)ref).numberValue(); + int value; + if (!(refValue instanceof Integer)) { + System.out.println(" *** Unexpected ref "+ref); + value = -1; + } else { + value = ((Integer)refValue).intValue(); + } + putInt(bandIndex, value); + } + public int encodeBCI(int bci) { + counts[1][1]++; + // move LSB to MSB of low byte + int code = (bci >> 8) << 8; // keep high bits + code += (bci & 0xFE) >> 1; + code += (bci & 0x01) << 7; + return code ^ (8<<8); // mark it clearly as coded + } + public int decodeBCI(int bciCode) { + counts[0][1]++; + bciCode ^= (8<<8); // remove extra mark + int bci = (bciCode >> 8) << 8; // keep high bits + bci += (bciCode & 0x7F) << 1; + bci += (bciCode & 0x80) >> 7; + return bci; + } + } + TestValueStream tts = new TestValueStream(); + tts.maxVal = maxVal; + tts.verbose = verbose; + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + for (int i = 0; i < (1 << 30); i = (i + 1) * 5) { + int ei = tts.encodeBCI(i); + int di = tts.decodeBCI(ei); + if (di != i) System.out.println("i="+Integer.toHexString(i)+ + " ei="+Integer.toHexString(ei)+ + " di="+Integer.toHexString(di)); + } + while (iters-- > 0) { + for (int i = ap; i < av.length; i++) { + String layout = av[i]; + if (layout.startsWith("=")) { + String name = layout.substring(1); + for (Attribute a : standardDefs.values()) { + if (a.name().equals(name)) { + layout = a.layout().layout(); + break; + } + } + if (layout.startsWith("=")) { + System.out.println("Could not find "+name+" in "+standardDefs.values()); + } + } + Layout self = new Layout(0, "Foo", layout); + if (verbose) { + System.out.print("/"+layout+"/ => "); + System.out.println(Arrays.asList(self.elems)); + } + buf.reset(); + tts.reset(); + Object fixups = self.unparse(tts, buf); + byte[] bytes = buf.toByteArray(); + // Attach the references to the byte array. + Fixups.setBytes(fixups, bytes); + // Patch the references to their frozen values. + Fixups.finishRefs(fixups, bytes, new Index("test", cpMap)); + if (verbose) { + System.out.print(" bytes: {"); + for (int j = 0; j < bytes.length; j++) { + System.out.print(" "+bytes[j]); + } + System.out.println("}"); + } + if (verbose) { + System.out.print(" parse: {"); + } + self.parse(cls, bytes, 0, bytes.length, tts); + if (verbose) { + System.out.println("}"); + } + } + } + for (int j = 0; j <= 1; j++) { + System.out.print("values "+(j==0?"read":"written")+": {"); + for (int k = 0; k < counts[j].length; k++) { + System.out.print(" "+counts[j][k]); + } + System.out.println(" }"); + } + } +//*/ +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/BandStructure.java b/src/main/java/net/minecraftforge/gradle/util/pack200/BandStructure.java new file mode 100644 index 000000000..422a9f94a --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/BandStructure.java @@ -0,0 +1,2761 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import net.minecraftforge.gradle.util.pack200.ConstantPool.Entry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.Index; +import net.minecraftforge.gradle.util.pack200.Package.Class.Field; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import static net.minecraftforge.gradle.util.pack200.Constants.*; +import java.util.LinkedList; + +/** + * Define the structure and ordering of "bands" in a packed file. + * @author John Rose + */ +@SuppressWarnings({"removal"}) +abstract +class BandStructure { + static final int MAX_EFFORT = 9; + static final int MIN_EFFORT = 1; + static final int DEFAULT_EFFORT = 5; + + // Inherit options from Pack200: + PropMap p200 = Utils.currentPropMap(); + + int verbose = p200.getInteger(Utils.DEBUG_VERBOSE); + int effort = p200.getInteger(Pack200.Packer.EFFORT); + { if (effort == 0) effort = DEFAULT_EFFORT; } + boolean optDumpBands = p200.getBoolean(Utils.COM_PREFIX+"dump.bands"); + boolean optDebugBands = p200.getBoolean(Utils.COM_PREFIX+"debug.bands"); + + // Various heuristic options. + boolean optVaryCodings = !p200.getBoolean(Utils.COM_PREFIX+"no.vary.codings"); + boolean optBigStrings = !p200.getBoolean(Utils.COM_PREFIX+"no.big.strings"); + + protected abstract Index getCPIndex(byte tag); + + // Local copy of highest class version. + private Package.Version highestClassVersion = null; + + /** Call this exactly once, early, to specify the archive major version. */ + public void initHighestClassVersion(Package.Version highestClassVersion) throws IOException { + if (this.highestClassVersion != null) { + throw new IOException( + "Highest class major version is already initialized to " + + this.highestClassVersion + "; new setting is " + highestClassVersion); + } + this.highestClassVersion = highestClassVersion; + adjustToClassVersion(); + } + + public Package.Version getHighestClassVersion() { + return highestClassVersion; + } + + private final boolean isReader = this instanceof PackageReader; + + protected BandStructure() {} + + static final Coding BYTE1 = Coding.of(1,256); + + static final Coding CHAR3 = Coding.of(3,128); + // Note: Tried sharper (3,16) with no post-zip benefit. + + // This is best used with BCI values: + static final Coding BCI5 = Coding.of(5,4); // mostly 1-byte offsets + static final Coding BRANCH5 = Coding.of(5,4,2); // mostly forward branches + + static final Coding UNSIGNED5 = Coding.of(5,64); + static final Coding UDELTA5 = UNSIGNED5.getDeltaCoding(); + // "sharp" (5,64) zips 0.4% better than "medium" (5,128) + // It zips 1.1% better than "flat" (5,192) + + static final Coding SIGNED5 = Coding.of(5,64,1); //sharp + static final Coding DELTA5 = SIGNED5.getDeltaCoding(); + // Note: Tried (5,128,2) and (5,192,2) with no benefit. + + static final Coding MDELTA5 = Coding.of(5,64,2).getDeltaCoding(); + + private static final Coding[] basicCodings = { + // Table of "Canonical BHSD Codings" from Pack200 spec. + null, // _meta_default + + // Fixed-length codings: + Coding.of(1,256,0), + Coding.of(1,256,1), + Coding.of(1,256,0).getDeltaCoding(), + Coding.of(1,256,1).getDeltaCoding(), + Coding.of(2,256,0), + Coding.of(2,256,1), + Coding.of(2,256,0).getDeltaCoding(), + Coding.of(2,256,1).getDeltaCoding(), + Coding.of(3,256,0), + Coding.of(3,256,1), + Coding.of(3,256,0).getDeltaCoding(), + Coding.of(3,256,1).getDeltaCoding(), + Coding.of(4,256,0), + Coding.of(4,256,1), + Coding.of(4,256,0).getDeltaCoding(), + Coding.of(4,256,1).getDeltaCoding(), + + // Full-range variable-length codings: + Coding.of(5, 4,0), + Coding.of(5, 4,1), + Coding.of(5, 4,2), + Coding.of(5, 16,0), + Coding.of(5, 16,1), + Coding.of(5, 16,2), + Coding.of(5, 32,0), + Coding.of(5, 32,1), + Coding.of(5, 32,2), + Coding.of(5, 64,0), + Coding.of(5, 64,1), + Coding.of(5, 64,2), + Coding.of(5,128,0), + Coding.of(5,128,1), + Coding.of(5,128,2), + + Coding.of(5, 4,0).getDeltaCoding(), + Coding.of(5, 4,1).getDeltaCoding(), + Coding.of(5, 4,2).getDeltaCoding(), + Coding.of(5, 16,0).getDeltaCoding(), + Coding.of(5, 16,1).getDeltaCoding(), + Coding.of(5, 16,2).getDeltaCoding(), + Coding.of(5, 32,0).getDeltaCoding(), + Coding.of(5, 32,1).getDeltaCoding(), + Coding.of(5, 32,2).getDeltaCoding(), + Coding.of(5, 64,0).getDeltaCoding(), + Coding.of(5, 64,1).getDeltaCoding(), + Coding.of(5, 64,2).getDeltaCoding(), + Coding.of(5,128,0).getDeltaCoding(), + Coding.of(5,128,1).getDeltaCoding(), + Coding.of(5,128,2).getDeltaCoding(), + + // Variable length subrange codings: + Coding.of(2,192,0), + Coding.of(2,224,0), + Coding.of(2,240,0), + Coding.of(2,248,0), + Coding.of(2,252,0), + + Coding.of(2, 8,0).getDeltaCoding(), + Coding.of(2, 8,1).getDeltaCoding(), + Coding.of(2, 16,0).getDeltaCoding(), + Coding.of(2, 16,1).getDeltaCoding(), + Coding.of(2, 32,0).getDeltaCoding(), + Coding.of(2, 32,1).getDeltaCoding(), + Coding.of(2, 64,0).getDeltaCoding(), + Coding.of(2, 64,1).getDeltaCoding(), + Coding.of(2,128,0).getDeltaCoding(), + Coding.of(2,128,1).getDeltaCoding(), + Coding.of(2,192,0).getDeltaCoding(), + Coding.of(2,192,1).getDeltaCoding(), + Coding.of(2,224,0).getDeltaCoding(), + Coding.of(2,224,1).getDeltaCoding(), + Coding.of(2,240,0).getDeltaCoding(), + Coding.of(2,240,1).getDeltaCoding(), + Coding.of(2,248,0).getDeltaCoding(), + Coding.of(2,248,1).getDeltaCoding(), + + Coding.of(3,192,0), + Coding.of(3,224,0), + Coding.of(3,240,0), + Coding.of(3,248,0), + Coding.of(3,252,0), + + Coding.of(3, 8,0).getDeltaCoding(), + Coding.of(3, 8,1).getDeltaCoding(), + Coding.of(3, 16,0).getDeltaCoding(), + Coding.of(3, 16,1).getDeltaCoding(), + Coding.of(3, 32,0).getDeltaCoding(), + Coding.of(3, 32,1).getDeltaCoding(), + Coding.of(3, 64,0).getDeltaCoding(), + Coding.of(3, 64,1).getDeltaCoding(), + Coding.of(3,128,0).getDeltaCoding(), + Coding.of(3,128,1).getDeltaCoding(), + Coding.of(3,192,0).getDeltaCoding(), + Coding.of(3,192,1).getDeltaCoding(), + Coding.of(3,224,0).getDeltaCoding(), + Coding.of(3,224,1).getDeltaCoding(), + Coding.of(3,240,0).getDeltaCoding(), + Coding.of(3,240,1).getDeltaCoding(), + Coding.of(3,248,0).getDeltaCoding(), + Coding.of(3,248,1).getDeltaCoding(), + + Coding.of(4,192,0), + Coding.of(4,224,0), + Coding.of(4,240,0), + Coding.of(4,248,0), + Coding.of(4,252,0), + + Coding.of(4, 8,0).getDeltaCoding(), + Coding.of(4, 8,1).getDeltaCoding(), + Coding.of(4, 16,0).getDeltaCoding(), + Coding.of(4, 16,1).getDeltaCoding(), + Coding.of(4, 32,0).getDeltaCoding(), + Coding.of(4, 32,1).getDeltaCoding(), + Coding.of(4, 64,0).getDeltaCoding(), + Coding.of(4, 64,1).getDeltaCoding(), + Coding.of(4,128,0).getDeltaCoding(), + Coding.of(4,128,1).getDeltaCoding(), + Coding.of(4,192,0).getDeltaCoding(), + Coding.of(4,192,1).getDeltaCoding(), + Coding.of(4,224,0).getDeltaCoding(), + Coding.of(4,224,1).getDeltaCoding(), + Coding.of(4,240,0).getDeltaCoding(), + Coding.of(4,240,1).getDeltaCoding(), + Coding.of(4,248,0).getDeltaCoding(), + Coding.of(4,248,1).getDeltaCoding(), + + null + }; + private static final Map basicCodingIndexes; + static { + assert(basicCodings[_meta_default] == null); + assert(basicCodings[_meta_canon_min] != null); + assert(basicCodings[_meta_canon_max] != null); + Map map = new HashMap<>(); + for (int i = 0; i < basicCodings.length; i++) { + Coding c = basicCodings[i]; + if (c == null) continue; + assert(i >= _meta_canon_min); + assert(i <= _meta_canon_max); + map.put(c, i); + } + basicCodingIndexes = map; + } + public static Coding codingForIndex(int i) { + return i < basicCodings.length ? basicCodings[i] : null; + } + public static int indexOf(Coding c) { + Integer i = basicCodingIndexes.get(c); + if (i == null) return 0; + return i.intValue(); + } + public static Coding[] getBasicCodings() { + return basicCodings.clone(); + } + + protected byte[] bandHeaderBytes; // used for input only + protected int bandHeaderBytePos; // BHB read pointer, for input only + protected int bandHeaderBytePos0; // for debug + + protected CodingMethod getBandHeader(int XB, Coding regularCoding) { + CodingMethod[] res = {null}; + // push back XB onto the band header bytes + bandHeaderBytes[--bandHeaderBytePos] = (byte) XB; + bandHeaderBytePos0 = bandHeaderBytePos; + // scan forward through XB and any additional band header bytes + bandHeaderBytePos = parseMetaCoding(bandHeaderBytes, + bandHeaderBytePos, + regularCoding, + res); + return res[0]; + } + + public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod[] res) { + if ((bytes[pos] & 0xFF) == _meta_default) { + res[0] = dflt; + return pos+1; + } + int pos2; + pos2 = Coding.parseMetaCoding(bytes, pos, dflt, res); + if (pos2 > pos) return pos2; + pos2 = PopulationCoding.parseMetaCoding(bytes, pos, dflt, res); + if (pos2 > pos) return pos2; + pos2 = AdaptiveCoding.parseMetaCoding(bytes, pos, dflt, res); + if (pos2 > pos) return pos2; + throw new RuntimeException("Bad meta-coding op "+(bytes[pos]&0xFF)); + } + + static final int SHORT_BAND_HEURISTIC = 100; + + public static final int NO_PHASE = 0; + + // package writing phases: + public static final int COLLECT_PHASE = 1; // collect data before write + public static final int FROZEN_PHASE = 3; // no longer collecting + public static final int WRITE_PHASE = 5; // ready to write bytes + + // package reading phases: + public static final int EXPECT_PHASE = 2; // gather expected counts + public static final int READ_PHASE = 4; // ready to read bytes + public static final int DISBURSE_PHASE = 6; // pass out data after read + + public static final int DONE_PHASE = 8; // done writing or reading + + static boolean phaseIsRead(int p) { + return (p % 2) == 0; + } + static int phaseCmp(int p0, int p1) { + assert((p0 % 2) == (p1 % 2) || (p0 % 8) == 0 || (p1 % 8) == 0); + return p0 - p1; + } + + /** The packed file is divided up into a number of segments. + * Most segments are typed as ValueBand, strongly-typed sequences + * of integer values, all interpreted in a single way. + * A few segments are ByteBands, which hetergeneous sequences + * of bytes. + * + * The two phases for writing a packed file are COLLECT and WRITE. + * 1. When writing a packed file, each band collects + * data in an ad-hoc order. + * 2. At the end, each band is assigned a coding scheme, + * and then all the bands are written in their global order. + * + * The three phases for reading a packed file are EXPECT, READ, + * and DISBURSE. + * 1. For each band, the expected number of integers is determined. + * 2. The data is actually read from the file into the band. + * 3. The band pays out its values as requested, in an ad hoc order. + * + * When the last phase of a band is done, it is marked so (DONE). + * Clearly, these phases must be properly ordered WRT each other. + */ + abstract class Band { + private int phase = NO_PHASE; + private final String name; + + private int valuesExpected; + + protected long outputSize = -1; // cache + + public final Coding regularCoding; + + public final int seqForDebug; + public int elementCountForDebug; + + + protected Band(String name, Coding regularCoding) { + this.name = name; + this.regularCoding = regularCoding; + this.seqForDebug = ++nextSeqForDebug; + if (verbose > 2) + Utils.log.fine("Band "+seqForDebug+" is "+name); + // caller must call init + } + + public Band init() { + // Cannot due this from the constructor, because constructor + // may wish to initialize some subclass variables. + // Set initial phase for reading or writing: + if (isReader) + readyToExpect(); + else + readyToCollect(); + return this; + } + + // common operations + boolean isReader() { return isReader; } + int phase() { return phase; } + String name() { return name; } + + /** Return -1 if data buffer not allocated, else max length. */ + public abstract int capacity(); + + /** Allocate data buffer to specified length. */ + protected abstract void setCapacity(int cap); + + /** Return current number of values in buffer, which must exist. */ + public abstract int length(); + + protected abstract int valuesRemainingForDebug(); + + public final int valuesExpected() { + return valuesExpected; + } + + /** Write out bytes, encoding the values. */ + public final void writeTo(OutputStream out) throws IOException { + assert(assertReadyToWriteTo(this, out)); + setPhase(WRITE_PHASE); + // subclasses continue by writing their contents to output + writeDataTo(out); + doneWriting(); + } + + abstract void chooseBandCodings() throws IOException; + + public final long outputSize() { + if (outputSize >= 0) { + long size = outputSize; + assert(size == computeOutputSize()); + return size; + } + return computeOutputSize(); + } + + protected abstract long computeOutputSize(); + + protected abstract void writeDataTo(OutputStream out) throws IOException; + + /** Expect a certain number of values. */ + void expectLength(int l) { + assert(assertPhase(this, EXPECT_PHASE)); + assert(valuesExpected == 0); // all at once + assert(l >= 0); + valuesExpected = l; + } + /** Expect more values. (Multiple calls accumulate.) */ + void expectMoreLength(int l) { + assert(assertPhase(this, EXPECT_PHASE)); + valuesExpected += l; + } + + + /// Phase change markers. + + private void readyToCollect() { // called implicitly by constructor + setCapacity(1); + setPhase(COLLECT_PHASE); + } + protected void doneWriting() { + assert(assertPhase(this, WRITE_PHASE)); + setPhase(DONE_PHASE); + } + private void readyToExpect() { // called implicitly by constructor + setPhase(EXPECT_PHASE); + } + /** Read in bytes, decoding the values. */ + public final void readFrom(InputStream in) throws IOException { + assert(assertReadyToReadFrom(this, in)); + setCapacity(valuesExpected()); + setPhase(READ_PHASE); + // subclasses continue by reading their contents from input: + readDataFrom(in); + readyToDisburse(); + } + protected abstract void readDataFrom(InputStream in) throws IOException; + protected void readyToDisburse() { + if (verbose > 1) Utils.log.fine("readyToDisburse "+this); + setPhase(DISBURSE_PHASE); + } + public void doneDisbursing() { + assert(assertPhase(this, DISBURSE_PHASE)); + setPhase(DONE_PHASE); + } + public final void doneWithUnusedBand() { + if (isReader) { + assert(assertPhase(this, EXPECT_PHASE)); + assert(valuesExpected() == 0); + // Fast forward: + setPhase(READ_PHASE); + setPhase(DISBURSE_PHASE); + setPhase(DONE_PHASE); + } else { + setPhase(FROZEN_PHASE); + } + } + + protected void setPhase(int newPhase) { + assert(assertPhaseChangeOK(this, phase, newPhase)); + this.phase = newPhase; + } + + protected int lengthForDebug = -1; // DEBUG ONLY + @Override + public String toString() { // DEBUG ONLY + int length = (lengthForDebug != -1 ? lengthForDebug : length()); + String str = name; + if (length != 0) + str += "[" + length + "]"; + if (elementCountForDebug != 0) + str += "(" + elementCountForDebug + ")"; + return str; + } + } + + class ValueBand extends Band { + private int[] values; // must be null in EXPECT phase + private int length; + private int valuesDisbursed; + + private CodingMethod bandCoding; + private byte[] metaCoding; + + protected ValueBand(String name, Coding regularCoding) { + super(name, regularCoding); + } + + @Override + public int capacity() { + return values == null ? -1 : values.length; + } + + /** Declare predicted or needed capacity. */ + @Override + protected void setCapacity(int cap) { + assert(length <= cap); + if (cap == -1) { values = null; return; } + values = realloc(values, cap); + } + + @Override + public int length() { + return length; + } + @Override + protected int valuesRemainingForDebug() { + return length - valuesDisbursed; + } + protected int valueAtForDebug(int i) { + return values[i]; + } + + void patchValue(int i, int value) { + // Only one use for this. + assert(this == archive_header_S); + assert(i == AH_ARCHIVE_SIZE_HI || i == AH_ARCHIVE_SIZE_LO); + assert(i < length); // must have already output a dummy + values[i] = value; + outputSize = -1; // decache + } + + protected void initializeValues(int[] values) { + assert(assertCanChangeLength(this)); + assert(length == 0); + this.values = values; + this.length = values.length; + } + + /** Collect one value, or store one decoded value. */ + protected void addValue(int x) { + assert(assertCanChangeLength(this)); + if (length == values.length) + setCapacity(length < 1000 ? length * 10 : length * 2); + values[length++] = x; + } + + private boolean canVaryCoding() { + if (!optVaryCodings) return false; + if (length == 0) return false; + // Can't read band_headers w/o the archive header: + if (this == archive_header_0) return false; + if (this == archive_header_S) return false; + if (this == archive_header_1) return false; + // BYTE1 bands can't vary codings, but the others can. + // All that's needed for the initial escape is at least + // 256 negative values or more than 256 non-negative values + return (regularCoding.min() <= -256 || regularCoding.max() >= 256); + } + + private boolean shouldVaryCoding() { + assert(canVaryCoding()); + if (effort < MAX_EFFORT && length < SHORT_BAND_HEURISTIC) + return false; + return true; + } + + @Override + protected void chooseBandCodings() throws IOException { + boolean canVary = canVaryCoding(); + if (!canVary || !shouldVaryCoding()) { + if (regularCoding.canRepresent(values, 0, length)) { + bandCoding = regularCoding; + } else { + assert(canVary); + if (verbose > 1) + Utils.log.fine("regular coding fails in band "+name()); + bandCoding = UNSIGNED5; + } + outputSize = -1; + } else { + int[] sizes = {0,0}; + bandCoding = chooseCoding(values, 0, length, + regularCoding, name(), + sizes); + outputSize = sizes[CodingChooser.BYTE_SIZE]; + if (outputSize == 0) // CodingChooser failed to size it. + outputSize = -1; + } + + // Compute and save the meta-coding bytes also. + if (bandCoding != regularCoding) { + metaCoding = bandCoding.getMetaCoding(regularCoding); + if (verbose > 1) { + Utils.log.fine("alternate coding "+this+" "+bandCoding); + } + } else if (canVary && + decodeEscapeValue(values[0], regularCoding) >= 0) { + // Need an explicit default. + metaCoding = defaultMetaCoding; + } else { + // Common case: Zero bytes of meta coding. + metaCoding = noMetaCoding; + } + if (metaCoding.length > 0 + && (verbose > 2 || verbose > 1 && metaCoding.length > 1)) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < metaCoding.length; i++) { + if (i == 1) sb.append(" /"); + sb.append(" ").append(metaCoding[i] & 0xFF); + } + Utils.log.fine(" meta-coding "+sb); + } + + assert((outputSize < 0) || + !(bandCoding instanceof Coding) || + (outputSize == ((Coding)bandCoding) + .getLength(values, 0, length))) + : (bandCoding+" : "+ + outputSize+" != "+ + ((Coding)bandCoding).getLength(values, 0, length) + +" ?= "+getCodingChooser().computeByteSize(bandCoding,values,0,length) + ); + + // Compute outputSize of the escape value X, if any. + if (metaCoding.length > 0) { + // First byte XB of meta-coding is treated specially, + // but any other bytes go into the band headers band. + // This must be done before any other output happens. + if (outputSize >= 0) + outputSize += computeEscapeSize(); // good cache + // Other bytes go into band_headers. + for (int i = 1; i < metaCoding.length; i++) { + band_headers.putByte(metaCoding[i] & 0xFF); + } + } + } + + @Override + protected long computeOutputSize() { + outputSize = getCodingChooser().computeByteSize(bandCoding, + values, 0, length); + assert(outputSize < Integer.MAX_VALUE); + outputSize += computeEscapeSize(); + return outputSize; + } + + protected int computeEscapeSize() { + if (metaCoding.length == 0) return 0; + int XB = metaCoding[0] & 0xFF; + int X = encodeEscapeValue(XB, regularCoding); + return regularCoding.setD(0).getLength(X); + } + + @Override + protected void writeDataTo(OutputStream out) throws IOException { + if (length == 0) return; // nothing to write + long len0 = 0; + if (out == outputCounter) { + len0 = outputCounter.getCount(); + } + if (metaCoding.length > 0) { + int XB = metaCoding[0] & 0xFF; + // We need an explicit band header, either because + // there is a non-default coding method, or because + // the first value would be parsed as an escape value. + int X = encodeEscapeValue(XB, regularCoding); + //System.out.println("X="+X+" XB="+XB+" in "+this); + regularCoding.setD(0).writeTo(out, X); + } + bandCoding.writeArrayTo(out, values, 0, length); + if (out == outputCounter) { + assert(outputSize == outputCounter.getCount() - len0) + : (outputSize+" != "+outputCounter.getCount()+"-"+len0); + } + if (optDumpBands) dumpBand(); + } + + @Override + protected void readDataFrom(InputStream in) throws IOException { + length = valuesExpected(); + if (length == 0) return; // nothing to read + if (verbose > 1) + Utils.log.fine("Reading band "+this); + if (!canVaryCoding()) { + bandCoding = regularCoding; + metaCoding = noMetaCoding; + } else { + assert(in.markSupported()); // input must be buffered + in.mark(Coding.B_MAX); + int X = regularCoding.setD(0).readFrom(in); + int XB = decodeEscapeValue(X, regularCoding); + if (XB < 0) { + // Do not consume this value. No alternate coding. + in.reset(); + bandCoding = regularCoding; + metaCoding = noMetaCoding; + } else if (XB == _meta_default) { + bandCoding = regularCoding; + metaCoding = defaultMetaCoding; + } else { + if (verbose > 2) + Utils.log.fine("found X="+X+" => XB="+XB); + bandCoding = getBandHeader(XB, regularCoding); + // This is really used only by dumpBands. + int p0 = bandHeaderBytePos0; + int p1 = bandHeaderBytePos; + metaCoding = new byte[p1-p0]; + System.arraycopy(bandHeaderBytes, p0, + metaCoding, 0, metaCoding.length); + } + } + if (bandCoding != regularCoding) { + if (verbose > 1) + Utils.log.fine(name()+": irregular coding "+bandCoding); + } + bandCoding.readArrayFrom(in, values, 0, length); + if (optDumpBands) dumpBand(); + } + + @Override + public void doneDisbursing() { + super.doneDisbursing(); + values = null; // for GC + } + + private void dumpBand() throws IOException { + assert(optDumpBands); + try (PrintStream ps = new PrintStream(getDumpStream(this, ".txt"))) { + String irr = (bandCoding == regularCoding) ? "" : " irregular"; + ps.print("# length="+length+ + " size="+outputSize()+ + irr+" coding="+bandCoding); + if (metaCoding != noMetaCoding) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < metaCoding.length; i++) { + if (i == 1) sb.append(" /"); + sb.append(" ").append(metaCoding[i] & 0xFF); + } + ps.print(" //header: "+sb); + } + printArrayTo(ps, values, 0, length); + } + try (OutputStream ds = getDumpStream(this, ".bnd")) { + bandCoding.writeArrayTo(ds, values, 0, length); + } + } + + /** Disburse one value. */ + protected int getValue() { + assert(phase() == DISBURSE_PHASE); + // when debugging return a zero if lengths are zero + if (optDebugBands && length == 0 && valuesDisbursed == length) + return 0; + assert(valuesDisbursed <= length); + return values[valuesDisbursed++]; + } + + /** Reset for another pass over the same value set. */ + public void resetForSecondPass() { + assert(phase() == DISBURSE_PHASE); + assert(valuesDisbursed == length()); // 1st pass is complete + valuesDisbursed = 0; + } + } + + class ByteBand extends Band { + private ByteArrayOutputStream bytes; // input buffer + private ByteArrayOutputStream bytesForDump; + private InputStream in; + + public ByteBand(String name) { + super(name, BYTE1); + } + + @Override + public int capacity() { + return bytes == null ? -1 : Integer.MAX_VALUE; + } + @Override + protected void setCapacity(int cap) { + assert(bytes == null); // do this just once + bytes = new ByteArrayOutputStream(cap); + } + public void destroy() { + lengthForDebug = length(); + bytes = null; + } + + @Override + public int length() { + return bytes == null ? -1 : bytes.size(); + } + public void reset() { + bytes.reset(); + } + @Override + protected int valuesRemainingForDebug() { + return (bytes == null) ? -1 : ((ByteArrayInputStream)in).available(); + } + + @Override + protected void chooseBandCodings() throws IOException { + // No-op. + assert(decodeEscapeValue(regularCoding.min(), regularCoding) < 0); + assert(decodeEscapeValue(regularCoding.max(), regularCoding) < 0); + } + + @Override + protected long computeOutputSize() { + // do not cache + return bytes.size(); + } + + @Override + public void writeDataTo(OutputStream out) throws IOException { + if (length() == 0) return; + bytes.writeTo(out); + if (optDumpBands) dumpBand(); + destroy(); // done with the bits! + } + + private void dumpBand() throws IOException { + assert(optDumpBands); + try (OutputStream ds = getDumpStream(this, ".bnd")) { + if (bytesForDump != null) + bytesForDump.writeTo(ds); + else + bytes.writeTo(ds); + } + } + + @Override + public void readDataFrom(InputStream in) throws IOException { + int vex = valuesExpected(); + if (vex == 0) return; + if (verbose > 1) { + lengthForDebug = vex; + Utils.log.fine("Reading band "+this); + lengthForDebug = -1; + } + byte[] buf = new byte[Math.min(vex, 1<<14)]; + while (vex > 0) { + int nr = in.read(buf, 0, Math.min(vex, buf.length)); + if (nr < 0) throw new EOFException(); + bytes.write(buf, 0, nr); + vex -= nr; + } + if (optDumpBands) dumpBand(); + } + + @Override + public void readyToDisburse() { + in = new ByteArrayInputStream(bytes.toByteArray()); + super.readyToDisburse(); + } + + @Override + public void doneDisbursing() { + super.doneDisbursing(); + if (optDumpBands + && bytesForDump != null && bytesForDump.size() > 0) { + try { + dumpBand(); + } catch (IOException ee) { + throw new RuntimeException(ee); + } + } + in = null; // GC + bytes = null; // GC + bytesForDump = null; // GC + } + + // alternative to readFrom: + public void setInputStreamFrom(InputStream in) throws IOException { + assert(bytes == null); + assert(assertReadyToReadFrom(this, in)); + setPhase(READ_PHASE); + this.in = in; + if (optDumpBands) { + // Tap the stream. + bytesForDump = new ByteArrayOutputStream(); + this.in = new FilterInputStream(in) { + @Override + public int read() throws IOException { + int ch = in.read(); + if (ch >= 0) bytesForDump.write(ch); + return ch; + } + @Override + public int read(byte b[], int off, int len) throws IOException { + int nr = in.read(b, off, len); + if (nr >= 0) bytesForDump.write(b, off, nr); + return nr; + } + }; + } + super.readyToDisburse(); + } + + public OutputStream collectorStream() { + assert(phase() == COLLECT_PHASE); + assert(bytes != null); + return bytes; + } + + public InputStream getInputStream() { + assert(phase() == DISBURSE_PHASE); + assert(in != null); + return in; + } + public int getByte() throws IOException { + int b = getInputStream().read(); + if (b < 0) throw new EOFException(); + return b; + } + public void putByte(int b) throws IOException { + assert(b == (b & 0xFF)); + collectorStream().write(b); + } + @Override + public String toString() { + return "byte "+super.toString(); + } + } + + class IntBand extends ValueBand { + // The usual coding for bands is 7bit/5byte/delta. + public IntBand(String name, Coding regularCoding) { + super(name, regularCoding); + } + + public void putInt(int x) { + assert(phase() == COLLECT_PHASE); + addValue(x); + } + + public int getInt() { + return getValue(); + } + /** Return the sum of all values in this band. */ + public int getIntTotal() { + assert(phase() == DISBURSE_PHASE); + // assert that this is the whole pass; no other reads allowed + assert(valuesRemainingForDebug() == length()); + int total = 0; + for (int k = length(); k > 0; k--) { + total += getInt(); + } + resetForSecondPass(); + return total; + } + /** Return the occurrence count of a specific value in this band. */ + public int getIntCount(int value) { + assert(phase() == DISBURSE_PHASE); + // assert that this is the whole pass; no other reads allowed + assert(valuesRemainingForDebug() == length()); + int total = 0; + for (int k = length(); k > 0; k--) { + if (getInt() == value) { + total += 1; + } + } + resetForSecondPass(); + return total; + } + } + + static int getIntTotal(int[] values) { + int total = 0; + for (int i = 0; i < values.length; i++) { + total += values[i]; + } + return total; + } + + class CPRefBand extends ValueBand { + Index index; + boolean nullOK; + + public CPRefBand(String name, Coding regularCoding, byte cpTag, boolean nullOK) { + super(name, regularCoding); + this.nullOK = nullOK; + if (cpTag != CONSTANT_None) + setBandIndex(this, cpTag); + } + public CPRefBand(String name, Coding regularCoding, byte cpTag) { + this(name, regularCoding, cpTag, false); + } + public CPRefBand(String name, Coding regularCoding, Object undef) { + this(name, regularCoding, CONSTANT_None, false); + } + + public void setIndex(Index index) { + this.index = index; + } + + protected void readDataFrom(InputStream in) throws IOException { + super.readDataFrom(in); + assert(assertValidCPRefs(this)); + } + + /** Write a constant pool reference. */ + public void putRef(Entry e) { + addValue(encodeRefOrNull(e, index)); + } + public void putRef(Entry e, Index index) { + assert(this.index == null); + addValue(encodeRefOrNull(e, index)); + } + public void putRef(Entry e, byte cptag) { + putRef(e, getCPIndex(cptag)); + } + + public Entry getRef() { + if (index == null) Utils.log.warning("No index for "+this); + assert(index != null); + return decodeRefOrNull(getValue(), index); + } + public Entry getRef(Index index) { + assert(this.index == null); + return decodeRefOrNull(getValue(), index); + } + public Entry getRef(byte cptag) { + return getRef(getCPIndex(cptag)); + } + + private int encodeRefOrNull(Entry e, Index index) { + int nonNullCode; // NNC is the coding which assumes nulls are rare + if (e == null) { + nonNullCode = -1; // negative values are rare + } else { + nonNullCode = encodeRef(e, index); + } + // If nulls are expected, increment, to make -1 code turn to 0. + return (nullOK ? 1 : 0) + nonNullCode; + } + private Entry decodeRefOrNull(int code, Index index) { + // Inverse to encodeRefOrNull... + int nonNullCode = code - (nullOK ? 1 : 0); + if (nonNullCode == -1) { + return null; + } else { + return decodeRef(nonNullCode, index); + } + } + } + + // Bootstrap support for CPRefBands. These are needed to record + // intended CP indexes, before the CP has been created. + private final List allKQBands = new ArrayList<>(); + private List needPredefIndex = new ArrayList<>(); + + + int encodeRef(Entry e, Index ix) { + if (ix == null) + throw new RuntimeException("null index for " + e.stringValue()); + int coding = ix.indexOf(e); + if (verbose > 2) + Utils.log.fine("putRef "+coding+" => "+e); + return coding; + } + + Entry decodeRef(int n, Index ix) { + if (n < 0 || n >= ix.size()) + Utils.log.warning("decoding bad ref "+n+" in "+ix); + Entry e = ix.getEntry(n); + if (verbose > 2) + Utils.log.fine("getRef "+n+" => "+e); + return e; + } + + private CodingChooser codingChooser; + protected CodingChooser getCodingChooser() { + if (codingChooser == null) { + codingChooser = new CodingChooser(effort, basicCodings); + if (codingChooser.stress != null + && this instanceof PackageWriter) { + // Twist the random state based on my first file. + // This sends each segment off in a different direction. + List classes = ((PackageWriter)this).pkg.classes; + if (!classes.isEmpty()) { + Package.Class cls = classes.get(0); + codingChooser.addStressSeed(cls.getName().hashCode()); + } + } + } + return codingChooser; + } + + public CodingMethod chooseCoding(int[] values, int start, int end, + Coding regular, String bandName, + int[] sizes) { + assert(optVaryCodings); + if (effort <= MIN_EFFORT) { + return regular; + } + CodingChooser cc = getCodingChooser(); + if (verbose > 1 || cc.verbose > 1) { + Utils.log.fine("--- chooseCoding "+bandName); + } + return cc.choose(values, start, end, regular, sizes); + } + + static final byte[] defaultMetaCoding = { _meta_default }; + static final byte[] noMetaCoding = {}; + + // The first value in a band is always coded with the default coding D. + // If this first value X is an escape value, it actually represents the + // first (and perhaps only) byte of a meta-coding. + // + // If D.S != 0 and D includes the range [-256..-1], + // the escape values are in that range, + // and the first byte XB is -1-X. + // + // If D.S == 0 and D includes the range [(D.L)..(D.L)+255], + // the escape values are in that range, + // and XB is X-(D.L). + // + // This representation is designed so that a band header is unlikely + // to be confused with the initial value of a headerless band, + // and yet so that a band header is likely to occupy only a byte or two. + // + // Result is in [0..255] if XB was successfully extracted, else -1. + // See section "Coding Specifier Meta-Encoding" in the JSR 200 spec. + protected static int decodeEscapeValue(int X, Coding regularCoding) { + // The first value in a band is always coded with the default coding D. + // If this first value X is an escape value, it actually represents the + // first (and perhaps only) byte of a meta-coding. + // Result is in [0..255] if XB was successfully extracted, else -1. + if (regularCoding.B() == 1 || regularCoding.L() == 0) + return -1; // degenerate regular coding (BYTE1) + if (regularCoding.S() != 0) { + if (-256 <= X && X <= -1 && regularCoding.min() <= -256) { + int XB = -1-X; + assert(XB >= 0 && XB < 256); + return XB; + } + } else { + int L = regularCoding.L(); + if (L <= X && X <= L+255 && regularCoding.max() >= L+255) { + int XB = X-L; + assert(XB >= 0 && XB < 256); + return XB; + } + } + return -1; // negative value for failure + } + // Inverse to decodeEscapeValue(). + protected static int encodeEscapeValue(int XB, Coding regularCoding) { + assert(XB >= 0 && XB < 256); + assert(regularCoding.B() > 1 && regularCoding.L() > 0); + int X; + if (regularCoding.S() != 0) { + assert(regularCoding.min() <= -256); + X = -1-XB; + } else { + int L = regularCoding.L(); + assert(regularCoding.max() >= L+255); + X = XB+L; + } + assert(decodeEscapeValue(X, regularCoding) == XB) + : (regularCoding+" XB="+XB+" X="+X); + return X; + } + + static { + boolean checkXB = false; + assert(checkXB = true); + if (checkXB) { + for (int i = 0; i < basicCodings.length; i++) { + Coding D = basicCodings[i]; + if (D == null) continue; + if (D.B() == 1) continue; + if (D.L() == 0) continue; + for (int XB = 0; XB <= 255; XB++) { + // The following exercises decodeEscapeValue also: + encodeEscapeValue(XB, D); + } + } + } + } + + class MultiBand extends Band { + MultiBand(String name, Coding regularCoding) { + super(name, regularCoding); + } + + @Override + public Band init() { + super.init(); + // This is all just to keep the asserts happy: + setCapacity(0); + if (phase() == EXPECT_PHASE) { + // Fast forward: + setPhase(READ_PHASE); + setPhase(DISBURSE_PHASE); + } + return this; + } + + Band[] bands = new Band[10]; + int bandCount = 0; + + int size() { + return bandCount; + } + Band get(int i) { + assert(i < bandCount); + return bands[i]; + } + Band[] toArray() { + return (Band[]) realloc(bands, bandCount); + } + + void add(Band b) { + assert(bandCount == 0 || notePrevForAssert(b, bands[bandCount-1])); + if (bandCount == bands.length) { + bands = (Band[]) realloc(bands); + } + bands[bandCount++] = b; + } + + ByteBand newByteBand(String name) { + ByteBand b = new ByteBand(name); + b.init(); add(b); + return b; + } + IntBand newIntBand(String name) { + IntBand b = new IntBand(name, regularCoding); + b.init(); add(b); + return b; + } + IntBand newIntBand(String name, Coding regularCoding) { + IntBand b = new IntBand(name, regularCoding); + b.init(); add(b); + return b; + } + MultiBand newMultiBand(String name, Coding regularCoding) { + MultiBand b = new MultiBand(name, regularCoding); + b.init(); add(b); + return b; + } + CPRefBand newCPRefBand(String name, byte cpTag) { + CPRefBand b = new CPRefBand(name, regularCoding, cpTag); + b.init(); add(b); + return b; + } + CPRefBand newCPRefBand(String name, Coding regularCoding, + byte cpTag) { + CPRefBand b = new CPRefBand(name, regularCoding, cpTag); + b.init(); add(b); + return b; + } + CPRefBand newCPRefBand(String name, Coding regularCoding, + byte cpTag, boolean nullOK) { + CPRefBand b = new CPRefBand(name, regularCoding, cpTag, nullOK); + b.init(); add(b); + return b; + } + + int bandCount() { return bandCount; } + + private int cap = -1; + @Override + public int capacity() { return cap; } + @Override + public void setCapacity(int cap) { this.cap = cap; } + + @Override + public int length() { return 0; } + @Override + public int valuesRemainingForDebug() { return 0; } + + @Override + protected void chooseBandCodings() throws IOException { + // coding decision pass + for (int i = 0; i < bandCount; i++) { + Band b = bands[i]; + b.chooseBandCodings(); + } + } + + @Override + protected long computeOutputSize() { + // coding decision pass + long sum = 0; + for (int i = 0; i < bandCount; i++) { + Band b = bands[i]; + long bsize = b.outputSize(); + assert(bsize >= 0) : b; + sum += bsize; + } + // do not cache + return sum; + } + + @Override + protected void writeDataTo(OutputStream out) throws IOException { + long preCount = 0; + if (outputCounter != null) preCount = outputCounter.getCount(); + for (int i = 0; i < bandCount; i++) { + Band b = bands[i]; + b.writeTo(out); + if (outputCounter != null) { + long postCount = outputCounter.getCount(); + long len = postCount - preCount; + preCount = postCount; + if ((verbose > 0 && len > 0) || verbose > 1) { + Utils.log.info(" ...wrote "+len+" bytes from "+b); + } + } + } + } + + @Override + protected void readDataFrom(InputStream in) throws IOException { + assert(false); // not called? + for (int i = 0; i < bandCount; i++) { + Band b = bands[i]; + b.readFrom(in); + if ((verbose > 0 && b.length() > 0) || verbose > 1) { + Utils.log.info(" ...read "+b); + } + } + } + + @Override + public String toString() { + return "{"+bandCount()+" bands: "+super.toString()+"}"; + } + } + + /** + * An output stream which counts the number of bytes written. + */ + private static + class ByteCounter extends FilterOutputStream { + // (should go public under the name CountingOutputStream?) + + private long count; + + public ByteCounter(OutputStream out) { + super(out); + } + + public long getCount() { return count; } + public void setCount(long c) { count = c; } + + @Override + public void write(int b) throws IOException { + count++; + if (out != null) out.write(b); + } + @Override + public void write(byte b[], int off, int len) throws IOException { + count += len; + if (out != null) out.write(b, off, len); + } + @Override + public String toString() { + return String.valueOf(getCount()); + } + } + ByteCounter outputCounter; + + void writeAllBandsTo(OutputStream out) throws IOException { + // Wrap a byte-counter around the output stream. + outputCounter = new ByteCounter(out); + out = outputCounter; + all_bands.writeTo(out); + if (verbose > 0) { + long nbytes = outputCounter.getCount(); + Utils.log.info("Wrote total of "+nbytes+" bytes."); + assert(nbytes == archiveSize0+archiveSize1); + } + outputCounter = null; + } + + // random AO_XXX bits, decoded from the archive header + protected int archiveOptions; + + // archiveSize1 sizes most of the archive [archive_options..file_bits). + protected long archiveSize0; // size through archive_size_lo + protected long archiveSize1; // size reported in archive_header + protected int archiveNextCount; // reported in archive_header + + static final int AH_LENGTH_0 = 3; // archive_header_0 = {minver, majver, options} + static final int AH_LENGTH_MIN = 15; // observed in spec {header_0[3], cp_counts[8], class_counts[4]} + // Length contributions from optional archive size fields: + static final int AH_LENGTH_S = 2; // archive_header_S = optional {size_hi, size_lo} + static final int AH_ARCHIVE_SIZE_HI = 0; // offset in archive_header_S + static final int AH_ARCHIVE_SIZE_LO = 1; // offset in archive_header_S + // Length contributions from optional header fields: + static final int AH_FILE_HEADER_LEN = 5; // file_counts = {{size_hi, size_lo}, next, modtime, files} + static final int AH_SPECIAL_FORMAT_LEN = 2; // special_counts = {layouts, band_headers} + static final int AH_CP_NUMBER_LEN = 4; // cp_number_counts = {int, float, long, double} + static final int AH_CP_EXTRA_LEN = 4; // cp_attr_counts = {MH, MT, InDy, BSM} + + // Common structure of attribute band groups: + static final int AB_FLAGS_HI = 0; + static final int AB_FLAGS_LO = 1; + static final int AB_ATTR_COUNT = 2; + static final int AB_ATTR_INDEXES = 3; + static final int AB_ATTR_CALLS = 4; + + static IntBand getAttrBand(MultiBand xxx_attr_bands, int which) { + IntBand b = (IntBand) xxx_attr_bands.get(which); + switch (which) { + case AB_FLAGS_HI: + assert(b.name().endsWith("_flags_hi")); break; + case AB_FLAGS_LO: + assert(b.name().endsWith("_flags_lo")); break; + case AB_ATTR_COUNT: + assert(b.name().endsWith("_attr_count")); break; + case AB_ATTR_INDEXES: + assert(b.name().endsWith("_attr_indexes")); break; + case AB_ATTR_CALLS: + assert(b.name().endsWith("_attr_calls")); break; + default: + assert(false); break; + } + return b; + } + + private static final boolean NULL_IS_OK = true; + + MultiBand all_bands = (MultiBand) new MultiBand("(package)", UNSIGNED5).init(); + + // file header (various random bytes) + ByteBand archive_magic = all_bands.newByteBand("archive_magic"); + IntBand archive_header_0 = all_bands.newIntBand("archive_header_0", UNSIGNED5); + IntBand archive_header_S = all_bands.newIntBand("archive_header_S", UNSIGNED5); + IntBand archive_header_1 = all_bands.newIntBand("archive_header_1", UNSIGNED5); + ByteBand band_headers = all_bands.newByteBand("band_headers"); + + // constant pool contents + MultiBand cp_bands = all_bands.newMultiBand("(constant_pool)", DELTA5); + IntBand cp_Utf8_prefix = cp_bands.newIntBand("cp_Utf8_prefix"); + IntBand cp_Utf8_suffix = cp_bands.newIntBand("cp_Utf8_suffix", UNSIGNED5); + IntBand cp_Utf8_chars = cp_bands.newIntBand("cp_Utf8_chars", CHAR3); + IntBand cp_Utf8_big_suffix = cp_bands.newIntBand("cp_Utf8_big_suffix"); + MultiBand cp_Utf8_big_chars = cp_bands.newMultiBand("(cp_Utf8_big_chars)", DELTA5); + IntBand cp_Int = cp_bands.newIntBand("cp_Int", UDELTA5); + IntBand cp_Float = cp_bands.newIntBand("cp_Float", UDELTA5); + IntBand cp_Long_hi = cp_bands.newIntBand("cp_Long_hi", UDELTA5); + IntBand cp_Long_lo = cp_bands.newIntBand("cp_Long_lo"); + IntBand cp_Double_hi = cp_bands.newIntBand("cp_Double_hi", UDELTA5); + IntBand cp_Double_lo = cp_bands.newIntBand("cp_Double_lo"); + CPRefBand cp_String = cp_bands.newCPRefBand("cp_String", UDELTA5, CONSTANT_Utf8); + CPRefBand cp_Class = cp_bands.newCPRefBand("cp_Class", UDELTA5, CONSTANT_Utf8); + CPRefBand cp_Signature_form = cp_bands.newCPRefBand("cp_Signature_form", CONSTANT_Utf8); + CPRefBand cp_Signature_classes = cp_bands.newCPRefBand("cp_Signature_classes", UDELTA5, CONSTANT_Class); + CPRefBand cp_Descr_name = cp_bands.newCPRefBand("cp_Descr_name", CONSTANT_Utf8); + CPRefBand cp_Descr_type = cp_bands.newCPRefBand("cp_Descr_type", UDELTA5, CONSTANT_Signature); + CPRefBand cp_Field_class = cp_bands.newCPRefBand("cp_Field_class", CONSTANT_Class); + CPRefBand cp_Field_desc = cp_bands.newCPRefBand("cp_Field_desc", UDELTA5, CONSTANT_NameandType); + CPRefBand cp_Method_class = cp_bands.newCPRefBand("cp_Method_class", CONSTANT_Class); + CPRefBand cp_Method_desc = cp_bands.newCPRefBand("cp_Method_desc", UDELTA5, CONSTANT_NameandType); + CPRefBand cp_Imethod_class = cp_bands.newCPRefBand("cp_Imethod_class", CONSTANT_Class); + CPRefBand cp_Imethod_desc = cp_bands.newCPRefBand("cp_Imethod_desc", UDELTA5, CONSTANT_NameandType); + IntBand cp_MethodHandle_refkind = cp_bands.newIntBand("cp_MethodHandle_refkind", DELTA5); + CPRefBand cp_MethodHandle_member = cp_bands.newCPRefBand("cp_MethodHandle_member", UDELTA5, CONSTANT_AnyMember); + CPRefBand cp_MethodType = cp_bands.newCPRefBand("cp_MethodType", UDELTA5, CONSTANT_Signature); + CPRefBand cp_BootstrapMethod_ref = cp_bands.newCPRefBand("cp_BootstrapMethod_ref", DELTA5, CONSTANT_MethodHandle); + IntBand cp_BootstrapMethod_arg_count = cp_bands.newIntBand("cp_BootstrapMethod_arg_count", UDELTA5); + CPRefBand cp_BootstrapMethod_arg = cp_bands.newCPRefBand("cp_BootstrapMethod_arg", DELTA5, CONSTANT_LoadableValue); + CPRefBand cp_InvokeDynamic_spec = cp_bands.newCPRefBand("cp_InvokeDynamic_spec", DELTA5, CONSTANT_BootstrapMethod); + CPRefBand cp_InvokeDynamic_desc = cp_bands.newCPRefBand("cp_InvokeDynamic_desc", UDELTA5, CONSTANT_NameandType); + + // bands for carrying attribute definitions: + MultiBand attr_definition_bands = all_bands.newMultiBand("(attr_definition_bands)", UNSIGNED5); + ByteBand attr_definition_headers = attr_definition_bands.newByteBand("attr_definition_headers"); + CPRefBand attr_definition_name = attr_definition_bands.newCPRefBand("attr_definition_name", CONSTANT_Utf8); + CPRefBand attr_definition_layout = attr_definition_bands.newCPRefBand("attr_definition_layout", CONSTANT_Utf8); + + // bands for hardwired InnerClasses attribute (shared across the package) + MultiBand ic_bands = all_bands.newMultiBand("(ic_bands)", DELTA5); + CPRefBand ic_this_class = ic_bands.newCPRefBand("ic_this_class", UDELTA5, CONSTANT_Class); + IntBand ic_flags = ic_bands.newIntBand("ic_flags", UNSIGNED5); + // These bands contain data only where flags sets ACC_IC_LONG_FORM: + CPRefBand ic_outer_class = ic_bands.newCPRefBand("ic_outer_class", DELTA5, CONSTANT_Class, NULL_IS_OK); + CPRefBand ic_name = ic_bands.newCPRefBand("ic_name", DELTA5, CONSTANT_Utf8, NULL_IS_OK); + + // bands for carrying class schema information: + MultiBand class_bands = all_bands.newMultiBand("(class_bands)", DELTA5); + CPRefBand class_this = class_bands.newCPRefBand("class_this", CONSTANT_Class); + CPRefBand class_super = class_bands.newCPRefBand("class_super", CONSTANT_Class); + IntBand class_interface_count = class_bands.newIntBand("class_interface_count"); + CPRefBand class_interface = class_bands.newCPRefBand("class_interface", CONSTANT_Class); + + // bands for class members + IntBand class_field_count = class_bands.newIntBand("class_field_count"); + IntBand class_method_count = class_bands.newIntBand("class_method_count"); + + CPRefBand field_descr = class_bands.newCPRefBand("field_descr", CONSTANT_NameandType); + MultiBand field_attr_bands = class_bands.newMultiBand("(field_attr_bands)", UNSIGNED5); + IntBand field_flags_hi = field_attr_bands.newIntBand("field_flags_hi"); + IntBand field_flags_lo = field_attr_bands.newIntBand("field_flags_lo"); + IntBand field_attr_count = field_attr_bands.newIntBand("field_attr_count"); + IntBand field_attr_indexes = field_attr_bands.newIntBand("field_attr_indexes"); + IntBand field_attr_calls = field_attr_bands.newIntBand("field_attr_calls"); + + // bands for predefined field attributes + CPRefBand field_ConstantValue_KQ = field_attr_bands.newCPRefBand("field_ConstantValue_KQ", CONSTANT_FieldSpecific); + CPRefBand field_Signature_RS = field_attr_bands.newCPRefBand("field_Signature_RS", CONSTANT_Signature); + MultiBand field_metadata_bands = field_attr_bands.newMultiBand("(field_metadata_bands)", UNSIGNED5); + MultiBand field_type_metadata_bands = field_attr_bands.newMultiBand("(field_type_metadata_bands)", UNSIGNED5); + + CPRefBand method_descr = class_bands.newCPRefBand("method_descr", MDELTA5, CONSTANT_NameandType); + MultiBand method_attr_bands = class_bands.newMultiBand("(method_attr_bands)", UNSIGNED5); + IntBand method_flags_hi = method_attr_bands.newIntBand("method_flags_hi"); + IntBand method_flags_lo = method_attr_bands.newIntBand("method_flags_lo"); + IntBand method_attr_count = method_attr_bands.newIntBand("method_attr_count"); + IntBand method_attr_indexes = method_attr_bands.newIntBand("method_attr_indexes"); + IntBand method_attr_calls = method_attr_bands.newIntBand("method_attr_calls"); + // band for predefined method attributes + IntBand method_Exceptions_N = method_attr_bands.newIntBand("method_Exceptions_N"); + CPRefBand method_Exceptions_RC = method_attr_bands.newCPRefBand("method_Exceptions_RC", CONSTANT_Class); + CPRefBand method_Signature_RS = method_attr_bands.newCPRefBand("method_Signature_RS", CONSTANT_Signature); + MultiBand method_metadata_bands = method_attr_bands.newMultiBand("(method_metadata_bands)", UNSIGNED5); + // band for predefine method parameters + IntBand method_MethodParameters_NB = method_attr_bands.newIntBand("method_MethodParameters_NB", BYTE1); + CPRefBand method_MethodParameters_name_RUN = method_attr_bands.newCPRefBand("method_MethodParameters_name_RUN", UNSIGNED5, CONSTANT_Utf8, NULL_IS_OK); + IntBand method_MethodParameters_flag_FH = method_attr_bands.newIntBand("method_MethodParameters_flag_FH"); + MultiBand method_type_metadata_bands = method_attr_bands.newMultiBand("(method_type_metadata_bands)", UNSIGNED5); + + MultiBand class_attr_bands = class_bands.newMultiBand("(class_attr_bands)", UNSIGNED5); + IntBand class_flags_hi = class_attr_bands.newIntBand("class_flags_hi"); + IntBand class_flags_lo = class_attr_bands.newIntBand("class_flags_lo"); + IntBand class_attr_count = class_attr_bands.newIntBand("class_attr_count"); + IntBand class_attr_indexes = class_attr_bands.newIntBand("class_attr_indexes"); + IntBand class_attr_calls = class_attr_bands.newIntBand("class_attr_calls"); + // band for predefined SourceFile and other class attributes + CPRefBand class_SourceFile_RUN = class_attr_bands.newCPRefBand("class_SourceFile_RUN", UNSIGNED5, CONSTANT_Utf8, NULL_IS_OK); + CPRefBand class_EnclosingMethod_RC = class_attr_bands.newCPRefBand("class_EnclosingMethod_RC", CONSTANT_Class); + CPRefBand class_EnclosingMethod_RDN = class_attr_bands.newCPRefBand("class_EnclosingMethod_RDN", UNSIGNED5, CONSTANT_NameandType, NULL_IS_OK); + CPRefBand class_Signature_RS = class_attr_bands.newCPRefBand("class_Signature_RS", CONSTANT_Signature); + MultiBand class_metadata_bands = class_attr_bands.newMultiBand("(class_metadata_bands)", UNSIGNED5); + IntBand class_InnerClasses_N = class_attr_bands.newIntBand("class_InnerClasses_N"); + CPRefBand class_InnerClasses_RC = class_attr_bands.newCPRefBand("class_InnerClasses_RC", CONSTANT_Class); + IntBand class_InnerClasses_F = class_attr_bands.newIntBand("class_InnerClasses_F"); + CPRefBand class_InnerClasses_outer_RCN = class_attr_bands.newCPRefBand("class_InnerClasses_outer_RCN", UNSIGNED5, CONSTANT_Class, NULL_IS_OK); + CPRefBand class_InnerClasses_name_RUN = class_attr_bands.newCPRefBand("class_InnerClasses_name_RUN", UNSIGNED5, CONSTANT_Utf8, NULL_IS_OK); + IntBand class_ClassFile_version_minor_H = class_attr_bands.newIntBand("class_ClassFile_version_minor_H"); + IntBand class_ClassFile_version_major_H = class_attr_bands.newIntBand("class_ClassFile_version_major_H"); + MultiBand class_type_metadata_bands = class_attr_bands.newMultiBand("(class_type_metadata_bands)", UNSIGNED5); + + MultiBand code_bands = class_bands.newMultiBand("(code_bands)", UNSIGNED5); + ByteBand code_headers = code_bands.newByteBand("code_headers"); //BYTE1 + IntBand code_max_stack = code_bands.newIntBand("code_max_stack", UNSIGNED5); + IntBand code_max_na_locals = code_bands.newIntBand("code_max_na_locals", UNSIGNED5); + IntBand code_handler_count = code_bands.newIntBand("code_handler_count", UNSIGNED5); + IntBand code_handler_start_P = code_bands.newIntBand("code_handler_start_P", BCI5); + IntBand code_handler_end_PO = code_bands.newIntBand("code_handler_end_PO", BRANCH5); + IntBand code_handler_catch_PO = code_bands.newIntBand("code_handler_catch_PO", BRANCH5); + CPRefBand code_handler_class_RCN = code_bands.newCPRefBand("code_handler_class_RCN", UNSIGNED5, CONSTANT_Class, NULL_IS_OK); + + MultiBand code_attr_bands = class_bands.newMultiBand("(code_attr_bands)", UNSIGNED5); + IntBand code_flags_hi = code_attr_bands.newIntBand("code_flags_hi"); + IntBand code_flags_lo = code_attr_bands.newIntBand("code_flags_lo"); + IntBand code_attr_count = code_attr_bands.newIntBand("code_attr_count"); + IntBand code_attr_indexes = code_attr_bands.newIntBand("code_attr_indexes"); + IntBand code_attr_calls = code_attr_bands.newIntBand("code_attr_calls"); + + MultiBand stackmap_bands = code_attr_bands.newMultiBand("(StackMapTable_bands)", UNSIGNED5); + IntBand code_StackMapTable_N = stackmap_bands.newIntBand("code_StackMapTable_N"); + IntBand code_StackMapTable_frame_T = stackmap_bands.newIntBand("code_StackMapTable_frame_T",BYTE1); + IntBand code_StackMapTable_local_N = stackmap_bands.newIntBand("code_StackMapTable_local_N"); + IntBand code_StackMapTable_stack_N = stackmap_bands.newIntBand("code_StackMapTable_stack_N"); + IntBand code_StackMapTable_offset = stackmap_bands.newIntBand("code_StackMapTable_offset", UNSIGNED5); + IntBand code_StackMapTable_T = stackmap_bands.newIntBand("code_StackMapTable_T", BYTE1); + CPRefBand code_StackMapTable_RC = stackmap_bands.newCPRefBand("code_StackMapTable_RC", CONSTANT_Class); + IntBand code_StackMapTable_P = stackmap_bands.newIntBand("code_StackMapTable_P", BCI5); + + // bands for predefined LineNumberTable attribute + IntBand code_LineNumberTable_N = code_attr_bands.newIntBand("code_LineNumberTable_N"); + IntBand code_LineNumberTable_bci_P = code_attr_bands.newIntBand("code_LineNumberTable_bci_P", BCI5); + IntBand code_LineNumberTable_line = code_attr_bands.newIntBand("code_LineNumberTable_line"); + + // bands for predefined LocalVariable{Type}Table attributes + IntBand code_LocalVariableTable_N = code_attr_bands.newIntBand("code_LocalVariableTable_N"); + IntBand code_LocalVariableTable_bci_P = code_attr_bands.newIntBand("code_LocalVariableTable_bci_P", BCI5); + IntBand code_LocalVariableTable_span_O = code_attr_bands.newIntBand("code_LocalVariableTable_span_O", BRANCH5); + CPRefBand code_LocalVariableTable_name_RU = code_attr_bands.newCPRefBand("code_LocalVariableTable_name_RU", CONSTANT_Utf8); + CPRefBand code_LocalVariableTable_type_RS = code_attr_bands.newCPRefBand("code_LocalVariableTable_type_RS", CONSTANT_Signature); + IntBand code_LocalVariableTable_slot = code_attr_bands.newIntBand("code_LocalVariableTable_slot"); + IntBand code_LocalVariableTypeTable_N = code_attr_bands.newIntBand("code_LocalVariableTypeTable_N"); + IntBand code_LocalVariableTypeTable_bci_P = code_attr_bands.newIntBand("code_LocalVariableTypeTable_bci_P", BCI5); + IntBand code_LocalVariableTypeTable_span_O = code_attr_bands.newIntBand("code_LocalVariableTypeTable_span_O", BRANCH5); + CPRefBand code_LocalVariableTypeTable_name_RU = code_attr_bands.newCPRefBand("code_LocalVariableTypeTable_name_RU", CONSTANT_Utf8); + CPRefBand code_LocalVariableTypeTable_type_RS = code_attr_bands.newCPRefBand("code_LocalVariableTypeTable_type_RS", CONSTANT_Signature); + IntBand code_LocalVariableTypeTable_slot = code_attr_bands.newIntBand("code_LocalVariableTypeTable_slot"); + MultiBand code_type_metadata_bands = code_attr_bands.newMultiBand("(code_type_metadata_bands)", UNSIGNED5); + + // bands for bytecodes + MultiBand bc_bands = all_bands.newMultiBand("(byte_codes)", UNSIGNED5); + ByteBand bc_codes = bc_bands.newByteBand("bc_codes"); //BYTE1 + // remaining bands provide typed opcode fields required by the bc_codes + + IntBand bc_case_count = bc_bands.newIntBand("bc_case_count"); // *switch + IntBand bc_case_value = bc_bands.newIntBand("bc_case_value", DELTA5); // *switch + ByteBand bc_byte = bc_bands.newByteBand("bc_byte"); //BYTE1 // bipush, iinc, *newarray + IntBand bc_short = bc_bands.newIntBand("bc_short", DELTA5); // sipush, wide iinc + IntBand bc_local = bc_bands.newIntBand("bc_local"); // *load, *store, iinc, ret + IntBand bc_label = bc_bands.newIntBand("bc_label", BRANCH5); // if*, goto*, jsr*, *switch + + // Most CP refs exhibit some correlation, and benefit from delta coding. + // The notable exceptions are class and method references. + + // ldc* operands: + CPRefBand bc_intref = bc_bands.newCPRefBand("bc_intref", DELTA5, CONSTANT_Integer); + CPRefBand bc_floatref = bc_bands.newCPRefBand("bc_floatref", DELTA5, CONSTANT_Float); + CPRefBand bc_longref = bc_bands.newCPRefBand("bc_longref", DELTA5, CONSTANT_Long); + CPRefBand bc_doubleref = bc_bands.newCPRefBand("bc_doubleref", DELTA5, CONSTANT_Double); + CPRefBand bc_stringref = bc_bands.newCPRefBand("bc_stringref", DELTA5, CONSTANT_String); + CPRefBand bc_loadablevalueref = bc_bands.newCPRefBand("bc_loadablevalueref", DELTA5, CONSTANT_LoadableValue); + + // nulls produced by bc_classref are taken to mean the current class + CPRefBand bc_classref = bc_bands.newCPRefBand("bc_classref", UNSIGNED5, CONSTANT_Class, NULL_IS_OK); // new, *anew*, c*cast, i*of, ldc + CPRefBand bc_fieldref = bc_bands.newCPRefBand("bc_fieldref", DELTA5, CONSTANT_Fieldref); // get*, put* + CPRefBand bc_methodref = bc_bands.newCPRefBand("bc_methodref", CONSTANT_Methodref); // invoke[vs]* + CPRefBand bc_imethodref = bc_bands.newCPRefBand("bc_imethodref", DELTA5, CONSTANT_InterfaceMethodref); // invokeinterface + CPRefBand bc_indyref = bc_bands.newCPRefBand("bc_indyref", DELTA5, CONSTANT_InvokeDynamic); // invokedynamic + + // _self_linker_op family + CPRefBand bc_thisfield = bc_bands.newCPRefBand("bc_thisfield", CONSTANT_None); // any field within cur. class + CPRefBand bc_superfield = bc_bands.newCPRefBand("bc_superfield", CONSTANT_None); // any field within superclass + CPRefBand bc_thismethod = bc_bands.newCPRefBand("bc_thismethod", CONSTANT_None); // any method within cur. class + CPRefBand bc_supermethod = bc_bands.newCPRefBand("bc_supermethod", CONSTANT_None); // any method within superclass + // bc_invokeinit family: + IntBand bc_initref = bc_bands.newIntBand("bc_initref"); + // escapes + CPRefBand bc_escref = bc_bands.newCPRefBand("bc_escref", CONSTANT_All); + IntBand bc_escrefsize = bc_bands.newIntBand("bc_escrefsize"); + IntBand bc_escsize = bc_bands.newIntBand("bc_escsize"); + ByteBand bc_escbyte = bc_bands.newByteBand("bc_escbyte"); + + // bands for carrying resource files and file attributes: + MultiBand file_bands = all_bands.newMultiBand("(file_bands)", UNSIGNED5); + CPRefBand file_name = file_bands.newCPRefBand("file_name", CONSTANT_Utf8); + IntBand file_size_hi = file_bands.newIntBand("file_size_hi"); + IntBand file_size_lo = file_bands.newIntBand("file_size_lo"); + IntBand file_modtime = file_bands.newIntBand("file_modtime", DELTA5); + IntBand file_options = file_bands.newIntBand("file_options"); + ByteBand file_bits = file_bands.newByteBand("file_bits"); + + // End of band definitions! + + /** Given CP indexes, distribute tag-specific indexes to bands. */ + protected void setBandIndexes() { + // Handle prior calls to setBandIndex: + for (Object[] need : needPredefIndex) { + CPRefBand b = (CPRefBand) need[0]; + Byte which = (Byte) need[1]; + b.setIndex(getCPIndex(which.byteValue())); + } + needPredefIndex = null; // no more predefs + + if (verbose > 3) { + printCDecl(all_bands); + } + } + + protected void setBandIndex(CPRefBand b, byte which) { + Object[] need = { b, Byte.valueOf(which) }; + if (which == CONSTANT_FieldSpecific) { + // I.e., attribute layouts KQ (no null) or KQN (null ok). + allKQBands.add(b); + } else if (needPredefIndex != null) { + needPredefIndex.add(need); + } else { + // Not in predefinition mode; getCPIndex now works. + b.setIndex(getCPIndex(which)); + } + } + + protected void setConstantValueIndex(Field f) { + Index ix = null; + if (f != null) { + byte tag = f.getLiteralTag(); + ix = getCPIndex(tag); + if (verbose > 2) + Utils.log.fine("setConstantValueIndex "+f+" "+ConstantPool.tagName(tag)+" => "+ix); + assert(ix != null); + } + // Typically, allKQBands is the singleton of field_ConstantValue_KQ. + for (CPRefBand xxx_KQ : allKQBands) { + xxx_KQ.setIndex(ix); + } + } + + // Table of bands which contain metadata. + protected MultiBand[] metadataBands = new MultiBand[ATTR_CONTEXT_LIMIT]; + { + metadataBands[ATTR_CONTEXT_CLASS] = class_metadata_bands; + metadataBands[ATTR_CONTEXT_FIELD] = field_metadata_bands; + metadataBands[ATTR_CONTEXT_METHOD] = method_metadata_bands; + } + // Table of bands which contains type_metadata (TypeAnnotations) + protected MultiBand[] typeMetadataBands = new MultiBand[ATTR_CONTEXT_LIMIT]; + { + typeMetadataBands[ATTR_CONTEXT_CLASS] = class_type_metadata_bands; + typeMetadataBands[ATTR_CONTEXT_FIELD] = field_type_metadata_bands; + typeMetadataBands[ATTR_CONTEXT_METHOD] = method_type_metadata_bands; + typeMetadataBands[ATTR_CONTEXT_CODE] = code_type_metadata_bands; + } + + // Attribute layouts. + public static final int ADH_CONTEXT_MASK = 0x3; // (ad_hdr & ADH_CONTEXT_MASK) + public static final int ADH_BIT_SHIFT = 0x2; // (ad_hdr >> ADH_BIT_SHIFT) + public static final int ADH_BIT_IS_LSB = 1; + public static final int ATTR_INDEX_OVERFLOW = -1; + + public int[] attrIndexLimit = new int[ATTR_CONTEXT_LIMIT]; + // Each index limit is either 32 or 63, depending on AO_HAVE_XXX_FLAGS_HI. + + // Which flag bits are taken over by attributes? + protected long[] attrFlagMask = new long[ATTR_CONTEXT_LIMIT]; + // Which flag bits have been taken over explicitly? + protected long[] attrDefSeen = new long[ATTR_CONTEXT_LIMIT]; + + // What pseudo-attribute bits are there to watch for? + protected int[] attrOverflowMask = new int[ATTR_CONTEXT_LIMIT]; + protected int attrClassFileVersionMask; + + // Mapping from Attribute.Layout to Band[] (layout element bands). + protected Map attrBandTable = new HashMap<>(); + + // Well-known attributes: + protected final Attribute.Layout attrCodeEmpty; + protected final Attribute.Layout attrInnerClassesEmpty; + protected final Attribute.Layout attrClassFileVersion; + protected final Attribute.Layout attrConstantValue; + + // Mapping from Attribute.Layout to Integer (inverse of attrDefs) + Map attrIndexTable = new HashMap<>(); + + // Mapping from attribute index (<32 are flag bits) to attributes. + protected List> attrDefs = + new FixedList<>(ATTR_CONTEXT_LIMIT); + { + for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { + assert(attrIndexLimit[i] == 0); + attrIndexLimit[i] = 32; // just for the sake of predefs. + attrDefs.set(i, new ArrayList<>(Collections.nCopies( + attrIndexLimit[i], (Attribute.Layout)null))); + + } + + // Add predefined attribute definitions: + attrInnerClassesEmpty = + predefineAttribute(CLASS_ATTR_InnerClasses, ATTR_CONTEXT_CLASS, null, + "InnerClasses", ""); + assert(attrInnerClassesEmpty == Package.attrInnerClassesEmpty); + predefineAttribute(CLASS_ATTR_SourceFile, ATTR_CONTEXT_CLASS, + new Band[] { class_SourceFile_RUN }, + "SourceFile", "RUNH"); + predefineAttribute(CLASS_ATTR_EnclosingMethod, ATTR_CONTEXT_CLASS, + new Band[] { + class_EnclosingMethod_RC, + class_EnclosingMethod_RDN + }, + "EnclosingMethod", "RCHRDNH"); + attrClassFileVersion = + predefineAttribute(CLASS_ATTR_ClassFile_version, ATTR_CONTEXT_CLASS, + new Band[] { + class_ClassFile_version_minor_H, + class_ClassFile_version_major_H + }, + ".ClassFile.version", "HH"); + predefineAttribute(X_ATTR_Signature, ATTR_CONTEXT_CLASS, + new Band[] { class_Signature_RS }, + "Signature", "RSH"); + predefineAttribute(X_ATTR_Deprecated, ATTR_CONTEXT_CLASS, null, + "Deprecated", ""); + //predefineAttribute(X_ATTR_Synthetic, ATTR_CONTEXT_CLASS, null, + // "Synthetic", ""); + predefineAttribute(X_ATTR_OVERFLOW, ATTR_CONTEXT_CLASS, null, + ".Overflow", ""); + attrConstantValue = + predefineAttribute(FIELD_ATTR_ConstantValue, ATTR_CONTEXT_FIELD, + new Band[] { field_ConstantValue_KQ }, + "ConstantValue", "KQH"); + predefineAttribute(X_ATTR_Signature, ATTR_CONTEXT_FIELD, + new Band[] { field_Signature_RS }, + "Signature", "RSH"); + predefineAttribute(X_ATTR_Deprecated, ATTR_CONTEXT_FIELD, null, + "Deprecated", ""); + //predefineAttribute(X_ATTR_Synthetic, ATTR_CONTEXT_FIELD, null, + // "Synthetic", ""); + predefineAttribute(X_ATTR_OVERFLOW, ATTR_CONTEXT_FIELD, null, + ".Overflow", ""); + attrCodeEmpty = + predefineAttribute(METHOD_ATTR_Code, ATTR_CONTEXT_METHOD, null, + "Code", ""); + predefineAttribute(METHOD_ATTR_Exceptions, ATTR_CONTEXT_METHOD, + new Band[] { + method_Exceptions_N, + method_Exceptions_RC + }, + "Exceptions", "NH[RCH]"); + predefineAttribute(METHOD_ATTR_MethodParameters, ATTR_CONTEXT_METHOD, + new Band[]{ + method_MethodParameters_NB, + method_MethodParameters_name_RUN, + method_MethodParameters_flag_FH + }, + "MethodParameters", "NB[RUNHFH]"); + assert(attrCodeEmpty == Package.attrCodeEmpty); + predefineAttribute(X_ATTR_Signature, ATTR_CONTEXT_METHOD, + new Band[] { method_Signature_RS }, + "Signature", "RSH"); + predefineAttribute(X_ATTR_Deprecated, ATTR_CONTEXT_METHOD, null, + "Deprecated", ""); + //predefineAttribute(X_ATTR_Synthetic, ATTR_CONTEXT_METHOD, null, + // "Synthetic", ""); + predefineAttribute(X_ATTR_OVERFLOW, ATTR_CONTEXT_METHOD, null, + ".Overflow", ""); + + for (int ctype = 0; ctype < ATTR_CONTEXT_LIMIT; ctype++) { + MultiBand xxx_metadata_bands = metadataBands[ctype]; + if (ctype != ATTR_CONTEXT_CODE) { + // These arguments cause the bands to be built + // automatically for this complicated layout: + predefineAttribute(X_ATTR_RuntimeVisibleAnnotations, + ATTR_CONTEXT_NAME[ctype]+"_RVA_", + xxx_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeVisibleAnnotations")); + predefineAttribute(X_ATTR_RuntimeInvisibleAnnotations, + ATTR_CONTEXT_NAME[ctype]+"_RIA_", + xxx_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeInvisibleAnnotations")); + + if (ctype == ATTR_CONTEXT_METHOD) { + predefineAttribute(METHOD_ATTR_RuntimeVisibleParameterAnnotations, + "method_RVPA_", xxx_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeVisibleParameterAnnotations")); + predefineAttribute(METHOD_ATTR_RuntimeInvisibleParameterAnnotations, + "method_RIPA_", xxx_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeInvisibleParameterAnnotations")); + predefineAttribute(METHOD_ATTR_AnnotationDefault, + "method_AD_", xxx_metadata_bands, + Attribute.lookup(null, ctype, + "AnnotationDefault")); + } + } + // All contexts have these + MultiBand xxx_type_metadata_bands = typeMetadataBands[ctype]; + predefineAttribute(X_ATTR_RuntimeVisibleTypeAnnotations, + ATTR_CONTEXT_NAME[ctype] + "_RVTA_", + xxx_type_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeVisibleTypeAnnotations")); + predefineAttribute(X_ATTR_RuntimeInvisibleTypeAnnotations, + ATTR_CONTEXT_NAME[ctype] + "_RITA_", + xxx_type_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeInvisibleTypeAnnotations")); + } + + + Attribute.Layout stackMapDef = Attribute.lookup(null, ATTR_CONTEXT_CODE, "StackMapTable").layout(); + predefineAttribute(CODE_ATTR_StackMapTable, ATTR_CONTEXT_CODE, + stackmap_bands.toArray(), + stackMapDef.name(), stackMapDef.layout()); + + predefineAttribute(CODE_ATTR_LineNumberTable, ATTR_CONTEXT_CODE, + new Band[] { + code_LineNumberTable_N, + code_LineNumberTable_bci_P, + code_LineNumberTable_line + }, + "LineNumberTable", "NH[PHH]"); + predefineAttribute(CODE_ATTR_LocalVariableTable, ATTR_CONTEXT_CODE, + new Band[] { + code_LocalVariableTable_N, + code_LocalVariableTable_bci_P, + code_LocalVariableTable_span_O, + code_LocalVariableTable_name_RU, + code_LocalVariableTable_type_RS, + code_LocalVariableTable_slot + }, + "LocalVariableTable", "NH[PHOHRUHRSHH]"); + predefineAttribute(CODE_ATTR_LocalVariableTypeTable, ATTR_CONTEXT_CODE, + new Band[] { + code_LocalVariableTypeTable_N, + code_LocalVariableTypeTable_bci_P, + code_LocalVariableTypeTable_span_O, + code_LocalVariableTypeTable_name_RU, + code_LocalVariableTypeTable_type_RS, + code_LocalVariableTypeTable_slot + }, + "LocalVariableTypeTable", "NH[PHOHRUHRSHH]"); + predefineAttribute(X_ATTR_OVERFLOW, ATTR_CONTEXT_CODE, null, + ".Overflow", ""); + + // Clear the record of having seen these definitions, + // so they may be redefined without error. + for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { + attrDefSeen[i] = 0; + } + + // Set up the special masks: + for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { + attrOverflowMask[i] = (1< 0) Utils.log.fine("Legacy package version"); + // Revoke definition of pre-1.6 attribute type. + undefineAttribute(CODE_ATTR_StackMapTable, ATTR_CONTEXT_CODE); + } + } + + protected void initAttrIndexLimit() { + for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { + assert(attrIndexLimit[i] == 0); // decide on it now! + attrIndexLimit[i] = (haveFlagsHi(i)? 63: 32); + List defList = attrDefs.get(i); + assert(defList.size() == 32); // all predef indexes are <32 + int addMore = attrIndexLimit[i] - defList.size(); + defList.addAll(Collections.nCopies(addMore, (Attribute.Layout) null)); + } + } + + protected boolean haveFlagsHi(int ctype) { + int mask = 1<<(LG_AO_HAVE_XXX_FLAGS_HI+ctype); + switch (ctype) { + case ATTR_CONTEXT_CLASS: + assert(mask == AO_HAVE_CLASS_FLAGS_HI); break; + case ATTR_CONTEXT_FIELD: + assert(mask == AO_HAVE_FIELD_FLAGS_HI); break; + case ATTR_CONTEXT_METHOD: + assert(mask == AO_HAVE_METHOD_FLAGS_HI); break; + case ATTR_CONTEXT_CODE: + assert(mask == AO_HAVE_CODE_FLAGS_HI); break; + default: + assert(false); + } + return testBit(archiveOptions, mask); + } + + protected List getPredefinedAttrs(int ctype) { + assert(attrIndexLimit[ctype] != 0); + List res = new ArrayList<>(attrIndexLimit[ctype]); + // Remove nulls and non-predefs. + for (int ai = 0; ai < attrIndexLimit[ctype]; ai++) { + if (testBit(attrDefSeen[ctype], 1L<= attrIndexLimit[ctype]) return false; + // If the bit is set, it was explicitly def'd. + if (testBit(attrDefSeen[ctype], 1L<= 0) { + setAttributeLayoutIndex(def, index); + } + if (ab == null) { + ab = new Band[0]; + } + assert(attrBandTable.get(def) == null); // no redef + attrBandTable.put(def, ab); + assert(def.bandCount == ab.length) + : (def+" // "+Arrays.asList(ab)); + // Let's make sure the band types match: + assert(assertBandOKForElems(ab, def.elems)); + return def; + } + + // This version takes bandPrefix/addHere instead of prebuilt Band[] ab. + private + Attribute.Layout predefineAttribute(int index, + String bandPrefix, MultiBand addHere, + Attribute attr) { + //Attribute.Layout def = Attribute.find(ctype, name, layout).layout(); + Attribute.Layout def = attr.layout(); + int ctype = def.ctype(); + return predefineAttribute(index, ctype, + makeNewAttributeBands(bandPrefix, def, addHere), + def.name(), def.layout()); + } + + private + void undefineAttribute(int index, int ctype) { + if (verbose > 1) { + System.out.println("Removing predefined "+ATTR_CONTEXT_NAME[ctype]+ + " attribute on bit "+index); + } + List defList = attrDefs.get(ctype); + Attribute.Layout def = defList.get(index); + assert(def != null); + defList.set(index, null); + attrIndexTable.put(def, null); + // Clear the def bit. (For predefs, it's already clear.) + assert(index < 64); + attrDefSeen[ctype] &= ~(1L< 1) + Utils.log.fine("Making new bands for "+def); + Band[] newAB = makeNewAttributeBands(pfx, def, + xxx_attr_bands); + assert(newAB.length == def.bandCount); + Band[] prevAB = attrBandTable.put(def, newAB); + if (prevAB != null) { + // We won't be using these predefined bands. + for (int j = 0; j < prevAB.length; j++) { + prevAB[j].doneWithUnusedBand(); + } + } + } + } + //System.out.println(prevForAssertMap); + } + private + Band[] makeNewAttributeBands(String pfx, Attribute.Layout def, + MultiBand addHere) { + int base = addHere.size(); + makeNewAttributeBands(pfx, def.elems, addHere); + int nb = addHere.size() - base; + Band[] newAB = new Band[nb]; + for (int i = 0; i < nb; i++) { + newAB[i] = addHere.get(base+i); + } + return newAB; + } + // Recursive helper, operates on a "body" or other sequence of elems: + private + void makeNewAttributeBands(String pfx, Attribute.Layout.Element[] elems, + MultiBand ab) { + for (int i = 0; i < elems.length; i++) { + Attribute.Layout.Element e = elems[i]; + String name = pfx+ab.size()+"_"+e.layout; + { + int tem; + if ((tem = name.indexOf('[')) > 0) + name = name.substring(0, tem); + if ((tem = name.indexOf('(')) > 0) + name = name.substring(0, tem); + if (name.endsWith("H")) + name = name.substring(0, name.length()-1); + } + Band nb; + switch (e.kind) { + case Attribute.EK_INT: + nb = newElemBand(e, name, ab); + break; + case Attribute.EK_BCI: + if (!e.flagTest(Attribute.EF_DELTA)) { + // PH: transmit R(bci), store bci + nb = ab.newIntBand(name, BCI5); + } else { + // POH: transmit D(R(bci)), store bci + nb = ab.newIntBand(name, BRANCH5); + } + // Note: No case for BYTE1 here. + break; + case Attribute.EK_BCO: + // OH: transmit D(R(bci)), store D(bci) + nb = ab.newIntBand(name, BRANCH5); + // Note: No case for BYTE1 here. + break; + case Attribute.EK_FLAG: + assert(!e.flagTest(Attribute.EF_SIGN)); + nb = newElemBand(e, name, ab); + break; + case Attribute.EK_REPL: + assert(!e.flagTest(Attribute.EF_SIGN)); + nb = newElemBand(e, name, ab); + makeNewAttributeBands(pfx, e.body, ab); + break; + case Attribute.EK_UN: + nb = newElemBand(e, name, ab); + makeNewAttributeBands(pfx, e.body, ab); + break; + case Attribute.EK_CASE: + if (!e.flagTest(Attribute.EF_BACK)) { + // If it's not a duplicate body, make the bands. + makeNewAttributeBands(pfx, e.body, ab); + } + continue; // no new band to make + case Attribute.EK_REF: + byte refKind = e.refKind; + boolean nullOK = e.flagTest(Attribute.EF_NULL); + nb = ab.newCPRefBand(name, UNSIGNED5, refKind, nullOK); + // Note: No case for BYTE1 here. + break; + case Attribute.EK_CALL: + continue; // no new band to make + case Attribute.EK_CBLE: + makeNewAttributeBands(pfx, e.body, ab); + continue; // no new band to make + default: assert(false); continue; + } + if (verbose > 1) { + Utils.log.fine("New attribute band "+nb); + } + } + } + private + Band newElemBand(Attribute.Layout.Element e, String name, MultiBand ab) { + if (e.flagTest(Attribute.EF_SIGN)) { + return ab.newIntBand(name, SIGNED5); + } else if (e.len == 1) { + return ab.newIntBand(name, BYTE1); // Not ByteBand, please. + } else { + return ab.newIntBand(name, UNSIGNED5); + } + } + + protected int setAttributeLayoutIndex(Attribute.Layout def, int index) { + int ctype = def.ctype; + assert(ATTR_INDEX_OVERFLOW <= index && index < attrIndexLimit[ctype]); + List defList = attrDefs.get(ctype); + if (index == ATTR_INDEX_OVERFLOW) { + // Overflow attribute. + index = defList.size(); + defList.add(def); + if (verbose > 0) + Utils.log.info("Adding new attribute at "+def +": "+index); + attrIndexTable.put(def, index); + return index; + } + + // Detect redefinitions: + if (testBit(attrDefSeen[ctype], 1L< (attrClassFileVersionMask == 0? 2:0)) + Utils.log.fine("Fixing new attribute at "+index + +": "+def + +(defList.get(index) == null? "": + "; replacing "+defList.get(index))); + attrFlagMask[ctype] |= (1L<= shortCodeLimits.length) return LONG_CODE_HEADER; + int siglen = code.getMethod().getArgumentSize(); + assert(l0 >= siglen); // enough locals for signature! + if (l0 < siglen) return LONG_CODE_HEADER; + int l1 = l0 - siglen; // do not count locals required by the signature + int lims = shortCodeLimits[h][0]; + int liml = shortCodeLimits[h][1]; + if (s >= lims || l1 >= liml) return LONG_CODE_HEADER; + int sc = shortCodeHeader_h_base(h); + sc += s + lims*l1; + if (sc > 255) return LONG_CODE_HEADER; + assert(shortCodeHeader_max_stack(sc) == s); + assert(shortCodeHeader_max_na_locals(sc) == l1); + assert(shortCodeHeader_handler_count(sc) == h); + return sc; + } + + static final int LONG_CODE_HEADER = 0; + static int shortCodeHeader_handler_count(int sc) { + assert(sc > 0 && sc <= 255); + for (int h = 0; ; h++) { + if (sc < shortCodeHeader_h_base(h+1)) + return h; + } + } + static int shortCodeHeader_max_stack(int sc) { + int h = shortCodeHeader_handler_count(sc); + int lims = shortCodeLimits[h][0]; + return (sc - shortCodeHeader_h_base(h)) % lims; + } + static int shortCodeHeader_max_na_locals(int sc) { + int h = shortCodeHeader_handler_count(sc); + int lims = shortCodeLimits[h][0]; + return (sc - shortCodeHeader_h_base(h)) / lims; + } + + private static int shortCodeHeader_h_base(int h) { + assert(h <= shortCodeLimits.length); + int sc = 1; + for (int h0 = 0; h0 < h; h0++) { + int lims = shortCodeLimits[h0][0]; + int liml = shortCodeLimits[h0][1]; + sc += lims * liml; + } + return sc; + } + + // utilities for accessing the bc_label band: + protected void putLabel(IntBand bc_label, Code c, int pc, int targetPC) { + bc_label.putInt(c.encodeBCI(targetPC) - c.encodeBCI(pc)); + } + protected int getLabel(IntBand bc_label, Code c, int pc) { + return c.decodeBCI(bc_label.getInt() + c.encodeBCI(pc)); + } + + protected CPRefBand getCPRefOpBand(int bc) { + switch (Instruction.getCPRefOpTag(bc)) { + case CONSTANT_Class: + return bc_classref; + case CONSTANT_Fieldref: + return bc_fieldref; + case CONSTANT_Methodref: + return bc_methodref; + case CONSTANT_InterfaceMethodref: + return bc_imethodref; + case CONSTANT_InvokeDynamic: + return bc_indyref; + case CONSTANT_LoadableValue: + switch (bc) { + case _ildc: case _ildc_w: + return bc_intref; + case _fldc: case _fldc_w: + return bc_floatref; + case _lldc2_w: + return bc_longref; + case _dldc2_w: + return bc_doubleref; + case _sldc: case _sldc_w: + return bc_stringref; + case _cldc: case _cldc_w: + return bc_classref; + case _qldc: case _qldc_w: + return bc_loadablevalueref; + } + break; + } + assert(false); + return null; + } + + protected CPRefBand selfOpRefBand(int self_bc) { + assert(Instruction.isSelfLinkerOp(self_bc)); + int idx = (self_bc - _self_linker_op); + boolean isSuper = (idx >= _self_linker_super_flag); + if (isSuper) idx -= _self_linker_super_flag; + boolean isAload = (idx >= _self_linker_aload_flag); + if (isAload) idx -= _self_linker_aload_flag; + int origBC = _first_linker_op + idx; + boolean isField = Instruction.isFieldOp(origBC); + if (!isSuper) + return isField? bc_thisfield: bc_thismethod; + else + return isField? bc_superfield: bc_supermethod; + } + + //////////////////////////////////////////////////////////////////// + + static int nextSeqForDebug; + static File dumpDir = null; + static OutputStream getDumpStream(Band b, String ext) throws IOException { + return getDumpStream(b.name, b.seqForDebug, ext, b); + } + static OutputStream getDumpStream(Index ix, String ext) throws IOException { + if (ix.size() == 0) return new ByteArrayOutputStream(); + int seq = ConstantPool.TAG_ORDER[ix.cpMap[0].tag]; + return getDumpStream(ix.debugName, seq, ext, ix); + } + static OutputStream getDumpStream(String name, int seq, String ext, Object b) throws IOException { + if (dumpDir == null) { + dumpDir = File.createTempFile("BD_", "", new File(".")); + dumpDir.delete(); + if (dumpDir.mkdir()) + Utils.log.info("Dumping bands to "+dumpDir); + } + name = name.replace('(', ' ').replace(')', ' '); + name = name.replace('/', ' '); + name = name.replace('*', ' '); + name = name.trim().replace(' ','_'); + name = ((10000+seq) + "_" + name).substring(1); + File dumpFile = new File(dumpDir, name+ext); + Utils.log.info("Dumping "+b+" to "+dumpFile); + return new BufferedOutputStream(new FileOutputStream(dumpFile)); + } + + // DEBUG ONLY: Validate me at each length change. + static boolean assertCanChangeLength(Band b) { + switch (b.phase) { + case COLLECT_PHASE: + case READ_PHASE: + return true; + } + return false; + } + + // DEBUG ONLY: Validate a phase. + static boolean assertPhase(Band b, int phaseExpected) { + if (b.phase() != phaseExpected) { + Utils.log.warning("phase expected "+phaseExpected+" was "+b.phase()+" in "+b); + return false; + } + return true; + } + + + // DEBUG ONLY: Tells whether verbosity is turned on. + static int verbose() { + return Utils.currentPropMap().getInteger(Utils.DEBUG_VERBOSE); + } + + + // DEBUG ONLY: Validate me at each phase change. + static boolean assertPhaseChangeOK(Band b, int p0, int p1) { + switch (p0*10+p1) { + /// Writing phases: + case NO_PHASE*10+COLLECT_PHASE: + // Ready to collect data from the input classes. + assert(!b.isReader()); + assert(b.capacity() >= 0); + assert(b.length() == 0); + return true; + case COLLECT_PHASE*10+FROZEN_PHASE: + case FROZEN_PHASE*10+FROZEN_PHASE: + assert(b.length() == 0); + return true; + case COLLECT_PHASE*10+WRITE_PHASE: + case FROZEN_PHASE*10+WRITE_PHASE: + // Data is all collected. Ready to write bytes to disk. + return true; + case WRITE_PHASE*10+DONE_PHASE: + // Done writing to disk. Ready to reset, in principle. + return true; + + /// Reading phases: + case NO_PHASE*10+EXPECT_PHASE: + assert(b.isReader()); + assert(b.capacity() < 0); + return true; + case EXPECT_PHASE*10+READ_PHASE: + // Ready to read values from disk. + assert(Math.max(0,b.capacity()) >= b.valuesExpected()); + assert(b.length() <= 0); + return true; + case READ_PHASE*10+DISBURSE_PHASE: + // Ready to disburse values. + assert(b.valuesRemainingForDebug() == b.length()); + return true; + case DISBURSE_PHASE*10+DONE_PHASE: + // Done disbursing values. Ready to reset, in principle. + assert(assertDoneDisbursing(b)); + return true; + } + if (p0 == p1) + Utils.log.warning("Already in phase "+p0); + else + Utils.log.warning("Unexpected phase "+p0+" -> "+p1); + return false; + } + + private static boolean assertDoneDisbursing(Band b) { + if (b.phase != DISBURSE_PHASE) { + Utils.log.warning("assertDoneDisbursing: still in phase "+b.phase+": "+b); + if (verbose() <= 1) return false; // fail now + } + int left = b.valuesRemainingForDebug(); + if (left > 0) { + Utils.log.warning("assertDoneDisbursing: "+left+" values left in "+b); + if (verbose() <= 1) return false; // fail now + } + if (b instanceof MultiBand) { + MultiBand mb = (MultiBand) b; + for (int i = 0; i < mb.bandCount; i++) { + Band sub = mb.bands[i]; + if (sub.phase != DONE_PHASE) { + Utils.log.warning("assertDoneDisbursing: sub-band still in phase "+sub.phase+": "+sub); + if (verbose() <= 1) return false; // fail now + } + } + } + return true; + } + + private static void printCDecl(Band b) { + if (b instanceof MultiBand) { + MultiBand mb = (MultiBand) b; + for (int i = 0; i < mb.bandCount; i++) { + printCDecl(mb.bands[i]); + } + return; + } + String ixS = "NULL"; + if (b instanceof CPRefBand) { + Index ix = ((CPRefBand)b).index; + if (ix != null) ixS = "INDEX("+ix.debugName+")"; + } + Coding[] knownc = { BYTE1, CHAR3, BCI5, BRANCH5, UNSIGNED5, + UDELTA5, SIGNED5, DELTA5, MDELTA5 }; + String[] knowns = { "BYTE1", "CHAR3", "BCI5", "BRANCH5", "UNSIGNED5", + "UDELTA5", "SIGNED5", "DELTA5", "MDELTA5" }; + Coding rc = b.regularCoding; + int rci = Arrays.asList(knownc).indexOf(rc); + String cstr; + if (rci >= 0) + cstr = knowns[rci]; + else + cstr = "CODING"+rc.keyString(); + System.out.println(" BAND_INIT(\""+b.name()+"\"" + +", "+cstr+", "+ixS+"),"); + } + + private Map prevForAssertMap; + + // DEBUG ONLY: Record something about the band order. + boolean notePrevForAssert(Band b, Band p) { + if (prevForAssertMap == null) + prevForAssertMap = new HashMap<>(); + prevForAssertMap.put(b, p); + return true; + } + + // DEBUG ONLY: Validate next input band, ensure bands are read in sequence + private boolean assertReadyToReadFrom(Band b, InputStream in) throws IOException { + Band p = prevForAssertMap.get(b); + // Any previous band must be done reading before this one starts. + if (p != null && phaseCmp(p.phase(), DISBURSE_PHASE) < 0) { + Utils.log.warning("Previous band not done reading."); + Utils.log.info(" Previous band: "+p); + Utils.log.info(" Next band: "+b); + assert(verbose > 0); // die unless verbose is true + } + String name = b.name; + if (optDebugBands && !name.startsWith("(")) { + assert(bandSequenceList != null); + // Verify synchronization between reader & writer: + String inName = bandSequenceList.removeFirst(); + // System.out.println("Reading: " + name); + if (!inName.equals(name)) { + Utils.log.warning("Expected " + name + " but read: " + inName); + return false; + } + Utils.log.info("Read band in sequence: " + name); + } + return true; + } + + // DEBUG ONLY: Make sure a bunch of cprefs are correct. + private boolean assertValidCPRefs(CPRefBand b) { + if (b.index == null) return true; + int limit = b.index.size()+1; + for (int i = 0; i < b.length(); i++) { + int v = b.valueAtForDebug(i); + if (v < 0 || v >= limit) { + Utils.log.warning("CP ref out of range "+ + "["+i+"] = "+v+" in "+b); + return false; + } + } + return true; + } + + /* + * DEBUG ONLY: write the bands to a list and read back the list in order, + * this works perfectly if we use the java packer and unpacker, typically + * this will work with --repack or if they are in the same jvm instance. + */ + static LinkedList bandSequenceList = null; + private boolean assertReadyToWriteTo(Band b, OutputStream out) throws IOException { + Band p = prevForAssertMap.get(b); + // Any previous band must be done writing before this one starts. + if (p != null && phaseCmp(p.phase(), DONE_PHASE) < 0) { + Utils.log.warning("Previous band not done writing."); + Utils.log.info(" Previous band: "+p); + Utils.log.info(" Next band: "+b); + assert(verbose > 0); // die unless verbose is true + } + String name = b.name; + if (optDebugBands && !name.startsWith("(")) { + if (bandSequenceList == null) + bandSequenceList = new LinkedList<>(); + // Verify synchronization between reader & writer: + bandSequenceList.add(name); + // System.out.println("Writing: " + b); + } + return true; + } + + protected static boolean testBit(int flags, int bitMask) { + return (flags & bitMask) != 0; + } + protected static int setBit(int flags, int bitMask, boolean z) { + return z ? (flags | bitMask) : (flags &~ bitMask); + } + protected static boolean testBit(long flags, long bitMask) { + return (flags & bitMask) != 0; + } + protected static long setBit(long flags, long bitMask, boolean z) { + return z ? (flags | bitMask) : (flags &~ bitMask); + } + + + static void printArrayTo(PrintStream ps, int[] values, int start, int end) { + int len = end-start; + for (int i = 0; i < len; i++) { + if (i % 10 == 0) + ps.println(); + else + ps.print(" "); + ps.print(values[start+i]); + } + ps.println(); + } + + static void printArrayTo(PrintStream ps, Entry[] cpMap, int start, int end) { + printArrayTo(ps, cpMap, start, end, false); + } + static void printArrayTo(PrintStream ps, Entry[] cpMap, int start, int end, boolean showTags) { + StringBuffer buf = new StringBuffer(); + int len = end-start; + for (int i = 0; i < len; i++) { + Entry e = cpMap[start+i]; + ps.print(start+i); ps.print("="); + if (showTags) { ps.print(e.tag); ps.print(":"); } + String s = e.stringValue(); + buf.setLength(0); + for (int j = 0; j < s.length(); j++) { + char ch = s.charAt(j); + if (!(ch < ' ' || ch > '~' || ch == '\\')) { + buf.append(ch); + } else if (ch == '\\') { + buf.append("\\\\"); + } else if (ch == '\n') { + buf.append("\\n"); + } else if (ch == '\t') { + buf.append("\\t"); + } else if (ch == '\r') { + buf.append("\\r"); + } else { + String str = "000"+Integer.toHexString(ch); + buf.append("\\u").append(str.substring(str.length()-4)); + } + } + ps.println(buf); + } + } + + + // Utilities for reallocating: + protected static Object[] realloc(Object[] a, int len) { + java.lang.Class elt = a.getClass().getComponentType(); + Object[] na = (Object[]) java.lang.reflect.Array.newInstance(elt, len); + System.arraycopy(a, 0, na, 0, Math.min(a.length, len)); + return na; + } + protected static Object[] realloc(Object[] a) { + return realloc(a, Math.max(10, a.length*2)); + } + + protected static int[] realloc(int[] a, int len) { + if (len == 0) return noInts; + if (a == null) return new int[len]; + int[] na = new int[len]; + System.arraycopy(a, 0, na, 0, Math.min(a.length, len)); + return na; + } + protected static int[] realloc(int[] a) { + return realloc(a, Math.max(10, a.length*2)); + } + + protected static byte[] realloc(byte[] a, int len) { + if (len == 0) return noBytes; + if (a == null) return new byte[len]; + byte[] na = new byte[len]; + System.arraycopy(a, 0, na, 0, Math.min(a.length, len)); + return na; + } + protected static byte[] realloc(byte[] a) { + return realloc(a, Math.max(10, a.length*2)); + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/ClassReader.java b/src/main/java/net/minecraftforge/gradle/util/pack200/ClassReader.java new file mode 100644 index 000000000..ce7e7b532 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/ClassReader.java @@ -0,0 +1,618 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import net.minecraftforge.gradle.util.pack200.ConstantPool.ClassEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.DescriptorEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.Entry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.SignatureEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.MemberEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.MethodHandleEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.BootstrapMethodEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.Utf8Entry; +import net.minecraftforge.gradle.util.pack200.Package.Class; +import net.minecraftforge.gradle.util.pack200.Package.InnerClass; +import java.io.DataInputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; +import static net.minecraftforge.gradle.util.pack200.Constants.*; + +/** + * Reader for a class file that is being incorporated into a package. + * @author John Rose + */ +class ClassReader { + int verbose; + + Package pkg; + Class cls; + long inPos; + long constantPoolLimit = -1; + DataInputStream in; + Map attrDefs; + Map attrCommands; + String unknownAttrCommand = "error";; + + ClassReader(Class cls, InputStream in) throws IOException { + this.pkg = cls.getPackage(); + this.cls = cls; + this.verbose = pkg.verbose; + this.in = new DataInputStream(new FilterInputStream(in) { + public int read(byte b[], int off, int len) throws IOException { + int nr = super.read(b, off, len); + if (nr >= 0) inPos += nr; + return nr; + } + public int read() throws IOException { + int ch = super.read(); + if (ch >= 0) inPos += 1; + return ch; + } + public long skip(long n) throws IOException { + long ns = super.skip(n); + if (ns >= 0) inPos += ns; + return ns; + } + }); + } + + public void setAttrDefs(Map attrDefs) { + this.attrDefs = attrDefs; + } + + public void setAttrCommands(Map attrCommands) { + this.attrCommands = attrCommands; + } + + private void skip(int n, String what) throws IOException { + Utils.log.warning("skipping "+n+" bytes of "+what); + long skipped = 0; + while (skipped < n) { + long j = in.skip(n - skipped); + assert(j > 0); + skipped += j; + } + assert(skipped == n); + } + + private int readUnsignedShort() throws IOException { + return in.readUnsignedShort(); + } + + private int readInt() throws IOException { + return in.readInt(); + } + + /** Read a 2-byte int, and return the global CP entry for it. */ + private Entry readRef() throws IOException { + int i = in.readUnsignedShort(); + return i == 0 ? null : cls.cpMap[i]; + } + + private Entry readRef(byte tag) throws IOException { + Entry e = readRef(); + assert(!(e instanceof UnresolvedEntry)); + checkTag(e, tag); + return e; + } + + /** Throw a ClassFormatException if the entry does not match the expected tag type. */ + private Entry checkTag(Entry e, byte tag) throws ClassFormatException { + if (e == null || !e.tagMatches(tag)) { + String where = (inPos == constantPoolLimit + ? " in constant pool" + : " at pos: " + inPos); + String got = (e == null + ? "null CP index" + : "type=" + ConstantPool.tagName(e.tag)); + throw new ClassFormatException("Bad constant, expected type=" + + ConstantPool.tagName(tag) + + " got "+ got + ", in File: " + cls.file.nameString + where); + } + return e; + } + private Entry checkTag(Entry e, byte tag, boolean nullOK) throws ClassFormatException { + return nullOK && e == null ? null : checkTag(e, tag); + } + + private Entry readRefOrNull(byte tag) throws IOException { + Entry e = readRef(); + checkTag(e, tag, true); + return e; + } + + private Utf8Entry readUtf8Ref() throws IOException { + return (Utf8Entry) readRef(CONSTANT_Utf8); + } + + private ClassEntry readClassRef() throws IOException { + return (ClassEntry) readRef(CONSTANT_Class); + } + + private ClassEntry readClassRefOrNull() throws IOException { + return (ClassEntry) readRefOrNull(CONSTANT_Class); + } + + private SignatureEntry readSignatureRef() throws IOException { + // The class file stores a Utf8, but we want a Signature. + Entry e = readRef(CONSTANT_Signature); + return (e != null && e.getTag() == CONSTANT_Utf8) + ? ConstantPool.getSignatureEntry(e.stringValue()) + : (SignatureEntry) e; + } + + void read() throws IOException { + boolean ok = false; + try { + readMagicNumbers(); + readConstantPool(); + readHeader(); + readMembers(false); // fields + readMembers(true); // methods + readAttributes(ATTR_CONTEXT_CLASS, cls); + fixUnresolvedEntries(); + cls.finishReading(); + assert(0 >= in.read(new byte[1])); + ok = true; + } finally { + if (!ok) { + if (verbose > 0) Utils.log.warning("Erroneous data at input offset "+inPos+" of "+cls.file); + } + } + } + + void readMagicNumbers() throws IOException { + cls.magic = in.readInt(); + if (cls.magic != JAVA_MAGIC) + throw new Attribute.FormatException + ("Bad magic number in class file " + +Integer.toHexString(cls.magic), + ATTR_CONTEXT_CLASS, "magic-number", "pass"); + int minver = (short) readUnsignedShort(); + int majver = (short) readUnsignedShort(); + cls.version = Package.Version.of(majver, minver); + + //System.out.println("ClassFile.version="+cls.majver+"."+cls.minver); + String bad = checkVersion(cls.version); + if (bad != null) { + throw new Attribute.FormatException + ("classfile version too "+bad+": " + +cls.version+" in "+cls.file, + ATTR_CONTEXT_CLASS, "version", "pass"); + } + } + + private String checkVersion(Package.Version ver) { + int majver = ver.major; + int minver = ver.minor; + if (majver < pkg.minClassVersion.major || + (majver == pkg.minClassVersion.major && + minver < pkg.minClassVersion.minor)) { + return "small"; + } + if (majver > pkg.maxClassVersion.major || + (majver == pkg.maxClassVersion.major && + minver > pkg.maxClassVersion.minor)) { + return "large"; + } + return null; // OK + } + + void readConstantPool() throws IOException { + int length = in.readUnsignedShort(); + //System.err.println("reading CP, length="+length); + + int[] fixups = new int[length*4]; + int fptr = 0; + + Entry[] cpMap = new Entry[length]; + cpMap[0] = null; + for (int i = 1; i < length; i++) { + //System.err.println("reading CP elt, i="+i); + int tag = in.readByte(); + switch (tag) { + case CONSTANT_Utf8: + cpMap[i] = ConstantPool.getUtf8Entry(in.readUTF()); + break; + case CONSTANT_Integer: + { + cpMap[i] = ConstantPool.getLiteralEntry(in.readInt()); + } + break; + case CONSTANT_Float: + { + cpMap[i] = ConstantPool.getLiteralEntry(in.readFloat()); + } + break; + case CONSTANT_Long: + { + cpMap[i] = ConstantPool.getLiteralEntry(in.readLong()); + cpMap[++i] = null; + } + break; + case CONSTANT_Double: + { + cpMap[i] = ConstantPool.getLiteralEntry(in.readDouble()); + cpMap[++i] = null; + } + break; + + // just read the refs; do not attempt to resolve while reading + case CONSTANT_Class: + case CONSTANT_String: + case CONSTANT_MethodType: + fixups[fptr++] = i; + fixups[fptr++] = tag; + fixups[fptr++] = in.readUnsignedShort(); + fixups[fptr++] = -1; // empty ref2 + break; + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + case CONSTANT_NameandType: + fixups[fptr++] = i; + fixups[fptr++] = tag; + fixups[fptr++] = in.readUnsignedShort(); + fixups[fptr++] = in.readUnsignedShort(); + break; + case CONSTANT_InvokeDynamic: + fixups[fptr++] = i; + fixups[fptr++] = tag; + fixups[fptr++] = -1 ^ in.readUnsignedShort(); // not a ref + fixups[fptr++] = in.readUnsignedShort(); + break; + case CONSTANT_MethodHandle: + fixups[fptr++] = i; + fixups[fptr++] = tag; + fixups[fptr++] = -1 ^ in.readUnsignedByte(); + fixups[fptr++] = in.readUnsignedShort(); + break; + default: + throw new ClassFormatException("Bad constant pool tag " + + tag + " in File: " + cls.file.nameString + + " at pos: " + inPos); + } + } + constantPoolLimit = inPos; + + // Fix up refs, which might be out of order. + while (fptr > 0) { + if (verbose > 3) + Utils.log.fine("CP fixups ["+fptr/4+"]"); + int flimit = fptr; + fptr = 0; + for (int fi = 0; fi < flimit; ) { + int cpi = fixups[fi++]; + int tag = fixups[fi++]; + int ref = fixups[fi++]; + int ref2 = fixups[fi++]; + if (verbose > 3) + Utils.log.fine(" cp["+cpi+"] = "+ConstantPool.tagName(tag)+"{"+ref+","+ref2+"}"); + if (ref >= 0 && cpMap[ref] == null || ref2 >= 0 && cpMap[ref2] == null) { + // Defer. + fixups[fptr++] = cpi; + fixups[fptr++] = tag; + fixups[fptr++] = ref; + fixups[fptr++] = ref2; + continue; + } + switch (tag) { + case CONSTANT_Class: + cpMap[cpi] = ConstantPool.getClassEntry(cpMap[ref].stringValue()); + break; + case CONSTANT_String: + cpMap[cpi] = ConstantPool.getStringEntry(cpMap[ref].stringValue()); + break; + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + ClassEntry mclass = (ClassEntry) checkTag(cpMap[ref], CONSTANT_Class); + DescriptorEntry mdescr = (DescriptorEntry) checkTag(cpMap[ref2], CONSTANT_NameandType); + cpMap[cpi] = ConstantPool.getMemberEntry((byte)tag, mclass, mdescr); + break; + case CONSTANT_NameandType: + Utf8Entry mname = (Utf8Entry) checkTag(cpMap[ref], CONSTANT_Utf8); + Utf8Entry mtype = (Utf8Entry) checkTag(cpMap[ref2], CONSTANT_Signature); + cpMap[cpi] = ConstantPool.getDescriptorEntry(mname, mtype); + break; + case CONSTANT_MethodType: + cpMap[cpi] = ConstantPool.getMethodTypeEntry((Utf8Entry) checkTag(cpMap[ref], CONSTANT_Signature)); + break; + case CONSTANT_MethodHandle: + byte refKind = (byte)(-1 ^ ref); + MemberEntry memRef = (MemberEntry) checkTag(cpMap[ref2], CONSTANT_AnyMember); + cpMap[cpi] = ConstantPool.getMethodHandleEntry(refKind, memRef); + break; + case CONSTANT_InvokeDynamic: + DescriptorEntry idescr = (DescriptorEntry) checkTag(cpMap[ref2], CONSTANT_NameandType); + cpMap[cpi] = new UnresolvedEntry((byte)tag, (-1 ^ ref), idescr); + // Note that ref must be resolved later, using the BootstrapMethods attribute. + break; + default: + assert(false); + } + } + assert(fptr < flimit); // Must make progress. + } + + cls.cpMap = cpMap; + } + + private /*non-static*/ + class UnresolvedEntry extends Entry { + final Object[] refsOrIndexes; + UnresolvedEntry(byte tag, Object... refsOrIndexes) { + super(tag); + this.refsOrIndexes = refsOrIndexes; + ClassReader.this.haveUnresolvedEntry = true; + } + Entry resolve() { + Class cls = ClassReader.this.cls; + Entry res; + switch (tag) { + case CONSTANT_InvokeDynamic: + BootstrapMethodEntry iboots = cls.bootstrapMethods.get((Integer) refsOrIndexes[0]); + DescriptorEntry idescr = (DescriptorEntry) refsOrIndexes[1]; + res = ConstantPool.getInvokeDynamicEntry(iboots, idescr); + break; + default: + throw new AssertionError(); + } + return res; + } + private void unresolved() { throw new RuntimeException("unresolved entry has no string"); } + public int compareTo(Object x) { unresolved(); return 0; } + public boolean equals(Object x) { unresolved(); return false; } + protected int computeValueHash() { unresolved(); return 0; } + public String stringValue() { unresolved(); return toString(); } + public String toString() { return "(unresolved "+ConstantPool.tagName(tag)+")"; } + } + + boolean haveUnresolvedEntry; + private void fixUnresolvedEntries() { + if (!haveUnresolvedEntry) return; + Entry[] cpMap = cls.getCPMap(); + for (int i = 0; i < cpMap.length; i++) { + Entry e = cpMap[i]; + if (e instanceof UnresolvedEntry) { + cpMap[i] = e = ((UnresolvedEntry)e).resolve(); + assert(!(e instanceof UnresolvedEntry)); + } + } + haveUnresolvedEntry = false; + } + + void readHeader() throws IOException { + cls.flags = readUnsignedShort(); + cls.thisClass = readClassRef(); + cls.superClass = readClassRefOrNull(); + int ni = readUnsignedShort(); + cls.interfaces = new ClassEntry[ni]; + for (int i = 0; i < ni; i++) { + cls.interfaces[i] = readClassRef(); + } + } + + void readMembers(boolean doMethods) throws IOException { + int nm = readUnsignedShort(); + for (int i = 0; i < nm; i++) { + readMember(doMethods); + } + } + + void readMember(boolean doMethod) throws IOException { + int mflags = readUnsignedShort(); + Utf8Entry mname = readUtf8Ref(); + SignatureEntry mtype = readSignatureRef(); + DescriptorEntry descr = ConstantPool.getDescriptorEntry(mname, mtype); + Class.Member m; + if (!doMethod) + m = cls.new Field(mflags, descr); + else + m = cls.new Method(mflags, descr); + readAttributes(!doMethod ? ATTR_CONTEXT_FIELD : ATTR_CONTEXT_METHOD, + m); + } + void readAttributes(int ctype, Attribute.Holder h) throws IOException { + int na = readUnsignedShort(); + if (na == 0) return; // nothing to do here + if (verbose > 3) + Utils.log.fine("readAttributes "+h+" ["+na+"]"); + for (int i = 0; i < na; i++) { + String name = readUtf8Ref().stringValue(); + int length = readInt(); + // See if there is a special command that applies. + if (attrCommands != null) { + Attribute.Layout lkey = Attribute.keyForLookup(ctype, name); + String cmd = attrCommands.get(lkey); + if (cmd != null) { + switch (cmd) { + case "pass": + String message1 = "passing attribute bitwise in " + h; + throw new Attribute.FormatException(message1, ctype, name, cmd); + case "error": + String message2 = "attribute not allowed in " + h; + throw new Attribute.FormatException(message2, ctype, name, cmd); + case "strip": + skip(length, name + " attribute in " + h); + continue; + } + } + } + // Find canonical instance of the requested attribute. + Attribute a = Attribute.lookup(Package.attrDefs, ctype, name); + if (verbose > 4 && a != null) + Utils.log.fine("pkg_attribute_lookup "+name+" = "+a); + if (a == null) { + a = Attribute.lookup(this.attrDefs, ctype, name); + if (verbose > 4 && a != null) + Utils.log.fine("this "+name+" = "+a); + } + if (a == null) { + a = Attribute.lookup(null, ctype, name); + if (verbose > 4 && a != null) + Utils.log.fine("null_attribute_lookup "+name+" = "+a); + } + if (a == null && length == 0) { + // Any zero-length attr is "known"... + // We can assume an empty attr. has an empty layout. + // Handles markers like Enum, Bridge, Synthetic, Deprecated. + a = Attribute.find(ctype, name, ""); + } + boolean isStackMap = (ctype == ATTR_CONTEXT_CODE + && (name.equals("StackMap") || + name.equals("StackMapX"))); + if (isStackMap) { + // Known attribute but with a corner case format, "pass" it. + Code code = (Code) h; + final int TOO_BIG = 0x10000; + if (code.max_stack >= TOO_BIG || + code.max_locals >= TOO_BIG || + code.getLength() >= TOO_BIG || + name.endsWith("X")) { + // No, we don't really know what to do with this one. + // Do not compress the rare and strange "u4" and "X" cases. + a = null; + } + } + if (a == null) { + if (isStackMap) { + // Known attribute but w/o a format; pass it. + String message = "unsupported StackMap variant in "+h; + throw new Attribute.FormatException(message, ctype, name, + "pass"); + } else if ("strip".equals(unknownAttrCommand)) { + // Skip the unknown attribute. + skip(length, "unknown "+name+" attribute in "+h); + continue; + } else { + String message = " is unknown attribute in class " + h; + throw new Attribute.FormatException(message, ctype, name, + unknownAttrCommand); + } + } + long pos0 = inPos; // in case we want to check it + if (a.layout() == Package.attrCodeEmpty) { + // These are hardwired. + Class.Method m = (Class.Method) h; + m.code = new Code(m); + try { + readCode(m.code); + } catch (Instruction.FormatException iie) { + String message = iie.getMessage() + " in " + h; + throw new ClassReader.ClassFormatException(message, iie); + } + assert(length == inPos - pos0); + // Keep empty attribute a... + } else if (a.layout() == Package.attrBootstrapMethodsEmpty) { + assert(h == cls); + readBootstrapMethods(cls); + assert(length == inPos - pos0); + // Delete the attribute; it is logically part of the constant pool. + continue; + } else if (a.layout() == Package.attrInnerClassesEmpty) { + // These are hardwired also. + assert(h == cls); + readInnerClasses(cls); + assert(length == inPos - pos0); + // Keep empty attribute a... + } else if (length > 0) { + byte[] bytes = new byte[length]; + in.readFully(bytes); + a = a.addContent(bytes); + } + if (a.size() == 0 && !a.layout().isEmpty()) { + throw new ClassFormatException(name + + ": attribute length cannot be zero, in " + h); + } + h.addAttribute(a); + if (verbose > 2) + Utils.log.fine("read "+a); + } + } + + void readCode(Code code) throws IOException { + code.max_stack = readUnsignedShort(); + code.max_locals = readUnsignedShort(); + code.bytes = new byte[readInt()]; + in.readFully(code.bytes); + Entry[] cpMap = cls.getCPMap(); + Instruction.opcodeChecker(code.bytes, cpMap, this.cls.version); + int nh = readUnsignedShort(); + code.setHandlerCount(nh); + for (int i = 0; i < nh; i++) { + code.handler_start[i] = readUnsignedShort(); + code.handler_end[i] = readUnsignedShort(); + code.handler_catch[i] = readUnsignedShort(); + code.handler_class[i] = readClassRefOrNull(); + } + readAttributes(ATTR_CONTEXT_CODE, code); + } + + void readBootstrapMethods(Class cls) throws IOException { + BootstrapMethodEntry[] bsms = new BootstrapMethodEntry[readUnsignedShort()]; + for (int i = 0; i < bsms.length; i++) { + MethodHandleEntry bsmRef = (MethodHandleEntry) readRef(CONSTANT_MethodHandle); + Entry[] argRefs = new Entry[readUnsignedShort()]; + for (int j = 0; j < argRefs.length; j++) { + argRefs[j] = readRef(CONSTANT_LoadableValue); + } + bsms[i] = ConstantPool.getBootstrapMethodEntry(bsmRef, argRefs); + } + cls.setBootstrapMethods(Arrays.asList(bsms)); + } + + void readInnerClasses(Class cls) throws IOException { + int nc = readUnsignedShort(); + ArrayList ics = new ArrayList<>(nc); + for (int i = 0; i < nc; i++) { + InnerClass ic = + new InnerClass(readClassRef(), + readClassRefOrNull(), + (Utf8Entry)readRefOrNull(CONSTANT_Utf8), + readUnsignedShort()); + ics.add(ic); + } + cls.innerClasses = ics; // set directly; do not use setInnerClasses. + // (Later, ics may be transferred to the pkg.) + } + + static class ClassFormatException extends IOException { + private static final long serialVersionUID = -3564121733989501833L; + + public ClassFormatException(String message) { + super(message); + } + + public ClassFormatException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/ClassWriter.java b/src/main/java/net/minecraftforge/gradle/util/pack200/ClassWriter.java new file mode 100644 index 000000000..334675d8c --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/ClassWriter.java @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + + +import net.minecraftforge.gradle.util.pack200.ConstantPool.Entry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.Index; +import net.minecraftforge.gradle.util.pack200.ConstantPool.NumberEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.MethodHandleEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.BootstrapMethodEntry; +import net.minecraftforge.gradle.util.pack200.Package.Class; +import net.minecraftforge.gradle.util.pack200.Package.InnerClass; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import static net.minecraftforge.gradle.util.pack200.Constants.*; +/** + * Writer for a class file that is incorporated into a package. + * @author John Rose + */ +class ClassWriter { + int verbose; + + Package pkg; + Class cls; + DataOutputStream out; + Index cpIndex; + Index bsmIndex; + + ClassWriter(Class cls, OutputStream out) throws IOException { + this.pkg = cls.getPackage(); + this.cls = cls; + this.verbose = pkg.verbose; + this.out = new DataOutputStream(new BufferedOutputStream(out)); + this.cpIndex = ConstantPool.makeIndex(cls.toString(), cls.getCPMap()); + this.cpIndex.flattenSigs = true; + if (cls.hasBootstrapMethods()) { + this.bsmIndex = ConstantPool.makeIndex(cpIndex.debugName+".BootstrapMethods", + cls.getBootstrapMethodMap()); + } + if (verbose > 1) + Utils.log.fine("local CP="+(verbose > 2 ? cpIndex.dumpString() : cpIndex.toString())); + } + + private void writeShort(int x) throws IOException { + out.writeShort(x); + } + + private void writeInt(int x) throws IOException { + out.writeInt(x); + } + + /** Write a 2-byte int representing a CP entry, using the local cpIndex. */ + private void writeRef(Entry e) throws IOException { + writeRef(e, cpIndex); + } + + /** Write a 2-byte int representing a CP entry, using the given cpIndex. */ + private void writeRef(Entry e, Index cpIndex) throws IOException { + int i = (e == null) ? 0 : cpIndex.indexOf(e); + writeShort(i); + } + + void write() throws IOException { + boolean ok = false; + try { + if (verbose > 1) Utils.log.fine("...writing "+cls); + writeMagicNumbers(); + writeConstantPool(); + writeHeader(); + writeMembers(false); // fields + writeMembers(true); // methods + writeAttributes(ATTR_CONTEXT_CLASS, cls); + /* Closing here will cause all the underlying + streams to close, Causing the jar stream + to close prematurely, instead we just flush. + out.close(); + */ + out.flush(); + ok = true; + } finally { + if (!ok) { + Utils.log.warning("Error on output of "+cls); + } + } + } + + void writeMagicNumbers() throws IOException { + writeInt(cls.magic); + writeShort(cls.version.minor); + writeShort(cls.version.major); + } + + void writeConstantPool() throws IOException { + Entry[] cpMap = cls.cpMap; + writeShort(cpMap.length); + for (int i = 0; i < cpMap.length; i++) { + Entry e = cpMap[i]; + assert((e == null) == (i == 0 || cpMap[i-1] != null && cpMap[i-1].isDoubleWord())); + if (e == null) continue; + byte tag = e.getTag(); + if (verbose > 2) Utils.log.fine(" CP["+i+"] = "+e); + out.write(tag); + switch (tag) { + case CONSTANT_Signature: + throw new AssertionError("CP should have Signatures remapped to Utf8"); + case CONSTANT_Utf8: + out.writeUTF(e.stringValue()); + break; + case CONSTANT_Integer: + out.writeInt(((NumberEntry)e).numberValue().intValue()); + break; + case CONSTANT_Float: + float fval = ((NumberEntry)e).numberValue().floatValue(); + out.writeInt(Float.floatToRawIntBits(fval)); + break; + case CONSTANT_Long: + out.writeLong(((NumberEntry)e).numberValue().longValue()); + break; + case CONSTANT_Double: + double dval = ((NumberEntry)e).numberValue().doubleValue(); + out.writeLong(Double.doubleToRawLongBits(dval)); + break; + case CONSTANT_Class: + case CONSTANT_String: + case CONSTANT_MethodType: + writeRef(e.getRef(0)); + break; + case CONSTANT_MethodHandle: + MethodHandleEntry mhe = (MethodHandleEntry) e; + out.writeByte(mhe.refKind); + writeRef(mhe.getRef(0)); + break; + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + case CONSTANT_NameandType: + writeRef(e.getRef(0)); + writeRef(e.getRef(1)); + break; + case CONSTANT_InvokeDynamic: + writeRef(e.getRef(0), bsmIndex); + writeRef(e.getRef(1)); + break; + case CONSTANT_BootstrapMethod: + throw new AssertionError("CP should have BootstrapMethods moved to side-table"); + default: + throw new IOException("Bad constant pool tag "+tag); + } + } + } + + void writeHeader() throws IOException { + writeShort(cls.flags); + writeRef(cls.thisClass); + writeRef(cls.superClass); + writeShort(cls.interfaces.length); + for (int i = 0; i < cls.interfaces.length; i++) { + writeRef(cls.interfaces[i]); + } + } + + void writeMembers(boolean doMethods) throws IOException { + List mems; + if (!doMethods) + mems = cls.getFields(); + else + mems = cls.getMethods(); + writeShort(mems.size()); + for (Class.Member m : mems) { + writeMember(m, doMethods); + } + } + + void writeMember(Class.Member m, boolean doMethod) throws IOException { + if (verbose > 2) Utils.log.fine("writeMember "+m); + writeShort(m.flags); + writeRef(m.getDescriptor().nameRef); + writeRef(m.getDescriptor().typeRef); + writeAttributes(!doMethod ? ATTR_CONTEXT_FIELD : ATTR_CONTEXT_METHOD, + m); + } + + private void reorderBSMandICS(Attribute.Holder h) { + Attribute bsmAttr = h.getAttribute(Package.attrBootstrapMethodsEmpty); + if (bsmAttr == null) return; + + Attribute icsAttr = h.getAttribute(Package.attrInnerClassesEmpty); + if (icsAttr == null) return; + + int bsmidx = h.attributes.indexOf(bsmAttr); + int icsidx = h.attributes.indexOf(icsAttr); + if (bsmidx > icsidx) { + h.attributes.remove(bsmAttr); + h.attributes.add(icsidx, bsmAttr); + } + return; + } + + // handy buffer for collecting attrs + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream bufOut = new DataOutputStream(buf); + + void writeAttributes(int ctype, Attribute.Holder h) throws IOException { + if (h.attributes == null) { + writeShort(0); // attribute size + return; + } + // there may be cases if an InnerClass attribute is explicit, then the + // ordering could be wrong, fix the ordering before we write it out. + if (h instanceof Package.Class) + reorderBSMandICS(h); + + writeShort(h.attributes.size()); + for (Attribute a : h.attributes) { + a.finishRefs(cpIndex); + writeRef(a.getNameRef()); + if (a.layout() == Package.attrCodeEmpty || + a.layout() == Package.attrBootstrapMethodsEmpty || + a.layout() == Package.attrInnerClassesEmpty) { + // These are hardwired. + DataOutputStream savedOut = out; + assert(out != bufOut); + buf.reset(); + out = bufOut; + if ("Code".equals(a.name())) { + Class.Method m = (Class.Method) h; + writeCode(m.code); + } else if ("BootstrapMethods".equals(a.name())) { + assert(h == cls); + writeBootstrapMethods(cls); + } else if ("InnerClasses".equals(a.name())) { + assert(h == cls); + writeInnerClasses(cls); + } else { + throw new AssertionError(); + } + out = savedOut; + if (verbose > 2) + Utils.log.fine("Attribute "+a.name()+" ["+buf.size()+"]"); + writeInt(buf.size()); + buf.writeTo(out); + } else { + if (verbose > 2) + Utils.log.fine("Attribute "+a.name()+" ["+a.size()+"]"); + writeInt(a.size()); + out.write(a.bytes()); + } + } + } + + void writeCode(Code code) throws IOException { + code.finishRefs(cpIndex); + writeShort(code.max_stack); + writeShort(code.max_locals); + writeInt(code.bytes.length); + out.write(code.bytes); + int nh = code.getHandlerCount(); + writeShort(nh); + for (int i = 0; i < nh; i++) { + writeShort(code.handler_start[i]); + writeShort(code.handler_end[i]); + writeShort(code.handler_catch[i]); + writeRef(code.handler_class[i]); + } + writeAttributes(ATTR_CONTEXT_CODE, code); + } + + void writeBootstrapMethods(Class cls) throws IOException { + List bsms = cls.getBootstrapMethods(); + writeShort(bsms.size()); + for (BootstrapMethodEntry e : bsms) { + writeRef(e.bsmRef); + writeShort(e.argRefs.length); + for (Entry argRef : e.argRefs) { + writeRef(argRef); + } + } + } + + void writeInnerClasses(Class cls) throws IOException { + List ics = cls.getInnerClasses(); + writeShort(ics.size()); + for (InnerClass ic : ics) { + writeRef(ic.thisClass); + writeRef(ic.outerClass); + writeRef(ic.name); + writeShort(ic.flags); + } + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/Code.java b/src/main/java/net/minecraftforge/gradle/util/pack200/Code.java new file mode 100644 index 000000000..af32be9b8 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/Code.java @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import net.minecraftforge.gradle.util.pack200.Package.Class; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collection; +import static net.minecraftforge.gradle.util.pack200.Constants.*; + +/** + * Represents a chunk of bytecodes. + * @author John Rose + */ +class Code extends Attribute.Holder { + Class.Method m; + + public Code(Class.Method m) { + this.m = m; + } + + public Class.Method getMethod() { + return m; + } + public Class thisClass() { + return m.thisClass(); + } + public Package getPackage() { + return m.thisClass().getPackage(); + } + + public ConstantPool.Entry[] getCPMap() { + return m.getCPMap(); + } + + private static final ConstantPool.Entry[] noRefs = ConstantPool.noRefs; + + // The following fields are used directly by the ClassReader, etc. + int max_stack; + int max_locals; + + ConstantPool.Entry handler_class[] = noRefs; + int handler_start[] = noInts; + int handler_end[] = noInts; + int handler_catch[] = noInts; + + byte[] bytes; + Fixups fixups; // reference relocations, if any are required + Object insnMap; // array of instruction boundaries + + int getLength() { return bytes.length; } + + int getMaxStack() { + return max_stack; + } + void setMaxStack(int ms) { + max_stack = ms; + } + + int getMaxNALocals() { + int argsize = m.getArgumentSize(); + return max_locals - argsize; + } + void setMaxNALocals(int ml) { + int argsize = m.getArgumentSize(); + max_locals = argsize + ml; + } + + int getHandlerCount() { + assert(handler_class.length == handler_start.length); + assert(handler_class.length == handler_end.length); + assert(handler_class.length == handler_catch.length); + return handler_class.length; + } + void setHandlerCount(int h) { + if (h > 0) { + handler_class = new ConstantPool.Entry[h]; + handler_start = new int[h]; + handler_end = new int[h]; + handler_catch = new int[h]; + // caller must fill these in ASAP + } + } + + void setBytes(byte[] bytes) { + this.bytes = bytes; + if (fixups != null) + fixups.setBytes(bytes); + } + + void setInstructionMap(int[] insnMap, int mapLen) { + //int[] oldMap = null; + //assert((oldMap = getInstructionMap()) != null); + this.insnMap = allocateInstructionMap(insnMap, mapLen); + //assert(Arrays.equals(oldMap, getInstructionMap())); + } + void setInstructionMap(int[] insnMap) { + setInstructionMap(insnMap, insnMap.length); + } + + int[] getInstructionMap() { + return expandInstructionMap(getInsnMap()); + } + + void addFixups(Collection moreFixups) { + if (fixups == null) { + fixups = new Fixups(bytes); + } + assert(fixups.getBytes() == bytes); + fixups.addAll(moreFixups); + } + + public void trimToSize() { + if (fixups != null) { + fixups.trimToSize(); + if (fixups.size() == 0) + fixups = null; + } + super.trimToSize(); + } + + protected void visitRefs(int mode, Collection refs) { + int verbose = getPackage().verbose; + if (verbose > 2) + System.out.println("Reference scan "+this); + refs.addAll(Arrays.asList(handler_class)); + if (fixups != null) { + fixups.visitRefs(refs); + } else { + // References (to a local cpMap) are embedded in the bytes. + ConstantPool.Entry[] cpMap = getCPMap(); + for (Instruction i = instructionAt(0); i != null; i = i.next()) { + if (verbose > 4) + System.out.println(i); + int cpref = i.getCPIndex(); + if (cpref >= 0) { + refs.add(cpMap[cpref]); + } + } + } + // Handle attribute list: + super.visitRefs(mode, refs); + } + + // Since bytecodes are the single largest contributor to + // package size, it's worth a little bit of trouble + // to reduce the per-bytecode memory footprint. + // In the current scheme, half of the bulk of these arrays + // due to bytes, and half to shorts. (Ints are insignificant.) + // Given an average of 1.8 bytes per instruction, this means + // instruction boundary arrays are about a 75% overhead--tolerable. + // (By using bytes, we get 33% savings over just shorts and ints. + // Using both bytes and shorts gives 66% savings over just ints.) + static final boolean shrinkMaps = true; + + private Object allocateInstructionMap(int[] insnMap, int mapLen) { + int PClimit = getLength(); + if (shrinkMaps && PClimit <= Byte.MAX_VALUE - Byte.MIN_VALUE) { + byte[] map = new byte[mapLen+1]; + for (int i = 0; i < mapLen; i++) { + map[i] = (byte)(insnMap[i] + Byte.MIN_VALUE); + } + map[mapLen] = (byte)(PClimit + Byte.MIN_VALUE); + return map; + } else if (shrinkMaps && PClimit < Short.MAX_VALUE - Short.MIN_VALUE) { + short[] map = new short[mapLen+1]; + for (int i = 0; i < mapLen; i++) { + map[i] = (short)(insnMap[i] + Short.MIN_VALUE); + } + map[mapLen] = (short)(PClimit + Short.MIN_VALUE); + return map; + } else { + int[] map = Arrays.copyOf(insnMap, mapLen + 1); + map[mapLen] = PClimit; + return map; + } + } + private int[] expandInstructionMap(Object map0) { + int[] imap; + if (map0 instanceof byte[]) { + byte[] map = (byte[]) map0; + imap = new int[map.length-1]; + for (int i = 0; i < imap.length; i++) { + imap[i] = map[i] - Byte.MIN_VALUE; + } + } else if (map0 instanceof short[]) { + short[] map = (short[]) map0; + imap = new int[map.length-1]; + for (int i = 0; i < imap.length; i++) { + imap[i] = map[i] - Byte.MIN_VALUE; + } + } else { + int[] map = (int[]) map0; + imap = Arrays.copyOfRange(map, 0, map.length - 1); + } + return imap; + } + + Object getInsnMap() { + // Build a map of instruction boundaries. + if (insnMap != null) { + return insnMap; + } + int[] map = new int[getLength()]; + int fillp = 0; + for (Instruction i = instructionAt(0); i != null; i = i.next()) { + map[fillp++] = i.getPC(); + } + // Make it byte[], short[], or int[] according to the max BCI. + insnMap = allocateInstructionMap(map, fillp); + //assert(assertBCICodingsOK()); + return insnMap; + } + + /** Encode the given BCI as an instruction boundary number. + * For completeness, irregular (non-boundary) BCIs are + * encoded compactly immediately after the boundary numbers. + * This encoding is the identity mapping outside 0..length, + * and it is 1-1 everywhere. All by itself this technique + * improved zipped rt.jar compression by 2.6%. + */ + public int encodeBCI(int bci) { + if (bci <= 0 || bci > getLength()) return bci; + Object map0 = getInsnMap(); + int i, len; + if (shrinkMaps && map0 instanceof byte[]) { + byte[] map = (byte[]) map0; + len = map.length; + i = Arrays.binarySearch(map, (byte)(bci + Byte.MIN_VALUE)); + } else if (shrinkMaps && map0 instanceof short[]) { + short[] map = (short[]) map0; + len = map.length; + i = Arrays.binarySearch(map, (short)(bci + Short.MIN_VALUE)); + } else { + int[] map = (int[]) map0; + len = map.length; + i = Arrays.binarySearch(map, bci); + } + assert(i != -1); + assert(i != 0); + assert(i != len); + assert(i != -len-1); + return (i >= 0) ? i : len + bci - (-i-1); + } + public int decodeBCI(int bciCode) { + if (bciCode <= 0 || bciCode > getLength()) return bciCode; + Object map0 = getInsnMap(); + int i, len; + // len == map.length + // If bciCode < len, result is map[bciCode], the common and fast case. + // Otherwise, let map[i] be the smallest map[*] larger than bci. + // Then, required by the return statement of encodeBCI: + // bciCode == len + bci - i + // Thus: + // bci-i == bciCode-len + // map[i]-adj-i == bciCode-len ; adj in (0..map[i]-map[i-1]) + // We can solve this by searching for adjacent entries + // map[i-1], map[i] such that: + // map[i-1]-(i-1) <= bciCode-len < map[i]-i + // This can be approximated by searching map[i] for bciCode and then + // linear searching backward. Given the right i, we then have: + // bci == bciCode-len + i + // This linear search is at its worst case for indexes in the beginning + // of a large method, but it's not clear that this is a problem in + // practice, since BCIs are usually on instruction boundaries. + if (shrinkMaps && map0 instanceof byte[]) { + byte[] map = (byte[]) map0; + len = map.length; + if (bciCode < len) + return map[bciCode] - Byte.MIN_VALUE; + i = Arrays.binarySearch(map, (byte)(bciCode + Byte.MIN_VALUE)); + if (i < 0) i = -i-1; + int key = bciCode-len + Byte.MIN_VALUE; + for (;; i--) { + if (map[i-1]-(i-1) <= key) break; + } + } else if (shrinkMaps && map0 instanceof short[]) { + short[] map = (short[]) map0; + len = map.length; + if (bciCode < len) + return map[bciCode] - Short.MIN_VALUE; + i = Arrays.binarySearch(map, (short)(bciCode + Short.MIN_VALUE)); + if (i < 0) i = -i-1; + int key = bciCode-len + Short.MIN_VALUE; + for (;; i--) { + if (map[i-1]-(i-1) <= key) break; + } + } else { + int[] map = (int[]) map0; + len = map.length; + if (bciCode < len) + return map[bciCode]; + i = Arrays.binarySearch(map, bciCode); + if (i < 0) i = -i-1; + int key = bciCode-len; + for (;; i--) { + if (map[i-1]-(i-1) <= key) break; + } + } + return bciCode-len + i; + } + + public void finishRefs(ConstantPool.Index ix) { + if (fixups != null) { + fixups.finishRefs(ix); + fixups = null; + } + // Code attributes are finished in ClassWriter.writeAttributes. + } + + Instruction instructionAt(int pc) { + return Instruction.at(bytes, pc); + } + + static boolean flagsRequireCode(int flags) { + // A method's flags force it to have a Code attribute, + // if the flags are neither native nor abstract. + return (flags & (Modifier.NATIVE | Modifier.ABSTRACT)) == 0; + } + + public String toString() { + return m+".Code"; + } + + /// Fetching values from my own array. + public int getInt(int pc) { return Instruction.getInt(bytes, pc); } + public int getShort(int pc) { return Instruction.getShort(bytes, pc); } + public int getByte(int pc) { return Instruction.getByte(bytes, pc); } + void setInt(int pc, int x) { Instruction.setInt(bytes, pc, x); } + void setShort(int pc, int x) { Instruction.setShort(bytes, pc, x); } + void setByte(int pc, int x) { Instruction.setByte(bytes, pc, x); } + +/* TEST CODE ONLY + private boolean assertBCICodingsOK() { + boolean ok = true; + int len = java.lang.reflect.Array.getLength(insnMap); + int base = 0; + if (insnMap.getClass().getComponentType() == Byte.TYPE) + base = Byte.MIN_VALUE; + if (insnMap.getClass().getComponentType() == Short.TYPE) + base = Short.MIN_VALUE; + for (int i = -1, imax = getLength()+1; i <= imax; i++) { + int bci = i; + int enc = Math.min(-999, bci-1); + int dec = enc; + try { + enc = encodeBCI(bci); + dec = decodeBCI(enc); + } catch (RuntimeException ee) { + ee.printStackTrace(); + } + if (dec == bci) { + //System.out.println("BCI="+bci+(enc, CodingMethod, Histogram.BitMetric { + /* + Coding schema for single integers, parameterized by (B,H,S): + + Let B in [1,5], H in [1,256], S in [0,3]. + (S limit is arbitrary. B follows the 32-bit limit. H is byte size.) + + A given (B,H,S) code varies in length from 1 to B bytes. + + The 256 values a byte may take on are divided into L=(256-H) and H + values, with all the H values larger than the L values. + (That is, the L values are [0,L) and the H are [L,256).) + + The last byte is always either the B-th byte, a byte with "L value" + (=L). + + Therefore, if L==0, the code always has the full length of B bytes. + The coding then becomes a classic B-byte little-endian unsigned integer. + (Also, if L==128, the high bit of each byte acts signals the presence + of a following byte, up to the maximum length.) + + In the unsigned case (S==0), the coding is compact and monotonic + in the ordering of byte sequences defined by appending zero bytes + to pad them to a common length B, reversing them, and ordering them + lexicographically. (This agrees with "little-endian" byte order.) + + Therefore, the unsigned value of a byte sequence may be defined as: +
+        U(b0)           == b0
+                           in [0..L)
+                           or [0..256) if B==1 (**)
+
+        U(b0,b1)        == b0 + b1*H
+                           in [L..L*(1+H))
+                           or [L..L*(1+H) + H^2) if B==2
+
+        U(b0,b1,b2)     == b0 + b1*H + b2*H^2
+                           in [L*(1+H)..L*(1+H+H^2))
+                           or [L*(1+H)..L*(1+H+H^2) + H^3) if B==3
+
+        U(b[i]: i
+
+      (**) If B==1, the values H,L play no role in the coding.
+      As a convention, we require that any (1,H,S) code must always
+      encode values less than H.  Thus, a simple unsigned byte is coded
+      specifically by the code (1,256,0).
+
+      (Properly speaking, the unsigned case should be parameterized as
+      S==Infinity.  If the schema were regular, the case S==0 would really
+      denote a numbering in which all coded values are negative.)
+
+      If S>0, the unsigned value of a byte sequence is regarded as a binary
+      integer.  If any of the S low-order bits are zero, the corresponding
+      signed value will be non-negative.  If all of the S low-order bits
+      (S>0) are one, the corresponding signed value will be negative.
+
+      The non-negative signed values are compact and monotonically increasing
+      (from 0) in the ordering of the corresponding unsigned values.
+
+      The negative signed values are compact and monotonically decreasing
+      (from -1) in the ordering of the corresponding unsigned values.
+
+      In essence, the low-order S bits function as a collective sign bit
+      for negative signed numbers, and as a low-order base-(2^S-1) digit
+      for non-negative signed numbers.
+
+      Therefore, the signed value corresponding to an unsigned value is:
+      
+        Sgn(x)  == x                               if S==0
+        Sgn(x)  == (x / 2^S)*(2^S-1) + (x % 2^S),  if S>0, (x % 2^S) < 2^S-1
+        Sgn(x)  == -(x / 2^S)-1,                   if S>0, (x % 2^S) == 2^S-1
+      
+ + Finally, the value of a byte sequence, given the coding parameters + (B,H,S), is defined as: +
+        V(b[i]: i
+
+      The extremal positive and negative signed value for a given range
+      of unsigned values may be found by sign-encoding the largest unsigned
+      value which is not 2^S-1 mod 2^S, and that which is, respectively.
+
+      Because B,H,S are variable, this is not a single coding but a schema
+      of codings.  For optimal compression, it is necessary to adaptively
+      select specific codings to the data being compressed.
+
+      For example, if a sequence of values happens never to be negative,
+      S==0 is the best choice.  If the values are equally balanced between
+      negative and positive, S==1.  If negative values are rare, then S>1
+      is more appropriate.
+
+      A (B,H,S) encoding is called a "subrange" if it does not encode
+      the largest 32-bit value, and if the number R of values it does
+      encode can be expressed as a positive 32-bit value.  (Note that
+      B=1 implies R<=256, B=2 implies R<=65536, etc.)
+
+      A delta version of a given (B,H,S) coding encodes an array of integers
+      by writing their successive differences in the (B,H,S) coding.
+      The original integers themselves may be recovered by making a
+      running accumulation of sum of the differences as they are read.
+
+      As a special case, if a (B,H,S) encoding is a subrange, its delta
+      version will only encode arrays of numbers in the coding's unsigned
+      range, [0..R-1].  The coding of deltas is still in the normal signed
+      range, if S!=0.  During delta encoding, all subtraction results are
+      reduced to the signed range, by adding multiples of R.  Likewise,
+.     during encoding, all addition results are reduced to the unsigned range.
+      This special case for subranges allows the benefits of wraparound
+      when encoding correlated sequences of very small positive numbers.
+     */
+
+    // Code-specific limits:
+    private static int saturate32(long x) {
+        if (x > Integer.MAX_VALUE)   return Integer.MAX_VALUE;
+        if (x < Integer.MIN_VALUE)   return Integer.MIN_VALUE;
+        return (int)x;
+    }
+    private static long codeRangeLong(int B, int H) {
+        return codeRangeLong(B, H, B);
+    }
+    private static long codeRangeLong(int B, int H, int nMax) {
+        // Code range for a all (B,H) codes of length <=nMax (<=B).
+        // n < B:   L*Sum[i= 0 && nMax <= B);
+        assert(B >= 1 && B <= 5);
+        assert(H >= 1 && H <= 256);
+        if (nMax == 0)  return 0;  // no codes of zero length
+        if (B == 1)     return H;  // special case; see (**) above
+        int L = 256-H;
+        long sum = 0;
+        long H_i = 1;
+        for (int n = 1; n <= nMax; n++) {
+            sum += H_i;
+            H_i *= H;
+        }
+        sum *= L;
+        if (nMax == B)
+            sum += H_i;
+        return sum;
+    }
+    /** Largest int representable by (B,H,S) in up to nMax bytes. */
+    public static int codeMax(int B, int H, int S, int nMax) {
+        //assert(S >= 0 && S <= S_MAX);
+        long range = codeRangeLong(B, H, nMax);
+        if (range == 0)
+            return -1;  // degenerate max value for empty set of codes
+        if (S == 0 || range >= (long)1<<32)
+            return saturate32(range-1);
+        long maxPos = range-1;
+        while (isNegativeCode(maxPos, S)) {
+            --maxPos;
+        }
+        if (maxPos < 0)  return -1;  // No positive codings at all.
+        int smax = decodeSign32(maxPos, S);
+        // check for 32-bit wraparound:
+        if (smax < 0)
+            return Integer.MAX_VALUE;
+        return smax;
+    }
+    /** Smallest int representable by (B,H,S) in up to nMax bytes.
+        Returns Integer.MIN_VALUE if 32-bit wraparound covers
+        the entire negative range.
+     */
+    public static int codeMin(int B, int H, int S, int nMax) {
+        //assert(S >= 0 && S <= S_MAX);
+        long range = codeRangeLong(B, H, nMax);
+        if (range >= (long)1<<32 && nMax == B) {
+            // Can code negative values via 32-bit wraparound.
+            return Integer.MIN_VALUE;
+        }
+        if (S == 0) {
+            return 0;
+        }
+        long maxNeg = range-1;
+        while (!isNegativeCode(maxNeg, S))
+            --maxNeg;
+
+        if (maxNeg < 0)  return 0;  // No negative codings at all.
+        return decodeSign32(maxNeg, S);
+    }
+
+    // Some of the arithmetic below is on unsigned 32-bit integers.
+    // These must be represented in Java as longs in the range [0..2^32-1].
+    // The conversion to a signed int is just the Java cast (int), but
+    // the conversion to an unsigned int is the following little method:
+    private static long toUnsigned32(int sx) {
+        return ((long)sx << 32) >>> 32;
+    }
+
+    // Sign encoding:
+    private static boolean isNegativeCode(long ux, int S) {
+        assert(S > 0);
+        assert(ux >= -1);  // can be out of 32-bit range; who cares
+        int Smask = (1< 0);
+        // If S>=2 very low negatives are coded by 32-bit-wrapped positives.
+        // The lowest negative representable by a negative coding is
+        // ~(umax32 >> S), and the next lower number is coded by wrapping
+        // the highest positive:
+        //    CodePos(umax32-1)  ->  (umax32-1)-((umax32-1)>>S)
+        // which simplifies to ~(umax32 >> S)-1.
+        return (0 > sx) && (sx >= ~(-1>>>S));
+    }
+    private static int decodeSign32(long ux, int S) {
+        assert(ux == toUnsigned32((int)ux))  // must be unsigned 32-bit number
+            : (Long.toHexString(ux));
+        if (S == 0) {
+            return (int) ux;  // cast to signed int
+        }
+        int sx;
+        if (isNegativeCode(ux, S)) {
+            // Sgn(x)  == -(x / 2^S)-1
+            sx = ~((int)ux >>> S);
+        } else {
+            // Sgn(x)  == (x / 2^S)*(2^S-1) + (x % 2^S)
+            sx = (int)ux - ((int)ux >>> S);
+        }
+        // Assert special case of S==1:
+        assert(!(S == 1) || sx == (((int)ux >>> 1) ^ -((int)ux & 1)));
+        return sx;
+    }
+    private static long encodeSign32(int sx, int S) {
+        if (S == 0) {
+            return toUnsigned32(sx);  // unsigned 32-bit int
+        }
+        int Smask = (1< "+
+               Integer.toHexString(sx)+" != "+
+               Integer.toHexString(decodeSign32(ux, S)));
+        return ux;
+    }
+
+    // Top-level coding of single integers:
+    public static void writeInt(byte[] out, int[] outpos, int sx, int B, int H, int S) {
+        long ux = encodeSign32(sx, S);
+        assert(ux == toUnsigned32((int)ux));
+        assert(ux < codeRangeLong(B, H))
+            : Long.toHexString(ux);
+        int L = 256-H;
+        long sum = ux;
+        int pos = outpos[0];
+        for (int i = 0; i < B-1; i++) {
+            if (sum < L)
+                break;
+            sum -= L;
+            int b_i = (int)( L + (sum % H) );
+            sum /= H;
+            out[pos++] = (byte)b_i;
+        }
+        out[pos++] = (byte)sum;
+        // Report number of bytes written by updating outpos[0]:
+        outpos[0] = pos;
+        // Check right away for mis-coding.
+        //assert(sx == readInt(out, new int[1], B, H, S));
+    }
+    public static int readInt(byte[] in, int[] inpos, int B, int H, int S) {
+        // U(b[i]: i= 0 && sum < codeRangeLong(B, H));
+        // Report number of bytes read by updating inpos[0]:
+        inpos[0] = pos;
+        return decodeSign32(sum, S);
+    }
+    // The Stream version doesn't fetch a byte unless it is needed for coding.
+    public static int readIntFrom(InputStream in, int B, int H, int S) throws IOException {
+        // U(b[i]: i= 0 && sum < codeRangeLong(B, H));
+        return decodeSign32(sum, S);
+    }
+
+    public static final int B_MAX = 5;    /* B: [1,5] */
+    public static final int H_MAX = 256;  /* H: [1,256] */
+    public static final int S_MAX = 2;    /* S: [0,2] */
+
+    // END OF STATICS.
+
+    private final int B; /*1..5*/       // # bytes (1..5)
+    private final int H; /*1..256*/     // # codes requiring a higher byte
+    private final int L; /*0..255*/     // # codes requiring a higher byte
+    private final int S; /*0..3*/       // # low-order bits representing sign
+    private final int del; /*0..2*/     // type of delta encoding (0 == none)
+    private final int min;              // smallest representable value
+    private final int max;              // largest representable value
+    private final int umin;             // smallest representable uns. value
+    private final int umax;             // largest representable uns. value
+    private final int[] byteMin;        // smallest repr. value, given # bytes
+    private final int[] byteMax;        // largest repr. value, given # bytes
+
+    private Coding(int B, int H, int S) {
+        this(B, H, S, 0);
+    }
+    private Coding(int B, int H, int S, int del) {
+        this.B = B;
+        this.H = H;
+        this.L = 256-H;
+        this.S = S;
+        this.del = del;
+        this.min = codeMin(B, H, S, B);
+        this.max = codeMax(B, H, S, B);
+        this.umin = codeMin(B, H, 0, B);
+        this.umax = codeMax(B, H, 0, B);
+        this.byteMin = new int[B];
+        this.byteMax = new int[B];
+
+        for (int nMax = 1; nMax <= B; nMax++) {
+            byteMin[nMax-1] = codeMin(B, H, S, nMax);
+            byteMax[nMax-1] = codeMax(B, H, S, nMax);
+        }
+    }
+
+    public boolean equals(Object x) {
+        if (!(x instanceof Coding))  return false;
+        Coding that = (Coding) x;
+        if (this.B != that.B)  return false;
+        if (this.H != that.H)  return false;
+        if (this.S != that.S)  return false;
+        if (this.del != that.del)  return false;
+        return true;
+    }
+
+    public int hashCode() {
+        return (del<<14)+(S<<11)+(B<<8)+(H<<0);
+    }
+
+    private static Map codeMap;
+
+    private static synchronized Coding of(int B, int H, int S, int del) {
+        if (codeMap == null)  codeMap = new HashMap<>();
+        Coding x0 = new Coding(B, H, S, del);
+        Coding x1 = codeMap.get(x0);
+        if (x1 == null)  codeMap.put(x0, x1 = x0);
+        return x1;
+    }
+
+    public static Coding of(int B, int H) {
+        return of(B, H, 0, 0);
+    }
+
+    public static Coding of(int B, int H, int S) {
+        return of(B, H, S, 0);
+    }
+
+    public boolean canRepresentValue(int x) {
+        if (isSubrange())
+            return canRepresentUnsigned(x);
+        else
+            return canRepresentSigned(x);
+    }
+    /** Can this coding represent a single value, possibly a delta?
+     *  This ignores the D property.  That is, for delta codings,
+     *  this tests whether a delta value of 'x' can be coded.
+     *  For signed delta codings which produce unsigned end values,
+     *  use canRepresentUnsigned.
+     */
+    public boolean canRepresentSigned(int x) {
+        return (x >= min && x <= max);
+    }
+    /** Can this coding, apart from its S property,
+     *  represent a single value?  (Negative values
+     *  can only be represented via 32-bit overflow,
+     *  so this returns true for negative values
+     *  if isFullRange is true.)
+     */
+    public boolean canRepresentUnsigned(int x) {
+        return (x >= umin && x <= umax);
+    }
+
+    // object-oriented code/decode
+    public int readFrom(byte[] in, int[] inpos) {
+        return readInt(in, inpos, B, H, S);
+    }
+    public void writeTo(byte[] out, int[] outpos, int x) {
+        writeInt(out, outpos, x, B, H, S);
+    }
+
+    // Stream versions
+    public int readFrom(InputStream in) throws IOException {
+        return readIntFrom(in, B, H, S);
+    }
+    public void writeTo(OutputStream out, int x) throws IOException {
+        byte[] buf = new byte[B];
+        int[] pos = new int[1];
+        writeInt(buf, pos, x, B, H, S);
+        out.write(buf, 0, pos[0]);
+    }
+
+    // Stream/array versions
+    public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException {
+        // %%% use byte[] buffer
+        for (int i = start; i < end; i++)
+            a[i] = readFrom(in);
+
+        for (int dstep = 0; dstep < del; dstep++) {
+            long state = 0;
+            for (int i = start; i < end; i++) {
+                state += a[i];
+                // Reduce array values to the required range.
+                if (isSubrange()) {
+                    state = reduceToUnsignedRange(state);
+                }
+                a[i] = (int) state;
+            }
+        }
+    }
+    public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException {
+        if (end <= start)  return;
+        for (int dstep = 0; dstep < del; dstep++) {
+            int[] deltas;
+            if (!isSubrange())
+                deltas = makeDeltas(a, start, end, 0, 0);
+            else
+                deltas = makeDeltas(a, start, end, min, max);
+            a = deltas;
+            start = 0;
+            end = deltas.length;
+        }
+        // The following code is a buffered version of this loop:
+        //    for (int i = start; i < end; i++)
+        //        writeTo(out, a[i]);
+        byte[] buf = new byte[1<<8];
+        final int bufmax = buf.length-B;
+        int[] pos = { 0 };
+        for (int i = start; i < end; ) {
+            while (pos[0] <= bufmax) {
+                writeTo(buf, pos, a[i++]);
+                if (i >= end)  break;
+            }
+            out.write(buf, 0, pos[0]);
+            pos[0] = 0;
+        }
+    }
+
+    /** Tell if the range of this coding (number of distinct
+     *  representable values) can be expressed in 32 bits.
+     */
+    boolean isSubrange() {
+        return max < Integer.MAX_VALUE
+            && ((long)max - (long)min + 1) <= Integer.MAX_VALUE;
+    }
+
+    /** Tell if this coding can represent all 32-bit values.
+     *  Note:  Some codings, such as unsigned ones, can be neither
+     *  subranges nor full-range codings.
+     */
+    boolean isFullRange() {
+        return max == Integer.MAX_VALUE && min == Integer.MIN_VALUE;
+    }
+
+    /** Return the number of values this coding (a subrange) can represent. */
+    int getRange() {
+        assert(isSubrange());
+        return (max - min) + 1;  // range includes both min & max
+    }
+
+    Coding setB(int B) { return Coding.of(B, H, S, del); }
+    Coding setH(int H) { return Coding.of(B, H, S, del); }
+    Coding setS(int S) { return Coding.of(B, H, S, del); }
+    Coding setL(int L) { return setH(256-L); }
+    Coding setD(int del) { return Coding.of(B, H, S, del); }
+    Coding getDeltaCoding() { return setD(del+1); }
+
+    /** Return a coding suitable for representing summed, modulo-reduced values. */
+    Coding getValueCoding() {
+        if (isDelta())
+            return Coding.of(B, H, 0, del-1);
+        else
+            return this;
+    }
+
+    /** Reduce the given value to be within this coding's unsigned range,
+     *  by adding or subtracting a multiple of (max-min+1).
+     */
+    int reduceToUnsignedRange(long value) {
+        if (value == (int)value && canRepresentUnsigned((int)value))
+            // already in unsigned range
+            return (int)value;
+        int range = getRange();
+        assert(range > 0);
+        value %= range;
+        if (value < 0)  value += range;
+        assert(canRepresentUnsigned((int)value));
+        return (int)value;
+    }
+
+    int reduceToSignedRange(int value) {
+        if (canRepresentSigned(value))
+            // already in signed range
+            return value;
+        return reduceToSignedRange(value, min, max);
+    }
+    static int reduceToSignedRange(int value, int min, int max) {
+        int range = (max-min+1);
+        assert(range > 0);
+        int value0 = value;
+        value -= min;
+        if (value < 0 && value0 >= 0) {
+            // 32-bit overflow, but the next '%=' op needs to be unsigned
+            value -= range;
+            assert(value >= 0);
+        }
+        value %= range;
+        if (value < 0)  value += range;
+        value += min;
+        assert(min <= value && value <= max);
+        return value;
+    }
+
+    /** Does this coding support at least one negative value?
+        Includes codings that can do so via 32-bit wraparound.
+     */
+    boolean isSigned() {
+        return min < 0;
+    }
+    /** Does this coding code arrays by making successive differences? */
+    boolean isDelta() {
+        return del != 0;
+    }
+
+    public int B() { return B; }
+    public int H() { return H; }
+    public int L() { return L; }
+    public int S() { return S; }
+    public int del() { return del; }
+    public int min() { return min; }
+    public int max() { return max; }
+    public int umin() { return umin; }
+    public int umax() { return umax; }
+    public int byteMin(int b) { return byteMin[b-1]; }
+    public int byteMax(int b) { return byteMax[b-1]; }
+
+    public int compareTo(Coding that) {
+        int dkey = this.del - that.del;
+        if (dkey == 0)
+            dkey = this.B - that.B;
+        if (dkey == 0)
+            dkey = this.H - that.H;
+        if (dkey == 0)
+            dkey = this.S - that.S;
+        return dkey;
+    }
+
+    /** Heuristic measure of the difference between two codings. */
+    public int distanceFrom(Coding that) {
+        int diffdel = this.del - that.del;
+        if (diffdel < 0)  diffdel = -diffdel;
+        int diffS = this.S - that.S;
+        if (diffS < 0)  diffS = -diffS;
+        int diffB = this.B - that.B;
+        if (diffB < 0)  diffB = -diffB;
+        int diffHL;
+        if (this.H == that.H) {
+            diffHL = 0;
+        } else {
+            // Distance in log space of H (<=128) and L (<128).
+            int thisHL = this.getHL();
+            int thatHL = that.getHL();
+            // Double the accuracy of the log:
+            thisHL *= thisHL;
+            thatHL *= thatHL;
+            if (thisHL > thatHL)
+                diffHL = ceil_lg2(1+(thisHL-1)/thatHL);
+            else
+                diffHL = ceil_lg2(1+(thatHL-1)/thisHL);
+        }
+        int norm = 5*(diffdel + diffS + diffB) + diffHL;
+        assert(norm != 0 || this.compareTo(that) == 0);
+        return norm;
+    }
+    private int getHL() {
+        // Follow H in log space by the multiplicative inverse of L.
+        if (H <= 128)  return H;
+        if (L >= 1)    return 128*128/L;
+        return 128*256;
+    }
+
+    /** ceiling(log[2](x)): {1->0, 2->1, 3->2, 4->2, ...} */
+    static int ceil_lg2(int x) {
+        assert(x-1 >= 0);  // x in range (int.MIN_VALUE -> 32)
+        x -= 1;
+        int lg = 0;
+        while (x != 0) {
+            lg++;
+            x >>= 1;
+        }
+        return lg;
+    }
+
+    private static final byte[] byteBitWidths = new byte[0x100];
+    static {
+        for (int b = 0; b < byteBitWidths.length; b++) {
+            byteBitWidths[b] = (byte) ceil_lg2(b + 1);
+        }
+        for (int i = 10; i >= 0; i = (i << 1) - (i >> 3)) {
+            assert(bitWidth(i) == ceil_lg2(i + 1));
+        }
+    }
+
+    /** Number of significant bits in i, not counting sign bits.
+     *  For positive i, it is ceil_lg2(i + 1).
+     */
+    static int bitWidth(int i) {
+        if (i < 0)  i = ~i;  // change sign
+        int w = 0;
+        int lo = i;
+        if (lo < byteBitWidths.length)
+            return byteBitWidths[lo];
+        int hi;
+        hi = (lo >>> 16);
+        if (hi != 0) {
+            lo = hi;
+            w += 16;
+        }
+        hi = (lo >>> 8);
+        if (hi != 0) {
+            lo = hi;
+            w += 8;
+        }
+        w += byteBitWidths[lo];
+        //assert(w == ceil_lg2(i + 1));
+        return w;
+    }
+
+    /** Create an array of successive differences.
+     *  If min==max, accept any and all 32-bit overflow.
+     *  Otherwise, avoid 32-bit overflow, and reduce all differences
+     *  to a value in the given range, by adding or subtracting
+     *  multiples of the range cardinality (max-min+1).
+     *  Also, the values are assumed to be in the range [0..(max-min)].
+     */
+    static int[] makeDeltas(int[] values, int start, int end,
+                            int min, int max) {
+        assert(max >= min);
+        int count = end-start;
+        int[] deltas = new int[count];
+        int state = 0;
+        if (min == max) {
+            for (int i = 0; i < count; i++) {
+                int value = values[start+i];
+                deltas[i] = value - state;
+                state = value;
+            }
+        } else {
+            for (int i = 0; i < count; i++) {
+                int value = values[start+i];
+                assert(value >= 0 && value+min <= max);
+                int delta = value - state;
+                assert(delta == (long)value - (long)state); // no overflow
+                state = value;
+                // Reduce delta values to the required range.
+                delta = reduceToSignedRange(delta, min, max);
+                deltas[i] = delta;
+            }
+        }
+        return deltas;
+    }
+
+    boolean canRepresent(int minValue, int maxValue) {
+        assert(minValue <= maxValue);
+        if (del > 0) {
+            if (isSubrange()) {
+                // We will force the values to reduce to the right subrange.
+                return canRepresentUnsigned(maxValue)
+                    && canRepresentUnsigned(minValue);
+            } else {
+                // Huge range; delta values must assume full 32-bit range.
+                return isFullRange();
+            }
+        }
+        else
+            // final values must be representable
+            return canRepresentSigned(maxValue)
+                && canRepresentSigned(minValue);
+    }
+
+    boolean canRepresent(int[] values, int start, int end) {
+        int len = end-start;
+        if (len == 0)       return true;
+        if (isFullRange())  return true;
+        // Calculate max, min:
+        int lmax = values[start];
+        int lmin = lmax;
+        for (int i = 1; i < len; i++) {
+            int value = values[start+i];
+            if (lmax < value)  lmax = value;
+            if (lmin > value)  lmin = value;
+        }
+        return canRepresent(lmin, lmax);
+    }
+
+    public double getBitLength(int value) {  // implements BitMetric
+        return (double) getLength(value) * 8;
+    }
+
+    /** How many bytes are in the coding of this value?
+     *  Returns Integer.MAX_VALUE if the value has no coding.
+     *  The coding must not be a delta coding, since there is no
+     *  definite size for a single value apart from its context.
+     */
+    public int getLength(int value) {
+        if (isDelta() && isSubrange()) {
+            if (!canRepresentUnsigned(value))
+                return Integer.MAX_VALUE;
+            value = reduceToSignedRange(value);
+        }
+        if (value >= 0) {
+            for (int n = 0; n < B; n++) {
+                if (value <= byteMax[n])  return n+1;
+            }
+        } else {
+            for (int n = 0; n < B; n++) {
+                if (value >= byteMin[n])  return n+1;
+            }
+        }
+        return Integer.MAX_VALUE;
+    }
+
+    public int getLength(int[] values, int start, int end) {
+        int len = end-start;
+        if (B == 1)  return len;
+        if (L == 0)  return len * B;
+        if (isDelta()) {
+            int[] deltas;
+            if (!isSubrange())
+                deltas = makeDeltas(values, start, end, 0, 0);
+            else
+                deltas = makeDeltas(values, start, end, min, max);
+            //return Coding.of(B, H, S).getLength(deltas, 0, len);
+            values = deltas;
+            start = 0;
+        }
+        int sum = len;  // at least 1 byte per
+        // add extra bytes for extra-long values
+        for (int n = 1; n <= B; n++) {
+            // what is the coding interval [min..max] for n bytes?
+            int lmax = byteMax[n-1];
+            int lmin = byteMin[n-1];
+            int longer = 0;  // count of guys longer than n bytes
+            for (int i = 0; i < len; i++) {
+                int value = values[start+i];
+                if (value >= 0) {
+                    if (value > lmax)  longer++;
+                } else {
+                    if (value < lmin)  longer++;
+                }
+            }
+            if (longer == 0)  break;  // no more passes needed
+            if (n == B)  return Integer.MAX_VALUE;  // cannot represent!
+            sum += longer;
+        }
+        return sum;
+    }
+
+    public byte[] getMetaCoding(Coding dflt) {
+        if (dflt == this)  return new byte[]{ (byte) _meta_default };
+        int canonicalIndex = BandStructure.indexOf(this);
+        if (canonicalIndex > 0)
+            return new byte[]{ (byte) canonicalIndex };
+        return new byte[]{
+            (byte)_meta_arb,
+            (byte)(del + 2*S + 8*(B-1)),
+            (byte)(H-1)
+        };
+    }
+    public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod res[]) {
+        int op = bytes[pos++] & 0xFF;
+        if (_meta_canon_min <= op && op <= _meta_canon_max) {
+            Coding c = BandStructure.codingForIndex(op);
+            assert(c != null);
+            res[0] = c;
+            return pos;
+        }
+        if (op == _meta_arb) {
+            int dsb = bytes[pos++] & 0xFF;
+            int H_1 = bytes[pos++] & 0xFF;
+            int del = dsb % 2;
+            int S = (dsb / 2) % 4;
+            int B = (dsb / 8)+1;
+            int H = H_1+1;
+            if (!((1 <= B && B <= B_MAX) &&
+                  (0 <= S && S <= S_MAX) &&
+                  (1 <= H && H <= H_MAX) &&
+                  (0 <= del && del <= 1))
+                || (B == 1 && H != 256)
+                || (B == 5 && H == 256)) {
+                throw new RuntimeException("Bad arb. coding: ("+B+","+H+","+S+","+del);
+            }
+            res[0] = Coding.of(B, H, S, del);
+            return pos;
+        }
+        return pos-1;  // backup
+    }
+
+
+    public String keyString() {
+        return "("+B+","+H+","+S+","+del+")";
+    }
+
+    public String toString() {
+        String str = "Coding"+keyString();
+        // If -ea, print out more informative strings!
+        //assert((str = stringForDebug()) != null);
+        return str;
+    }
+
+    static boolean verboseStringForDebug = false;
+    String stringForDebug() {
+        String minS = (min == Integer.MIN_VALUE ? "min" : ""+min);
+        String maxS = (max == Integer.MAX_VALUE ? "max" : ""+max);
+        String str = keyString()+" L="+L+" r=["+minS+","+maxS+"]";
+        if (isSubrange())
+            str += " subrange";
+        else if (!isFullRange())
+            str += " MIDRANGE";
+        if (verboseStringForDebug) {
+            str += " {";
+            int prev_range = 0;
+            for (int n = 1; n <= B; n++) {
+                int range_n = saturate32((long)byteMax[n-1] - byteMin[n-1] + 1);
+                assert(range_n == saturate32(codeRangeLong(B, H, n)));
+                range_n -= prev_range;
+                prev_range = range_n;
+                String rngS = (range_n == Integer.MAX_VALUE ? "max" : ""+range_n);
+                str += " #"+n+"="+rngS;
+            }
+            str += " }";
+        }
+        return str;
+    }
+}
diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/CodingChooser.java b/src/main/java/net/minecraftforge/gradle/util/pack200/CodingChooser.java
new file mode 100644
index 000000000..7a8b03e29
--- /dev/null
+++ b/src/main/java/net/minecraftforge/gradle/util/pack200/CodingChooser.java
@@ -0,0 +1,1489 @@
+/*
+ * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package net.minecraftforge.gradle.util.pack200;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import static net.minecraftforge.gradle.util.pack200.Constants.*;
+/**
+ * Heuristic chooser of basic encodings.
+ * Runs "zip" to measure the apparent information content after coding.
+ * @author John Rose
+ */
+class CodingChooser {
+    int verbose;
+    int effort;
+    boolean optUseHistogram = true;
+    boolean optUsePopulationCoding = true;
+    boolean optUseAdaptiveCoding = true;
+    boolean disablePopCoding;
+    boolean disableRunCoding;
+    boolean topLevel = true;
+
+    // Derived from effort; >1 (<1) means try more (less) experiments
+    // when looking to beat a best score.
+    double fuzz;
+
+    Coding[] allCodingChoices;
+    Choice[] choices;
+    ByteArrayOutputStream context;
+    CodingChooser popHelper;
+    CodingChooser runHelper;
+
+    Random stress;  // If not null, stress mode oracle.
+
+    // Element in sorted set of coding choices:
+    static
+    class Choice {
+        final Coding coding;
+        final int index;       // index in choices
+        final int[] distance;  // cache of distance
+        Choice(Coding coding, int index, int[] distance) {
+            this.coding   = coding;
+            this.index    = index;
+            this.distance = distance;
+        }
+        // These variables are reset and reused:
+        int searchOrder; // order in which it is checked
+        int minDistance; // min distance from already-checked choices
+        int zipSize;     // size of encoding in sample, zipped output
+        int byteSize;    // size of encoding in sample (debug only)
+        int histSize;    // size of encoding, according to histogram
+
+        void reset() {
+            searchOrder = Integer.MAX_VALUE;
+            minDistance = Integer.MAX_VALUE;
+            zipSize = byteSize = histSize = -1;
+        }
+
+        boolean isExtra() {
+            return index < 0;
+        }
+
+        public String toString() {
+            return stringForDebug();
+        }
+
+        private String stringForDebug() {
+            String s = "";
+            if (searchOrder < Integer.MAX_VALUE)
+                s += " so: "+searchOrder;
+            if (minDistance < Integer.MAX_VALUE)
+                s += " md: "+minDistance;
+            if (zipSize > 0)
+                s += " zs: "+zipSize;
+            if (byteSize > 0)
+                s += " bs: "+byteSize;
+            if (histSize > 0)
+                s += " hs: "+histSize;
+            return "Choice["+index+"] "+s+" "+coding;
+        }
+    }
+
+    CodingChooser(int effort, Coding[] allCodingChoices) {
+        PropMap p200 = Utils.currentPropMap();
+        if (p200 != null) {
+            this.verbose
+                = Math.max(p200.getInteger(Utils.DEBUG_VERBOSE),
+                           p200.getInteger(Utils.COM_PREFIX+"verbose.coding"));
+            this.optUseHistogram
+                = !p200.getBoolean(Utils.COM_PREFIX+"no.histogram");
+            this.optUsePopulationCoding
+                = !p200.getBoolean(Utils.COM_PREFIX+"no.population.coding");
+            this.optUseAdaptiveCoding
+                = !p200.getBoolean(Utils.COM_PREFIX+"no.adaptive.coding");
+            int lstress
+                = p200.getInteger(Utils.COM_PREFIX+"stress.coding");
+            if (lstress != 0)
+                this.stress = new Random(lstress);
+        }
+
+        this.effort = effort;
+        // The following line "makes sense" but is too much
+        // work for a simple heuristic.
+        //if (effort > 5)  zipDef.setLevel(effort);
+
+        this.allCodingChoices = allCodingChoices;
+
+        // If effort = 9, look carefully at any solution
+        // whose initial metrics are within 1% of the best
+        // so far.  If effort = 1, look carefully only at
+        // solutions whose initial metrics promise a 1% win.
+        this.fuzz = 1 + (0.0025 * (effort-MID_EFFORT));
+
+        int nc = 0;
+        for (int i = 0; i < allCodingChoices.length; i++) {
+            if (allCodingChoices[i] == null)  continue;
+            nc++;
+        }
+        choices = new Choice[nc];
+        nc = 0;
+        for (int i = 0; i < allCodingChoices.length; i++) {
+            if (allCodingChoices[i] == null)  continue;
+            int[] distance = new int[choices.length];
+            choices[nc++] = new Choice(allCodingChoices[i], i, distance);
+        }
+        for (int i = 0; i < choices.length; i++) {
+            Coding ci = choices[i].coding;
+            assert(ci.distanceFrom(ci) == 0);
+            for (int j = 0; j < i; j++) {
+                Coding cj = choices[j].coding;
+                int dij = ci.distanceFrom(cj);
+                assert(dij > 0);
+                assert(dij == cj.distanceFrom(ci));
+                choices[i].distance[j] = dij;
+                choices[j].distance[i] = dij;
+            }
+        }
+    }
+
+    Choice makeExtraChoice(Coding coding) {
+        int[] distance = new int[choices.length];
+        for (int i = 0; i < distance.length; i++) {
+            Coding ci = choices[i].coding;
+            int dij = coding.distanceFrom(ci);
+            assert(dij > 0);
+            assert(dij == ci.distanceFrom(coding));
+            distance[i] = dij;
+        }
+        Choice c = new Choice(coding, -1, distance);
+        c.reset();
+        return c;
+    }
+
+    ByteArrayOutputStream getContext() {
+        if (context == null)
+            context = new ByteArrayOutputStream(1 << 16);
+        return context;
+    }
+
+    // These variables are reset and reused:
+    private int[] values;
+    private int start, end;  // slice of values
+    private int[] deltas;
+    private int min, max;
+    private Histogram vHist;
+    private Histogram dHist;
+    private int searchOrder;
+    private Choice regularChoice;
+    private Choice bestChoice;
+    private CodingMethod bestMethod;
+    private int bestByteSize;
+    private int bestZipSize;
+    private int targetSize;   // fuzzed target byte size
+
+    private void reset(int[] values, int start, int end) {
+        this.values = values;
+        this.start = start;
+        this.end = end;
+        this.deltas = null;
+        this.min = Integer.MAX_VALUE;
+        this.max = Integer.MIN_VALUE;
+        this.vHist = null;
+        this.dHist = null;
+        this.searchOrder = 0;
+        this.regularChoice = null;
+        this.bestChoice = null;
+        this.bestMethod = null;
+        this.bestZipSize = Integer.MAX_VALUE;
+        this.bestByteSize = Integer.MAX_VALUE;
+        this.targetSize = Integer.MAX_VALUE;
+    }
+
+    public static final int MIN_EFFORT = 1;
+    public static final int MID_EFFORT = 5;
+    public static final int MAX_EFFORT = 9;
+
+    public static final int POP_EFFORT = MID_EFFORT-1;
+    public static final int RUN_EFFORT = MID_EFFORT-2;
+
+    public static final int BYTE_SIZE = 0;
+    public static final int ZIP_SIZE = 1;
+
+    CodingMethod choose(int[] values, int start, int end, Coding regular, int[] sizes) {
+        // Save the value array
+        reset(values, start, end);
+
+        if (effort <= MIN_EFFORT || start >= end) {
+            if (sizes != null) {
+                int[] computed = computeSizePrivate(regular);
+                sizes[BYTE_SIZE] = computed[BYTE_SIZE];
+                sizes[ZIP_SIZE]  = computed[ZIP_SIZE];
+            }
+            return regular;
+        }
+
+        if (optUseHistogram) {
+            getValueHistogram();
+            getDeltaHistogram();
+        }
+
+        for (int i = start; i < end; i++) {
+            int val = values[i];
+            if (min > val)  min = val;
+            if (max < val)  max = val;
+        }
+
+        // Find all the preset choices that might be worth looking at:
+        int numChoices = markUsableChoices(regular);
+
+        if (stress != null) {
+            // Make a random choice.
+            int rand = stress.nextInt(numChoices*2 + 4);
+            CodingMethod coding = null;
+            for (int i = 0; i < choices.length; i++) {
+                Choice c = choices[i];
+                if (c.searchOrder >= 0 && rand-- == 0) {
+                    coding = c.coding;
+                    break;
+                }
+            }
+            if (coding == null) {
+                if ((rand & 7) != 0) {
+                    coding = regular;
+                } else {
+                    // Pick a totally random coding 6% of the time.
+                    coding = stressCoding(min, max);
+                }
+            }
+            if (!disablePopCoding
+                && optUsePopulationCoding
+                && effort >= POP_EFFORT) {
+                coding = stressPopCoding(coding);
+            }
+            if (!disableRunCoding
+                && optUseAdaptiveCoding
+                && effort >= RUN_EFFORT) {
+                coding = stressAdaptiveCoding(coding);
+            }
+            return coding;
+        }
+
+        double searchScale = 1.0;
+        for (int x = effort; x < MAX_EFFORT; x++) {
+            searchScale /= 1.414;  // every 2 effort points doubles work
+        }
+        int searchOrderLimit = (int)Math.ceil( numChoices * searchScale );
+
+        // Start by evaluating the "regular" choice.
+        bestChoice = regularChoice;
+        evaluate(regularChoice);
+        int maxd = updateDistances(regularChoice);
+
+        // save these first-cut numbers for later
+        int zipSize1 = bestZipSize;
+        int byteSize1 = bestByteSize;
+
+        if (regularChoice.coding == regular && topLevel) {
+            // Give credit for being the default; no band header is needed.
+            // Rather than increasing every other size value by the band
+            // header amount, we decrement this one metric, to give it an edge.
+            // Decreasing zipSize by a byte length is conservatively correct,
+            // especially considering that the escape byte is not likely to
+            // zip well with other bytes in the band.
+            int X = BandStructure.encodeEscapeValue(_meta_canon_max, regular);
+            if (regular.canRepresentSigned(X)) {
+                int Xlen = regular.getLength(X);  // band coding header
+                //regularChoice.histSize -= Xlen; // keep exact byteSize
+                //regularChoice.byteSize -= Xlen; // keep exact byteSize
+                regularChoice.zipSize -= Xlen;
+                bestByteSize = regularChoice.byteSize;
+                bestZipSize = regularChoice.zipSize;
+            }
+        }
+
+        int dscale = 1;
+        // Continually select a new choice to evaluate.
+        while (searchOrder < searchOrderLimit) {
+            Choice nextChoice;
+            if (dscale > maxd)  dscale = 1;  // cycle dscale values!
+            int dhi = maxd / dscale;
+            int dlo = maxd / (dscale *= 2) + 1;
+            nextChoice = findChoiceNear(bestChoice, dhi, dlo);
+            if (nextChoice == null)  continue;
+            assert(nextChoice.coding.canRepresent(min, max));
+            evaluate(nextChoice);
+            int nextMaxd = updateDistances(nextChoice);
+            if (nextChoice == bestChoice) {
+                maxd = nextMaxd;
+                if (verbose > 5)  Utils.log.info("maxd = "+maxd);
+            }
+        }
+
+        // Record best "plain coding" choice.
+        Coding plainBest = bestChoice.coding;
+        assert(plainBest == bestMethod);
+
+        if (verbose > 2) {
+            Utils.log.info("chooser: plain result="+bestChoice+" after "+bestChoice.searchOrder+" rounds, "+(regularChoice.zipSize-bestZipSize)+" fewer bytes than regular "+regular);
+        }
+        bestChoice = null;
+
+        if (!disablePopCoding
+            && optUsePopulationCoding
+            && effort >= POP_EFFORT
+            && bestMethod instanceof Coding) {
+            tryPopulationCoding(plainBest);
+        }
+
+        if (!disableRunCoding
+            && optUseAdaptiveCoding
+            && effort >= RUN_EFFORT
+            && bestMethod instanceof Coding) {
+            tryAdaptiveCoding(plainBest);
+        }
+
+        // Pass back the requested information:
+        if (sizes != null) {
+            sizes[BYTE_SIZE] = bestByteSize;
+            sizes[ZIP_SIZE]  = bestZipSize;
+        }
+        if (verbose > 1) {
+            Utils.log.info("chooser: result="+bestMethod+" "+
+                             (zipSize1-bestZipSize)+
+                             " fewer bytes than regular "+regular+
+                             "; win="+pct(zipSize1-bestZipSize, zipSize1));
+        }
+        CodingMethod lbestMethod = this.bestMethod;
+        reset(null, 0, 0);  // for GC
+        return lbestMethod;
+    }
+    CodingMethod choose(int[] values, int start, int end, Coding regular) {
+        return choose(values, start, end, regular, null);
+    }
+    CodingMethod choose(int[] values, Coding regular, int[] sizes) {
+        return choose(values, 0, values.length, regular, sizes);
+    }
+    CodingMethod choose(int[] values, Coding regular) {
+        return choose(values, 0, values.length, regular, null);
+    }
+
+    private int markUsableChoices(Coding regular) {
+        int numChoices = 0;
+        for (int i = 0; i < choices.length; i++) {
+            Choice c = choices[i];
+            c.reset();
+            if (!c.coding.canRepresent(min, max)) {
+                // Mark as already visited:
+                c.searchOrder = -1;
+                if (verbose > 1 && c.coding == regular) {
+                    Utils.log.info("regular coding cannot represent ["+min+".."+max+"]: "+regular);
+                }
+                continue;
+            }
+            if (c.coding == regular)
+                regularChoice = c;
+            numChoices++;
+        }
+        if (regularChoice == null && regular.canRepresent(min, max)) {
+            regularChoice = makeExtraChoice(regular);
+            if (verbose > 1) {
+                Utils.log.info("*** regular choice is extra: "+regularChoice.coding);
+            }
+        }
+        if (regularChoice == null) {
+            for (int i = 0; i < choices.length; i++) {
+                Choice c = choices[i];
+                if (c.searchOrder != -1) {
+                    regularChoice = c;  // arbitrary pick
+                    break;
+                }
+            }
+            if (verbose > 1) {
+                Utils.log.info("*** regular choice does not apply "+regular);
+                Utils.log.info("    using instead "+regularChoice.coding);
+            }
+        }
+        if (verbose > 2) {
+            Utils.log.info("chooser: #choices="+numChoices+" ["+min+".."+max+"]");
+            if (verbose > 4) {
+                for (int i = 0; i < choices.length; i++) {
+                    Choice c = choices[i];
+                    if (c.searchOrder >= 0)
+                        Utils.log.info("  "+c);
+                }
+            }
+        }
+        return numChoices;
+    }
+
+    // Find an arbitrary choice at least dlo away from a previously
+    // evaluated choices, and at most dhi.  Try also to regulate its
+    // min distance to all previously evaluated choices, in this range.
+    private Choice findChoiceNear(Choice near, int dhi, int dlo) {
+        if (verbose > 5)
+            Utils.log.info("findChoice "+dhi+".."+dlo+" near: "+near);
+        int[] distance = near.distance;
+        Choice found = null;
+        for (int i = 0; i < choices.length; i++) {
+            Choice c = choices[i];
+            if (c.searchOrder < searchOrder)
+                continue;  // already searched
+            // Distance from "near" guy must be in bounds:
+            if (distance[i] >= dlo && distance[i] <= dhi) {
+                // Try also to keep min-distance from other guys in bounds:
+                if (c.minDistance >= dlo && c.minDistance <= dhi) {
+                    if (verbose > 5)
+                        Utils.log.info("findChoice => good "+c);
+                    return c;
+                }
+                found = c;
+            }
+        }
+        if (verbose > 5)
+            Utils.log.info("findChoice => found "+found);
+        return found;
+    }
+
+    private void evaluate(Choice c) {
+        assert(c.searchOrder == Integer.MAX_VALUE);
+        c.searchOrder = searchOrder++;
+        boolean mustComputeSize;
+        if (c == bestChoice || c.isExtra()) {
+            mustComputeSize = true;
+        } else if (optUseHistogram) {
+            Histogram hist = getHistogram(c.coding.isDelta());
+            c.histSize = (int)Math.ceil(hist.getBitLength(c.coding) / 8);
+            c.byteSize = c.histSize;
+            mustComputeSize = (c.byteSize <= targetSize);
+        } else {
+            mustComputeSize = true;
+        }
+        if (mustComputeSize) {
+            int[] sizes = computeSizePrivate(c.coding);
+            c.byteSize = sizes[BYTE_SIZE];
+            c.zipSize  = sizes[ZIP_SIZE];
+            if (noteSizes(c.coding, c.byteSize, c.zipSize))
+                bestChoice = c;
+        }
+        if (c.histSize >= 0) {
+            assert(c.byteSize == c.histSize);  // models should agree
+        }
+        if (verbose > 4) {
+            Utils.log.info("evaluated "+c);
+        }
+    }
+
+    private boolean noteSizes(CodingMethod c, int byteSize, int zipSize) {
+        assert(zipSize > 0 && byteSize > 0);
+        boolean better = (zipSize < bestZipSize);
+        if (verbose > 3)
+            Utils.log.info("computed size "+c+" "+byteSize+"/zs="+zipSize+
+                             ((better && bestMethod != null)?
+                              (" better by "+
+                               pct(bestZipSize - zipSize, zipSize)): ""));
+        if (better) {
+            bestMethod = c;
+            bestZipSize = zipSize;
+            bestByteSize = byteSize;
+            targetSize = (int)(byteSize * fuzz);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+    private int updateDistances(Choice c) {
+        // update all minDistance values in still unevaluated choices
+        int[] distance = c.distance;
+        int maxd = 0;  // how far is c from everybody else?
+        for (int i = 0; i < choices.length; i++) {
+            Choice c2 = choices[i];
+            if (c2.searchOrder < searchOrder)
+                continue;
+            int d = distance[i];
+            if (verbose > 5)
+                Utils.log.info("evaluate dist "+d+" to "+c2);
+            int mind = c2.minDistance;
+            if (mind > d)
+                c2.minDistance = mind = d;
+            if (maxd < d)
+                maxd = d;
+        }
+        // Now maxd has the distance of the farthest outlier
+        // from all evaluated choices.
+        if (verbose > 5)
+            Utils.log.info("evaluate maxd => "+maxd);
+        return maxd;
+    }
+
+    // Compute the coded size of a sequence of values.
+    // The first int is the size in uncompressed bytes.
+    // The second is an estimate of the compressed size of these bytes.
+    public void computeSize(CodingMethod c, int[] values, int start, int end, int[] sizes) {
+        if (end <= start) {
+            sizes[BYTE_SIZE] = sizes[ZIP_SIZE] = 0;
+            return;
+        }
+        try {
+            resetData();
+            c.writeArrayTo(byteSizer, values, start, end);
+            sizes[BYTE_SIZE] = getByteSize();
+            sizes[ZIP_SIZE] = getZipSize();
+        } catch (IOException ee) {
+            throw new RuntimeException(ee); // cannot happen
+        }
+    }
+    public void computeSize(CodingMethod c, int[] values, int[] sizes) {
+        computeSize(c, values, 0, values.length, sizes);
+    }
+    public int[] computeSize(CodingMethod c, int[] values, int start, int end) {
+        int[] sizes = { 0, 0 };
+        computeSize(c, values, start, end, sizes);
+        return sizes;
+    }
+    public int[] computeSize(CodingMethod c, int[] values) {
+        return computeSize(c, values, 0, values.length);
+    }
+    // This version uses the implicit local arguments
+    private int[] computeSizePrivate(CodingMethod c) {
+        int[] sizes = { 0, 0 };
+        computeSize(c, values, start, end, sizes);
+        return sizes;
+    }
+    public int computeByteSize(CodingMethod cm, int[] values, int start, int end) {
+        int len = end-start;
+        if (len < 0) {
+            return 0;
+        }
+        if (cm instanceof Coding) {
+            Coding c = (Coding) cm;
+            int size = c.getLength(values, start, end);
+            int size2;
+            assert(size == (size2=countBytesToSizer(cm, values, start, end)))
+                : (cm+" : "+size+" != "+size2);
+            return size;
+        }
+        return countBytesToSizer(cm, values, start, end);
+    }
+    private int countBytesToSizer(CodingMethod cm, int[] values, int start, int end) {
+        try {
+            byteOnlySizer.reset();
+            cm.writeArrayTo(byteOnlySizer, values, start, end);
+            return byteOnlySizer.getSize();
+        } catch (IOException ee) {
+            throw new RuntimeException(ee); // cannot happen
+        }
+    }
+
+    int[] getDeltas(int min, int max) {
+        if ((min|max) != 0)
+            return Coding.makeDeltas(values, start, end, min, max);
+        if (deltas == null) {
+            deltas = Coding.makeDeltas(values, start, end, 0, 0);
+        }
+        return deltas;
+    }
+    Histogram getValueHistogram() {
+        if (vHist == null) {
+            vHist = new Histogram(values, start, end);
+            if (verbose > 3) {
+                vHist.print("vHist", System.out);
+            } else if (verbose > 1) {
+                vHist.print("vHist", null, System.out);
+            }
+        }
+        return vHist;
+    }
+    Histogram getDeltaHistogram() {
+        if (dHist == null) {
+            dHist = new Histogram(getDeltas(0, 0));
+            if (verbose > 3) {
+                dHist.print("dHist", System.out);
+            } else if (verbose > 1) {
+                dHist.print("dHist", null, System.out);
+            }
+        }
+        return dHist;
+    }
+    Histogram getHistogram(boolean isDelta) {
+        return isDelta ? getDeltaHistogram(): getValueHistogram();
+    }
+
+    private void tryPopulationCoding(Coding plainCoding) {
+        // assert(plainCoding.canRepresent(min, max));
+        Histogram hist = getValueHistogram();
+        // Start with "reasonable" default codings.
+        final int approxL = 64;
+        Coding favoredCoding = plainCoding.getValueCoding();
+        Coding tokenCoding = BandStructure.UNSIGNED5.setL(approxL);
+        Coding unfavoredCoding = plainCoding.getValueCoding();
+        // There's going to be a band header.  Estimate conservatively large.
+        final int BAND_HEADER = 4;
+        // Keep a running model of the predicted sizes of the F/T/U sequences.
+        int currentFSize;
+        int currentTSize;
+        int currentUSize;
+        // Start by assuming a degenerate favored-value length of 0,
+        // which looks like a bunch of zero tokens followed by the
+        // original sequence.
+        // The {F} list ends with a repeated F value; find worst case:
+        currentFSize =
+            BAND_HEADER + Math.max(favoredCoding.getLength(min),
+                                   favoredCoding.getLength(max));
+        // The {T} list starts out a bunch of zeros, each of length 1.
+        final int ZERO_LEN = tokenCoding.getLength(0);
+        currentTSize = ZERO_LEN * (end-start);
+        // The {U} list starts out a copy of the plainCoding:
+        currentUSize = (int) Math.ceil(hist.getBitLength(unfavoredCoding) / 8);
+
+        int bestPopSize = (currentFSize + currentTSize + currentUSize);
+        int bestPopFVC  = 0;
+
+        // Record all the values, in decreasing order of favor.
+        int[] allFavoredValues = new int[1+hist.getTotalLength()];
+        //int[] allPopSizes    = new int[1+hist.getTotalLength()];
+
+        // What sizes are "interesting"?
+        int targetLowFVC = -1;
+        int targetHighFVC = -1;
+
+        // For each length, adjust the currentXSize model, and look for a win.
+        int[][] matrix = hist.getMatrix();
+        int mrow = -1;
+        int mcol = 1;
+        int mrowFreq = 0;
+        for (int fvcount = 1; fvcount <= hist.getTotalLength(); fvcount++) {
+            // The {F} list gets an additional member.
+            // Take it from the end of the current matrix row.
+            // (It's the end, so that we get larger favored values first.)
+            if (mcol == 1) {
+                mrow += 1;
+                mrowFreq = matrix[mrow][0];
+                mcol = matrix[mrow].length;
+            }
+            int thisValue = matrix[mrow][--mcol];
+            allFavoredValues[fvcount] = thisValue;
+            int thisVLen = favoredCoding.getLength(thisValue);
+            currentFSize += thisVLen;
+            // The token list replaces occurrences of zero with a new token:
+            int thisVCount = mrowFreq;
+            int thisToken = fvcount;
+            currentTSize += (tokenCoding.getLength(thisToken)
+                             - ZERO_LEN) * thisVCount;
+            // The unfavored list loses occurrences of the newly favored value.
+            // (This is the whole point of the exercise!)
+            currentUSize -= thisVLen * thisVCount;
+            int currentSize = (currentFSize + currentTSize + currentUSize);
+            //allPopSizes[fvcount] = currentSize;
+            if (bestPopSize > currentSize) {
+                if (currentSize <= targetSize) {
+                    targetHighFVC = fvcount;
+                    if (targetLowFVC < 0)
+                        targetLowFVC = fvcount;
+                    if (verbose > 4)
+                        Utils.log.info("better pop-size at fvc="+fvcount+
+                                         " by "+pct(bestPopSize-currentSize,
+                                                    bestPopSize));
+                }
+                bestPopSize = currentSize;
+                bestPopFVC = fvcount;
+            }
+        }
+        if (targetLowFVC < 0) {
+            if (verbose > 1) {
+                // Complete loss.
+                if (verbose > 1)
+                    Utils.log.info("no good pop-size; best was "+
+                                     bestPopSize+" at "+bestPopFVC+
+                                     " worse by "+
+                                     pct(bestPopSize-bestByteSize,
+                                         bestByteSize));
+            }
+            return;
+        }
+        if (verbose > 1)
+            Utils.log.info("initial best pop-size at fvc="+bestPopFVC+
+                             " in ["+targetLowFVC+".."+targetHighFVC+"]"+
+                             " by "+pct(bestByteSize-bestPopSize,
+                                        bestByteSize));
+        int oldZipSize = bestZipSize;
+        // Now close onto a specific coding, testing more rigorously
+        // with the zipSize metric.
+        // Questions to decide:
+        //   1. How many favored values?
+        //   2. What token coding (TC)?
+        //   3. Sort favored values by value within length brackets?
+        //   4. What favored coding?
+        //   5. What unfavored coding?
+        // Steps 1/2/3 are interdependent, and may be iterated.
+        // Steps 4 and 5 may be decided independently afterward.
+        int[] LValuesCoded = PopulationCoding.LValuesCoded;
+        List bestFits = new ArrayList<>();
+        List fullFits = new ArrayList<>();
+        List longFits = new ArrayList<>();
+        final int PACK_TO_MAX_S = 1;
+        if (bestPopFVC <= 255) {
+            bestFits.add(BandStructure.BYTE1);
+        } else {
+            int bestB = Coding.B_MAX;
+            boolean doFullAlso = (effort > POP_EFFORT);
+            if (doFullAlso)
+                fullFits.add(BandStructure.BYTE1.setS(PACK_TO_MAX_S));
+            for (int i = LValuesCoded.length-1; i >= 1; i--) {
+                int L = LValuesCoded[i];
+                Coding c0 = PopulationCoding.fitTokenCoding(targetLowFVC,  L);
+                Coding c1 = PopulationCoding.fitTokenCoding(bestPopFVC,    L);
+                Coding c3 = PopulationCoding.fitTokenCoding(targetHighFVC, L);
+                if (c1 != null) {
+                    if (!bestFits.contains(c1))
+                        bestFits.add(c1);
+                    if (bestB > c1.B())
+                        bestB = c1.B();
+                }
+                if (doFullAlso) {
+                    if (c3 == null)  c3 = c1;
+                    for (int B = c0.B(); B <= c3.B(); B++) {
+                        if (B == c1.B())  continue;
+                        if (B == 1)  continue;
+                        Coding c2 = c3.setB(B).setS(PACK_TO_MAX_S);
+                        if (!fullFits.contains(c2))
+                            fullFits.add(c2);
+                    }
+                }
+            }
+            // interleave all B greater than bestB with best and full fits
+            for (Iterator i = bestFits.iterator(); i.hasNext(); ) {
+                Coding c = i.next();
+                if (c.B() > bestB) {
+                    i.remove();
+                    longFits.add(0, c);
+                }
+            }
+        }
+        List allFits = new ArrayList<>();
+        for (Iterator i = bestFits.iterator(),
+                      j = fullFits.iterator(),
+                      k = longFits.iterator();
+             i.hasNext() || j.hasNext() || k.hasNext(); ) {
+            if (i.hasNext())  allFits.add(i.next());
+            if (j.hasNext())  allFits.add(j.next());
+            if (k.hasNext())  allFits.add(k.next());
+        }
+        bestFits.clear();
+        fullFits.clear();
+        longFits.clear();
+        int maxFits = allFits.size();
+        if (effort == POP_EFFORT)
+            maxFits = 2;
+        else if (maxFits > 4) {
+            maxFits -= 4;
+            maxFits = (maxFits * (effort-POP_EFFORT)
+                       ) / (MAX_EFFORT-POP_EFFORT);
+            maxFits += 4;
+        }
+        if (allFits.size() > maxFits) {
+            if (verbose > 4)
+                Utils.log.info("allFits before clip: "+allFits);
+            allFits.subList(maxFits, allFits.size()).clear();
+        }
+        if (verbose > 3)
+            Utils.log.info("allFits: "+allFits);
+        for (Coding tc : allFits) {
+            boolean packToMax = false;
+            if (tc.S() == PACK_TO_MAX_S) {
+                // Kludge:  setS(PACK_TO_MAX_S) means packToMax here.
+                packToMax = true;
+                tc = tc.setS(0);
+            }
+            int fVlen;
+            if (!packToMax) {
+                fVlen = bestPopFVC;
+                assert(tc.umax() >= fVlen);
+                assert(tc.B() == 1 || tc.setB(tc.B()-1).umax() < fVlen);
+            } else {
+                fVlen = Math.min(tc.umax(), targetHighFVC);
+                if (fVlen < targetLowFVC)
+                    continue;
+                if (fVlen == bestPopFVC)
+                    continue;  // redundant test
+            }
+            PopulationCoding pop = new PopulationCoding();
+            pop.setHistogram(hist);
+            pop.setL(tc.L());
+            pop.setFavoredValues(allFavoredValues, fVlen);
+            assert(pop.tokenCoding == tc);  // predict correctly
+            pop.resortFavoredValues();
+            int[] tcsizes =
+                computePopSizePrivate(pop,
+                                      favoredCoding, unfavoredCoding);
+            noteSizes(pop, tcsizes[BYTE_SIZE], BAND_HEADER+tcsizes[ZIP_SIZE]);
+        }
+        if (verbose > 3) {
+            Utils.log.info("measured best pop, size="+bestByteSize+
+                             "/zs="+bestZipSize+
+                             " better by "+
+                             pct(oldZipSize-bestZipSize, oldZipSize));
+            if (bestZipSize < oldZipSize) {
+                Utils.log.info(">>> POP WINS BY "+
+                                 (oldZipSize - bestZipSize));
+            }
+        }
+    }
+
+    private
+    int[] computePopSizePrivate(PopulationCoding pop,
+                                Coding favoredCoding,
+                                Coding unfavoredCoding) {
+        if (popHelper == null) {
+            popHelper = new CodingChooser(effort, allCodingChoices);
+            if (stress != null)
+                popHelper.addStressSeed(stress.nextInt());
+            popHelper.topLevel = false;
+            popHelper.verbose -= 1;
+            popHelper.disablePopCoding = true;
+            popHelper.disableRunCoding = this.disableRunCoding;
+            if (effort < MID_EFFORT)
+                // No nested run codings.
+                popHelper.disableRunCoding = true;
+        }
+        int fVlen = pop.fVlen;
+        if (verbose > 2) {
+            Utils.log.info("computePopSizePrivate fvlen="+fVlen+
+                             " tc="+pop.tokenCoding);
+            Utils.log.info("{ //BEGIN");
+        }
+
+        // Find good coding choices for the token and unfavored sequences.
+        int[] favoredValues = pop.fValues;
+        int[][] vals = pop.encodeValues(values, start, end);
+        int[] tokens = vals[0];
+        int[] unfavoredValues = vals[1];
+        if (verbose > 2)
+            Utils.log.info("-- refine on fv["+fVlen+"] fc="+favoredCoding);
+        pop.setFavoredCoding(popHelper.choose(favoredValues, 1, 1+fVlen, favoredCoding));
+        if (pop.tokenCoding instanceof Coding &&
+            (stress == null || stress.nextBoolean())) {
+            if (verbose > 2)
+                Utils.log.info("-- refine on tv["+tokens.length+"] tc="+pop.tokenCoding);
+            CodingMethod tc = popHelper.choose(tokens, (Coding) pop.tokenCoding);
+            if (tc != pop.tokenCoding) {
+                if (verbose > 2)
+                    Utils.log.info(">>> refined tc="+tc);
+                pop.setTokenCoding(tc);
+            }
+        }
+        if (unfavoredValues.length == 0)
+            pop.setUnfavoredCoding(null);
+        else {
+            if (verbose > 2)
+                Utils.log.info("-- refine on uv["+unfavoredValues.length+"] uc="+pop.unfavoredCoding);
+            pop.setUnfavoredCoding(popHelper.choose(unfavoredValues, unfavoredCoding));
+        }
+        if (verbose > 3) {
+            Utils.log.info("finish computePopSizePrivate fvlen="+fVlen+
+                             " fc="+pop.favoredCoding+
+                             " tc="+pop.tokenCoding+
+                             " uc="+pop.unfavoredCoding);
+            //pop.hist.print("pop-hist", null, System.out);
+            StringBuilder sb = new StringBuilder();
+            sb.append("fv = {");
+            for (int i = 1; i <= fVlen; i++) {
+                if ((i % 10) == 0)
+                    sb.append('\n');
+                sb.append(" ").append(favoredValues[i]);
+            }
+            sb.append('\n');
+            sb.append("}");
+            Utils.log.info(sb.toString());
+        }
+        if (verbose > 2) {
+            Utils.log.info("} //END");
+        }
+        if (stress != null) {
+            return null;  // do not bother with size computation
+        }
+        int[] sizes;
+        try {
+            resetData();
+            // Write the array of favored values.
+            pop.writeSequencesTo(byteSizer, tokens, unfavoredValues);
+            sizes = new int[] { getByteSize(), getZipSize() };
+        } catch (IOException ee) {
+            throw new RuntimeException(ee); // cannot happen
+        }
+        int[] checkSizes = null;
+        assert((checkSizes = computeSizePrivate(pop)) != null);
+        assert(checkSizes[BYTE_SIZE] == sizes[BYTE_SIZE])
+            : (checkSizes[BYTE_SIZE]+" != "+sizes[BYTE_SIZE]);
+        return sizes;
+    }
+
+    private void tryAdaptiveCoding(Coding plainCoding) {
+        int oldZipSize = bestZipSize;
+        // Scan the value sequence, determining whether an interesting
+        // run occupies too much space.  ("Too much" means, say 5% more
+        // than the average integer size of the band as a whole.)
+        // Try to find a better coding for those segments.
+        int   lstart  = this.start;
+        int   lend    = this.end;
+        int[] lvalues = this.values;
+        int len = lend-lstart;
+        if (plainCoding.isDelta()) {
+            lvalues = getDeltas(0,0); //%%% not quite right!
+            lstart = 0;
+            lend = lvalues.length;
+        }
+        int[] sizes = new int[len+1];
+        int fillp = 0;
+        int totalSize = 0;
+        for (int i = lstart; i < lend; i++) {
+            int val = lvalues[i];
+            sizes[fillp++] = totalSize;
+            int size = plainCoding.getLength(val);
+            assert(size < Integer.MAX_VALUE);
+            //System.out.println("len "+val+" = "+size);
+            totalSize += size;
+        }
+        sizes[fillp++] = totalSize;
+        assert(fillp == sizes.length);
+        double avgSize = (double)totalSize / len;
+        double sizeFuzz;
+        double sizeFuzz2;
+        double sizeFuzz3;
+        if (effort >= MID_EFFORT) {
+            if (effort > MID_EFFORT+1)
+                sizeFuzz = 1.001;
+            else
+                sizeFuzz = 1.003;
+        } else {
+            if (effort > RUN_EFFORT)
+                sizeFuzz = 1.01;
+            else
+                sizeFuzz = 1.03;
+        }
+        // for now:
+        sizeFuzz *= sizeFuzz; // double the thresh
+        sizeFuzz2 = (sizeFuzz*sizeFuzz);
+        sizeFuzz3 = (sizeFuzz*sizeFuzz*sizeFuzz);
+        // Find some mesh scales we like.
+        double[] dmeshes = new double[1 + (effort-RUN_EFFORT)];
+        double logLen = Math.log(len);
+        for (int i = 0; i < dmeshes.length; i++) {
+            dmeshes[i] = Math.exp(logLen*(i+1)/(dmeshes.length+1));
+        }
+        int[] meshes = new int[dmeshes.length];
+        int mfillp = 0;
+        for (int i = 0; i < dmeshes.length; i++) {
+            int m = (int)Math.round(dmeshes[i]);
+            m = AdaptiveCoding.getNextK(m-1);
+            if (m <= 0 || m >= len)  continue;
+            if (mfillp > 0 && m == meshes[mfillp-1])  continue;
+            meshes[mfillp++] = m;
+        }
+        meshes = BandStructure.realloc(meshes, mfillp);
+        // There's going to be a band header.  Estimate conservatively large.
+        final int BAND_HEADER = 4; // op, KB, A, B
+        // Threshold values for a "too big" mesh.
+        int[]    threshes = new int[meshes.length];
+        double[] fuzzes   = new double[meshes.length];
+        for (int i = 0; i < meshes.length; i++) {
+            int mesh = meshes[i];
+            double lfuzz;
+            if (mesh < 10)
+                lfuzz = sizeFuzz3;
+            else if (mesh < 100)
+                lfuzz = sizeFuzz2;
+            else
+                lfuzz = sizeFuzz;
+            fuzzes[i] = lfuzz;
+            threshes[i] = BAND_HEADER + (int)Math.ceil(mesh * avgSize * lfuzz);
+        }
+        if (verbose > 1) {
+            System.out.print("tryAdaptiveCoding ["+len+"]"+
+                             " avgS="+avgSize+" fuzz="+sizeFuzz+
+                             " meshes: {");
+            for (int i = 0; i < meshes.length; i++) {
+                System.out.print(" " + meshes[i] + "(" + threshes[i] + ")");
+            }
+            Utils.log.info(" }");
+        }
+        if (runHelper == null) {
+            runHelper = new CodingChooser(effort, allCodingChoices);
+            if (stress != null)
+                runHelper.addStressSeed(stress.nextInt());
+            runHelper.topLevel = false;
+            runHelper.verbose -= 1;
+            runHelper.disableRunCoding = true;
+            runHelper.disablePopCoding = this.disablePopCoding;
+            if (effort < MID_EFFORT)
+                // No nested pop codings.
+                runHelper.disablePopCoding = true;
+        }
+        for (int i = 0; i < len; i++) {
+            i = AdaptiveCoding.getNextK(i-1);
+            if (i > len)  i = len;
+            for (int j = meshes.length-1; j >= 0; j--) {
+                int mesh   = meshes[j];
+                int thresh = threshes[j];
+                if (i+mesh > len)  continue;
+                int size = sizes[i+mesh] - sizes[i];
+                if (size >= thresh) {
+                    // Found a size bulge.
+                    int bend  = i+mesh;
+                    int bsize = size;
+                    double bigSize = avgSize * fuzzes[j];
+                    while (bend < len && (bend-i) <= len/2) {
+                        int bend0 = bend;
+                        int bsize0 = bsize;
+                        bend += mesh;
+                        bend = i+AdaptiveCoding.getNextK(bend-i-1);
+                        if (bend < 0 || bend > len)
+                            bend = len;
+                        bsize = sizes[bend]-sizes[i];
+                        if (bsize < BAND_HEADER + (bend-i) * bigSize) {
+                            bsize = bsize0;
+                            bend = bend0;
+                            break;
+                        }
+                    }
+                    int nexti = bend;
+                    if (verbose > 2) {
+                        Utils.log.info("bulge at "+i+"["+(bend-i)+"] of "+
+                                         pct(bsize - avgSize*(bend-i),
+                                             avgSize*(bend-i)));
+                        Utils.log.info("{ //BEGIN");
+                    }
+                    CodingMethod begcm, midcm, endcm;
+                    midcm = runHelper.choose(this.values,
+                                             this.start+i,
+                                             this.start+bend,
+                                             plainCoding);
+                    if (midcm == plainCoding) {
+                        // No use working further.
+                        begcm = plainCoding;
+                        endcm = plainCoding;
+                    } else {
+                        begcm = runHelper.choose(this.values,
+                                                 this.start,
+                                                 this.start+i,
+                                                 plainCoding);
+                        endcm = runHelper.choose(this.values,
+                                                 this.start+bend,
+                                                 this.start+len,
+                                                 plainCoding);
+                    }
+                    if (verbose > 2)
+                        Utils.log.info("} //END");
+                    if (begcm == midcm && i > 0 &&
+                        AdaptiveCoding.isCodableLength(bend)) {
+                        i = 0;
+                    }
+                    if (midcm == endcm && bend < len) {
+                        bend = len;
+                    }
+                    if (begcm != plainCoding ||
+                        midcm != plainCoding ||
+                        endcm != plainCoding) {
+                        CodingMethod chain;
+                        int hlen = 0;
+                        if (bend == len) {
+                            chain = midcm;
+                        } else {
+                            chain = new AdaptiveCoding(bend-i, midcm, endcm);
+                            hlen += BAND_HEADER;
+                        }
+                        if (i > 0) {
+                            chain = new AdaptiveCoding(i, begcm, chain);
+                            hlen += BAND_HEADER;
+                        }
+                        int[] chainSize = computeSizePrivate(chain);
+                        noteSizes(chain,
+                                  chainSize[BYTE_SIZE],
+                                  chainSize[ZIP_SIZE]+hlen);
+                    }
+                    i = nexti;
+                    break;
+                }
+            }
+        }
+        if (verbose > 3) {
+            if (bestZipSize < oldZipSize) {
+                Utils.log.info(">>> RUN WINS BY "+
+                                 (oldZipSize - bestZipSize));
+            }
+        }
+    }
+
+    private static
+    String pct(double num, double den) {
+        return (Math.round((num / den)*10000)/100.0)+"%";
+    }
+
+    static
+    class Sizer extends OutputStream {
+        final OutputStream out;  // if non-null, copy output here also
+        Sizer(OutputStream out) {
+            this.out = out;
+        }
+        Sizer() {
+            this(null);
+        }
+        private int count;
+        public void write(int b) throws IOException {
+            count++;
+            if (out != null)  out.write(b);
+        }
+        public void write(byte b[], int off, int len) throws IOException {
+            count += len;
+            if (out != null)  out.write(b, off, len);
+        }
+        public void reset() {
+            count = 0;
+        }
+        public int getSize() { return count; }
+
+        public String toString() {
+            String str = super.toString();
+            // If -ea, print out more informative strings!
+            assert((str = stringForDebug()) != null);
+            return str;
+        }
+        String stringForDebug() {
+            return "";
+        }
+    }
+
+    private Sizer zipSizer  = new Sizer();
+    private Deflater zipDef = new Deflater();
+    private DeflaterOutputStream zipOut = new DeflaterOutputStream(zipSizer, zipDef);
+    private Sizer byteSizer = new Sizer(zipOut);
+    private Sizer byteOnlySizer = new Sizer();
+
+    private void resetData() {
+        flushData();
+        zipDef.reset();
+        if (context != null) {
+            // Prepend given salt to the test output.
+            try {
+                context.writeTo(byteSizer);
+            } catch (IOException ee) {
+                throw new RuntimeException(ee); // cannot happen
+            }
+        }
+        zipSizer.reset();
+        byteSizer.reset();
+    }
+    private void flushData() {
+        try {
+            zipOut.finish();
+        } catch (IOException ee) {
+            throw new RuntimeException(ee); // cannot happen
+        }
+    }
+    private int getByteSize() {
+        return byteSizer.getSize();
+    }
+    private int getZipSize() {
+        flushData();
+        return zipSizer.getSize();
+    }
+
+
+    /// Stress-test helpers.
+
+    void addStressSeed(int x) {
+        if (stress == null)  return;
+        stress.setSeed(x + ((long)stress.nextInt() << 32));
+    }
+
+    // Pick a random pop-coding.
+    private CodingMethod stressPopCoding(CodingMethod coding) {
+        assert(stress != null);  // this method is only for testing
+        // Don't turn it into a pop coding if it's already something special.
+        if (!(coding instanceof Coding))  return coding;
+        Coding valueCoding = ((Coding)coding).getValueCoding();
+        Histogram hist = getValueHistogram();
+        int fVlen = stressLen(hist.getTotalLength());
+        if (fVlen == 0)  return coding;
+        List popvals = new ArrayList<>();
+        if (stress.nextBoolean()) {
+            // Build the population from the value list.
+            Set popset = new HashSet<>();
+            for (int i = start; i < end; i++) {
+                if (popset.add(values[i]))  popvals.add(values[i]);
+            }
+        } else {
+            int[][] matrix = hist.getMatrix();
+            for (int mrow = 0; mrow < matrix.length; mrow++) {
+                int[] row = matrix[mrow];
+                for (int mcol = 1; mcol < row.length; mcol++) {
+                    popvals.add(row[mcol]);
+                }
+            }
+        }
+        int reorder = stress.nextInt();
+        if ((reorder & 7) <= 2) {
+            // Lose the order.
+            Collections.shuffle(popvals, stress);
+        } else {
+            // Keep the order, mostly.
+            if (((reorder >>>= 3) & 7) <= 2)  Collections.sort(popvals);
+            if (((reorder >>>= 3) & 7) <= 2)  Collections.reverse(popvals);
+            if (((reorder >>>= 3) & 7) <= 2)  Collections.rotate(popvals, stressLen(popvals.size()));
+        }
+        if (popvals.size() > fVlen) {
+            // Cut the list down.
+            if (((reorder >>>= 3) & 7) <= 2) {
+                // Cut at end.
+                popvals.subList(fVlen,   popvals.size()).clear();
+            } else {
+                // Cut at start.
+                popvals.subList(0, popvals.size()-fVlen).clear();
+            }
+        }
+        fVlen = popvals.size();
+        int[] fvals = new int[1+fVlen];
+        for (int i = 0; i < fVlen; i++) {
+            fvals[1+i] = (popvals.get(i)).intValue();
+        }
+        PopulationCoding pop = new PopulationCoding();
+        pop.setFavoredValues(fvals, fVlen);
+        int[] lvals = PopulationCoding.LValuesCoded;
+        for (int i = 0; i < lvals.length / 2; i++) {
+            int popl = lvals[stress.nextInt(lvals.length)];
+            if (popl < 0)  continue;
+            if (PopulationCoding.fitTokenCoding(fVlen, popl) != null) {
+                pop.setL(popl);
+                break;
+            }
+        }
+        if (pop.tokenCoding == null) {
+            int lmin = fvals[1], lmax = lmin;
+            for (int i = 2; i <= fVlen; i++) {
+                int val = fvals[i];
+                if (lmin > val)  lmin = val;
+                if (lmax < val)  lmax = val;
+            }
+            pop.tokenCoding = stressCoding(lmin, lmax);
+        }
+
+        computePopSizePrivate(pop, valueCoding, valueCoding);
+        return pop;
+    }
+
+    // Pick a random adaptive coding.
+    private CodingMethod stressAdaptiveCoding(CodingMethod coding) {
+        assert(stress != null);  // this method is only for testing
+        // Don't turn it into a run coding if it's already something special.
+        if (!(coding instanceof Coding))  return coding;
+        Coding plainCoding = (Coding)coding;
+        int len = end-start;
+        if (len < 2)  return coding;
+        // Decide how many spans we'll create.
+        int spanlen = stressLen(len-1)+1;
+        if (spanlen == len)  return coding;
+        try {
+            assert(!disableRunCoding);
+            disableRunCoding = true;  // temporary, while I decide spans
+            int[] allValues = values.clone();
+            CodingMethod result = null;
+            int scan  = this.end;
+            int lstart = this.start;
+            for (int split; scan > lstart; scan = split) {
+                int thisspan;
+                int rand = (scan - lstart < 100)? -1: stress.nextInt();
+                if ((rand & 7) != 0) {
+                    thisspan = (spanlen==1? spanlen: stressLen(spanlen-1)+1);
+                } else {
+                    // Every so often generate a value based on KX/KB format.
+                    int KX = (rand >>>= 3) & AdaptiveCoding.KX_MAX;
+                    int KB = (rand >>>= 3) & AdaptiveCoding.KB_MAX;
+                    for (;;) {
+                        thisspan = AdaptiveCoding.decodeK(KX, KB);
+                        if (thisspan <= scan - lstart)  break;
+                        // Try smaller and smaller codings:
+                        if (KB != AdaptiveCoding.KB_DEFAULT)
+                            KB = AdaptiveCoding.KB_DEFAULT;
+                        else
+                            KX -= 1;
+                    }
+                    //System.out.println("KX="+KX+" KB="+KB+" K="+thisspan);
+                    assert(AdaptiveCoding.isCodableLength(thisspan));
+                }
+                if (thisspan > scan - lstart)  thisspan = scan - lstart;
+                while (!AdaptiveCoding.isCodableLength(thisspan)) {
+                    --thisspan;
+                }
+                split = scan - thisspan;
+                assert(split < scan);
+                assert(split >= lstart);
+                // Choose a coding for the span [split..scan).
+                CodingMethod sc = choose(allValues, split, scan, plainCoding);
+                if (result == null) {
+                    result = sc;  // the caboose
+                } else {
+                    result = new AdaptiveCoding(scan-split, sc, result);
+                }
+            }
+            return result;
+        } finally {
+            disableRunCoding = false; // return to normal value
+        }
+    }
+
+    // Return a random value in [0..len], gently biased toward extremes.
+    private Coding stressCoding(int min, int max) {
+        assert(stress != null);  // this method is only for testing
+        for (int i = 0; i < 100; i++) {
+            Coding c = Coding.of(stress.nextInt(Coding.B_MAX)+1,
+                                 stress.nextInt(Coding.H_MAX)+1,
+                                 stress.nextInt(Coding.S_MAX+1));
+            if (c.B() == 1)  c = c.setH(256);
+            if (c.H() == 256 && c.B() >= 5)  c = c.setB(4);
+            if (stress.nextBoolean()) {
+                Coding dc = c.setD(1);
+                if (dc.canRepresent(min, max))  return dc;
+            }
+            if (c.canRepresent(min, max))  return c;
+        }
+        return BandStructure.UNSIGNED5;
+    }
+
+    // Return a random value in [0..len], gently biased toward extremes.
+    private int stressLen(int len) {
+        assert(stress != null);  // this method is only for testing
+        assert(len >= 0);
+        int rand = stress.nextInt(100);
+        if (rand < 20)
+            return Math.min(len/5, rand);
+        else if (rand < 40)
+            return len;
+        else
+            return stress.nextInt(len);
+    }
+
+    // For debug only.
+/*
+    public static
+    int[] readValuesFrom(InputStream instr) {
+        return readValuesFrom(new InputStreamReader(instr));
+    }
+    public static
+    int[] readValuesFrom(Reader inrdr) {
+        inrdr = new BufferedReader(inrdr);
+        final StreamTokenizer in = new StreamTokenizer(inrdr);
+        final int TT_NOTHING = -99;
+        in.commentChar('#');
+        return readValuesFrom(new Iterator() {
+            int token = TT_NOTHING;
+            private int getToken() {
+                if (token == TT_NOTHING) {
+                    try {
+                        token = in.nextToken();
+                        assert(token != TT_NOTHING);
+                    } catch (IOException ee) {
+                        throw new RuntimeException(ee);
+                    }
+                }
+                return token;
+            }
+            public boolean hasNext() {
+                return getToken() != StreamTokenizer.TT_EOF;
+            }
+            public Object next() {
+                int ntok = getToken();
+                token = TT_NOTHING;
+                switch (ntok) {
+                case StreamTokenizer.TT_EOF:
+                    throw new NoSuchElementException();
+                case StreamTokenizer.TT_NUMBER:
+                    return Integer.valueOf((int) in.nval);
+                default:
+                    assert(false);
+                    return null;
+                }
+            }
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        });
+    }
+    public static
+    int[] readValuesFrom(Iterator iter) {
+        return readValuesFrom(iter, 0);
+    }
+    public static
+    int[] readValuesFrom(Iterator iter, int initSize) {
+        int[] na = new int[Math.max(10, initSize)];
+        int np = 0;
+        while (iter.hasNext()) {
+            Integer val = (Integer) iter.next();
+            if (np == na.length) {
+                na = BandStructure.realloc(na);
+            }
+            na[np++] = val.intValue();
+        }
+        if (np != na.length) {
+            na = BandStructure.realloc(na, np);
+        }
+        return na;
+    }
+
+    public static
+    void main(String[] av) throws IOException {
+        int effort = MID_EFFORT;
+        int ap = 0;
+        if (ap < av.length && av[ap].equals("-e")) {
+            ap++;
+            effort = Integer.parseInt(av[ap++]);
+        }
+        int verbose = 1;
+        if (ap < av.length && av[ap].equals("-v")) {
+            ap++;
+            verbose = Integer.parseInt(av[ap++]);
+        }
+        Coding[] bcs = BandStructure.getBasicCodings();
+        CodingChooser cc = new CodingChooser(effort, bcs);
+        if (ap < av.length && av[ap].equals("-p")) {
+            ap++;
+            cc.optUsePopulationCoding = false;
+        }
+        if (ap < av.length && av[ap].equals("-a")) {
+            ap++;
+            cc.optUseAdaptiveCoding = false;
+        }
+        cc.verbose = verbose;
+        int[] values = readValuesFrom(System.in);
+        int[] sizes = {0,0};
+        CodingMethod cm = cc.choose(values, BandStructure.UNSIGNED5, sizes);
+        System.out.println("size: "+sizes[BYTE_SIZE]+"/zs="+sizes[ZIP_SIZE]);
+        System.out.println(cm);
+    }
+//*/
+
+}
diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/CodingMethod.java b/src/main/java/net/minecraftforge/gradle/util/pack200/CodingMethod.java
new file mode 100644
index 000000000..4d952792f
--- /dev/null
+++ b/src/main/java/net/minecraftforge/gradle/util/pack200/CodingMethod.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package net.minecraftforge.gradle.util.pack200;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Interface for encoding and decoding int arrays using bytewise codes.
+ * @author John Rose
+ */
+interface CodingMethod {
+    // Read and write an array of ints from/to a stream.
+    public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException;
+    public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException;
+
+    // how to express me in a band header?
+    public byte[] getMetaCoding(Coding dflt);
+}
diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/ConstantPool.java b/src/main/java/net/minecraftforge/gradle/util/pack200/ConstantPool.java
new file mode 100644
index 000000000..cf989ca6f
--- /dev/null
+++ b/src/main/java/net/minecraftforge/gradle/util/pack200/ConstantPool.java
@@ -0,0 +1,1658 @@
+/*
+ * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package net.minecraftforge.gradle.util.pack200;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import static net.minecraftforge.gradle.util.pack200.Constants.*;
+
+/**
+ * Representation of constant pool entries and indexes.
+ * @author John Rose
+ */
+abstract
+class ConstantPool {
+    private ConstantPool() {}  // do not instantiate
+
+    static int verbose() {
+        return Utils.currentPropMap().getInteger(Utils.DEBUG_VERBOSE);
+    }
+
+    /** Factory for Utf8 string constants.
+     *  Used for well-known strings like "SourceFile", "", etc.
+     *  Also used to back up more complex constant pool entries, like Class.
+     */
+    public static synchronized Utf8Entry getUtf8Entry(String value) {
+        Map utf8Entries  = Utils.getTLGlobals().getUtf8Entries();
+        Utf8Entry e = utf8Entries.get(value);
+        if (e == null) {
+            e = new Utf8Entry(value);
+            utf8Entries.put(e.stringValue(), e);
+        }
+        return e;
+    }
+    /** Factory for Class constants. */
+    public static ClassEntry getClassEntry(String name) {
+        Map classEntries = Utils.getTLGlobals().getClassEntries();
+        ClassEntry e = classEntries.get(name);
+        if (e == null) {
+            e = new ClassEntry(getUtf8Entry(name));
+            assert(name.equals(e.stringValue()));
+            classEntries.put(e.stringValue(), e);
+        }
+        return e;
+    }
+    /** Factory for literal constants (String, Integer, etc.). */
+    public static LiteralEntry getLiteralEntry(Comparable value) {
+        Map literalEntries = Utils.getTLGlobals().getLiteralEntries();
+        LiteralEntry e = literalEntries.get(value);
+        if (e == null) {
+            if (value instanceof String)
+                e = new StringEntry(getUtf8Entry((String)value));
+            else
+                e = new NumberEntry((Number)value);
+            literalEntries.put(value, e);
+        }
+        return e;
+    }
+    /** Factory for literal constants (String, Integer, etc.). */
+    public static StringEntry getStringEntry(String value) {
+        return (StringEntry) getLiteralEntry(value);
+    }
+
+    /** Factory for signature (type) constants. */
+    public static SignatureEntry getSignatureEntry(String type) {
+        Map signatureEntries = Utils.getTLGlobals().getSignatureEntries();
+        SignatureEntry e = signatureEntries.get(type);
+        if (e == null) {
+            e = new SignatureEntry(type);
+            assert(e.stringValue().equals(type));
+            signatureEntries.put(type, e);
+        }
+        return e;
+    }
+    // Convenience overloading.
+    public static SignatureEntry getSignatureEntry(Utf8Entry formRef, ClassEntry[] classRefs) {
+        return getSignatureEntry(SignatureEntry.stringValueOf(formRef, classRefs));
+    }
+
+    /** Factory for descriptor (name-and-type) constants. */
+    public static DescriptorEntry getDescriptorEntry(Utf8Entry nameRef, SignatureEntry typeRef) {
+        Map descriptorEntries = Utils.getTLGlobals().getDescriptorEntries();
+        String key = DescriptorEntry.stringValueOf(nameRef, typeRef);
+        DescriptorEntry e = descriptorEntries.get(key);
+        if (e == null) {
+            e = new DescriptorEntry(nameRef, typeRef);
+            assert(e.stringValue().equals(key))
+                : (e.stringValue()+" != "+(key));
+            descriptorEntries.put(key, e);
+        }
+        return e;
+    }
+    // Convenience overloading.
+    public static DescriptorEntry getDescriptorEntry(Utf8Entry nameRef, Utf8Entry typeRef) {
+        return getDescriptorEntry(nameRef, getSignatureEntry(typeRef.stringValue()));
+    }
+
+    /** Factory for member reference constants. */
+    public static MemberEntry getMemberEntry(byte tag, ClassEntry classRef, DescriptorEntry descRef) {
+        Map memberEntries = Utils.getTLGlobals().getMemberEntries();
+        String key = MemberEntry.stringValueOf(tag, classRef, descRef);
+        MemberEntry e = memberEntries.get(key);
+        if (e == null) {
+            e = new MemberEntry(tag, classRef, descRef);
+            assert(e.stringValue().equals(key))
+                : (e.stringValue()+" != "+(key));
+            memberEntries.put(key, e);
+        }
+        return e;
+    }
+
+    /** Factory for MethodHandle constants. */
+    public static MethodHandleEntry getMethodHandleEntry(byte refKind, MemberEntry memRef) {
+        Map methodHandleEntries = Utils.getTLGlobals().getMethodHandleEntries();
+        String key = MethodHandleEntry.stringValueOf(refKind, memRef);
+        MethodHandleEntry e = methodHandleEntries.get(key);
+        if (e == null) {
+            e = new MethodHandleEntry(refKind, memRef);
+            assert(e.stringValue().equals(key));
+            methodHandleEntries.put(key, e);
+        }
+        return e;
+    }
+
+    /** Factory for MethodType constants. */
+    public static MethodTypeEntry getMethodTypeEntry(SignatureEntry sigRef) {
+        Map methodTypeEntries = Utils.getTLGlobals().getMethodTypeEntries();
+        String key = sigRef.stringValue();
+        MethodTypeEntry e = methodTypeEntries.get(key);
+        if (e == null) {
+            e = new MethodTypeEntry(sigRef);
+            assert(e.stringValue().equals(key));
+            methodTypeEntries.put(key, e);
+        }
+        return e;
+    }
+    public static MethodTypeEntry getMethodTypeEntry(Utf8Entry typeRef) {
+        return getMethodTypeEntry(getSignatureEntry(typeRef.stringValue()));
+    }
+
+    /** Factory for InvokeDynamic constants. */
+    public static InvokeDynamicEntry getInvokeDynamicEntry(BootstrapMethodEntry bssRef, DescriptorEntry descRef) {
+        Map invokeDynamicEntries = Utils.getTLGlobals().getInvokeDynamicEntries();
+        String key = InvokeDynamicEntry.stringValueOf(bssRef, descRef);
+        InvokeDynamicEntry e = invokeDynamicEntries.get(key);
+        if (e == null) {
+            e = new InvokeDynamicEntry(bssRef, descRef);
+            assert(e.stringValue().equals(key));
+            invokeDynamicEntries.put(key, e);
+        }
+        return e;
+    }
+
+    /** Factory for BootstrapMethod pseudo-constants. */
+    public static BootstrapMethodEntry getBootstrapMethodEntry(MethodHandleEntry bsmRef, Entry[] argRefs) {
+        Map bootstrapMethodEntries = Utils.getTLGlobals().getBootstrapMethodEntries();
+        String key = BootstrapMethodEntry.stringValueOf(bsmRef, argRefs);
+        BootstrapMethodEntry e = bootstrapMethodEntries.get(key);
+        if (e == null) {
+            e = new BootstrapMethodEntry(bsmRef, argRefs);
+            assert(e.stringValue().equals(key));
+            bootstrapMethodEntries.put(key, e);
+        }
+        return e;
+    }
+
+
+    /** Entries in the constant pool. */
+    public abstract static
+    class Entry implements Comparable {
+        protected final byte tag;       // a CONSTANT_foo code
+        protected int valueHash;        // cached hashCode
+
+        protected Entry(byte tag) {
+            this.tag = tag;
+        }
+
+        public final byte getTag() {
+            return tag;
+        }
+
+        public final boolean tagEquals(int tag) {
+            return getTag() == tag;
+        }
+
+        public Entry getRef(int i) {
+            return null;
+        }
+
+        public boolean eq(Entry that) {  // same reference
+            assert(that != null);
+            return this == that || this.equals(that);
+        }
+
+        // Equality of Entries is value-based.
+        public abstract boolean equals(Object o);
+        public final int hashCode() {
+            if (valueHash == 0) {
+                valueHash = computeValueHash();
+                if (valueHash == 0)  valueHash = 1;
+            }
+            return valueHash;
+        }
+        protected abstract int computeValueHash();
+
+        public abstract int compareTo(Object o);
+
+        protected int superCompareTo(Object o) {
+            Entry that = (Entry) o;
+
+            if (this.tag != that.tag) {
+                return TAG_ORDER[this.tag] - TAG_ORDER[that.tag];
+            }
+
+            return 0;  // subclasses must refine this
+        }
+
+        public final boolean isDoubleWord() {
+            return tag == CONSTANT_Double || tag == CONSTANT_Long;
+        }
+
+        public final boolean tagMatches(int matchTag) {
+            if (tag == matchTag)
+                return true;
+            byte[] allowedTags;
+            switch (matchTag) {
+                case CONSTANT_All:
+                    return true;
+                case CONSTANT_Signature:
+                    return tag == CONSTANT_Utf8;  // format check also?
+                case CONSTANT_LoadableValue:
+                    allowedTags = LOADABLE_VALUE_TAGS;
+                    break;
+                case CONSTANT_AnyMember:
+                    allowedTags = ANY_MEMBER_TAGS;
+                    break;
+                case CONSTANT_FieldSpecific:
+                    allowedTags = FIELD_SPECIFIC_TAGS;
+                    break;
+                default:
+                    return false;
+            }
+            for (byte b : allowedTags) {
+                if (b == tag)
+                    return true;
+            }
+            return false;
+        }
+
+        public String toString() {
+            String valuePrint = stringValue();
+            if (verbose() > 4) {
+                if (valueHash != 0)
+                    valuePrint += " hash="+valueHash;
+                valuePrint += " id="+System.identityHashCode(this);
+            }
+            return tagName(tag)+"="+valuePrint;
+        }
+        public abstract String stringValue();
+    }
+
+    public static
+    class Utf8Entry extends Entry {
+        final String value;
+
+        Utf8Entry(String value) {
+            super(CONSTANT_Utf8);
+            this.value = value.intern();
+            hashCode();  // force computation of valueHash
+        }
+        protected int computeValueHash() {
+            return value.hashCode();
+        }
+        public boolean equals(Object o) {
+            // Use reference equality of interned strings:
+            return (o != null && o.getClass() == Utf8Entry.class
+                    && ((Utf8Entry) o).value.equals(value));
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                x = value.compareTo(((Utf8Entry)o).value);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return value;
+        }
+    }
+
+    static boolean isMemberTag(byte tag) {
+        switch (tag) {
+        case CONSTANT_Fieldref:
+        case CONSTANT_Methodref:
+        case CONSTANT_InterfaceMethodref:
+            return true;
+        }
+        return false;
+    }
+
+    static byte numberTagOf(Number value) {
+        if (value instanceof Integer)  return CONSTANT_Integer;
+        if (value instanceof Float)    return CONSTANT_Float;
+        if (value instanceof Long)     return CONSTANT_Long;
+        if (value instanceof Double)   return CONSTANT_Double;
+        throw new RuntimeException("bad literal value "+value);
+    }
+
+    static boolean isRefKind(byte refKind) {
+        return (REF_getField <= refKind && refKind <= REF_invokeInterface);
+    }
+
+    public abstract static
+    class LiteralEntry extends Entry {
+        protected LiteralEntry(byte tag) {
+            super(tag);
+        }
+
+        public abstract Comparable literalValue();
+    }
+
+    public static
+    class NumberEntry extends LiteralEntry {
+        final Number value;
+        NumberEntry(Number value) {
+            super(numberTagOf(value));
+            this.value = value;
+            hashCode();  // force computation of valueHash
+        }
+        protected int computeValueHash() {
+            return value.hashCode();
+        }
+
+        public boolean equals(Object o) {
+            return (o != null && o.getClass() == NumberEntry.class
+                    && ((NumberEntry) o).value.equals(value));
+
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                @SuppressWarnings("unchecked")
+                Comparable compValue = (Comparable)value;
+                x = compValue.compareTo(((NumberEntry)o).value);
+            }
+            return x;
+        }
+        public Number numberValue() {
+            return value;
+        }
+        public Comparable literalValue() {
+            return (Comparable) value;
+        }
+        public String stringValue() {
+            return value.toString();
+        }
+    }
+
+    public static
+    class StringEntry extends LiteralEntry {
+        final Utf8Entry ref;
+        public Entry getRef(int i) { return i == 0 ? ref : null; }
+
+        StringEntry(Entry ref) {
+            super(CONSTANT_String);
+            this.ref = (Utf8Entry) ref;
+            hashCode();  // force computation of valueHash
+        }
+        protected int computeValueHash() {
+            return ref.hashCode() + tag;
+        }
+        public boolean equals(Object o) {
+            return (o != null && o.getClass() == StringEntry.class &&
+                    ((StringEntry)o).ref.eq(ref));
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                x = ref.compareTo(((StringEntry)o).ref);
+            }
+            return x;
+        }
+        public Comparable literalValue() {
+            return ref.stringValue();
+        }
+        public String stringValue() {
+            return ref.stringValue();
+        }
+    }
+
+    public static
+    class ClassEntry extends Entry {
+        final Utf8Entry ref;
+        public Entry getRef(int i) { return i == 0 ? ref : null; }
+
+        protected int computeValueHash() {
+            return ref.hashCode() + tag;
+        }
+        ClassEntry(Entry ref) {
+            super(CONSTANT_Class);
+            this.ref = (Utf8Entry) ref;
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            return (o != null && o.getClass() == ClassEntry.class
+                    && ((ClassEntry) o).ref.eq(ref));
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                x = ref.compareTo(((ClassEntry)o).ref);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return ref.stringValue();
+        }
+    }
+
+    public static
+    class DescriptorEntry extends Entry {
+        final Utf8Entry      nameRef;
+        final SignatureEntry typeRef;
+        public Entry getRef(int i) {
+            if (i == 0)  return nameRef;
+            if (i == 1)  return typeRef;
+            return null;
+        }
+        DescriptorEntry(Entry nameRef, Entry typeRef) {
+            super(CONSTANT_NameandType);
+            if (typeRef instanceof Utf8Entry) {
+                typeRef = getSignatureEntry(typeRef.stringValue());
+            }
+            this.nameRef = (Utf8Entry) nameRef;
+            this.typeRef = (SignatureEntry) typeRef;
+            hashCode();  // force computation of valueHash
+        }
+        protected int computeValueHash() {
+            int hc2 = typeRef.hashCode();
+            return (nameRef.hashCode() + (hc2 << 8)) ^ hc2;
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != DescriptorEntry.class) {
+                return false;
+            }
+            DescriptorEntry that = (DescriptorEntry)o;
+            return this.nameRef.eq(that.nameRef)
+                && this.typeRef.eq(that.typeRef);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                DescriptorEntry that = (DescriptorEntry)o;
+                // Primary key is typeRef, not nameRef.
+                x = this.typeRef.compareTo(that.typeRef);
+                if (x == 0)
+                    x = this.nameRef.compareTo(that.nameRef);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return stringValueOf(nameRef, typeRef);
+        }
+        static
+        String stringValueOf(Entry nameRef, Entry typeRef) {
+            return qualifiedStringValue(typeRef, nameRef);
+        }
+
+        public String prettyString() {
+            return nameRef.stringValue()+typeRef.prettyString();
+        }
+
+        public boolean isMethod() {
+            return typeRef.isMethod();
+        }
+
+        public byte getLiteralTag() {
+            return typeRef.getLiteralTag();
+        }
+    }
+
+    static String qualifiedStringValue(Entry e1, Entry e2) {
+        return qualifiedStringValue(e1.stringValue(), e2.stringValue());
+    }
+    static String qualifiedStringValue(String s1, String s234) {
+        // Qualification by dot must decompose uniquely.  Second string might already be qualified.
+        assert(s1.indexOf('.') < 0);
+        return s1+"."+s234;
+    }
+
+    public static
+    class MemberEntry extends Entry {
+        final ClassEntry classRef;
+        final DescriptorEntry descRef;
+        public Entry getRef(int i) {
+            if (i == 0)  return classRef;
+            if (i == 1)  return descRef;
+            return null;
+        }
+        protected int computeValueHash() {
+            int hc2 = descRef.hashCode();
+            return (classRef.hashCode() + (hc2 << 8)) ^ hc2;
+        }
+
+        MemberEntry(byte tag, ClassEntry classRef, DescriptorEntry descRef) {
+            super(tag);
+            assert(isMemberTag(tag));
+            this.classRef = classRef;
+            this.descRef  = descRef;
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != MemberEntry.class) {
+                return false;
+            }
+            MemberEntry that = (MemberEntry)o;
+            return this.classRef.eq(that.classRef)
+                && this.descRef.eq(that.descRef);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                MemberEntry that = (MemberEntry)o;
+                if (Utils.SORT_MEMBERS_DESCR_MAJOR)
+                    // descRef is transmitted as UDELTA5; sort it first?
+                    x = this.descRef.compareTo(that.descRef);
+                // Primary key is classRef.
+                if (x == 0)
+                    x = this.classRef.compareTo(that.classRef);
+                if (x == 0)
+                    x = this.descRef.compareTo(that.descRef);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return stringValueOf(tag, classRef, descRef);
+        }
+        static
+        String stringValueOf(byte tag, ClassEntry classRef, DescriptorEntry descRef) {
+            assert(isMemberTag(tag));
+            String pfx;
+            switch (tag) {
+            case CONSTANT_Fieldref:            pfx = "Field:";   break;
+            case CONSTANT_Methodref:           pfx = "Method:";  break;
+            case CONSTANT_InterfaceMethodref:  pfx = "IMethod:"; break;
+            default:                           pfx = tag+"???";  break;
+            }
+            return pfx+qualifiedStringValue(classRef, descRef);
+        }
+
+        public boolean isMethod() {
+            return descRef.isMethod();
+        }
+    }
+
+    public static
+    class SignatureEntry extends Entry {
+        final Utf8Entry    formRef;
+        final ClassEntry[] classRefs;
+        String             value;
+        Utf8Entry          asUtf8Entry;
+        public Entry getRef(int i) {
+            if (i == 0)  return formRef;
+            return i-1 < classRefs.length ? classRefs[i-1] : null;
+        }
+        SignatureEntry(String value) {
+            super(CONSTANT_Signature);
+            value = value.intern();  // always do this
+            this.value = value;
+            String[] parts = structureSignature(value);
+            formRef = getUtf8Entry(parts[0]);
+            classRefs = new ClassEntry[parts.length-1];
+            for (int i = 1; i < parts.length; i++) {
+                classRefs[i - 1] = getClassEntry(parts[i]);
+            }
+            hashCode();  // force computation of valueHash
+        }
+        protected int computeValueHash() {
+            stringValue();  // force computation of value
+            return value.hashCode() + tag;
+        }
+
+        public Utf8Entry asUtf8Entry() {
+            if (asUtf8Entry == null) {
+                asUtf8Entry = getUtf8Entry(stringValue());
+            }
+            return asUtf8Entry;
+        }
+
+        public boolean equals(Object o) {
+            return (o != null && o.getClass() == SignatureEntry.class &&
+                    ((SignatureEntry)o).value.equals(value));
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                SignatureEntry that = (SignatureEntry)o;
+                x = compareSignatures(this.value, that.value);
+            }
+            return x;
+        }
+        public String stringValue() {
+            if (value == null) {
+                value = stringValueOf(formRef, classRefs);
+            }
+            return value;
+        }
+        static
+        String stringValueOf(Utf8Entry formRef, ClassEntry[] classRefs) {
+            String[] parts = new String[1+classRefs.length];
+            parts[0] = formRef.stringValue();
+            for (int i = 1; i < parts.length; i++) {
+                parts[i] = classRefs[i - 1].stringValue();
+            }
+            return flattenSignature(parts).intern();
+        }
+
+        public int computeSize(boolean countDoublesTwice) {
+            String form = formRef.stringValue();
+            int min = 0;
+            int max = 1;
+            if (isMethod()) {
+                min = 1;
+                max = form.indexOf(')');
+            }
+            int size = 0;
+            for (int i = min; i < max; i++) {
+                switch (form.charAt(i)) {
+                    case 'D':
+                    case 'J':
+                        if (countDoublesTwice) {
+                            size++;
+                        }
+                        break;
+                    case '[':
+                        // Skip rest of array info.
+                        while (form.charAt(i) == '[') {
+                            ++i;
+                        }
+                        break;
+                    case ';':
+                        continue;
+                    default:
+                        assert (0 <= JAVA_SIGNATURE_CHARS.indexOf(form.charAt(i)));
+                        break;
+                }
+                size++;
+            }
+            return size;
+        }
+        public boolean isMethod() {
+            return formRef.stringValue().charAt(0) == '(';
+        }
+        public byte getLiteralTag() {
+            switch (formRef.stringValue().charAt(0)) {
+            case 'I': return CONSTANT_Integer;
+            case 'J': return CONSTANT_Long;
+            case 'F': return CONSTANT_Float;
+            case 'D': return CONSTANT_Double;
+            case 'B': case 'S': case 'C': case 'Z':
+                return CONSTANT_Integer;
+            case 'L':
+                /*
+                switch (classRefs[0].stringValue()) {
+                case "java/lang/String":
+                    return CONSTANT_String;
+                case "java/lang/invoke/MethodHandle":
+                    return CONSTANT_MethodHandle;
+                case "java/lang/invoke/MethodType":
+                    return CONSTANT_MethodType;
+                default:  // java/lang/Object, etc.
+                    return CONSTANT_LoadableValue;
+                }
+                */
+                return CONSTANT_String;  // JDK 7 ConstantValue limited to String
+            }
+            assert(false);
+            return CONSTANT_None;
+        }
+        public String prettyString() {
+            String s;
+            if (isMethod()) {
+                s = formRef.stringValue();
+                s = s.substring(0, 1+s.indexOf(')'));
+            } else {
+                s = "/" + formRef.stringValue();
+            }
+            int i;
+            while ((i = s.indexOf(';')) >= 0) {
+                s = s.substring(0, i) + s.substring(i + 1);
+            }
+            return s;
+        }
+    }
+
+    static int compareSignatures(String s1, String s2) {
+        return compareSignatures(s1, s2, null, null);
+    }
+    static int compareSignatures(String s1, String s2, String[] p1, String[] p2) {
+        final int S1_COMES_FIRST = -1;
+        final int S2_COMES_FIRST = +1;
+        char c1 = s1.charAt(0);
+        char c2 = s2.charAt(0);
+        // fields before methods (because there are fewer of them)
+        if (c1 != '(' && c2 == '(')  return S1_COMES_FIRST;
+        if (c2 != '(' && c1 == '(')  return S2_COMES_FIRST;
+        if (p1 == null)  p1 = structureSignature(s1);
+        if (p2 == null)  p2 = structureSignature(s2);
+        /*
+         // non-classes before classes (because there are fewer of them)
+         if (p1.length == 1 && p2.length > 1)  return S1_COMES_FIRST;
+         if (p2.length == 1 && p1.length > 1)  return S2_COMES_FIRST;
+         // all else being equal, use the same comparison as for Utf8 strings
+         return s1.compareTo(s2);
+         */
+        if (p1.length != p2.length)  return p1.length - p2.length;
+        int length = p1.length;
+        for (int i = length; --i >= 0; ) {
+            int res = p1[i].compareTo(p2[i]);
+            if (res != 0)  return res;
+        }
+        assert(s1.equals(s2));
+        return 0;
+    }
+
+    static int countClassParts(Utf8Entry formRef) {
+        int num = 0;
+        String s = formRef.stringValue();
+        for (int i = 0; i < s.length(); i++) {
+            if (s.charAt(i) == 'L')  ++num;
+        }
+        return num;
+    }
+
+    static String flattenSignature(String[] parts) {
+        String form = parts[0];
+        if (parts.length == 1)  return form;
+        int len = form.length();
+        for (int i = 1; i < parts.length; i++) {
+            len += parts[i].length();
+        }
+        char[] sig = new char[len];
+        int j = 0;
+        int k = 1;
+        for (int i = 0; i < form.length(); i++) {
+            char ch = form.charAt(i);
+            sig[j++] = ch;
+            if (ch == 'L') {
+                String cls = parts[k++];
+                cls.getChars(0, cls.length(), sig, j);
+                j += cls.length();
+                //sig[j++] = ';';
+            }
+        }
+        assert(j == len);
+        assert(k == parts.length);
+        return new String(sig);
+    }
+
+    private static int skipTo(char semi, String sig, int i) {
+        i = sig.indexOf(semi, i);
+        return (i >= 0) ? i : sig.length();
+    }
+
+    static String[] structureSignature(String sig) {
+        int firstl = sig.indexOf('L');
+        if (firstl < 0) {
+            String[] parts = { sig };
+            return parts;
+        }
+        // Segment the string like sig.split("L\\([^;<]*\\)").
+        // N.B.: Previous version of this code did a more complex match,
+        // to next ch < ' ' or ch in [';'..'@'].  The only important
+        // characters are ';' and '<', since they are part of the
+        // signature syntax.
+        // Examples:
+        //   "(Ljava/lang/Object;IJLLoo;)V" => {"(L;IJL;)V", "java/lang/Object", "Loo"}
+        //   "Ljava/util/List;" => {"L;", "java/util/List", "java/lang/String"}
+        char[] form = null;
+        String[] parts = null;
+        for (int pass = 0; pass <= 1; pass++) {
+            // pass 0 is a sizing pass, pass 1 packs the arrays
+            int formPtr = 0;
+            int partPtr = 1;
+            int nextsemi = 0, nextangl = 0;  // next ';' or '<', or zero, or sigLen
+            int lastj = 0;
+            for (int i = firstl + 1, j; i > 0; i = sig.indexOf('L', j) + 1) {
+                // sig[i-1] is 'L', while sig[j] will be the first ';' or '<' after it
+                // each part is in sig[i .. j-1]
+                if (nextsemi < i)  nextsemi = skipTo(';', sig, i);
+                if (nextangl < i)  nextangl = skipTo('<', sig, i);
+                j = (nextsemi < nextangl ? nextsemi : nextangl);
+                if (pass != 0) {
+                    sig.getChars(lastj, i, form, formPtr);
+                    parts[partPtr] = sig.substring(i, j);
+                }
+                formPtr += (i - lastj);
+                partPtr += 1;
+                lastj = j;
+            }
+            if (pass != 0) {
+                sig.getChars(lastj, sig.length(), form, formPtr);
+                break;
+            }
+            formPtr += (sig.length() - lastj);
+            form = new char[formPtr];
+            parts = new String[partPtr];
+        }
+        parts[0] = new String(form);
+        //assert(flattenSignature(parts).equals(sig));
+        return parts;
+    }
+
+    /** @since 1.7, JSR 292 */
+    public static
+    class MethodHandleEntry extends Entry {
+        final int refKind;
+        final MemberEntry memRef;
+        public Entry getRef(int i) { return i == 0 ? memRef : null; }
+
+        protected int computeValueHash() {
+            int hc2 = refKind;
+            return (memRef.hashCode() + (hc2 << 8)) ^ hc2;
+        }
+
+        MethodHandleEntry(byte refKind, MemberEntry memRef) {
+            super(CONSTANT_MethodHandle);
+            assert(isRefKind(refKind));
+            this.refKind = refKind;
+            this.memRef  = memRef;
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != MethodHandleEntry.class) {
+                return false;
+            }
+            MethodHandleEntry that = (MethodHandleEntry)o;
+            return this.refKind == that.refKind
+                && this.memRef.eq(that.memRef);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                MethodHandleEntry that = (MethodHandleEntry)o;
+                if (Utils.SORT_HANDLES_KIND_MAJOR)
+                    // Primary key could be refKind.
+                    x = this.refKind - that.refKind;
+                // Primary key is memRef, which is transmitted as UDELTA5.
+                if (x == 0)
+                    x = this.memRef.compareTo(that.memRef);
+                if (x == 0)
+                    x = this.refKind - that.refKind;
+            }
+            return x;
+        }
+        public static String stringValueOf(int refKind, MemberEntry memRef) {
+            return refKindName(refKind)+":"+memRef.stringValue();
+        }
+        public String stringValue() {
+            return stringValueOf(refKind, memRef);
+        }
+    }
+
+    /** @since 1.7, JSR 292 */
+    public static
+    class MethodTypeEntry extends Entry {
+        final SignatureEntry typeRef;
+        public Entry getRef(int i) { return i == 0 ? typeRef : null; }
+
+        protected int computeValueHash() {
+            return typeRef.hashCode() + tag;
+        }
+
+        MethodTypeEntry(SignatureEntry typeRef) {
+            super(CONSTANT_MethodType);
+            this.typeRef  = typeRef;
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != MethodTypeEntry.class) {
+                return false;
+            }
+            MethodTypeEntry that = (MethodTypeEntry)o;
+            return this.typeRef.eq(that.typeRef);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                MethodTypeEntry that = (MethodTypeEntry)o;
+                x = this.typeRef.compareTo(that.typeRef);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return typeRef.stringValue();
+        }
+    }
+
+    /** @since 1.7, JSR 292 */
+    public static
+    class InvokeDynamicEntry extends Entry {
+        final BootstrapMethodEntry bssRef;
+        final DescriptorEntry descRef;
+        public Entry getRef(int i) {
+            if (i == 0)  return bssRef;
+            if (i == 1)  return descRef;
+            return null;
+        }
+        protected int computeValueHash() {
+            int hc2 = descRef.hashCode();
+            return (bssRef.hashCode() + (hc2 << 8)) ^ hc2;
+        }
+
+        InvokeDynamicEntry(BootstrapMethodEntry bssRef, DescriptorEntry descRef) {
+            super(CONSTANT_InvokeDynamic);
+            this.bssRef  = bssRef;
+            this.descRef = descRef;
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != InvokeDynamicEntry.class) {
+                return false;
+            }
+            InvokeDynamicEntry that = (InvokeDynamicEntry)o;
+            return this.bssRef.eq(that.bssRef)
+                && this.descRef.eq(that.descRef);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                InvokeDynamicEntry that = (InvokeDynamicEntry)o;
+                if (Utils.SORT_INDY_BSS_MAJOR)
+                    // Primary key could be bsmRef.
+                    x = this.bssRef.compareTo(that.bssRef);
+                // Primary key is descriptor, which is transmitted as UDELTA5.
+                if (x == 0)
+                    x = this.descRef.compareTo(that.descRef);
+                if (x == 0)
+                    x = this.bssRef.compareTo(that.bssRef);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return stringValueOf(bssRef, descRef);
+        }
+        static
+        String stringValueOf(BootstrapMethodEntry bssRef, DescriptorEntry descRef) {
+            return "Indy:"+bssRef.stringValue()+"."+descRef.stringValue();
+        }
+    }
+
+    /** @since 1.7, JSR 292 */
+    public static
+    class BootstrapMethodEntry extends Entry {
+        final MethodHandleEntry bsmRef;
+        final Entry[] argRefs;
+        public Entry getRef(int i) {
+            if (i == 0)  return bsmRef;
+            if (i-1 < argRefs.length)  return argRefs[i-1];
+            return null;
+        }
+        protected int computeValueHash() {
+            int hc2 = bsmRef.hashCode();
+            return (Arrays.hashCode(argRefs) + (hc2 << 8)) ^ hc2;
+        }
+
+        BootstrapMethodEntry(MethodHandleEntry bsmRef, Entry[] argRefs) {
+            super(CONSTANT_BootstrapMethod);
+            this.bsmRef  = bsmRef;
+            this.argRefs = argRefs.clone();
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != BootstrapMethodEntry.class) {
+                return false;
+            }
+            BootstrapMethodEntry that = (BootstrapMethodEntry)o;
+            return this.bsmRef.eq(that.bsmRef)
+                && Arrays.equals(this.argRefs, that.argRefs);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                BootstrapMethodEntry that = (BootstrapMethodEntry)o;
+                if (Utils.SORT_BSS_BSM_MAJOR)
+                    // Primary key is bsmRef.
+                    x = this.bsmRef.compareTo(that.bsmRef);
+                // Primary key is args array length, which is transmitted as UDELTA5.
+                if (x == 0)
+                    x = compareArgArrays(this.argRefs, that.argRefs);
+                if (x == 0)
+                    x = this.bsmRef.compareTo(that.bsmRef);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return stringValueOf(bsmRef, argRefs);
+        }
+        static
+        String stringValueOf(MethodHandleEntry bsmRef, Entry[] argRefs) {
+            StringBuilder sb = new StringBuilder(bsmRef.stringValue());
+            // Arguments are formatted as "" instead of "[foo,bar,baz]".
+            // This ensures there will be no confusion if "[,]" appear inside of names.
+            char nextSep = '<';
+            boolean didOne = false;
+            for (Entry argRef : argRefs) {
+                sb.append(nextSep).append(argRef.stringValue());
+                nextSep = ';';
+            }
+            if (nextSep == '<')  sb.append(nextSep);
+            sb.append('>');
+            return sb.toString();
+        }
+        static
+        int compareArgArrays(Entry[] a1, Entry[] a2) {
+            int x = a1.length - a2.length;
+            if (x != 0)  return x;
+            for (int i = 0; i < a1.length; i++) {
+                x = a1[i].compareTo(a2[i]);
+                if (x != 0)  break;
+            }
+            return x;
+        }
+    }
+
+    // Handy constants:
+    protected static final Entry[] noRefs = {};
+    protected static final ClassEntry[] noClassRefs = {};
+
+    /** An Index is a mapping between CP entries and small integers. */
+    public static final
+    class Index extends AbstractList {
+        protected String debugName;
+        protected Entry[] cpMap;
+        protected boolean flattenSigs;
+        protected Entry[] getMap() {
+            return cpMap;
+        }
+        protected Index(String debugName) {
+            this.debugName = debugName;
+        }
+        protected Index(String debugName, Entry[] cpMap) {
+            this(debugName);
+            setMap(cpMap);
+        }
+        protected void setMap(Entry[] cpMap) {
+            clearIndex();
+            this.cpMap = cpMap;
+        }
+        protected Index(String debugName, Collection cpMapList) {
+            this(debugName);
+            setMap(cpMapList);
+        }
+        protected void setMap(Collection cpMapList) {
+            cpMap = new Entry[cpMapList.size()];
+            cpMapList.toArray(cpMap);
+            setMap(cpMap);
+        }
+        public int size() {
+            return cpMap.length;
+        }
+        public Entry get(int i) {
+            return cpMap[i];
+        }
+        public Entry getEntry(int i) {
+            // same as get(), with covariant return type
+            return cpMap[i];
+        }
+
+        // Find index of e in cpMap, or return -1 if none.
+        //
+        // As a special hack, if flattenSigs, signatures are
+        // treated as equivalent entries of cpMap.  This is wrong
+        // from a Collection point of view, because contains()
+        // reports true for signatures, but the iterator()
+        // never produces them!
+        private int findIndexOf(Entry e) {
+            if (indexKey == null) {
+                initializeIndex();
+            }
+            int probe = findIndexLocation(e);
+            if (indexKey[probe] != e) {
+                if (flattenSigs && e.tag == CONSTANT_Signature) {
+                    SignatureEntry se = (SignatureEntry) e;
+                    return findIndexOf(se.asUtf8Entry());
+                }
+                return -1;
+            }
+            int index = indexValue[probe];
+            assert(e.equals(cpMap[index]));
+            return index;
+        }
+        public boolean contains(Entry e) {
+            return findIndexOf(e) >= 0;
+        }
+        // Find index of e in cpMap.  Should not return -1.
+        public int indexOf(Entry e) {
+            int index = findIndexOf(e);
+            if (index < 0 && verbose() > 0) {
+                System.out.println("not found: "+e);
+                System.out.println("       in: "+this.dumpString());
+                Thread.dumpStack();
+            }
+            assert(index >= 0);
+            return index;
+        }
+        public int lastIndexOf(Entry e) {
+            return indexOf(e);
+        }
+
+        public boolean assertIsSorted() {
+            for (int i = 1; i < cpMap.length; i++) {
+                if (cpMap[i-1].compareTo(cpMap[i]) > 0) {
+                    System.out.println("Not sorted at "+(i-1)+"/"+i+": "+this.dumpString());
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // internal hash table
+        protected Entry[] indexKey;
+        protected int[]   indexValue;
+        protected void clearIndex() {
+            indexKey   = null;
+            indexValue = null;
+        }
+        private int findIndexLocation(Entry e) {
+            int size   = indexKey.length;
+            int hash   = e.hashCode();
+            int probe  = hash & (size - 1);
+            int stride = ((hash >>> 8) | 1) & (size - 1);
+            for (;;) {
+                Entry e1 = indexKey[probe];
+                if (e1 == e || e1 == null)
+                    return probe;
+                probe += stride;
+                if (probe >= size)  probe -= size;
+            }
+        }
+        private void initializeIndex() {
+            if (verbose() > 2)
+                System.out.println("initialize Index "+debugName+" ["+size()+"]");
+            int hsize0 = (int)((cpMap.length + 10) * 1.5);
+            int hsize = 1;
+            while (hsize < hsize0) {
+                hsize <<= 1;
+            }
+            indexKey   = new Entry[hsize];
+            indexValue = new int[hsize];
+            for (int i = 0; i < cpMap.length; i++) {
+                Entry e = cpMap[i];
+                if (e == null)  continue;
+                int probe = findIndexLocation(e);
+                assert(indexKey[probe] == null);  // e has unique index
+                indexKey[probe] = e;
+                indexValue[probe] = i;
+            }
+        }
+        public Entry[] toArray(Entry[] a) {
+            int sz = size();
+            if (a.length < sz)  return super.toArray(a);
+            System.arraycopy(cpMap, 0, a, 0, sz);
+            if (a.length > sz)  a[sz] = null;
+            return a;
+        }
+        public Entry[] toArray() {
+            return toArray(new Entry[size()]);
+        }
+        public Object clone() {
+            return new Index(debugName, cpMap.clone());
+        }
+        public String toString() {
+            return "Index "+debugName+" ["+size()+"]";
+        }
+        public String dumpString() {
+            String s = toString();
+            s += " {\n";
+            for (int i = 0; i < cpMap.length; i++) {
+                s += "    "+i+": "+cpMap[i]+"\n";
+            }
+            s += "}";
+            return s;
+        }
+    }
+
+    // Index methods.
+
+    public static
+    Index makeIndex(String debugName, Entry[] cpMap) {
+        return new Index(debugName, cpMap);
+    }
+
+    public static
+    Index makeIndex(String debugName, Collection cpMapList) {
+        return new Index(debugName, cpMapList);
+    }
+
+    /** Sort this index (destructively) into canonical order. */
+    public static
+    void sort(Index ix) {
+        // %%% Should move this into class Index.
+        ix.clearIndex();
+        Arrays.sort(ix.cpMap);
+        if (verbose() > 2)
+            System.out.println("sorted "+ix.dumpString());
+    }
+
+    /** Return a set of indexes partitioning these entries.
+     *  The keys array must of length this.size(), and marks entries.
+     *  The result array is as long as one plus the largest key value.
+     *  Entries with a negative key are dropped from the partition.
+     */
+    public static
+    Index[] partition(Index ix, int[] keys) {
+        // %%% Should move this into class Index.
+        List> parts = new ArrayList<>();
+        Entry[] cpMap = ix.cpMap;
+        assert(keys.length == cpMap.length);
+        for (int i = 0; i < keys.length; i++) {
+            int key = keys[i];
+            if (key < 0)  continue;
+            while (key >= parts.size()) {
+                parts.add(null);
+            }
+            List part = parts.get(key);
+            if (part == null) {
+                parts.set(key, part = new ArrayList<>());
+            }
+            part.add(cpMap[i]);
+        }
+        Index[] indexes = new Index[parts.size()];
+        for (int key = 0; key < indexes.length; key++) {
+            List part = parts.get(key);
+            if (part == null)  continue;
+            indexes[key] = new Index(ix.debugName+"/part#"+key, part);
+            assert(indexes[key].indexOf(part.get(0)) == 0);
+        }
+        return indexes;
+    }
+    public static
+    Index[] partitionByTag(Index ix) {
+        // Partition by tag.
+        Entry[] cpMap = ix.cpMap;
+        int[] keys = new int[cpMap.length];
+        for (int i = 0; i < keys.length; i++) {
+            Entry e = cpMap[i];
+            keys[i] = (e == null)? -1: e.tag;
+        }
+        Index[] byTag = partition(ix, keys);
+        for (int tag = 0; tag < byTag.length; tag++) {
+            if (byTag[tag] == null)  continue;
+            byTag[tag].debugName = tagName(tag);
+        }
+        if (byTag.length < CONSTANT_Limit) {
+            Index[] longer = new Index[CONSTANT_Limit];
+            System.arraycopy(byTag, 0, longer, 0, byTag.length);
+            byTag = longer;
+        }
+        return byTag;
+    }
+
+    /** Coherent group of constant pool indexes. */
+    public static
+    class IndexGroup {
+        private Index[] indexByTag = new Index[CONSTANT_Limit];
+        private Index[] indexByTagGroup;
+        private int[]   untypedFirstIndexByTag;
+        private int     totalSizeQQ;
+        private Index[][] indexByTagAndClass;
+
+        /** Index of all CP entries of all types, in definition order. */
+        private Index makeTagGroupIndex(byte tagGroupTag, byte[] tagsInGroup) {
+            if (indexByTagGroup == null)
+                indexByTagGroup = new Index[CONSTANT_GroupLimit - CONSTANT_GroupFirst];
+            int which = tagGroupTag - CONSTANT_GroupFirst;
+            assert(indexByTagGroup[which] == null);
+            int fillp = 0;
+            Entry[] cpMap = null;
+            for (int pass = 1; pass <= 2; pass++) {
+                untypedIndexOf(null);  // warm up untypedFirstIndexByTag
+                for (byte tag : tagsInGroup) {
+                    Index ix = indexByTag[tag];
+                    if (ix == null)  continue;
+                    int ixLen = ix.cpMap.length;
+                    if (ixLen == 0)  continue;
+                    assert(tagGroupTag == CONSTANT_All
+                            ? fillp == untypedFirstIndexByTag[tag]
+                            : fillp  < untypedFirstIndexByTag[tag]);
+                    if (cpMap != null) {
+                        assert(cpMap[fillp] == null);
+                        assert(cpMap[fillp+ixLen-1] == null);
+                        System.arraycopy(ix.cpMap, 0, cpMap, fillp, ixLen);
+                    }
+                    fillp += ixLen;
+                }
+                if (cpMap == null) {
+                    assert(pass == 1);
+                    // get ready for pass 2
+                    cpMap = new Entry[fillp];
+                    fillp = 0;
+                }
+            }
+            indexByTagGroup[which] = new Index(tagName(tagGroupTag), cpMap);
+            return indexByTagGroup[which];
+        }
+
+        public int untypedIndexOf(Entry e) {
+            if (untypedFirstIndexByTag == null) {
+                untypedFirstIndexByTag = new int[CONSTANT_Limit+1];
+                int fillp = 0;
+                for (int i = 0; i < TAGS_IN_ORDER.length; i++) {
+                    byte tag = TAGS_IN_ORDER[i];
+                    Index ix = indexByTag[tag];
+                    if (ix == null)  continue;
+                    int ixLen = ix.cpMap.length;
+                    untypedFirstIndexByTag[tag] = fillp;
+                    fillp += ixLen;
+                }
+                untypedFirstIndexByTag[CONSTANT_Limit] = fillp;
+            }
+            if (e == null)  return -1;
+            int tag = e.tag;
+            Index ix = indexByTag[tag];
+            if (ix == null)  return -1;
+            int idx = ix.findIndexOf(e);
+            if (idx >= 0)
+                idx += untypedFirstIndexByTag[tag];
+            return idx;
+        }
+
+        public void initIndexByTag(byte tag, Index ix) {
+            assert(indexByTag[tag] == null);  // do not init twice
+            Entry[] cpMap = ix.cpMap;
+            for (int i = 0; i < cpMap.length; i++) {
+                // It must be a homogeneous Entry set.
+                assert(cpMap[i].tag == tag);
+            }
+            if (tag == CONSTANT_Utf8) {
+                // Special case:  First Utf8 must always be empty string.
+                assert(cpMap.length == 0 || cpMap[0].stringValue().equals(""));
+            }
+            indexByTag[tag] = ix;
+            // decache indexes derived from this one:
+            untypedFirstIndexByTag = null;
+            indexByTagGroup = null;
+            if (indexByTagAndClass != null)
+                indexByTagAndClass[tag] = null;
+        }
+
+        /** Index of all CP entries of a given tag. */
+        public Index getIndexByTag(byte tag) {
+            if (tag >= CONSTANT_GroupFirst)
+                return getIndexByTagGroup(tag);
+            Index ix = indexByTag[tag];
+            if (ix == null) {
+                // Make an empty one by default.
+                ix = new Index(tagName(tag), new Entry[0]);
+                indexByTag[tag] = ix;
+            }
+            return ix;
+        }
+
+        private Index getIndexByTagGroup(byte tag) {
+            // pool groups:
+            if (indexByTagGroup != null) {
+                Index ix = indexByTagGroup[tag - CONSTANT_GroupFirst];
+                if (ix != null)  return ix;
+            }
+            switch (tag) {
+            case CONSTANT_All:
+                return makeTagGroupIndex(CONSTANT_All, TAGS_IN_ORDER);
+            case CONSTANT_LoadableValue:
+                    return makeTagGroupIndex(CONSTANT_LoadableValue, LOADABLE_VALUE_TAGS);
+            case CONSTANT_AnyMember:
+                return makeTagGroupIndex(CONSTANT_AnyMember, ANY_MEMBER_TAGS);
+            case CONSTANT_FieldSpecific:
+                // This one does not have any fixed index, since it is context-specific.
+                return null;
+            }
+            throw new AssertionError("bad tag group "+tag);
+        }
+
+        /** Index of all CP entries of a given tag and class. */
+        public Index getMemberIndex(byte tag, ClassEntry classRef) {
+            if (classRef == null)
+                throw new RuntimeException("missing class reference for " + tagName(tag));
+            if (indexByTagAndClass == null)
+                indexByTagAndClass = new Index[CONSTANT_Limit][];
+            Index allClasses =  getIndexByTag(CONSTANT_Class);
+            Index[] perClassIndexes = indexByTagAndClass[tag];
+            if (perClassIndexes == null) {
+                // Create the partition now.
+                // Divide up all entries of the given tag according to their class.
+                Index allMembers = getIndexByTag(tag);
+                int[] whichClasses = new int[allMembers.size()];
+                for (int i = 0; i < whichClasses.length; i++) {
+                    MemberEntry e = (MemberEntry) allMembers.get(i);
+                    int whichClass = allClasses.indexOf(e.classRef);
+                    whichClasses[i] = whichClass;
+                }
+                perClassIndexes = partition(allMembers, whichClasses);
+                for (int i = 0; i < perClassIndexes.length; i++) {
+                    assert (perClassIndexes[i] == null ||
+                            perClassIndexes[i].assertIsSorted());
+                }
+                indexByTagAndClass[tag] = perClassIndexes;
+            }
+            int whichClass = allClasses.indexOf(classRef);
+            return perClassIndexes[whichClass];
+        }
+
+        // Given the sequence of all methods of the given name and class,
+        // produce the ordinal of this particular given overloading.
+        public int getOverloadingIndex(MemberEntry methodRef) {
+            Index ix = getMemberIndex(methodRef.tag, methodRef.classRef);
+            Utf8Entry nameRef = methodRef.descRef.nameRef;
+            int ord = 0;
+            for (int i = 0; i < ix.cpMap.length; i++) {
+                MemberEntry e = (MemberEntry) ix.cpMap[i];
+                if (e.equals(methodRef))
+                    return ord;
+                if (e.descRef.nameRef.equals(nameRef))
+                    // Found a different overloading.  Increment the ordinal.
+                    ord++;
+            }
+            throw new RuntimeException("should not reach here");
+        }
+
+        // Inverse of getOverloadingIndex
+        public MemberEntry getOverloadingForIndex(byte tag, ClassEntry classRef, String name, int which) {
+            assert(name.equals(name.intern()));
+            Index ix = getMemberIndex(tag, classRef);
+            int ord = 0;
+            for (int i = 0; i < ix.cpMap.length; i++) {
+                MemberEntry e = (MemberEntry) ix.cpMap[i];
+                if (e.descRef.nameRef.stringValue().equals(name)) {
+                    if (ord == which)  return e;
+                    ord++;
+                }
+            }
+            throw new RuntimeException("should not reach here");
+        }
+
+        public boolean haveNumbers() {
+            for (byte tag : NUMBER_TAGS) {
+                if (getIndexByTag(tag).size() > 0)  return true;
+            }
+            return false;
+        }
+
+        public boolean haveExtraTags() {
+            for (byte tag : EXTRA_TAGS) {
+                if (getIndexByTag(tag).size() > 0)  return true;
+            }
+            return false;
+        }
+
+    }
+
+    /** Close the set cpRefs under the getRef(*) relation.
+     *  Also, if flattenSigs, replace all signatures in cpRefs
+     *  by their equivalent Utf8s.
+     *  Also, discard null from cpRefs.
+     */
+    public static void completeReferencesIn(Set cpRefs, boolean flattenSigs) {
+         completeReferencesIn(cpRefs, flattenSigs, null);
+    }
+
+    public static
+    void completeReferencesIn(Set cpRefs, boolean flattenSigs,
+                              Listbsms) {
+        cpRefs.remove(null);
+        for (ListIterator work =
+                 new ArrayList<>(cpRefs).listIterator(cpRefs.size());
+             work.hasPrevious(); ) {
+            Entry e = work.previous();
+            work.remove();          // pop stack
+            assert(e != null);
+            if (flattenSigs && e.tag == CONSTANT_Signature) {
+                SignatureEntry se = (SignatureEntry) e;
+                Utf8Entry      ue = se.asUtf8Entry();
+                // Totally replace e by se.
+                cpRefs.remove(se);
+                cpRefs.add(ue);
+                e = ue;   // do not descend into the sig
+            }
+            if (bsms != null && e.tag == CONSTANT_BootstrapMethod) {
+                BootstrapMethodEntry bsm = (BootstrapMethodEntry)e;
+                cpRefs.remove(bsm);
+                // move it away to the side table where it belongs
+                if (!bsms.contains(bsm))
+                    bsms.add(bsm);
+                // fall through to recursively add refs for this entry
+            }
+            // Recursively add the refs of e to cpRefs:
+            for (int i = 0; ; i++) {
+                Entry re = e.getRef(i);
+                if (re == null)
+                    break;          // no more refs in e
+                if (cpRefs.add(re)) // output the ref
+                    work.add(re);   // push stack, if a new ref
+            }
+        }
+    }
+
+    static double percent(int num, int den) {
+        return (int)((10000.0*num)/den + 0.5) / 100.0;
+    }
+
+    public static String tagName(int tag) {
+        switch (tag) {
+            case CONSTANT_Utf8:                 return "Utf8";
+            case CONSTANT_Integer:              return "Integer";
+            case CONSTANT_Float:                return "Float";
+            case CONSTANT_Long:                 return "Long";
+            case CONSTANT_Double:               return "Double";
+            case CONSTANT_Class:                return "Class";
+            case CONSTANT_String:               return "String";
+            case CONSTANT_Fieldref:             return "Fieldref";
+            case CONSTANT_Methodref:            return "Methodref";
+            case CONSTANT_InterfaceMethodref:   return "InterfaceMethodref";
+            case CONSTANT_NameandType:          return "NameandType";
+            case CONSTANT_MethodHandle:         return "MethodHandle";
+            case CONSTANT_MethodType:           return "MethodType";
+            case CONSTANT_InvokeDynamic:        return "InvokeDynamic";
+
+                // pseudo-tags:
+            case CONSTANT_All:                  return "**All";
+            case CONSTANT_None:                 return "**None";
+            case CONSTANT_LoadableValue:        return "**LoadableValue";
+            case CONSTANT_AnyMember:            return "**AnyMember";
+            case CONSTANT_FieldSpecific:        return "*FieldSpecific";
+            case CONSTANT_Signature:            return "*Signature";
+            case CONSTANT_BootstrapMethod:      return "*BootstrapMethod";
+        }
+        return "tag#"+tag;
+    }
+
+    public static String refKindName(int refKind) {
+        switch (refKind) {
+            case REF_getField:                  return "getField";
+            case REF_getStatic:                 return "getStatic";
+            case REF_putField:                  return "putField";
+            case REF_putStatic:                 return "putStatic";
+            case REF_invokeVirtual:             return "invokeVirtual";
+            case REF_invokeStatic:              return "invokeStatic";
+            case REF_invokeSpecial:             return "invokeSpecial";
+            case REF_newInvokeSpecial:          return "newInvokeSpecial";
+            case REF_invokeInterface:           return "invokeInterface";
+        }
+        return "refKind#"+refKind;
+    }
+
+    // archive constant pool definition order
+    static final byte TAGS_IN_ORDER[] = {
+        CONSTANT_Utf8,
+        CONSTANT_Integer,           // cp_Int
+        CONSTANT_Float,
+        CONSTANT_Long,
+        CONSTANT_Double,
+        CONSTANT_String,            // note that String=8 precedes Class=7
+        CONSTANT_Class,
+        CONSTANT_Signature,
+        CONSTANT_NameandType,       // cp_Descr
+        CONSTANT_Fieldref,          // cp_Field
+        CONSTANT_Methodref,         // cp_Method
+        CONSTANT_InterfaceMethodref, // cp_Imethod
+
+        // Constants defined in JDK 7 and later:
+        CONSTANT_MethodHandle,
+        CONSTANT_MethodType,
+        CONSTANT_BootstrapMethod,  // pseudo-tag, really stored in a class attribute
+        CONSTANT_InvokeDynamic
+    };
+    static final byte TAG_ORDER[];
+    static {
+        TAG_ORDER = new byte[CONSTANT_Limit];
+        for (int i = 0; i < TAGS_IN_ORDER.length; i++) {
+            TAG_ORDER[TAGS_IN_ORDER[i]] = (byte)(i+1);
+        }
+        /*
+        System.out.println("TAG_ORDER[] = {");
+        for (int i = 0; i < TAG_ORDER.length; i++)
+            System.out.println("  "+TAG_ORDER[i]+",");
+        System.out.println("};");
+        */
+    }
+    static final byte[] NUMBER_TAGS = {
+        CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, CONSTANT_Double
+    };
+    static final byte[] EXTRA_TAGS = {
+        CONSTANT_MethodHandle, CONSTANT_MethodType,
+        CONSTANT_BootstrapMethod, // pseudo-tag
+        CONSTANT_InvokeDynamic
+    };
+    static final byte[] LOADABLE_VALUE_TAGS = { // for CONSTANT_LoadableValue
+        CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, CONSTANT_Double,
+        CONSTANT_String, CONSTANT_Class,
+        CONSTANT_MethodHandle, CONSTANT_MethodType
+    };
+    static final byte[] ANY_MEMBER_TAGS = { // for CONSTANT_AnyMember
+        CONSTANT_Fieldref, CONSTANT_Methodref, CONSTANT_InterfaceMethodref
+    };
+    static final byte[] FIELD_SPECIFIC_TAGS = { // for CONSTANT_FieldSpecific
+        CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, CONSTANT_Double,
+        CONSTANT_String
+    };
+    static {
+        assert(
+            verifyTagOrder(TAGS_IN_ORDER) &&
+            verifyTagOrder(NUMBER_TAGS) &&
+            verifyTagOrder(EXTRA_TAGS) &&
+            verifyTagOrder(LOADABLE_VALUE_TAGS) &&
+            verifyTagOrder(ANY_MEMBER_TAGS) &&
+            verifyTagOrder(FIELD_SPECIFIC_TAGS)
+        );
+    }
+    private static boolean verifyTagOrder(byte[] tags) {
+        int prev = -1;
+        for (byte tag : tags) {
+            int next = TAG_ORDER[tag];
+            assert(next > 0) : "tag not found: "+tag;
+            assert(TAGS_IN_ORDER[next-1] == tag) : "tag repeated: "+tag+" => "+next+" => "+TAGS_IN_ORDER[next-1];
+            assert(prev < next) : "tags not in order: "+Arrays.toString(tags)+" at "+tag;
+            prev = next;
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/Constants.java b/src/main/java/net/minecraftforge/gradle/util/pack200/Constants.java
new file mode 100644
index 000000000..007fa6a76
--- /dev/null
+++ b/src/main/java/net/minecraftforge/gradle/util/pack200/Constants.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright (c) 2001, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package net.minecraftforge.gradle.util.pack200;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Shared constants
+ * @author John Rose
+ */
+class Constants {
+
+    private Constants(){}
+
+    public static final int JAVA_MAGIC = 0xCAFEBABE;
+
+    /*
+        Java Class Version numbers history
+        1.0 to 1.3.X 45,3
+        1.4 to 1.4.X 46,0
+        1.5 to 1.5.X 49,0
+        1.6 to 1.6.X 50,0
+        1.7 to 1.7.X 51,0
+        1.8 to 1.8.X 52,0
+        1.9 to 1.9.X 53,0
+        1.10 to 1.10.X 54,0
+        1.11 to 1.11.X 55,0
+    */
+
+    public static final Package.Version JAVA_MIN_CLASS_VERSION =
+            Package.Version.of(45, 03);
+
+    public static final Package.Version JAVA5_MAX_CLASS_VERSION =
+            Package.Version.of(49, 00);
+
+    public static final Package.Version JAVA6_MAX_CLASS_VERSION =
+            Package.Version.of(50, 00);
+
+    public static final Package.Version JAVA7_MAX_CLASS_VERSION =
+            Package.Version.of(51, 00);
+
+    public static final Package.Version JAVA8_MAX_CLASS_VERSION =
+            Package.Version.of(52, 00);
+
+    public static final Package.Version JAVA9_MAX_CLASS_VERSION =
+            Package.Version.of(53, 00);
+
+    public static final Package.Version JAVA10_MAX_CLASS_VERSION =
+            Package.Version.of(54, 00);
+
+    public static final Package.Version JAVA11_MAX_CLASS_VERSION =
+            Package.Version.of(55, 00);
+
+    public static final int JAVA_PACKAGE_MAGIC = 0xCAFED00D;
+
+    public static final Package.Version JAVA5_PACKAGE_VERSION =
+            Package.Version.of(150, 7);
+
+    public static final Package.Version JAVA6_PACKAGE_VERSION =
+            Package.Version.of(160, 1);
+
+    public static final Package.Version JAVA7_PACKAGE_VERSION =
+            Package.Version.of(170, 1);
+
+    public static final Package.Version JAVA8_PACKAGE_VERSION =
+            Package.Version.of(171, 0);
+
+    // upper limit, should point to the latest class version
+    public static final Package.Version JAVA_MAX_CLASS_VERSION =
+            JAVA11_MAX_CLASS_VERSION;
+
+    // upper limit should point to the latest package version, for version info!.
+    public static final Package.Version MAX_PACKAGE_VERSION =
+            JAVA7_PACKAGE_VERSION;
+
+    public static final int CONSTANT_POOL_INDEX_LIMIT  = 0x10000;
+    public static final int CONSTANT_POOL_NARROW_LIMIT = 0x00100;
+
+    public static final String JAVA_SIGNATURE_CHARS = "BSCIJFDZLV([";
+
+    public static final byte CONSTANT_Utf8 = 1;
+    public static final byte CONSTANT_unused2 = 2;  // unused, was Unicode
+    public static final byte CONSTANT_Integer = 3;
+    public static final byte CONSTANT_Float = 4;
+    public static final byte CONSTANT_Long = 5;
+    public static final byte CONSTANT_Double = 6;
+    public static final byte CONSTANT_Class = 7;
+    public static final byte CONSTANT_String = 8;
+    public static final byte CONSTANT_Fieldref = 9;
+    public static final byte CONSTANT_Methodref = 10;
+    public static final byte CONSTANT_InterfaceMethodref = 11;
+    public static final byte CONSTANT_NameandType = 12;
+    public static final byte CONSTANT_unused13 = 13;
+    public static final byte CONSTANT_unused14 = 14;
+    public static final byte CONSTANT_MethodHandle = 15;
+    public static final byte CONSTANT_MethodType = 16;
+    public static final byte CONSTANT_unused17 = 17;  // unused
+    public static final byte CONSTANT_InvokeDynamic = 18;
+
+    // pseudo-constants:
+    public static final byte CONSTANT_None = 0;
+    public static final byte CONSTANT_Signature = CONSTANT_unused13;
+    public static final byte CONSTANT_BootstrapMethod = CONSTANT_unused17; // used only in InvokeDynamic constants
+    public static final byte CONSTANT_Limit = 19;
+
+    public static final byte CONSTANT_All = 50;  // combined global map
+    public static final byte CONSTANT_LoadableValue = 51; // used for 'KL' and qldc operands
+    public static final byte CONSTANT_AnyMember = 52; // union of refs to field or (interface) method
+    public static final byte CONSTANT_FieldSpecific = 53; // used only for 'KQ' ConstantValue attrs
+    public static final byte CONSTANT_GroupFirst = CONSTANT_All;
+    public static final byte CONSTANT_GroupLimit = CONSTANT_FieldSpecific+1;
+
+    // CONSTANT_MethodHandle reference kinds
+    public static final byte REF_getField = 1;
+    public static final byte REF_getStatic = 2;
+    public static final byte REF_putField = 3;
+    public static final byte REF_putStatic = 4;
+    public static final byte REF_invokeVirtual = 5;
+    public static final byte REF_invokeStatic = 6;
+    public static final byte REF_invokeSpecial = 7;
+    public static final byte REF_newInvokeSpecial = 8;
+    public static final byte REF_invokeInterface = 9;
+
+    // pseudo-access bits
+    public static final int ACC_IC_LONG_FORM   = (1<<16); //for ic_flags
+
+    // attribute "context types"
+    public static final int ATTR_CONTEXT_CLASS  = 0;
+    public static final int ATTR_CONTEXT_FIELD  = 1;
+    public static final int ATTR_CONTEXT_METHOD = 2;
+    public static final int ATTR_CONTEXT_CODE   = 3;
+    public static final int ATTR_CONTEXT_LIMIT  = 4;
+    public static final String[] ATTR_CONTEXT_NAME
+        = { "class", "field", "method", "code" };
+
+    // predefined attr bits
+    public static final int
+        X_ATTR_OVERFLOW = 16,
+        CLASS_ATTR_SourceFile = 17,
+        METHOD_ATTR_Code = 17,
+        FIELD_ATTR_ConstantValue = 17,
+        CLASS_ATTR_EnclosingMethod = 18,
+        METHOD_ATTR_Exceptions = 18,
+        X_ATTR_Signature = 19,
+        X_ATTR_Deprecated = 20,
+        X_ATTR_RuntimeVisibleAnnotations = 21,
+        X_ATTR_RuntimeInvisibleAnnotations = 22,
+        METHOD_ATTR_RuntimeVisibleParameterAnnotations = 23,
+        CLASS_ATTR_InnerClasses = 23,
+        METHOD_ATTR_RuntimeInvisibleParameterAnnotations = 24,
+        CLASS_ATTR_ClassFile_version = 24,
+        METHOD_ATTR_AnnotationDefault = 25,
+        METHOD_ATTR_MethodParameters = 26,           // JDK8
+        X_ATTR_RuntimeVisibleTypeAnnotations = 27,   // JDK8
+        X_ATTR_RuntimeInvisibleTypeAnnotations = 28, // JDK8
+        CODE_ATTR_StackMapTable = 0,  // new in Java 6
+        CODE_ATTR_LineNumberTable = 1,
+        CODE_ATTR_LocalVariableTable = 2,
+        CODE_ATTR_LocalVariableTypeTable = 3;
+
+    // File option bits, from LSB in ascending bit position.
+    public static final int FO_DEFLATE_HINT           = 1<<0;
+    public static final int FO_IS_CLASS_STUB          = 1<<1;
+
+    // Archive option bits, from LSB in ascending bit position:
+    public static final int AO_HAVE_SPECIAL_FORMATS   = 1<<0;
+    public static final int AO_HAVE_CP_NUMBERS        = 1<<1;
+    public static final int AO_HAVE_ALL_CODE_FLAGS    = 1<<2;
+    public static final int AO_HAVE_CP_EXTRAS         = 1<<3;
+    public static final int AO_HAVE_FILE_HEADERS      = 1<<4;
+    public static final int AO_DEFLATE_HINT           = 1<<5;
+    public static final int AO_HAVE_FILE_MODTIME      = 1<<6;
+    public static final int AO_HAVE_FILE_OPTIONS      = 1<<7;
+    public static final int AO_HAVE_FILE_SIZE_HI      = 1<<8;
+    public static final int AO_HAVE_CLASS_FLAGS_HI    = 1<<9;
+    public static final int AO_HAVE_FIELD_FLAGS_HI    = 1<<10;
+    public static final int AO_HAVE_METHOD_FLAGS_HI   = 1<<11;
+    public static final int AO_HAVE_CODE_FLAGS_HI     = 1<<12;
+    public static final int AO_UNUSED_MBZ          = (-1)<<13;  // option bits reserved for future use
+
+    public static final int LG_AO_HAVE_XXX_FLAGS_HI   = 9;
+
+    // visitRefs modes:
+    static final int VRM_CLASSIC = 0;
+    static final int VRM_PACKAGE = 1;
+
+    public static final int NO_MODTIME = 0;  // null modtime value
+
+    // some comstantly empty containers
+    public static final int[]        noInts = {};
+    public static final byte[]       noBytes = {};
+    public static final Object[]     noValues = {};
+    public static final String[]     noStrings = {};
+    public static final List emptyList = Arrays.asList(noValues);
+
+    // meta-coding
+    public static final int
+        _meta_default = 0,
+        _meta_canon_min = 1,
+        _meta_canon_max = 115,
+        _meta_arb = 116,
+        _meta_run = 117,
+        _meta_pop = 141,
+        _meta_limit = 189;
+
+    // bytecodes
+    public static final int
+        _nop                  =   0, // 0x00
+        _aconst_null          =   1, // 0x01
+        _iconst_m1            =   2, // 0x02
+        _iconst_0             =   3, // 0x03
+        _iconst_1             =   4, // 0x04
+        _iconst_2             =   5, // 0x05
+        _iconst_3             =   6, // 0x06
+        _iconst_4             =   7, // 0x07
+        _iconst_5             =   8, // 0x08
+        _lconst_0             =   9, // 0x09
+        _lconst_1             =  10, // 0x0a
+        _fconst_0             =  11, // 0x0b
+        _fconst_1             =  12, // 0x0c
+        _fconst_2             =  13, // 0x0d
+        _dconst_0             =  14, // 0x0e
+        _dconst_1             =  15, // 0x0f
+        _bipush               =  16, // 0x10
+        _sipush               =  17, // 0x11
+        _ldc                  =  18, // 0x12
+        _ldc_w                =  19, // 0x13
+        _ldc2_w               =  20, // 0x14
+        _iload                =  21, // 0x15
+        _lload                =  22, // 0x16
+        _fload                =  23, // 0x17
+        _dload                =  24, // 0x18
+        _aload                =  25, // 0x19
+        _iload_0              =  26, // 0x1a
+        _iload_1              =  27, // 0x1b
+        _iload_2              =  28, // 0x1c
+        _iload_3              =  29, // 0x1d
+        _lload_0              =  30, // 0x1e
+        _lload_1              =  31, // 0x1f
+        _lload_2              =  32, // 0x20
+        _lload_3              =  33, // 0x21
+        _fload_0              =  34, // 0x22
+        _fload_1              =  35, // 0x23
+        _fload_2              =  36, // 0x24
+        _fload_3              =  37, // 0x25
+        _dload_0              =  38, // 0x26
+        _dload_1              =  39, // 0x27
+        _dload_2              =  40, // 0x28
+        _dload_3              =  41, // 0x29
+        _aload_0              =  42, // 0x2a
+        _aload_1              =  43, // 0x2b
+        _aload_2              =  44, // 0x2c
+        _aload_3              =  45, // 0x2d
+        _iaload               =  46, // 0x2e
+        _laload               =  47, // 0x2f
+        _faload               =  48, // 0x30
+        _daload               =  49, // 0x31
+        _aaload               =  50, // 0x32
+        _baload               =  51, // 0x33
+        _caload               =  52, // 0x34
+        _saload               =  53, // 0x35
+        _istore               =  54, // 0x36
+        _lstore               =  55, // 0x37
+        _fstore               =  56, // 0x38
+        _dstore               =  57, // 0x39
+        _astore               =  58, // 0x3a
+        _istore_0             =  59, // 0x3b
+        _istore_1             =  60, // 0x3c
+        _istore_2             =  61, // 0x3d
+        _istore_3             =  62, // 0x3e
+        _lstore_0             =  63, // 0x3f
+        _lstore_1             =  64, // 0x40
+        _lstore_2             =  65, // 0x41
+        _lstore_3             =  66, // 0x42
+        _fstore_0             =  67, // 0x43
+        _fstore_1             =  68, // 0x44
+        _fstore_2             =  69, // 0x45
+        _fstore_3             =  70, // 0x46
+        _dstore_0             =  71, // 0x47
+        _dstore_1             =  72, // 0x48
+        _dstore_2             =  73, // 0x49
+        _dstore_3             =  74, // 0x4a
+        _astore_0             =  75, // 0x4b
+        _astore_1             =  76, // 0x4c
+        _astore_2             =  77, // 0x4d
+        _astore_3             =  78, // 0x4e
+        _iastore              =  79, // 0x4f
+        _lastore              =  80, // 0x50
+        _fastore              =  81, // 0x51
+        _dastore              =  82, // 0x52
+        _aastore              =  83, // 0x53
+        _bastore              =  84, // 0x54
+        _castore              =  85, // 0x55
+        _sastore              =  86, // 0x56
+        _pop                  =  87, // 0x57
+        _pop2                 =  88, // 0x58
+        _dup                  =  89, // 0x59
+        _dup_x1               =  90, // 0x5a
+        _dup_x2               =  91, // 0x5b
+        _dup2                 =  92, // 0x5c
+        _dup2_x1              =  93, // 0x5d
+        _dup2_x2              =  94, // 0x5e
+        _swap                 =  95, // 0x5f
+        _iadd                 =  96, // 0x60
+        _ladd                 =  97, // 0x61
+        _fadd                 =  98, // 0x62
+        _dadd                 =  99, // 0x63
+        _isub                 = 100, // 0x64
+        _lsub                 = 101, // 0x65
+        _fsub                 = 102, // 0x66
+        _dsub                 = 103, // 0x67
+        _imul                 = 104, // 0x68
+        _lmul                 = 105, // 0x69
+        _fmul                 = 106, // 0x6a
+        _dmul                 = 107, // 0x6b
+        _idiv                 = 108, // 0x6c
+        _ldiv                 = 109, // 0x6d
+        _fdiv                 = 110, // 0x6e
+        _ddiv                 = 111, // 0x6f
+        _irem                 = 112, // 0x70
+        _lrem                 = 113, // 0x71
+        _frem                 = 114, // 0x72
+        _drem                 = 115, // 0x73
+        _ineg                 = 116, // 0x74
+        _lneg                 = 117, // 0x75
+        _fneg                 = 118, // 0x76
+        _dneg                 = 119, // 0x77
+        _ishl                 = 120, // 0x78
+        _lshl                 = 121, // 0x79
+        _ishr                 = 122, // 0x7a
+        _lshr                 = 123, // 0x7b
+        _iushr                = 124, // 0x7c
+        _lushr                = 125, // 0x7d
+        _iand                 = 126, // 0x7e
+        _land                 = 127, // 0x7f
+        _ior                  = 128, // 0x80
+        _lor                  = 129, // 0x81
+        _ixor                 = 130, // 0x82
+        _lxor                 = 131, // 0x83
+        _iinc                 = 132, // 0x84
+        _i2l                  = 133, // 0x85
+        _i2f                  = 134, // 0x86
+        _i2d                  = 135, // 0x87
+        _l2i                  = 136, // 0x88
+        _l2f                  = 137, // 0x89
+        _l2d                  = 138, // 0x8a
+        _f2i                  = 139, // 0x8b
+        _f2l                  = 140, // 0x8c
+        _f2d                  = 141, // 0x8d
+        _d2i                  = 142, // 0x8e
+        _d2l                  = 143, // 0x8f
+        _d2f                  = 144, // 0x90
+        _i2b                  = 145, // 0x91
+        _i2c                  = 146, // 0x92
+        _i2s                  = 147, // 0x93
+        _lcmp                 = 148, // 0x94
+        _fcmpl                = 149, // 0x95
+        _fcmpg                = 150, // 0x96
+        _dcmpl                = 151, // 0x97
+        _dcmpg                = 152, // 0x98
+        _ifeq                 = 153, // 0x99
+        _ifne                 = 154, // 0x9a
+        _iflt                 = 155, // 0x9b
+        _ifge                 = 156, // 0x9c
+        _ifgt                 = 157, // 0x9d
+        _ifle                 = 158, // 0x9e
+        _if_icmpeq            = 159, // 0x9f
+        _if_icmpne            = 160, // 0xa0
+        _if_icmplt            = 161, // 0xa1
+        _if_icmpge            = 162, // 0xa2
+        _if_icmpgt            = 163, // 0xa3
+        _if_icmple            = 164, // 0xa4
+        _if_acmpeq            = 165, // 0xa5
+        _if_acmpne            = 166, // 0xa6
+        _goto                 = 167, // 0xa7
+        _jsr                  = 168, // 0xa8
+        _ret                  = 169, // 0xa9
+        _tableswitch          = 170, // 0xaa
+        _lookupswitch         = 171, // 0xab
+        _ireturn              = 172, // 0xac
+        _lreturn              = 173, // 0xad
+        _freturn              = 174, // 0xae
+        _dreturn              = 175, // 0xaf
+        _areturn              = 176, // 0xb0
+        _return               = 177, // 0xb1
+        _getstatic            = 178, // 0xb2
+        _putstatic            = 179, // 0xb3
+        _getfield             = 180, // 0xb4
+        _putfield             = 181, // 0xb5
+        _invokevirtual        = 182, // 0xb6
+        _invokespecial        = 183, // 0xb7
+        _invokestatic         = 184, // 0xb8
+        _invokeinterface      = 185, // 0xb9
+        _invokedynamic        = 186, // 0xba
+        _new                  = 187, // 0xbb
+        _newarray             = 188, // 0xbc
+        _anewarray            = 189, // 0xbd
+        _arraylength          = 190, // 0xbe
+        _athrow               = 191, // 0xbf
+        _checkcast            = 192, // 0xc0
+        _instanceof           = 193, // 0xc1
+        _monitorenter         = 194, // 0xc2
+        _monitorexit          = 195, // 0xc3
+        _wide                 = 196, // 0xc4
+        _multianewarray       = 197, // 0xc5
+        _ifnull               = 198, // 0xc6
+        _ifnonnull            = 199, // 0xc7
+        _goto_w               = 200, // 0xc8
+        _jsr_w                = 201, // 0xc9
+        _bytecode_limit       = 202; // 0xca
+
+    // End marker, used to terminate bytecode sequences:
+    public static final int _end_marker = 255;
+    // Escapes:
+    public static final int _byte_escape = 254;
+    public static final int _ref_escape = 253;
+
+    // Self-relative pseudo-opcodes for better compression.
+    // A "linker op" is a bytecode which links to a class member.
+    // (But in what follows, "invokeinterface" ops are excluded.)
+    //
+    // A "self linker op" is a variant bytecode which works only
+    // with the current class or its super.  Because the number of
+    // possible targets is small, it admits a more compact encoding.
+    // Self linker ops are allowed to absorb a previous "aload_0" op.
+    // There are (7 * 4) self linker ops (super or not, aload_0 or not).
+    //
+    // For simplicity, we define the full symmetric set of variants.
+    // However, some of them are relatively useless.
+    // Self linker ops are enabled by Pack.selfCallVariants (true).
+    public static final int _first_linker_op = _getstatic;
+    public static final int _last_linker_op  = _invokestatic;
+    public static final int _num_linker_ops  = (_last_linker_op - _first_linker_op) + 1;
+    public static final int _self_linker_op  = _bytecode_limit;
+    public static final int _self_linker_aload_flag = 1*_num_linker_ops;
+    public static final int _self_linker_super_flag = 2*_num_linker_ops;
+    public static final int _self_linker_limit = _self_linker_op + 4*_num_linker_ops;
+    // An "invoke init" op is a variant of invokespecial which works
+    // only with the method name "".  There are variants which
+    // link to the current class, the super class, or the class of the
+    // immediately previous "newinstance" op.  There are 3 of these ops.
+    // They all take method signature references as operands.
+    // Invoke init ops are enabled by Pack.initCallVariants (true).
+    public static final int _invokeinit_op = _self_linker_limit;
+    public static final int _invokeinit_self_option = 0;
+    public static final int _invokeinit_super_option = 1;
+    public static final int _invokeinit_new_option = 2;
+    public static final int _invokeinit_limit = _invokeinit_op+3;
+
+    public static final int _pseudo_instruction_limit = _invokeinit_limit;
+    // linker variant limit == 202+(7*4)+3 == 233
+
+    // Ldc variants support strongly typed references to constants.
+    // This lets us index constant pool entries completely according to tag,
+    // which is a great simplification.
+    // Ldc variants gain us only 0.007% improvement in compression ratio,
+    // but they simplify the file format greatly.
+    public static final int _xldc_op = _invokeinit_limit;
+    public static final int _sldc = _ldc;  // previously named _aldc
+    public static final int _cldc = _xldc_op+0;
+    public static final int _ildc = _xldc_op+1;
+    public static final int _fldc = _xldc_op+2;
+    public static final int _sldc_w = _ldc_w;  // previously named _aldc_w
+    public static final int _cldc_w = _xldc_op+3;
+    public static final int _ildc_w = _xldc_op+4;
+    public static final int _fldc_w = _xldc_op+5;
+    public static final int _lldc2_w = _ldc2_w;
+    public static final int _dldc2_w = _xldc_op+6;
+    // anything other than primitive, string, or class must be handled with qldc:
+    public static final int _qldc   = _xldc_op+7;
+    public static final int _qldc_w = _xldc_op+8;
+    public static final int _xldc_limit = _xldc_op+9;
+
+    // handling of InterfaceMethodRef
+    public static final int _invoke_int_op = _xldc_limit;
+    public static final int _invokespecial_int = _invoke_int_op+0;
+    public static final int _invokestatic_int = _invoke_int_op+1;
+    public static final int _invoke_int_limit = _invoke_int_op+2;
+}
diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/Driver.java b/src/main/java/net/minecraftforge/gradle/util/pack200/Driver.java
new file mode 100644
index 000000000..0e00ec246
--- /dev/null
+++ b/src/main/java/net/minecraftforge/gradle/util/pack200/Driver.java
@@ -0,0 +1,747 @@
+/*
+ * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package net.minecraftforge.gradle.util.pack200;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.text.MessageFormat;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/** Command line interface for Pack200.
+ */
+
+@SuppressWarnings({"removal"})
+class Driver {
+    private static final ResourceBundle RESOURCE =
+        ResourceBundle.getBundle("net.minecraftforge.gradle.util.pack200.DriverResource");
+    private static boolean suppressDeprecateMsg = false;
+
+    public static void main(String[] ava) throws IOException {
+        List av = new ArrayList<>(Arrays.asList(ava));
+
+        boolean doPack   = true;
+        boolean doUnpack = false;
+        boolean doRepack = false;
+        boolean doZip = true;
+        suppressDeprecateMsg = av.remove("-XDsuppress-tool-removal-message");
+        String logFile = null;
+        String verboseProp = Utils.DEBUG_VERBOSE;
+
+        {
+            // Non-standard, undocumented "--unpack" switch enables unpack mode.
+            String arg0 = av.isEmpty() ? "" : av.get(0);
+            switch (arg0) {
+                case "--pack":
+                av.remove(0);
+                    break;
+                case "--unpack":
+                av.remove(0);
+                doPack = false;
+                doUnpack = true;
+                    break;
+            }
+        }
+
+        if (!suppressDeprecateMsg) {
+            printDeprecateWarning(doPack, System.out);
+        }
+
+        // Collect engine properties here:
+        Map engProps = new HashMap<>();
+        engProps.put(verboseProp, System.getProperty(verboseProp));
+
+        String optionMap;
+        String[] propTable;
+        if (doPack) {
+            optionMap = PACK200_OPTION_MAP;
+            propTable = PACK200_PROPERTY_TO_OPTION;
+        } else {
+            optionMap = UNPACK200_OPTION_MAP;
+            propTable = UNPACK200_PROPERTY_TO_OPTION;
+        }
+
+        // Collect argument properties here:
+        Map avProps = new HashMap<>();
+        try {
+            for (;;) {
+                String state = parseCommandOptions(av, optionMap, avProps);
+                // Translate command line options to Pack200 properties:
+            eachOpt:
+                for (Iterator opti = avProps.keySet().iterator();
+                     opti.hasNext(); ) {
+                    String opt = opti.next();
+                    String prop = null;
+                    for (int i = 0; i < propTable.length; i += 2) {
+                        if (opt.equals(propTable[1+i])) {
+                            prop = propTable[0+i];
+                            break;
+                        }
+                    }
+                    if (prop != null) {
+                        String val = avProps.get(opt);
+                        opti.remove();  // remove opt from avProps
+                        if (!prop.endsWith(".")) {
+                            // Normal string or boolean.
+                            if (!(opt.equals("--verbose")
+                                  || opt.endsWith("="))) {
+                                // Normal boolean; convert to T/F.
+                                boolean flag = (val != null);
+                                if (opt.startsWith("--no-"))
+                                    flag = !flag;
+                                val = flag? "true": "false";
+                            }
+                            engProps.put(prop, val);
+                        } else if (prop.contains(".attribute.")) {
+                            for (String val1 : val.split("\0")) {
+                                String[] val2 = val1.split("=", 2);
+                                engProps.put(prop+val2[0], val2[1]);
+                            }
+                        } else {
+                            // Collection property: pack.pass.file.cli.NNN
+                            int idx = 1;
+                            for (String val1 : val.split("\0")) {
+                                String prop1;
+                                do {
+                                    prop1 = prop+"cli."+(idx++);
+                                } while (engProps.containsKey(prop1));
+                                engProps.put(prop1, val1);
+                            }
+                        }
+                    }
+                }
+
+                // See if there is any other action to take.
+                if ("--config-file=".equals(state)) {
+                    String propFile = av.remove(0);
+                    Properties fileProps = new Properties();
+                    try (InputStream propIn = new FileInputStream(propFile)) {
+                        fileProps.load(propIn);
+                    }
+                    if (engProps.get(verboseProp) != null)
+                        fileProps.list(System.out);
+                    for (Map.Entry me : fileProps.entrySet()) {
+                        engProps.put((String) me.getKey(), (String) me.getValue());
+                    }
+                } else if ("--version".equals(state)) {
+                        System.out.println(MessageFormat.format(RESOURCE.getString(DriverResource.VERSION),
+                                                                Driver.class.getName(), "1.31, 07/05/05"));
+                    return;
+                } else if ("--help".equals(state)) {
+                    printUsage(doPack, true, System.out);
+                    System.exit(0);
+                    return;
+                } else {
+                    break;
+                }
+            }
+        } catch (IllegalArgumentException ee) {
+                System.err.println(MessageFormat.format(RESOURCE.getString(DriverResource.BAD_ARGUMENT), ee));
+            printUsage(doPack, false, System.err);
+            System.exit(2);
+            return;
+        }
+
+        // Deal with remaining non-engine properties:
+        for (String opt : avProps.keySet()) {
+            String val = avProps.get(opt);
+            switch (opt) {
+                case "--repack":
+                    doRepack = true;
+                    break;
+                case "--no-gzip":
+                    doZip = (val == null);
+                    break;
+                case "--log-file=":
+                    logFile = val;
+                    break;
+                default:
+                    throw new InternalError(MessageFormat.format(
+                            RESOURCE.getString(DriverResource.BAD_OPTION),
+                            opt, avProps.get(opt)));
+            }
+        }
+
+        if (logFile != null && !logFile.equals("")) {
+            if (logFile.equals("-")) {
+                System.setErr(System.out);
+            } else {
+                OutputStream log = new FileOutputStream(logFile);
+                //log = new BufferedOutputStream(out);
+                System.setErr(new PrintStream(log));
+            }
+        }
+
+        boolean verbose = (engProps.get(verboseProp) != null);
+
+        String packfile = "";
+        if (!av.isEmpty())
+            packfile = av.remove(0);
+
+        String jarfile = "";
+        if (!av.isEmpty())
+            jarfile = av.remove(0);
+
+        String newfile = "";  // output JAR file if --repack
+        String bakfile = "";  // temporary backup of input JAR
+        String tmpfile = "";  // temporary file to be deleted
+        if (doRepack) {
+            // The first argument is the target JAR file.
+            // (Note:  *.pac is nonstandard, but may be necessary
+            // if a host OS truncates file extensions.)
+            if (packfile.toLowerCase().endsWith(".pack") ||
+                packfile.toLowerCase().endsWith(".pac") ||
+                packfile.toLowerCase().endsWith(".gz")) {
+                System.err.println(MessageFormat.format(
+                        RESOURCE.getString(DriverResource.BAD_REPACK_OUTPUT),
+                        packfile));
+                printUsage(doPack, false, System.err);
+                System.exit(2);
+            }
+            newfile = packfile;
+            // The optional second argument is the source JAR file.
+            if (jarfile.equals("")) {
+                // If only one file is given, it is the only JAR.
+                // It serves as both input and output.
+                jarfile = newfile;
+            }
+            tmpfile = createTempFile(newfile, ".pack").getPath();
+            packfile = tmpfile;
+            doZip = false;  // no need to zip the temporary file
+        }
+
+        if (!av.isEmpty()
+            // Accept jarfiles ending with .jar or .zip.
+            // Accept jarfile of "-" (stdout), but only if unpacking.
+            || !(jarfile.toLowerCase().endsWith(".jar")
+                 || jarfile.toLowerCase().endsWith(".zip")
+                 || (jarfile.equals("-") && !doPack))) {
+            printUsage(doPack, false, System.err);
+            System.exit(2);
+            return;
+        }
+
+        if (doRepack)
+            doPack = doUnpack = true;
+        else if (doPack)
+            doUnpack = false;
+
+        Pack200.Packer jpack = Pack200.newPacker();
+        Pack200.Unpacker junpack = Pack200.newUnpacker();
+
+        jpack.properties().putAll(engProps);
+        junpack.properties().putAll(engProps);
+        if (doRepack && newfile.equals(jarfile)) {
+            String zipc = getZipComment(jarfile);
+            if (verbose && zipc.length() > 0)
+                System.out.println(MessageFormat.format(RESOURCE.getString(DriverResource.DETECTED_ZIP_COMMENT), zipc));
+            if (zipc.indexOf(Utils.PACK_ZIP_ARCHIVE_MARKER_COMMENT) >= 0) {
+                    System.out.println(MessageFormat.format(RESOURCE.getString(DriverResource.SKIP_FOR_REPACKED), jarfile));
+                        doPack = false;
+                        doUnpack = false;
+                        doRepack = false;
+            }
+        }
+
+        try {
+
+            if (doPack) {
+                // Mode = Pack.
+                JarFile in = new JarFile(new File(jarfile));
+                OutputStream out;
+                // Packfile must be -, *.gz, *.pack, or *.pac.
+                if (packfile.equals("-")) {
+                    out = System.out;
+                    // Send warnings, etc., to stderr instead of stdout.
+                    System.setOut(System.err);
+                } else if (doZip) {
+                    if (!packfile.endsWith(".gz")) {
+                    System.err.println(MessageFormat.format(RESOURCE.getString(DriverResource.WRITE_PACK_FILE), packfile));
+                        printUsage(doPack, false, System.err);
+                        System.exit(2);
+                    }
+                    out = new FileOutputStream(packfile);
+                    out = new BufferedOutputStream(out);
+                    out = new GZIPOutputStream(out);
+                } else {
+                    if (!packfile.toLowerCase().endsWith(".pack") &&
+                            !packfile.toLowerCase().endsWith(".pac")) {
+                        System.err.println(MessageFormat.format(RESOURCE.getString(DriverResource.WRITE_PACKGZ_FILE),packfile));
+                        printUsage(doPack, false, System.err);
+                        System.exit(2);
+                    }
+                    out = new FileOutputStream(packfile);
+                    out = new BufferedOutputStream(out);
+                }
+                jpack.pack(in, out);
+                //in.close();  // p200 closes in but not out
+                out.close();
+            }
+
+            if (doRepack && newfile.equals(jarfile)) {
+                // If the source and destination are the same,
+                // we will move the input JAR aside while regenerating it.
+                // This allows us to restore it if something goes wrong.
+                File bakf = createTempFile(jarfile, ".bak");
+                // On Windows target must be deleted see 4017593
+                bakf.delete();
+                boolean okBackup = new File(jarfile).renameTo(bakf);
+                if (!okBackup) {
+                        throw new Error(MessageFormat.format(RESOURCE.getString(DriverResource.SKIP_FOR_MOVE_FAILED),bakfile));
+                } else {
+                    // Open jarfile recovery bracket.
+                    bakfile = bakf.getPath();
+                }
+            }
+
+            if (doUnpack) {
+                // Mode = Unpack.
+                InputStream in;
+                if (packfile.equals("-"))
+                    in = System.in;
+                else
+                    in = new FileInputStream(new File(packfile));
+                BufferedInputStream inBuf = new BufferedInputStream(in);
+                in = inBuf;
+                if (Utils.isGZIPMagic(Utils.readMagic(inBuf))) {
+                    in = new GZIPInputStream(in);
+                }
+                String outfile = newfile.equals("")? jarfile: newfile;
+                OutputStream fileOut;
+                if (outfile.equals("-"))
+                    fileOut = System.out;
+                else
+                    fileOut = new FileOutputStream(outfile);
+                fileOut = new BufferedOutputStream(fileOut);
+                try (JarOutputStream out = new JarOutputStream(fileOut)) {
+                    junpack.unpack(in, out);
+                    // p200 closes in but not out
+                }
+                // At this point, we have a good jarfile (or newfile, if -r)
+            }
+
+            if (!bakfile.equals("")) {
+                        // On success, abort jarfile recovery bracket.
+                        new File(bakfile).delete();
+                        bakfile = "";
+            }
+
+        } finally {
+            // Close jarfile recovery bracket.
+            if (!bakfile.equals("")) {
+                File jarFile = new File(jarfile);
+                jarFile.delete(); // Win32 requires this, see above
+                new File(bakfile).renameTo(jarFile);
+            }
+            // In all cases, delete temporary *.pack.
+            if (!tmpfile.equals(""))
+                new File(tmpfile).delete();
+        }
+    }
+
+    private static
+    File createTempFile(String basefile, String suffix) throws IOException {
+        File base = new File(basefile);
+        String prefix = base.getName();
+        if (prefix.length() < 3)  prefix += "tmp";
+
+        File where = (base.getParentFile() == null && suffix.equals(".bak"))
+                ? new File(".").getAbsoluteFile()
+                : base.getParentFile();
+
+        Path tmpfile = (where == null)
+                ? Files.createTempFile(prefix, suffix)
+                : Files.createTempFile(where.toPath(), prefix, suffix);
+
+        return tmpfile.toFile();
+    }
+
+    private static
+    void printDeprecateWarning(boolean doPack, PrintStream out) {
+        String prog = doPack ? "pack200" : "unpack200";
+        out.println(MessageFormat.format(RESOURCE.getString(DriverResource.DEPRECATED), prog));
+    }
+
+    private static
+    void printUsage(boolean doPack, boolean full, PrintStream out) {
+        String prog = doPack ? "pack200" : "unpack200";
+        String[] packUsage = (String[])RESOURCE.getObject(DriverResource.PACK_HELP);
+        String[] unpackUsage = (String[])RESOURCE.getObject(DriverResource.UNPACK_HELP);
+        String[] usage = doPack? packUsage: unpackUsage;
+        for (int i = 0; i < usage.length; i++) {
+            out.println(usage[i]);
+            if (!full) {
+            out.println(MessageFormat.format(RESOURCE.getString(DriverResource.MORE_INFO), prog));
+                break;
+            }
+        }
+        // Print a warning at the end
+        // The full help page is long, the beginning warning could be out of sight
+        if (full && !suppressDeprecateMsg) {
+            printDeprecateWarning(doPack, out);
+        }
+    }
+
+    private static
+        String getZipComment(String jarfile) throws IOException {
+        byte[] tail = new byte[1000];
+        long filelen = new File(jarfile).length();
+        if (filelen <= 0)  return "";
+        long skiplen = Math.max(0, filelen - tail.length);
+        try (InputStream in = new FileInputStream(new File(jarfile))) {
+            in.skip(skiplen);
+            in.read(tail);
+            for (int i = tail.length-4; i >= 0; i--) {
+                if (tail[i+0] == 'P' && tail[i+1] == 'K' &&
+                    tail[i+2] ==  5  && tail[i+3] ==  6) {
+                    // Skip sig4, disks4, entries4, clen4, coff4, cmt2
+                    i += 4+4+4+4+4+2;
+                    if (i < tail.length)
+                        return new String(tail, i, tail.length-i, "UTF8");
+                    return "";
+                }
+            }
+            return "";
+        }
+    }
+
+    private static final String PACK200_OPTION_MAP =
+        (""
+         +"--repack                 $ \n  -r +>- @--repack              $ \n"
+         +"--no-gzip                $ \n  -g +>- @--no-gzip             $ \n"
+         +"--strip-debug            $ \n  -G +>- @--strip-debug         $ \n"
+         +"--no-keep-file-order     $ \n  -O +>- @--no-keep-file-order  $ \n"
+         +"--segment-limit=      *> = \n  -S +>  @--segment-limit=      = \n"
+         +"--effort=             *> = \n  -E +>  @--effort=             = \n"
+         +"--deflate-hint=       *> = \n  -H +>  @--deflate-hint=       = \n"
+         +"--modification-time=  *> = \n  -m +>  @--modification-time=  = \n"
+         +"--pass-file=        *> &\0 \n  -P +>  @--pass-file=        &\0 \n"
+         +"--unknown-attribute=  *> = \n  -U +>  @--unknown-attribute=  = \n"
+         +"--class-attribute=  *> &\0 \n  -C +>  @--class-attribute=  &\0 \n"
+         +"--field-attribute=  *> &\0 \n  -F +>  @--field-attribute=  &\0 \n"
+         +"--method-attribute= *> &\0 \n  -M +>  @--method-attribute= &\0 \n"
+         +"--code-attribute=   *> &\0 \n  -D +>  @--code-attribute=   &\0 \n"
+         +"--config-file=      *>   . \n  -f +>  @--config-file=        . \n"
+
+         // Negative options as required by CLIP:
+         +"--no-strip-debug  !--strip-debug         \n"
+         +"--gzip            !--no-gzip             \n"
+         +"--keep-file-order !--no-keep-file-order  \n"
+
+         // Non-Standard Options
+         +"--verbose                $ \n  -v +>- @--verbose             $ \n"
+         +"--quiet        !--verbose  \n  -q +>- !--verbose               \n"
+         +"--log-file=           *> = \n  -l +>  @--log-file=           = \n"
+         //+"--java-option=      *> = \n  -J +>  @--java-option=        = \n"
+         +"--version                . \n  -V +>  @--version             . \n"
+         +"--help               . \n  -? +> @--help . \n  -h +> @--help . \n"
+
+         // Termination:
+         +"--           . \n"  // end option sequence here
+         +"-   +?    >- . \n"  // report error if -XXX present; else use stdout
+         );
+    // Note: Collection options use "\0" as a delimiter between arguments.
+
+    // For Java version of unpacker (used for testing only):
+    private static final String UNPACK200_OPTION_MAP =
+        (""
+         +"--deflate-hint=       *> = \n  -H +>  @--deflate-hint=       = \n"
+         +"--verbose                $ \n  -v +>- @--verbose             $ \n"
+         +"--quiet        !--verbose  \n  -q +>- !--verbose               \n"
+         +"--remove-pack-file       $ \n  -r +>- @--remove-pack-file    $ \n"
+         +"--log-file=           *> = \n  -l +>  @--log-file=           = \n"
+         +"--config-file=        *> . \n  -f +>  @--config-file=        . \n"
+
+         // Termination:
+         +"--           . \n"  // end option sequence here
+         +"-   +?    >- . \n"  // report error if -XXX present; else use stdin
+         +"--version                . \n  -V +>  @--version             . \n"
+         +"--help               . \n  -? +> @--help . \n  -h +> @--help . \n"
+         );
+
+    private static final String[] PACK200_PROPERTY_TO_OPTION = {
+        Pack200.Packer.SEGMENT_LIMIT, "--segment-limit=",
+        Pack200.Packer.KEEP_FILE_ORDER, "--no-keep-file-order",
+        Pack200.Packer.EFFORT, "--effort=",
+        Pack200.Packer.DEFLATE_HINT, "--deflate-hint=",
+        Pack200.Packer.MODIFICATION_TIME, "--modification-time=",
+        Pack200.Packer.PASS_FILE_PFX, "--pass-file=",
+        Pack200.Packer.UNKNOWN_ATTRIBUTE, "--unknown-attribute=",
+        Pack200.Packer.CLASS_ATTRIBUTE_PFX, "--class-attribute=",
+        Pack200.Packer.FIELD_ATTRIBUTE_PFX, "--field-attribute=",
+        Pack200.Packer.METHOD_ATTRIBUTE_PFX, "--method-attribute=",
+        Pack200.Packer.CODE_ATTRIBUTE_PFX, "--code-attribute=",
+        //Pack200.Packer.PROGRESS, "--progress=",
+        Utils.DEBUG_VERBOSE, "--verbose",
+        Utils.COM_PREFIX+"strip.debug", "--strip-debug",
+    };
+
+    private static final String[] UNPACK200_PROPERTY_TO_OPTION = {
+        Pack200.Unpacker.DEFLATE_HINT, "--deflate-hint=",
+        //Pack200.Unpacker.PROGRESS, "--progress=",
+        Utils.DEBUG_VERBOSE, "--verbose",
+        Utils.UNPACK_REMOVE_PACKFILE, "--remove-pack-file",
+    };
+
+    /*-*
+     * Remove a set of command-line options from args,
+     * storing them in the map in a canonicalized form.
+     * 

+ * The options string is a newline-separated series of + * option processing specifiers. + */ + private static + String parseCommandOptions(List args, + String options, + Map properties) { + //System.out.println(args+" // "+properties); + + String resultString = null; + + // Convert options string into optLines dictionary. + TreeMap optmap = new TreeMap<>(); + loadOptmap: + for (String optline : options.split("\n")) { + String[] words = optline.split("\\p{Space}+"); + if (words.length == 0) continue loadOptmap; + String opt = words[0]; + words[0] = ""; // initial word is not a spec + if (opt.length() == 0 && words.length >= 1) { + opt = words[1]; // initial "word" is empty due to leading ' ' + words[1] = ""; + } + if (opt.length() == 0) continue loadOptmap; + String[] prevWords = optmap.put(opt, words); + if (prevWords != null) + throw new RuntimeException(MessageFormat.format(RESOURCE.getString(DriverResource.DUPLICATE_OPTION), optline.trim())); + } + + // State machine for parsing a command line. + ListIterator argp = args.listIterator(); + ListIterator pbp = new ArrayList().listIterator(); + doArgs: + for (;;) { + // One trip through this loop per argument. + // Multiple trips per option only if several options per argument. + String arg; + if (pbp.hasPrevious()) { + arg = pbp.previous(); + pbp.remove(); + } else if (argp.hasNext()) { + arg = argp.next(); + } else { + // No more arguments at all. + break doArgs; + } + tryOpt: + for (int optlen = arg.length(); ; optlen--) { + // One time through this loop for each matching arg prefix. + String opt; + // Match some prefix of the argument to a key in optmap. + findOpt: + for (;;) { + opt = arg.substring(0, optlen); + if (optmap.containsKey(opt)) break findOpt; + if (optlen == 0) break tryOpt; + // Decide on a smaller prefix to search for. + SortedMap pfxmap = optmap.headMap(opt); + // pfxmap.lastKey is no shorter than any prefix in optmap. + int len = pfxmap.isEmpty() ? 0 : pfxmap.lastKey().length(); + optlen = Math.min(len, optlen - 1); + opt = arg.substring(0, optlen); + // (Note: We could cut opt down to its common prefix with + // pfxmap.lastKey, but that wouldn't save many cycles.) + } + opt = opt.intern(); + assert(arg.startsWith(opt)); + assert(opt.length() == optlen); + String val = arg.substring(optlen); // arg == opt+val + + // Execute the option processing specs for this opt. + // If no actions are taken, then look for a shorter prefix. + boolean didAction = false; + boolean isError = false; + + int pbpMark = pbp.nextIndex(); // in case of backtracking + String[] specs = optmap.get(opt); + eachSpec: + for (String spec : specs) { + if (spec.length() == 0) continue eachSpec; + if (spec.startsWith("#")) break eachSpec; + int sidx = 0; + char specop = spec.charAt(sidx++); + + // Deal with '+'/'*' prefixes (spec conditions). + boolean ok; + switch (specop) { + case '+': + // + means we want an non-empty val suffix. + ok = (val.length() != 0); + specop = spec.charAt(sidx++); + break; + case '*': + // * means we accept empty or non-empty + ok = true; + specop = spec.charAt(sidx++); + break; + default: + // No condition prefix means we require an exact + // match, as indicated by an empty val suffix. + ok = (val.length() == 0); + break; + } + if (!ok) continue eachSpec; + + String specarg = spec.substring(sidx); + switch (specop) { + case '.': // terminate the option sequence + resultString = (specarg.length() != 0)? specarg.intern(): opt; + break doArgs; + case '?': // abort the option sequence + resultString = (specarg.length() != 0)? specarg.intern(): arg; + isError = true; + break eachSpec; + case '@': // change the effective opt name + opt = specarg.intern(); + break; + case '>': // shift remaining arg val to next arg + pbp.add(specarg + val); // push a new argument + val = ""; + break; + case '!': // negation option + String negopt = (specarg.length() != 0)? specarg.intern(): opt; + properties.remove(negopt); + properties.put(negopt, null); // leave placeholder + didAction = true; + break; + case '$': // normal "boolean" option + String boolval; + if (specarg.length() != 0) { + // If there is a given spec token, store it. + boolval = specarg; + } else { + String old = properties.get(opt); + if (old == null || old.length() == 0) { + boolval = "1"; + } else { + // Increment any previous value as a numeral. + boolval = ""+(1+Integer.parseInt(old)); + } + } + properties.put(opt, boolval); + didAction = true; + break; + case '=': // "string" option + case '&': // "collection" option + // Read an option. + boolean append = (specop == '&'); + String strval; + if (pbp.hasPrevious()) { + strval = pbp.previous(); + pbp.remove(); + } else if (argp.hasNext()) { + strval = argp.next(); + } else { + resultString = arg + " ?"; + isError = true; + break eachSpec; + } + if (append) { + String old = properties.get(opt); + if (old != null) { + // Append new val to old with embedded delim. + String delim = specarg; + if (delim.length() == 0) delim = " "; + strval = old + specarg + strval; + } + } + properties.put(opt, strval); + didAction = true; + break; + default: + throw new RuntimeException(MessageFormat.format(RESOURCE.getString(DriverResource.BAD_SPEC),opt, spec)); + } + } + + // Done processing specs. + if (didAction && !isError) { + continue doArgs; + } + + // The specs should have done something, but did not. + while (pbp.nextIndex() > pbpMark) { + // Remove anything pushed during these specs. + pbp.previous(); + pbp.remove(); + } + + if (isError) { + throw new IllegalArgumentException(resultString); + } + + if (optlen == 0) { + // We cannot try a shorter matching option. + break tryOpt; + } + } + + // If we come here, there was no matching option. + // So, push back the argument, and return to caller. + pbp.add(arg); + break doArgs; + } + // Report number of arguments consumed. + args.subList(0, argp.nextIndex()).clear(); + // Report any unconsumed partial argument. + while (pbp.hasPrevious()) { + args.add(0, pbp.previous()); + } + //System.out.println(args+" // "+properties+" -> "+resultString); + return resultString; + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/DriverResource.java b/src/main/java/net/minecraftforge/gradle/util/pack200/DriverResource.java new file mode 100644 index 000000000..cc1a3dee8 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/DriverResource.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.minecraftforge.gradle.util.pack200; + +import java.util.ListResourceBundle; + +public class DriverResource extends ListResourceBundle { + + public static final String VERSION = "VERSION"; + public static final String BAD_ARGUMENT = "BAD_ARGUMENT"; + public static final String BAD_OPTION = "BAD_OPTION"; + public static final String BAD_REPACK_OUTPUT = "BAD_REPACK_OUTPUT"; + public static final String DETECTED_ZIP_COMMENT = "DETECTED_ZIP_COMMENT"; + public static final String SKIP_FOR_REPACKED = "SKIP_FOR_REPACKED"; + public static final String WRITE_PACK_FILE = "WRITE_PACK_FILE"; + public static final String WRITE_PACKGZ_FILE = "WRITE_PACKGZ_FILE"; + public static final String SKIP_FOR_MOVE_FAILED = "SKIP_FOR_MOVE_FAILED"; + public static final String PACK_HELP = "PACK_HELP"; + public static final String UNPACK_HELP = "UNPACK_HELP"; + public static final String MORE_INFO = "MORE_INFO"; + public static final String DUPLICATE_OPTION = "DUPLICATE_OPTION"; + public static final String BAD_SPEC = "BAD_SPEC"; + public static final String DEPRECATED = "DEPRECATED"; + + /* + * The following are the output of 'pack200' and 'unpack200' commands. + * Do not translate command arguments and words with a prefix of '-' or '--'. + */ + private static final Object[][] resource = { + {VERSION, "{0} version {1}"}, // parameter 0:class name;parameter 1: version value + {BAD_ARGUMENT, "Bad argument: {0}"}, + {BAD_OPTION, "Bad option: {0}={1}"}, // parameter 0:option name;parameter 1:option value + {BAD_REPACK_OUTPUT, "Bad --repack output: {0}"}, // parameter 0:filename + {DETECTED_ZIP_COMMENT, "Detected ZIP comment: {0}"}, // parameter 0:comment + {SKIP_FOR_REPACKED, "Skipping because already repacked: {0}"}, // parameter 0:filename + {WRITE_PACK_FILE, "To write a *.pack file, specify --no-gzip: {0}"}, // parameter 0:filename + {WRITE_PACKGZ_FILE, "To write a *.pack.gz file, specify --gzip: {0}"}, // parameter 0:filename + {SKIP_FOR_MOVE_FAILED, "Skipping unpack because move failed: {0}"}, // parameter 0:filename + {PACK_HELP, new String[] { + "Usage: pack200 [-opt... | --option=value]... x.pack[.gz] y.jar", + "", + "Packing Options", + " -r, --repack repack or normalize a jar, suitable for ", + " signing with jarsigner", + " -g, --no-gzip output a plain pack file, suitable to be", + " compressed with a file compression utility", + " --gzip (default) post compress the pack output", + " with gzip", + " -G, --strip-debug remove debugging attributes (SourceFile,", + " LineNumberTable, LocalVariableTable", + " and LocalVariableTypeTable) while packing", + " -O, --no-keep-file-order do not transmit file ordering information", + " --keep-file-order (default) preserve input file ordering", + " -S{N}, --segment-limit={N} limit segment sizes (default unlimited)", + " -E{N}, --effort={N} packing effort (default N=5)", + " -H{h}, --deflate-hint={h} transmit deflate hint: true, false,", + " or keep (default)", + " -m{V}, --modification-time={V} transmit modtimes: latest or keep (default)", + " -P{F}, --pass-file={F} transmit the given input element(s) unchanged", + " -U{a}, --unknown-attribute={a} unknown attribute action: error, strip,", + " or pass (default)", + " -C{N}={L}, --class-attribute={N}={L} (user-defined attribute)", + " -F{N}={L}, --field-attribute={N}={L} (user-defined attribute)", + " -M{N}={L}, --method-attribute={N}={L} (user-defined attribute)", + " -D{N}={L}, --code-attribute={N}={L} (user-defined attribute)", + " -f{F}, --config-file={F} read file F for Pack200.Packer properties", + " -v, --verbose increase program verbosity", + " -q, --quiet set verbosity to lowest level", + " -l{F}, --log-file={F} output to the given log file, ", + " or '-' for System.out", + " -?, -h, --help print this help message", + " -V, --version print program version", + " -J{X} pass option X to underlying Java VM", + "", + "Notes:", + " The -P, -C, -F, -M, and -D options accumulate.", + " Example attribute definition: -C SourceFile=RUH .", + " Config. file properties are defined by the Pack200 API.", + " For meaning of -S, -E, -H-, -m, -U values, see Pack200 API.", + " Layout definitions (like RUH) are defined by JSR 200.", + "", + "Repacking mode updates the JAR file with a pack/unpack cycle:", + " pack200 [-r|--repack] [-opt | --option=value]... [repackedy.jar] y.jar\n", + "", + "Exit Status:", + " 0 if successful, >0 if an error occurred" + } + }, + {UNPACK_HELP, new String[] { + "Usage: unpack200 [-opt... | --option=value]... x.pack[.gz] y.jar\n", + "", + "Unpacking Options", + " -H{h}, --deflate-hint={h} override transmitted deflate hint:", + " true, false, or keep (default)", + " -r, --remove-pack-file remove input file after unpacking", + " -v, --verbose increase program verbosity", + " -q, --quiet set verbosity to lowest level", + " -l{F}, --log-file={F} output to the given log file, or", + " '-' for System.out", + " -?, -h, --help print this help message", + " -V, --version print program version", + " -J{X} pass option X to underlying Java VM" + } + }, + {MORE_INFO, "(For more information, run {0} --help .)"}, // parameter 0:command name + {DUPLICATE_OPTION, "duplicate option: {0}"}, // parameter 0:option + {BAD_SPEC, "bad spec for {0}: {1}"}, // parameter 0:option;parameter 1:specifier + {DEPRECATED, "\nWarning: The {0} tool is deprecated, and is planned for removal in a future JDK release.\n"} // parameter 0:command name + }; + + protected Object[][] getContents() { + return resource; + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/DriverResource_ja.java b/src/main/java/net/minecraftforge/gradle/util/pack200/DriverResource_ja.java new file mode 100644 index 000000000..66ea93b55 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/DriverResource_ja.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.minecraftforge.gradle.util.pack200; + +import java.util.ListResourceBundle; + +public class DriverResource_ja extends ListResourceBundle { + + public static final String VERSION = "VERSION"; + public static final String BAD_ARGUMENT = "BAD_ARGUMENT"; + public static final String BAD_OPTION = "BAD_OPTION"; + public static final String BAD_REPACK_OUTPUT = "BAD_REPACK_OUTPUT"; + public static final String DETECTED_ZIP_COMMENT = "DETECTED_ZIP_COMMENT"; + public static final String SKIP_FOR_REPACKED = "SKIP_FOR_REPACKED"; + public static final String WRITE_PACK_FILE = "WRITE_PACK_FILE"; + public static final String WRITE_PACKGZ_FILE = "WRITE_PACKGZ_FILE"; + public static final String SKIP_FOR_MOVE_FAILED = "SKIP_FOR_MOVE_FAILED"; + public static final String PACK_HELP = "PACK_HELP"; + public static final String UNPACK_HELP = "UNPACK_HELP"; + public static final String MORE_INFO = "MORE_INFO"; + public static final String DUPLICATE_OPTION = "DUPLICATE_OPTION"; + public static final String BAD_SPEC = "BAD_SPEC"; + public static final String DEPRECATED = "DEPRECATED"; + + /* + * The following are the output of 'pack200' and 'unpack200' commands. + * Do not translate command arguments and words with a prefix of '-' or '--'. + */ + private static final Object[][] resource = { + {VERSION, "{0}\u30D0\u30FC\u30B8\u30E7\u30F3{1}"}, // parameter 0:class name;parameter 1: version value + {BAD_ARGUMENT, "\u7121\u52B9\u306A\u5F15\u6570: {0}"}, + {BAD_OPTION, "\u7121\u52B9\u306A\u30AA\u30D7\u30B7\u30E7\u30F3: {0}={1}"}, // parameter 0:option name;parameter 1:option value + {BAD_REPACK_OUTPUT, "\u7121\u52B9\u306A--repack\u51FA\u529B: {0}"}, // parameter 0:filename + {DETECTED_ZIP_COMMENT, "\u691C\u51FA\u3055\u308C\u305FZIP\u30B3\u30E1\u30F3\u30C8: {0}"}, // parameter 0:comment + {SKIP_FOR_REPACKED, "\u3059\u3067\u306B\u518D\u5727\u7E2E\u3055\u308C\u3066\u3044\u308B\u305F\u3081\u30B9\u30AD\u30C3\u30D7\u3057\u3066\u3044\u307E\u3059: {0}"}, // parameter 0:filename + {WRITE_PACK_FILE, "*.pack\u30D5\u30A1\u30A4\u30EB\u3092\u66F8\u304D\u8FBC\u3080\u306B\u306F\u3001--no-gzip\u3092\u6307\u5B9A\u3057\u307E\u3059: {0}"}, // parameter 0:filename + {WRITE_PACKGZ_FILE, "*.pack.gz\u30D5\u30A1\u30A4\u30EB\u3092\u66F8\u304D\u8FBC\u3080\u306B\u306F\u3001--gzip\u3092\u6307\u5B9A\u3057\u307E\u3059: {0}"}, // parameter 0:filename + {SKIP_FOR_MOVE_FAILED, "\u79FB\u52D5\u304C\u5931\u6557\u3057\u305F\u305F\u3081\u89E3\u51CD\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u3066\u3044\u307E\u3059: {0}"}, // parameter 0:filename + {PACK_HELP, new String[] { + "\u4F7F\u7528\u65B9\u6CD5: pack200 [-opt... | --option=value]... x.pack[.gz] y.jar", + "", + "\u5727\u7E2E\u30AA\u30D7\u30B7\u30E7\u30F3", + " -r\u3001--repack jar\u3092\u518D\u5727\u7E2E\u307E\u305F\u306F\u6B63\u898F\u5316\u3059\u308B\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u3001", + " jarsigner\u306B\u3088\u308B\u7F72\u540D\u306B\u9069\u3057\u307E\u3059", + " -g\u3001--no-gzip \u30D7\u30EC\u30FC\u30F3\u306Apack\u30D5\u30A1\u30A4\u30EB\u3092\u51FA\u529B\u3059\u308B\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u3001", + " \u30D5\u30A1\u30A4\u30EB\u5727\u7E2E\u30E6\u30FC\u30C6\u30A3\u30EA\u30C6\u30A3\u306B\u3088\u308B\u5727\u7E2E\u306B\u9069\u3057\u307E\u3059", + " --gzip (\u30C7\u30D5\u30A9\u30EB\u30C8) pack\u51FA\u529B\u3092\u5F8C\u51E6\u7406\u3067\u5727\u7E2E\u3057\u307E\u3059", + " (gzip\u3092\u4F7F\u7528)", + " -G\u3001--strip-debug \u5727\u7E2E\u4E2D\u306B\u30C7\u30D0\u30C3\u30B0\u5C5E\u6027(SourceFile\u3001", + " LineNumberTable\u3001LocalVariableTable", + " \u3001LocalVariableTypeTable)\u3092\u524A\u9664\u3057\u307E\u3059", + " -O\u3001--no-keep-file-order \u30D5\u30A1\u30A4\u30EB\u306E\u9806\u5E8F\u4ED8\u3051\u60C5\u5831\u3092\u8EE2\u9001\u3057\u307E\u305B\u3093", + " --keep-file-order (\u30C7\u30D5\u30A9\u30EB\u30C8)\u5165\u529B\u30D5\u30A1\u30A4\u30EB\u306E\u9806\u5E8F\u4ED8\u3051\u3092\u4FDD\u6301\u3057\u307E\u3059", + " -S{N}\u3001--segment-limit={N} \u30BB\u30B0\u30E1\u30F3\u30C8\u30FB\u30B5\u30A4\u30BA\u3092\u5236\u9650\u3057\u307E\u3059(\u30C7\u30D5\u30A9\u30EB\u30C8\u306F\u7121\u5236\u9650)", + " -E{N}\u3001--effort={N} \u5727\u7E2E\u306E\u8A66\u884C(\u30C7\u30D5\u30A9\u30EB\u30C8N=5)", + " -H{h}\u3001--deflate-hint={h} \u30C7\u30D5\u30EC\u30FC\u30C8\u30FB\u30D2\u30F3\u30C8\u3092\u8EE2\u9001\u3057\u307E\u3059: true\u3001false", + " \u307E\u305F\u306Fkeep(\u30C7\u30D5\u30A9\u30EB\u30C8)", + " -m{V}\u3001--modification-time={V} \u5909\u66F4\u6642\u9593\u3092\u8EE2\u9001\u3057\u307E\u3059: latest\u307E\u305F\u306Fkeep(\u30C7\u30D5\u30A9\u30EB\u30C8)", + " -P{F}\u3001--pass-file={F} \u6307\u5B9A\u3055\u308C\u305F\u5165\u529B\u8981\u7D20\u3092\u305D\u306E\u307E\u307E\u8EE2\u9001\u3057\u307E\u3059", + " -U{a}\u3001--unknown-attribute={a} \u4E0D\u660E\u306E\u5C5E\u6027\u30A2\u30AF\u30B7\u30E7\u30F3: error\u3001strip", + " \u307E\u305F\u306Fpass(\u30C7\u30D5\u30A9\u30EB\u30C8)", + " -C{N}={L}\u3001--class-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", + " -F{N}={L}\u3001--field-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", + " -M{N}={L}\u3001--method-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", + " -D{N}={L}\u3001--code-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", + " -f{F}\u3001--config-file={F} Pack200.Packer\u30D7\u30ED\u30D1\u30C6\u30A3\u306B\u30D5\u30A1\u30A4\u30EBF\u3092\u8AAD\u307F\u8FBC\u307F\u307E\u3059", + " -v\u3001--verbose \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u5197\u9577\u6027\u3092\u9AD8\u3081\u307E\u3059", + " -q\u3001--quiet \u5197\u9577\u6027\u3092\u6700\u4F4E\u30EC\u30D9\u30EB\u306B\u8A2D\u5B9A\u3057\u307E\u3059", + " -l{F}\u3001--log-file={F} \u6307\u5B9A\u306E\u30ED\u30B0\u30FB\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306FSystem.out ", + " ('-'\u306E\u5834\u5408)\u306B\u51FA\u529B\u3057\u307E\u3059", + " -?\u3001-h\u3001--help \u3053\u306E\u30D8\u30EB\u30D7\u30FB\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u51FA\u529B\u3057\u307E\u3059", + " -V\u3001--version \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u51FA\u529B\u3057\u307E\u3059", + " -J{X} \u30AA\u30D7\u30B7\u30E7\u30F3X\u3092\u57FA\u790E\u3068\u306A\u308BJava VM\u306B\u6E21\u3057\u307E\u3059", + "", + "\u6CE8:", + " -P\u3001-C\u3001-F\u3001-M\u304A\u3088\u3073-D\u30AA\u30D7\u30B7\u30E7\u30F3\u306F\u7D2F\u7A4D\u3055\u308C\u307E\u3059\u3002", + " \u5C5E\u6027\u5B9A\u7FA9\u306E\u4F8B: -C SourceFile=RUH .", + " Config.\u30D5\u30A1\u30A4\u30EB\u30FB\u30D7\u30ED\u30D1\u30C6\u30A3\u306F\u3001Pack200 API\u306B\u3088\u3063\u3066\u5B9A\u7FA9\u3055\u308C\u307E\u3059\u3002", + " -S\u3001-E\u3001-H\u3001-m\u3001-U\u306E\u5024\u306E\u610F\u5473\u306F\u3001Pack200 API\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002", + " \u30EC\u30A4\u30A2\u30A6\u30C8\u5B9A\u7FA9(RUH\u306A\u3069)\u306FJSR 200\u306B\u3088\u3063\u3066\u5B9A\u7FA9\u3055\u308C\u307E\u3059\u3002", + "", + "\u518D\u5727\u7E2E\u30E2\u30FC\u30C9\u3067\u306F\u3001JAR\u30D5\u30A1\u30A4\u30EB\u304C\u5727\u7E2E/\u89E3\u51CD\u30B5\u30A4\u30AF\u30EB\u3067\u66F4\u65B0\u3055\u308C\u307E\u3059:", + " pack200 [-r|--repack] [-opt | --option=value]... [repackedy.jar] y.jar\n", + "", + "\u7D42\u4E86\u30B9\u30C6\u30FC\u30BF\u30B9:", + " 0 (\u6210\u529F\u3057\u305F\u5834\u5408)\u3001>0 (\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u305F\u5834\u5408)" + } + }, + {UNPACK_HELP, new String[] { + "\u4F7F\u7528\u65B9\u6CD5: unpack200 [-opt... | --option=value]... x.pack[.gz] y.jar\n", + "", + "\u89E3\u51CD\u30AA\u30D7\u30B7\u30E7\u30F3", + " -H{h}\u3001--deflate-hint={h} \u8EE2\u9001\u3055\u308C\u305F\u30C7\u30D5\u30EC\u30FC\u30C8\u30FB\u30D2\u30F3\u30C8\u3092\u30AA\u30FC\u30D0\u30FC\u30E9\u30A4\u30C9\u3057\u307E\u3059:", + " true\u3001false\u307E\u305F\u306Fkeep(\u30C7\u30D5\u30A9\u30EB\u30C8)", + " -r\u3001--remove-pack-file \u89E3\u51CD\u5F8C\u306B\u5165\u529B\u30D5\u30A1\u30A4\u30EB\u3092\u524A\u9664\u3057\u307E\u3059", + " -v\u3001--verbose \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u5197\u9577\u6027\u3092\u9AD8\u3081\u307E\u3059", + " -q\u3001--quiet \u5197\u9577\u6027\u3092\u6700\u4F4E\u30EC\u30D9\u30EB\u306B\u8A2D\u5B9A\u3057\u307E\u3059", + " -l{F}\u3001--log-file={F} \u6307\u5B9A\u306E\u30ED\u30B0\u30FB\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306F", + " System.out ('-'\u306E\u5834\u5408)\u306B\u51FA\u529B\u3057\u307E\u3059", + " -?\u3001-h\u3001--help \u3053\u306E\u30D8\u30EB\u30D7\u30FB\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u51FA\u529B\u3057\u307E\u3059", + " -V\u3001--version \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u51FA\u529B\u3057\u307E\u3059", + " -J{X} \u30AA\u30D7\u30B7\u30E7\u30F3X\u3092\u57FA\u790E\u3068\u306A\u308BJava VM\u306B\u6E21\u3057\u307E\u3059" + } + }, + {MORE_INFO, "(\u8A73\u7D30\u306F\u3001{0} --help\u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002)"}, // parameter 0:command name + {DUPLICATE_OPTION, "\u91CD\u8907\u30AA\u30D7\u30B7\u30E7\u30F3: {0}"}, // parameter 0:option + {BAD_SPEC, "{0}\u306E\u7121\u52B9\u306A\u4ED5\u69D8: {1}"}, // parameter 0:option;parameter 1:specifier + {DEPRECATED, "\n\u8B66\u544A: {0}\u30C4\u30FC\u30EB\u306F\u975E\u63A8\u5968\u3067\u3042\u308A\u3001\u4ECA\u5F8C\u306EJDK\u30EA\u30EA\u30FC\u30B9\u3067\u524A\u9664\u3055\u308C\u308B\u4E88\u5B9A\u3067\u3059\u3002\n"} // parameter 0:command name + }; + + protected Object[][] getContents() { + return resource; + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/DriverResource_zh_CN.java b/src/main/java/net/minecraftforge/gradle/util/pack200/DriverResource_zh_CN.java new file mode 100644 index 000000000..83a2c9e75 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/DriverResource_zh_CN.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.minecraftforge.gradle.util.pack200; + +import java.util.ListResourceBundle; + +public class DriverResource_zh_CN extends ListResourceBundle { + + public static final String VERSION = "VERSION"; + public static final String BAD_ARGUMENT = "BAD_ARGUMENT"; + public static final String BAD_OPTION = "BAD_OPTION"; + public static final String BAD_REPACK_OUTPUT = "BAD_REPACK_OUTPUT"; + public static final String DETECTED_ZIP_COMMENT = "DETECTED_ZIP_COMMENT"; + public static final String SKIP_FOR_REPACKED = "SKIP_FOR_REPACKED"; + public static final String WRITE_PACK_FILE = "WRITE_PACK_FILE"; + public static final String WRITE_PACKGZ_FILE = "WRITE_PACKGZ_FILE"; + public static final String SKIP_FOR_MOVE_FAILED = "SKIP_FOR_MOVE_FAILED"; + public static final String PACK_HELP = "PACK_HELP"; + public static final String UNPACK_HELP = "UNPACK_HELP"; + public static final String MORE_INFO = "MORE_INFO"; + public static final String DUPLICATE_OPTION = "DUPLICATE_OPTION"; + public static final String BAD_SPEC = "BAD_SPEC"; + public static final String DEPRECATED = "DEPRECATED"; + + /* + * The following are the output of 'pack200' and 'unpack200' commands. + * Do not translate command arguments and words with a prefix of '-' or '--'. + */ + private static final Object[][] resource = { + {VERSION, "{0}\u7248\u672C {1}"}, // parameter 0:class name;parameter 1: version value + {BAD_ARGUMENT, "\u9519\u8BEF\u53C2\u6570: {0}"}, + {BAD_OPTION, "\u9519\u8BEF\u9009\u9879: {0}={1}"}, // parameter 0:option name;parameter 1:option value + {BAD_REPACK_OUTPUT, "--repack \u8F93\u51FA\u9519\u8BEF: {0}"}, // parameter 0:filename + {DETECTED_ZIP_COMMENT, "\u68C0\u6D4B\u5230 ZIP \u6CE8\u91CA: {0}"}, // parameter 0:comment + {SKIP_FOR_REPACKED, "\u7531\u4E8E\u5DF2\u91CD\u65B0\u6253\u5305\u800C\u8DF3\u8FC7: {0}"}, // parameter 0:filename + {WRITE_PACK_FILE, "\u8981\u5199\u5165 *.pack \u6587\u4EF6, \u8BF7\u6307\u5B9A --no-gzip: {0}"}, // parameter 0:filename + {WRITE_PACKGZ_FILE, "\u8981\u5199\u5165 *.pack.gz \u6587\u4EF6, \u8BF7\u6307\u5B9A --gzip: {0}"}, // parameter 0:filename + {SKIP_FOR_MOVE_FAILED, "\u7531\u4E8E\u79FB\u52A8\u5931\u8D25\u800C\u8DF3\u8FC7\u89E3\u5305: {0}"}, // parameter 0:filename + {PACK_HELP, new String[] { + "\u7528\u6CD5: pack200 [-opt... | --option=value]... x.pack[.gz] y.jar", + "", + "\u6253\u5305\u9009\u9879", + " -r, --repack \u518D\u6B21\u6253\u5305\u6216\u89C4\u8303\u5316 jar, \u9002\u5408\u4E8E ", + " \u4F7F\u7528 jarsigner \u8FDB\u884C\u7B7E\u540D", + " -g, --no-gzip \u8F93\u51FA\u65E0\u683C\u5F0F\u7684\u5305\u6587\u4EF6, \u9002\u5408\u4E8E", + " \u4F7F\u7528\u6587\u4EF6\u538B\u7F29\u5B9E\u7528\u7A0B\u5E8F\u8FDB\u884C\u538B\u7F29", + " --gzip (\u9ED8\u8BA4\u503C) \u4F7F\u7528 gzip \u5BF9\u5305\u8F93\u51FA\u8FDB\u884C", + " \u538B\u7F29\u540E\u5904\u7406", + " -G, --strip-debug \u6253\u5305\u65F6\u5220\u9664\u8C03\u8BD5\u5C5E\u6027 (SourceFile,", + " LineNumberTable, LocalVariableTable", + " \u548C LocalVariableTypeTable)", + " -O, --no-keep-file-order \u4E0D\u4F20\u8F93\u6587\u4EF6\u6392\u5E8F\u4FE1\u606F", + " --keep-file-order (\u9ED8\u8BA4\u503C) \u4FDD\u7559\u8F93\u5165\u6587\u4EF6\u6392\u5E8F", + " -S{N}, --segment-limit={N} \u9650\u5236\u6BB5\u5927\u5C0F (\u9ED8\u8BA4\u4E3A\u65E0\u9650\u5236)", + " -E{N}, --effort={N} \u6253\u5305\u6548\u679C (\u9ED8\u8BA4\u503C N=5)", + " -H{h}, --deflate-hint={h} \u4F20\u8F93\u538B\u7F29\u63D0\u793A: true, false", + " \u6216 keep (\u9ED8\u8BA4\u503C)", + " -m{V}, --modification-time={V} \u4F20\u8F93 modtimes: latest \u6216 keep (\u9ED8\u8BA4\u503C)", + " -P{F}, --pass-file={F} \u4F20\u8F93\u672A\u66F4\u6539\u7684\u7ED9\u5B9A\u8F93\u5165\u5143\u7D20", + " -U{a}, --unknown-attribute={a} \u672A\u77E5\u5C5E\u6027\u64CD\u4F5C: error, strip", + " \u6216 pass (\u9ED8\u8BA4\u503C)", + " -C{N}={L}, --class-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", + " -F{N}={L}, --field-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", + " -M{N}={L}, --method-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", + " -D{N}={L}, --code-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", + " -f{F}, --config-file={F} \u8BFB\u53D6\u6587\u4EF6 F \u7684 Pack200.Packer \u5C5E\u6027", + " -v, --verbose \u63D0\u9AD8\u7A0B\u5E8F\u8BE6\u7EC6\u7A0B\u5EA6", + " -q, --quiet \u5C06\u8BE6\u7EC6\u7A0B\u5EA6\u8BBE\u7F6E\u4E3A\u6700\u4F4E\u7EA7\u522B", + " -l{F}, --log-file={F} \u8F93\u51FA\u5230\u7ED9\u5B9A\u65E5\u5FD7\u6587\u4EF6, ", + " \u6216\u5BF9\u4E8E System.out \u6307\u5B9A '-'", + " -?, -h, --help \u8F93\u51FA\u6B64\u5E2E\u52A9\u6D88\u606F", + " -V, --version \u8F93\u51FA\u7A0B\u5E8F\u7248\u672C", + " -J{X} \u5C06\u9009\u9879 X \u4F20\u9012\u7ED9\u57FA\u7840 Java VM", + "", + "\u6CE8:", + " -P, -C, -F, -M \u548C -D \u9009\u9879\u7D2F\u8BA1\u3002", + " \u793A\u4F8B\u5C5E\u6027\u5B9A\u4E49: -C SourceFile=RUH\u3002", + " Config. \u6587\u4EF6\u5C5E\u6027\u7531 Pack200 API \u5B9A\u4E49\u3002", + " \u6709\u5173 -S, -E, -H-, -m, -U \u503C\u7684\u542B\u4E49, \u8BF7\u53C2\u9605 Pack200 API\u3002", + " \u5E03\u5C40\u5B9A\u4E49 (\u4F8B\u5982 RUH) \u7531 JSR 200 \u5B9A\u4E49\u3002", + "", + "\u91CD\u65B0\u6253\u5305\u6A21\u5F0F\u901A\u8FC7\u6253\u5305/\u89E3\u5305\u5468\u671F\u66F4\u65B0 JAR \u6587\u4EF6:", + " pack200 [-r|--repack] [-opt | --option=value]... [repackedy.jar] y.jar\n", + "", + "\u9000\u51FA\u72B6\u6001:", + " \u5982\u679C\u6210\u529F\u5219\u4E3A 0; \u5982\u679C\u51FA\u73B0\u9519\u8BEF, \u5219\u4E3A\u5927\u4E8E 0 \u7684\u503C" + } + }, + {UNPACK_HELP, new String[] { + "\u7528\u6CD5: unpack200 [-opt... | --option=value]... x.pack[.gz] y.jar\n", + "", + "\u89E3\u5305\u9009\u9879", + " -H{h}, --deflate-hint={h} \u8986\u76D6\u5DF2\u4F20\u8F93\u7684\u538B\u7F29\u63D0\u793A:", + " true, false \u6216 keep (\u9ED8\u8BA4\u503C)", + " -r, --remove-pack-file \u89E3\u5305\u4E4B\u540E\u5220\u9664\u8F93\u5165\u6587\u4EF6", + " -v, --verbose \u63D0\u9AD8\u7A0B\u5E8F\u8BE6\u7EC6\u7A0B\u5EA6", + " -q, --quiet \u5C06\u8BE6\u7EC6\u7A0B\u5EA6\u8BBE\u7F6E\u4E3A\u6700\u4F4E\u7EA7\u522B", + " -l{F}, --log-file={F} \u8F93\u51FA\u5230\u7ED9\u5B9A\u65E5\u5FD7\u6587\u4EF6, \u6216", + " \u5BF9\u4E8E System.out \u6307\u5B9A '-'", + " -?, -h, --help \u8F93\u51FA\u6B64\u5E2E\u52A9\u6D88\u606F", + " -V, --version \u8F93\u51FA\u7A0B\u5E8F\u7248\u672C", + " -J{X} \u5C06\u9009\u9879 X \u4F20\u9012\u7ED9\u57FA\u7840 Java VM" + } + }, + {MORE_INFO, "(\u6709\u5173\u8BE6\u7EC6\u4FE1\u606F, \u8BF7\u8FD0\u884C {0} --help\u3002)"}, // parameter 0:command name + {DUPLICATE_OPTION, "\u91CD\u590D\u7684\u9009\u9879: {0}"}, // parameter 0:option + {BAD_SPEC, "{0}\u7684\u89C4\u8303\u9519\u8BEF: {1}"}, // parameter 0:option;parameter 1:specifier + {DEPRECATED, "\n\u8B66\u544A\uFF1A{0} \u5DE5\u5177\u5DF2\u8FC7\u65F6\uFF0C\u8BA1\u5212\u5728\u672A\u6765\u7684 JDK \u53D1\u884C\u7248\u4E2D\u5220\u9664\u3002\n"} // parameter 0:command name + }; + + protected Object[][] getContents() { + return resource; + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/FixedList.java b/src/main/java/net/minecraftforge/gradle/util/pack200/FixedList.java new file mode 100644 index 000000000..c79e8a95f --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/FixedList.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.minecraftforge.gradle.util.pack200; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/* + * @author ksrini + */ + +/* + * This class provides an ArrayList implementation which has a fixed size, + * thus all the operations which modifies the size have been rendered + * inoperative. This essentially allows us to use generified array + * lists in lieu of arrays. + */ +final class FixedList implements List { + + private final ArrayList flist; + + protected FixedList(int capacity) { + flist = new ArrayList<>(capacity); + // initialize the list to null + for (int i = 0 ; i < capacity ; i++) { + flist.add(null); + } + } + @Override + public int size() { + return flist.size(); + } + + @Override + public boolean isEmpty() { + return flist.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return flist.contains(o); + } + + @Override + public Iterator iterator() { + return flist.iterator(); + } + + @Override + public Object[] toArray() { + return flist.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return flist.toArray(a); + } + + @Override + public boolean add(E e) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public boolean remove(Object o) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public boolean containsAll(Collection c) { + return flist.containsAll(c); + } + + @Override + public boolean addAll(Collection c) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public boolean addAll(int index, Collection c) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public boolean removeAll(Collection c) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public boolean retainAll(Collection c) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public void clear() throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public E get(int index) { + return flist.get(index); + } + + @Override + public E set(int index, E element) { + return flist.set(index, element); + } + + @Override + public void add(int index, E element) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public E remove(int index) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public int indexOf(Object o) { + return flist.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return flist.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return flist.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return flist.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return flist.subList(fromIndex, toIndex); + } + + @Override + public String toString() { + return "FixedList{" + "plist=" + flist + '}'; + } +} + diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/Fixups.java b/src/main/java/net/minecraftforge/gradle/util/pack200/Fixups.java new file mode 100644 index 000000000..95ca3cc90 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/Fixups.java @@ -0,0 +1,570 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import net.minecraftforge.gradle.util.pack200.ConstantPool.Entry; +import java.util.AbstractCollection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; + +/** + * Collection of relocatable constant pool references. + * It operates with respect to a particular byte array, + * and stores some of its state in the bytes themselves. + *

+ * As a Collection, it can be iterated over, but it is not a List, + * since it does not natively support indexed access. + *

+ * + * @author John Rose + */ +final class Fixups extends AbstractCollection { + byte[] bytes; // the subject of the relocations + int head; // desc locating first reloc + int tail; // desc locating last reloc + int size; // number of relocations + Entry[] entries; // [0..size-1] relocations + int[] bigDescs; // descs which cannot be stored in the bytes + + // A "desc" (descriptor) is a bit-encoded pair of a location + // and format. Every fixup occurs at a "desc". Until final + // patching, bytes addressed by descs may also be used to + // link this data structure together. If the bytes are missing, + // or if the "desc" is too large to encode in the bytes, + // it is kept in the bigDescs array. + + Fixups(byte[] bytes) { + this.bytes = bytes; + entries = new Entry[3]; + bigDescs = noBigDescs; + } + Fixups() { + // If there are no bytes, all descs are kept in bigDescs. + this((byte[])null); + } + Fixups(byte[] bytes, Collection fixups) { + this(bytes); + addAll(fixups); + } + Fixups(Collection fixups) { + this((byte[])null); + addAll(fixups); + } + + private static final int MINBIGSIZE = 1; + // cleverly share empty bigDescs: + private static final int[] noBigDescs = {MINBIGSIZE}; + + @Override + public int size() { + return size; + } + + public void trimToSize() { + if (size != entries.length) { + Entry[] oldEntries = entries; + entries = new Entry[size]; + System.arraycopy(oldEntries, 0, entries, 0, size); + } + int bigSize = bigDescs[BIGSIZE]; + if (bigSize == MINBIGSIZE) { + bigDescs = noBigDescs; + } else if (bigSize != bigDescs.length) { + int[] oldBigDescs = bigDescs; + bigDescs = new int[bigSize]; + System.arraycopy(oldBigDescs, 0, bigDescs, 0, bigSize); + } + } + + public void visitRefs(Collection refs) { + for (int i = 0; i < size; i++) { + refs.add(entries[i]); + } + } + + @Override + public void clear() { + if (bytes != null) { + // Clean the bytes: + for (Fixup fx : this) { + //System.out.println("clean "+fx); + storeIndex(fx.location(), fx.format(), 0); + } + } + size = 0; + if (bigDescs != noBigDescs) + bigDescs[BIGSIZE] = MINBIGSIZE; + // do not trim to size, however + } + + public byte[] getBytes() { + return bytes; + } + + public void setBytes(byte[] newBytes) { + if (bytes == newBytes) return; + ArrayList old = null; + assert((old = new ArrayList<>(this)) != null); + if (bytes == null || newBytes == null) { + // One or the other representations is deficient. + // Construct a checkpoint. + ArrayList save = new ArrayList<>(this); + clear(); + bytes = newBytes; + addAll(save); + } else { + // assume newBytes is some sort of bitwise copy of the old bytes + bytes = newBytes; + } + assert(old.equals(new ArrayList<>(this))); + } + + private static final int LOC_SHIFT = 1; + private static final int FMT_MASK = 0x1; + private static final byte UNUSED_BYTE = 0; + private static final byte OVERFLOW_BYTE = -1; + // fill pointer of bigDescs array is in element [0] + private static final int BIGSIZE = 0; + + // Format values: + private static final int U2_FORMAT = 0; + private static final int U1_FORMAT = 1; + + // Special values for the static methods. + private static final int SPECIAL_LOC = 0; + private static final int SPECIAL_FMT = U2_FORMAT; + + static int fmtLen(int fmt) { return 1+(fmt-U1_FORMAT)/(U2_FORMAT-U1_FORMAT); } + static int descLoc(int desc) { return desc >>> LOC_SHIFT; } + static int descFmt(int desc) { return desc & FMT_MASK; } + static int descEnd(int desc) { return descLoc(desc) + fmtLen(descFmt(desc)); } + static int makeDesc(int loc, int fmt) { + int desc = (loc << LOC_SHIFT) | fmt; + assert(descLoc(desc) == loc); + assert(descFmt(desc) == fmt); + return desc; + } + int fetchDesc(int loc, int fmt) { + byte b1 = bytes[loc]; + assert(b1 != OVERFLOW_BYTE); + int value; + if (fmt == U2_FORMAT) { + byte b2 = bytes[loc+1]; + value = ((b1 & 0xFF) << 8) + (b2 & 0xFF); + } else { + value = (b1 & 0xFF); + } + // Stored loc field is difference between its own loc and next loc. + return value + (loc << LOC_SHIFT); + } + boolean storeDesc(int loc, int fmt, int desc) { + if (bytes == null) + return false; + int value = desc - (loc << LOC_SHIFT); + byte b1, b2; + switch (fmt) { + case U2_FORMAT: + assert(bytes[loc+0] == UNUSED_BYTE); + assert(bytes[loc+1] == UNUSED_BYTE); + b1 = (byte)(value >> 8); + b2 = (byte)(value >> 0); + if (value == (value & 0xFFFF) && b1 != OVERFLOW_BYTE) { + bytes[loc+0] = b1; + bytes[loc+1] = b2; + assert(fetchDesc(loc, fmt) == desc); + return true; + } + break; + case U1_FORMAT: + assert(bytes[loc] == UNUSED_BYTE); + b1 = (byte)value; + if (value == (value & 0xFF) && b1 != OVERFLOW_BYTE) { + bytes[loc] = b1; + assert(fetchDesc(loc, fmt) == desc); + return true; + } + break; + default: assert(false); + } + // Failure. Caller must allocate a bigDesc. + bytes[loc] = OVERFLOW_BYTE; + assert(fmt==U1_FORMAT || (bytes[loc+1]=(byte)bigDescs[BIGSIZE])!=999); + return false; + } + void storeIndex(int loc, int fmt, int value) { + storeIndex(bytes, loc, fmt, value); + } + static + void storeIndex(byte[] bytes, int loc, int fmt, int value) { + switch (fmt) { + case U2_FORMAT: + assert(value == (value & 0xFFFF)) : (value); + bytes[loc+0] = (byte)(value >> 8); + bytes[loc+1] = (byte)(value >> 0); + break; + case U1_FORMAT: + assert(value == (value & 0xFF)) : (value); + bytes[loc] = (byte)value; + break; + default: assert(false); + } + } + + void addU1(int pc, Entry ref) { + add(pc, U1_FORMAT, ref); + } + + void addU2(int pc, Entry ref) { + add(pc, U2_FORMAT, ref); + } + + /** Simple and necessary tuple to present each fixup. */ + public static + class Fixup implements Comparable { + int desc; // location and format of reloc + Entry entry; // which entry to plug into the bytes + Fixup(int desc, Entry entry) { + this.desc = desc; + this.entry = entry; + } + public Fixup(int loc, int fmt, Entry entry) { + this.desc = makeDesc(loc, fmt); + this.entry = entry; + } + public int location() { return descLoc(desc); } + public int format() { return descFmt(desc); } + public Entry entry() { return entry; } + @Override + public int compareTo(Fixup that) { + // Ordering depends only on location. + return this.location() - that.location(); + } + @Override + public boolean equals(Object x) { + if (!(x instanceof Fixup)) return false; + Fixup that = (Fixup) x; + return this.desc == that.desc && this.entry == that.entry; + } + @Override + public int hashCode() { + int hash = 7; + hash = 59 * hash + this.desc; + hash = 59 * hash + Objects.hashCode(this.entry); + return hash; + } + @Override + public String toString() { + return "@"+location()+(format()==U1_FORMAT?".1":"")+"="+entry; + } + } + + private + class Itr implements Iterator { + int index = 0; // index into entries + int bigIndex = BIGSIZE+1; // index into bigDescs + int next = head; // desc pointing to next fixup + @Override + public boolean hasNext() { return index < size; } + @Override + public void remove() { throw new UnsupportedOperationException(); } + @Override + public Fixup next() { + int thisIndex = index; + return new Fixup(nextDesc(), entries[thisIndex]); + } + int nextDesc() { + index++; + int thisDesc = next; + if (index < size) { + // Fetch next desc eagerly, in case this fixup gets finalized. + int loc = descLoc(thisDesc); + int fmt = descFmt(thisDesc); + if (bytes != null && bytes[loc] != OVERFLOW_BYTE) { + next = fetchDesc(loc, fmt); + } else { + // The unused extra byte is "asserted" to be equal to BI. + // This helps keep the overflow descs in sync. + assert(fmt==U1_FORMAT || bytes == null || bytes[loc+1]==(byte)bigIndex); + next = bigDescs[bigIndex++]; + } + } + return thisDesc; + } + } + + @Override + public Iterator iterator() { + return new Itr(); + } + public void add(int location, int format, Entry entry) { + addDesc(makeDesc(location, format), entry); + } + @Override + public boolean add(Fixup f) { + addDesc(f.desc, f.entry); + return true; + } + + @Override + public boolean addAll(Collection c) { + if (c instanceof Fixups) { + // Use knowledge of Itr structure to avoid building little structs. + Fixups that = (Fixups) c; + if (that.size == 0) return false; + if (this.size == 0 && entries.length < that.size) + growEntries(that.size); // presize exactly + Entry[] thatEntries = that.entries; + for (Itr i = that.new Itr(); i.hasNext(); ) { + int ni = i.index; + addDesc(i.nextDesc(), thatEntries[ni]); + } + return true; + } else { + return super.addAll(c); + } + } + // Here is how things get added: + private void addDesc(int thisDesc, Entry entry) { + if (entries.length == size) + growEntries(size * 2); + entries[size] = entry; + if (size == 0) { + head = tail = thisDesc; + } else { + int prevDesc = tail; + // Store new desc in previous tail. + int prevLoc = descLoc(prevDesc); + int prevFmt = descFmt(prevDesc); + int prevLen = fmtLen(prevFmt); + int thisLoc = descLoc(thisDesc); + // The collection must go in ascending order, and not overlap. + if (thisLoc < prevLoc + prevLen) + badOverlap(thisLoc); + tail = thisDesc; + if (!storeDesc(prevLoc, prevFmt, thisDesc)) { + // overflow + int bigSize = bigDescs[BIGSIZE]; + if (bigDescs.length == bigSize) + growBigDescs(); + //System.out.println("bigDescs["+bigSize+"] = "+thisDesc); + bigDescs[bigSize++] = thisDesc; + bigDescs[BIGSIZE] = bigSize; + } + } + size += 1; + } + private void badOverlap(int thisLoc) { + throw new IllegalArgumentException("locs must be ascending and must not overlap: "+thisLoc+" >> "+this); + } + + private void growEntries(int newSize) { + Entry[] oldEntries = entries; + entries = new Entry[Math.max(3, newSize)]; + System.arraycopy(oldEntries, 0, entries, 0, oldEntries.length); + } + private void growBigDescs() { + int[] oldBigDescs = bigDescs; + bigDescs = new int[oldBigDescs.length * 2]; + System.arraycopy(oldBigDescs, 0, bigDescs, 0, oldBigDescs.length); + } + + /// Static methods that optimize the use of this class. + static Object addRefWithBytes(Object f, byte[] bytes, Entry e) { + return add(f, bytes, 0, U2_FORMAT, e); + } + static Object addRefWithLoc(Object f, int loc, Entry entry) { + return add(f, null, loc, U2_FORMAT, entry); + } + private static + Object add(Object prevFixups, + byte[] bytes, int loc, int fmt, + Entry e) { + Fixups f; + if (prevFixups == null) { + if (loc == SPECIAL_LOC && fmt == SPECIAL_FMT) { + // Special convention: If the attribute has a + // U2 relocation at position zero, store the Entry + // rather than building a Fixups structure. + return e; + } + f = new Fixups(bytes); + } else if (!(prevFixups instanceof Fixups)) { + // Recognize the special convention: + Entry firstEntry = (Entry) prevFixups; + f = new Fixups(bytes); + f.add(SPECIAL_LOC, SPECIAL_FMT, firstEntry); + } else { + f = (Fixups) prevFixups; + assert(f.bytes == bytes); + } + f.add(loc, fmt, e); + return f; + } + + public static + void setBytes(Object fixups, byte[] bytes) { + if (fixups instanceof Fixups) { + Fixups f = (Fixups) fixups; + f.setBytes(bytes); + } + } + + public static + Object trimToSize(Object fixups) { + if (fixups instanceof Fixups) { + Fixups f = (Fixups) fixups; + f.trimToSize(); + if (f.size() == 0) + fixups = null; + } + return fixups; + } + + // Iterate over all the references in this set of fixups. + public static + void visitRefs(Object fixups, Collection refs) { + if (fixups == null) { + } else if (!(fixups instanceof Fixups)) { + // Special convention; see above. + refs.add((Entry) fixups); + } else { + Fixups f = (Fixups) fixups; + f.visitRefs(refs); + } + } + + // Clear out this set of fixups by replacing each reference + // by a hardcoded coding of its reference, drawn from ix. + public static + void finishRefs(Object fixups, byte[] bytes, ConstantPool.Index ix) { + if (fixups == null) + return; + if (!(fixups instanceof Fixups)) { + // Special convention; see above. + int index = ix.indexOf((Entry) fixups); + storeIndex(bytes, SPECIAL_LOC, SPECIAL_FMT, index); + return; + } + Fixups f = (Fixups) fixups; + assert(f.bytes == bytes); + f.finishRefs(ix); + } + + void finishRefs(ConstantPool.Index ix) { + if (isEmpty()) + return; + for (Fixup fx : this) { + int index = ix.indexOf(fx.entry); + //System.out.println("finish "+fx+" = "+index); + // Note that the iterator has already fetched the + // bytes we are about to overwrite. + storeIndex(fx.location(), fx.format(), index); + } + // Further iterations should do nothing: + bytes = null; // do not clean them + clear(); + } + +/* + /// Testing. + public static void main(String[] av) { + byte[] bytes = new byte[1 << 20]; + ConstantPool cp = new ConstantPool(); + Fixups f = new Fixups(bytes); + boolean isU1 = false; + int span = 3; + int nextLoc = 0; + int[] locs = new int[100]; + final int[] indexes = new int[100]; + int iptr = 1; + for (int loc = 0; loc < bytes.length; loc++) { + if (loc == nextLoc && loc+1 < bytes.length) { + int fmt = (isU1 ? U1_FORMAT : U2_FORMAT); + Entry e = ConstantPool.getUtf8Entry("L"+loc); + f.add(loc, fmt, e); + isU1 ^= true; + if (iptr < 10) { + // Make it close in. + nextLoc += fmtLen(fmt) + (iptr < 5 ? 0 : 1); + } else { + nextLoc += span; + span = (int)(span * 1.77); + } + // Here are the bytes that would have gone here: + locs[iptr] = loc; + if (fmt == U1_FORMAT) { + indexes[iptr++] = (loc & 0xFF); + } else { + indexes[iptr++] = ((loc & 0xFF) << 8) | ((loc+1) & 0xFF); + ++loc; // skip a byte + } + continue; + } + bytes[loc] = (byte)loc; + } + System.out.println("size="+f.size() + +" overflow="+(f.bigDescs[BIGSIZE]-1)); + System.out.println("Fixups: "+f); + // Test collection contents. + assert(iptr == 1+f.size()); + List l = new ArrayList(f); + Collections.sort(l); // should not change the order + if (!l.equals(new ArrayList(f))) System.out.println("** disordered"); + f.setBytes(null); + if (!l.equals(new ArrayList(f))) System.out.println("** bad set 1"); + f.setBytes(bytes); + if (!l.equals(new ArrayList(f))) System.out.println("** bad set 2"); + Fixups f3 = new Fixups(f); + if (!l.equals(new ArrayList(f3))) System.out.println("** bad set 3"); + Iterator fi = f.iterator(); + for (int i = 1; i < iptr; i++) { + Fixup fx = (Fixup) fi.next(); + if (fx.location() != locs[i]) { + System.out.println("** "+fx+" != "+locs[i]); + } + if (fx.format() == U1_FORMAT) + System.out.println(fx+" -> "+bytes[locs[i]]); + else + System.out.println(fx+" -> "+bytes[locs[i]]+" "+bytes[locs[i]+1]); + } + assert(!fi.hasNext()); + indexes[0] = 1; // like iptr + Index ix = new Index("ix") { + public int indexOf(Entry e) { + return indexes[indexes[0]++]; + } + }; + f.finishRefs(ix); + for (int loc = 0; loc < bytes.length; loc++) { + if (bytes[loc] != (byte)loc) { + System.out.println("** ["+loc+"] = "+bytes[loc]+" != "+(byte)loc); + } + } + } +//*/ +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/Histogram.java b/src/main/java/net/minecraftforge/gradle/util/pack200/Histogram.java new file mode 100644 index 000000000..c038aa3f6 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/Histogram.java @@ -0,0 +1,818 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Arrays; + +/** + * Histogram derived from an integer array of events (int[]). + * @author John Rose + */ +final class Histogram { + // Compact histogram representation: 4 bytes per distinct value, + // plus 5 words per distinct count. + protected final int[][] matrix; // multi-row matrix {{counti,valueij...}} + protected final int totalWeight; // sum of all counts + + // These are created eagerly also, since that saves work. + // They cost another 8 bytes per distinct value. + protected final int[] values; // unique values, sorted by value + protected final int[] counts; // counts, same order as values + + private static final long LOW32 = (long)-1 >>> 32; + + /** Build a histogram given a sequence of values. + * To save work, the input should be sorted, but need not be. + */ + public + Histogram(int[] valueSequence) { + long[] hist2col = computeHistogram2Col(maybeSort(valueSequence)); + int[][] table = makeTable(hist2col); + values = table[0]; + counts = table[1]; + this.matrix = makeMatrix(hist2col); + this.totalWeight = valueSequence.length; + assert(assertWellFormed(valueSequence)); + } + public + Histogram(int[] valueSequence, int start, int end) { + this(sortedSlice(valueSequence, start, end)); + } + + /** Build a histogram given a compact matrix of counts and values. */ + public + Histogram(int[][] matrix) { + // sort the rows + matrix = normalizeMatrix(matrix); // clone and sort + this.matrix = matrix; + int length = 0; + int weight = 0; + for (int i = 0; i < matrix.length; i++) { + int rowLength = matrix[i].length-1; + length += rowLength; + weight += matrix[i][0] * rowLength; + } + this.totalWeight = weight; + long[] hist2col = new long[length]; + int fillp = 0; + for (int i = 0; i < matrix.length; i++) { + for (int j = 1; j < matrix[i].length; j++) { + // sort key is value, so put it in the high 32! + hist2col[fillp++] = ((long) matrix[i][j] << 32) + | (LOW32 & matrix[i][0]); + } + } + assert(fillp == hist2col.length); + Arrays.sort(hist2col); + int[][] table = makeTable(hist2col); + values = table[1]; //backwards + counts = table[0]; //backwards + assert(assertWellFormed(null)); + } + + /** Histogram of int values, reported compactly as a ragged matrix, + * indexed by descending frequency rank. + *

+ * Format of matrix: + * Each row in the matrix begins with an occurrence count, + * and continues with all int values that occur at that frequency. + *

+     *  int[][] matrix = {
+     *    { count1, value11, value12, value13, ...  },
+     *    { count2, value21, value22, ... },
+     *    ...
+     *  }
+     *  
+ * The first column of the matrix { count1, count2, ... } + * is sorted in descending order, and contains no duplicates. + * Each row of the matrix (apart from its first element) + * is sorted in ascending order, and contains no duplicates. + * That is, each sequence { valuei1, valuei2, ... } is sorted. + */ + public + int[][] getMatrix() { return matrix; } + + public + int getRowCount() { return matrix.length; } + + public + int getRowFrequency(int rn) { return matrix[rn][0]; } + + public + int getRowLength(int rn) { return matrix[rn].length-1; } + + public + int getRowValue(int rn, int vn) { return matrix[rn][vn+1]; } + + public + int getRowWeight(int rn) { + return getRowFrequency(rn) * getRowLength(rn); + } + + public + int getTotalWeight() { + return totalWeight; + } + + public + int getTotalLength() { + return values.length; + } + + /** Returns an array of all values, sorted. */ + public + int[] getAllValues() { + + return values; + } + + /** Returns an array parallel with {@link #getValues}, + * with a frequency for each value. + */ + public + int[] getAllFrequencies() { + return counts; + } + + private static double log2 = Math.log(2); + + public + int getFrequency(int value) { + int pos = Arrays.binarySearch(values, value); + if (pos < 0) return 0; + assert(values[pos] == value); + return counts[pos]; + } + + public + double getBitLength(int value) { + double prob = (double) getFrequency(value) / getTotalWeight(); + return - Math.log(prob) / log2; + } + + public + double getRowBitLength(int rn) { + double prob = (double) getRowFrequency(rn) / getTotalWeight(); + return - Math.log(prob) / log2; + } + + public + interface BitMetric { + public double getBitLength(int value); + } + private final BitMetric bitMetric = new BitMetric() { + public double getBitLength(int value) { + return Histogram.this.getBitLength(value); + } + }; + public BitMetric getBitMetric() { + return bitMetric; + } + + /** bit-length is negative entropy: -H(matrix). */ + public + double getBitLength() { + double sum = 0; + for (int i = 0; i < matrix.length; i++) { + sum += getRowBitLength(i) * getRowWeight(i); + } + assert(0.1 > Math.abs(sum - getBitLength(bitMetric))); + return sum; + } + + /** bit-length in to another coding (cross-entropy) */ + public + double getBitLength(BitMetric len) { + double sum = 0; + for (int i = 0; i < matrix.length; i++) { + for (int j = 1; j < matrix[i].length; j++) { + sum += matrix[i][0] * len.getBitLength(matrix[i][j]); + } + } + return sum; + } + + private static + double round(double x, double scale) { + return Math.round(x * scale) / scale; + } + + /** Sort rows and columns. + * Merge adjacent rows with the same key element [0]. + * Make a fresh copy of all of it. + */ + public int[][] normalizeMatrix(int[][] matrix) { + long[] rowMap = new long[matrix.length]; + for (int i = 0; i < matrix.length; i++) { + if (matrix[i].length <= 1) continue; + int count = matrix[i][0]; + if (count <= 0) continue; + rowMap[i] = (long) count << 32 | i; + } + Arrays.sort(rowMap); + int[][] newMatrix = new int[matrix.length][]; + int prevCount = -1; + int fillp1 = 0; + int fillp2 = 0; + for (int i = 0; ; i++) { + int[] row; + if (i < matrix.length) { + long rowMapEntry = rowMap[rowMap.length-i-1]; + if (rowMapEntry == 0) continue; + row = matrix[(int)rowMapEntry]; + assert(rowMapEntry>>>32 == row[0]); + } else { + row = new int[]{ -1 }; // close it off + } + if (row[0] != prevCount && fillp2 > fillp1) { + // Close off previous run. + int length = 0; + for (int p = fillp1; p < fillp2; p++) { + int[] row0 = newMatrix[p]; // previously visited row + assert(row0[0] == prevCount); + length += row0.length-1; + } + int[] row1 = new int[1+length]; // cloned & consolidated row + row1[0] = prevCount; + int rfillp = 1; + for (int p = fillp1; p < fillp2; p++) { + int[] row0 = newMatrix[p]; // previously visited row + assert(row0[0] == prevCount); + System.arraycopy(row0, 1, row1, rfillp, row0.length-1); + rfillp += row0.length-1; + } + if (!isSorted(row1, 1, true)) { + Arrays.sort(row1, 1, row1.length); + int jfillp = 2; + // Detect and squeeze out duplicates. + for (int j = 2; j < row1.length; j++) { + if (row1[j] != row1[j-1]) + row1[jfillp++] = row1[j]; + } + if (jfillp < row1.length) { + // Reallocate because of lost duplicates. + int[] newRow1 = new int[jfillp]; + System.arraycopy(row1, 0, newRow1, 0, jfillp); + row1 = newRow1; + } + } + newMatrix[fillp1++] = row1; + fillp2 = fillp1; + } + if (i == matrix.length) + break; + prevCount = row[0]; + newMatrix[fillp2++] = row; + } + assert(fillp1 == fillp2); // no unfinished business + // Now drop missing rows. + matrix = newMatrix; + if (fillp1 < matrix.length) { + newMatrix = new int[fillp1][]; + System.arraycopy(matrix, 0, newMatrix, 0, fillp1); + matrix = newMatrix; + } + return matrix; + } + + public + String[] getRowTitles(String name) { + int totalUnique = getTotalLength(); + int ltotalWeight = getTotalWeight(); + String[] histTitles = new String[matrix.length]; + int cumWeight = 0; + int cumUnique = 0; + for (int i = 0; i < matrix.length; i++) { + int count = getRowFrequency(i); + int unique = getRowLength(i); + int weight = getRowWeight(i); + cumWeight += weight; + cumUnique += unique; + long wpct = ((long)cumWeight * 100 + ltotalWeight/2) / ltotalWeight; + long upct = ((long)cumUnique * 100 + totalUnique/2) / totalUnique; + double len = getRowBitLength(i); + assert(0.1 > Math.abs(len - getBitLength(matrix[i][1]))); + histTitles[i] = name+"["+i+"]" + +" len="+round(len,10) + +" ("+count+"*["+unique+"])" + +" ("+cumWeight+":"+wpct+"%)" + +" ["+cumUnique+":"+upct+"%]"; + } + return histTitles; + } + + /** Print a report of this histogram. + */ + public + void print(PrintStream out) { + print("hist", out); + } + + /** Print a report of this histogram. + */ + public + void print(String name, PrintStream out) { + print(name, getRowTitles(name), out); + } + + /** Print a report of this histogram. + */ + public + void print(String name, String[] histTitles, PrintStream out) { + int totalUnique = getTotalLength(); + int ltotalWeight = getTotalWeight(); + double tlen = getBitLength(); + double avgLen = tlen / ltotalWeight; + double avg = (double) ltotalWeight / totalUnique; + String title = (name + +" len="+round(tlen,10) + +" avgLen="+round(avgLen,10) + +" weight("+ltotalWeight+")" + +" unique["+totalUnique+"]" + +" avgWeight("+round(avg,100)+")"); + if (histTitles == null) { + out.println(title); + } else { + out.println(title+" {"); + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < matrix.length; i++) { + buf.setLength(0); + buf.append(" ").append(histTitles[i]).append(" {"); + for (int j = 1; j < matrix[i].length; j++) { + buf.append(" ").append(matrix[i][j]); + } + buf.append(" }"); + out.println(buf); + } + out.println("}"); + } + } + +/* + public static + int[][] makeHistogramMatrix(int[] values) { + // Make sure they are sorted. + values = maybeSort(values); + long[] hist2col = computeHistogram2Col(values); + int[][] matrix = makeMatrix(hist2col); + return matrix; + } +*/ + + private static + int[][] makeMatrix(long[] hist2col) { + // Sort by increasing count, then by increasing value. + Arrays.sort(hist2col); + int[] counts = new int[hist2col.length]; + for (int i = 0; i < counts.length; i++) { + counts[i] = (int)( hist2col[i] >>> 32 ); + } + long[] countHist = computeHistogram2Col(counts); + int[][] matrix = new int[countHist.length][]; + int histp = 0; // cursor into hist2col (increasing count, value) + int countp = 0; // cursor into countHist (increasing count) + // Do a join between hist2col (resorted) and countHist. + for (int i = matrix.length; --i >= 0; ) { + long countAndRep = countHist[countp++]; + int count = (int) (countAndRep); // what is the value count? + int repeat = (int) (countAndRep >>> 32); // # times repeated? + int[] row = new int[1+repeat]; + row[0] = count; + for (int j = 0; j < repeat; j++) { + long countAndValue = hist2col[histp++]; + assert(countAndValue >>> 32 == count); + row[1+j] = (int) countAndValue; + } + matrix[i] = row; + } + assert(histp == hist2col.length); + return matrix; + } + + private static + int[][] makeTable(long[] hist2col) { + int[][] table = new int[2][hist2col.length]; + // Break apart the entries in hist2col. + // table[0] gets values, table[1] gets entries. + for (int i = 0; i < hist2col.length; i++) { + table[0][i] = (int)( hist2col[i] ); + table[1][i] = (int)( hist2col[i] >>> 32 ); + } + return table; + } + + /** Simple two-column histogram. Contains repeated counts. + * Assumes input is sorted. Does not sort output columns. + *

+ * Format of result: + *

+     *  long[] hist = {
+     *    (count1 << 32) | (value1),
+     *    (count2 << 32) | (value2),
+     *    ...
+     *  }
+     *  
+ * In addition, the sequence {valuei...} is guaranteed to be sorted. + * Note that resorting this using Arrays.sort() will reorder the + * entries by increasing count. + */ + private static + long[] computeHistogram2Col(int[] sortedValues) { + switch (sortedValues.length) { + case 0: + return new long[]{ }; + case 1: + return new long[]{ ((long)1 << 32) | (LOW32 & sortedValues[0]) }; + } + long[] hist = null; + for (boolean sizeOnly = true; ; sizeOnly = false) { + int prevIndex = -1; + int prevValue = sortedValues[0] ^ -1; // force a difference + int prevCount = 0; + for (int i = 0; i <= sortedValues.length; i++) { + int thisValue; + if (i < sortedValues.length) + thisValue = sortedValues[i]; + else + thisValue = prevValue ^ -1; // force a difference at end + if (thisValue == prevValue) { + prevCount += 1; + } else { + // Found a new value. + if (!sizeOnly && prevCount != 0) { + // Save away previous value. + hist[prevIndex] = ((long)prevCount << 32) + | (LOW32 & prevValue); + } + prevValue = thisValue; + prevCount = 1; + prevIndex += 1; + } + } + if (sizeOnly) { + // Finished the sizing pass. Allocate the histogram. + hist = new long[prevIndex]; + } else { + break; // done + } + } + return hist; + } + + /** Regroup the histogram, so that it becomes an approximate histogram + * whose rows are of the given lengths. + * If matrix rows must be split, the latter parts (larger values) + * are placed earlier in the new matrix. + * If matrix rows are joined, they are resorted into ascending order. + * In the new histogram, the counts are averaged over row entries. + */ + private static + int[][] regroupHistogram(int[][] matrix, int[] groups) { + long oldEntries = 0; + for (int i = 0; i < matrix.length; i++) { + oldEntries += matrix[i].length-1; + } + long newEntries = 0; + for (int ni = 0; ni < groups.length; ni++) { + newEntries += groups[ni]; + } + if (newEntries > oldEntries) { + int newlen = groups.length; + long ok = oldEntries; + for (int ni = 0; ni < groups.length; ni++) { + if (ok < groups[ni]) { + int[] newGroups = new int[ni+1]; + System.arraycopy(groups, 0, newGroups, 0, ni+1); + groups = newGroups; + groups[ni] = (int) ok; + ok = 0; + break; + } + ok -= groups[ni]; + } + } else { + long excess = oldEntries - newEntries; + int[] newGroups = new int[groups.length+1]; + System.arraycopy(groups, 0, newGroups, 0, groups.length); + newGroups[groups.length] = (int) excess; + groups = newGroups; + } + int[][] newMatrix = new int[groups.length][]; + // Fill pointers. + int i = 0; // into matrix + int jMin = 1; + int jMax = matrix[i].length; + for (int ni = 0; ni < groups.length; ni++) { + int groupLength = groups[ni]; + int[] group = new int[1+groupLength]; + long groupWeight = 0; // count of all in new group + newMatrix[ni] = group; + int njFill = 1; + while (njFill < group.length) { + int len = group.length - njFill; + while (jMin == jMax) { + jMin = 1; + jMax = matrix[++i].length; + } + if (len > jMax - jMin) len = jMax - jMin; + groupWeight += (long) matrix[i][0] * len; + System.arraycopy(matrix[i], jMax - len, group, njFill, len); + jMax -= len; + njFill += len; + } + Arrays.sort(group, 1, group.length); + // compute average count of new group: + group[0] = (int) ((groupWeight + groupLength/2) / groupLength); + } + assert(jMin == jMax); + assert(i == matrix.length-1); + return newMatrix; + } + + public static + Histogram makeByteHistogram(InputStream bytes) throws IOException { + byte[] buf = new byte[1<<12]; + int[] tally = new int[1<<8]; + for (int nr; (nr = bytes.read(buf)) > 0; ) { + for (int i = 0; i < nr; i++) { + tally[buf[i] & 0xFF] += 1; + } + } + // Build a matrix. + int[][] matrix = new int[1<<8][2]; + for (int i = 0; i < tally.length; i++) { + matrix[i][0] = tally[i]; + matrix[i][1] = i; + } + return new Histogram(matrix); + } + + /** Slice and sort the given input array. */ + private static + int[] sortedSlice(int[] valueSequence, int start, int end) { + if (start == 0 && end == valueSequence.length && + isSorted(valueSequence, 0, false)) { + return valueSequence; + } else { + int[] slice = new int[end-start]; + System.arraycopy(valueSequence, start, slice, 0, slice.length); + Arrays.sort(slice); + return slice; + } + } + + /** Tell if an array is sorted. */ + private static + boolean isSorted(int[] values, int from, boolean strict) { + for (int i = from+1; i < values.length; i++) { + if (strict ? !(values[i-1] < values[i]) + : !(values[i-1] <= values[i])) { + return false; // found witness to disorder + } + } + return true; // no witness => sorted + } + + /** Clone and sort the array, if not already sorted. */ + private static + int[] maybeSort(int[] values) { + if (!isSorted(values, 0, false)) { + values = values.clone(); + Arrays.sort(values); + } + return values; + } + + + /// Debug stuff follows. + + private boolean assertWellFormed(int[] valueSequence) { +/* + // Sanity check. + int weight = 0; + int vlength = 0; + for (int i = 0; i < matrix.length; i++) { + int vlengthi = (matrix[i].length-1); + int count = matrix[i][0]; + assert(vlengthi > 0); // no empty rows + assert(count > 0); // no impossible rows + vlength += vlengthi; + weight += count * vlengthi; + } + assert(isSorted(values, 0, true)); + // make sure the counts all add up + assert(totalWeight == weight); + assert(vlength == values.length); + assert(vlength == counts.length); + int weight2 = 0; + for (int i = 0; i < counts.length; i++) { + weight2 += counts[i]; + } + assert(weight2 == weight); + int[] revcol1 = new int[matrix.length]; //1st matrix colunm + for (int i = 0; i < matrix.length; i++) { + // spot checking: try a random query on each matrix row + assert(matrix[i].length > 1); + revcol1[matrix.length-i-1] = matrix[i][0]; + assert(isSorted(matrix[i], 1, true)); + int rand = (matrix[i].length+1) / 2; + int val = matrix[i][rand]; + int count = matrix[i][0]; + int pos = Arrays.binarySearch(values, val); + assert(values[pos] == val); + assert(counts[pos] == matrix[i][0]); + if (valueSequence != null) { + int count2 = 0; + for (int j = 0; j < valueSequence.length; j++) { + if (valueSequence[j] == val) count2++; + } + assert(count2 == count); + } + } + assert(isSorted(revcol1, 0, true)); +//*/ + return true; + } + +/* + public static + int[] readValuesFrom(InputStream instr) { + return readValuesFrom(new InputStreamReader(instr)); + } + public static + int[] readValuesFrom(Reader inrdr) { + inrdr = new BufferedReader(inrdr); + final StreamTokenizer in = new StreamTokenizer(inrdr); + final int TT_NOTHING = -99; + in.commentChar('#'); + return readValuesFrom(new Iterator() { + int token = TT_NOTHING; + private int getToken() { + if (token == TT_NOTHING) { + try { + token = in.nextToken(); + assert(token != TT_NOTHING); + } catch (IOException ee) { + throw new RuntimeException(ee); + } + } + return token; + } + public boolean hasNext() { + return getToken() != StreamTokenizer.TT_EOF; + } + public Object next() { + int ntok = getToken(); + token = TT_NOTHING; + switch (ntok) { + case StreamTokenizer.TT_EOF: + throw new NoSuchElementException(); + case StreamTokenizer.TT_NUMBER: + return new Integer((int) in.nval); + default: + assert(false); + return null; + } + } + public void remove() { + throw new UnsupportedOperationException(); + } + }); + } + public static + int[] readValuesFrom(Iterator iter) { + return readValuesFrom(iter, 0); + } + public static + int[] readValuesFrom(Iterator iter, int initSize) { + int[] na = new int[Math.max(10, initSize)]; + int np = 0; + while (iter.hasNext()) { + Integer val = (Integer) iter.next(); + if (np == na.length) { + int[] na2 = new int[np*2]; + System.arraycopy(na, 0, na2, 0, np); + na = na2; + } + na[np++] = val.intValue(); + } + if (np != na.length) { + int[] na2 = new int[np]; + System.arraycopy(na, 0, na2, 0, np); + na = na2; + } + return na; + } + + public static + Histogram makeByteHistogram(byte[] bytes) { + try { + return makeByteHistogram(new ByteArrayInputStream(bytes)); + } catch (IOException ee) { + throw new RuntimeException(ee); + } + } + + public static + void main(String[] av) throws IOException { + if (av.length > 0 && av[0].equals("-r")) { + int[] values = new int[Integer.parseInt(av[1])]; + int limit = values.length; + if (av.length >= 3) { + limit = (int)( limit * Double.parseDouble(av[2]) ); + } + Random rnd = new Random(); + for (int i = 0; i < values.length; i++) { + values[i] = rnd.nextInt(limit);; + } + Histogram rh = new Histogram(values); + rh.print("random", System.out); + return; + } + if (av.length > 0 && av[0].equals("-s")) { + int[] values = readValuesFrom(System.in); + Random rnd = new Random(); + for (int i = values.length; --i > 0; ) { + int j = rnd.nextInt(i+1); + if (j < i) { + int tem = values[i]; + values[i] = values[j]; + values[j] = tem; + } + } + for (int i = 0; i < values.length; i++) + System.out.println(values[i]); + return; + } + if (av.length > 0 && av[0].equals("-e")) { + // edge cases + new Histogram(new int[][] { + {1, 11, 111}, + {0, 123, 456}, + {1, 111, 1111}, + {0, 456, 123}, + {3}, + {}, + {3}, + {2, 22}, + {4} + }).print(System.out); + return; + } + if (av.length > 0 && av[0].equals("-b")) { + // edge cases + Histogram bh = makeByteHistogram(System.in); + bh.print("bytes", System.out); + return; + } + boolean regroup = false; + if (av.length > 0 && av[0].equals("-g")) { + regroup = true; + } + + int[] values = readValuesFrom(System.in); + Histogram h = new Histogram(values); + if (!regroup) + h.print(System.out); + if (regroup) { + int[] groups = new int[12]; + for (int i = 0; i < groups.length; i++) { + groups[i] = 1< 0); + assert(pc+length <= bytes.length); + // Speed hack: Instruction.next reuses self if possible. + if (reuse != null && !reuse.special) { + reuse.reset(bytes, pc, bc, w, length); + return reuse; + } + return new Instruction(bytes, pc, bc, w, length); + } + + // Return the constant pool reference type, or 0 if none. + public byte getCPTag() { + return BC_TAG[w][bc]; + } + + // Return the constant pool index, or -1 if none. + public int getCPIndex() { + int indexLoc = BC_INDEX[w][bc]; + if (indexLoc == 0) return -1; + assert(w == 0); + if (length == 2) + return getByte(bytes, pc+indexLoc); // _ldc opcode only + else + return getShort(bytes, pc+indexLoc); + } + + public void setCPIndex(int cpi) { + int indexLoc = BC_INDEX[w][bc]; + assert(indexLoc != 0); + if (length == 2) + setByte(bytes, pc+indexLoc, cpi); // _ldc opcode only + else + setShort(bytes, pc+indexLoc, cpi); + assert(getCPIndex() == cpi); + } + + public ConstantPool.Entry getCPRef(ConstantPool.Entry[] cpMap) { + int index = getCPIndex(); + return (index < 0) ? null : cpMap[index]; + } + + // Return the slot of the affected local, or -1 if none. + public int getLocalSlot() { + int slotLoc = BC_SLOT[w][bc]; + if (slotLoc == 0) return -1; + if (w == 0) + return getByte(bytes, pc+slotLoc); + else + return getShort(bytes, pc+slotLoc); + } + + // Return the target of the branch, or -1 if none. + public int getBranchLabel() { + int branchLoc = BC_BRANCH[w][bc]; + if (branchLoc == 0) return -1; + assert(w == 0); + assert(length == 3 || length == 5); + int offset; + if (length == 3) + offset = (short)getShort(bytes, pc+branchLoc); + else + offset = getInt(bytes, pc+branchLoc); + assert(offset+pc >= 0); + assert(offset+pc <= bytes.length); + return offset+pc; + } + + public void setBranchLabel(int targetPC) { + int branchLoc = BC_BRANCH[w][bc]; + assert(branchLoc != 0); + if (length == 3) + setShort(bytes, pc+branchLoc, targetPC-pc); + else + setInt(bytes, pc+branchLoc, targetPC-pc); + assert(targetPC == getBranchLabel()); + } + + // Return the trailing constant in the instruction (as a signed value). + // Return 0 if there is none. + public int getConstant() { + int conLoc = BC_CON[w][bc]; + if (conLoc == 0) return 0; + switch (length - conLoc) { + case 1: return (byte) getByte(bytes, pc+conLoc); + case 2: return (short) getShort(bytes, pc+conLoc); + } + assert(false); + return 0; + } + + public void setConstant(int con) { + int conLoc = BC_CON[w][bc]; + assert(conLoc != 0); + switch (length - conLoc) { + case 1: setByte(bytes, pc+conLoc, con); break; + case 2: setShort(bytes, pc+conLoc, con); break; + } + assert(con == getConstant()); + } + + public abstract static class Switch extends Instruction { + // Each case is a (value, label) pair, indexed 0 <= n < caseCount + public abstract int getCaseCount(); + public abstract int getCaseValue(int n); + public abstract int getCaseLabel(int n); + public abstract void setCaseCount(int caseCount); + public abstract void setCaseValue(int n, int value); + public abstract void setCaseLabel(int n, int targetPC); + protected abstract int getLength(int caseCount); + + public int getDefaultLabel() { return intAt(0)+pc; } + public void setDefaultLabel(int targetPC) { setIntAt(0, targetPC-pc); } + + protected int apc; // aligned pc (table base) + protected int intAt(int n) { return getInt(bytes, apc + n*4); } + protected void setIntAt(int n, int x) { setInt(bytes, apc + n*4, x); } + protected Switch(byte[] bytes, int pc, int bc) { + super(bytes, pc, bc, /*w*/0, /*length*/0); + this.apc = alignPC(pc+1); + this.special = true; + length = getLength(getCaseCount()); + } + public int getAlignedPC() { return apc; } + public String toString() { + String s = super.toString(); + s += " Default:"+labstr(getDefaultLabel()); + int caseCount = getCaseCount(); + for (int i = 0; i < caseCount; i++) { + s += "\n\tCase "+getCaseValue(i)+":"+labstr(getCaseLabel(i)); + } + return s; + } + public static int alignPC(int apc) { + while (apc % 4 != 0) ++apc; + return apc; + } + } + + public static class TableSwitch extends Switch { + // apc: (df, lo, hi, (hi-lo+1)*(label)) + public int getLowCase() { return intAt(1); } + public int getHighCase() { return intAt(2); } + public int getCaseCount() { return intAt(2)-intAt(1)+1; } + public int getCaseValue(int n) { return getLowCase()+n; } + public int getCaseLabel(int n) { return intAt(3+n)+pc; } + + public void setLowCase(int val) { setIntAt(1, val); } + public void setHighCase(int val) { setIntAt(2, val); } + public void setCaseLabel(int n, int tpc) { setIntAt(3+n, tpc-pc); } + public void setCaseCount(int caseCount) { + setHighCase(getLowCase() + caseCount - 1); + length = getLength(caseCount); + } + public void setCaseValue(int n, int val) { + if (n != 0) throw new UnsupportedOperationException(); + int caseCount = getCaseCount(); + setLowCase(val); + setCaseCount(caseCount); // keep invariant + } + + TableSwitch(byte[] bytes, int pc) { + super(bytes, pc, _tableswitch); + } + protected int getLength(int caseCount) { + return (apc-pc) + (3 + caseCount) * 4; + } + } + + public static class LookupSwitch extends Switch { + // apc: (df, nc, nc*(case, label)) + public int getCaseCount() { return intAt(1); } + public int getCaseValue(int n) { return intAt(2+n*2+0); } + public int getCaseLabel(int n) { return intAt(2+n*2+1)+pc; } + + public void setCaseCount(int caseCount) { + setIntAt(1, caseCount); + length = getLength(caseCount); + } + public void setCaseValue(int n, int val) { setIntAt(2+n*2+0, val); } + public void setCaseLabel(int n, int tpc) { setIntAt(2+n*2+1, tpc-pc); } + + LookupSwitch(byte[] bytes, int pc) { + super(bytes, pc, _lookupswitch); + } + protected int getLength(int caseCount) { + return (apc-pc) + (2 + caseCount*2) * 4; + } + } + + /** Two instructions are equal if they have the same bytes. */ + public boolean equals(Object o) { + return (o != null) && (o.getClass() == Instruction.class) + && equals((Instruction) o); + } + + public int hashCode() { + int hash = 3; + hash = 11 * hash + Arrays.hashCode(this.bytes); + hash = 11 * hash + this.pc; + hash = 11 * hash + this.bc; + hash = 11 * hash + this.w; + hash = 11 * hash + this.length; + return hash; + } + + public boolean equals(Instruction that) { + if (this.pc != that.pc) return false; + if (this.bc != that.bc) return false; + if (this.w != that.w) return false; + if (this.length != that.length) return false; + for (int i = 1; i < length; i++) { + if (this.bytes[this.pc+i] != that.bytes[that.pc+i]) + return false; + } + return true; + } + + static String labstr(int pc) { + if (pc >= 0 && pc < 100000) + return ((100000+pc)+"").substring(1); + return pc+""; + } + public String toString() { + return toString(null); + } + public String toString(ConstantPool.Entry[] cpMap) { + String s = labstr(pc) + ": "; + if (bc >= _bytecode_limit) { + s += Integer.toHexString(bc); + return s; + } + if (w == 1) s += "wide "; + String bcname = (bc < BC_NAME.length)? BC_NAME[bc]: null; + if (bcname == null) { + return s+"opcode#"+bc; + } + s += bcname; + int tag = getCPTag(); + if (tag != 0) s += " "+ConstantPool.tagName(tag)+":"; + int idx = getCPIndex(); + if (idx >= 0) s += (cpMap == null) ? ""+idx : "="+cpMap[idx].stringValue(); + int slt = getLocalSlot(); + if (slt >= 0) s += " Local:"+slt; + int lab = getBranchLabel(); + if (lab >= 0) s += " To:"+labstr(lab); + int con = getConstant(); + if (con != 0) s += " Con:"+con; + return s; + } + + + //public static byte constantPoolTagFor(int bc) { return BC_TAG[0][bc]; } + + /// Fetching values from byte arrays: + + public int getIntAt(int off) { + return getInt(bytes, pc+off); + } + public int getShortAt(int off) { + return getShort(bytes, pc+off); + } + public int getByteAt(int off) { + return getByte(bytes, pc+off); + } + + + public static int getInt(byte[] bytes, int pc) { + return (getShort(bytes, pc+0) << 16) + (getShort(bytes, pc+2) << 0); + } + public static int getShort(byte[] bytes, int pc) { + return (getByte(bytes, pc+0) << 8) + (getByte(bytes, pc+1) << 0); + } + public static int getByte(byte[] bytes, int pc) { + return bytes[pc] & 0xFF; + } + + + public static void setInt(byte[] bytes, int pc, int x) { + setShort(bytes, pc+0, x >> 16); + setShort(bytes, pc+2, x >> 0); + } + public static void setShort(byte[] bytes, int pc, int x) { + setByte(bytes, pc+0, x >> 8); + setByte(bytes, pc+1, x >> 0); + } + public static void setByte(byte[] bytes, int pc, int x) { + bytes[pc] = (byte)x; + } + + // some bytecode classifiers + + + public static boolean isNonstandard(int bc) { + return BC_LENGTH[0][bc] < 0; + } + + public static int opLength(int bc) { + int l = BC_LENGTH[0][bc]; + assert(l > 0); + return l; + } + public static int opWideLength(int bc) { + int l = BC_LENGTH[1][bc]; + assert(l > 0); + return l; + } + + public static boolean isLocalSlotOp(int bc) { + return (bc < BC_SLOT[0].length && BC_SLOT[0][bc] > 0); + } + + public static boolean isBranchOp(int bc) { + return (bc < BC_BRANCH[0].length && BC_BRANCH[0][bc] > 0); + } + + public static boolean isCPRefOp(int bc) { + if (bc < BC_INDEX[0].length && BC_INDEX[0][bc] > 0) return true; + if (bc >= _xldc_op && bc < _xldc_limit) return true; + if (bc == _invokespecial_int || bc == _invokestatic_int) return true; + return false; + } + + public static byte getCPRefOpTag(int bc) { + if (bc < BC_INDEX[0].length && BC_INDEX[0][bc] > 0) return BC_TAG[0][bc]; + if (bc >= _xldc_op && bc < _xldc_limit) return CONSTANT_LoadableValue; + if (bc == _invokestatic_int || bc == _invokespecial_int) return CONSTANT_InterfaceMethodref; + return CONSTANT_None; + } + + public static boolean isFieldOp(int bc) { + return (bc >= _getstatic && bc <= _putfield); + } + + public static boolean isInvokeInitOp(int bc) { + return (bc >= _invokeinit_op && bc < _invokeinit_limit); + } + + public static boolean isSelfLinkerOp(int bc) { + return (bc >= _self_linker_op && bc < _self_linker_limit); + } + + /// Format definitions. + + private static final byte[][] BC_LENGTH = new byte[2][0x100]; + private static final byte[][] BC_INDEX = new byte[2][0x100]; + private static final byte[][] BC_TAG = new byte[2][0x100]; + private static final byte[][] BC_BRANCH = new byte[2][0x100]; + private static final byte[][] BC_SLOT = new byte[2][0x100]; + private static final byte[][] BC_CON = new byte[2][0x100]; + private static final String[] BC_NAME = new String[0x100]; // debug only + private static final String[][] BC_FORMAT = new String[2][_bytecode_limit]; // debug only + static { + for (int i = 0; i < _bytecode_limit; i++) { + BC_LENGTH[0][i] = -1; + BC_LENGTH[1][i] = -1; + } + def("b", _nop, _dconst_1); + def("bx", _bipush); + def("bxx", _sipush); + def("bk", _ldc); // do not pack + def("bkk", _ldc_w, _ldc2_w); // do not pack + def("blwbll", _iload, _aload); + def("b", _iload_0, _saload); + def("blwbll", _istore, _astore); + def("b", _istore_0, _lxor); + def("blxwbllxx", _iinc); + def("b", _i2l, _dcmpg); + def("boo", _ifeq, _jsr); // pack oo + def("blwbll", _ret); + def("", _tableswitch, _lookupswitch); // pack all ints, omit padding + def("b", _ireturn, _return); + def("bkf", _getstatic, _putfield); // pack kf (base=Field) + def("bkm", _invokevirtual, _invokestatic); // pack kn (base=Method) + def("bkixx", _invokeinterface); // pack ki (base=IMethod), omit xx + def("bkyxx", _invokedynamic); // pack ky (base=Any), omit xx + def("bkc", _new); // pack kc + def("bx", _newarray); + def("bkc", _anewarray); // pack kc + def("b", _arraylength, _athrow); + def("bkc", _checkcast, _instanceof); // pack kc + def("b", _monitorenter, _monitorexit); + def("", _wide); + def("bkcx", _multianewarray); // pack kc + def("boo", _ifnull, _ifnonnull); // pack oo + def("boooo", _goto_w, _jsr_w); // pack oooo + for (int i = 0; i < _bytecode_limit; i++) { + //System.out.println(i+": l="+BC_LENGTH[0][i]+" i="+BC_INDEX[0][i]); + //assert(BC_LENGTH[0][i] != -1); + if (BC_LENGTH[0][i] == -1) { + continue; // unknown opcode + } + + // Have a complete mapping, to support spurious _wide prefixes. + if (BC_LENGTH[1][i] == -1) + BC_LENGTH[1][i] = (byte)(1+BC_LENGTH[0][i]); + } + + String names = + "nop aconst_null iconst_m1 iconst_0 iconst_1 iconst_2 iconst_3 iconst_4 "+ + "iconst_5 lconst_0 lconst_1 fconst_0 fconst_1 fconst_2 dconst_0 dconst_1 "+ + "bipush sipush ldc ldc_w ldc2_w iload lload fload dload aload iload_0 "+ + "iload_1 iload_2 iload_3 lload_0 lload_1 lload_2 lload_3 fload_0 fload_1 "+ + "fload_2 fload_3 dload_0 dload_1 dload_2 dload_3 aload_0 aload_1 aload_2 "+ + "aload_3 iaload laload faload daload aaload baload caload saload istore "+ + "lstore fstore dstore astore istore_0 istore_1 istore_2 istore_3 lstore_0 "+ + "lstore_1 lstore_2 lstore_3 fstore_0 fstore_1 fstore_2 fstore_3 dstore_0 "+ + "dstore_1 dstore_2 dstore_3 astore_0 astore_1 astore_2 astore_3 iastore "+ + "lastore fastore dastore aastore bastore castore sastore pop pop2 dup "+ + "dup_x1 dup_x2 dup2 dup2_x1 dup2_x2 swap iadd ladd fadd dadd isub lsub "+ + "fsub dsub imul lmul fmul dmul idiv ldiv fdiv ddiv irem lrem frem drem "+ + "ineg lneg fneg dneg ishl lshl ishr lshr iushr lushr iand land ior lor "+ + "ixor lxor iinc i2l i2f i2d l2i l2f l2d f2i f2l f2d d2i d2l d2f i2b i2c "+ + "i2s lcmp fcmpl fcmpg dcmpl dcmpg ifeq ifne iflt ifge ifgt ifle if_icmpeq "+ + "if_icmpne if_icmplt if_icmpge if_icmpgt if_icmple if_acmpeq if_acmpne "+ + "goto jsr ret tableswitch lookupswitch ireturn lreturn freturn dreturn "+ + "areturn return getstatic putstatic getfield putfield invokevirtual "+ + "invokespecial invokestatic invokeinterface invokedynamic new newarray "+ + "anewarray arraylength athrow checkcast instanceof monitorenter "+ + "monitorexit wide multianewarray ifnull ifnonnull goto_w jsr_w "; + for (int bc = 0; names.length() > 0; bc++) { + int sp = names.indexOf(' '); + BC_NAME[bc] = names.substring(0, sp); + names = names.substring(sp+1); + } + } + public static String byteName(int bc) { + String iname; + if (bc < BC_NAME.length && BC_NAME[bc] != null) { + iname = BC_NAME[bc]; + } else if (isSelfLinkerOp(bc)) { + int idx = (bc - _self_linker_op); + boolean isSuper = (idx >= _self_linker_super_flag); + if (isSuper) idx -= _self_linker_super_flag; + boolean isAload = (idx >= _self_linker_aload_flag); + if (isAload) idx -= _self_linker_aload_flag; + int origBC = _first_linker_op + idx; + assert(origBC >= _first_linker_op && origBC <= _last_linker_op); + iname = BC_NAME[origBC]; + iname += (isSuper ? "_super" : "_this"); + if (isAload) iname = "aload_0&" + iname; + iname = "*"+iname; + } else if (isInvokeInitOp(bc)) { + int idx = (bc - _invokeinit_op); + switch (idx) { + case _invokeinit_self_option: + iname = "*invokespecial_init_this"; break; + case _invokeinit_super_option: + iname = "*invokespecial_init_super"; break; + default: + assert(idx == _invokeinit_new_option); + iname = "*invokespecial_init_new"; break; + } + } else { + switch (bc) { + case _ildc: iname = "*ildc"; break; + case _fldc: iname = "*fldc"; break; + case _ildc_w: iname = "*ildc_w"; break; + case _fldc_w: iname = "*fldc_w"; break; + case _dldc2_w: iname = "*dldc2_w"; break; + case _cldc: iname = "*cldc"; break; + case _cldc_w: iname = "*cldc_w"; break; + case _qldc: iname = "*qldc"; break; + case _qldc_w: iname = "*qldc_w"; break; + case _byte_escape: iname = "*byte_escape"; break; + case _ref_escape: iname = "*ref_escape"; break; + case _end_marker: iname = "*end"; break; + default: iname = "*bc#"+bc; break; + } + } + return iname; + } + private static int BW = 4; // width of classification field + private static void def(String fmt, int bc) { + def(fmt, bc, bc); + } + private static void def(String fmt, int from_bc, int to_bc) { + String[] fmts = { fmt, null }; + if (fmt.indexOf('w') > 0) { + fmts[1] = fmt.substring(fmt.indexOf('w')); + fmts[0] = fmt.substring(0, fmt.indexOf('w')); + } + for (int w = 0; w <= 1; w++) { + fmt = fmts[w]; + if (fmt == null) continue; + int length = fmt.length(); + int index = Math.max(0, fmt.indexOf('k')); + int tag = CONSTANT_None; + int branch = Math.max(0, fmt.indexOf('o')); + int slot = Math.max(0, fmt.indexOf('l')); + int con = Math.max(0, fmt.indexOf('x')); + if (index > 0 && index+1 < length) { + switch (fmt.charAt(index+1)) { + case 'c': tag = CONSTANT_Class; break; + case 'k': tag = CONSTANT_LoadableValue; break; + case 'f': tag = CONSTANT_Fieldref; break; + case 'm': tag = CONSTANT_Methodref; break; + case 'i': tag = CONSTANT_InterfaceMethodref; break; + case 'y': tag = CONSTANT_InvokeDynamic; break; + } + assert(tag != CONSTANT_None); + } else if (index > 0 && length == 2) { + assert(from_bc == _ldc); + tag = CONSTANT_LoadableValue; // _ldc opcode only + } + for (int bc = from_bc; bc <= to_bc; bc++) { + BC_FORMAT[w][bc] = fmt; + assert(BC_LENGTH[w][bc] == -1); + BC_LENGTH[w][bc] = (byte) length; + BC_INDEX[w][bc] = (byte) index; + BC_TAG[w][bc] = (byte) tag; + assert(!(index == 0 && tag != CONSTANT_None)); + BC_BRANCH[w][bc] = (byte) branch; + BC_SLOT[w][bc] = (byte) slot; + assert(branch == 0 || slot == 0); // not both branch & local + assert(branch == 0 || index == 0); // not both branch & cp + assert(slot == 0 || index == 0); // not both local & cp + BC_CON[w][bc] = (byte) con; + } + } + } + + public static void opcodeChecker(byte[] code, ConstantPool.Entry[] cpMap, + Package.Version clsVersion) throws FormatException { + Instruction i = at(code, 0); + while (i != null) { + int opcode = i.getBC(); + if (opcode < _nop || opcode > _jsr_w) { + String message = "illegal opcode: " + opcode + " " + i; + throw new FormatException(message); + } + ConstantPool.Entry e = i.getCPRef(cpMap); + if (e != null) { + byte tag = i.getCPTag(); + boolean match = e.tagMatches(tag); + if (!match && + (i.bc == _invokespecial || i.bc == _invokestatic) && + e.tagMatches(CONSTANT_InterfaceMethodref) && + clsVersion.greaterThan(Constants.JAVA7_MAX_CLASS_VERSION)) { + match = true; + } + if (!match) { + String message = "illegal reference, expected type=" + + ConstantPool.tagName(tag) + ": " + + i.toString(cpMap); + throw new FormatException(message); + } + } + i = i.next(); + } + } + static class FormatException extends IOException { + private static final long serialVersionUID = 3175572275651367015L; + + FormatException(String message) { + super(message); + } + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/NOTE b/src/main/java/net/minecraftforge/gradle/util/pack200/NOTE new file mode 100644 index 000000000..1d70f309c --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/NOTE @@ -0,0 +1,8 @@ +ForgeGradle relies on pack200 for its binary patches. +The pack200 classes in Java were deprecated for removal in Java 11 and removed in Java 14, so for ForgeGradle to +continue working in Java 16, we cannot rely on the JVM to provide pack200. +Instead, we re-package OpenJDK 11's implementation (https://github.com/openjdk/jdk/tree/jdk-11%2B28) in this package. +We do not build the native unpacker, so it will always fall back to the Java one. + +OpenJDK 11 is licensed under the GPLv2, so we can combine it with FG source which is licensed under LGPLv2.1, yielding +a combined jar licensed under GPLv2. \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/NativeUnpack.java b/src/main/java/net/minecraftforge/gradle/util/pack200/NativeUnpack.java new file mode 100644 index 000000000..0a87fed45 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/NativeUnpack.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package net.minecraftforge.gradle.util.pack200; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.jar.JarOutputStream; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +@SuppressWarnings({"removal"}) +class NativeUnpack { + // Pointer to the native unpacker obj + private long unpackerPtr; + + // Input stream. + private BufferedInputStream in; + + private static synchronized native void initIDs(); + + // Starts processing at the indicated position in the buffer. + // If the buffer is null, the readInputFn callback is used to get bytes. + // Returns (s<<32|f), the number of following segments and files. + private synchronized native long start(ByteBuffer buf, long offset); + + // Returns true if there's another, and fills in the parts. + private synchronized native boolean getNextFile(Object[] parts); + + private synchronized native ByteBuffer getUnusedInput(); + + // Resets the engine and frees all resources. + // Returns total number of bytes consumed by the engine. + private synchronized native long finish(); + + // Setting state in the unpacker. + protected synchronized native boolean setOption(String opt, String value); + protected synchronized native String getOption(String opt); + + private int _verbose; + + // State for progress bar: + private long _byteCount; // bytes read in current segment + private int _segCount; // number of segs scanned + private int _fileCount; // number of files written + private long _estByteLimit; // estimate of eventual total + private int _estSegLimit; // ditto + private int _estFileLimit; // ditto + private int _prevPercent = -1; // for monotonicity + + private final CRC32 _crc32 = new CRC32(); + private byte[] _buf = new byte[1<<14]; + + private UnpackerImpl _p200; + private PropMap _props; + + static { + // If loading from stand alone build uncomment this. + // System.loadLibrary("unpack"); + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction<>() { + public Void run() { + System.loadLibrary("unpack"); + return null; + } + }); + initIDs(); + } + + NativeUnpack(UnpackerImpl p200) { + super(); + _p200 = p200; + _props = p200.props; + p200._nunp = this; + } + + // for JNI callbacks + private static Object currentInstance() { + UnpackerImpl p200 = (UnpackerImpl) Utils.getTLGlobals(); + return (p200 == null)? null: p200._nunp; + } + + private synchronized long getUnpackerPtr() { + return unpackerPtr; + } + + // Callback from the unpacker engine to get more data. + private long readInputFn(ByteBuffer pbuf, long minlen) throws IOException { + if (in == null) return 0; // nothing is readable + long maxlen = pbuf.capacity() - pbuf.position(); + assert(minlen <= maxlen); // don't talk nonsense + long numread = 0; + int steps = 0; + while (numread < minlen) { + steps++; + // read available input, up to buf.length or maxlen + int readlen = _buf.length; + if (readlen > (maxlen - numread)) + readlen = (int)(maxlen - numread); + int nr = in.read(_buf, 0, readlen); + if (nr <= 0) break; + numread += nr; + assert(numread <= maxlen); + // %%% get rid of this extra copy by using nio? + pbuf.put(_buf, 0, nr); + } + if (_verbose > 1) + Utils.log.fine("readInputFn("+minlen+","+maxlen+") => "+numread+" steps="+steps); + if (maxlen > 100) { + _estByteLimit = _byteCount + maxlen; + } else { + _estByteLimit = (_byteCount + numread) * 20; + } + _byteCount += numread; + updateProgress(); + return numread; + } + + private void updateProgress() { + // Progress is a combination of segment reading and file writing. + final double READ_WT = 0.33; + final double WRITE_WT = 0.67; + double readProgress = _segCount; + if (_estByteLimit > 0 && _byteCount > 0) + readProgress += (double)_byteCount / _estByteLimit; + double writeProgress = _fileCount; + double scaledProgress + = READ_WT * readProgress / Math.max(_estSegLimit,1) + + WRITE_WT * writeProgress / Math.max(_estFileLimit,1); + int percent = (int) Math.round(100*scaledProgress); + if (percent > 100) percent = 100; + if (percent > _prevPercent) { + _prevPercent = percent; + _props.setInteger(Pack200.Unpacker.PROGRESS, percent); + if (_verbose > 0) + Utils.log.info("progress = "+percent); + } + } + + private void copyInOption(String opt) { + String val = _props.getProperty(opt); + if (_verbose > 0) + Utils.log.info("set "+opt+"="+val); + if (val != null) { + boolean set = setOption(opt, val); + if (!set) + Utils.log.warning("Invalid option "+opt+"="+val); + } + } + + void run(InputStream inRaw, JarOutputStream jstream, + ByteBuffer presetInput) throws IOException { + BufferedInputStream in0 = new BufferedInputStream(inRaw); + this.in = in0; // for readInputFn to see + _verbose = _props.getInteger(Utils.DEBUG_VERBOSE); + // Fix for BugId: 4902477, -unpack.modification.time = 1059010598000 + // TODO eliminate and fix in unpack.cpp + + final int modtime = Pack200.Packer.KEEP.equals(_props.getProperty(Utils.UNPACK_MODIFICATION_TIME, "0")) ? + Constants.NO_MODTIME : _props.getTime(Utils.UNPACK_MODIFICATION_TIME); + + copyInOption(Utils.DEBUG_VERBOSE); + copyInOption(Pack200.Unpacker.DEFLATE_HINT); + if (modtime == Constants.NO_MODTIME) // Don't pass KEEP && NOW + copyInOption(Utils.UNPACK_MODIFICATION_TIME); + updateProgress(); // reset progress bar + for (;;) { + // Read the packed bits. + long counts = start(presetInput, 0); + _byteCount = _estByteLimit = 0; // reset partial scan counts + ++_segCount; // just finished scanning a whole segment... + int nextSeg = (int)( counts >>> 32 ); + int nextFile = (int)( counts >>> 0 ); + + // Estimate eventual total number of segments and files. + _estSegLimit = _segCount + nextSeg; + double filesAfterThisSeg = _fileCount + nextFile; + _estFileLimit = (int)( (filesAfterThisSeg * + _estSegLimit) / _segCount ); + + // Write the files. + int[] intParts = { 0,0, 0, 0 }; + // intParts = {size.hi/lo, mod, defl} + Object[] parts = { intParts, null, null, null }; + // parts = { {intParts}, name, data0/1 } + while (getNextFile(parts)) { + //BandStructure.printArrayTo(System.out, intParts, 0, parts.length); + String name = (String) parts[1]; + long size = ( (long)intParts[0] << 32) + + (((long)intParts[1] << 32) >>> 32); + + long mtime = (modtime != Constants.NO_MODTIME ) ? + modtime : intParts[2] ; + boolean deflateHint = (intParts[3] != 0); + ByteBuffer data0 = (ByteBuffer) parts[2]; + ByteBuffer data1 = (ByteBuffer) parts[3]; + writeEntry(jstream, name, mtime, size, deflateHint, + data0, data1); + ++_fileCount; + updateProgress(); + } + presetInput = getUnusedInput(); + long consumed = finish(); + if (_verbose > 0) + Utils.log.info("bytes consumed = "+consumed); + if (presetInput == null && + !Utils.isPackMagic(Utils.readMagic(in0))) { + break; + } + if (_verbose > 0 ) { + if (presetInput != null) + Utils.log.info("unused input = "+presetInput); + } + } + } + + void run(InputStream in, JarOutputStream jstream) throws IOException { + run(in, jstream, null); + } + + void run(File inFile, JarOutputStream jstream) throws IOException { + // %%% maybe memory-map the file, and pass it straight into unpacker + ByteBuffer mappedFile = null; + try (FileInputStream fis = new FileInputStream(inFile)) { + run(fis, jstream, mappedFile); + } + // Note: caller is responsible to finish with jstream. + } + + private void writeEntry(JarOutputStream j, String name, + long mtime, long lsize, boolean deflateHint, + ByteBuffer data0, ByteBuffer data1) throws IOException { + int size = (int)lsize; + if (size != lsize) + throw new IOException("file too large: "+lsize); + + CRC32 crc32 = _crc32; + + if (_verbose > 1) + Utils.log.fine("Writing entry: "+name+" size="+size + +(deflateHint?" deflated":"")); + + if (_buf.length < size) { + int newSize = size; + while (newSize < _buf.length) { + newSize <<= 1; + if (newSize <= 0) { + newSize = size; + break; + } + } + _buf = new byte[newSize]; + } + assert(_buf.length >= size); + + int fillp = 0; + if (data0 != null) { + int size0 = data0.capacity(); + data0.get(_buf, fillp, size0); + fillp += size0; + } + if (data1 != null) { + int size1 = data1.capacity(); + data1.get(_buf, fillp, size1); + fillp += size1; + } + while (fillp < size) { + // Fill in rest of data from the stream itself. + int nr = in.read(_buf, fillp, size - fillp); + if (nr <= 0) throw new IOException("EOF at end of archive"); + fillp += nr; + } + + ZipEntry z = new ZipEntry(name); + z.setTime(mtime * 1000); + + if (size == 0) { + z.setMethod(ZipOutputStream.STORED); + z.setSize(0); + z.setCrc(0); + z.setCompressedSize(0); + } else if (!deflateHint) { + z.setMethod(ZipOutputStream.STORED); + z.setSize(size); + z.setCompressedSize(size); + crc32.reset(); + crc32.update(_buf, 0, size); + z.setCrc(crc32.getValue()); + } else { + z.setMethod(Deflater.DEFLATED); + z.setSize(size); + } + + j.putNextEntry(z); + + if (size > 0) + j.write(_buf, 0, size); + + j.closeEntry(); + if (_verbose > 0) Utils.log.info("Writing " + Utils.zeString(z)); + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/Pack200.java b/src/main/java/net/minecraftforge/gradle/util/pack200/Pack200.java new file mode 100644 index 000000000..4569d817b --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/Pack200.java @@ -0,0 +1,719 @@ +/* + * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.minecraftforge.gradle.util.pack200; + +import java.util.SortedMap; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.File; +import java.io.IOException; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; + + +/** + * Transforms a JAR file to or from a packed stream in Pack200 format. + * Please refer to Network Transfer Format JSR 200 Specification + *

+ * Typically the packer engine is used by application developers + * to deploy or host JAR files on a website. + * The unpacker engine is used by deployment applications to + * transform the byte-stream back to JAR format. + *

+ * Here is an example using packer and unpacker: + *

{@code
+ *    import java.util.jar.Pack200;
+ *    import java.util.jar.Pack200.*;
+ *    ...
+ *    // Create the Packer object
+ *    Packer packer = Pack200.newPacker();
+ *
+ *    // Initialize the state by setting the desired properties
+ *    Map p = packer.properties();
+ *    // take more time choosing codings for better compression
+ *    p.put(Packer.EFFORT, "7");  // default is "5"
+ *    // use largest-possible archive segments (>10% better compression).
+ *    p.put(Packer.SEGMENT_LIMIT, "-1");
+ *    // reorder files for better compression.
+ *    p.put(Packer.KEEP_FILE_ORDER, Packer.FALSE);
+ *    // smear modification times to a single value.
+ *    p.put(Packer.MODIFICATION_TIME, Packer.LATEST);
+ *    // ignore all JAR deflation requests,
+ *    // transmitting a single request to use "store" mode.
+ *    p.put(Packer.DEFLATE_HINT, Packer.FALSE);
+ *    // discard debug attributes
+ *    p.put(Packer.CODE_ATTRIBUTE_PFX+"LineNumberTable", Packer.STRIP);
+ *    // throw an error if an attribute is unrecognized
+ *    p.put(Packer.UNKNOWN_ATTRIBUTE, Packer.ERROR);
+ *    // pass one class file uncompressed:
+ *    p.put(Packer.PASS_FILE_PFX+0, "mutants/Rogue.class");
+ *    try {
+ *        JarFile jarFile = new JarFile("/tmp/testref.jar");
+ *        FileOutputStream fos = new FileOutputStream("/tmp/test.pack");
+ *        // Call the packer
+ *        packer.pack(jarFile, fos);
+ *        jarFile.close();
+ *        fos.close();
+ *
+ *        File f = new File("/tmp/test.pack");
+ *        FileOutputStream fostream = new FileOutputStream("/tmp/test.jar");
+ *        JarOutputStream jostream = new JarOutputStream(fostream);
+ *        Unpacker unpacker = Pack200.newUnpacker();
+ *        // Call the unpacker
+ *        unpacker.unpack(f, jostream);
+ *        // Must explicitly close the output.
+ *        jostream.close();
+ *    } catch (IOException ioe) {
+ *        ioe.printStackTrace();
+ *    }
+ * }
+ *

+ * A Pack200 file compressed with gzip can be hosted on HTTP/1.1 web servers. + * The deployment applications can use "Accept-Encoding=pack200-gzip". This + * indicates to the server that the client application desires a version of + * the file encoded with Pack200 and further compressed with gzip. Please + * refer to the Java Deployment Guide for techniques and details. + *

+ * Unless otherwise noted, passing a {@code null} argument to a constructor or + * method in this class will cause a {@link NullPointerException} to be thrown. + * + * @author John Rose + * @author Kumar Srinivasan + * @since 1.5 + */ +public abstract class Pack200 { + private Pack200() {} //prevent instantiation + + // Static methods of the Pack200 class. + /** + * Obtain new instance of a class that implements Packer. + *

    + *
  • If the system property {@code java.util.jar.Pack200.Packer} + * is defined, then the value is taken to be the fully-qualified name + * of a concrete implementation class, which must implement Packer. + * This class is loaded and instantiated. If this process fails + * then an unspecified error is thrown.

  • + * + *
  • If an implementation has not been specified with the system + * property, then the system-default implementation class is instantiated, + * and the result is returned.

  • + *
+ * + *

Note: The returned object is not guaranteed to operate + * correctly if multiple threads use it at the same time. + * A multi-threaded application should either allocate multiple + * packer engines, or else serialize use of one engine with a lock. + * + * @return A newly allocated Packer engine. + */ + public static synchronized Packer newPacker() { + return (Packer) newInstance(PACK_PROVIDER); + } + + + /** + * Obtain new instance of a class that implements Unpacker. + *

    + *
  • If the system property {@code java.util.jar.Pack200.Unpacker} + * is defined, then the value is taken to be the fully-qualified + * name of a concrete implementation class, which must implement Unpacker. + * The class is loaded and instantiated. If this process fails + * then an unspecified error is thrown.

  • + * + *
  • If an implementation has not been specified with the + * system property, then the system-default implementation class + * is instantiated, and the result is returned.

  • + *
+ * + *

Note: The returned object is not guaranteed to operate + * correctly if multiple threads use it at the same time. + * A multi-threaded application should either allocate multiple + * unpacker engines, or else serialize use of one engine with a lock. + * + * @return A newly allocated Unpacker engine. + */ + + public static Unpacker newUnpacker() { + return (Unpacker) newInstance(UNPACK_PROVIDER); + } + + // Interfaces + /** + * The packer engine applies various transformations to the input JAR file, + * making the pack stream highly compressible by a compressor such as + * gzip or zip. An instance of the engine can be obtained + * using {@link #newPacker}. + + * The high degree of compression is achieved + * by using a number of techniques described in the JSR 200 specification. + * Some of the techniques are sorting, re-ordering and co-location of the + * constant pool. + *

+ * The pack engine is initialized to an initial state as described + * by their properties below. + * The initial state can be manipulated by getting the + * engine properties (using {@link #properties}) and storing + * the modified properties on the map. + * The resource files will be passed through with no changes at all. + * The class files will not contain identical bytes, since the unpacker + * is free to change minor class file features such as constant pool order. + * However, the class files will be semantically identical, + * as specified in + * The Java™ Virtual Machine Specification. + *

+ * By default, the packer does not change the order of JAR elements. + * Also, the modification time and deflation hint of each + * JAR element is passed unchanged. + * (Any other ZIP-archive information, such as extra attributes + * giving Unix file permissions, are lost.) + *

+ * Note that packing and unpacking a JAR will in general alter the + * bytewise contents of classfiles in the JAR. This means that packing + * and unpacking will in general invalidate any digital signatures + * which rely on bytewise images of JAR elements. In order both to sign + * and to pack a JAR, you must first pack and unpack the JAR to + * "normalize" it, then compute signatures on the unpacked JAR elements, + * and finally repack the signed JAR. + * Both packing steps should + * use precisely the same options, and the segment limit may also + * need to be set to "-1", to prevent accidental variation of segment + * boundaries as class file sizes change slightly. + *

+ * (Here's why this works: Any reordering the packer does + * of any classfile structures is idempotent, so the second packing + * does not change the orderings produced by the first packing. + * Also, the unpacker is guaranteed by the JSR 200 specification + * to produce a specific bytewise image for any given transmission + * ordering of archive elements.) + *

+ * In order to maintain backward compatibility, the pack file's version is + * set to accommodate the class files present in the input JAR file. In + * other words, the pack file version will be the latest, if the class files + * are the latest and conversely the pack file version will be the oldest + * if the class file versions are also the oldest. For intermediate class + * file versions the corresponding pack file version will be used. + * For example: + * If the input JAR-files are solely comprised of 1.5 (or lesser) + * class files, a 1.5 compatible pack file is produced. This will also be + * the case for archives that have no class files. + * If the input JAR-files contains a 1.6 class file, then the pack file + * version will be set to 1.6. + *

+ * Note: Unless otherwise noted, passing a {@code null} argument to a + * constructor or method in this class will cause a {@link NullPointerException} + * to be thrown. + * + * @since 1.5 + */ + public interface Packer { + /** + * This property is a numeral giving the estimated target size N + * (in bytes) of each archive segment. + * If a single input file requires more than N bytes, + * it will be given its own archive segment. + *

+ * As a special case, a value of -1 will produce a single large + * segment with all input files, while a value of 0 will + * produce one segment for each class. + * Larger archive segments result in less fragmentation and + * better compression, but processing them requires more memory. + *

+ * The size of each segment is estimated by counting the size of each + * input file to be transmitted in the segment, along with the size + * of its name and other transmitted properties. + *

+ * The default is -1, which means the packer will always create a single + * segment output file. In cases where extremely large output files are + * generated, users are strongly encouraged to use segmenting or break + * up the input file into smaller JARs. + *

+ * A 10Mb JAR packed without this limit will + * typically pack about 10% smaller, but the packer may require + * a larger Java heap (about ten times the segment limit). + */ + String SEGMENT_LIMIT = "pack.segment.limit"; + + /** + * If this property is set to {@link #TRUE}, the packer will transmit + * all elements in their original order within the source archive. + *

+ * If it is set to {@link #FALSE}, the packer may reorder elements, + * and also remove JAR directory entries, which carry no useful + * information for Java applications. + * (Typically this enables better compression.) + *

+ * The default is {@link #TRUE}, which preserves the input information, + * but may cause the transmitted archive to be larger than necessary. + */ + String KEEP_FILE_ORDER = "pack.keep.file.order"; + + + /** + * If this property is set to a single decimal digit, the packer will + * use the indicated amount of effort in compressing the archive. + * Level 1 may produce somewhat larger size and faster compression speed, + * while level 9 will take much longer but may produce better compression. + *

+ * The special value 0 instructs the packer to copy through the + * original JAR file directly, with no compression. The JSR 200 + * standard requires any unpacker to understand this special case + * as a pass-through of the entire archive. + *

+ * The default is 5, investing a modest amount of time to + * produce reasonable compression. + */ + String EFFORT = "pack.effort"; + + /** + * If this property is set to {@link #TRUE} or {@link #FALSE}, the packer + * will set the deflation hint accordingly in the output archive, and + * will not transmit the individual deflation hints of archive elements. + *

+ * If this property is set to the special string {@link #KEEP}, the packer + * will attempt to determine an independent deflation hint for each + * available element of the input archive, and transmit this hint separately. + *

+ * The default is {@link #KEEP}, which preserves the input information, + * but may cause the transmitted archive to be larger than necessary. + *

+ * It is up to the unpacker implementation + * to take action upon the hint to suitably compress the elements of + * the resulting unpacked jar. + *

+ * The deflation hint of a ZIP or JAR element indicates + * whether the element was deflated or stored directly. + */ + String DEFLATE_HINT = "pack.deflate.hint"; + + /** + * If this property is set to the special string {@link #LATEST}, + * the packer will attempt to determine the latest modification time, + * among all the available entries in the original archive or the latest + * modification time of all the available entries in each segment. + * This single value will be transmitted as part of the segment and applied + * to all the entries in each segment, {@link #SEGMENT_LIMIT}. + *

+ * This can marginally decrease the transmitted size of the + * archive, at the expense of setting all installed files to a single + * date. + *

+ * If this property is set to the special string {@link #KEEP}, + * the packer transmits a separate modification time for each input + * element. + *

+ * The default is {@link #KEEP}, which preserves the input information, + * but may cause the transmitted archive to be larger than necessary. + *

+ * It is up to the unpacker implementation to take action to suitably + * set the modification time of each element of its output file. + * @see #SEGMENT_LIMIT + */ + String MODIFICATION_TIME = "pack.modification.time"; + + /** + * Indicates that a file should be passed through bytewise, with no + * compression. Multiple files may be specified by specifying + * additional properties with distinct strings appended, to + * make a family of properties with the common prefix. + *

+ * There is no pathname transformation, except + * that the system file separator is replaced by the JAR file + * separator '/'. + *

+ * The resulting file names must match exactly as strings with their + * occurrences in the JAR file. + *

+ * If a property value is a directory name, all files under that + * directory will be passed also. + *

+ * Examples: + *

{@code
+         *     Map p = packer.properties();
+         *     p.put(PASS_FILE_PFX+0, "mutants/Rogue.class");
+         *     p.put(PASS_FILE_PFX+1, "mutants/Wolverine.class");
+         *     p.put(PASS_FILE_PFX+2, "mutants/Storm.class");
+         *     # Pass all files in an entire directory hierarchy:
+         *     p.put(PASS_FILE_PFX+3, "police/");
+         * }
+ */ + String PASS_FILE_PFX = "pack.pass.file."; + + /// Attribute control. + + /** + * Indicates the action to take when a class-file containing an unknown + * attribute is encountered. Possible values are the strings {@link #ERROR}, + * {@link #STRIP}, and {@link #PASS}. + *

+ * The string {@link #ERROR} means that the pack operation + * as a whole will fail, with an exception of type {@code IOException}. + * The string + * {@link #STRIP} means that the attribute will be dropped. + * The string + * {@link #PASS} means that the whole class-file will be passed through + * (as if it were a resource file) without compression, with a suitable warning. + * This is the default value for this property. + *

+ * Examples: + *

{@code
+         *     Map p = pack200.getProperties();
+         *     p.put(UNKNOWN_ATTRIBUTE, ERROR);
+         *     p.put(UNKNOWN_ATTRIBUTE, STRIP);
+         *     p.put(UNKNOWN_ATTRIBUTE, PASS);
+         * }
+ */ + String UNKNOWN_ATTRIBUTE = "pack.unknown.attribute"; + + /** + * When concatenated with a class attribute name, + * indicates the format of that attribute, + * using the layout language specified in the JSR 200 specification. + *

+ * For example, the effect of this option is built in: + * {@code pack.class.attribute.SourceFile=RUH}. + *

+ * The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS} are + * also allowed, with the same meaning as {@link #UNKNOWN_ATTRIBUTE}. + * This provides a way for users to request that specific attributes be + * refused, stripped, or passed bitwise (with no class compression). + *

+ * Code like this might be used to support attributes for JCOV: + *

{@code
+         *     Map p = packer.properties();
+         *     p.put(CODE_ATTRIBUTE_PFX+"CoverageTable",       "NH[PHHII]");
+         *     p.put(CODE_ATTRIBUTE_PFX+"CharacterRangeTable", "NH[PHPOHIIH]");
+         *     p.put(CLASS_ATTRIBUTE_PFX+"SourceID",           "RUH");
+         *     p.put(CLASS_ATTRIBUTE_PFX+"CompilationID",      "RUH");
+         * }
+ *

+ * Code like this might be used to strip debugging attributes: + *

{@code
+         *     Map p = packer.properties();
+         *     p.put(CODE_ATTRIBUTE_PFX+"LineNumberTable",    STRIP);
+         *     p.put(CODE_ATTRIBUTE_PFX+"LocalVariableTable", STRIP);
+         *     p.put(CLASS_ATTRIBUTE_PFX+"SourceFile",        STRIP);
+         * }
+ */ + String CLASS_ATTRIBUTE_PFX = "pack.class.attribute."; + + /** + * When concatenated with a field attribute name, + * indicates the format of that attribute. + * For example, the effect of this option is built in: + * {@code pack.field.attribute.Deprecated=}. + * The special strings {@link #ERROR}, {@link #STRIP}, and + * {@link #PASS} are also allowed. + * @see #CLASS_ATTRIBUTE_PFX + */ + String FIELD_ATTRIBUTE_PFX = "pack.field.attribute."; + + /** + * When concatenated with a method attribute name, + * indicates the format of that attribute. + * For example, the effect of this option is built in: + * {@code pack.method.attribute.Exceptions=NH[RCH]}. + * The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS} + * are also allowed. + * @see #CLASS_ATTRIBUTE_PFX + */ + String METHOD_ATTRIBUTE_PFX = "pack.method.attribute."; + + /** + * When concatenated with a code attribute name, + * indicates the format of that attribute. + * For example, the effect of this option is built in: + * {@code pack.code.attribute.LocalVariableTable=NH[PHOHRUHRSHH]}. + * The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS} + * are also allowed. + * @see #CLASS_ATTRIBUTE_PFX + */ + String CODE_ATTRIBUTE_PFX = "pack.code.attribute."; + + /** + * The packer's progress as a percentage, as periodically + * updated by the packer. + * Values of 0 - 100 are normal, and -1 indicates a stall. + * Progress can be monitored by polling the value of this + * property. + *

+ * At a minimum, the packer must set progress to 0 + * at the beginning of a packing operation, and to 100 + * at the end. + */ + String PROGRESS = "pack.progress"; + + /** The string "keep", a possible value for certain properties. + * @see #DEFLATE_HINT + * @see #MODIFICATION_TIME + */ + String KEEP = "keep"; + + /** The string "pass", a possible value for certain properties. + * @see #UNKNOWN_ATTRIBUTE + * @see #CLASS_ATTRIBUTE_PFX + * @see #FIELD_ATTRIBUTE_PFX + * @see #METHOD_ATTRIBUTE_PFX + * @see #CODE_ATTRIBUTE_PFX + */ + String PASS = "pass"; + + /** The string "strip", a possible value for certain properties. + * @see #UNKNOWN_ATTRIBUTE + * @see #CLASS_ATTRIBUTE_PFX + * @see #FIELD_ATTRIBUTE_PFX + * @see #METHOD_ATTRIBUTE_PFX + * @see #CODE_ATTRIBUTE_PFX + */ + String STRIP = "strip"; + + /** The string "error", a possible value for certain properties. + * @see #UNKNOWN_ATTRIBUTE + * @see #CLASS_ATTRIBUTE_PFX + * @see #FIELD_ATTRIBUTE_PFX + * @see #METHOD_ATTRIBUTE_PFX + * @see #CODE_ATTRIBUTE_PFX + */ + String ERROR = "error"; + + /** The string "true", a possible value for certain properties. + * @see #KEEP_FILE_ORDER + * @see #DEFLATE_HINT + */ + String TRUE = "true"; + + /** The string "false", a possible value for certain properties. + * @see #KEEP_FILE_ORDER + * @see #DEFLATE_HINT + */ + String FALSE = "false"; + + /** The string "latest", a possible value for certain properties. + * @see #MODIFICATION_TIME + */ + String LATEST = "latest"; + + /** + * Get the set of this engine's properties. + * This set is a "live view", so that changing its + * contents immediately affects the Packer engine, and + * changes from the engine (such as progress indications) + * are immediately visible in the map. + * + *

The property map may contain pre-defined implementation + * specific and default properties. Users are encouraged to + * read the information and fully understand the implications, + * before modifying pre-existing properties. + *

+ * Implementation specific properties are prefixed with a + * package name associated with the implementor, beginning + * with {@code com.} or a similar prefix. + * All property names beginning with {@code pack.} and + * {@code unpack.} are reserved for use by this API. + *

+ * Unknown properties may be ignored or rejected with an + * unspecified error, and invalid entries may cause an + * unspecified error to be thrown. + * + *

+ * The returned map implements all optional {@link SortedMap} operations + * @return A sorted association of property key strings to property + * values. + */ + SortedMap properties(); + + /** + * Takes a JarFile and converts it into a Pack200 archive. + *

+ * Closes its input but not its output. (Pack200 archives are appendable.) + * @param in a JarFile + * @param out an OutputStream + * @exception IOException if an error is encountered. + */ + void pack(JarFile in, OutputStream out) throws IOException ; + + /** + * Takes a JarInputStream and converts it into a Pack200 archive. + *

+ * Closes its input but not its output. (Pack200 archives are appendable.) + *

+ * The modification time and deflation hint attributes are not available, + * for the JAR manifest file and its containing directory. + * + * @see #MODIFICATION_TIME + * @see #DEFLATE_HINT + * @param in a JarInputStream + * @param out an OutputStream + * @exception IOException if an error is encountered. + */ + void pack(JarInputStream in, OutputStream out) throws IOException ; + } + + /** + * The unpacker engine converts the packed stream to a JAR file. + * An instance of the engine can be obtained + * using {@link #newUnpacker}. + *

+ * Every JAR file produced by this engine will include the string + * "{@code PACK200}" as a zip file comment. + * This allows a deployer to detect if a JAR archive was packed and unpacked. + *

+ * Note: Unless otherwise noted, passing a {@code null} argument to a + * constructor or method in this class will cause a {@link NullPointerException} + * to be thrown. + *

+ * This version of the unpacker is compatible with all previous versions. + * @since 1.5 + */ + public interface Unpacker { + + /** The string "keep", a possible value for certain properties. + * @see #DEFLATE_HINT + */ + String KEEP = "keep"; + + /** The string "true", a possible value for certain properties. + * @see #DEFLATE_HINT + */ + String TRUE = "true"; + + /** The string "false", a possible value for certain properties. + * @see #DEFLATE_HINT + */ + String FALSE = "false"; + + /** + * Property indicating that the unpacker should + * ignore all transmitted values for DEFLATE_HINT, + * replacing them by the given value, {@link #TRUE} or {@link #FALSE}. + * The default value is the special string {@link #KEEP}, + * which asks the unpacker to preserve all transmitted + * deflation hints. + */ + String DEFLATE_HINT = "unpack.deflate.hint"; + + + + /** + * The unpacker's progress as a percentage, as periodically + * updated by the unpacker. + * Values of 0 - 100 are normal, and -1 indicates a stall. + * Progress can be monitored by polling the value of this + * property. + *

+ * At a minimum, the unpacker must set progress to 0 + * at the beginning of an unpacking operation, and to 100 + * at the end. + */ + String PROGRESS = "unpack.progress"; + + /** + * Get the set of this engine's properties. This set is + * a "live view", so that changing its + * contents immediately affects the Unpacker engine, and + * changes from the engine (such as progress indications) + * are immediately visible in the map. + * + *

The property map may contain pre-defined implementation + * specific and default properties. Users are encouraged to + * read the information and fully understand the implications, + * before modifying pre-existing properties. + *

+ * Implementation specific properties are prefixed with a + * package name associated with the implementor, beginning + * with {@code com.} or a similar prefix. + * All property names beginning with {@code pack.} and + * {@code unpack.} are reserved for use by this API. + *

+ * Unknown properties may be ignored or rejected with an + * unspecified error, and invalid entries may cause an + * unspecified error to be thrown. + * + * @return A sorted association of option key strings to option values. + */ + SortedMap properties(); + + /** + * Read a Pack200 archive, and write the encoded JAR to + * a JarOutputStream. + * The entire contents of the input stream will be read. + * It may be more efficient to read the Pack200 archive + * to a file and pass the File object, using the alternate + * method described below. + *

+ * Closes its input but not its output. (The output can accumulate more elements.) + * @param in an InputStream. + * @param out a JarOutputStream. + * @exception IOException if an error is encountered. + */ + void unpack(InputStream in, JarOutputStream out) throws IOException; + + /** + * Read a Pack200 archive, and write the encoded JAR to + * a JarOutputStream. + *

+ * Does not close its output. (The output can accumulate more elements.) + * @param in a File. + * @param out a JarOutputStream. + * @exception IOException if an error is encountered. + */ + void unpack(File in, JarOutputStream out) throws IOException; + } + + // Private stuff.... + + private static final String PACK_PROVIDER = "java.util.jar.Pack200.Packer"; + private static final String UNPACK_PROVIDER = "java.util.jar.Pack200.Unpacker"; + + private static Class packerImpl; + private static Class unpackerImpl; + + private static synchronized Object newInstance(String prop) { + String implName = "(unknown)"; + try { + Class impl = (PACK_PROVIDER.equals(prop))? packerImpl: unpackerImpl; + if (impl == null) { + // The first time, we must decide which class to use. + if (PACK_PROVIDER.equals(prop)) + impl = PackerImpl.class; + else + impl = UnpackerImpl.class; + } + // We have a class. Now instantiate it. + @SuppressWarnings("deprecation") + Object result = impl.newInstance(); + return result; + } catch (InstantiationException e) { + throw new Error("Could not instantiate: " + implName + + ":\ncheck property " + prop + + " in your properties file.", e); + } catch (IllegalAccessException e) { + throw new Error("Cannot access class: " + implName + + ":\ncheck property " + prop + + " in your properties file.", e); + } + } + +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/Package.java b/src/main/java/net/minecraftforge/gradle/util/pack200/Package.java new file mode 100644 index 000000000..3e80e0a31 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/Package.java @@ -0,0 +1,1376 @@ +/* + * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import net.minecraftforge.gradle.util.pack200.Attribute.Layout; +import net.minecraftforge.gradle.util.pack200.ConstantPool.ClassEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.DescriptorEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.BootstrapMethodEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.Index; +import net.minecraftforge.gradle.util.pack200.ConstantPool.LiteralEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.Utf8Entry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.Entry; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +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.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarFile; +import static net.minecraftforge.gradle.util.pack200.Constants.*; + +/** + * Define the main data structure transmitted by pack/unpack. + * @author John Rose + */ +class Package { + int verbose; + { + PropMap pmap = Utils.currentPropMap(); + if (pmap != null) + verbose = pmap.getInteger(Utils.DEBUG_VERBOSE); + } + + final int magic = JAVA_PACKAGE_MAGIC; + + int default_modtime = NO_MODTIME; + int default_options = 0; // FO_DEFLATE_HINT + + Version defaultClassVersion = null; + + // These fields can be adjusted by driver properties. + final Version minClassVersion; + final Version maxClassVersion; + // null, indicates that consensus rules during package write + final Version packageVersion; + + Version observedHighestClassVersion = null; + + + // What constants are used in this unit? + ConstantPool.IndexGroup cp = new ConstantPool.IndexGroup(); + + /* + * typically used by the PackageReader to set the defaults, in which + * case we take the defaults. + */ + public Package() { + minClassVersion = JAVA_MIN_CLASS_VERSION; + maxClassVersion = JAVA_MAX_CLASS_VERSION; + packageVersion = null; + } + + + /* + * Typically used by the PackerImpl during before packing, the defaults are + * overridden by the users preferences. + */ + public Package(Version minClassVersion, Version maxClassVersion, Version packageVersion) { + // Fill in permitted range of major/minor version numbers. + this.minClassVersion = minClassVersion == null + ? JAVA_MIN_CLASS_VERSION + : minClassVersion; + this.maxClassVersion = maxClassVersion == null + ? JAVA_MAX_CLASS_VERSION + : maxClassVersion; + this.packageVersion = packageVersion; + } + + + public void reset() { + cp = new ConstantPool.IndexGroup(); + classes.clear(); + files.clear(); + BandStructure.nextSeqForDebug = 0; + observedHighestClassVersion = null; + } + + // Special empty versions of Code and InnerClasses, used for markers. + public static final Attribute.Layout attrCodeEmpty; + public static final Attribute.Layout attrBootstrapMethodsEmpty; + public static final Attribute.Layout attrInnerClassesEmpty; + public static final Attribute.Layout attrSourceFileSpecial; + public static final Map attrDefs; + static { + Map ad = new HashMap<>(3); + attrCodeEmpty = Attribute.define(ad, ATTR_CONTEXT_METHOD, + "Code", "").layout(); + attrBootstrapMethodsEmpty = Attribute.define(ad, ATTR_CONTEXT_CLASS, + "BootstrapMethods", "").layout(); + attrInnerClassesEmpty = Attribute.define(ad, ATTR_CONTEXT_CLASS, + "InnerClasses", "").layout(); + attrSourceFileSpecial = Attribute.define(ad, ATTR_CONTEXT_CLASS, + "SourceFile", "RUNH").layout(); + attrDefs = Collections.unmodifiableMap(ad); + } + + Version getDefaultClassVersion() { + return defaultClassVersion; + } + + /** Return the highest version number of all classes, + * or 0 if there are no classes. + */ + private void setHighestClassVersion() { + if (observedHighestClassVersion != null) + return; + Version res = JAVA_MIN_CLASS_VERSION; // initial low value + for (Class cls : classes) { + Version ver = cls.getVersion(); + if (res.lessThan(ver)) res = ver; + } + observedHighestClassVersion = res; + } + + Version getHighestClassVersion() { + setHighestClassVersion(); + return observedHighestClassVersion; + } + + // What Java classes are in this unit? + + ArrayList classes = new ArrayList<>(); + + public List getClasses() { + return classes; + } + + public final + class Class extends Attribute.Holder implements Comparable { + public Package getPackage() { return Package.this; } + + // Optional file characteristics and data source (a "class stub") + File file; + + // File header + int magic; + Version version; + + // Local constant pool (one-way mapping of index => package cp). + Entry[] cpMap; + + // Class header + //int flags; // in Attribute.Holder.this.flags + ClassEntry thisClass; + ClassEntry superClass; + ClassEntry[] interfaces; + + // Class parts + ArrayList fields; + ArrayList methods; + //ArrayList attributes; // in Attribute.Holder.this.attributes + // Note that InnerClasses may be collected at the package level. + ArrayList innerClasses; + ArrayList bootstrapMethods; + + Class(int flags, ClassEntry thisClass, ClassEntry superClass, ClassEntry[] interfaces) { + this.magic = JAVA_MAGIC; + this.version = defaultClassVersion; + this.flags = flags; + this.thisClass = thisClass; + this.superClass = superClass; + this.interfaces = interfaces; + + boolean added = classes.add(this); + assert(added); + } + + Class(String classFile) { + // A blank class; must be read with a ClassReader, etc. + initFile(newStub(classFile)); + } + + List getFields() { return fields == null ? noFields : fields; } + List getMethods() { return methods == null ? noMethods : methods; } + + public String getName() { + return thisClass.stringValue(); + } + + Version getVersion() { + return this.version; + } + + // Note: equals and hashCode are identity-based. + public int compareTo(Class that) { + String n0 = this.getName(); + String n1 = that.getName(); + return n0.compareTo(n1); + } + + String getObviousSourceFile() { + return Package.getObviousSourceFile(getName()); + } + + private void transformSourceFile(boolean minimize) { + // Replace "obvious" SourceFile by null. + Attribute olda = getAttribute(attrSourceFileSpecial); + if (olda == null) + return; // no SourceFile attr. + String obvious = getObviousSourceFile(); + List ref = new ArrayList<>(1); + olda.visitRefs(this, VRM_PACKAGE, ref); + Utf8Entry sfName = (Utf8Entry) ref.get(0); + Attribute a = olda; + if (sfName == null) { + if (minimize) { + // A pair of zero bytes. Cannot use predef. layout. + a = Attribute.find(ATTR_CONTEXT_CLASS, "SourceFile", "H"); + a = a.addContent(new byte[2]); + } else { + // Expand null attribute to the obvious string. + byte[] bytes = new byte[2]; + sfName = getRefString(obvious); + Object f = null; + f = Fixups.addRefWithBytes(f, bytes, sfName); + a = attrSourceFileSpecial.addContent(bytes, f); + } + } else if (obvious.equals(sfName.stringValue())) { + if (minimize) { + // Replace by an all-zero attribute. + a = attrSourceFileSpecial.addContent(new byte[2]); + } else { + assert(false); + } + } + if (a != olda) { + if (verbose > 2) + Utils.log.fine("recoding obvious SourceFile="+obvious); + List newAttrs = new ArrayList<>(getAttributes()); + int where = newAttrs.indexOf(olda); + newAttrs.set(where, a); + setAttributes(newAttrs); + } + } + + void minimizeSourceFile() { + transformSourceFile(true); + } + void expandSourceFile() { + transformSourceFile(false); + } + + protected Entry[] getCPMap() { + return cpMap; + } + + protected void setCPMap(Entry[] cpMap) { + this.cpMap = cpMap; + } + + boolean hasBootstrapMethods() { + return bootstrapMethods != null && !bootstrapMethods.isEmpty(); + } + + List getBootstrapMethods() { + return bootstrapMethods; + } + + BootstrapMethodEntry[] getBootstrapMethodMap() { + return (hasBootstrapMethods()) + ? bootstrapMethods.toArray(new BootstrapMethodEntry[bootstrapMethods.size()]) + : null; + } + + void setBootstrapMethods(Collection bsms) { + assert(bootstrapMethods == null); // do not do this twice + bootstrapMethods = new ArrayList<>(bsms); + } + + boolean hasInnerClasses() { + return innerClasses != null; + } + List getInnerClasses() { + return innerClasses; + } + + public void setInnerClasses(Collection ics) { + innerClasses = (ics == null) ? null : new ArrayList<>(ics); + // Edit the attribute list, if necessary. + Attribute a = getAttribute(attrInnerClassesEmpty); + if (innerClasses != null && a == null) + addAttribute(attrInnerClassesEmpty.canonicalInstance()); + else if (innerClasses == null && a != null) + removeAttribute(a); + } + + /** Given a global map of ICs (keyed by thisClass), + * compute the subset of its Map.values which are + * required to be present in the local InnerClasses + * attribute. Perform this calculation without + * reference to any actual InnerClasses attribute. + *

+ * The order of the resulting list is consistent + * with that of Package.this.allInnerClasses. + */ + public List computeGloballyImpliedICs() { + Set cpRefs = new HashSet<>(); + { // This block temporarily displaces this.innerClasses. + ArrayList innerClassesSaved = innerClasses; + innerClasses = null; // ignore for the moment + visitRefs(VRM_CLASSIC, cpRefs); + innerClasses = innerClassesSaved; + } + ConstantPool.completeReferencesIn(cpRefs, true); + + Set icRefs = new HashSet<>(); + for (Entry e : cpRefs) { + // Restrict cpRefs to InnerClasses entries only. + if (!(e instanceof ClassEntry)) continue; + // For every IC reference, add its outers also. + while (e != null) { + InnerClass ic = getGlobalInnerClass(e); + if (ic == null) break; + if (!icRefs.add(e)) break; + e = ic.outerClass; + // If we add A$B$C to the mix, we must also add A$B. + } + } + // This loop is structured this way so as to accumulate + // entries into impliedICs in an order which reflects + // the order of allInnerClasses. + ArrayList impliedICs = new ArrayList<>(); + for (InnerClass ic : allInnerClasses) { + // This one is locally relevant if it describes + // a member of the current class, or if the current + // class uses it somehow. In the particular case + // where thisClass is an inner class, it will already + // be a member of icRefs. + if (icRefs.contains(ic.thisClass) + || ic.outerClass == this.thisClass) { + // Add every relevant class to the IC attribute: + if (verbose > 1) + Utils.log.fine("Relevant IC: "+ic); + impliedICs.add(ic); + } + } + return impliedICs; + } + + // Helper for both minimizing and expanding. + // Computes a symmetric difference. + private List computeICdiff() { + List impliedICs = computeGloballyImpliedICs(); + List actualICs = getInnerClasses(); + if (actualICs == null) + actualICs = Collections.emptyList(); + + // Symmetric difference is calculated from I, A like this: + // diff = (I+A) - (I*A) + // Note that the center C is unordered, but the result + // preserves the original ordering of I and A. + // + // Class file rules require that outers precede inners. + // So, add I before A, in case A$B$Z is local, but A$B + // is implicit. The reverse is never the case. + if (actualICs.isEmpty()) { + return impliedICs; + // Diff is I since A is empty. + } + if (impliedICs.isEmpty()) { + return actualICs; + // Diff is A since I is empty. + } + // (I*A) is non-trivial + Set center = new HashSet<>(actualICs); + center.retainAll(new HashSet<>(impliedICs)); + impliedICs.addAll(actualICs); + impliedICs.removeAll(center); + // Diff is now I^A = (I+A)-(I*A). + return impliedICs; + } + + /** When packing, anticipate the effect of expandLocalICs. + * Replace the local ICs by their symmetric difference + * with the globally implied ICs for this class; if this + * difference is empty, remove the local ICs altogether. + *

+ * An empty local IC attribute is reserved to signal + * the unpacker to delete the attribute altogether, + * so a missing local IC attribute signals the unpacker + * to use the globally implied ICs changed. + */ + void minimizeLocalICs() { + List diff = computeICdiff(); + List actualICs = innerClasses; + List localICs; // will be the diff, modulo edge cases + if (diff.isEmpty()) { + // No diff, so transmit no attribute. + localICs = null; + if (actualICs != null && actualICs.isEmpty()) { + // Odd case: No implied ICs, and a zero length attr. + // Do not support it directly. + if (verbose > 0) + Utils.log.info("Warning: Dropping empty InnerClasses attribute from "+this); + } + } else if (actualICs == null) { + // No local IC attribute, even though some are implied. + // Signal with trivial attribute. + localICs = Collections.emptyList(); + } else { + // Transmit a non-empty diff, which will create + // a local ICs attribute. + localICs = diff; + } + // Reduce the set to the symmetric difference. + setInnerClasses(localICs); + if (verbose > 1 && localICs != null) + Utils.log.fine("keeping local ICs in "+this+": "+localICs); + } + + /** When unpacking, undo the effect of minimizeLocalICs. + * Must return negative if any IC tuples may have been deleted. + * Otherwise, return positive if any IC tuples were added. + */ + int expandLocalICs() { + List localICs = innerClasses; + List actualICs; + int changed; + if (localICs == null) { + // Diff was empty. (Common case.) + List impliedICs = computeGloballyImpliedICs(); + if (impliedICs.isEmpty()) { + actualICs = null; + changed = 0; + } else { + actualICs = impliedICs; + changed = 1; // added more tuples + } + } else if (localICs.isEmpty()) { + // It was a non-empty diff, but the local ICs were absent. + actualICs = null; + // [] => null, no tuple change, but attribute deletion. + changed = -1; + } else { + // Non-trivial diff was transmitted. + actualICs = computeICdiff(); + // If we only added more ICs, return +1. + changed = actualICs.containsAll(localICs)? +1: -1; + } + setInnerClasses(actualICs); + return changed; + } + + public abstract + class Member extends Attribute.Holder implements Comparable { + DescriptorEntry descriptor; + + protected Member(int flags, DescriptorEntry descriptor) { + this.flags = flags; + this.descriptor = descriptor; + } + + public Class thisClass() { return Class.this; } + + public DescriptorEntry getDescriptor() { + return descriptor; + } + public String getName() { + return descriptor.nameRef.stringValue(); + } + public String getType() { + return descriptor.typeRef.stringValue(); + } + + protected Entry[] getCPMap() { + return cpMap; + } + protected void visitRefs(int mode, Collection refs) { + if (verbose > 2) Utils.log.fine("visitRefs "+this); + // Careful: The descriptor is used by the package, + // but the classfile breaks it into component refs. + if (mode == VRM_CLASSIC) { + refs.add(descriptor.nameRef); + refs.add(descriptor.typeRef); + } else { + refs.add(descriptor); + } + // Handle attribute list: + super.visitRefs(mode, refs); + } + + public String toString() { + return Class.this + "." + descriptor.prettyString(); + } + } + + public + class Field extends Member { + // Order is significant for fields: It is visible to reflection. + int order; + + public Field(int flags, DescriptorEntry descriptor) { + super(flags, descriptor); + assert(!descriptor.isMethod()); + if (fields == null) + fields = new ArrayList<>(); + boolean added = fields.add(this); + assert(added); + order = fields.size(); + } + + public byte getLiteralTag() { + return descriptor.getLiteralTag(); + } + + public int compareTo(Member o) { + Field that = (Field)o; + return this.order - that.order; + } + } + + public + class Method extends Member { + // Code attribute is specially hardwired. + Code code; + + public Method(int flags, DescriptorEntry descriptor) { + super(flags, descriptor); + assert(descriptor.isMethod()); + if (methods == null) + methods = new ArrayList<>(); + boolean added = methods.add(this); + assert(added); + } + + public void trimToSize() { + super.trimToSize(); + if (code != null) + code.trimToSize(); + } + + public int getArgumentSize() { + int argSize = descriptor.typeRef.computeSize(true); + int thisSize = Modifier.isStatic(flags) ? 0 : 1; + return thisSize + argSize; + } + + // Sort methods in a canonical order (by type, then by name). + public int compareTo(Member o) { + Method that = (Method)o; + return this.getDescriptor().compareTo(that.getDescriptor()); + } + + public void strip(String attrName) { + if ("Code".equals(attrName)) + code = null; + if (code != null) + code.strip(attrName); + super.strip(attrName); + } + protected void visitRefs(int mode, Collection refs) { + super.visitRefs(mode, refs); + if (code != null) { + if (mode == VRM_CLASSIC) { + refs.add(getRefString("Code")); + } + code.visitRefs(mode, refs); + } + } + } + + public void trimToSize() { + super.trimToSize(); + for (int isM = 0; isM <= 1; isM++) { + ArrayList members = (isM == 0) ? fields : methods; + if (members == null) continue; + members.trimToSize(); + for (Member m : members) { + m.trimToSize(); + } + } + if (innerClasses != null) { + innerClasses.trimToSize(); + } + } + + public void strip(String attrName) { + if ("InnerClass".equals(attrName)) + innerClasses = null; + for (int isM = 0; isM <= 1; isM++) { + ArrayList members = (isM == 0) ? fields : methods; + if (members == null) continue; + for (Member m : members) { + m.strip(attrName); + } + } + super.strip(attrName); + } + + protected void visitRefs(int mode, Collection refs) { + if (verbose > 2) Utils.log.fine("visitRefs "+this); + refs.add(thisClass); + refs.add(superClass); + refs.addAll(Arrays.asList(interfaces)); + for (int isM = 0; isM <= 1; isM++) { + ArrayList members = (isM == 0) ? fields : methods; + if (members == null) continue; + for (Member m : members) { + boolean ok = false; + try { + m.visitRefs(mode, refs); + ok = true; + } finally { + if (!ok) + Utils.log.warning("Error scanning "+m); + } + } + } + visitInnerClassRefs(mode, refs); + // Handle attribute list: + super.visitRefs(mode, refs); + } + + protected void visitInnerClassRefs(int mode, Collection refs) { + Package.visitInnerClassRefs(innerClasses, mode, refs); + } + + // Hook called by ClassReader when it's done. + void finishReading() { + trimToSize(); + maybeChooseFileName(); + } + + public void initFile(File file) { + assert(this.file == null); // set-once + if (file == null) { + // Build a trivial stub. + file = newStub(canonicalFileName()); + } + this.file = file; + assert(file.isClassStub()); + file.stubClass = this; + maybeChooseFileName(); + } + + public void maybeChooseFileName() { + if (thisClass == null) { + return; // do not choose yet + } + String canonName = canonicalFileName(); + if (file.nameString.equals("")) { + file.nameString = canonName; + } + if (file.nameString.equals(canonName)) { + // The file name is predictable. Transmit "". + file.name = getRefString(""); + return; + } + // If name has not yet been looked up, find it now. + if (file.name == null) { + file.name = getRefString(file.nameString); + } + } + + public String canonicalFileName() { + if (thisClass == null) return null; + return thisClass.stringValue() + ".class"; + } + + public java.io.File getFileName(java.io.File parent) { + String name = file.name.stringValue(); + if (name.equals("")) + name = canonicalFileName(); + String fname = name.replace('/', java.io.File.separatorChar); + return new java.io.File(parent, fname); + } + public java.io.File getFileName() { + return getFileName(null); + } + + public String toString() { + return thisClass.stringValue(); + } + } + + void addClass(Class c) { + assert(c.getPackage() == this); + boolean added = classes.add(c); + assert(added); + // Make sure the class is represented in the total file order: + if (c.file == null) c.initFile(null); + addFile(c.file); + } + + // What non-class files are in this unit? + ArrayList files = new ArrayList<>(); + + public List getFiles() { + return files; + } + + public List getClassStubs() { + List classStubs = new ArrayList<>(classes.size()); + for (Class cls : classes) { + assert(cls.file.isClassStub()); + classStubs.add(cls.file); + } + return classStubs; + } + + public final class File implements Comparable { + String nameString; // true name of this file + Utf8Entry name; + int modtime = NO_MODTIME; + int options = 0; // random flag bits, such as deflate_hint + Class stubClass; // if this is a stub, here's the class + ArrayList prepend = new ArrayList<>(); // list of byte[] + java.io.ByteArrayOutputStream append = new ByteArrayOutputStream(); + + File(Utf8Entry name) { + this.name = name; + this.nameString = name.stringValue(); + // caller must fill in contents + } + File(String nameString) { + nameString = fixupFileName(nameString); + this.name = getRefString(nameString); + this.nameString = name.stringValue(); + } + + public boolean isDirectory() { + // JAR directory. Useless. + return nameString.endsWith("/"); + } + public boolean isClassStub() { + return (options & FO_IS_CLASS_STUB) != 0; + } + public Class getStubClass() { + assert(isClassStub()); + assert(stubClass != null); + return stubClass; + } + public boolean isTrivialClassStub() { + return isClassStub() + && name.stringValue().equals("") + && (modtime == NO_MODTIME || modtime == default_modtime) + && (options &~ FO_IS_CLASS_STUB) == 0; + } + + // The nameString is the key. Ignore other things. + // (Note: The name might be "", in the case of a trivial class stub.) + public boolean equals(Object o) { + if (o == null || (o.getClass() != File.class)) + return false; + File that = (File)o; + return that.nameString.equals(this.nameString); + } + public int hashCode() { + return nameString.hashCode(); + } + // Simple alphabetic sort. PackageWriter uses a better comparator. + public int compareTo(File that) { + return this.nameString.compareTo(that.nameString); + } + public String toString() { + return nameString+"{" + +(isClassStub()?"*":"") + +(BandStructure.testBit(options,FO_DEFLATE_HINT)?"@":"") + +(modtime==NO_MODTIME?"":"M"+modtime) + +(getFileLength()==0?"":"["+getFileLength()+"]") + +"}"; + } + + public java.io.File getFileName() { + return getFileName(null); + } + public java.io.File getFileName(java.io.File parent) { + String lname = this.nameString; + //if (name.startsWith("./")) name = name.substring(2); + String fname = lname.replace('/', java.io.File.separatorChar); + return new java.io.File(parent, fname); + } + + public void addBytes(byte[] bytes) { + addBytes(bytes, 0, bytes.length); + } + public void addBytes(byte[] bytes, int off, int len) { + if (((append.size() | len) << 2) < 0) { + prepend.add(append.toByteArray()); + append.reset(); + } + append.write(bytes, off, len); + } + public long getFileLength() { + long len = 0; + if (prepend == null || append == null) return 0; + for (byte[] block : prepend) { + len += block.length; + } + len += append.size(); + return len; + } + public void writeTo(OutputStream out) throws IOException { + if (prepend == null || append == null) return; + for (byte[] block : prepend) { + out.write(block); + } + append.writeTo(out); + } + public void readFrom(InputStream in) throws IOException { + byte[] buf = new byte[1 << 16]; + int nr; + while ((nr = in.read(buf)) > 0) { + addBytes(buf, 0, nr); + } + } + public InputStream getInputStream() { + InputStream in = new ByteArrayInputStream(append.toByteArray()); + if (prepend.isEmpty()) return in; + List isa = new ArrayList<>(prepend.size()+1); + for (byte[] bytes : prepend) { + isa.add(new ByteArrayInputStream(bytes)); + } + isa.add(in); + return new SequenceInputStream(Collections.enumeration(isa)); + } + + protected void visitRefs(int mode, Collection refs) { + assert(name != null); + refs.add(name); + } + } + + File newStub(String classFileNameString) { + File stub = new File(classFileNameString); + stub.options |= FO_IS_CLASS_STUB; + stub.prepend = null; + stub.append = null; // do not collect data + return stub; + } + + private static String fixupFileName(String name) { + String fname = name.replace(java.io.File.separatorChar, '/'); + if (fname.startsWith("/")) { + throw new IllegalArgumentException("absolute file name "+fname); + } + return fname; + } + + void addFile(File file) { + boolean added = files.add(file); + assert(added); + } + + // Is there a globally declared table of inner classes? + List allInnerClasses = new ArrayList<>(); + Map allInnerClassesByThis; + + public + List getAllInnerClasses() { + return allInnerClasses; + } + + public + void setAllInnerClasses(Collection ics) { + assert(ics != allInnerClasses); + allInnerClasses.clear(); + allInnerClasses.addAll(ics); + + // Make an index: + allInnerClassesByThis = new HashMap<>(allInnerClasses.size()); + for (InnerClass ic : allInnerClasses) { + Object pic = allInnerClassesByThis.put(ic.thisClass, ic); + assert(pic == null); // caller must ensure key uniqueness! + } + } + + /** Return a global inner class record for the given thisClass. */ + public + InnerClass getGlobalInnerClass(Entry thisClass) { + assert(thisClass instanceof ClassEntry); + return allInnerClassesByThis.get(thisClass); + } + + static + class InnerClass implements Comparable { + final ClassEntry thisClass; + final ClassEntry outerClass; + final Utf8Entry name; + final int flags; + + // Can name and outerClass be derived from thisClass? + final boolean predictable; + + // About 30% of inner classes are anonymous (in rt.jar). + // About 60% are class members; the rest are named locals. + // Nearly all have predictable outers and names. + + InnerClass(ClassEntry thisClass, ClassEntry outerClass, + Utf8Entry name, int flags) { + this.thisClass = thisClass; + this.outerClass = outerClass; + this.name = name; + this.flags = flags; + this.predictable = computePredictable(); + } + + private boolean computePredictable() { + //System.out.println("computePredictable "+outerClass+" "+this.name); + String[] parse = parseInnerClassName(thisClass.stringValue()); + if (parse == null) return false; + String pkgOuter = parse[0]; + //String number = parse[1]; + String lname = parse[2]; + String haveName = (this.name == null) ? null : this.name.stringValue(); + String haveOuter = (outerClass == null) ? null : outerClass.stringValue(); + boolean lpredictable = (lname == haveName && pkgOuter == haveOuter); + //System.out.println("computePredictable => "+predictable); + return lpredictable; + } + + public boolean equals(Object o) { + if (o == null || o.getClass() != InnerClass.class) + return false; + InnerClass that = (InnerClass)o; + return eq(this.thisClass, that.thisClass) + && eq(this.outerClass, that.outerClass) + && eq(this.name, that.name) + && this.flags == that.flags; + } + private static boolean eq(Object x, Object y) { + return (x == null)? y == null: x.equals(y); + } + public int hashCode() { + return thisClass.hashCode(); + } + public int compareTo(InnerClass that) { + return this.thisClass.compareTo(that.thisClass); + } + + protected void visitRefs(int mode, Collection refs) { + refs.add(thisClass); + if (mode == VRM_CLASSIC || !predictable) { + // If the name can be demangled, the package omits + // the products of demangling. Otherwise, include them. + refs.add(outerClass); + refs.add(name); + } + } + + public String toString() { + return thisClass.stringValue(); + } + } + + // Helper for building InnerClasses attributes. + private static + void visitInnerClassRefs(Collection innerClasses, int mode, Collection refs) { + if (innerClasses == null) { + return; // no attribute; nothing to do + } + if (mode == VRM_CLASSIC) { + refs.add(getRefString("InnerClasses")); + } + if (innerClasses.size() > 0) { + // Count the entries themselves: + for (InnerClass c : innerClasses) { + c.visitRefs(mode, refs); + } + } + } + + static String[] parseInnerClassName(String n) { + //System.out.println("parseInnerClassName "+n); + String pkgOuter, number, name; + int dollar1, dollar2; // pointers to $ in the pattern + // parse n = (/)*($)?($)? + int nlen = n.length(); + int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; + dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, n.length()); + if (dollar2 < pkglen) return null; + if (isDigitString(n, dollar2+1, nlen)) { + // n = (/)*$ + number = n.substring(dollar2+1, nlen); + name = null; + dollar1 = dollar2; + } else if ((dollar1 + = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2-1)) + > pkglen + && isDigitString(n, dollar1+1, dollar2)) { + // n = (/)*$$ + number = n.substring(dollar1+1, dollar2); + name = n.substring(dollar2+1, nlen).intern(); + } else { + // n = (/)*$ + dollar1 = dollar2; + number = null; + name = n.substring(dollar2+1, nlen).intern(); + } + if (number == null) + pkgOuter = n.substring(0, dollar1).intern(); + else + pkgOuter = null; + //System.out.println("parseInnerClassName parses "+pkgOuter+" "+number+" "+name); + return new String[] { pkgOuter, number, name }; + } + + private static final int SLASH_MIN = '.'; + private static final int SLASH_MAX = '/'; + private static final int DOLLAR_MIN = 0; + private static final int DOLLAR_MAX = '-'; + static { + assert(lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, "x$$y$", 4) == 2); + assert(lastIndexOf(SLASH_MIN, SLASH_MAX, "x//y/", 4) == 2); + } + + private static int lastIndexOf(int chMin, int chMax, String str, int pos) { + for (int i = pos; --i >= 0; ) { + int ch = str.charAt(i); + if (ch >= chMin && ch <= chMax) { + return i; + } + } + return -1; + } + + private static boolean isDigitString(String x, int beg, int end) { + if (beg == end) return false; // null string + for (int i = beg; i < end; i++) { + char ch = x.charAt(i); + if (!(ch >= '0' && ch <= '9')) return false; + } + return true; + } + + static String getObviousSourceFile(String className) { + String n = className; + int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; + n = n.substring(pkglen); + int cutoff = n.length(); + for (;;) { + // Work backwards, finding all '$', '#', etc. + int dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, cutoff-1); + if (dollar2 < 0) + break; + cutoff = dollar2; + if (cutoff == 0) + break; + } + String obvious = n.substring(0, cutoff)+".java"; + return obvious; + } +/* + static { + assert(getObviousSourceFile("foo").equals("foo.java")); + assert(getObviousSourceFile("foo/bar").equals("bar.java")); + assert(getObviousSourceFile("foo/bar$baz").equals("bar.java")); + assert(getObviousSourceFile("foo/bar#baz#1").equals("bar.java")); + assert(getObviousSourceFile("foo.bar.baz#1").equals("baz.java")); + } +*/ + + static Utf8Entry getRefString(String s) { + return ConstantPool.getUtf8Entry(s); + } + + static LiteralEntry getRefLiteral(Comparable s) { + return ConstantPool.getLiteralEntry(s); + } + + void stripAttributeKind(String what) { + // what is one of { Debug, Compile, Constant, Exceptions, InnerClasses } + if (verbose > 0) + Utils.log.info("Stripping "+what.toLowerCase()+" data and attributes..."); + switch (what) { + case "Debug": + strip("SourceFile"); + strip("LineNumberTable"); + strip("LocalVariableTable"); + strip("LocalVariableTypeTable"); + break; + case "Compile": + // Keep the inner classes normally. + // Although they have no effect on execution, + // the Reflection API exposes them, and JCK checks them. + // NO: // strip("InnerClasses"); + strip("Deprecated"); + strip("Synthetic"); + break; + case "Exceptions": + // Keep the exceptions normally. + // Although they have no effect on execution, + // the Reflection API exposes them, and JCK checks them. + strip("Exceptions"); + break; + case "Constant": + stripConstantFields(); + break; + } + } + + public void trimToSize() { + classes.trimToSize(); + for (Class c : classes) { + c.trimToSize(); + } + files.trimToSize(); + } + + public void strip(String attrName) { + for (Class c : classes) { + c.strip(attrName); + } + } + + public void stripConstantFields() { + for (Class c : classes) { + for (Iterator j = c.fields.iterator(); j.hasNext(); ) { + Class.Field f = j.next(); + if (Modifier.isFinal(f.flags) + // do not strip non-static finals: + && Modifier.isStatic(f.flags) + && f.getAttribute("ConstantValue") != null + && !f.getName().startsWith("serial")) { + if (verbose > 2) { + Utils.log.fine(">> Strip "+this+" ConstantValue"); + j.remove(); + } + } + } + } + } + + protected void visitRefs(int mode, Collection refs) { + for ( Class c : classes) { + c.visitRefs(mode, refs); + } + if (mode != VRM_CLASSIC) { + for (File f : files) { + f.visitRefs(mode, refs); + } + visitInnerClassRefs(allInnerClasses, mode, refs); + } + } + + // Use this before writing the package file. + // It sorts files into a new order which seems likely to + // compress better. It also moves classes to the end of the + // file order. It also removes JAR directory entries, which + // are useless. + void reorderFiles(boolean keepClassOrder, boolean stripDirectories) { + // First reorder the classes, if that is allowed. + if (!keepClassOrder) { + // In one test with rt.jar, this trick gained 0.7% + Collections.sort(classes); + } + + // Remove stubs from resources; maybe we'll add them on at the end, + // if there are some non-trivial ones. The best case is that + // modtimes and options are not transmitted, and the stub files + // for class files do not need to be transmitted at all. + // Also + List stubs = getClassStubs(); + for (Iterator i = files.iterator(); i.hasNext(); ) { + File file = i.next(); + if (file.isClassStub() || + (stripDirectories && file.isDirectory())) { + i.remove(); + } + } + + // Sort the remaining non-class files. + // We sort them by file type. + // This keeps files of similar format near each other. + // Put class files at the end, keeping their fixed order. + // Be sure the JAR file's required manifest stays at the front. (4893051) + Collections.sort(files, new Comparator<>() { + public int compare(File r0, File r1) { + // Get the file name. + String f0 = r0.nameString; + String f1 = r1.nameString; + if (f0.equals(f1)) return 0; + if (JarFile.MANIFEST_NAME.equals(f0)) return 0-1; + if (JarFile.MANIFEST_NAME.equals(f1)) return 1-0; + // Extract file basename. + String n0 = f0.substring(1+f0.lastIndexOf('/')); + String n1 = f1.substring(1+f1.lastIndexOf('/')); + // Extract basename extension. + String x0 = n0.substring(1+n0.lastIndexOf('.')); + String x1 = n1.substring(1+n1.lastIndexOf('.')); + int r; + // Primary sort key is file extension. + r = x0.compareTo(x1); + if (r != 0) return r; + r = f0.compareTo(f1); + return r; + } + }); + + // Add back the class stubs after sorting, before trimStubs. + files.addAll(stubs); + } + + void trimStubs() { + // Restore enough non-trivial stubs to carry the needed class modtimes. + for (ListIterator i = files.listIterator(files.size()); i.hasPrevious(); ) { + File file = i.previous(); + if (!file.isTrivialClassStub()) { + if (verbose > 1) + Utils.log.fine("Keeping last non-trivial "+file); + break; + } + if (verbose > 2) + Utils.log.fine("Removing trivial "+file); + i.remove(); + } + + if (verbose > 0) { + Utils.log.info("Transmitting "+files.size()+" files, including per-file data for "+getClassStubs().size()+" classes out of "+classes.size()); + } + } + + // Use this before writing the package file. + void buildGlobalConstantPool(Set requiredEntries) { + if (verbose > 1) + Utils.log.fine("Checking for unused CP entries"); + requiredEntries.add(getRefString("")); // uconditionally present + visitRefs(VRM_PACKAGE, requiredEntries); + ConstantPool.completeReferencesIn(requiredEntries, false); + if (verbose > 1) + Utils.log.fine("Sorting CP entries"); + Index cpAllU = ConstantPool.makeIndex("unsorted", requiredEntries); + Index[] byTagU = ConstantPool.partitionByTag(cpAllU); + for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { + byte tag = ConstantPool.TAGS_IN_ORDER[i]; + // Work on all entries of a given kind. + Index ix = byTagU[tag]; + if (ix == null) continue; + ConstantPool.sort(ix); + cp.initIndexByTag(tag, ix); + byTagU[tag] = null; // done with it + } + for (int i = 0; i < byTagU.length; i++) { + Index ix = byTagU[i]; + assert(ix == null); // all consumed + } + for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { + byte tag = ConstantPool.TAGS_IN_ORDER[i]; + Index ix = cp.getIndexByTag(tag); + assert(ix.assertIsSorted()); + if (verbose > 2) Utils.log.fine(ix.dumpString()); + } + } + + // Use this before writing the class files. + void ensureAllClassFiles() { + Set fileSet = new HashSet<>(files); + for (Class cls : classes) { + // Add to the end of ths list: + if (!fileSet.contains(cls.file)) + files.add(cls.file); + } + } + + static final List noObjects = Arrays.asList(new Object[0]); + static final List noFields = Arrays.asList(new Class.Field[0]); + static final List noMethods = Arrays.asList(new Class.Method[0]); + static final List noInnerClasses = Arrays.asList(new InnerClass[0]); + + protected static final class Version { + + public final short major; + public final short minor; + + private Version(short major, short minor) { + this.major = major; + this.minor = minor; + } + + public String toString() { + return major + "." + minor; + } + + public boolean equals(Object that) { + return that instanceof Version + && major == ((Version)that).major + && minor == ((Version)that).minor; + } + + public int intValue() { + return (major << 16) + minor; + } + + public int hashCode() { + return (major << 16) + 7 + minor; + } + + public static Version of(int major, int minor) { + return new Version((short)major, (short)minor); + } + + public static Version of(byte[] bytes) { + int minor = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF); + int major = ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); + return new Version((short)major, (short)minor); + } + + public static Version of(int major_minor) { + short minor = (short)major_minor; + short major = (short)(major_minor >>> 16); + return new Version(major, minor); + } + + public static Version makeVersion(PropMap props, String partialKey) { + int min = props.getInteger(Utils.COM_PREFIX + + partialKey + ".minver", -1); + int maj = props.getInteger(Utils.COM_PREFIX + + partialKey + ".majver", -1); + return min >= 0 && maj >= 0 ? Version.of(maj, min) : null; + } + public byte[] asBytes() { + byte[] bytes = { + (byte) (minor >> 8), (byte) minor, + (byte) (major >> 8), (byte) major + }; + return bytes; + } + public int compareTo(Version that) { + return this.intValue() - that.intValue(); + } + + public boolean lessThan(Version that) { + return compareTo(that) < 0 ; + } + + public boolean greaterThan(Version that) { + return compareTo(that) > 0 ; + } + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/PackageReader.java b/src/main/java/net/minecraftforge/gradle/util/pack200/PackageReader.java new file mode 100644 index 000000000..b89b27fee --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/PackageReader.java @@ -0,0 +1,2376 @@ +/* + * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import net.minecraftforge.gradle.util.pack200.ConstantPool.*; +import net.minecraftforge.gradle.util.pack200.Package.Class; +import net.minecraftforge.gradle.util.pack200.Package.File; +import net.minecraftforge.gradle.util.pack200.Package.InnerClass; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.PrintStream; +import java.io.FilterInputStream; +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; +import static net.minecraftforge.gradle.util.pack200.Constants.*; + +/** + * Reader for a package file. + * + * @see PackageWriter + * @author John Rose + */ +class PackageReader extends BandStructure { + Package pkg; + byte[] bytes; + LimitedBuffer in; + Package.Version packageVersion; + + PackageReader(Package pkg, InputStream in) throws IOException { + this.pkg = pkg; + this.in = new LimitedBuffer(in); + } + + /** A buffered input stream which is careful not to + * read its underlying stream ahead of a given mark, + * called the 'readLimit'. This property declares + * the maximum number of characters that future reads + * can consume from the underlying stream. + */ + static + class LimitedBuffer extends BufferedInputStream { + long served; // total number of charburgers served + int servedPos; // ...as of this value of super.pos + long limit; // current declared limit + long buffered; + public boolean atLimit() { + boolean z = (getBytesServed() == limit); + assert(!z || limit == buffered); + return z; + } + public long getBytesServed() { + return served + (pos - servedPos); + } + public void setReadLimit(long newLimit) { + if (newLimit == -1) + limit = -1; + else + limit = getBytesServed() + newLimit; + } + public long getReadLimit() { + if (limit == -1) + return limit; + else + return limit - getBytesServed(); + } + public int read() throws IOException { + if (pos < count) { + // fast path + return buf[pos++] & 0xFF; + } + served += (pos - servedPos); + int ch = super.read(); + servedPos = pos; + if (ch >= 0) served += 1; + assert(served <= limit || limit == -1); + return ch; + } + public int read(byte b[], int off, int len) throws IOException { + served += (pos - servedPos); + int nr = super.read(b, off, len); + servedPos = pos; + if (nr >= 0) served += nr; + //assert(served <= limit || limit == -1); + return nr; + } + public long skip(long n) throws IOException { + throw new RuntimeException("no skipping"); + } + LimitedBuffer(InputStream originalIn) { + super(null, 1<<14); + servedPos = pos; + super.in = new FilterInputStream(originalIn) { + public int read() throws IOException { + if (buffered == limit) + return -1; + ++buffered; + return super.read(); + } + public int read(byte b[], int off, int len) throws IOException { + if (buffered == limit) + return -1; + if (limit != -1) { + long remaining = limit - buffered; + if (len > remaining) + len = (int)remaining; + } + int nr = super.read(b, off, len); + if (nr >= 0) buffered += nr; + return nr; + } + }; + } + } + + void read() throws IOException { + boolean ok = false; + try { + // pack200_archive: + // file_header + // *band_headers :BYTE1 + // cp_bands + // attr_definition_bands + // ic_bands + // class_bands + // bc_bands + // file_bands + readFileHeader(); + readBandHeaders(); + readConstantPool(); // cp_bands + readAttrDefs(); + readInnerClasses(); + Class[] classes = readClasses(); + readByteCodes(); + readFiles(); // file_bands + assert(archiveSize1 == 0 || in.atLimit()); + assert(archiveSize1 == 0 || + in.getBytesServed() == archiveSize0+archiveSize1); + all_bands.doneDisbursing(); + + // As a post-pass, build constant pools and inner classes. + for (int i = 0; i < classes.length; i++) { + reconstructClass(classes[i]); + } + + ok = true; + } catch (Exception ee) { + Utils.log.warning("Error on input: "+ee, ee); + if (verbose > 0) + Utils.log.info("Stream offsets:"+ + " served="+in.getBytesServed()+ + " buffered="+in.buffered+ + " limit="+in.limit); + //if (verbose > 0) ee.printStackTrace(); + if (ee instanceof IOException) throw (IOException)ee; + if (ee instanceof RuntimeException) throw (RuntimeException)ee; + throw new Error("error unpacking", ee); + } + } + + // Temporary count values, until band decoding gets rolling. + int[] tagCount = new int[CONSTANT_Limit]; + int numFiles; + int numAttrDefs; + int numInnerClasses; + int numClasses; + + void readFileHeader() throws IOException { + // file_header: + // archive_magic archive_header + readArchiveMagic(); + readArchiveHeader(); + } + + // Local routine used to parse fixed-format scalars + // in the file_header: + private int getMagicInt32() throws IOException { + int res = 0; + for (int i = 0; i < 4; i++) { + res <<= 8; + res |= (archive_magic.getByte() & 0xFF); + } + return res; + } + + static final int MAGIC_BYTES = 4; + + void readArchiveMagic() throws IOException { + // Read a minimum of bytes in the first gulp. + in.setReadLimit(MAGIC_BYTES + AH_LENGTH_MIN); + + // archive_magic: + // #archive_magic_word :BYTE1[4] + archive_magic.expectLength(MAGIC_BYTES); + archive_magic.readFrom(in); + + // read and check magic numbers: + int magic = getMagicInt32(); + if (pkg.magic != magic) { + throw new IOException("Unexpected package magic number: got " + + magic + "; expected " + pkg.magic); + } + archive_magic.doneDisbursing(); + } + + // Fixed 6211177, converted to throw IOException + void checkArchiveVersion() throws IOException { + Package.Version versionFound = null; + for (Package.Version v : new Package.Version[] { + JAVA8_PACKAGE_VERSION, + JAVA7_PACKAGE_VERSION, + JAVA6_PACKAGE_VERSION, + JAVA5_PACKAGE_VERSION + }) { + if (packageVersion.equals(v)) { + versionFound = v; + break; + } + } + if (versionFound == null) { + String expVer = JAVA8_PACKAGE_VERSION.toString() + + "OR" + + JAVA7_PACKAGE_VERSION.toString() + + " OR " + + JAVA6_PACKAGE_VERSION.toString() + + " OR " + + JAVA5_PACKAGE_VERSION.toString(); + throw new IOException("Unexpected package minor version: got " + + packageVersion.toString() + "; expected " + expVer); + } + } + + void readArchiveHeader() throws IOException { + // archive_header: + // #archive_minver :UNSIGNED5[1] + // #archive_majver :UNSIGNED5[1] + // #archive_options :UNSIGNED5[1] + // (archive_file_counts) ** (#have_file_headers) + // (archive_special_counts) ** (#have_special_formats) + // cp_counts + // class_counts + // + // archive_file_counts: + // #archive_size_hi :UNSIGNED5[1] + // #archive_size_lo :UNSIGNED5[1] + // #archive_next_count :UNSIGNED5[1] + // #archive_modtime :UNSIGNED5[1] + // #file_count :UNSIGNED5[1] + // + // class_counts: + // #ic_count :UNSIGNED5[1] + // #default_class_minver :UNSIGNED5[1] + // #default_class_majver :UNSIGNED5[1] + // #class_count :UNSIGNED5[1] + // + // archive_special_counts: + // #band_headers_size :UNSIGNED5[1] + // #attr_definition_count :UNSIGNED5[1] + // + archive_header_0.expectLength(AH_LENGTH_0); + archive_header_0.readFrom(in); + + int minver = archive_header_0.getInt(); + int majver = archive_header_0.getInt(); + packageVersion = Package.Version.of(majver, minver); + checkArchiveVersion(); + this.initHighestClassVersion(JAVA7_MAX_CLASS_VERSION); + + archiveOptions = archive_header_0.getInt(); + archive_header_0.doneDisbursing(); + + // detect archive optional fields in archive header + boolean haveSpecial = testBit(archiveOptions, AO_HAVE_SPECIAL_FORMATS); + boolean haveFiles = testBit(archiveOptions, AO_HAVE_FILE_HEADERS); + boolean haveNumbers = testBit(archiveOptions, AO_HAVE_CP_NUMBERS); + boolean haveCPExtra = testBit(archiveOptions, AO_HAVE_CP_EXTRAS); + initAttrIndexLimit(); + + // now we are ready to use the data: + archive_header_S.expectLength(haveFiles? AH_LENGTH_S: 0); + archive_header_S.readFrom(in); + if (haveFiles) { + long sizeHi = archive_header_S.getInt(); + long sizeLo = archive_header_S.getInt(); + archiveSize1 = (sizeHi << 32) + ((sizeLo << 32) >>> 32); + // Set the limit, now, up to the file_bits. + in.setReadLimit(archiveSize1); // for debug only + } else { + archiveSize1 = 0; + in.setReadLimit(-1); // remove limitation + } + archive_header_S.doneDisbursing(); + archiveSize0 = in.getBytesServed(); + + int remainingHeaders = AH_LENGTH_MIN - AH_LENGTH_0 - AH_LENGTH_S; + if (haveFiles) remainingHeaders += AH_FILE_HEADER_LEN; + if (haveSpecial) remainingHeaders += AH_SPECIAL_FORMAT_LEN; + if (haveNumbers) remainingHeaders += AH_CP_NUMBER_LEN; + if (haveCPExtra) remainingHeaders += AH_CP_EXTRA_LEN; + archive_header_1.expectLength(remainingHeaders); + archive_header_1.readFrom(in); + + if (haveFiles) { + archiveNextCount = archive_header_1.getInt(); + pkg.default_modtime = archive_header_1.getInt(); + numFiles = archive_header_1.getInt(); + } else { + archiveNextCount = 0; + numFiles = 0; + } + + if (haveSpecial) { + band_headers.expectLength(archive_header_1.getInt()); + numAttrDefs = archive_header_1.getInt(); + } else { + band_headers.expectLength(0); + numAttrDefs = 0; + } + + readConstantPoolCounts(haveNumbers, haveCPExtra); + + numInnerClasses = archive_header_1.getInt(); + + minver = (short) archive_header_1.getInt(); + majver = (short) archive_header_1.getInt(); + pkg.defaultClassVersion = Package.Version.of(majver, minver); + numClasses = archive_header_1.getInt(); + + archive_header_1.doneDisbursing(); + + // set some derived archive bits + if (testBit(archiveOptions, AO_DEFLATE_HINT)) { + pkg.default_options |= FO_DEFLATE_HINT; + } + } + + void readBandHeaders() throws IOException { + band_headers.readFrom(in); + bandHeaderBytePos = 1; // Leave room to pushback the initial XB byte. + bandHeaderBytes = new byte[bandHeaderBytePos + band_headers.length()]; + for (int i = bandHeaderBytePos; i < bandHeaderBytes.length; i++) { + bandHeaderBytes[i] = (byte) band_headers.getByte(); + } + band_headers.doneDisbursing(); + } + + void readConstantPoolCounts(boolean haveNumbers, boolean haveCPExtra) throws IOException { + // size the constant pools: + for (int k = 0; k < ConstantPool.TAGS_IN_ORDER.length; k++) { + // cp_counts: + // #cp_Utf8_count :UNSIGNED5[1] + // (cp_number_counts) ** (#have_cp_numbers) + // #cp_String_count :UNSIGNED5[1] + // #cp_Class_count :UNSIGNED5[1] + // #cp_Signature_count :UNSIGNED5[1] + // #cp_Descr_count :UNSIGNED5[1] + // #cp_Field_count :UNSIGNED5[1] + // #cp_Method_count :UNSIGNED5[1] + // #cp_Imethod_count :UNSIGNED5[1] + // (cp_attr_counts) ** (#have_cp_attr_counts) + // + // cp_number_counts: + // #cp_Int_count :UNSIGNED5[1] + // #cp_Float_count :UNSIGNED5[1] + // #cp_Long_count :UNSIGNED5[1] + // #cp_Double_count :UNSIGNED5[1] + // + // cp_extra_counts: + // #cp_MethodHandle_count :UNSIGNED5[1] + // #cp_MethodType_count :UNSIGNED5[1] + // #cp_InvokeDynamic_count :UNSIGNED5[1] + // #cp_BootstrapMethod_count :UNSIGNED5[1] + // + byte tag = ConstantPool.TAGS_IN_ORDER[k]; + if (!haveNumbers) { + // These four counts are optional. + switch (tag) { + case CONSTANT_Integer: + case CONSTANT_Float: + case CONSTANT_Long: + case CONSTANT_Double: + continue; + } + } + if (!haveCPExtra) { + // These four counts are optional. + switch (tag) { + case CONSTANT_MethodHandle: + case CONSTANT_MethodType: + case CONSTANT_InvokeDynamic: + case CONSTANT_BootstrapMethod: + continue; + } + } + tagCount[tag] = archive_header_1.getInt(); + } + } + + protected Index getCPIndex(byte tag) { + return pkg.cp.getIndexByTag(tag); + } + Index initCPIndex(byte tag, Entry[] cpMap) { + if (verbose > 3) { + for (int i = 0; i < cpMap.length; i++) { + Utils.log.fine("cp.add "+cpMap[i]); + } + } + Index index = ConstantPool.makeIndex(ConstantPool.tagName(tag), cpMap); + if (verbose > 1) Utils.log.fine("Read "+index); + pkg.cp.initIndexByTag(tag, index); + return index; + } + + void checkLegacy(String bandname) { + if (packageVersion.lessThan(JAVA7_PACKAGE_VERSION)) { + throw new RuntimeException("unexpected band " + bandname); + } + } + void readConstantPool() throws IOException { + // cp_bands: + // cp_Utf8 + // *cp_Int :UDELTA5 + // *cp_Float :UDELTA5 + // cp_Long + // cp_Double + // *cp_String :UDELTA5 (cp_Utf8) + // *cp_Class :UDELTA5 (cp_Utf8) + // cp_Signature + // cp_Descr + // cp_Field + // cp_Method + // cp_Imethod + + if (verbose > 0) Utils.log.info("Reading CP"); + + for (int k = 0; k < ConstantPool.TAGS_IN_ORDER.length; k++) { + byte tag = ConstantPool.TAGS_IN_ORDER[k]; + int len = tagCount[tag]; + + Entry[] cpMap = new Entry[len]; + if (verbose > 0) + Utils.log.info("Reading "+cpMap.length+" "+ConstantPool.tagName(tag)+" entries..."); + + switch (tag) { + case CONSTANT_Utf8: + readUtf8Bands(cpMap); + break; + case CONSTANT_Integer: + cp_Int.expectLength(cpMap.length); + cp_Int.readFrom(in); + for (int i = 0; i < cpMap.length; i++) { + int x = cp_Int.getInt(); // coding handles signs OK + cpMap[i] = ConstantPool.getLiteralEntry(x); + } + cp_Int.doneDisbursing(); + break; + case CONSTANT_Float: + cp_Float.expectLength(cpMap.length); + cp_Float.readFrom(in); + for (int i = 0; i < cpMap.length; i++) { + int x = cp_Float.getInt(); + float fx = Float.intBitsToFloat(x); + cpMap[i] = ConstantPool.getLiteralEntry(fx); + } + cp_Float.doneDisbursing(); + break; + case CONSTANT_Long: + // cp_Long: + // *cp_Long_hi :UDELTA5 + // *cp_Long_lo :DELTA5 + cp_Long_hi.expectLength(cpMap.length); + cp_Long_hi.readFrom(in); + cp_Long_lo.expectLength(cpMap.length); + cp_Long_lo.readFrom(in); + for (int i = 0; i < cpMap.length; i++) { + long hi = cp_Long_hi.getInt(); + long lo = cp_Long_lo.getInt(); + long x = (hi << 32) + ((lo << 32) >>> 32); + cpMap[i] = ConstantPool.getLiteralEntry(x); + } + cp_Long_hi.doneDisbursing(); + cp_Long_lo.doneDisbursing(); + break; + case CONSTANT_Double: + // cp_Double: + // *cp_Double_hi :UDELTA5 + // *cp_Double_lo :DELTA5 + cp_Double_hi.expectLength(cpMap.length); + cp_Double_hi.readFrom(in); + cp_Double_lo.expectLength(cpMap.length); + cp_Double_lo.readFrom(in); + for (int i = 0; i < cpMap.length; i++) { + long hi = cp_Double_hi.getInt(); + long lo = cp_Double_lo.getInt(); + long x = (hi << 32) + ((lo << 32) >>> 32); + double dx = Double.longBitsToDouble(x); + cpMap[i] = ConstantPool.getLiteralEntry(dx); + } + cp_Double_hi.doneDisbursing(); + cp_Double_lo.doneDisbursing(); + break; + case CONSTANT_String: + cp_String.expectLength(cpMap.length); + cp_String.readFrom(in); + cp_String.setIndex(getCPIndex(CONSTANT_Utf8)); + for (int i = 0; i < cpMap.length; i++) { + cpMap[i] = ConstantPool.getLiteralEntry(cp_String.getRef().stringValue()); + } + cp_String.doneDisbursing(); + break; + case CONSTANT_Class: + cp_Class.expectLength(cpMap.length); + cp_Class.readFrom(in); + cp_Class.setIndex(getCPIndex(CONSTANT_Utf8)); + for (int i = 0; i < cpMap.length; i++) { + cpMap[i] = ConstantPool.getClassEntry(cp_Class.getRef().stringValue()); + } + cp_Class.doneDisbursing(); + break; + case CONSTANT_Signature: + readSignatureBands(cpMap); + break; + case CONSTANT_NameandType: + // cp_Descr: + // *cp_Descr_type :DELTA5 (cp_Signature) + // *cp_Descr_name :UDELTA5 (cp_Utf8) + cp_Descr_name.expectLength(cpMap.length); + cp_Descr_name.readFrom(in); + cp_Descr_name.setIndex(getCPIndex(CONSTANT_Utf8)); + cp_Descr_type.expectLength(cpMap.length); + cp_Descr_type.readFrom(in); + cp_Descr_type.setIndex(getCPIndex(CONSTANT_Signature)); + for (int i = 0; i < cpMap.length; i++) { + Entry ref = cp_Descr_name.getRef(); + Entry ref2 = cp_Descr_type.getRef(); + cpMap[i] = ConstantPool.getDescriptorEntry((Utf8Entry)ref, + (SignatureEntry)ref2); + } + cp_Descr_name.doneDisbursing(); + cp_Descr_type.doneDisbursing(); + break; + case CONSTANT_Fieldref: + readMemberRefs(tag, cpMap, cp_Field_class, cp_Field_desc); + break; + case CONSTANT_Methodref: + readMemberRefs(tag, cpMap, cp_Method_class, cp_Method_desc); + break; + case CONSTANT_InterfaceMethodref: + readMemberRefs(tag, cpMap, cp_Imethod_class, cp_Imethod_desc); + break; + case CONSTANT_MethodHandle: + if (cpMap.length > 0) { + checkLegacy(cp_MethodHandle_refkind.name()); + } + cp_MethodHandle_refkind.expectLength(cpMap.length); + cp_MethodHandle_refkind.readFrom(in); + cp_MethodHandle_member.expectLength(cpMap.length); + cp_MethodHandle_member.readFrom(in); + cp_MethodHandle_member.setIndex(getCPIndex(CONSTANT_AnyMember)); + for (int i = 0; i < cpMap.length; i++) { + byte refKind = (byte) cp_MethodHandle_refkind.getInt(); + MemberEntry memRef = (MemberEntry) cp_MethodHandle_member.getRef(); + cpMap[i] = ConstantPool.getMethodHandleEntry(refKind, memRef); + } + cp_MethodHandle_refkind.doneDisbursing(); + cp_MethodHandle_member.doneDisbursing(); + break; + case CONSTANT_MethodType: + if (cpMap.length > 0) { + checkLegacy(cp_MethodType.name()); + } + cp_MethodType.expectLength(cpMap.length); + cp_MethodType.readFrom(in); + cp_MethodType.setIndex(getCPIndex(CONSTANT_Signature)); + for (int i = 0; i < cpMap.length; i++) { + SignatureEntry typeRef = (SignatureEntry) cp_MethodType.getRef(); + cpMap[i] = ConstantPool.getMethodTypeEntry(typeRef); + } + cp_MethodType.doneDisbursing(); + break; + case CONSTANT_InvokeDynamic: + if (cpMap.length > 0) { + checkLegacy(cp_InvokeDynamic_spec.name()); + } + cp_InvokeDynamic_spec.expectLength(cpMap.length); + cp_InvokeDynamic_spec.readFrom(in); + cp_InvokeDynamic_spec.setIndex(getCPIndex(CONSTANT_BootstrapMethod)); + cp_InvokeDynamic_desc.expectLength(cpMap.length); + cp_InvokeDynamic_desc.readFrom(in); + cp_InvokeDynamic_desc.setIndex(getCPIndex(CONSTANT_NameandType)); + for (int i = 0; i < cpMap.length; i++) { + BootstrapMethodEntry bss = (BootstrapMethodEntry) cp_InvokeDynamic_spec.getRef(); + DescriptorEntry descr = (DescriptorEntry) cp_InvokeDynamic_desc.getRef(); + cpMap[i] = ConstantPool.getInvokeDynamicEntry(bss, descr); + } + cp_InvokeDynamic_spec.doneDisbursing(); + cp_InvokeDynamic_desc.doneDisbursing(); + break; + case CONSTANT_BootstrapMethod: + if (cpMap.length > 0) { + checkLegacy(cp_BootstrapMethod_ref.name()); + } + cp_BootstrapMethod_ref.expectLength(cpMap.length); + cp_BootstrapMethod_ref.readFrom(in); + cp_BootstrapMethod_ref.setIndex(getCPIndex(CONSTANT_MethodHandle)); + cp_BootstrapMethod_arg_count.expectLength(cpMap.length); + cp_BootstrapMethod_arg_count.readFrom(in); + int totalArgCount = cp_BootstrapMethod_arg_count.getIntTotal(); + cp_BootstrapMethod_arg.expectLength(totalArgCount); + cp_BootstrapMethod_arg.readFrom(in); + cp_BootstrapMethod_arg.setIndex(getCPIndex(CONSTANT_LoadableValue)); + for (int i = 0; i < cpMap.length; i++) { + MethodHandleEntry bsm = (MethodHandleEntry) cp_BootstrapMethod_ref.getRef(); + int argc = cp_BootstrapMethod_arg_count.getInt(); + Entry[] argRefs = new Entry[argc]; + for (int j = 0; j < argc; j++) { + argRefs[j] = cp_BootstrapMethod_arg.getRef(); + } + cpMap[i] = ConstantPool.getBootstrapMethodEntry(bsm, argRefs); + } + cp_BootstrapMethod_ref.doneDisbursing(); + cp_BootstrapMethod_arg_count.doneDisbursing(); + cp_BootstrapMethod_arg.doneDisbursing(); + break; + default: + throw new AssertionError("unexpected CP tag in package"); + } + + Index index = initCPIndex(tag, cpMap); + + if (optDumpBands) { + try (PrintStream ps = new PrintStream(getDumpStream(index, ".idx"))) { + printArrayTo(ps, index.cpMap, 0, index.cpMap.length); + } + } + } + + cp_bands.doneDisbursing(); + + if (optDumpBands || verbose > 1) { + for (byte tag = CONSTANT_GroupFirst; tag < CONSTANT_GroupLimit; tag++) { + Index index = pkg.cp.getIndexByTag(tag); + if (index == null || index.isEmpty()) continue; + Entry[] cpMap = index.cpMap; + if (verbose > 1) + Utils.log.info("Index group "+ConstantPool.tagName(tag)+" contains "+cpMap.length+" entries."); + if (optDumpBands) { + try (PrintStream ps = new PrintStream(getDumpStream(index.debugName, tag, ".gidx", index))) { + printArrayTo(ps, cpMap, 0, cpMap.length, true); + } + } + } + } + + setBandIndexes(); + } + + void readUtf8Bands(Entry[] cpMap) throws IOException { + // cp_Utf8: + // *cp_Utf8_prefix :DELTA5 + // *cp_Utf8_suffix :UNSIGNED5 + // *cp_Utf8_chars :CHAR3 + // *cp_Utf8_big_suffix :DELTA5 + // (*cp_Utf8_big_chars :DELTA5) + // ** length(cp_Utf8_big_suffix) + int len = cpMap.length; + if (len == 0) + return; // nothing to read + + // Bands have implicit leading zeroes, for the empty string: + final int SUFFIX_SKIP_1 = 1; + final int PREFIX_SKIP_2 = 2; + + // First band: Read lengths of shared prefixes. + cp_Utf8_prefix.expectLength(Math.max(0, len - PREFIX_SKIP_2)); + cp_Utf8_prefix.readFrom(in); + + // Second band: Read lengths of unshared suffixes: + cp_Utf8_suffix.expectLength(Math.max(0, len - SUFFIX_SKIP_1)); + cp_Utf8_suffix.readFrom(in); + + char[][] suffixChars = new char[len][]; + int bigSuffixCount = 0; + + // Third band: Read the char values in the unshared suffixes: + cp_Utf8_chars.expectLength(cp_Utf8_suffix.getIntTotal()); + cp_Utf8_chars.readFrom(in); + for (int i = 0; i < len; i++) { + int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt(); + if (suffix == 0 && i >= SUFFIX_SKIP_1) { + // chars are packed in cp_Utf8_big_chars + bigSuffixCount += 1; + continue; + } + suffixChars[i] = new char[suffix]; + for (int j = 0; j < suffix; j++) { + int ch = cp_Utf8_chars.getInt(); + assert(ch == (char)ch); + suffixChars[i][j] = (char)ch; + } + } + cp_Utf8_chars.doneDisbursing(); + + // Fourth band: Go back and size the specially packed strings. + int maxChars = 0; + cp_Utf8_big_suffix.expectLength(bigSuffixCount); + cp_Utf8_big_suffix.readFrom(in); + cp_Utf8_suffix.resetForSecondPass(); + for (int i = 0; i < len; i++) { + int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt(); + int prefix = (i < PREFIX_SKIP_2)? 0: cp_Utf8_prefix.getInt(); + if (suffix == 0 && i >= SUFFIX_SKIP_1) { + assert(suffixChars[i] == null); + suffix = cp_Utf8_big_suffix.getInt(); + } else { + assert(suffixChars[i] != null); + } + if (maxChars < prefix + suffix) + maxChars = prefix + suffix; + } + char[] buf = new char[maxChars]; + + // Fifth band(s): Get the specially packed characters. + cp_Utf8_suffix.resetForSecondPass(); + cp_Utf8_big_suffix.resetForSecondPass(); + for (int i = 0; i < len; i++) { + if (i < SUFFIX_SKIP_1) continue; + int suffix = cp_Utf8_suffix.getInt(); + if (suffix != 0) continue; // already input + suffix = cp_Utf8_big_suffix.getInt(); + suffixChars[i] = new char[suffix]; + if (suffix == 0) { + // Do not bother to add an empty "(Utf8_big_0)" band. + continue; + } + IntBand packed = cp_Utf8_big_chars.newIntBand("(Utf8_big_"+i+")"); + packed.expectLength(suffix); + packed.readFrom(in); + for (int j = 0; j < suffix; j++) { + int ch = packed.getInt(); + assert(ch == (char)ch); + suffixChars[i][j] = (char)ch; + } + packed.doneDisbursing(); + } + cp_Utf8_big_chars.doneDisbursing(); + + // Finally, sew together all the prefixes and suffixes. + cp_Utf8_prefix.resetForSecondPass(); + cp_Utf8_suffix.resetForSecondPass(); + cp_Utf8_big_suffix.resetForSecondPass(); + for (int i = 0; i < len; i++) { + int prefix = (i < PREFIX_SKIP_2)? 0: cp_Utf8_prefix.getInt(); + int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt(); + if (suffix == 0 && i >= SUFFIX_SKIP_1) + suffix = cp_Utf8_big_suffix.getInt(); + + // by induction, the buffer is already filled with the prefix + System.arraycopy(suffixChars[i], 0, buf, prefix, suffix); + + cpMap[i] = ConstantPool.getUtf8Entry(new String(buf, 0, prefix+suffix)); + } + + cp_Utf8_prefix.doneDisbursing(); + cp_Utf8_suffix.doneDisbursing(); + cp_Utf8_big_suffix.doneDisbursing(); + } + + Map utf8Signatures; + + void readSignatureBands(Entry[] cpMap) throws IOException { + // cp_Signature: + // *cp_Signature_form :DELTA5 (cp_Utf8) + // *cp_Signature_classes :UDELTA5 (cp_Class) + cp_Signature_form.expectLength(cpMap.length); + cp_Signature_form.readFrom(in); + cp_Signature_form.setIndex(getCPIndex(CONSTANT_Utf8)); + int[] numSigClasses = new int[cpMap.length]; + for (int i = 0; i < cpMap.length; i++) { + Utf8Entry formRef = (Utf8Entry) cp_Signature_form.getRef(); + numSigClasses[i] = ConstantPool.countClassParts(formRef); + } + cp_Signature_form.resetForSecondPass(); + cp_Signature_classes.expectLength(getIntTotal(numSigClasses)); + cp_Signature_classes.readFrom(in); + cp_Signature_classes.setIndex(getCPIndex(CONSTANT_Class)); + utf8Signatures = new HashMap<>(); + for (int i = 0; i < cpMap.length; i++) { + Utf8Entry formRef = (Utf8Entry) cp_Signature_form.getRef(); + ClassEntry[] classRefs = new ClassEntry[numSigClasses[i]]; + for (int j = 0; j < classRefs.length; j++) { + classRefs[j] = (ClassEntry) cp_Signature_classes.getRef(); + } + SignatureEntry se = ConstantPool.getSignatureEntry(formRef, classRefs); + cpMap[i] = se; + utf8Signatures.put(se.asUtf8Entry(), se); + } + cp_Signature_form.doneDisbursing(); + cp_Signature_classes.doneDisbursing(); + } + + void readMemberRefs(byte tag, Entry[] cpMap, CPRefBand cp_class, CPRefBand cp_desc) throws IOException { + // cp_Field: + // *cp_Field_class :DELTA5 (cp_Class) + // *cp_Field_desc :UDELTA5 (cp_Descr) + // cp_Method: + // *cp_Method_class :DELTA5 (cp_Class) + // *cp_Method_desc :UDELTA5 (cp_Descr) + // cp_Imethod: + // *cp_Imethod_class :DELTA5 (cp_Class) + // *cp_Imethod_desc :UDELTA5 (cp_Descr) + cp_class.expectLength(cpMap.length); + cp_class.readFrom(in); + cp_class.setIndex(getCPIndex(CONSTANT_Class)); + cp_desc.expectLength(cpMap.length); + cp_desc.readFrom(in); + cp_desc.setIndex(getCPIndex(CONSTANT_NameandType)); + for (int i = 0; i < cpMap.length; i++) { + ClassEntry mclass = (ClassEntry ) cp_class.getRef(); + DescriptorEntry mdescr = (DescriptorEntry) cp_desc.getRef(); + cpMap[i] = ConstantPool.getMemberEntry(tag, mclass, mdescr); + } + cp_class.doneDisbursing(); + cp_desc.doneDisbursing(); + } + + void readFiles() throws IOException { + // file_bands: + // *file_name :UNSIGNED5 (cp_Utf8) + // *file_size_hi :UNSIGNED5 + // *file_size_lo :UNSIGNED5 + // *file_modtime :DELTA5 + // *file_options :UNSIGNED5 + // *file_bits :BYTE1 + if (verbose > 0) + Utils.log.info(" ...building "+numFiles+" files..."); + file_name.expectLength(numFiles); + file_size_lo.expectLength(numFiles); + int options = archiveOptions; + boolean haveSizeHi = testBit(options, AO_HAVE_FILE_SIZE_HI); + boolean haveModtime = testBit(options, AO_HAVE_FILE_MODTIME); + boolean haveOptions = testBit(options, AO_HAVE_FILE_OPTIONS); + if (haveSizeHi) + file_size_hi.expectLength(numFiles); + if (haveModtime) + file_modtime.expectLength(numFiles); + if (haveOptions) + file_options.expectLength(numFiles); + + file_name.readFrom(in); + file_size_hi.readFrom(in); + file_size_lo.readFrom(in); + file_modtime.readFrom(in); + file_options.readFrom(in); + file_bits.setInputStreamFrom(in); + + Iterator nextClass = pkg.getClasses().iterator(); + + // Compute file lengths before reading any file bits. + long totalFileLength = 0; + long[] fileLengths = new long[numFiles]; + for (int i = 0; i < numFiles; i++) { + long size = ((long)file_size_lo.getInt() << 32) >>> 32; + if (haveSizeHi) + size += (long)file_size_hi.getInt() << 32; + fileLengths[i] = size; + totalFileLength += size; + } + assert(in.getReadLimit() == -1 || in.getReadLimit() == totalFileLength); + + byte[] buf = new byte[1<<16]; + for (int i = 0; i < numFiles; i++) { + // %%% Use a big temp file for file bits? + Utf8Entry name = (Utf8Entry) file_name.getRef(); + long size = fileLengths[i]; + File file = pkg.new File(name); + file.modtime = pkg.default_modtime; + file.options = pkg.default_options; + if (haveModtime) + file.modtime += file_modtime.getInt(); + if (haveOptions) + file.options |= file_options.getInt(); + if (verbose > 1) + Utils.log.fine("Reading "+size+" bytes of "+name.stringValue()); + long toRead = size; + while (toRead > 0) { + int nr = buf.length; + if (nr > toRead) nr = (int) toRead; + nr = file_bits.getInputStream().read(buf, 0, nr); + if (nr < 0) throw new EOFException(); + file.addBytes(buf, 0, nr); + toRead -= nr; + } + pkg.addFile(file); + if (file.isClassStub()) { + assert(file.getFileLength() == 0); + Class cls = nextClass.next(); + cls.initFile(file); + } + } + + // Do the rest of the classes. + while (nextClass.hasNext()) { + Class cls = nextClass.next(); + cls.initFile(null); // implicitly initialize to a trivial one + cls.file.modtime = pkg.default_modtime; + } + + file_name.doneDisbursing(); + file_size_hi.doneDisbursing(); + file_size_lo.doneDisbursing(); + file_modtime.doneDisbursing(); + file_options.doneDisbursing(); + file_bits.doneDisbursing(); + file_bands.doneDisbursing(); + + if (archiveSize1 != 0 && !in.atLimit()) { + throw new RuntimeException("Predicted archive_size "+ + archiveSize1+" != "+ + (in.getBytesServed()-archiveSize0)); + } + } + + void readAttrDefs() throws IOException { + // attr_definition_bands: + // *attr_definition_headers :BYTE1 + // *attr_definition_name :UNSIGNED5 (cp_Utf8) + // *attr_definition_layout :UNSIGNED5 (cp_Utf8) + attr_definition_headers.expectLength(numAttrDefs); + attr_definition_name.expectLength(numAttrDefs); + attr_definition_layout.expectLength(numAttrDefs); + attr_definition_headers.readFrom(in); + attr_definition_name.readFrom(in); + attr_definition_layout.readFrom(in); + try (PrintStream dump = !optDumpBands ? null + : new PrintStream(getDumpStream(attr_definition_headers, ".def"))) + { + for (int i = 0; i < numAttrDefs; i++) { + int header = attr_definition_headers.getByte(); + Utf8Entry name = (Utf8Entry) attr_definition_name.getRef(); + Utf8Entry layout = (Utf8Entry) attr_definition_layout.getRef(); + int ctype = (header & ADH_CONTEXT_MASK); + int index = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB; + Attribute.Layout def = new Attribute.Layout(ctype, + name.stringValue(), + layout.stringValue()); + // Check layout string for Java 6 extensions. + String pvLayout = def.layoutForClassVersion(getHighestClassVersion()); + if (!pvLayout.equals(def.layout())) { + throw new IOException("Bad attribute layout in archive: "+def.layout()); + } + this.setAttributeLayoutIndex(def, index); + if (dump != null) dump.println(index+" "+def); + } + } + attr_definition_headers.doneDisbursing(); + attr_definition_name.doneDisbursing(); + attr_definition_layout.doneDisbursing(); + // Attribute layouts define bands, one per layout element. + // Create them now, all at once. + makeNewAttributeBands(); + attr_definition_bands.doneDisbursing(); + } + + void readInnerClasses() throws IOException { + // ic_bands: + // *ic_this_class :UDELTA5 (cp_Class) + // *ic_flags :UNSIGNED5 + // *ic_outer_class :DELTA5 (null or cp_Class) + // *ic_name :DELTA5 (null or cp_Utf8) + ic_this_class.expectLength(numInnerClasses); + ic_this_class.readFrom(in); + ic_flags.expectLength(numInnerClasses); + ic_flags.readFrom(in); + int longICCount = 0; + for (int i = 0; i < numInnerClasses; i++) { + int flags = ic_flags.getInt(); + boolean longForm = (flags & ACC_IC_LONG_FORM) != 0; + if (longForm) { + longICCount += 1; + } + } + ic_outer_class.expectLength(longICCount); + ic_outer_class.readFrom(in); + ic_name.expectLength(longICCount); + ic_name.readFrom(in); + ic_flags.resetForSecondPass(); + List icList = new ArrayList<>(numInnerClasses); + for (int i = 0; i < numInnerClasses; i++) { + int flags = ic_flags.getInt(); + boolean longForm = (flags & ACC_IC_LONG_FORM) != 0; + flags &= ~ACC_IC_LONG_FORM; + ClassEntry thisClass = (ClassEntry) ic_this_class.getRef(); + ClassEntry outerClass; + Utf8Entry thisName; + if (longForm) { + outerClass = (ClassEntry) ic_outer_class.getRef(); + thisName = (Utf8Entry) ic_name.getRef(); + } else { + String n = thisClass.stringValue(); + String[] parse = Package.parseInnerClassName(n); + assert(parse != null); + String pkgOuter = parse[0]; + //String number = parse[1]; + String name = parse[2]; + if (pkgOuter == null) + outerClass = null; + else + outerClass = ConstantPool.getClassEntry(pkgOuter); + if (name == null) + thisName = null; + else + thisName = ConstantPool.getUtf8Entry(name); + } + InnerClass ic = + new InnerClass(thisClass, outerClass, thisName, flags); + assert(longForm || ic.predictable); + icList.add(ic); + } + ic_flags.doneDisbursing(); + ic_this_class.doneDisbursing(); + ic_outer_class.doneDisbursing(); + ic_name.doneDisbursing(); + pkg.setAllInnerClasses(icList); + ic_bands.doneDisbursing(); + } + + void readLocalInnerClasses(Class cls) throws IOException { + int nc = class_InnerClasses_N.getInt(); + List localICs = new ArrayList<>(nc); + for (int i = 0; i < nc; i++) { + ClassEntry thisClass = (ClassEntry) class_InnerClasses_RC.getRef(); + int flags = class_InnerClasses_F.getInt(); + if (flags == 0) { + // A zero flag means copy a global IC here. + InnerClass ic = pkg.getGlobalInnerClass(thisClass); + assert(ic != null); // must be a valid global IC reference + localICs.add(ic); + } else { + if (flags == ACC_IC_LONG_FORM) + flags = 0; // clear the marker bit + ClassEntry outer = (ClassEntry) class_InnerClasses_outer_RCN.getRef(); + Utf8Entry name = (Utf8Entry) class_InnerClasses_name_RUN.getRef(); + localICs.add(new InnerClass(thisClass, outer, name, flags)); + } + } + cls.setInnerClasses(localICs); + // cls.expandLocalICs may add more tuples to ics also, + // or may even delete tuples. + // We cannot do that now, because we do not know the + // full contents of the local constant pool yet. + } + + static final int NO_FLAGS_YET = 0; // placeholder for later flag read-in + + Class[] readClasses() throws IOException { + // class_bands: + // *class_this :DELTA5 (cp_Class) + // *class_super :DELTA5 (cp_Class) + // *class_interface_count :DELTA5 + // *class_interface :DELTA5 (cp_Class) + // ...(member bands)... + // class_attr_bands + // code_bands + Class[] classes = new Class[numClasses]; + if (verbose > 0) + Utils.log.info(" ...building "+classes.length+" classes..."); + + class_this.expectLength(numClasses); + class_super.expectLength(numClasses); + class_interface_count.expectLength(numClasses); + + class_this.readFrom(in); + class_super.readFrom(in); + class_interface_count.readFrom(in); + class_interface.expectLength(class_interface_count.getIntTotal()); + class_interface.readFrom(in); + for (int i = 0; i < classes.length; i++) { + ClassEntry thisClass = (ClassEntry) class_this.getRef(); + ClassEntry superClass = (ClassEntry) class_super.getRef(); + ClassEntry[] interfaces = new ClassEntry[class_interface_count.getInt()]; + for (int j = 0; j < interfaces.length; j++) { + interfaces[j] = (ClassEntry) class_interface.getRef(); + } + // Packer encoded rare case of null superClass as thisClass: + if (superClass == thisClass) superClass = null; + Class cls = pkg.new Class(NO_FLAGS_YET, + thisClass, superClass, interfaces); + classes[i] = cls; + } + class_this.doneDisbursing(); + class_super.doneDisbursing(); + class_interface_count.doneDisbursing(); + class_interface.doneDisbursing(); + readMembers(classes); + countAndReadAttrs(ATTR_CONTEXT_CLASS, Arrays.asList(classes)); + pkg.trimToSize(); + readCodeHeaders(); + //code_bands.doneDisbursing(); // still need to read code attrs + //class_bands.doneDisbursing(); // still need to read code attrs + return classes; + } + + private int getOutputIndex(Entry e) { + // Output CPs do not contain signatures. + assert(e.tag != CONSTANT_Signature); + int k = pkg.cp.untypedIndexOf(e); + // In the output ordering, input signatures can serve + // in place of Utf8s. + if (k >= 0) + return k; + if (e.tag == CONSTANT_Utf8) { + Entry se = utf8Signatures.get(e); + return pkg.cp.untypedIndexOf(se); + } + return -1; + } + + Comparator entryOutputOrder = new Comparator<>() { + public int compare(Entry e0, Entry e1) { + int k0 = getOutputIndex(e0); + int k1 = getOutputIndex(e1); + if (k0 >= 0 && k1 >= 0) + // If both have keys, use the keys. + return k0 - k1; + if (k0 == k1) + // If neither have keys, use their native tags & spellings. + return e0.compareTo(e1); + // Otherwise, the guy with the key comes first. + return (k0 >= 0)? 0-1: 1-0; + } + }; + + void reconstructClass(Class cls) { + if (verbose > 1) Utils.log.fine("reconstruct "+cls); + + // check for local .ClassFile.version + Attribute retroVersion = cls.getAttribute(attrClassFileVersion); + if (retroVersion != null) { + cls.removeAttribute(retroVersion); + cls.version = parseClassFileVersionAttr(retroVersion); + } else { + cls.version = pkg.defaultClassVersion; + } + + // Replace null SourceFile by "obvious" string. + cls.expandSourceFile(); + + // record the local cp: + cls.setCPMap(reconstructLocalCPMap(cls)); + } + + Entry[] reconstructLocalCPMap(Class cls) { + Set ldcRefs = ldcRefMap.get(cls); + Set cpRefs = new HashSet<>(); + + // look for constant pool entries: + cls.visitRefs(VRM_CLASSIC, cpRefs); + + ArrayList bsms = new ArrayList<>(); + // flesh out the local constant pool + ConstantPool.completeReferencesIn(cpRefs, true, bsms); + + // add the bsm and references as required + if (!bsms.isEmpty()) { + cls.addAttribute(Package.attrBootstrapMethodsEmpty.canonicalInstance()); + cpRefs.add(Package.getRefString("BootstrapMethods")); + Collections.sort(bsms); + cls.setBootstrapMethods(bsms); + } + + // Now that we know all our local class references, + // compute the InnerClasses attribute. + // An InnerClasses attribute usually gets added here, + // although it might already have been present. + int changed = cls.expandLocalICs(); + + if (changed != 0) { + if (changed > 0) { + // Just visit the expanded InnerClasses attr. + cls.visitInnerClassRefs(VRM_CLASSIC, cpRefs); + } else { + // Have to recompute from scratch, because of deletions. + cpRefs.clear(); + cls.visitRefs(VRM_CLASSIC, cpRefs); + } + + // flesh out the local constant pool, again + ConstantPool.completeReferencesIn(cpRefs, true, bsms); + } + + // construct a local constant pool + int numDoubles = 0; + for (Entry e : cpRefs) { + if (e.isDoubleWord()) numDoubles++; + } + Entry[] cpMap = new Entry[1+numDoubles+cpRefs.size()]; + int fillp = 1; + + // Add all ldc operands first. + if (ldcRefs != null) { + assert(cpRefs.containsAll(ldcRefs)); + for (Entry e : ldcRefs) { + cpMap[fillp++] = e; + } + assert(fillp == 1+ldcRefs.size()); + cpRefs.removeAll(ldcRefs); + ldcRefs = null; // done with it + } + + // Next add all the two-byte references. + Set wideRefs = cpRefs; + cpRefs = null; // do not use! + int narrowLimit = fillp; + for (Entry e : wideRefs) { + cpMap[fillp++] = e; + } + assert(fillp == narrowLimit+wideRefs.size()); + Arrays.sort(cpMap, 1, narrowLimit, entryOutputOrder); + Arrays.sort(cpMap, narrowLimit, fillp, entryOutputOrder); + + if (verbose > 3) { + Utils.log.fine("CP of "+this+" {"); + for (int i = 0; i < fillp; i++) { + Entry e = cpMap[i]; + Utils.log.fine(" "+((e==null)?-1:getOutputIndex(e)) + +" : "+e); + } + Utils.log.fine("}"); + } + + // Now repack backwards, introducing null elements. + int revp = cpMap.length; + for (int i = fillp; --i >= 1; ) { + Entry e = cpMap[i]; + if (e.isDoubleWord()) + cpMap[--revp] = null; + cpMap[--revp] = e; + } + assert(revp == 1); // do not process the initial null + + return cpMap; + } + + void readMembers(Class[] classes) throws IOException { + // class_bands: + // ... + // *class_field_count :DELTA5 + // *class_method_count :DELTA5 + // + // *field_descr :DELTA5 (cp_Descr) + // field_attr_bands + // + // *method_descr :MDELTA5 (cp_Descr) + // method_attr_bands + // ... + assert(classes.length == numClasses); + class_field_count.expectLength(numClasses); + class_method_count.expectLength(numClasses); + class_field_count.readFrom(in); + class_method_count.readFrom(in); + + // Make a pre-pass over field and method counts to size the descrs: + int totalNF = class_field_count.getIntTotal(); + int totalNM = class_method_count.getIntTotal(); + field_descr.expectLength(totalNF); + method_descr.expectLength(totalNM); + if (verbose > 1) Utils.log.fine("expecting #fields="+totalNF+ + " and #methods="+totalNM+" in #classes="+numClasses); + + List fields = new ArrayList<>(totalNF); + field_descr.readFrom(in); + for (int i = 0; i < classes.length; i++) { + Class c = classes[i]; + int nf = class_field_count.getInt(); + for (int j = 0; j < nf; j++) { + Class.Field f = c.new Field(NO_FLAGS_YET, (DescriptorEntry) + field_descr.getRef()); + fields.add(f); + } + } + class_field_count.doneDisbursing(); + field_descr.doneDisbursing(); + countAndReadAttrs(ATTR_CONTEXT_FIELD, fields); + fields = null; // release to GC + + List methods = new ArrayList<>(totalNM); + method_descr.readFrom(in); + for (int i = 0; i < classes.length; i++) { + Class c = classes[i]; + int nm = class_method_count.getInt(); + for (int j = 0; j < nm; j++) { + Class.Method m = c.new Method(NO_FLAGS_YET, (DescriptorEntry) + method_descr.getRef()); + methods.add(m); + } + } + class_method_count.doneDisbursing(); + method_descr.doneDisbursing(); + countAndReadAttrs(ATTR_CONTEXT_METHOD, methods); + + // Up to this point, Code attributes look like empty attributes. + // Now we start to special-case them. The empty canonical Code + // attributes stay in the method attribute lists, however. + allCodes = buildCodeAttrs(methods); + } + + Code[] allCodes; + List codesWithFlags; + Map> ldcRefMap = new HashMap<>(); + + Code[] buildCodeAttrs(List methods) { + List codes = new ArrayList<>(methods.size()); + for (Class.Method m : methods) { + if (m.getAttribute(attrCodeEmpty) != null) { + m.code = new Code(m); + codes.add(m.code); + } + } + Code[] a = new Code[codes.size()]; + codes.toArray(a); + return a; + } + + void readCodeHeaders() throws IOException { + // code_bands: + // *code_headers :BYTE1 + // + // *code_max_stack :UNSIGNED5 + // *code_max_na_locals :UNSIGNED5 + // *code_handler_count :UNSIGNED5 + // ... + // code_attr_bands + boolean attrsOK = testBit(archiveOptions, AO_HAVE_ALL_CODE_FLAGS); + code_headers.expectLength(allCodes.length); + code_headers.readFrom(in); + List longCodes = new ArrayList<>(allCodes.length / 10); + for (int i = 0; i < allCodes.length; i++) { + Code c = allCodes[i]; + int sc = code_headers.getByte(); + assert(sc == (sc & 0xFF)); + if (verbose > 2) + Utils.log.fine("codeHeader "+c+" = "+sc); + if (sc == LONG_CODE_HEADER) { + // We will read ms/ml/nh/flags from bands shortly. + longCodes.add(c); + continue; + } + // Short code header is the usual case: + c.setMaxStack( shortCodeHeader_max_stack(sc) ); + c.setMaxNALocals( shortCodeHeader_max_na_locals(sc) ); + c.setHandlerCount( shortCodeHeader_handler_count(sc) ); + assert(shortCodeHeader(c) == sc); + } + code_headers.doneDisbursing(); + code_max_stack.expectLength(longCodes.size()); + code_max_na_locals.expectLength(longCodes.size()); + code_handler_count.expectLength(longCodes.size()); + + // Do the long headers now. + code_max_stack.readFrom(in); + code_max_na_locals.readFrom(in); + code_handler_count.readFrom(in); + for (Code c : longCodes) { + c.setMaxStack( code_max_stack.getInt() ); + c.setMaxNALocals( code_max_na_locals.getInt() ); + c.setHandlerCount( code_handler_count.getInt() ); + } + code_max_stack.doneDisbursing(); + code_max_na_locals.doneDisbursing(); + code_handler_count.doneDisbursing(); + + readCodeHandlers(); + + if (attrsOK) { + // Code attributes are common (debug info not stripped). + codesWithFlags = Arrays.asList(allCodes); + } else { + // Code attributes are very sparse (debug info is stripped). + codesWithFlags = longCodes; + } + countAttrs(ATTR_CONTEXT_CODE, codesWithFlags); + // do readAttrs later, after BCs are scanned + } + + void readCodeHandlers() throws IOException { + // code_bands: + // ... + // *code_handler_start_P :BCI5 + // *code_handler_end_PO :BRANCH5 + // *code_handler_catch_PO :BRANCH5 + // *code_handler_class_RCN :UNSIGNED5 (null or cp_Class) + // ... + int nh = 0; + for (int i = 0; i < allCodes.length; i++) { + Code c = allCodes[i]; + nh += c.getHandlerCount(); + } + + ValueBand[] code_handler_bands = { + code_handler_start_P, + code_handler_end_PO, + code_handler_catch_PO, + code_handler_class_RCN + }; + + for (int i = 0; i < code_handler_bands.length; i++) { + code_handler_bands[i].expectLength(nh); + code_handler_bands[i].readFrom(in); + } + + for (int i = 0; i < allCodes.length; i++) { + Code c = allCodes[i]; + for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) { + c.handler_class[j] = code_handler_class_RCN.getRef(); + // For now, just record the raw BCI codes. + // We must wait until we have instruction boundaries. + c.handler_start[j] = code_handler_start_P.getInt(); + c.handler_end[j] = code_handler_end_PO.getInt(); + c.handler_catch[j] = code_handler_catch_PO.getInt(); + } + } + for (int i = 0; i < code_handler_bands.length; i++) { + code_handler_bands[i].doneDisbursing(); + } + } + + void fixupCodeHandlers() { + // Actually decode (renumber) the BCIs now. + for (int i = 0; i < allCodes.length; i++) { + Code c = allCodes[i]; + for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) { + int sum = c.handler_start[j]; + c.handler_start[j] = c.decodeBCI(sum); + sum += c.handler_end[j]; + c.handler_end[j] = c.decodeBCI(sum); + sum += c.handler_catch[j]; + c.handler_catch[j] = c.decodeBCI(sum); + } + } + } + + // Generic routines for reading attributes of + // classes, fields, methods, and codes. + // The holders is a global list, already collected, + // of attribute "customers". + void countAndReadAttrs(int ctype, Collection holders) + throws IOException { + // class_attr_bands: + // *class_flags :UNSIGNED5 + // *class_attr_count :UNSIGNED5 + // *class_attr_indexes :UNSIGNED5 + // *class_attr_calls :UNSIGNED5 + // *class_Signature_RS :UNSIGNED5 (cp_Signature) + // class_metadata_bands + // *class_SourceFile_RU :UNSIGNED5 (cp_Utf8) + // *class_EnclosingMethod_RM :UNSIGNED5 (cp_Method) + // ic_local_bands + // *class_ClassFile_version_minor_H :UNSIGNED5 + // *class_ClassFile_version_major_H :UNSIGNED5 + // class_type_metadata_bands + // + // field_attr_bands: + // *field_flags :UNSIGNED5 + // *field_attr_count :UNSIGNED5 + // *field_attr_indexes :UNSIGNED5 + // *field_attr_calls :UNSIGNED5 + // *field_Signature_RS :UNSIGNED5 (cp_Signature) + // field_metadata_bands + // *field_ConstantValue_KQ :UNSIGNED5 (cp_Int, etc.; see note) + // field_type_metadata_bands + // + // method_attr_bands: + // *method_flags :UNSIGNED5 + // *method_attr_count :UNSIGNED5 + // *method_attr_indexes :UNSIGNED5 + // *method_attr_calls :UNSIGNED5 + // *method_Signature_RS :UNSIGNED5 (cp_Signature) + // method_metadata_bands + // *method_Exceptions_N :UNSIGNED5 + // *method_Exceptions_RC :UNSIGNED5 (cp_Class) + // *method_MethodParameters_NB: BYTE1 + // *method_MethodParameters_RUN: UNSIGNED5 (cp_Utf8) + // *method_MethodParameters_FH: UNSIGNED5 (flag) + // method_type_metadata_bands + // + // code_attr_bands: + // *code_flags :UNSIGNED5 + // *code_attr_count :UNSIGNED5 + // *code_attr_indexes :UNSIGNED5 + // *code_attr_calls :UNSIGNED5 + // *code_LineNumberTable_N :UNSIGNED5 + // *code_LineNumberTable_bci_P :BCI5 + // *code_LineNumberTable_line :UNSIGNED5 + // *code_LocalVariableTable_N :UNSIGNED5 + // *code_LocalVariableTable_bci_P :BCI5 + // *code_LocalVariableTable_span_O :BRANCH5 + // *code_LocalVariableTable_name_RU :UNSIGNED5 (cp_Utf8) + // *code_LocalVariableTable_type_RS :UNSIGNED5 (cp_Signature) + // *code_LocalVariableTable_slot :UNSIGNED5 + // code_type_metadata_bands + + countAttrs(ctype, holders); + readAttrs(ctype, holders); + } + + // Read flags and count the attributes that are to be placed + // on the given holders. + void countAttrs(int ctype, Collection holders) + throws IOException { + // Here, xxx stands for one of class, field, method, code. + MultiBand xxx_attr_bands = attrBands[ctype]; + long flagMask = attrFlagMask[ctype]; + if (verbose > 1) { + Utils.log.fine("scanning flags and attrs for "+ + Attribute.contextName(ctype)+"["+holders.size()+"]"); + } + + // Fetch the attribute layout definitions which govern the bands + // we are about to read. + List defList = attrDefs.get(ctype); + Attribute.Layout[] defs = new Attribute.Layout[defList.size()]; + defList.toArray(defs); + IntBand xxx_flags_hi = getAttrBand(xxx_attr_bands, AB_FLAGS_HI); + IntBand xxx_flags_lo = getAttrBand(xxx_attr_bands, AB_FLAGS_LO); + IntBand xxx_attr_count = getAttrBand(xxx_attr_bands, AB_ATTR_COUNT); + IntBand xxx_attr_indexes = getAttrBand(xxx_attr_bands, AB_ATTR_INDEXES); + IntBand xxx_attr_calls = getAttrBand(xxx_attr_bands, AB_ATTR_CALLS); + + // Count up the number of holders which have overflow attrs. + int overflowMask = attrOverflowMask[ctype]; + int overflowHolderCount = 0; + boolean haveLongFlags = haveFlagsHi(ctype); + xxx_flags_hi.expectLength(haveLongFlags? holders.size(): 0); + xxx_flags_hi.readFrom(in); + xxx_flags_lo.expectLength(holders.size()); + xxx_flags_lo.readFrom(in); + assert((flagMask & overflowMask) == overflowMask); + for (Attribute.Holder h : holders) { + int flags = xxx_flags_lo.getInt(); + h.flags = flags; + if ((flags & overflowMask) != 0) + overflowHolderCount += 1; + } + + // For each holder with overflow attrs, read a count. + xxx_attr_count.expectLength(overflowHolderCount); + xxx_attr_count.readFrom(in); + xxx_attr_indexes.expectLength(xxx_attr_count.getIntTotal()); + xxx_attr_indexes.readFrom(in); + + // Now it's time to check flag bits that indicate attributes. + // We accumulate (a) a list of attribute types for each holder + // (class/field/method/code), and also we accumulate (b) a total + // count for each attribute type. + int[] totalCounts = new int[defs.length]; + for (Attribute.Holder h : holders) { + assert(h.attributes == null); + // System.out.println("flags="+h.flags+" using fm="+flagMask); + long attrBits = ((h.flags & flagMask) << 32) >>> 32; + // Clean up the flags now. + h.flags -= (int)attrBits; // strip attr bits + assert(h.flags == (char)h.flags); // 16 bits only now + assert((ctype != ATTR_CONTEXT_CODE) || h.flags == 0); + if (haveLongFlags) + attrBits += (long)xxx_flags_hi.getInt() << 32; + if (attrBits == 0) continue; // no attrs on this guy + + int noa = 0; // number of overflow attrs + long overflowBit = (attrBits & overflowMask); + assert(overflowBit >= 0); + attrBits -= overflowBit; + if (overflowBit != 0) { + noa = xxx_attr_count.getInt(); + } + + int nfa = 0; // number of flag attrs + long bits = attrBits; + for (int ai = 0; bits != 0; ai++) { + if ((bits & (1L< ha = new ArrayList<>(nfa + noa); + h.attributes = ha; + bits = attrBits; // iterate again + for (int ai = 0; bits != 0; ai++) { + if ((bits & (1L< 0; noa--) { + int ai = xxx_attr_indexes.getInt(); + totalCounts[ai] += 1; + // This definition index is live in this holder. + if (defs[ai] == null) badAttrIndex(ai, ctype); + Attribute canonical = defs[ai].canonicalInstance(); + ha.add(canonical); + } + } + + xxx_flags_hi.doneDisbursing(); + xxx_flags_lo.doneDisbursing(); + xxx_attr_count.doneDisbursing(); + xxx_attr_indexes.doneDisbursing(); + + // Now each holder has a list of canonical attribute instances. + // For layouts with no elements, we are done. However, for + // layouts with bands, we must replace each canonical (empty) + // instance with a value-bearing one, initialized from the + // appropriate bands. + + // Make a small pass to detect and read backward call counts. + int callCounts = 0; + for (boolean predef = true; ; predef = false) { + for (int ai = 0; ai < defs.length; ai++) { + Attribute.Layout def = defs[ai]; + if (def == null) continue; // unused index + if (predef != isPredefinedAttr(ctype, ai)) + continue; // wrong pass + int totalCount = totalCounts[ai]; + if (totalCount == 0) + continue; // irrelevant + Attribute.Layout.Element[] cbles = def.getCallables(); + for (int j = 0; j < cbles.length; j++) { + assert(cbles[j].kind == Attribute.EK_CBLE); + if (cbles[j].flagTest(Attribute.EF_BACK)) + callCounts += 1; + } + } + if (!predef) break; + } + xxx_attr_calls.expectLength(callCounts); + xxx_attr_calls.readFrom(in); + + // Finally, size all the attribute bands. + for (boolean predef = true; ; predef = false) { + for (int ai = 0; ai < defs.length; ai++) { + Attribute.Layout def = defs[ai]; + if (def == null) continue; // unused index + if (predef != isPredefinedAttr(ctype, ai)) + continue; // wrong pass + int totalCount = totalCounts[ai]; + Band[] ab = attrBandTable.get(def); + if (def == attrInnerClassesEmpty) { + // Special case. + // Size the bands as if using the following layout: + // [RCH TI[ (0)[] ()[RCNH RUNH] ]]. + class_InnerClasses_N.expectLength(totalCount); + class_InnerClasses_N.readFrom(in); + int tupleCount = class_InnerClasses_N.getIntTotal(); + class_InnerClasses_RC.expectLength(tupleCount); + class_InnerClasses_RC.readFrom(in); + class_InnerClasses_F.expectLength(tupleCount); + class_InnerClasses_F.readFrom(in); + // Drop remaining columns wherever flags are zero: + tupleCount -= class_InnerClasses_F.getIntCount(0); + class_InnerClasses_outer_RCN.expectLength(tupleCount); + class_InnerClasses_outer_RCN.readFrom(in); + class_InnerClasses_name_RUN.expectLength(tupleCount); + class_InnerClasses_name_RUN.readFrom(in); + } else if (!optDebugBands && totalCount == 0) { + // Expect no elements at all. Skip quickly. however if we + // are debugging bands, read all bands regardless + for (int j = 0; j < ab.length; j++) { + ab[j].doneWithUnusedBand(); + } + } else { + // Read these bands in sequence. + boolean hasCallables = def.hasCallables(); + if (!hasCallables) { + readAttrBands(def.elems, totalCount, new int[0], ab); + } else { + Attribute.Layout.Element[] cbles = def.getCallables(); + // At first, record initial calls. + // Later, forward calls may also accumulate here: + int[] forwardCounts = new int[cbles.length]; + forwardCounts[0] = totalCount; + for (int j = 0; j < cbles.length; j++) { + assert(cbles[j].kind == Attribute.EK_CBLE); + int entryCount = forwardCounts[j]; + forwardCounts[j] = -1; // No more, please! + if (totalCount > 0 && cbles[j].flagTest(Attribute.EF_BACK)) + entryCount += xxx_attr_calls.getInt(); + readAttrBands(cbles[j].body, entryCount, forwardCounts, ab); + } + } + // mark them read, to satisfy asserts + if (optDebugBands && totalCount == 0) { + for (int j = 0; j < ab.length; j++) { + ab[j].doneDisbursing(); + } + } + } + } + if (!predef) break; + } + xxx_attr_calls.doneDisbursing(); + } + + void badAttrIndex(int ai, int ctype) throws IOException { + throw new IOException("Unknown attribute index "+ai+" for "+ + ATTR_CONTEXT_NAME[ctype]+" attribute"); + } + + void readAttrs(int ctype, Collection holders) + throws IOException { + // Decode band values into attributes. + Set sawDefs = new HashSet<>(); + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + for (final Attribute.Holder h : holders) { + if (h.attributes == null) continue; + for (ListIterator j = h.attributes.listIterator(); j.hasNext(); ) { + Attribute a = j.next(); + Attribute.Layout def = a.layout(); + if (def.bandCount == 0) { + if (def == attrInnerClassesEmpty) { + // Special logic to read this attr. + readLocalInnerClasses((Class) h); + continue; + } + // Canonical empty attr works fine (e.g., Synthetic). + continue; + } + sawDefs.add(def); + boolean isCV = (ctype == ATTR_CONTEXT_FIELD && def == attrConstantValue); + if (isCV) setConstantValueIndex((Class.Field)h); + if (verbose > 2) + Utils.log.fine("read "+a+" in "+h); + final Band[] ab = attrBandTable.get(def); + // Read one attribute of type def from ab into a byte array. + buf.reset(); + Object fixups = a.unparse(new Attribute.ValueStream() { + public int getInt(int bandIndex) { + return ((IntBand) ab[bandIndex]).getInt(); + } + public Entry getRef(int bandIndex) { + return ((CPRefBand) ab[bandIndex]).getRef(); + } + public int decodeBCI(int bciCode) { + Code code = (Code) h; + return code.decodeBCI(bciCode); + } + }, buf); + // Replace the canonical attr with the one just read. + j.set(a.addContent(buf.toByteArray(), fixups)); + if (isCV) setConstantValueIndex(null); // clean up + } + } + + // Mark the bands we just used as done disbursing. + for (Attribute.Layout def : sawDefs) { + if (def == null) continue; // unused index + Band[] ab = attrBandTable.get(def); + for (int j = 0; j < ab.length; j++) { + ab[j].doneDisbursing(); + } + } + + if (ctype == ATTR_CONTEXT_CLASS) { + class_InnerClasses_N.doneDisbursing(); + class_InnerClasses_RC.doneDisbursing(); + class_InnerClasses_F.doneDisbursing(); + class_InnerClasses_outer_RCN.doneDisbursing(); + class_InnerClasses_name_RUN.doneDisbursing(); + } + + MultiBand xxx_attr_bands = attrBands[ctype]; + for (int i = 0; i < xxx_attr_bands.size(); i++) { + Band b = xxx_attr_bands.get(i); + if (b instanceof MultiBand) + b.doneDisbursing(); + } + xxx_attr_bands.doneDisbursing(); + } + + private + void readAttrBands(Attribute.Layout.Element[] elems, + int count, int[] forwardCounts, + Band[] ab) + throws IOException { + for (int i = 0; i < elems.length; i++) { + Attribute.Layout.Element e = elems[i]; + Band eBand = null; + if (e.hasBand()) { + eBand = ab[e.bandIndex]; + eBand.expectLength(count); + eBand.readFrom(in); + } + switch (e.kind) { + case Attribute.EK_REPL: + // Recursive call. + int repCount = ((IntBand)eBand).getIntTotal(); + // Note: getIntTotal makes an extra pass over this band. + readAttrBands(e.body, repCount, forwardCounts, ab); + break; + case Attribute.EK_UN: + int remainingCount = count; + for (int j = 0; j < e.body.length; j++) { + int caseCount; + if (j == e.body.length-1) { + caseCount = remainingCount; + } else { + caseCount = 0; + for (int j0 = j; + (j == j0) + || (j < e.body.length + && e.body[j].flagTest(Attribute.EF_BACK)); + j++) { + caseCount += ((IntBand)eBand).getIntCount(e.body[j].value); + } + --j; // back up to last occurrence of this body + } + remainingCount -= caseCount; + readAttrBands(e.body[j].body, caseCount, forwardCounts, ab); + } + assert(remainingCount == 0); + break; + case Attribute.EK_CALL: + assert(e.body.length == 1); + assert(e.body[0].kind == Attribute.EK_CBLE); + if (!e.flagTest(Attribute.EF_BACK)) { + // Backward calls are pre-counted, but forwards are not. + // Push the present count forward. + assert(forwardCounts[e.value] >= 0); + forwardCounts[e.value] += count; + } + break; + case Attribute.EK_CBLE: + assert(false); + break; + } + } + } + + void readByteCodes() throws IOException { + // bc_bands: + // *bc_codes :BYTE1 + // *bc_case_count :UNSIGNED5 + // *bc_case_value :DELTA5 + // *bc_byte :BYTE1 + // *bc_short :DELTA5 + // *bc_local :UNSIGNED5 + // *bc_label :BRANCH5 + // *bc_intref :DELTA5 (cp_Int) + // *bc_floatref :DELTA5 (cp_Float) + // *bc_longref :DELTA5 (cp_Long) + // *bc_doubleref :DELTA5 (cp_Double) + // *bc_stringref :DELTA5 (cp_String) + // *bc_classref :UNSIGNED5 (current class or cp_Class) + // *bc_fieldref :DELTA5 (cp_Field) + // *bc_methodref :UNSIGNED5 (cp_Method) + // *bc_imethodref :DELTA5 (cp_Imethod) + // *bc_thisfield :UNSIGNED5 (cp_Field, only for current class) + // *bc_superfield :UNSIGNED5 (cp_Field, only for current super) + // *bc_thismethod :UNSIGNED5 (cp_Method, only for current class) + // *bc_supermethod :UNSIGNED5 (cp_Method, only for current super) + // *bc_initref :UNSIGNED5 (cp_Field, only for most recent new) + // *bc_escref :UNSIGNED5 (cp_All) + // *bc_escrefsize :UNSIGNED5 + // *bc_escsize :UNSIGNED5 + // *bc_escbyte :BYTE1 + bc_codes.elementCountForDebug = allCodes.length; + bc_codes.setInputStreamFrom(in); + readByteCodeOps(); // reads from bc_codes and bc_case_count + bc_codes.doneDisbursing(); + + // All the operand bands have now been sized. Read them all in turn. + Band[] operand_bands = { + bc_case_value, + bc_byte, bc_short, + bc_local, bc_label, + bc_intref, bc_floatref, + bc_longref, bc_doubleref, bc_stringref, + bc_loadablevalueref, + bc_classref, bc_fieldref, + bc_methodref, bc_imethodref, + bc_indyref, + bc_thisfield, bc_superfield, + bc_thismethod, bc_supermethod, + bc_initref, + bc_escref, bc_escrefsize, bc_escsize + }; + for (int i = 0; i < operand_bands.length; i++) { + operand_bands[i].readFrom(in); + } + bc_escbyte.expectLength(bc_escsize.getIntTotal()); + bc_escbyte.readFrom(in); + + expandByteCodeOps(); + + // Done fetching values from operand bands: + bc_case_count.doneDisbursing(); + for (int i = 0; i < operand_bands.length; i++) { + operand_bands[i].doneDisbursing(); + } + bc_escbyte.doneDisbursing(); + bc_bands.doneDisbursing(); + + // We must delay the parsing of Code attributes until we + // have a complete model of bytecodes, for BCI encodings. + readAttrs(ATTR_CONTEXT_CODE, codesWithFlags); + // Ditto for exception handlers in codes. + fixupCodeHandlers(); + // Now we can finish with class_bands; cf. readClasses(). + code_bands.doneDisbursing(); + class_bands.doneDisbursing(); + } + + private void readByteCodeOps() throws IOException { + // scratch buffer for collecting code:: + byte[] buf = new byte[1<<12]; + // record of all switch opcodes (these are variable-length) + List allSwitchOps = new ArrayList<>(); + for (int k = 0; k < allCodes.length; k++) { + Code c = allCodes[k]; + scanOneMethod: + for (int i = 0; ; i++) { + int bc = bc_codes.getByte(); + if (i + 10 > buf.length) buf = realloc(buf); + buf[i] = (byte)bc; + boolean isWide = false; + if (bc == _wide) { + bc = bc_codes.getByte(); + buf[++i] = (byte)bc; + isWide = true; + } + assert(bc == (0xFF & bc)); + // Adjust expectations of various band sizes. + switch (bc) { + case _tableswitch: + case _lookupswitch: + bc_case_count.expectMoreLength(1); + allSwitchOps.add(bc); + break; + case _iinc: + bc_local.expectMoreLength(1); + if (isWide) + bc_short.expectMoreLength(1); + else + bc_byte.expectMoreLength(1); + break; + case _sipush: + bc_short.expectMoreLength(1); + break; + case _bipush: + bc_byte.expectMoreLength(1); + break; + case _newarray: + bc_byte.expectMoreLength(1); + break; + case _multianewarray: + assert(getCPRefOpBand(bc) == bc_classref); + bc_classref.expectMoreLength(1); + bc_byte.expectMoreLength(1); + break; + case _ref_escape: + bc_escrefsize.expectMoreLength(1); + bc_escref.expectMoreLength(1); + break; + case _byte_escape: + bc_escsize.expectMoreLength(1); + // bc_escbyte will have to be counted too + break; + default: + if (Instruction.isInvokeInitOp(bc)) { + bc_initref.expectMoreLength(1); + break; + } + if (Instruction.isSelfLinkerOp(bc)) { + CPRefBand bc_which = selfOpRefBand(bc); + bc_which.expectMoreLength(1); + break; + } + if (Instruction.isBranchOp(bc)) { + bc_label.expectMoreLength(1); + break; + } + if (Instruction.isCPRefOp(bc)) { + CPRefBand bc_which = getCPRefOpBand(bc); + bc_which.expectMoreLength(1); + assert(bc != _multianewarray); // handled elsewhere + break; + } + if (Instruction.isLocalSlotOp(bc)) { + bc_local.expectMoreLength(1); + break; + } + break; + case _end_marker: + { + // Transfer from buf to a more permanent place: + c.bytes = realloc(buf, i); + break scanOneMethod; + } + } + } + } + + // To size instruction bands correctly, we need info on switches: + bc_case_count.readFrom(in); + for (Integer i : allSwitchOps) { + int bc = i.intValue(); + int caseCount = bc_case_count.getInt(); + bc_label.expectMoreLength(1+caseCount); // default label + cases + bc_case_value.expectMoreLength(bc == _tableswitch ? 1 : caseCount); + } + bc_case_count.resetForSecondPass(); + } + + private void expandByteCodeOps() throws IOException { + // scratch buffer for collecting code: + byte[] buf = new byte[1<<12]; + // scratch buffer for collecting instruction boundaries: + int[] insnMap = new int[1<<12]; + // list of label carriers, for label decoding post-pass: + int[] labels = new int[1<<10]; + // scratch buffer for registering CP refs: + Fixups fixupBuf = new Fixups(); + + for (int k = 0; k < allCodes.length; k++) { + Code code = allCodes[k]; + byte[] codeOps = code.bytes; + code.bytes = null; // just for now, while we accumulate bits + + Class curClass = code.thisClass(); + + Set ldcRefSet = ldcRefMap.get(curClass); + if (ldcRefSet == null) + ldcRefMap.put(curClass, ldcRefSet = new HashSet<>()); + + ClassEntry thisClass = curClass.thisClass; + ClassEntry superClass = curClass.superClass; + ClassEntry newClass = null; // class of last _new opcode + + int pc = 0; // fill pointer in buf; actual bytecode PC + int numInsns = 0; + int numLabels = 0; + boolean hasEscs = false; + fixupBuf.clear(); + for (int i = 0; i < codeOps.length; i++) { + int bc = Instruction.getByte(codeOps, i); + int curPC = pc; + insnMap[numInsns++] = curPC; + if (pc + 10 > buf.length) buf = realloc(buf); + if (numInsns+10 > insnMap.length) insnMap = realloc(insnMap); + if (numLabels+10 > labels.length) labels = realloc(labels); + boolean isWide = false; + if (bc == _wide) { + buf[pc++] = (byte) bc; + bc = Instruction.getByte(codeOps, ++i); + isWide = true; + } + switch (bc) { + case _tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label)) + case _lookupswitch: // apc: (df, nc, nc*(case, label)) + { + int caseCount = bc_case_count.getInt(); + while ((pc + 30 + caseCount*8) > buf.length) + buf = realloc(buf); + buf[pc++] = (byte) bc; + //initialize apc, df, lo, hi bytes to reasonable bits: + Arrays.fill(buf, pc, pc+30, (byte)0); + Instruction.Switch isw = (Instruction.Switch) + Instruction.at(buf, curPC); + //isw.setDefaultLabel(getLabel(bc_label, code, curPC)); + isw.setCaseCount(caseCount); + if (bc == _tableswitch) { + isw.setCaseValue(0, bc_case_value.getInt()); + } else { + for (int j = 0; j < caseCount; j++) { + isw.setCaseValue(j, bc_case_value.getInt()); + } + } + // Make our getLabel calls later. + labels[numLabels++] = curPC; + pc = isw.getNextPC(); + continue; + } + case _iinc: + { + buf[pc++] = (byte) bc; + int local = bc_local.getInt(); + int delta; + if (isWide) { + delta = bc_short.getInt(); + Instruction.setShort(buf, pc, local); pc += 2; + Instruction.setShort(buf, pc, delta); pc += 2; + } else { + delta = (byte) bc_byte.getByte(); + buf[pc++] = (byte)local; + buf[pc++] = (byte)delta; + } + continue; + } + case _sipush: + { + int val = bc_short.getInt(); + buf[pc++] = (byte) bc; + Instruction.setShort(buf, pc, val); pc += 2; + continue; + } + case _bipush: + case _newarray: + { + int val = bc_byte.getByte(); + buf[pc++] = (byte) bc; + buf[pc++] = (byte) val; + continue; + } + case _ref_escape: + { + // Note that insnMap has one entry for this. + hasEscs = true; + int size = bc_escrefsize.getInt(); + Entry ref = bc_escref.getRef(); + if (size == 1) ldcRefSet.add(ref); + int fmt; + switch (size) { + case 1: fixupBuf.addU1(pc, ref); break; + case 2: fixupBuf.addU2(pc, ref); break; + default: assert(false); fmt = 0; + } + buf[pc+0] = buf[pc+1] = 0; + pc += size; + } + continue; + case _byte_escape: + { + // Note that insnMap has one entry for all these bytes. + hasEscs = true; + int size = bc_escsize.getInt(); + while ((pc + size) > buf.length) + buf = realloc(buf); + while (size-- > 0) { + buf[pc++] = (byte) bc_escbyte.getByte(); + } + } + continue; + default: + if (Instruction.isInvokeInitOp(bc)) { + int idx = (bc - _invokeinit_op); + int origBC = _invokespecial; + ClassEntry classRef; + switch (idx) { + case _invokeinit_self_option: + classRef = thisClass; break; + case _invokeinit_super_option: + classRef = superClass; break; + default: + assert(idx == _invokeinit_new_option); + classRef = newClass; break; + } + buf[pc++] = (byte) origBC; + int coding = bc_initref.getInt(); + // Find the nth overloading of in classRef. + MemberEntry ref = pkg.cp.getOverloadingForIndex(CONSTANT_Methodref, classRef, "", coding); + fixupBuf.addU2(pc, ref); + buf[pc+0] = buf[pc+1] = 0; + pc += 2; + assert(Instruction.opLength(origBC) == (pc - curPC)); + continue; + } + if (Instruction.isSelfLinkerOp(bc)) { + int idx = (bc - _self_linker_op); + boolean isSuper = (idx >= _self_linker_super_flag); + if (isSuper) idx -= _self_linker_super_flag; + boolean isAload = (idx >= _self_linker_aload_flag); + if (isAload) idx -= _self_linker_aload_flag; + int origBC = _first_linker_op + idx; + boolean isField = Instruction.isFieldOp(origBC); + CPRefBand bc_which; + ClassEntry which_cls = isSuper ? superClass : thisClass; + Index which_ix; + if (isField) { + bc_which = isSuper ? bc_superfield : bc_thisfield; + which_ix = pkg.cp.getMemberIndex(CONSTANT_Fieldref, which_cls); + } else { + bc_which = isSuper ? bc_supermethod : bc_thismethod; + which_ix = pkg.cp.getMemberIndex(CONSTANT_Methodref, which_cls); + } + assert(bc_which == selfOpRefBand(bc)); + MemberEntry ref = (MemberEntry) bc_which.getRef(which_ix); + if (isAload) { + buf[pc++] = (byte) _aload_0; + curPC = pc; + // Note: insnMap keeps the _aload_0 separate. + insnMap[numInsns++] = curPC; + } + buf[pc++] = (byte) origBC; + fixupBuf.addU2(pc, ref); + buf[pc+0] = buf[pc+1] = 0; + pc += 2; + assert(Instruction.opLength(origBC) == (pc - curPC)); + continue; + } + if (Instruction.isBranchOp(bc)) { + buf[pc++] = (byte) bc; + assert(!isWide); // no wide prefix for branches + int nextPC = curPC + Instruction.opLength(bc); + // Make our getLabel calls later. + labels[numLabels++] = curPC; + //Instruction.at(buf, curPC).setBranchLabel(getLabel(bc_label, code, curPC)); + while (pc < nextPC) buf[pc++] = 0; + continue; + } + if (Instruction.isCPRefOp(bc)) { + CPRefBand bc_which = getCPRefOpBand(bc); + Entry ref = bc_which.getRef(); + if (ref == null) { + if (bc_which == bc_classref) { + // Shorthand for class self-references. + ref = thisClass; + } else { + assert(false); + } + } + int origBC = bc; + int size = 2; + switch (bc) { + case _invokestatic_int: + origBC = _invokestatic; + break; + case _invokespecial_int: + origBC = _invokespecial; + break; + case _ildc: + case _cldc: + case _fldc: + case _sldc: + case _qldc: + origBC = _ldc; + size = 1; + ldcRefSet.add(ref); + break; + case _ildc_w: + case _cldc_w: + case _fldc_w: + case _sldc_w: + case _qldc_w: + origBC = _ldc_w; + break; + case _lldc2_w: + case _dldc2_w: + origBC = _ldc2_w; + break; + case _new: + newClass = (ClassEntry) ref; + break; + } + buf[pc++] = (byte) origBC; + int fmt; + switch (size) { + case 1: fixupBuf.addU1(pc, ref); break; + case 2: fixupBuf.addU2(pc, ref); break; + default: assert(false); fmt = 0; + } + buf[pc+0] = buf[pc+1] = 0; + pc += size; + if (origBC == _multianewarray) { + // Copy the trailing byte also. + int val = bc_byte.getByte(); + buf[pc++] = (byte) val; + } else if (origBC == _invokeinterface) { + int argSize = ((MemberEntry)ref).descRef.typeRef.computeSize(true); + buf[pc++] = (byte)( 1 + argSize ); + buf[pc++] = 0; + } else if (origBC == _invokedynamic) { + buf[pc++] = 0; + buf[pc++] = 0; + } + assert(Instruction.opLength(origBC) == (pc - curPC)); + continue; + } + if (Instruction.isLocalSlotOp(bc)) { + buf[pc++] = (byte) bc; + int local = bc_local.getInt(); + if (isWide) { + Instruction.setShort(buf, pc, local); + pc += 2; + if (bc == _iinc) { + int iVal = bc_short.getInt(); + Instruction.setShort(buf, pc, iVal); + pc += 2; + } + } else { + Instruction.setByte(buf, pc, local); + pc += 1; + if (bc == _iinc) { + int iVal = bc_byte.getByte(); + Instruction.setByte(buf, pc, iVal); + pc += 1; + } + } + assert(Instruction.opLength(bc) == (pc - curPC)); + continue; + } + // Random bytecode. Just copy it. + if (bc >= _bytecode_limit) + Utils.log.warning("unrecognized bytescode "+bc + +" "+Instruction.byteName(bc)); + assert(bc < _bytecode_limit); + buf[pc++] = (byte) bc; + assert(Instruction.opLength(bc) == (pc - curPC)); + continue; + } + } + // now make a permanent copy of the bytecodes + code.setBytes(realloc(buf, pc)); + code.setInstructionMap(insnMap, numInsns); + // fix up labels, now that code has its insnMap + Instruction ibr = null; // temporary branch instruction + for (int i = 0; i < numLabels; i++) { + int curPC = labels[i]; + // (Note: Passing ibr in allows reuse, a speed hack.) + ibr = Instruction.at(code.bytes, curPC, ibr); + if (ibr instanceof Instruction.Switch) { + Instruction.Switch isw = (Instruction.Switch) ibr; + isw.setDefaultLabel(getLabel(bc_label, code, curPC)); + int caseCount = isw.getCaseCount(); + for (int j = 0; j < caseCount; j++) { + isw.setCaseLabel(j, getLabel(bc_label, code, curPC)); + } + } else { + ibr.setBranchLabel(getLabel(bc_label, code, curPC)); + } + } + if (fixupBuf.size() > 0) { + if (verbose > 2) + Utils.log.fine("Fixups in code: "+fixupBuf); + code.addFixups(fixupBuf); + } + } + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/PackageWriter.java b/src/main/java/net/minecraftforge/gradle/util/pack200/PackageWriter.java new file mode 100644 index 000000000..f4b7956e9 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/PackageWriter.java @@ -0,0 +1,1743 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import net.minecraftforge.gradle.util.pack200.ConstantPool.*; +import net.minecraftforge.gradle.util.pack200.Package.Class; +import net.minecraftforge.gradle.util.pack200.Package.File; +import net.minecraftforge.gradle.util.pack200.Package.InnerClass; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +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 static net.minecraftforge.gradle.util.pack200.Constants.*; + +/** + * Writer for a package file. + * @author John Rose + */ +class PackageWriter extends BandStructure { + Package pkg; + OutputStream finalOut; + Package.Version packageVersion; + + PackageWriter(Package pkg, OutputStream out) throws IOException { + this.pkg = pkg; + this.finalOut = out; + // Caller has specified maximum class file version in the package: + initHighestClassVersion(pkg.getHighestClassVersion()); + } + + void write() throws IOException { + boolean ok = false; + try { + if (verbose > 0) { + Utils.log.info("Setting up constant pool..."); + } + setup(); + + if (verbose > 0) { + Utils.log.info("Packing..."); + } + + // writeFileHeader() is done last, since it has ultimate counts + // writeBandHeaders() is called after all other bands are done + writeConstantPool(); + writeFiles(); + writeAttrDefs(); + writeInnerClasses(); + writeClassesAndByteCodes(); + writeAttrCounts(); + + if (verbose > 1) printCodeHist(); + + // choose codings (fill band_headers if needed) + if (verbose > 0) { + Utils.log.info("Coding..."); + } + all_bands.chooseBandCodings(); + + // now we can write the headers: + writeFileHeader(); + + writeAllBandsTo(finalOut); + + ok = true; + } catch (Exception ee) { + Utils.log.warning("Error on output: "+ee, ee); + //if (verbose > 0) ee.printStackTrace(); + // Write partial output only if we are verbose. + if (verbose > 0) finalOut.close(); + if (ee instanceof IOException) throw (IOException)ee; + if (ee instanceof RuntimeException) throw (RuntimeException)ee; + throw new Error("error packing", ee); + } + } + + Set requiredEntries; // for the CP + Map backCountTable; // for layout callables + int[][] attrCounts; // count attr. occurrences + + void setup() { + requiredEntries = new HashSet<>(); + setArchiveOptions(); + trimClassAttributes(); + collectAttributeLayouts(); + pkg.buildGlobalConstantPool(requiredEntries); + setBandIndexes(); + makeNewAttributeBands(); + collectInnerClasses(); + } + + /* + * Convenience function to choose an archive version based + * on the class file versions observed within the archive + * or set the user defined version preset via properties. + */ + void chooseDefaultPackageVersion() throws IOException { + if (pkg.packageVersion != null) { + packageVersion = pkg.packageVersion; + if (verbose > 0) { + Utils.log.info("package version overridden with: " + + packageVersion); + } + return; + } + + Package.Version highV = getHighestClassVersion(); + // set the package version now + if (highV.lessThan(JAVA6_MAX_CLASS_VERSION)) { + // There are only old classfiles in this segment or resources + packageVersion = JAVA5_PACKAGE_VERSION; + } else if (highV.equals(JAVA6_MAX_CLASS_VERSION) || + (highV.equals(JAVA7_MAX_CLASS_VERSION) && !pkg.cp.haveExtraTags())) { + // force down the package version if we have jdk7 classes without + // any Indy references, this is because jdk7 class file (51.0) without + // Indy is identical to jdk6 class file (50.0). + packageVersion = JAVA6_PACKAGE_VERSION; + } else if (highV.equals(JAVA7_MAX_CLASS_VERSION)) { + packageVersion = JAVA7_PACKAGE_VERSION; + } else { + // Normal case. Use the newest archive format, when available + packageVersion = JAVA8_PACKAGE_VERSION; + } + + if (verbose > 0) { + Utils.log.info("Highest version class file: " + highV + + " package version: " + packageVersion); + } + } + + void checkVersion() throws IOException { + assert(packageVersion != null); + + if (packageVersion.lessThan(JAVA7_PACKAGE_VERSION)) { + // this bit was reserved for future use in previous versions + if (testBit(archiveOptions, AO_HAVE_CP_EXTRAS)) { + throw new IOException("Format bits for Java 7 must be zero in previous releases"); + } + } + if (testBit(archiveOptions, AO_UNUSED_MBZ)) { + throw new IOException("High archive option bits are reserved and must be zero: " + Integer.toHexString(archiveOptions)); + } + } + + void setArchiveOptions() { + // Decide on some archive options early. + // Does not decide on: AO_HAVE_SPECIAL_FORMATS, + // AO_HAVE_CP_NUMBERS, AO_HAVE_FILE_HEADERS. + // Also, AO_HAVE_FILE_OPTIONS may be forced on later. + int minModtime = pkg.default_modtime; + int maxModtime = pkg.default_modtime; + int minOptions = -1; + int maxOptions = 0; + + // Import defaults from package (deflate hint, etc.). + archiveOptions |= pkg.default_options; + + for (File file : pkg.files) { + int modtime = file.modtime; + int options = file.options; + + if (minModtime == NO_MODTIME) { + minModtime = maxModtime = modtime; + } else { + if (minModtime > modtime) minModtime = modtime; + if (maxModtime < modtime) maxModtime = modtime; + } + minOptions &= options; + maxOptions |= options; + } + if (pkg.default_modtime == NO_MODTIME) { + // Make everything else be a positive offset from here. + pkg.default_modtime = minModtime; + } + if (minModtime != NO_MODTIME && minModtime != maxModtime) { + // Put them into a band. + archiveOptions |= AO_HAVE_FILE_MODTIME; + } + // If the archive deflation is set do not bother with each file. + if (!testBit(archiveOptions,AO_DEFLATE_HINT) && minOptions != -1) { + if (testBit(minOptions, FO_DEFLATE_HINT)) { + // Every file has the deflate_hint set. + // Set it for the whole archive, and omit options. + archiveOptions |= AO_DEFLATE_HINT; + minOptions -= FO_DEFLATE_HINT; + maxOptions -= FO_DEFLATE_HINT; + } + pkg.default_options |= minOptions; + if (minOptions != maxOptions + || minOptions != pkg.default_options) { + archiveOptions |= AO_HAVE_FILE_OPTIONS; + } + } + // Decide on default version number (majority rule). + Map verCounts = new HashMap<>(); + int bestCount = 0; + Package.Version bestVersion = null; + for (Class cls : pkg.classes) { + Package.Version version = cls.getVersion(); + int[] var = verCounts.get(version); + if (var == null) { + var = new int[1]; + verCounts.put(version, var); + } + int count = (var[0] += 1); + //System.out.println("version="+version+" count="+count); + if (bestCount < count) { + bestCount = count; + bestVersion = version; + } + } + verCounts.clear(); + if (bestVersion == null) bestVersion = JAVA_MIN_CLASS_VERSION; // degenerate case + pkg.defaultClassVersion = bestVersion; + if (verbose > 0) + Utils.log.info("Consensus version number in segment is " + bestVersion); + if (verbose > 0) + Utils.log.info("Highest version number in segment is " + + pkg.getHighestClassVersion()); + + // Now add explicit pseudo-attrs. to classes with odd versions. + for (Class cls : pkg.classes) { + if (!cls.getVersion().equals(bestVersion)) { + Attribute a = makeClassFileVersionAttr(cls.getVersion()); + if (verbose > 1) { + Utils.log.fine("Version "+cls.getVersion() + " of " + cls + + " doesn't match package version " + + bestVersion); + } + // Note: Does not add in "natural" order. (Who cares?) + cls.addAttribute(a); + } + } + + // Decide if we are transmitting a huge resource file: + for (File file : pkg.files) { + long len = file.getFileLength(); + if (len != (int)len) { + archiveOptions |= AO_HAVE_FILE_SIZE_HI; + if (verbose > 0) + Utils.log.info("Note: Huge resource file "+file.getFileName()+" forces 64-bit sizing"); + break; + } + } + + // Decide if code attributes typically have sub-attributes. + // In that case, to preserve compact 1-byte code headers, + // we must declare unconditional presence of code flags. + int cost0 = 0; + int cost1 = 0; + for (Class cls : pkg.classes) { + for (Class.Method m : cls.getMethods()) { + if (m.code != null) { + if (m.code.attributeSize() == 0) { + // cost of a useless unconditional flags byte + cost1 += 1; + } else if (shortCodeHeader(m.code) != LONG_CODE_HEADER) { + // cost of inflating a short header + cost0 += 3; + } + } + } + } + if (cost0 > cost1) { + archiveOptions |= AO_HAVE_ALL_CODE_FLAGS; + } + if (verbose > 0) + Utils.log.info("archiveOptions = " + +"0b"+Integer.toBinaryString(archiveOptions)); + } + + void writeFileHeader() throws IOException { + chooseDefaultPackageVersion(); + writeArchiveMagic(); + writeArchiveHeader(); + } + + // Local routine used to format fixed-format scalars + // in the file_header: + private void putMagicInt32(int val) throws IOException { + int res = val; + for (int i = 0; i < 4; i++) { + archive_magic.putByte(0xFF & (res >>> 24)); + res <<= 8; + } + } + + void writeArchiveMagic() throws IOException { + putMagicInt32(pkg.magic); + } + + void writeArchiveHeader() throws IOException { + // for debug only: number of words optimized away + int headerSizeForDebug = AH_LENGTH_MIN; + + // AO_HAVE_SPECIAL_FORMATS is set if non-default + // coding techniques are used, or if there are + // compressor-defined attributes transmitted. + boolean haveSpecial = testBit(archiveOptions, AO_HAVE_SPECIAL_FORMATS); + if (!haveSpecial) { + haveSpecial |= (band_headers.length() != 0); + haveSpecial |= (attrDefsWritten.length != 0); + if (haveSpecial) + archiveOptions |= AO_HAVE_SPECIAL_FORMATS; + } + if (haveSpecial) + headerSizeForDebug += AH_SPECIAL_FORMAT_LEN; + + // AO_HAVE_FILE_HEADERS is set if there is any + // file or segment envelope information present. + boolean haveFiles = testBit(archiveOptions, AO_HAVE_FILE_HEADERS); + if (!haveFiles) { + haveFiles |= (archiveNextCount > 0); + haveFiles |= (pkg.default_modtime != NO_MODTIME); + if (haveFiles) + archiveOptions |= AO_HAVE_FILE_HEADERS; + } + if (haveFiles) + headerSizeForDebug += AH_FILE_HEADER_LEN; + + // AO_HAVE_CP_NUMBERS is set if there are any numbers + // in the global constant pool. (Numbers are in 15% of classes.) + boolean haveNumbers = testBit(archiveOptions, AO_HAVE_CP_NUMBERS); + if (!haveNumbers) { + haveNumbers |= pkg.cp.haveNumbers(); + if (haveNumbers) + archiveOptions |= AO_HAVE_CP_NUMBERS; + } + if (haveNumbers) + headerSizeForDebug += AH_CP_NUMBER_LEN; + + // AO_HAVE_CP_EXTRAS is set if there are constant pool entries + // beyond the Java 6 version of the class file format. + boolean haveCPExtra = testBit(archiveOptions, AO_HAVE_CP_EXTRAS); + if (!haveCPExtra) { + haveCPExtra |= pkg.cp.haveExtraTags(); + if (haveCPExtra) + archiveOptions |= AO_HAVE_CP_EXTRAS; + } + if (haveCPExtra) + headerSizeForDebug += AH_CP_EXTRA_LEN; + + // the archiveOptions are all initialized, sanity check now!. + checkVersion(); + + archive_header_0.putInt(packageVersion.minor); + archive_header_0.putInt(packageVersion.major); + if (verbose > 0) + Utils.log.info("Package Version for this segment:" + packageVersion); + archive_header_0.putInt(archiveOptions); // controls header format + assert(archive_header_0.length() == AH_LENGTH_0); + + final int DUMMY = 0; + if (haveFiles) { + assert(archive_header_S.length() == AH_ARCHIVE_SIZE_HI); + archive_header_S.putInt(DUMMY); // (archiveSize1 >>> 32) + assert(archive_header_S.length() == AH_ARCHIVE_SIZE_LO); + archive_header_S.putInt(DUMMY); // (archiveSize1 >>> 0) + assert(archive_header_S.length() == AH_LENGTH_S); + } + + // Done with unsized part of header.... + + if (haveFiles) { + archive_header_1.putInt(archiveNextCount); // usually zero + archive_header_1.putInt(pkg.default_modtime); + archive_header_1.putInt(pkg.files.size()); + } else { + assert(pkg.files.isEmpty()); + } + + if (haveSpecial) { + archive_header_1.putInt(band_headers.length()); + archive_header_1.putInt(attrDefsWritten.length); + } else { + assert(band_headers.length() == 0); + assert(attrDefsWritten.length == 0); + } + + writeConstantPoolCounts(haveNumbers, haveCPExtra); + + archive_header_1.putInt(pkg.getAllInnerClasses().size()); + archive_header_1.putInt(pkg.defaultClassVersion.minor); + archive_header_1.putInt(pkg.defaultClassVersion.major); + archive_header_1.putInt(pkg.classes.size()); + + // Sanity: Make sure we came out to 29 (less optional fields): + assert(archive_header_0.length() + + archive_header_S.length() + + archive_header_1.length() + == headerSizeForDebug); + + // Figure out all the sizes now, first cut: + archiveSize0 = 0; + archiveSize1 = all_bands.outputSize(); + // Second cut: + archiveSize0 += archive_magic.outputSize(); + archiveSize0 += archive_header_0.outputSize(); + archiveSize0 += archive_header_S.outputSize(); + // Make the adjustments: + archiveSize1 -= archiveSize0; + + // Patch the header: + if (haveFiles) { + int archiveSizeHi = (int)(archiveSize1 >>> 32); + int archiveSizeLo = (int)(archiveSize1 >>> 0); + archive_header_S.patchValue(AH_ARCHIVE_SIZE_HI, archiveSizeHi); + archive_header_S.patchValue(AH_ARCHIVE_SIZE_LO, archiveSizeLo); + int zeroLen = UNSIGNED5.getLength(DUMMY); + archiveSize0 += UNSIGNED5.getLength(archiveSizeHi) - zeroLen; + archiveSize0 += UNSIGNED5.getLength(archiveSizeLo) - zeroLen; + } + if (verbose > 1) + Utils.log.fine("archive sizes: "+ + archiveSize0+"+"+archiveSize1); + assert(all_bands.outputSize() == archiveSize0+archiveSize1); + } + + void writeConstantPoolCounts(boolean haveNumbers, boolean haveCPExtra) throws IOException { + for (byte tag : ConstantPool.TAGS_IN_ORDER) { + int count = pkg.cp.getIndexByTag(tag).size(); + switch (tag) { + case CONSTANT_Utf8: + // The null string is always first. + if (count > 0) + assert(pkg.cp.getIndexByTag(tag).get(0) + == ConstantPool.getUtf8Entry("")); + break; + + case CONSTANT_Integer: + case CONSTANT_Float: + case CONSTANT_Long: + case CONSTANT_Double: + // Omit counts for numbers if possible. + if (!haveNumbers) { + assert(count == 0); + continue; + } + break; + + case CONSTANT_MethodHandle: + case CONSTANT_MethodType: + case CONSTANT_InvokeDynamic: + case CONSTANT_BootstrapMethod: + // Omit counts for newer entities if possible. + if (!haveCPExtra) { + assert(count == 0); + continue; + } + break; + } + archive_header_1.putInt(count); + } + } + + protected Index getCPIndex(byte tag) { + return pkg.cp.getIndexByTag(tag); + } + +// (The following observations are out of date; they apply only to +// "banding" the constant pool itself. Later revisions of this algorithm +// applied the banding technique to every part of the package file, +// applying the benefits more broadly.) + +// Note: Keeping the data separate in passes (or "bands") allows the +// compressor to issue significantly shorter indexes for repeated data. +// The difference in zipped size is 4%, which is remarkable since the +// unzipped sizes are the same (only the byte order differs). + +// After moving similar data into bands, it becomes natural to delta-encode +// each band. (This is especially useful if we sort the constant pool first.) +// Delta encoding saves an extra 5% in the output size (13% of the CP itself). +// Because a typical delta usees much less data than a byte, the savings after +// zipping is even better: A zipped delta-encoded package is 8% smaller than +// a zipped non-delta-encoded package. Thus, in the zipped file, a banded, +// delta-encoded constant pool saves over 11% (of the total file size) compared +// with a zipped unbanded file. + + void writeConstantPool() throws IOException { + IndexGroup cp = pkg.cp; + + if (verbose > 0) Utils.log.info("Writing CP"); + + for (byte tag : ConstantPool.TAGS_IN_ORDER) { + Index index = cp.getIndexByTag(tag); + + Entry[] cpMap = index.cpMap; + if (verbose > 0) + Utils.log.info("Writing "+cpMap.length+" "+ConstantPool.tagName(tag)+" entries..."); + + if (optDumpBands) { + try (PrintStream ps = new PrintStream(getDumpStream(index, ".idx"))) { + printArrayTo(ps, cpMap, 0, cpMap.length); + } + } + + switch (tag) { + case CONSTANT_Utf8: + writeUtf8Bands(cpMap); + break; + case CONSTANT_Integer: + for (int i = 0; i < cpMap.length; i++) { + NumberEntry e = (NumberEntry) cpMap[i]; + int x = ((Integer)e.numberValue()).intValue(); + cp_Int.putInt(x); + } + break; + case CONSTANT_Float: + for (int i = 0; i < cpMap.length; i++) { + NumberEntry e = (NumberEntry) cpMap[i]; + float fx = ((Float)e.numberValue()).floatValue(); + int x = Float.floatToIntBits(fx); + cp_Float.putInt(x); + } + break; + case CONSTANT_Long: + for (int i = 0; i < cpMap.length; i++) { + NumberEntry e = (NumberEntry) cpMap[i]; + long x = ((Long)e.numberValue()).longValue(); + cp_Long_hi.putInt((int)(x >>> 32)); + cp_Long_lo.putInt((int)(x >>> 0)); + } + break; + case CONSTANT_Double: + for (int i = 0; i < cpMap.length; i++) { + NumberEntry e = (NumberEntry) cpMap[i]; + double dx = ((Double)e.numberValue()).doubleValue(); + long x = Double.doubleToLongBits(dx); + cp_Double_hi.putInt((int)(x >>> 32)); + cp_Double_lo.putInt((int)(x >>> 0)); + } + break; + case CONSTANT_String: + for (int i = 0; i < cpMap.length; i++) { + StringEntry e = (StringEntry) cpMap[i]; + cp_String.putRef(e.ref); + } + break; + case CONSTANT_Class: + for (int i = 0; i < cpMap.length; i++) { + ClassEntry e = (ClassEntry) cpMap[i]; + cp_Class.putRef(e.ref); + } + break; + case CONSTANT_Signature: + writeSignatureBands(cpMap); + break; + case CONSTANT_NameandType: + for (int i = 0; i < cpMap.length; i++) { + DescriptorEntry e = (DescriptorEntry) cpMap[i]; + cp_Descr_name.putRef(e.nameRef); + cp_Descr_type.putRef(e.typeRef); + } + break; + case CONSTANT_Fieldref: + writeMemberRefs(tag, cpMap, cp_Field_class, cp_Field_desc); + break; + case CONSTANT_Methodref: + writeMemberRefs(tag, cpMap, cp_Method_class, cp_Method_desc); + break; + case CONSTANT_InterfaceMethodref: + writeMemberRefs(tag, cpMap, cp_Imethod_class, cp_Imethod_desc); + break; + case CONSTANT_MethodHandle: + for (int i = 0; i < cpMap.length; i++) { + MethodHandleEntry e = (MethodHandleEntry) cpMap[i]; + cp_MethodHandle_refkind.putInt(e.refKind); + cp_MethodHandle_member.putRef(e.memRef); + } + break; + case CONSTANT_MethodType: + for (int i = 0; i < cpMap.length; i++) { + MethodTypeEntry e = (MethodTypeEntry) cpMap[i]; + cp_MethodType.putRef(e.typeRef); + } + break; + case CONSTANT_InvokeDynamic: + for (int i = 0; i < cpMap.length; i++) { + InvokeDynamicEntry e = (InvokeDynamicEntry) cpMap[i]; + cp_InvokeDynamic_spec.putRef(e.bssRef); + cp_InvokeDynamic_desc.putRef(e.descRef); + } + break; + case CONSTANT_BootstrapMethod: + for (int i = 0; i < cpMap.length; i++) { + BootstrapMethodEntry e = (BootstrapMethodEntry) cpMap[i]; + cp_BootstrapMethod_ref.putRef(e.bsmRef); + cp_BootstrapMethod_arg_count.putInt(e.argRefs.length); + for (Entry argRef : e.argRefs) { + cp_BootstrapMethod_arg.putRef(argRef); + } + } + break; + default: + throw new AssertionError("unexpected CP tag in package"); + } + } + if (optDumpBands || verbose > 1) { + for (byte tag = CONSTANT_GroupFirst; tag < CONSTANT_GroupLimit; tag++) { + Index index = cp.getIndexByTag(tag); + if (index == null || index.isEmpty()) continue; + Entry[] cpMap = index.cpMap; + if (verbose > 1) + Utils.log.info("Index group "+ConstantPool.tagName(tag)+" contains "+cpMap.length+" entries."); + if (optDumpBands) { + try (PrintStream ps = new PrintStream(getDumpStream(index.debugName, tag, ".gidx", index))) { + printArrayTo(ps, cpMap, 0, cpMap.length, true); + } + } + } + } + } + + void writeUtf8Bands(Entry[] cpMap) throws IOException { + if (cpMap.length == 0) + return; // nothing to write + + // The first element must always be the empty string. + assert(cpMap[0].stringValue().equals("")); + final int SUFFIX_SKIP_1 = 1; + final int PREFIX_SKIP_2 = 2; + + // Fetch the char arrays, first of all. + char[][] chars = new char[cpMap.length][]; + for (int i = 0; i < chars.length; i++) { + chars[i] = cpMap[i].stringValue().toCharArray(); + } + + // First band: Write lengths of shared prefixes. + int[] prefixes = new int[cpMap.length]; // includes 2 skipped zeroes + char[] prevChars = {}; + for (int i = 0; i < chars.length; i++) { + int prefix = 0; + char[] curChars = chars[i]; + int limit = Math.min(curChars.length, prevChars.length); + while (prefix < limit && curChars[prefix] == prevChars[prefix]) + prefix++; + prefixes[i] = prefix; + if (i >= PREFIX_SKIP_2) + cp_Utf8_prefix.putInt(prefix); + else + assert(prefix == 0); + prevChars = curChars; + } + + // Second band: Write lengths of unshared suffixes. + // Third band: Write the char values in the unshared suffixes. + for (int i = 0; i < chars.length; i++) { + char[] str = chars[i]; + int prefix = prefixes[i]; + int suffix = str.length - prefixes[i]; + boolean isPacked = false; + if (suffix == 0) { + // Zero suffix length is special flag to indicate + // separate treatment in cp_Utf8_big bands. + // This suffix length never occurs naturally, + // except in the one case of a zero-length string. + // (If it occurs, it is the first, due to sorting.) + // The zero length string must, paradoxically, be + // encoded as a zero-length cp_Utf8_big band. + // This wastes exactly (& tolerably) one null byte. + isPacked = (i >= SUFFIX_SKIP_1); + // Do not bother to add an empty "(Utf8_big_0)" band. + // Also, the initial empty string does not require a band. + } else if (optBigStrings && effort > 1 && suffix > 100) { + int numWide = 0; + for (int n = 0; n < suffix; n++) { + if (str[prefix+n] > 127) { + numWide++; + } + } + if (numWide > 100) { + // Try packing the chars with an alternate encoding. + isPacked = tryAlternateEncoding(i, numWide, str, prefix); + } + } + if (i < SUFFIX_SKIP_1) { + // No output. + assert(!isPacked); + assert(suffix == 0); + } else if (isPacked) { + // Mark packed string with zero-length suffix count. + // This tells the unpacker to go elsewhere for the suffix bits. + // Fourth band: Write unshared suffix with alternate coding. + cp_Utf8_suffix.putInt(0); + cp_Utf8_big_suffix.putInt(suffix); + } else { + assert(suffix != 0); // would be ambiguous + // Normal string. Save suffix in third and fourth bands. + cp_Utf8_suffix.putInt(suffix); + for (int n = 0; n < suffix; n++) { + int ch = str[prefix+n]; + cp_Utf8_chars.putInt(ch); + } + } + } + if (verbose > 0) { + int normCharCount = cp_Utf8_chars.length(); + int packCharCount = cp_Utf8_big_chars.length(); + int charCount = normCharCount + packCharCount; + Utils.log.info("Utf8string #CHARS="+charCount+" #PACKEDCHARS="+packCharCount); + } + } + + private boolean tryAlternateEncoding(int i, int numWide, + char[] str, int prefix) { + int suffix = str.length - prefix; + int[] cvals = new int[suffix]; + for (int n = 0; n < suffix; n++) { + cvals[n] = str[prefix+n]; + } + CodingChooser cc = getCodingChooser(); + Coding bigRegular = cp_Utf8_big_chars.regularCoding; + String bandName = "(Utf8_big_"+i+")"; + int[] sizes = { 0, 0 }; + final int BYTE_SIZE = CodingChooser.BYTE_SIZE; + final int ZIP_SIZE = CodingChooser.ZIP_SIZE; + if (verbose > 1 || cc.verbose > 1) { + Utils.log.fine("--- chooseCoding "+bandName); + } + CodingMethod special = cc.choose(cvals, bigRegular, sizes); + Coding charRegular = cp_Utf8_chars.regularCoding; + if (verbose > 1) + Utils.log.fine("big string["+i+"] len="+suffix+" #wide="+numWide+" size="+sizes[BYTE_SIZE]+"/z="+sizes[ZIP_SIZE]+" coding "+special); + if (special != charRegular) { + int specialZipSize = sizes[ZIP_SIZE]; + int[] normalSizes = cc.computeSize(charRegular, cvals); + int normalZipSize = normalSizes[ZIP_SIZE]; + int minWin = Math.max(5, normalZipSize/1000); + if (verbose > 1) + Utils.log.fine("big string["+i+"] normalSize="+normalSizes[BYTE_SIZE]+"/z="+normalSizes[ZIP_SIZE]+" win="+(specialZipSize>> 32)); + if (haveModtime) + file_modtime.putInt(file.modtime - pkg.default_modtime); + if (haveOptions) + file_options.putInt(file.options); + file.writeTo(file_bits.collectorStream()); + if (verbose > 1) + Utils.log.fine("Wrote "+len+" bytes of "+file.name.stringValue()); + } + if (verbose > 0) + Utils.log.info("Wrote "+numFiles+" resource files"); + } + + void collectAttributeLayouts() { + maxFlags = new int[ATTR_CONTEXT_LIMIT]; + allLayouts = new FixedList<>(ATTR_CONTEXT_LIMIT); + for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { + allLayouts.set(i, new HashMap<>()); + } + // Collect maxFlags and allLayouts. + for (Class cls : pkg.classes) { + visitAttributeLayoutsIn(ATTR_CONTEXT_CLASS, cls); + for (Class.Field f : cls.getFields()) { + visitAttributeLayoutsIn(ATTR_CONTEXT_FIELD, f); + } + for (Class.Method m : cls.getMethods()) { + visitAttributeLayoutsIn(ATTR_CONTEXT_METHOD, m); + if (m.code != null) { + visitAttributeLayoutsIn(ATTR_CONTEXT_CODE, m.code); + } + } + } + // If there are many species of attributes, use 63-bit flags. + for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { + int nl = allLayouts.get(i).size(); + boolean haveLongFlags = haveFlagsHi(i); + final int TOO_MANY_ATTRS = 32 /*int flag size*/ + - 12 /*typical flag bits in use*/ + + 4 /*typical number of OK overflows*/; + if (nl >= TOO_MANY_ATTRS) { // heuristic + int mask = 1<<(LG_AO_HAVE_XXX_FLAGS_HI+i); + archiveOptions |= mask; + haveLongFlags = true; + if (verbose > 0) + Utils.log.info("Note: Many "+Attribute.contextName(i)+" attributes forces 63-bit flags"); + } + if (verbose > 1) { + Utils.log.fine(Attribute.contextName(i)+".maxFlags = 0x"+Integer.toHexString(maxFlags[i])); + Utils.log.fine(Attribute.contextName(i)+".#layouts = "+nl); + } + assert(haveFlagsHi(i) == haveLongFlags); + } + initAttrIndexLimit(); + + // Standard indexes can never conflict with flag bits. Assert it. + for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { + assert((attrFlagMask[i] & maxFlags[i]) == 0); + } + // Collect counts for both predefs. and custom defs. + // Decide on custom, local attribute definitions. + backCountTable = new HashMap<>(); + attrCounts = new int[ATTR_CONTEXT_LIMIT][]; + for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { + // Now the remaining defs in allLayouts[i] need attr. indexes. + // Fill up unused flag bits with new defs. + // Unused bits are those which are not used by predefined attrs, + // and which are always clear in the classfiles. + long avHiBits = ~(maxFlags[i] | attrFlagMask[i]); + assert(attrIndexLimit[i] > 0); + assert(attrIndexLimit[i] < 64); // all bits fit into a Java long + avHiBits &= (1L< defMap = allLayouts.get(i); + @SuppressWarnings({"unchecked", "rawtypes"}) + Map.Entry[] layoutsAndCounts = + new Map.Entry[defMap.size()]; + defMap.entrySet().toArray(layoutsAndCounts); + // Sort by count, most frequent first. + // Predefs. participate in this sort, though it does not matter. + Arrays.sort(layoutsAndCounts, + new Comparator<>() { + public int compare(Map.Entry e0, + Map.Entry e1) { + // Primary sort key is count, reversed. + int r = -(e0.getValue()[0] - e1.getValue()[0]); + if (r != 0) return r; + return e0.getKey().compareTo(e1.getKey()); + } + }); + attrCounts[i] = new int[attrIndexLimit[i]+layoutsAndCounts.length]; + for (int j = 0; j < layoutsAndCounts.length; j++) { + Map.Entry e = layoutsAndCounts[j]; + Attribute.Layout def = e.getKey(); + int count = e.getValue()[0]; + int index; + Integer predefIndex = attrIndexTable.get(def); + if (predefIndex != null) { + // The index is already set. + index = predefIndex.intValue(); + } else if (avHiBits != 0) { + while ((avHiBits & 1) == 0) { + avHiBits >>>= 1; + nextLoBit += 1; + } + avHiBits -= 1; // clear low bit; we are using it now + // Update attrIndexTable: + index = setAttributeLayoutIndex(def, nextLoBit); + } else { + // Update attrIndexTable: + index = setAttributeLayoutIndex(def, ATTR_INDEX_OVERFLOW); + } + + // Now that we know the index, record the count of this def. + attrCounts[i][index] = count; + + // For all callables in the def, keep a tally of back-calls. + Attribute.Layout.Element[] cbles = def.getCallables(); + final int[] bc = new int[cbles.length]; + for (int k = 0; k < cbles.length; k++) { + assert(cbles[k].kind == Attribute.EK_CBLE); + if (!cbles[k].flagTest(Attribute.EF_BACK)) { + bc[k] = -1; // no count to accumulate here + } + } + backCountTable.put(def, bc); + + if (predefIndex == null) { + // Make sure the package CP can name the local attribute. + Entry ne = ConstantPool.getUtf8Entry(def.name()); + String layout = def.layoutForClassVersion(getHighestClassVersion()); + Entry le = ConstantPool.getUtf8Entry(layout); + requiredEntries.add(ne); + requiredEntries.add(le); + if (verbose > 0) { + if (index < attrIndexLimit[i]) + Utils.log.info("Using free flag bit 1<<"+index+" for "+count+" occurrences of "+def); + else + Utils.log.info("Using overflow index "+index+" for "+count+" occurrences of "+def); + } + } + } + } + // Later, when emitting attr_definition_bands, we will look at + // attrDefSeen and attrDefs at position 32/63 and beyond. + // The attrIndexTable will provide elements of xxx_attr_indexes bands. + + // Done with scratch variables: + maxFlags = null; + allLayouts = null; + } + + // Scratch variables for processing attributes and flags. + int[] maxFlags; + List> allLayouts; + + void visitAttributeLayoutsIn(int ctype, Attribute.Holder h) { + // Make note of which flags appear in the class file. + // Set them in maxFlags. + maxFlags[ctype] |= h.flags; + for (Attribute a : h.getAttributes()) { + Attribute.Layout def = a.layout(); + Map defMap = allLayouts.get(ctype); + int[] count = defMap.get(def); + if (count == null) { + defMap.put(def, count = new int[1]); + } + if (count[0] < Integer.MAX_VALUE) { + count[0] += 1; + } + } + } + + Attribute.Layout[] attrDefsWritten; + + void writeAttrDefs() throws IOException { + List defList = new ArrayList<>(); + for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { + int limit = attrDefs.get(i).size(); + for (int j = 0; j < limit; j++) { + int header = i; // ctype + if (j < attrIndexLimit[i]) { + header |= ((j + ADH_BIT_IS_LSB) << ADH_BIT_SHIFT); + assert(header < 0x100); // must fit into a byte + // (...else header is simply ctype, with zero high bits.) + if (!testBit(attrDefSeen[i], 1L<() { + public int compare(Object[] a0, Object[] a1) { + // Primary sort key is attr def header. + @SuppressWarnings("unchecked") + int r = ((Comparable)a0[0]).compareTo(a1[0]); + if (r != 0) return r; + Integer ind0 = attrIndexTable.get(a0[1]); + Integer ind1 = attrIndexTable.get(a1[1]); + // Secondary sort key is attribute index. + // (This must be so, in order to keep overflow attr order.) + assert(ind0 != null); + assert(ind1 != null); + return ind0.compareTo(ind1); + } + }); + attrDefsWritten = new Attribute.Layout[numAttrDefs]; + try (PrintStream dump = !optDumpBands ? null + : new PrintStream(getDumpStream(attr_definition_headers, ".def"))) + { + int[] indexForDebug = Arrays.copyOf(attrIndexLimit, ATTR_CONTEXT_LIMIT); + for (int i = 0; i < defs.length; i++) { + int header = ((Integer)defs[i][0]).intValue(); + Attribute.Layout def = (Attribute.Layout) defs[i][1]; + attrDefsWritten[i] = def; + assert((header & ADH_CONTEXT_MASK) == def.ctype()); + attr_definition_headers.putByte(header); + attr_definition_name.putRef(ConstantPool.getUtf8Entry(def.name())); + String layout = def.layoutForClassVersion(getHighestClassVersion()); + attr_definition_layout.putRef(ConstantPool.getUtf8Entry(layout)); + // Check that we are transmitting that correct attribute index: + boolean debug = false; + assert(debug = true); + if (debug) { + int hdrIndex = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB; + if (hdrIndex < 0) hdrIndex = indexForDebug[def.ctype()]++; + int realIndex = (attrIndexTable.get(def)).intValue(); + assert(hdrIndex == realIndex); + } + if (dump != null) { + int index = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB; + dump.println(index+" "+def); + } + } + } + } + + void writeAttrCounts() throws IOException { + // Write the four xxx_attr_calls bands. + for (int ctype = 0; ctype < ATTR_CONTEXT_LIMIT; ctype++) { + MultiBand xxx_attr_bands = attrBands[ctype]; + IntBand xxx_attr_calls = getAttrBand(xxx_attr_bands, AB_ATTR_CALLS); + Attribute.Layout[] defs = new Attribute.Layout[attrDefs.get(ctype).size()]; + attrDefs.get(ctype).toArray(defs); + for (boolean predef = true; ; predef = false) { + for (int ai = 0; ai < defs.length; ai++) { + Attribute.Layout def = defs[ai]; + if (def == null) continue; // unused index + if (predef != isPredefinedAttr(ctype, ai)) + continue; // wrong pass + int totalCount = attrCounts[ctype][ai]; + if (totalCount == 0) + continue; // irrelevant + int[] bc = backCountTable.get(def); + for (int j = 0; j < bc.length; j++) { + if (bc[j] >= 0) { + int backCount = bc[j]; + bc[j] = -1; // close out; do not collect further counts + xxx_attr_calls.putInt(backCount); + assert(def.getCallables()[j].flagTest(Attribute.EF_BACK)); + } else { + assert(!def.getCallables()[j].flagTest(Attribute.EF_BACK)); + } + } + } + if (!predef) break; + } + } + } + + void trimClassAttributes() { + for (Class cls : pkg.classes) { + // Replace "obvious" SourceFile attrs by null. + cls.minimizeSourceFile(); + // BootstrapMethods should never have been inserted. + assert(cls.getAttribute(Package.attrBootstrapMethodsEmpty) == null); + } + } + + void collectInnerClasses() { + // Capture inner classes, removing them from individual classes. + // Irregular inner classes must stay local, though. + Map allICMap = new HashMap<>(); + // First, collect a consistent global set. + for (Class cls : pkg.classes) { + if (!cls.hasInnerClasses()) continue; + for (InnerClass ic : cls.getInnerClasses()) { + InnerClass pic = allICMap.put(ic.thisClass, ic); + if (pic != null && !pic.equals(ic) && pic.predictable) { + // Different ICs. Choose the better to make global. + allICMap.put(pic.thisClass, pic); + } + } + } + + InnerClass[] allICs = new InnerClass[allICMap.size()]; + allICMap.values().toArray(allICs); + allICMap = null; // done with it + + // Note: The InnerClasses attribute must be in a valid order, + // so that A$B always occurs earlier than A$B$C. This is an + // important side-effect of sorting lexically by class name. + Arrays.sort(allICs); // put in canonical order + pkg.setAllInnerClasses(Arrays.asList(allICs)); + + // Next, empty out of every local set the consistent entries. + // Calculate whether there is any remaining need to have a local + // set, and whether it needs to be locked. + for (Class cls : pkg.classes) { + cls.minimizeLocalICs(); + } + } + + void writeInnerClasses() throws IOException { + for (InnerClass ic : pkg.getAllInnerClasses()) { + int flags = ic.flags; + assert((flags & ACC_IC_LONG_FORM) == 0); + if (!ic.predictable) { + flags |= ACC_IC_LONG_FORM; + } + ic_this_class.putRef(ic.thisClass); + ic_flags.putInt(flags); + if (!ic.predictable) { + ic_outer_class.putRef(ic.outerClass); + ic_name.putRef(ic.name); + } + } + } + + /** If there are any extra InnerClasses entries to write which are + * not already implied by the global table, put them into a + * local attribute. This is expected to be rare. + */ + void writeLocalInnerClasses(Class cls) throws IOException { + List localICs = cls.getInnerClasses(); + class_InnerClasses_N.putInt(localICs.size()); + for(InnerClass ic : localICs) { + class_InnerClasses_RC.putRef(ic.thisClass); + // Is it redundant with the global version? + if (ic.equals(pkg.getGlobalInnerClass(ic.thisClass))) { + // A zero flag means copy a global IC here. + class_InnerClasses_F.putInt(0); + } else { + int flags = ic.flags; + if (flags == 0) + flags = ACC_IC_LONG_FORM; // force it to be non-zero + class_InnerClasses_F.putInt(flags); + class_InnerClasses_outer_RCN.putRef(ic.outerClass); + class_InnerClasses_name_RUN.putRef(ic.name); + } + } + } + + void writeClassesAndByteCodes() throws IOException { + Class[] classes = new Class[pkg.classes.size()]; + pkg.classes.toArray(classes); + // Note: This code respects the order in which caller put classes. + if (verbose > 0) + Utils.log.info(" ...scanning "+classes.length+" classes..."); + + int nwritten = 0; + for (int i = 0; i < classes.length; i++) { + // Collect the class body, sans bytecodes. + Class cls = classes[i]; + if (verbose > 1) + Utils.log.fine("Scanning "+cls); + + ClassEntry thisClass = cls.thisClass; + ClassEntry superClass = cls.superClass; + ClassEntry[] interfaces = cls.interfaces; + // Encode rare case of null superClass as thisClass: + assert(superClass != thisClass); // bad class file!? + if (superClass == null) superClass = thisClass; + class_this.putRef(thisClass); + class_super.putRef(superClass); + class_interface_count.putInt(cls.interfaces.length); + for (int j = 0; j < interfaces.length; j++) { + class_interface.putRef(interfaces[j]); + } + + writeMembers(cls); + writeAttrs(ATTR_CONTEXT_CLASS, cls, cls); + + nwritten++; + if (verbose > 0 && (nwritten % 1000) == 0) + Utils.log.info("Have scanned "+nwritten+" classes..."); + } + } + + void writeMembers(Class cls) throws IOException { + List fields = cls.getFields(); + class_field_count.putInt(fields.size()); + for (Class.Field f : fields) { + field_descr.putRef(f.getDescriptor()); + writeAttrs(ATTR_CONTEXT_FIELD, f, cls); + } + + List methods = cls.getMethods(); + class_method_count.putInt(methods.size()); + for (Class.Method m : methods) { + method_descr.putRef(m.getDescriptor()); + writeAttrs(ATTR_CONTEXT_METHOD, m, cls); + assert((m.code != null) == (m.getAttribute(attrCodeEmpty) != null)); + if (m.code != null) { + writeCodeHeader(m.code); + writeByteCodes(m.code); + } + } + } + + void writeCodeHeader(Code c) throws IOException { + boolean attrsOK = testBit(archiveOptions, AO_HAVE_ALL_CODE_FLAGS); + int na = c.attributeSize(); + int sc = shortCodeHeader(c); + if (!attrsOK && na > 0) + // We must write flags, and can only do so for long headers. + sc = LONG_CODE_HEADER; + if (verbose > 2) { + int siglen = c.getMethod().getArgumentSize(); + Utils.log.fine("Code sizes info "+c.max_stack+" "+c.max_locals+" "+c.getHandlerCount()+" "+siglen+" "+na+(sc > 0 ? " SHORT="+sc : "")); + } + code_headers.putByte(sc); + if (sc == LONG_CODE_HEADER) { + code_max_stack.putInt(c.getMaxStack()); + code_max_na_locals.putInt(c.getMaxNALocals()); + code_handler_count.putInt(c.getHandlerCount()); + } else { + assert(attrsOK || na == 0); + assert(c.getHandlerCount() < shortCodeHeader_h_limit); + } + writeCodeHandlers(c); + if (sc == LONG_CODE_HEADER || attrsOK) + writeAttrs(ATTR_CONTEXT_CODE, c, c.thisClass()); + } + + void writeCodeHandlers(Code c) throws IOException { + int sum, del; + for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) { + code_handler_class_RCN.putRef(c.handler_class[j]); // null OK + // Encode end as offset from start, and catch as offset from end, + // because they are strongly correlated. + sum = c.encodeBCI(c.handler_start[j]); + code_handler_start_P.putInt(sum); + del = c.encodeBCI(c.handler_end[j]) - sum; + code_handler_end_PO.putInt(del); + sum += del; + del = c.encodeBCI(c.handler_catch[j]) - sum; + code_handler_catch_PO.putInt(del); + } + } + + // Generic routines for writing attributes and flags of + // classes, fields, methods, and codes. + void writeAttrs(int ctype, + final Attribute.Holder h, + Class cls) throws IOException { + MultiBand xxx_attr_bands = attrBands[ctype]; + IntBand xxx_flags_hi = getAttrBand(xxx_attr_bands, AB_FLAGS_HI); + IntBand xxx_flags_lo = getAttrBand(xxx_attr_bands, AB_FLAGS_LO); + boolean haveLongFlags = haveFlagsHi(ctype); + assert(attrIndexLimit[ctype] == (haveLongFlags? 63: 32)); + if (h.attributes == null) { + xxx_flags_lo.putInt(h.flags); // no extra bits to set here + if (haveLongFlags) + xxx_flags_hi.putInt(0); + return; + } + if (verbose > 3) + Utils.log.fine("Transmitting attrs for "+h+" flags="+Integer.toHexString(h.flags)); + + long flagMask = attrFlagMask[ctype]; // which flags are attr bits? + long flagsToAdd = 0; + int overflowCount = 0; + for (Attribute a : h.attributes) { + Attribute.Layout def = a.layout(); + int index = (attrIndexTable.get(def)).intValue(); + assert(attrDefs.get(ctype).get(index) == def); + if (verbose > 3) + Utils.log.fine("add attr @"+index+" "+a+" in "+h); + if (index < attrIndexLimit[ctype] && testBit(flagMask, 1L< 3) + Utils.log.fine("Adding flag bit 1<<"+index+" in "+Long.toHexString(flagMask)); + assert(!testBit(h.flags, 1L< 3) + Utils.log.fine("Adding overflow attr #"+overflowCount); + IntBand xxx_attr_indexes = getAttrBand(xxx_attr_bands, AB_ATTR_INDEXES); + xxx_attr_indexes.putInt(index); + // System.out.println("overflow @"+index); + } + if (def.bandCount == 0) { + if (def == attrInnerClassesEmpty) { + // Special logic to write this attr. + writeLocalInnerClasses((Class) h); + continue; + } + // Empty attr; nothing more to write here. + continue; + } + assert(a.fixups == null); + final Band[] ab = attrBandTable.get(def); + assert(ab != null); + assert(ab.length == def.bandCount); + final int[] bc = backCountTable.get(def); + assert(bc != null); + assert(bc.length == def.getCallables().length); + // Write one attribute of type def into ab. + if (verbose > 2) Utils.log.fine("writing "+a+" in "+h); + boolean isCV = (ctype == ATTR_CONTEXT_FIELD && def == attrConstantValue); + if (isCV) setConstantValueIndex((Class.Field)h); + a.parse(cls, a.bytes(), 0, a.size(), + new Attribute.ValueStream() { + public void putInt(int bandIndex, int value) { + ((IntBand) ab[bandIndex]).putInt(value); + } + public void putRef(int bandIndex, Entry ref) { + ((CPRefBand) ab[bandIndex]).putRef(ref); + } + public int encodeBCI(int bci) { + Code code = (Code) h; + return code.encodeBCI(bci); + } + public void noteBackCall(int whichCallable) { + assert(bc[whichCallable] >= 0); + bc[whichCallable] += 1; + } + }); + if (isCV) setConstantValueIndex(null); // clean up + } + + if (overflowCount > 0) { + IntBand xxx_attr_count = getAttrBand(xxx_attr_bands, AB_ATTR_COUNT); + xxx_attr_count.putInt(overflowCount); + } + + xxx_flags_lo.putInt(h.flags | (int)flagsToAdd); + if (haveLongFlags) + xxx_flags_hi.putInt((int)(flagsToAdd >>> 32)); + else + assert((flagsToAdd >>> 32) == 0); + assert((h.flags & flagsToAdd) == 0) + : (h+".flags=" + +Integer.toHexString(h.flags)+"^" + +Long.toHexString(flagsToAdd)); + } + + // temporary scratch variables for processing code blocks + private Code curCode; + private Class curClass; + private Entry[] curCPMap; + private void beginCode(Code c) { + assert(curCode == null); + curCode = c; + curClass = c.m.thisClass(); + curCPMap = c.getCPMap(); + } + private void endCode() { + curCode = null; + curClass = null; + curCPMap = null; + } + + // Return an _invokeinit_op variant, if the instruction matches one, + // else -1. + private int initOpVariant(Instruction i, Entry newClass) { + if (i.getBC() != _invokespecial) return -1; + MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap); + if ("".equals(ref.descRef.nameRef.stringValue()) == false) + return -1; + ClassEntry refClass = ref.classRef; + if (refClass == curClass.thisClass) + return _invokeinit_op+_invokeinit_self_option; + if (refClass == curClass.superClass) + return _invokeinit_op+_invokeinit_super_option; + if (refClass == newClass) + return _invokeinit_op+_invokeinit_new_option; + return -1; + } + + // Return a _self_linker_op variant, if the instruction matches one, + // else -1. + private int selfOpVariant(Instruction i) { + int bc = i.getBC(); + if (!(bc >= _first_linker_op && bc <= _last_linker_op)) return -1; + MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap); + // do not optimize this case, simply fall back to regular coding + if ((bc == _invokespecial || bc == _invokestatic) && + ref.tagEquals(CONSTANT_InterfaceMethodref)) + return -1; + ClassEntry refClass = ref.classRef; + int self_bc = _self_linker_op + (bc - _first_linker_op); + if (refClass == curClass.thisClass) + return self_bc; + if (refClass == curClass.superClass) + return self_bc + _self_linker_super_flag; + return -1; + } + + void writeByteCodes(Code code) throws IOException { + beginCode(code); + IndexGroup cp = pkg.cp; + + // true if the previous instruction is an aload to absorb + boolean prevAload = false; + + // class of most recent new; helps compress calls + Entry newClass = null; + + for (Instruction i = code.instructionAt(0); i != null; i = i.next()) { + // %%% Add a stress mode which issues _ref/_byte_escape. + if (verbose > 3) Utils.log.fine(i.toString()); + + if (i.isNonstandard()) { + // Crash and burn with a complaint if there are funny + // bytecodes in this class file. + String complaint = code.getMethod() + +" contains an unrecognized bytecode "+i + +"; please use the pass-file option on this class."; + Utils.log.warning(complaint); + throw new IOException(complaint); + } + + if (i.isWide()) { + if (verbose > 1) { + Utils.log.fine("_wide opcode in "+code); + Utils.log.fine(i.toString()); + } + bc_codes.putByte(_wide); + codeHist[_wide]++; + } + + int bc = i.getBC(); + + // Begin "bc_linker" compression. + if (bc == _aload_0) { + // Try to group aload_0 with a following operation. + Instruction ni = code.instructionAt(i.getNextPC()); + if (selfOpVariant(ni) >= 0) { + prevAload = true; + continue; + } + } + + // Test for invocations: + int init_bc = initOpVariant(i, newClass); + if (init_bc >= 0) { + if (prevAload) { + // get rid of it + bc_codes.putByte(_aload_0); + codeHist[_aload_0]++; + prevAload = false; //used up + } + // Write special bytecode. + bc_codes.putByte(init_bc); + codeHist[init_bc]++; + MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap); + // Write operand to a separate band. + int coding = cp.getOverloadingIndex(ref); + bc_initref.putInt(coding); + continue; + } + + int self_bc = selfOpVariant(i); + if (self_bc >= 0) { + boolean isField = Instruction.isFieldOp(bc); + boolean isSuper = (self_bc >= _self_linker_op+_self_linker_super_flag); + boolean isAload = prevAload; + prevAload = false; //used up + if (isAload) + self_bc += _self_linker_aload_flag; + // Write special bytecode. + bc_codes.putByte(self_bc); + codeHist[self_bc]++; + // Write field or method ref to a separate band. + MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap); + CPRefBand bc_which = selfOpRefBand(self_bc); + Index which_ix = cp.getMemberIndex(ref.tag, ref.classRef); + bc_which.putRef(ref, which_ix); + continue; + } + assert(!prevAload); + // End "bc_linker" compression. + + // Normal bytecode. + codeHist[bc]++; + switch (bc) { + case _tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label)) + case _lookupswitch: // apc: (df, nc, nc*(case, label)) + bc_codes.putByte(bc); + Instruction.Switch isw = (Instruction.Switch) i; + // Note that we do not write the alignment bytes. + int apc = isw.getAlignedPC(); + int npc = isw.getNextPC(); + // write a length specification into the bytecode stream + int caseCount = isw.getCaseCount(); + bc_case_count.putInt(caseCount); + putLabel(bc_label, code, i.getPC(), isw.getDefaultLabel()); + for (int j = 0; j < caseCount; j++) { + putLabel(bc_label, code, i.getPC(), isw.getCaseLabel(j)); + } + // Transmit case values in their own band. + if (bc == _tableswitch) { + bc_case_value.putInt(isw.getCaseValue(0)); + } else { + for (int j = 0; j < caseCount; j++) { + bc_case_value.putInt(isw.getCaseValue(j)); + } + } + // Done with the switch. + continue; + } + + int branch = i.getBranchLabel(); + if (branch >= 0) { + bc_codes.putByte(bc); + putLabel(bc_label, code, i.getPC(), branch); + continue; + } + Entry ref = i.getCPRef(curCPMap); + if (ref != null) { + if (bc == _new) newClass = ref; + if (bc == _ldc) ldcHist[ref.tag]++; + CPRefBand bc_which; + int vbc = bc; + switch (i.getCPTag()) { + case CONSTANT_LoadableValue: + switch (ref.tag) { + case CONSTANT_Integer: + bc_which = bc_intref; + switch (bc) { + case _ldc: vbc = _ildc; break; + case _ldc_w: vbc = _ildc_w; break; + default: assert(false); + } + break; + case CONSTANT_Float: + bc_which = bc_floatref; + switch (bc) { + case _ldc: vbc = _fldc; break; + case _ldc_w: vbc = _fldc_w; break; + default: assert(false); + } + break; + case CONSTANT_Long: + bc_which = bc_longref; + assert(bc == _ldc2_w); + vbc = _lldc2_w; + break; + case CONSTANT_Double: + bc_which = bc_doubleref; + assert(bc == _ldc2_w); + vbc = _dldc2_w; + break; + case CONSTANT_String: + bc_which = bc_stringref; + switch (bc) { + case _ldc: vbc = _sldc; break; + case _ldc_w: vbc = _sldc_w; break; + default: assert(false); + } + break; + case CONSTANT_Class: + bc_which = bc_classref; + switch (bc) { + case _ldc: vbc = _cldc; break; + case _ldc_w: vbc = _cldc_w; break; + default: assert(false); + } + break; + default: + // CONSTANT_MethodHandle, etc. + if (getHighestClassVersion().lessThan(JAVA7_MAX_CLASS_VERSION)) { + throw new IOException("bad class file major version for Java 7 ldc"); + } + bc_which = bc_loadablevalueref; + switch (bc) { + case _ldc: vbc = _qldc; break; + case _ldc_w: vbc = _qldc_w; break; + default: assert(false); + } + } + break; + case CONSTANT_Class: + // Use a special shorthand for the current class: + if (ref == curClass.thisClass) ref = null; + bc_which = bc_classref; break; + case CONSTANT_Fieldref: + bc_which = bc_fieldref; break; + case CONSTANT_Methodref: + if (ref.tagEquals(CONSTANT_InterfaceMethodref)) { + if (bc == _invokespecial) + vbc = _invokespecial_int; + if (bc == _invokestatic) + vbc = _invokestatic_int; + bc_which = bc_imethodref; + } else { + bc_which = bc_methodref; + } + break; + case CONSTANT_InterfaceMethodref: + bc_which = bc_imethodref; break; + case CONSTANT_InvokeDynamic: + bc_which = bc_indyref; break; + default: + bc_which = null; + assert(false); + } + if (ref != null && bc_which.index != null && !bc_which.index.contains(ref)) { + // Crash and burn with a complaint if there are funny + // references for this bytecode instruction. + // Example: invokestatic of a CONSTANT_InterfaceMethodref. + String complaint = code.getMethod() + + " contains a bytecode " + i + + " with an unsupported constant reference; please use the pass-file option on this class."; + Utils.log.warning(complaint); + throw new IOException(complaint); + } + bc_codes.putByte(vbc); + bc_which.putRef(ref); + // handle trailing junk + if (bc == _multianewarray) { + assert(i.getConstant() == code.getByte(i.getPC()+3)); + // Just dump the byte into the bipush pile + bc_byte.putByte(0xFF & i.getConstant()); + } else if (bc == _invokeinterface) { + assert(i.getLength() == 5); + // Make sure the discarded bytes are sane: + assert(i.getConstant() == (1+((MemberEntry)ref).descRef.typeRef.computeSize(true)) << 8); + } else if (bc == _invokedynamic) { + if (getHighestClassVersion().lessThan(JAVA7_MAX_CLASS_VERSION)) { + throw new IOException("bad class major version for Java 7 invokedynamic"); + } + assert(i.getLength() == 5); + assert(i.getConstant() == 0); // last 2 bytes MBZ + } else { + // Make sure there is nothing else to write. + assert(i.getLength() == ((bc == _ldc)?2:3)); + } + continue; + } + int slot = i.getLocalSlot(); + if (slot >= 0) { + bc_codes.putByte(bc); + bc_local.putInt(slot); + int con = i.getConstant(); + if (bc == _iinc) { + if (!i.isWide()) { + bc_byte.putByte(0xFF & con); + } else { + bc_short.putInt(0xFFFF & con); + } + } else { + assert(con == 0); + } + continue; + } + // Generic instruction. Copy the body. + bc_codes.putByte(bc); + int pc = i.getPC()+1; + int npc = i.getNextPC(); + if (pc < npc) { + // Do a few remaining multi-byte instructions. + switch (bc) { + case _sipush: + bc_short.putInt(0xFFFF & i.getConstant()); + break; + case _bipush: + bc_byte.putByte(0xFF & i.getConstant()); + break; + case _newarray: + bc_byte.putByte(0xFF & i.getConstant()); + break; + default: + assert(false); // that's it + } + } + } + bc_codes.putByte(_end_marker); + bc_codes.elementCountForDebug++; + codeHist[_end_marker]++; + endCode(); + } + + int[] codeHist = new int[1<<8]; + int[] ldcHist = new int[20]; + void printCodeHist() { + assert(verbose > 0); + String[] hist = new String[codeHist.length]; + int totalBytes = 0; + for (int bc = 0; bc < codeHist.length; bc++) { + totalBytes += codeHist[bc]; + } + for (int bc = 0; bc < codeHist.length; bc++) { + if (codeHist[bc] == 0) { hist[bc] = ""; continue; } + String iname = Instruction.byteName(bc); + String count = "" + codeHist[bc]; + count = " ".substring(count.length()) + count; + String pct = "" + (codeHist[bc] * 10000 / totalBytes); + while (pct.length() < 4) { + pct = "0" + pct; + } + pct = pct.substring(0, pct.length()-2) + "." + pct.substring(pct.length()-2); + hist[bc] = count + " " + pct + "% " + iname; + } + Arrays.sort(hist); + System.out.println("Bytecode histogram ["+totalBytes+"]"); + for (int i = hist.length; --i >= 0; ) { + if ("".equals(hist[i])) continue; + System.out.println(hist[i]); + } + for (int tag = 0; tag < ldcHist.length; tag++) { + int count = ldcHist[tag]; + if (count == 0) continue; + System.out.println("ldc "+ConstantPool.tagName(tag)+" "+count); + } + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/PackerImpl.java b/src/main/java/net/minecraftforge/gradle/util/pack200/PackerImpl.java new file mode 100644 index 000000000..df76fc6d0 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/PackerImpl.java @@ -0,0 +1,610 @@ +/* + * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import net.minecraftforge.gradle.util.pack200.Attribute.Layout; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.SortedMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; + + +/* + * Implementation of the Pack provider. + * + * @author John Rose + * @author Kumar Srinivasan + */ + +@SuppressWarnings({"removal"}) +public class PackerImpl extends TLGlobals implements Pack200.Packer { + + /** + * Constructs a Packer object and sets the initial state of + * the packer engines. + */ + public PackerImpl() {} + + /** + * Get the set of options for the pack and unpack engines. + * @return A sorted association of option key strings to option values. + */ + public SortedMap properties() { + return props; + } + + //Driver routines + + /** + * Takes a JarFile and converts into a pack-stream. + *

+ * Closes its input but not its output. (Pack200 archives are appendable.) + * @param in a JarFile + * @param out an OutputStream + * @exception IOException if an error is encountered. + */ + public synchronized void pack(JarFile in, OutputStream out) throws IOException { + assert(Utils.currentInstance.get() == null); + try { + Utils.currentInstance.set(this); + if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) { + Utils.copyJarFile(in, out); + } else { + (new DoPack()).run(in, out); + } + } finally { + Utils.currentInstance.set(null); + in.close(); + } + } + + /** + * Takes a JarInputStream and converts into a pack-stream. + *

+ * Closes its input but not its output. (Pack200 archives are appendable.) + *

+ * The modification time and deflation hint attributes are not available, + * for the jar-manifest file and the directory containing the file. + * + * @see #MODIFICATION_TIME + * @see #DEFLATION_HINT + * @param in a JarInputStream + * @param out an OutputStream + * @exception IOException if an error is encountered. + */ + public synchronized void pack(JarInputStream in, OutputStream out) throws IOException { + assert(Utils.currentInstance.get() == null); + try { + Utils.currentInstance.set(this); + if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) { + Utils.copyJarFile(in, out); + } else { + (new DoPack()).run(in, out); + } + } finally { + Utils.currentInstance.set(null); + in.close(); + } + } + + // All the worker bees..... + // The packer worker. + private class DoPack { + final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); + + { + props.setInteger(Pack200.Packer.PROGRESS, 0); + if (verbose > 0) Utils.log.info(props.toString()); + } + + // Here's where the bits are collected before getting packed, we also + // initialize the version numbers now. + final Package pkg = new Package(Package.Version.makeVersion(props, "min.class"), + Package.Version.makeVersion(props, "max.class"), + Package.Version.makeVersion(props, "package")); + + final String unknownAttrCommand; + { + String uaMode = props.getProperty(Pack200.Packer.UNKNOWN_ATTRIBUTE, Pack200.Packer.PASS); + if (!(Pack200.Packer.STRIP.equals(uaMode) || + Pack200.Packer.PASS.equals(uaMode) || + Pack200.Packer.ERROR.equals(uaMode))) { + throw new RuntimeException("Bad option: " + Pack200.Packer.UNKNOWN_ATTRIBUTE + " = " + uaMode); + } + unknownAttrCommand = uaMode.intern(); + } + final String classFormatCommand; + { + String fmtMode = props.getProperty(Utils.CLASS_FORMAT_ERROR, Pack200.Packer.PASS); + if (!(Pack200.Packer.PASS.equals(fmtMode) || + Pack200.Packer.ERROR.equals(fmtMode))) { + throw new RuntimeException("Bad option: " + Utils.CLASS_FORMAT_ERROR + " = " + fmtMode); + } + classFormatCommand = fmtMode.intern(); + } + + final Map attrDefs; + final Map attrCommands; + { + Map lattrDefs = new HashMap<>(); + Map lattrCommands = new HashMap<>(); + String[] keys = { + Pack200.Packer.CLASS_ATTRIBUTE_PFX, + Pack200.Packer.FIELD_ATTRIBUTE_PFX, + Pack200.Packer.METHOD_ATTRIBUTE_PFX, + Pack200.Packer.CODE_ATTRIBUTE_PFX + }; + int[] ctypes = { + Constants.ATTR_CONTEXT_CLASS, + Constants.ATTR_CONTEXT_FIELD, + Constants.ATTR_CONTEXT_METHOD, + Constants.ATTR_CONTEXT_CODE + }; + for (int i = 0; i < ctypes.length; i++) { + String pfx = keys[i]; + Map map = props.prefixMap(pfx); + for (String key : map.keySet()) { + assert(key.startsWith(pfx)); + String name = key.substring(pfx.length()); + String layout = props.getProperty(key); + Layout lkey = Attribute.keyForLookup(ctypes[i], name); + if (Pack200.Packer.STRIP.equals(layout) || + Pack200.Packer.PASS.equals(layout) || + Pack200.Packer.ERROR.equals(layout)) { + lattrCommands.put(lkey, layout.intern()); + } else { + Attribute.define(lattrDefs, ctypes[i], name, layout); + if (verbose > 1) { + Utils.log.fine("Added layout for "+Constants.ATTR_CONTEXT_NAME[i]+" attribute "+name+" = "+layout); + } + assert(lattrDefs.containsKey(lkey)); + } + } + } + this.attrDefs = (lattrDefs.isEmpty()) ? null : lattrDefs; + this.attrCommands = (lattrCommands.isEmpty()) ? null : lattrCommands; + } + + final boolean keepFileOrder + = props.getBoolean(Pack200.Packer.KEEP_FILE_ORDER); + final boolean keepClassOrder + = props.getBoolean(Utils.PACK_KEEP_CLASS_ORDER); + + final boolean keepModtime + = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME)); + final boolean latestModtime + = Pack200.Packer.LATEST.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME)); + final boolean keepDeflateHint + = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.DEFLATE_HINT)); + { + if (!keepModtime && !latestModtime) { + int modtime = props.getTime(Pack200.Packer.MODIFICATION_TIME); + if (modtime != Constants.NO_MODTIME) { + pkg.default_modtime = modtime; + } + } + if (!keepDeflateHint) { + boolean deflate_hint = props.getBoolean(Pack200.Packer.DEFLATE_HINT); + if (deflate_hint) { + pkg.default_options |= Constants.AO_DEFLATE_HINT; + } + } + } + + long totalOutputSize = 0; + int segmentCount = 0; + long segmentTotalSize = 0; + long segmentSize = 0; // running counter + final long segmentLimit; + { + long limit; + if (props.getProperty(Pack200.Packer.SEGMENT_LIMIT, "").equals("")) + limit = -1; + else + limit = props.getLong(Pack200.Packer.SEGMENT_LIMIT); + limit = Math.min(Integer.MAX_VALUE, limit); + limit = Math.max(-1, limit); + if (limit == -1) + limit = Long.MAX_VALUE; + segmentLimit = limit; + } + + final List passFiles; // parsed pack.pass.file options + { + // Which class files will be passed through? + passFiles = props.getProperties(Pack200.Packer.PASS_FILE_PFX); + for (ListIterator i = passFiles.listIterator(); i.hasNext(); ) { + String file = i.next(); + if (file == null) { i.remove(); continue; } + file = Utils.getJarEntryName(file); // normalize '\\' to '/' + if (file.endsWith("/")) + file = file.substring(0, file.length()-1); + i.set(file); + } + if (verbose > 0) Utils.log.info("passFiles = " + passFiles); + } + + { + // Hook for testing: Forces use of special archive modes. + int opt = props.getInteger(Utils.COM_PREFIX+"archive.options"); + if (opt != 0) + pkg.default_options |= opt; + } + + // (Done collecting options from props.) + + // Get a new package, based on the old one. + private void makeNextPackage() { + pkg.reset(); + } + + final class InFile { + final String name; + final JarFile jf; + final JarEntry je; + final File f; + int modtime = Constants.NO_MODTIME; + int options; + InFile(String name) { + this.name = Utils.getJarEntryName(name); + this.f = new File(name); + this.jf = null; + this.je = null; + int timeSecs = getModtime(f.lastModified()); + if (keepModtime && timeSecs != Constants.NO_MODTIME) { + this.modtime = timeSecs; + } else if (latestModtime && timeSecs > pkg.default_modtime) { + pkg.default_modtime = timeSecs; + } + } + InFile(JarFile jf, JarEntry je) { + this.name = Utils.getJarEntryName(je.getName()); + this.f = null; + this.jf = jf; + this.je = je; + int timeSecs = (int) je.getTimeLocal() + .atOffset(ZoneOffset.UTC) + .toEpochSecond(); + if (keepModtime && timeSecs != Constants.NO_MODTIME) { + this.modtime = timeSecs; + } else if (latestModtime && timeSecs > pkg.default_modtime) { + pkg.default_modtime = timeSecs; + } + if (keepDeflateHint && je.getMethod() == JarEntry.DEFLATED) { + options |= Constants.FO_DEFLATE_HINT; + } + } + InFile(JarEntry je) { + this(null, je); + } + boolean isClassFile() { + if (!name.endsWith(".class") || name.endsWith("module-info.class")) { + return false; + } + for (String prefix = name;;) { + if (passFiles.contains(prefix)) { + return false; + } + int chop = prefix.lastIndexOf('/'); + if (chop < 0) { + break; + } + prefix = prefix.substring(0, chop); + } + return true; + } + boolean isMetaInfFile() { + return name.startsWith("/" + Utils.METAINF) + || name.startsWith(Utils.METAINF); + } + boolean mustProcess() { + return !isMetaInfFile() && isClassFile(); + } + long getInputLength() { + long len = (je != null)? je.getSize(): f.length(); + assert(len >= 0) : this+".len="+len; + // Bump size by pathname length and modtime/def-hint bytes. + return Math.max(0, len) + name.length() + 5; + } + int getModtime(long timeMillis) { + // Convert milliseconds to seconds. + long seconds = (timeMillis+500) / 1000; + if ((int)seconds == seconds) { + return (int)seconds; + } else { + Utils.log.warning("overflow in modtime for "+f); + return Constants.NO_MODTIME; + } + } + void copyTo(Package.File file) { + if (modtime != Constants.NO_MODTIME) + file.modtime = modtime; + file.options |= options; + } + InputStream getInputStream() throws IOException { + if (jf != null) + return jf.getInputStream(je); + else + return new FileInputStream(f); + } + + public String toString() { + return name; + } + } + + private int nread = 0; // used only if (verbose > 0) + private void noteRead(InFile f) { + nread++; + if (verbose > 2) + Utils.log.fine("...read "+f.name); + if (verbose > 0 && (nread % 1000) == 0) + Utils.log.info("Have read "+nread+" files..."); + } + + void run(JarInputStream in, OutputStream out) throws IOException { + // First thing we do is get the manifest, as JIS does + // not provide the Manifest as an entry. + if (in.getManifest() != null) { + ByteArrayOutputStream tmp = new ByteArrayOutputStream(); + in.getManifest().write(tmp); + InputStream tmpIn = new ByteArrayInputStream(tmp.toByteArray()); + pkg.addFile(readFile(JarFile.MANIFEST_NAME, tmpIn)); + } + for (JarEntry je; (je = in.getNextJarEntry()) != null; ) { + InFile inFile = new InFile(je); + + String name = inFile.name; + Package.File bits = readFile(name, in); + Package.File file = null; + // (5078608) : discount the resource files in META-INF + // from segment computation. + long inflen = (inFile.isMetaInfFile()) + ? 0L + : inFile.getInputLength(); + + if ((segmentSize += inflen) > segmentLimit) { + segmentSize -= inflen; + int nextCount = -1; // don't know; it's a stream + flushPartial(out, nextCount); + } + if (verbose > 1) { + Utils.log.fine("Reading " + name); + } + + assert(je.isDirectory() == name.endsWith("/")); + + if (inFile.mustProcess()) { + file = readClass(name, bits.getInputStream()); + } + if (file == null) { + file = bits; + pkg.addFile(file); + } + inFile.copyTo(file); + noteRead(inFile); + } + flushAll(out); + } + + void run(JarFile in, OutputStream out) throws IOException { + List inFiles = scanJar(in); + + if (verbose > 0) + Utils.log.info("Reading " + inFiles.size() + " files..."); + + int numDone = 0; + for (InFile inFile : inFiles) { + String name = inFile.name; + // (5078608) : discount the resource files completely from segmenting + long inflen = (inFile.isMetaInfFile()) + ? 0L + : inFile.getInputLength() ; + if ((segmentSize += inflen) > segmentLimit) { + segmentSize -= inflen; + // Estimate number of remaining segments: + float filesDone = numDone+1; + float segsDone = segmentCount+1; + float filesToDo = inFiles.size() - filesDone; + float segsToDo = filesToDo * (segsDone/filesDone); + if (verbose > 1) + Utils.log.fine("Estimated segments to do: "+segsToDo); + flushPartial(out, (int) Math.ceil(segsToDo)); + } + InputStream strm = inFile.getInputStream(); + if (verbose > 1) + Utils.log.fine("Reading " + name); + Package.File file = null; + if (inFile.mustProcess()) { + file = readClass(name, strm); + if (file == null) { + strm.close(); + strm = inFile.getInputStream(); + } + } + if (file == null) { + file = readFile(name, strm); + pkg.addFile(file); + } + inFile.copyTo(file); + strm.close(); // tidy up + noteRead(inFile); + numDone += 1; + } + flushAll(out); + } + + Package.File readClass(String fname, InputStream in) throws IOException { + Package.Class cls = pkg.new Class(fname); + in = new BufferedInputStream(in); + ClassReader reader = new ClassReader(cls, in); + reader.setAttrDefs(attrDefs); + reader.setAttrCommands(attrCommands); + reader.unknownAttrCommand = unknownAttrCommand; + try { + reader.read(); + } catch (IOException ioe) { + String message = "Passing class file uncompressed due to"; + if (ioe instanceof Attribute.FormatException) { + Attribute.FormatException ee = (Attribute.FormatException) ioe; + // He passed up the category to us in layout. + if (ee.layout.equals(Pack200.Packer.PASS)) { + Utils.log.info(ee.toString()); + Utils.log.warning(message + " unrecognized attribute: " + + fname); + return null; + } + } else if (ioe instanceof ClassReader.ClassFormatException) { + ClassReader.ClassFormatException ce = (ClassReader.ClassFormatException) ioe; + if (classFormatCommand.equals(Pack200.Packer.PASS)) { + Utils.log.info(ce.toString()); + Utils.log.warning(message + " unknown class format: " + + fname); + return null; + } + } + // Otherwise, it must be an error. + throw ioe; + } + pkg.addClass(cls); + return cls.file; + } + + // Read raw data. + Package.File readFile(String fname, InputStream in) throws IOException { + + Package.File file = pkg.new File(fname); + file.readFrom(in); + if (file.isDirectory() && file.getFileLength() != 0) + throw new IllegalArgumentException("Non-empty directory: "+file.getFileName()); + return file; + } + + void flushPartial(OutputStream out, int nextCount) throws IOException { + if (pkg.files.isEmpty() && pkg.classes.isEmpty()) { + return; // do not flush an empty segment + } + flushPackage(out, Math.max(1, nextCount)); + props.setInteger(Pack200.Packer.PROGRESS, 25); + // In case there will be another segment: + makeNextPackage(); + segmentCount += 1; + segmentTotalSize += segmentSize; + segmentSize = 0; + } + + void flushAll(OutputStream out) throws IOException { + props.setInteger(Pack200.Packer.PROGRESS, 50); + flushPackage(out, 0); + out.flush(); + props.setInteger(Pack200.Packer.PROGRESS, 100); + segmentCount += 1; + segmentTotalSize += segmentSize; + segmentSize = 0; + if (verbose > 0 && segmentCount > 1) { + Utils.log.info("Transmitted " + +segmentTotalSize+" input bytes in " + +segmentCount+" segments totaling " + +totalOutputSize+" bytes"); + } + } + + + /** Write all information in the current package segment + * to the output stream. + */ + void flushPackage(OutputStream out, int nextCount) throws IOException { + int nfiles = pkg.files.size(); + if (!keepFileOrder) { + // Keeping the order of classes costs about 1% + // Keeping the order of all files costs something more. + if (verbose > 1) Utils.log.fine("Reordering files."); + boolean stripDirectories = true; + pkg.reorderFiles(keepClassOrder, stripDirectories); + } else { + // Package builder must have created a stub for each class. + assert(pkg.files.containsAll(pkg.getClassStubs())); + // Order of stubs in file list must agree with classes. + List res = pkg.files; + assert((res = new ArrayList<>(pkg.files)) + .retainAll(pkg.getClassStubs()) || true); + assert(res.equals(pkg.getClassStubs())); + } + pkg.trimStubs(); + + // Do some stripping, maybe. + if (props.getBoolean(Utils.COM_PREFIX+"strip.debug")) pkg.stripAttributeKind("Debug"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.compile")) pkg.stripAttributeKind("Compile"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.constants")) pkg.stripAttributeKind("Constant"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.exceptions")) pkg.stripAttributeKind("Exceptions"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.innerclasses")) pkg.stripAttributeKind("InnerClasses"); + + PackageWriter pw = new PackageWriter(pkg, out); + pw.archiveNextCount = nextCount; + pw.write(); + out.flush(); + if (verbose > 0) { + long outSize = pw.archiveSize0+pw.archiveSize1; + totalOutputSize += outSize; + long inSize = segmentSize; + Utils.log.info("Transmitted " + +nfiles+" files of " + +inSize+" input bytes in a segment of " + +outSize+" bytes"); + } + } + + List scanJar(JarFile jf) throws IOException { + // Collect jar entries, preserving order. + List inFiles = new ArrayList<>(); + try { + for (JarEntry je : Collections.list(jf.entries())) { + InFile inFile = new InFile(jf, je); + assert(je.isDirectory() == inFile.name.endsWith("/")); + inFiles.add(inFile); + } + } catch (IllegalStateException ise) { + throw new IOException(ise.getLocalizedMessage(), ise); + } + return inFiles; + } + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/PopulationCoding.java b/src/main/java/net/minecraftforge/gradle/util/pack200/PopulationCoding.java new file mode 100644 index 000000000..1a58b7522 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/PopulationCoding.java @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import static net.minecraftforge.gradle.util.pack200.Constants.*; + +/** + * Population-based coding. + * See the section "Encodings of Uncorrelated Values" in the Pack200 spec. + * @author John Rose + */ +// This tactic alone reduces the final zipped rt.jar by about a percent. +class PopulationCoding implements CodingMethod { + Histogram vHist; // histogram of all values + int[] fValues; // list of favored values + int fVlen; // inclusive max index + long[] symtab; // int map of favored value -> token [1..#fValues] + + CodingMethod favoredCoding; + CodingMethod tokenCoding; + CodingMethod unfavoredCoding; + + int L = -1; //preferred L value for tokenCoding + + public void setFavoredValues(int[] fValues, int fVlen) { + // Note: {f} is allFavoredValues[1..fvlen], not [0..fvlen-1]. + // This is because zero is an exceptional favored value index. + assert(fValues[0] == 0); // must be empty + assert(this.fValues == null); // do not do this twice + this.fValues = fValues; + this.fVlen = fVlen; + if (L >= 0) { + setL(L); // reassert + } + } + public void setFavoredValues(int[] fValues) { + int lfVlen = fValues.length-1; + setFavoredValues(fValues, lfVlen); + } + public void setHistogram(Histogram vHist) { + this.vHist = vHist; + } + public void setL(int L) { + this.L = L; + if (L >= 0 && fValues != null && tokenCoding == null) { + tokenCoding = fitTokenCoding(fVlen, L); + assert(tokenCoding != null); + } + } + + public static Coding fitTokenCoding(int fVlen, int L) { + // Find the smallest B s.t. (B,H,0) covers fVlen. + if (fVlen < 256) + // H/L do not matter when B==1 + return BandStructure.BYTE1; + Coding longest = BandStructure.UNSIGNED5.setL(L); + if (!longest.canRepresentUnsigned(fVlen)) + return null; // failure; L is too sharp and fVlen too large + Coding tc = longest; + for (Coding shorter = longest; ; ) { + shorter = shorter.setB(shorter.B()-1); + if (shorter.umax() < fVlen) + break; + tc = shorter; // shorten it by reducing B + } + return tc; + } + + public void setFavoredCoding(CodingMethod favoredCoding) { + this.favoredCoding = favoredCoding; + } + public void setTokenCoding(CodingMethod tokenCoding) { + this.tokenCoding = tokenCoding; + this.L = -1; + if (tokenCoding instanceof Coding && fValues != null) { + Coding tc = (Coding) tokenCoding; + if (tc == fitTokenCoding(fVlen, tc.L())) + this.L = tc.L(); + // Otherwise, it's a non-default coding. + } + } + public void setUnfavoredCoding(CodingMethod unfavoredCoding) { + this.unfavoredCoding = unfavoredCoding; + } + + public int favoredValueMaxLength() { + if (L == 0) + return Integer.MAX_VALUE; + else + return BandStructure.UNSIGNED5.setL(L).umax(); + } + + public void resortFavoredValues() { + Coding tc = (Coding) tokenCoding; + // Make a local copy before reordering. + fValues = BandStructure.realloc(fValues, 1+fVlen); + // Resort favoredValues within each byte-size cadre. + int fillp = 1; // skip initial zero + for (int n = 1; n <= tc.B(); n++) { + int nmax = tc.byteMax(n); + if (nmax > fVlen) + nmax = fVlen; + if (nmax < tc.byteMin(n)) + break; + int low = fillp; + int high = nmax+1; + if (high == low) continue; + assert(high > low) + : high+"!>"+low; + assert(tc.getLength(low) == n) + : n+" != len("+(low)+") == "+ + tc.getLength(low); + assert(tc.getLength(high-1) == n) + : n+" != len("+(high-1)+") == "+ + tc.getLength(high-1); + int midTarget = low + (high-low)/2; + int mid = low; + // Divide the values into cadres, and sort within each. + int prevCount = -1; + int prevLimit = low; + for (int i = low; i < high; i++) { + int val = fValues[i]; + int count = vHist.getFrequency(val); + if (prevCount != count) { + if (n == 1) { + // For the single-byte encoding, keep strict order + // among frequency groups. + Arrays.sort(fValues, prevLimit, i); + } else if (Math.abs(mid - midTarget) > + Math.abs(i - midTarget)) { + // Find a single inflection point + // close to the middle of the byte-size cadre. + mid = i; + } + prevCount = count; + prevLimit = i; + } + } + if (n == 1) { + Arrays.sort(fValues, prevLimit, high); + } else { + // Sort up to the midpoint, if any. + Arrays.sort(fValues, low, mid); + Arrays.sort(fValues, mid, high); + } + assert(tc.getLength(low) == tc.getLength(mid)); + assert(tc.getLength(low) == tc.getLength(high-1)); + fillp = nmax+1; + } + assert(fillp == fValues.length); + + // Reset symtab. + symtab = null; + } + + public int getToken(int value) { + if (symtab == null) + symtab = makeSymtab(); + int pos = Arrays.binarySearch(symtab, (long)value << 32); + if (pos < 0) pos = -pos-1; + if (pos < symtab.length && value == (int)(symtab[pos] >>> 32)) + return (int)symtab[pos]; + else + return 0; + } + + public int[][] encodeValues(int[] values, int start, int end) { + // Compute token sequence. + int[] tokens = new int[end-start]; + int nuv = 0; + for (int i = 0; i < tokens.length; i++) { + int val = values[start+i]; + int tok = getToken(val); + if (tok != 0) + tokens[i] = tok; + else + nuv += 1; + } + // Compute unfavored value sequence. + int[] unfavoredValues = new int[nuv]; + nuv = 0; // reset + for (int i = 0; i < tokens.length; i++) { + if (tokens[i] != 0) continue; // already covered + int val = values[start+i]; + unfavoredValues[nuv++] = val; + } + assert(nuv == unfavoredValues.length); + return new int[][]{ tokens, unfavoredValues }; + } + + private long[] makeSymtab() { + long[] lsymtab = new long[fVlen]; + for (int token = 1; token <= fVlen; token++) { + lsymtab[token-1] = ((long)fValues[token] << 32) | token; + } + // Index by value: + Arrays.sort(lsymtab); + return lsymtab; + } + + private Coding getTailCoding(CodingMethod c) { + while (c instanceof AdaptiveCoding) + c = ((AdaptiveCoding)c).tailCoding; + return (Coding) c; + } + + // CodingMethod methods. + public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException { + int[][] vals = encodeValues(a, start, end); + writeSequencesTo(out, vals[0], vals[1]); + } + void writeSequencesTo(OutputStream out, int[] tokens, int[] uValues) throws IOException { + favoredCoding.writeArrayTo(out, fValues, 1, 1+fVlen); + getTailCoding(favoredCoding).writeTo(out, computeSentinelValue()); + tokenCoding.writeArrayTo(out, tokens, 0, tokens.length); + if (uValues.length > 0) + unfavoredCoding.writeArrayTo(out, uValues, 0, uValues.length); + } + + int computeSentinelValue() { + Coding fc = getTailCoding(favoredCoding); + if (fc.isDelta()) { + // repeat the last favored value, using delta=0 + return 0; + } else { + // else repeat the shorter of the min or last value + int min = fValues[1]; + int last = min; + // (remember that fVlen is an inclusive limit in fValues) + for (int i = 2; i <= fVlen; i++) { + last = fValues[i]; + min = moreCentral(min, last); + } + int endVal; + if (fc.getLength(min) <= fc.getLength(last)) + return min; + else + return last; + } + } + + public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException { + // Parameters are fCode, L, uCode. + setFavoredValues(readFavoredValuesFrom(in, end-start)); + // Read the tokens. Read them into the final array, for the moment. + tokenCoding.readArrayFrom(in, a, start, end); + // Decode the favored tokens. + int headp = 0, tailp = -1; + int uVlen = 0; + for (int i = start; i < end; i++) { + int tok = a[i]; + if (tok == 0) { + // Make a linked list, and decode in a second pass. + if (tailp < 0) { + headp = i; + } else { + a[tailp] = i; + } + tailp = i; + uVlen += 1; + } else { + a[i] = fValues[tok]; + } + } + // Walk the linked list of "zero" locations, decoding unfavored vals. + int[] uValues = new int[uVlen]; + if (uVlen > 0) + unfavoredCoding.readArrayFrom(in, uValues, 0, uVlen); + for (int i = 0; i < uVlen; i++) { + int nextp = a[headp]; + a[headp] = uValues[i]; + headp = nextp; + } + } + + int[] readFavoredValuesFrom(InputStream in, int maxForDebug) throws IOException { + int[] lfValues = new int[1000]; // realloc as needed + // The set uniqueValuesForDebug records all favored values. + // As each new value is added, we assert that the value + // was not already in the set. + Set uniqueValuesForDebug = null; + assert((uniqueValuesForDebug = new HashSet<>()) != null); + int fillp = 1; + maxForDebug += fillp; + int min = Integer.MIN_VALUE; // farthest from the center + //int min2 = Integer.MIN_VALUE; // emulate buggy 150.7 spec. + int last = 0; + CodingMethod fcm = favoredCoding; + while (fcm instanceof AdaptiveCoding) { + AdaptiveCoding ac = (AdaptiveCoding) fcm; + int len = ac.headLength; + while (fillp + len > lfValues.length) { + lfValues = BandStructure.realloc(lfValues); + } + int newFillp = fillp + len; + ac.headCoding.readArrayFrom(in, lfValues, fillp, newFillp); + while (fillp < newFillp) { + int val = lfValues[fillp++]; + assert(uniqueValuesForDebug.add(val)); + assert(fillp <= maxForDebug); + last = val; + min = moreCentral(min, val); + //min2 = moreCentral2(min2, val, min); + } + fcm = ac.tailCoding; + } + Coding fc = (Coding) fcm; + if (fc.isDelta()) { + for (long state = 0;;) { + // Read a new value: + state += fc.readFrom(in); + int val; + if (fc.isSubrange()) + val = fc.reduceToUnsignedRange(state); + else + val = (int)state; + state = val; + if (fillp > 1 && (val == last || val == min)) //|| val == min2 + break; + if (fillp == lfValues.length) + lfValues = BandStructure.realloc(lfValues); + lfValues[fillp++] = val; + assert(uniqueValuesForDebug.add(val)); + assert(fillp <= maxForDebug); + last = val; + min = moreCentral(min, val); + //min2 = moreCentral(min2, val); + } + } else { + for (;;) { + int val = fc.readFrom(in); + if (fillp > 1 && (val == last || val == min)) //|| val == min2 + break; + if (fillp == lfValues.length) + lfValues = BandStructure.realloc(lfValues); + lfValues[fillp++] = val; + assert(uniqueValuesForDebug.add(val)); + assert(fillp <= maxForDebug); + last = val; + min = moreCentral(min, val); + //min2 = moreCentral2(min2, val, min); + } + } + return BandStructure.realloc(lfValues, fillp); + } + + private static int moreCentral(int x, int y) { + int kx = (x >> 31) ^ (x << 1); + int ky = (y >> 31) ^ (y << 1); + // bias kx/ky to get an unsigned comparison: + kx -= Integer.MIN_VALUE; + ky -= Integer.MIN_VALUE; + int xy = (kx < ky? x: y); + // assert that this ALU-ish version is the same: + assert(xy == moreCentralSlow(x, y)); + return xy; + } +// private static int moreCentral2(int x, int y, int min) { +// // Strict implementation of buggy 150.7 specification. +// // The bug is that the spec. says absolute-value ties are broken +// // in favor of positive numbers, but the suggested implementation +// // (also mentioned in the spec.) breaks ties in favor of negatives. +// if (x + y == 0) return (x > y? x : y); +// return min; +// } + private static int moreCentralSlow(int x, int y) { + int ax = x; + if (ax < 0) ax = -ax; + if (ax < 0) return y; //x is MIN_VALUE + int ay = y; + if (ay < 0) ay = -ay; + if (ay < 0) return x; //y is MIN_VALUE + if (ax < ay) return x; + if (ax > ay) return y; + // At this point the absolute values agree, and the negative wins. + return x < y ? x : y; + } + + static final int[] LValuesCoded + = { -1, 4, 8, 16, 32, 64, 128, 192, 224, 240, 248, 252 }; + + public byte[] getMetaCoding(Coding dflt) { + int K = fVlen; + int LCoded = 0; + if (tokenCoding instanceof Coding) { + Coding tc = (Coding) tokenCoding; + if (tc.B() == 1) { + LCoded = 1; + } else if (L >= 0) { + assert(L == tc.L()); + for (int i = 1; i < LValuesCoded.length; i++) { + if (LValuesCoded[i] == L) { LCoded = i; break; } + } + } + } + CodingMethod tokenDflt = null; + if (LCoded != 0 && tokenCoding == fitTokenCoding(fVlen, L)) { + // A simple L value is enough to recover the tokenCoding. + tokenDflt = tokenCoding; + } + int FDef = (favoredCoding == dflt)?1:0; + int UDef = (unfavoredCoding == dflt || unfavoredCoding == null)?1:0; + int TDef = (tokenCoding == tokenDflt)?1:0; + int TDefL = (TDef == 1) ? LCoded : 0; + assert(TDef == ((TDefL>0)?1:0)); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(10); + bytes.write(_meta_pop + FDef + 2*UDef + 4*TDefL); + try { + if (FDef == 0) bytes.write(favoredCoding.getMetaCoding(dflt)); + if (TDef == 0) bytes.write(tokenCoding.getMetaCoding(dflt)); + if (UDef == 0) bytes.write(unfavoredCoding.getMetaCoding(dflt)); + } catch (IOException ee) { + throw new RuntimeException(ee); + } + return bytes.toByteArray(); + } + public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod res[]) { + int op = bytes[pos++] & 0xFF; + if (op < _meta_pop || op >= _meta_limit) return pos-1; // backup + op -= _meta_pop; + int FDef = op % 2; + int UDef = (op / 2) % 2; + int TDefL = (op / 4); + int TDef = (TDefL > 0)?1:0; + int L = LValuesCoded[TDefL]; + CodingMethod[] FCode = {dflt}, TCode = {null}, UCode = {dflt}; + if (FDef == 0) + pos = BandStructure.parseMetaCoding(bytes, pos, dflt, FCode); + if (TDef == 0) + pos = BandStructure.parseMetaCoding(bytes, pos, dflt, TCode); + if (UDef == 0) + pos = BandStructure.parseMetaCoding(bytes, pos, dflt, UCode); + PopulationCoding pop = new PopulationCoding(); + pop.L = L; // might be -1 + pop.favoredCoding = FCode[0]; + pop.tokenCoding = TCode[0]; // might be null! + pop.unfavoredCoding = UCode[0]; + res[0] = pop; + return pos; + } + + private String keyString(CodingMethod m) { + if (m instanceof Coding) + return ((Coding)m).keyString(); + if (m == null) + return "none"; + return m.toString(); + } + public String toString() { + PropMap p200 = Utils.currentPropMap(); + boolean verbose + = (p200 != null && + p200.getBoolean(Utils.COM_PREFIX+"verbose.pop")); + StringBuilder res = new StringBuilder(100); + res.append("pop(").append("fVlen=").append(fVlen); + if (verbose && fValues != null) { + res.append(" fV=["); + for (int i = 1; i <= fVlen; i++) { + res.append(i==1?"":",").append(fValues[i]); + } + res.append(";").append(computeSentinelValue()); + res.append("]"); + } + res.append(" fc=").append(keyString(favoredCoding)); + res.append(" tc=").append(keyString(tokenCoding)); + res.append(" uc=").append(keyString(unfavoredCoding)); + res.append(")"); + return res.toString(); + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/PropMap.java b/src/main/java/net/minecraftforge/gradle/util/pack200/PropMap.java new file mode 100644 index 000000000..6813e6167 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/PropMap.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * Control block for publishing Pack200 options to the other classes. + */ + +@SuppressWarnings({"removal"}) +final class PropMap implements SortedMap { + private final TreeMap theMap = new TreeMap<>();; + + // Override: + public String put(String key, String value) { + String oldValue = theMap.put(key, value); + return oldValue; + } + + // All this other stuff is private to the current package. + // Outide clients of Pack200 do not need to use it; they can + // get by with generic SortedMap functionality. + private static Map defaultProps; + static { + Properties props = new Properties(); + + // Allow implementation selected via -Dpack.disable.native=true + String propValue = getPropertyValue(Utils.DEBUG_DISABLE_NATIVE, "false"); + props.put(Utils.DEBUG_DISABLE_NATIVE, + String.valueOf(Boolean.parseBoolean(propValue))); + + // Set the DEBUG_VERBOSE from system + int verbose = 0; + try { + verbose = Integer.decode(getPropertyValue(Utils.DEBUG_VERBOSE, "0")); + } catch (NumberFormatException e) { + } + props.put(Utils.DEBUG_VERBOSE, String.valueOf(verbose)); + + // The segment size is unlimited + props.put(Pack200.Packer.SEGMENT_LIMIT, "-1"); + + // Preserve file ordering by default. + props.put(Pack200.Packer.KEEP_FILE_ORDER, Pack200.Packer.TRUE); + + // Preserve all modification times by default. + props.put(Pack200.Packer.MODIFICATION_TIME, Pack200.Packer.KEEP); + + // Preserve deflation hints by default. + props.put(Pack200.Packer.DEFLATE_HINT, Pack200.Packer.KEEP); + + // Pass through files with unrecognized attributes by default. + props.put(Pack200.Packer.UNKNOWN_ATTRIBUTE, Pack200.Packer.PASS); + + // Pass through files with unrecognized format by default, also + // allow system property to be set + props.put(Utils.CLASS_FORMAT_ERROR, + getPropertyValue(Utils.CLASS_FORMAT_ERROR, Pack200.Packer.PASS)); + + // Default effort is 5, midway between 1 and 9. + props.put(Pack200.Packer.EFFORT, "5"); + + for (Map.Entry e : props.entrySet()) { + String key = (String) e.getKey(); + String val = (String) e.getValue(); + if (key.startsWith("attribute.")) { + e.setValue(Attribute.normalizeLayoutString(val)); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + HashMap temp = new HashMap(props); // shrink to fit + defaultProps = temp; + } + + private static String getPropertyValue(String key, String defaultValue) { + PrivilegedAction pa = () -> System.getProperty(key); + String s = AccessController.doPrivileged(pa); + return s != null ? s : defaultValue; + } + + PropMap() { + theMap.putAll(defaultProps); + } + + // Return a view of this map which includes only properties + // that begin with the given prefix. This is easy because + // the map is sorted, and has a subMap accessor. + SortedMap prefixMap(String prefix) { + int len = prefix.length(); + if (len == 0) + return this; + char nextch = (char)(prefix.charAt(len-1) + 1); + String limit = prefix.substring(0, len-1)+nextch; + //System.out.println(prefix+" => "+subMap(prefix, limit)); + return subMap(prefix, limit); + } + + String getProperty(String s) { + return get(s); + } + String getProperty(String s, String defaultVal) { + String val = getProperty(s); + if (val == null) + return defaultVal; + return val; + } + String setProperty(String s, String val) { + return put(s, val); + } + + // Get sequence of props for "prefix", and "prefix.*". + List getProperties(String prefix) { + Collection values = prefixMap(prefix).values(); + List res = new ArrayList<>(values.size()); + res.addAll(values); + while (res.remove(null)); + return res; + } + + private boolean toBoolean(String val) { + return Boolean.valueOf(val).booleanValue(); + } + boolean getBoolean(String s) { + return toBoolean(getProperty(s)); + } + boolean setBoolean(String s, boolean val) { + return toBoolean(setProperty(s, String.valueOf(val))); + } + int toInteger(String val) { + return toInteger(val, 0); + } + int toInteger(String val, int def) { + if (val == null) return def; + if (Pack200.Packer.TRUE.equals(val)) return 1; + if (Pack200.Packer.FALSE.equals(val)) return 0; + return Integer.parseInt(val); + } + int getInteger(String s, int def) { + return toInteger(getProperty(s), def); + } + int getInteger(String s) { + return toInteger(getProperty(s)); + } + int setInteger(String s, int val) { + return toInteger(setProperty(s, String.valueOf(val))); + } + + long toLong(String val) { + try { + return val == null ? 0 : Long.parseLong(val); + } catch (java.lang.NumberFormatException nfe) { + throw new IllegalArgumentException("Invalid value"); + } + } + long getLong(String s) { + return toLong(getProperty(s)); + } + long setLong(String s, long val) { + return toLong(setProperty(s, String.valueOf(val))); + } + + int getTime(String s) { + String sval = getProperty(s, "0"); + if (Utils.NOW.equals(sval)) { + return (int)((System.currentTimeMillis()+500)/1000); + } + long lval = toLong(sval); + final long recentSecondCount = 1000000000; + + if (lval < recentSecondCount*10 && !"0".equals(sval)) + Utils.log.warning("Supplied modtime appears to be seconds rather than milliseconds: "+sval); + + return (int)((lval+500)/1000); + } + + void list(PrintStream out) { + PrintWriter outw = new PrintWriter(out); + list(outw); + outw.flush(); + } + void list(PrintWriter out) { + out.println("#"+Utils.PACK_ZIP_ARCHIVE_MARKER_COMMENT+"["); + Set> defaults = defaultProps.entrySet(); + for (Map.Entry e : theMap.entrySet()) { + if (defaults.contains(e)) continue; + out.println(" " + e.getKey() + " = " + e.getValue()); + } + out.println("#]"); + } + + @Override + public int size() { + return theMap.size(); + } + + @Override + public boolean isEmpty() { + return theMap.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return theMap.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return theMap.containsValue(value); + } + + @Override + public String get(Object key) { + return theMap.get(key); + } + + @Override + public String remove(Object key) { + return theMap.remove(key); + } + + @Override + public void putAll(Map m) { + theMap.putAll(m); + } + + @Override + public void clear() { + theMap.clear(); + } + + @Override + public Set keySet() { + return theMap.keySet(); + } + + @Override + public Collection values() { + return theMap.values(); + } + + @Override + public Set> entrySet() { + return theMap.entrySet(); + } + + @Override + public Comparator comparator() { + return theMap.comparator(); + } + + @Override + public SortedMap subMap(String fromKey, String toKey) { + return theMap.subMap(fromKey, toKey); + } + + @Override + public SortedMap headMap(String toKey) { + return theMap.headMap(toKey); + } + + @Override + public SortedMap tailMap(String fromKey) { + return theMap.tailMap(fromKey); + } + + @Override + public String firstKey() { + return theMap.firstKey(); + } + + @Override + public String lastKey() { + return theMap.lastKey(); + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/TLGlobals.java b/src/main/java/net/minecraftforge/gradle/util/pack200/TLGlobals.java new file mode 100644 index 000000000..8af831f38 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/TLGlobals.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.minecraftforge.gradle.util.pack200; + +import net.minecraftforge.gradle.util.pack200.ConstantPool.ClassEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.DescriptorEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.LiteralEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.MemberEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.MethodHandleEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.MethodTypeEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.InvokeDynamicEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.BootstrapMethodEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.SignatureEntry; +import net.minecraftforge.gradle.util.pack200.ConstantPool.Utf8Entry; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; + +/* + * @author ksrini + */ + +/* + * This class provides a container to hold the global variables, for packer + * and unpacker instances. This is typically stashed away in a ThreadLocal, + * and the storage is destroyed upon completion. Therefore any local + * references to these members must be eliminated appropriately to prevent a + * memory leak. + */ +class TLGlobals { + // Global environment + final PropMap props; + + // Needed by ConstantPool.java + private final Map utf8Entries; + private final Map classEntries; + private final Map literalEntries; + private final Map signatureEntries; + private final Map descriptorEntries; + private final Map memberEntries; + private final Map methodHandleEntries; + private final Map methodTypeEntries; + private final Map invokeDynamicEntries; + private final Map bootstrapMethodEntries; + + TLGlobals() { + utf8Entries = new HashMap<>(); + classEntries = new HashMap<>(); + literalEntries = new HashMap<>(); + signatureEntries = new HashMap<>(); + descriptorEntries = new HashMap<>(); + memberEntries = new HashMap<>(); + methodHandleEntries = new HashMap<>(); + methodTypeEntries = new HashMap<>(); + invokeDynamicEntries = new HashMap<>(); + bootstrapMethodEntries = new HashMap<>(); + props = new PropMap(); + } + + SortedMap getPropMap() { + return props; + } + + Map getUtf8Entries() { + return utf8Entries; + } + + Map getClassEntries() { + return classEntries; + } + + Map getLiteralEntries() { + return literalEntries; + } + + Map getDescriptorEntries() { + return descriptorEntries; + } + + Map getSignatureEntries() { + return signatureEntries; + } + + Map getMemberEntries() { + return memberEntries; + } + + Map getMethodHandleEntries() { + return methodHandleEntries; + } + + Map getMethodTypeEntries() { + return methodTypeEntries; + } + + Map getInvokeDynamicEntries() { + return invokeDynamicEntries; + } + + Map getBootstrapMethodEntries() { + return bootstrapMethodEntries; + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/UnpackerImpl.java b/src/main/java/net/minecraftforge/gradle/util/pack200/UnpackerImpl.java new file mode 100644 index 000000000..67d444cf2 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/UnpackerImpl.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.HashSet; +import java.util.Set; +import java.util.SortedMap; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.zip.CRC32; +import java.util.zip.CheckedOutputStream; +import java.util.zip.ZipEntry; + +/* + * Implementation of the Pack provider. + * + * @author John Rose + * @author Kumar Srinivasan + */ + + +@SuppressWarnings({"removal"}) +public class UnpackerImpl extends TLGlobals implements Pack200.Unpacker { + + public UnpackerImpl() {} + + + + /** + * Get the set of options for the pack and unpack engines. + * @return A sorted association of option key strings to option values. + */ + public SortedMap properties() { + return props; + } + + // Back-pointer to NativeUnpacker, when active. + Object _nunp; + + + public String toString() { + return Utils.getVersionString(); + } + + //Driver routines + + // The unpack worker... + /** + * Takes a packed-stream InputStream, and writes to a JarOutputStream. Internally + * the entire buffer must be read, it may be more efficient to read the packed-stream + * to a file and pass the File object, in the alternate method described below. + *

+ * Closes its input but not its output. (The output can accumulate more elements.) + * @param in an InputStream. + * @param out a JarOutputStream. + * @exception IOException if an error is encountered. + */ + public synchronized void unpack(InputStream in, JarOutputStream out) throws IOException { + if (in == null) { + throw new NullPointerException("null input"); + } + if (out == null) { + throw new NullPointerException("null output"); + } + assert(Utils.currentInstance.get() == null); + + try { + Utils.currentInstance.set(this); + final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); + BufferedInputStream in0 = new BufferedInputStream(in); + if (Utils.isJarMagic(Utils.readMagic(in0))) { + if (verbose > 0) + Utils.log.info("Copying unpacked JAR file..."); + Utils.copyJarFile(new JarInputStream(in0), out); + } else if (props.getBoolean(Utils.DEBUG_DISABLE_NATIVE)) { + (new DoUnpack()).run(in0, out); + in0.close(); + Utils.markJarFile(out); + } else { + try { + (new NativeUnpack(this)).run(in0, out); + } catch (UnsatisfiedLinkError | NoClassDefFoundError ex) { + // failover to java implementation + (new DoUnpack()).run(in0, out); + } + in0.close(); + Utils.markJarFile(out); + } + } finally { + _nunp = null; + Utils.currentInstance.set(null); + } + } + + /** + * Takes an input File containing the pack file, and generates a JarOutputStream. + *

+ * Does not close its output. (The output can accumulate more elements.) + * @param in a File. + * @param out a JarOutputStream. + * @exception IOException if an error is encountered. + */ + public synchronized void unpack(File in, JarOutputStream out) throws IOException { + if (in == null) { + throw new NullPointerException("null input"); + } + if (out == null) { + throw new NullPointerException("null output"); + } + // Use the stream-based implementation. + // %%% Reconsider if native unpacker learns to memory-map the file. + try (FileInputStream instr = new FileInputStream(in)) { + unpack(instr, out); + } + if (props.getBoolean(Utils.UNPACK_REMOVE_PACKFILE)) { + in.delete(); + } + } + + private class DoUnpack { + final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); + + { + props.setInteger(Pack200.Unpacker.PROGRESS, 0); + } + + // Here's where the bits are read from disk: + final Package pkg = new Package(); + + final boolean keepModtime + = Pack200.Packer.KEEP.equals( + props.getProperty(Utils.UNPACK_MODIFICATION_TIME, Pack200.Packer.KEEP)); + final boolean keepDeflateHint + = Pack200.Packer.KEEP.equals( + props.getProperty(Pack200.Unpacker.DEFLATE_HINT, Pack200.Packer.KEEP)); + final int modtime; + final boolean deflateHint; + { + if (!keepModtime) { + modtime = props.getTime(Utils.UNPACK_MODIFICATION_TIME); + } else { + modtime = pkg.default_modtime; + } + + deflateHint = (keepDeflateHint) ? false : + props.getBoolean(Pack200.Unpacker.DEFLATE_HINT); + } + + // Checksum apparatus. + final CRC32 crc = new CRC32(); + final ByteArrayOutputStream bufOut = new ByteArrayOutputStream(); + final OutputStream crcOut = new CheckedOutputStream(bufOut, crc); + + public void run(BufferedInputStream in, JarOutputStream out) throws IOException { + if (verbose > 0) { + props.list(System.out); + } + for (int seg = 1; ; seg++) { + unpackSegment(in, out); + + // Try to get another segment. + if (!Utils.isPackMagic(Utils.readMagic(in))) break; + if (verbose > 0) + Utils.log.info("Finished segment #"+seg); + } + } + + private void unpackSegment(InputStream in, JarOutputStream out) throws IOException { + props.setProperty(Pack200.Unpacker.PROGRESS,"0"); + // Process the output directory or jar output. + new PackageReader(pkg, in).read(); + + if (props.getBoolean("unpack.strip.debug")) pkg.stripAttributeKind("Debug"); + if (props.getBoolean("unpack.strip.compile")) pkg.stripAttributeKind("Compile"); + props.setProperty(Pack200.Unpacker.PROGRESS,"50"); + pkg.ensureAllClassFiles(); + // Now write out the files. + Set classesToWrite = new HashSet<>(pkg.getClasses()); + for (Package.File file : pkg.getFiles()) { + String name = file.nameString; + JarEntry je = new JarEntry(Utils.getJarEntryName(name)); + boolean deflate; + + deflate = (keepDeflateHint) + ? (((file.options & Constants.FO_DEFLATE_HINT) != 0) || + ((pkg.default_options & Constants.AO_DEFLATE_HINT) != 0)) + : deflateHint; + + boolean needCRC = !deflate; // STORE mode requires CRC + + if (needCRC) crc.reset(); + bufOut.reset(); + if (file.isClassStub()) { + Package.Class cls = file.getStubClass(); + assert(cls != null); + new ClassWriter(cls, needCRC ? crcOut : bufOut).write(); + classesToWrite.remove(cls); // for an error check + } else { + // collect data & maybe CRC + file.writeTo(needCRC ? crcOut : bufOut); + } + je.setMethod(deflate ? JarEntry.DEFLATED : JarEntry.STORED); + if (needCRC) { + if (verbose > 0) + Utils.log.info("stored size="+bufOut.size()+" and crc="+crc.getValue()); + + je.setMethod(JarEntry.STORED); + je.setSize(bufOut.size()); + je.setCrc(crc.getValue()); + } + if (keepModtime) { + LocalDateTime ldt = LocalDateTime + .ofEpochSecond(file.modtime, 0, ZoneOffset.UTC); + je.setTimeLocal(ldt); + } else { + je.setTime((long)modtime * 1000); + } + out.putNextEntry(je); + bufOut.writeTo(out); + out.closeEntry(); + if (verbose > 0) + Utils.log.info("Writing "+Utils.zeString((ZipEntry)je)); + } + assert(classesToWrite.isEmpty()); + props.setProperty(Pack200.Unpacker.PROGRESS,"100"); + pkg.reset(); // reset for the next segment, if any + } + } +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/Utils.java b/src/main/java/net/minecraftforge/gradle/util/pack200/Utils.java new file mode 100644 index 000000000..6f07f7856 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/Utils.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.minecraftforge.gradle.util.pack200; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.Date; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; +// import sun.util.logging.PlatformLogger; + +class Utils { + static final String COM_PREFIX = "net.minecraftforge.gradle.util.pack200."; + static final String METAINF = "META-INF"; + + /* + * Outputs various diagnostic support information. + * If >0, print summary comments (e.g., constant pool info). + * If >1, print unit comments (e.g., processing of classes). + * If >2, print many comments (e.g., processing of members). + * If >3, print tons of comments (e.g., processing of references). + * (installer only) + */ + static final String DEBUG_VERBOSE = COM_PREFIX+"verbose"; + + /* + * Disables use of native code, prefers the Java-coded implementation. + * (installer only) + */ + static final String DEBUG_DISABLE_NATIVE = COM_PREFIX+"disable.native"; + + /* + * Property indicating that the unpacker should + * ignore the transmitted PACK_MODIFICATION_TIME, + * replacing it by the given value. The value can + * be a numeric string, representing the number of + * mSecs since the epoch (UTC), or the special string + * {@link #NOW}, meaning the current time (UTC). + * The default value is the special string {@link #KEEP}, + * which asks the unpacker to preserve all transmitted + * modification time information. + * (installer only) + */ + static final String UNPACK_MODIFICATION_TIME = COM_PREFIX+"unpack.modification.time"; + + /* + * Property indicating that the unpacker strip the + * Debug Attributes, if they are present, in the pack stream. + * The default value is false. + * (installer only) + */ + static final String UNPACK_STRIP_DEBUG = COM_PREFIX+"unpack.strip.debug"; + + /* + * Remove the input file after unpacking. + * (installer only) + */ + static final String UNPACK_REMOVE_PACKFILE = COM_PREFIX+"unpack.remove.packfile"; + + /* + * A possible value for MODIFICATION_TIME + */ + static final String NOW = "now"; + // Other debug options: + // com...debug.bands=false add band IDs to pack file, to verify sync + // com...dump.bands=false dump band contents to local disk + // com...no.vary.codings=false turn off coding variation heuristics + // com...no.big.strings=false turn off "big string" feature + + /* + * If this property is set to {@link #TRUE}, the packer will preserve + * the ordering of class files of the original jar in the output archive. + * The ordering is preserved only for class-files; resource files + * may be reordered. + *

+ * If the packer is allowed to reorder class files, it can marginally + * decrease the transmitted size of the archive. + */ + static final String PACK_KEEP_CLASS_ORDER = COM_PREFIX+"keep.class.order"; + /* + * This string PACK200 is given as a zip comment on all JAR files + * produced by this utility. + */ + static final String PACK_ZIP_ARCHIVE_MARKER_COMMENT = "PACK200"; + + /* + * behaviour when we hit a class format error, but not necessarily + * an unknown attribute, by default it is allowed to PASS. + */ + static final String CLASS_FORMAT_ERROR = COM_PREFIX+"class.format.error"; + + // Keep a TLS point to the global data and environment. + // This makes it simpler to supply environmental options + // to the engine code, especially the native code. + static final ThreadLocal currentInstance = new ThreadLocal<>(); + + // convenience method to access the TL globals + static TLGlobals getTLGlobals() { + return currentInstance.get(); + } + + static PropMap currentPropMap() { + Object obj = currentInstance.get(); + if (obj instanceof PackerImpl) + return ((PackerImpl)obj).props; + if (obj instanceof UnpackerImpl) + return ((UnpackerImpl)obj).props; + return null; + } + + static final boolean nolog + = Boolean.getBoolean(COM_PREFIX+"nolog"); + + static final boolean SORT_MEMBERS_DESCR_MAJOR + = Boolean.getBoolean(COM_PREFIX+"sort.members.descr.major"); + + static final boolean SORT_HANDLES_KIND_MAJOR + = Boolean.getBoolean(COM_PREFIX+"sort.handles.kind.major"); + + static final boolean SORT_INDY_BSS_MAJOR + = Boolean.getBoolean(COM_PREFIX+"sort.indy.bss.major"); + + static final boolean SORT_BSS_BSM_MAJOR + = Boolean.getBoolean(COM_PREFIX+"sort.bss.bsm.major"); + + static class Pack200Logger { + private final String name; + // private PlatformLogger log; + Pack200Logger(String name) { + this.name = name; + } + + // private synchronized PlatformLogger getLogger() { + // if (log == null) { + // log = PlatformLogger.getLogger(name); + // } + // return log; + // } + + public void warning(String msg, Object param) { + // getLogger().warning(msg, param); + } + + public void warning(String msg) { + warning(msg, null); + } + + public void info(String msg) { + int verbose = currentPropMap().getInteger(DEBUG_VERBOSE); + if (verbose > 0) { + if (nolog) { + System.out.println(msg); + } else { + // getLogger().info(msg); + } + } + } + + public void fine(String msg) { + int verbose = currentPropMap().getInteger(DEBUG_VERBOSE); + if (verbose > 0) { + System.out.println(msg); + } + } + } + + static final Pack200Logger log + = new Pack200Logger("java.util.jar.Pack200"); + + // Returns the Max Version String of this implementation + static String getVersionString() { + return "Pack200, Vendor: " + + System.getProperty("java.vendor") + + ", Version: " + Constants.MAX_PACKAGE_VERSION; + } + + static void markJarFile(JarOutputStream out) throws IOException { + out.setComment(PACK_ZIP_ARCHIVE_MARKER_COMMENT); + } + + // -0 mode helper + static void copyJarFile(JarInputStream in, JarOutputStream out) throws IOException { + if (in.getManifest() != null) { + ZipEntry me = new ZipEntry(JarFile.MANIFEST_NAME); + out.putNextEntry(me); + in.getManifest().write(out); + out.closeEntry(); + } + byte[] buffer = new byte[1 << 14]; + for (JarEntry je; (je = in.getNextJarEntry()) != null; ) { + out.putNextEntry(je); + for (int nr; 0 < (nr = in.read(buffer)); ) { + out.write(buffer, 0, nr); + } + } + in.close(); + markJarFile(out); // add PACK200 comment + } + static void copyJarFile(JarFile in, JarOutputStream out) throws IOException { + byte[] buffer = new byte[1 << 14]; + for (JarEntry je : Collections.list(in.entries())) { + out.putNextEntry(je); + InputStream ein = in.getInputStream(je); + for (int nr; 0 < (nr = ein.read(buffer)); ) { + out.write(buffer, 0, nr); + } + } + in.close(); + markJarFile(out); // add PACK200 comment + } + static void copyJarFile(JarInputStream in, OutputStream out) throws IOException { + // 4947205 : Peformance is slow when using pack-effort=0 + out = new BufferedOutputStream(out); + out = new NonCloser(out); // protect from JarOutputStream.close() + try (JarOutputStream jout = new JarOutputStream(out)) { + copyJarFile(in, jout); + } + } + static void copyJarFile(JarFile in, OutputStream out) throws IOException { + + // 4947205 : Peformance is slow when using pack-effort=0 + out = new BufferedOutputStream(out); + out = new NonCloser(out); // protect from JarOutputStream.close() + try (JarOutputStream jout = new JarOutputStream(out)) { + copyJarFile(in, jout); + } + } + // Wrapper to prevent closing of client-supplied stream. + private static + class NonCloser extends FilterOutputStream { + NonCloser(OutputStream out) { super(out); } + public void close() throws IOException { flush(); } + } + static String getJarEntryName(String name) { + if (name == null) return null; + return name.replace(File.separatorChar, '/'); + } + + static String zeString(ZipEntry ze) { + int store = (ze.getCompressedSize() > 0) ? + (int)( (1.0 - ((double)ze.getCompressedSize()/(double)ze.getSize()))*100 ) + : 0 ; + // Follow unzip -lv output + return ze.getSize() + "\t" + ze.getMethod() + + "\t" + ze.getCompressedSize() + "\t" + + store + "%\t" + + new Date(ze.getTime()) + "\t" + + Long.toHexString(ze.getCrc()) + "\t" + + ze.getName() ; + } + + + + static byte[] readMagic(BufferedInputStream in) throws IOException { + in.mark(4); + byte[] magic = new byte[4]; + for (int i = 0; i < magic.length; i++) { + // read 1 byte at a time, so we always get 4 + if (1 != in.read(magic, i, 1)) + break; + } + in.reset(); + return magic; + } + + // magic number recognizers + static boolean isJarMagic(byte[] magic) { + return (magic[0] == (byte)'P' && + magic[1] == (byte)'K' && + magic[2] >= 1 && + magic[2] < 8 && + magic[3] == magic[2] + 1); + } + static boolean isPackMagic(byte[] magic) { + return (magic[0] == (byte)0xCA && + magic[1] == (byte)0xFE && + magic[2] == (byte)0xD0 && + magic[3] == (byte)0x0D); + } + static boolean isGZIPMagic(byte[] magic) { + return (magic[0] == (byte)0x1F && + magic[1] == (byte)0x8B && + magic[2] == (byte)0x08); + // fourth byte is variable "flg" field + } + + private Utils() { } // do not instantiate +} diff --git a/src/main/java/net/minecraftforge/gradle/util/pack200/package-info.java b/src/main/java/net/minecraftforge/gradle/util/pack200/package-info.java new file mode 100644 index 000000000..2e6df4894 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/util/pack200/package-info.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * This package provides methods to read files from a JAR file and to + * transform them to a more compact transfer format called Pack200. + * It also provides methods to receive the transmitted data and expand + * it into a JAR file equivalent to the original JAR file. + * + *

+ * The {@code pack} methods may be used by application developers who + * wish to deploy large JARs on the web. The {@code unpack} methods + * may be used by deployment applications such as Java Web Start and + * Java Plugin. + * + *

+ * In typical use, the packed output should be further compressed + * using a suitable tool such as gzip or + * {@code java.util.zip.GZIPOutputStream}. The resulting file (with + * a suffix ".pack.gz") should be hosted on a HTTP/1.1 compliant + * server, which will be capable of handling "Accept-Encoding", as + * specified by the HTTP 1.1 RFC2616 specification. + * + *

+ * NOTE: It is recommended that the original ".jar" file be + * hosted in addition to the ".pack.gz" file, so that older client + * implementations will continue to work reliably. (On-demand + * compression by the server is not recommended.) + * + *

+ * When a client application requests a ".jar" file (call it + * "Large.jar"), the client will transmit the headers + * "Content-Type=application/x-java-archive" as well as + * "Accept-Encoding=pack200-gzip". This indicates to the server that + * the client application desires an version of the file encoded with + * Pack200 and further compressed with gzip. + * + *

+ * The server implementation will typically check for the existence of + * "Large.pack.gz". If that file is available, the server will + * transmit it with the headers "Content-Encoding=pack200-gzip" and + * "Content-Type=application/x-java-archive". + * + *

+ * If the ".pack.gz" file, is not available, then the server will + * transmit the original ".jar" with "Content-Encoding=null" and + * "Content-Type=application/x-java-archive". + * + *

+ * A MIME type of "application/x-java-pack200" may be specified by the + * client application to indicate a ".pack" file is required. + * However, this has limited capability, and is not recommended. + * + *

Package Specification

+ * Network Transfer Format Specification : + * http://jcp.org/en/jsr/detail?id=200 + * + *

Related Documentation

+ * For overviews, tutorials, examples, guides, and tool documentation, please + * see: + * + * + *
  • + * @since 1.5
  • + */ +package net.minecraftforge.gradle.util.pack200; diff --git a/src/test/java/net/minecraftforge/gradle/user/patcherUser/TaskApplyBinPatchesTest.java b/src/test/java/net/minecraftforge/gradle/user/patcherUser/TaskApplyBinPatchesTest.java index 7a81a34d2..2c0813e97 100644 --- a/src/test/java/net/minecraftforge/gradle/user/patcherUser/TaskApplyBinPatchesTest.java +++ b/src/test/java/net/minecraftforge/gradle/user/patcherUser/TaskApplyBinPatchesTest.java @@ -23,6 +23,7 @@ import com.nothome.delta.Delta; import lzma.streams.LzmaOutputStream; import net.minecraftforge.gradle.testsupport.TaskTest; +import net.minecraftforge.gradle.util.pack200.Pack200; import net.minecraftforge.gradle.util.patching.BinPatches; import org.junit.Assert; import org.junit.Test;