feat(jdk8): move files to new folder to avoid resources compiled.

This commit is contained in:
2025-09-07 15:25:52 +08:00
parent 3f0047bf6f
commit 8c35cfb1c0
17415 changed files with 217 additions and 213 deletions
@@ -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 com.sun.java.util.jar.pack;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import static com.sun.java.util.jar.pack.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));
}
}
}
//*/
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,647 @@
/*
* 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 com.sun.java.util.jar.pack;
import com.sun.java.util.jar.pack.ConstantPool.ClassEntry;
import com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry;
import com.sun.java.util.jar.pack.ConstantPool.Entry;
import com.sun.java.util.jar.pack.ConstantPool.SignatureEntry;
import com.sun.java.util.jar.pack.ConstantPool.MemberEntry;
import com.sun.java.util.jar.pack.ConstantPool.MethodHandleEntry;
import com.sun.java.util.jar.pack.ConstantPool.BootstrapMethodEntry;
import com.sun.java.util.jar.pack.ConstantPool.Utf8Entry;
import com.sun.java.util.jar.pack.Package.Class;
import com.sun.java.util.jar.pack.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 com.sun.java.util.jar.pack.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<Attribute.Layout, Attribute> attrDefs;
Map<Attribute.Layout, String> 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<Attribute.Layout, Attribute> attrDefs) {
this.attrDefs = attrDefs;
}
public void setAttrCommands(Map<Attribute.Layout, String> 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 <em>global</em> 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;
}
private Entry checkValid(Entry e) {
if (e == INVALID_ENTRY) {
throw new IllegalStateException("Invalid constant pool reference");
}
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
}
// use this identity for invalid references
private static final Entry INVALID_ENTRY = new Entry((byte) -1) {
@Override
public boolean equals(Object o) {
throw new IllegalStateException("Should not call this");
}
@Override
protected int computeValueHash() {
throw new IllegalStateException("Should not call this");
}
@Override
public int compareTo(Object o) {
throw new IllegalStateException("Should not call this");
}
@Override
public String stringValue() {
throw new IllegalStateException("Should not call this");
}
};
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] = INVALID_ENTRY;
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] = INVALID_ENTRY;
}
break;
case CONSTANT_Double:
{
cpMap[i] = ConstantPool.getLiteralEntry(in.readDouble());
cpMap[++i] = INVALID_ENTRY;
}
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 && checkValid(cpMap[ref]) == null || ref2 >= 0 && checkValid(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 this 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<InnerClass> 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);
}
}
}
@@ -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 com.sun.java.util.jar.pack;
import com.sun.java.util.jar.pack.ConstantPool.Entry;
import com.sun.java.util.jar.pack.ConstantPool.Index;
import com.sun.java.util.jar.pack.ConstantPool.NumberEntry;
import com.sun.java.util.jar.pack.ConstantPool.MethodHandleEntry;
import com.sun.java.util.jar.pack.ConstantPool.BootstrapMethodEntry;
import com.sun.java.util.jar.pack.Package.Class;
import com.sun.java.util.jar.pack.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 com.sun.java.util.jar.pack.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<? extends Class.Member> 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<BootstrapMethodEntry> 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<InnerClass> ics = cls.getInnerClasses();
writeShort(ics.size());
for (InnerClass ic : ics) {
writeRef(ic.thisClass);
writeRef(ic.outerClass);
writeRef(ic.name);
writeShort(ic.flags);
}
}
}
@@ -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 com.sun.java.util.jar.pack;
import com.sun.java.util.jar.pack.Package.Class;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import static com.sun.java.util.jar.pack.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();
}
static private 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<Fixups.Fixup> 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<ConstantPool.Entry> 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<len?"":" ")+" enc="+enc);
continue;
}
if (ok) {
for (int q = 0; q <= 1; q++) {
StringBuffer sb = new StringBuffer();
sb.append("bci "+(q==0?"map":"del")+"["+len+"] = {");
for (int j = 0; j < len; j++) {
int mapi = ((Number)java.lang.reflect.Array.get(insnMap, j)).intValue() - base;
mapi -= j*q;
sb.append(" "+mapi);
}
sb.append(" }");
System.out.println("*** "+sb);
}
}
System.out.println("*** BCI="+bci+" enc="+enc+" dec="+dec);
ok = false;
}
return ok;
}
//*/
}
@@ -0,0 +1,906 @@
/*
* Copyright (c) 2001, 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.
*/
package com.sun.java.util.jar.pack;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import static com.sun.java.util.jar.pack.Constants.*;
/**
* Define the conversions between sequences of small integers and raw bytes.
* This is a schema of encodings which incorporates varying lengths,
* varying degrees of length variability, and varying amounts of signed-ness.
* @author John Rose
*/
class Coding implements Comparable<Coding>, 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), or both. There is no other byte that satisfies these conditions.
All bytes before the last always have "H values" (>=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:
<pre>
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<n) == Sum[i<n]( b[i] * H^i )
up to L*Sum[i<n]( H^i )
or to L*Sum[i<n]( H^i ) + H^n if n==B
</pre>
(**) 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 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:
<pre>
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
</pre>
Finally, the value of a byte sequence, given the coding parameters
(B,H,S), is defined as:
<pre>
V(b[i]: i<n) == Sgn(U(b[i]: i<n))
</pre>
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<n]( H^i )
// n == B: L*Sum[i<B]( H^i ) + H^B
assert(nMax >= 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<<S)-1;
return (((int)ux+1) & Smask) == 0;
}
private static boolean hasNegativeCode(int sx, int S) {
assert(S > 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<<S)-1;
long ux;
if (!hasNegativeCode(sx, S)) {
// InvSgn(sx) = (sx / (2^S-1))*2^S + (sx % (2^S-1))
ux = sx + (toUnsigned32(sx) / Smask);
} else {
// InvSgn(sx) = (-sx-1)*2^S + (2^S-1)
ux = (-sx << S) - 1;
}
ux = toUnsigned32((int)ux);
assert(sx == decodeSign32(ux, S))
: (Long.toHexString(ux)+" -> "+
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<n) == Sum[i<n]( b[i] * H^i )
int L = 256-H;
long sum = 0;
long H_i = 1;
int pos = inpos[0];
for (int i = 0; i < B; i++) {
int b_i = in[pos++] & 0xFF;
sum += b_i*H_i;
H_i *= H;
if (b_i < L) break;
}
//assert(sum >= 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<n) == Sum[i<n]( b[i] * H^i )
int L = 256-H;
long sum = 0;
long H_i = 1;
for (int i = 0; i < B; i++) {
int b_i = in.read();
if (b_i < 0) throw new RuntimeException("unexpected EOF");
sum += b_i*H_i;
H_i *= H;
if (b_i < L) break;
}
assert(sum >= 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<Coding, Coding> 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;
}
static private 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;
}
}
File diff suppressed because it is too large Load Diff
@@ -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 com.sun.java.util.jar.pack;
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);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,491 @@
/*
* 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 com.sun.java.util.jar.pack;
import java.util.Arrays;
import java.util.List;
/**
* Shared constants
* @author John Rose
*/
class Constants {
private Constants(){}
public final static 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.5.x 50,0
1.7 to 1.6.x 51,0
1.8 to 1.7.x 52,0
*/
public final static Package.Version JAVA_MIN_CLASS_VERSION =
Package.Version.of(45, 03);
public final static Package.Version JAVA5_MAX_CLASS_VERSION =
Package.Version.of(49, 00);
public final static Package.Version JAVA6_MAX_CLASS_VERSION =
Package.Version.of(50, 00);
public final static Package.Version JAVA7_MAX_CLASS_VERSION =
Package.Version.of(51, 00);
public final static Package.Version JAVA8_MAX_CLASS_VERSION =
Package.Version.of(52, 00);
public final static int JAVA_PACKAGE_MAGIC = 0xCAFED00D;
public final static Package.Version JAVA5_PACKAGE_VERSION =
Package.Version.of(150, 7);
public final static Package.Version JAVA6_PACKAGE_VERSION =
Package.Version.of(160, 1);
public final static Package.Version JAVA7_PACKAGE_VERSION =
Package.Version.of(170, 1);
public final static Package.Version JAVA8_PACKAGE_VERSION =
Package.Version.of(171, 0);
// upper limit, should point to the latest class version
public final static Package.Version JAVA_MAX_CLASS_VERSION =
JAVA8_MAX_CLASS_VERSION;
// upper limit should point to the latest package version, for version info!.
public final static Package.Version MAX_PACKAGE_VERSION =
JAVA7_PACKAGE_VERSION;
public final static int CONSTANT_POOL_INDEX_LIMIT = 0x10000;
public final static int CONSTANT_POOL_NARROW_LIMIT = 0x00100;
public final static String JAVA_SIGNATURE_CHARS = "BSCIJFDZLV([";
public final static byte CONSTANT_Utf8 = 1;
public final static byte CONSTANT_unused2 = 2; // unused, was Unicode
public final static byte CONSTANT_Integer = 3;
public final static byte CONSTANT_Float = 4;
public final static byte CONSTANT_Long = 5;
public final static byte CONSTANT_Double = 6;
public final static byte CONSTANT_Class = 7;
public final static byte CONSTANT_String = 8;
public final static byte CONSTANT_Fieldref = 9;
public final static byte CONSTANT_Methodref = 10;
public final static byte CONSTANT_InterfaceMethodref = 11;
public final static byte CONSTANT_NameandType = 12;
public final static byte CONSTANT_unused13 = 13;
public final static byte CONSTANT_unused14 = 14;
public final static byte CONSTANT_MethodHandle = 15;
public final static byte CONSTANT_MethodType = 16;
public final static byte CONSTANT_unused17 = 17; // unused
public final static byte CONSTANT_InvokeDynamic = 18;
// pseudo-constants:
public final static byte CONSTANT_None = 0;
public final static byte CONSTANT_Signature = CONSTANT_unused13;
public final static byte CONSTANT_BootstrapMethod = CONSTANT_unused17; // used only in InvokeDynamic constants
public final static byte CONSTANT_Limit = 19;
public final static byte CONSTANT_All = 50; // combined global map
public final static byte CONSTANT_LoadableValue = 51; // used for 'KL' and qldc operands
public final static byte CONSTANT_AnyMember = 52; // union of refs to field or (interface) method
public final static byte CONSTANT_FieldSpecific = 53; // used only for 'KQ' ConstantValue attrs
public final static byte CONSTANT_GroupFirst = CONSTANT_All;
public final static byte CONSTANT_GroupLimit = CONSTANT_FieldSpecific+1;
// CONSTANT_MethodHandle reference kinds
public final static byte REF_getField = 1;
public final static byte REF_getStatic = 2;
public final static byte REF_putField = 3;
public final static byte REF_putStatic = 4;
public final static byte REF_invokeVirtual = 5;
public final static byte REF_invokeStatic = 6;
public final static byte REF_invokeSpecial = 7;
public final static byte REF_newInvokeSpecial = 8;
public final static byte REF_invokeInterface = 9;
// pseudo-access bits
public final static 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 final static int[] noInts = {};
public final static byte[] noBytes = {};
public final static Object[] noValues = {};
public final static String[] noStrings = {};
public final static List<Object> emptyList = Arrays.asList(noValues);
// meta-coding
public final static 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 final static 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 final static int _end_marker = 255;
// Escapes:
public final static int _byte_escape = 254;
public final static 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 final static int _first_linker_op = _getstatic;
public final static int _last_linker_op = _invokestatic;
public final static int _num_linker_ops = (_last_linker_op - _first_linker_op) + 1;
public final static int _self_linker_op = _bytecode_limit;
public final static int _self_linker_aload_flag = 1*_num_linker_ops;
public final static int _self_linker_super_flag = 2*_num_linker_ops;
public final static 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 "<init>". 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 final static int _invokeinit_op = _self_linker_limit;
public final static int _invokeinit_self_option = 0;
public final static int _invokeinit_super_option = 1;
public final static int _invokeinit_new_option = 2;
public final static int _invokeinit_limit = _invokeinit_op+3;
public final static 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 final static int _xldc_op = _invokeinit_limit;
public final static int _sldc = _ldc; // previously named _aldc
public final static int _cldc = _xldc_op+0;
public final static int _ildc = _xldc_op+1;
public final static int _fldc = _xldc_op+2;
public final static int _sldc_w = _ldc_w; // previously named _aldc_w
public final static int _cldc_w = _xldc_op+3;
public final static int _ildc_w = _xldc_op+4;
public final static int _fldc_w = _xldc_op+5;
public final static int _lldc2_w = _ldc2_w;
public final static int _dldc2_w = _xldc_op+6;
// anything other than primitive, string, or class must be handled with qldc:
public final static int _qldc = _xldc_op+7;
public final static int _qldc_w = _xldc_op+8;
public final static int _xldc_limit = _xldc_op+9;
// handling of InterfaceMethodRef
public final static int _invoke_int_op = _xldc_limit;
public final static int _invokespecial_int = _invoke_int_op+0;
public final static int _invokestatic_int = _invoke_int_op+1;
public final static int _invoke_int_limit = _invoke_int_op+2;
}
@@ -0,0 +1,728 @@
/*
* 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 com.sun.java.util.jar.pack;
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.jar.Pack200;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/** Command line interface for Pack200.
*/
class Driver {
private static final ResourceBundle RESOURCE =
ResourceBundle.getBundle("com.sun.java.util.jar.pack.DriverResource");
public static void main(String[] ava) throws IOException {
List<String> av = new ArrayList<>(Arrays.asList(ava));
boolean doPack = true;
boolean doUnpack = false;
boolean doRepack = false;
boolean doZip = true;
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;
}
}
// Collect engine properties here:
Map<String,String> 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<String,String> avProps = new HashMap<>();
try {
for (;;) {
String state = parseCommandOptions(av, optionMap, avProps);
// Translate command line options to Pack200 properties:
eachOpt:
for (Iterator<String> 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<Object,Object> 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(1);
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();
}
}
static private
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();
}
static private
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;
}
}
}
static private
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.
* <p>
* The options string is a newline-separated series of
* option processing specifiers.
*/
private static
String parseCommandOptions(List<String> args,
String options,
Map<String,String> properties) {
//System.out.println(args+" // "+properties);
String resultString = null;
// Convert options string into optLines dictionary.
TreeMap<String,String[]> 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<String> argp = args.listIterator();
ListIterator<String> pbp = new ArrayList<String>().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<String,String[]> 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;
}
}
@@ -0,0 +1,120 @@
/*
* Copyright (c) 2005, 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 com.sun.java.util.jar.pack;
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";
/*
* 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",
" -g, --no-gzip output a plain *.pack file with no zipping",
" --gzip (default) post-process the pack output with gzip",
" -G, --strip-debug remove debugging attributes 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} output segment limit (default N=1Mb)",
" -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) uncompressed",
" -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 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"
}
},
{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 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
};
protected Object[][] getContents() {
return resource;
}
}
@@ -0,0 +1,120 @@
/*
* Copyright (c) 2005, 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 com.sun.java.util.jar.pack;
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";
/*
* 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",
" -g\u3001--no-gzip \u5727\u7E2E\u305B\u305A\u306B\u30D7\u30EC\u30FC\u30F3\u306A*.pack\u30D5\u30A1\u30A4\u30EB\u3092\u51FA\u529B\u3057\u307E\u3059",
" --gzip (\u30C7\u30D5\u30A9\u30EB\u30C8) pack\u51FA\u529B\u3092gzip\u3067\u5F8C\u51E6\u7406\u3057\u307E\u3059",
" -G\u3001--strip-debug \u5727\u7E2E\u4E2D\u306B\u30C7\u30D0\u30C3\u30B0\u5C5E\u6027\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\u5236\u9650\u3092\u51FA\u529B\u3057\u307E\u3059(\u30C7\u30D5\u30A9\u30EB\u30C8N=1Mb)",
" -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\u5727\u7E2E\u3055\u308C\u3066\u3044\u306A\u3044\u5165\u529B\u8981\u7D20\u3092\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\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"
}
},
{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\u306FSystem.out ('-'\u306E\u5834\u5408)\u306B\u51FA\u529B\u3057\u307E\u3059",
" -?\u3001-h\u3001--help \u3053\u306E\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
};
protected Object[][] getContents() {
return resource;
}
}
@@ -0,0 +1,120 @@
/*
* Copyright (c) 2005, 2014, 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 com.sun.java.util.jar.pack;
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";
/*
* 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\u91CD\u65B0\u6253\u5305: {0}"}, // parameter 0:filename
{PACK_HELP, new String[] {
"\u7528\u6CD5: pack200 [-opt... | --option=value]... x.pack[.gz] y.jar",
"",
"\u6253\u5305\u9009\u9879",
" -g, --no-gzip \u8F93\u51FA\u65E0\u683C\u5F0F\u7684 *.pack \u6587\u4EF6, \u4E0D\u538B\u7F29",
" --gzip (\u9ED8\u8BA4\u503C) \u4F7F\u7528 gzip \u5BF9\u6253\u5305\u8FDB\u884C\u540E\u5904\u7406",
" -G, --strip-debug \u6253\u5305\u65F6\u5220\u9664\u8C03\u8BD5\u5C5E\u6027",
" -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} \u8F93\u51FA\u6BB5\u9650\u5236 (\u9ED8\u8BA4\u503C N=1Mb)",
" -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\u89E3\u538B\u7F29\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\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"
}
},
{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\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
};
protected Object[][] getContents() {
return resource;
}
}
@@ -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 com.sun.java.util.jar.pack;
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<E> implements List<E> {
private final ArrayList<E> 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<E> iterator() {
return flist.iterator();
}
@Override
public Object[] toArray() {
return flist.toArray();
}
@Override
public <T> 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<? extends E> c) throws UnsupportedOperationException {
throw new UnsupportedOperationException("operation not permitted");
}
@Override
public boolean addAll(int index, Collection<? extends E> 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<E> listIterator() {
return flist.listIterator();
}
@Override
public ListIterator<E> listIterator(int index) {
return flist.listIterator(index);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
return flist.subList(fromIndex, toIndex);
}
@Override
public String toString() {
return "FixedList{" + "plist=" + flist + '}';
}
}
@@ -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 com.sun.java.util.jar.pack;
import com.sun.java.util.jar.pack.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.
* <p>
* As a Collection, it can be iterated over, but it is not a List,
* since it does not natively support indexed access.
* <p>
*
* @author John Rose
*/
final class Fixups extends AbstractCollection<Fixups.Fixup> {
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<Fixup> fixups) {
this(bytes);
addAll(fixups);
}
Fixups(Collection<Fixup> 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<Entry> 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<Fixup> old = null;
assert((old = new ArrayList<>(this)) != null);
if (bytes == null || newBytes == null) {
// One or the other representations is deficient.
// Construct a checkpoint.
ArrayList<Fixup> 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<Fixup> {
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<Fixup> {
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<Fixup> 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<? extends Fixup> 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<Entry> 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);
}
}
}
//*/
}
@@ -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 com.sun.java.util.jar.pack;
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.
* <p>
* Format of matrix:
* Each row in the matrix begins with an occurrence count,
* and continues with all int values that occur at that frequency.
* <pre>
* int[][] matrix = {
* { count1, value11, value12, value13, ... },
* { count2, value21, value22, ... },
* ...
* }
* </pre>
* 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;
}
static private
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.
* <p>
* Format of result:
* <pre>
* long[] hist = {
* (count1 << 32) | (value1),
* (count2 << 32) | (value2),
* ...
* }
* </pre>
* 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<<i;
}
int[][] gm = regroupHistogram(h.getMatrix(), groups);
Histogram g = new Histogram(gm);
System.out.println("h.getBitLength(g) = "+
h.getBitLength(g.getBitMetric()));
System.out.println("g.getBitLength(h) = "+
g.getBitLength(h.getBitMetric()));
g.print("regrouped", System.out);
}
}
//*/
}
@@ -0,0 +1,688 @@
/*
* 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 com.sun.java.util.jar.pack;
import java.io.IOException;
import java.util.Arrays;
import static com.sun.java.util.jar.pack.Constants.*;
/**
* A parsed bytecode instruction.
* Provides accessors to various relevant bits.
* @author John Rose
*/
class Instruction {
protected byte[] bytes; // bytecodes
protected int pc; // location of this instruction
protected int bc; // opcode of this instruction
protected int w; // 0 if normal, 1 if a _wide prefix at pc
protected int length; // bytes in this instruction
protected boolean special;
protected Instruction(byte[] bytes, int pc, int bc, int w, int length) {
reset(bytes, pc, bc, w, length);
}
private void reset(byte[] bytes, int pc, int bc, int w, int length) {
this.bytes = bytes;
this.pc = pc;
this.bc = bc;
this.w = w;
this.length = length;
}
public int getBC() {
return bc;
}
public boolean isWide() {
return w != 0;
}
public byte[] getBytes() {
return bytes;
}
public int getPC() {
return pc;
}
public int getLength() {
return length;
}
public int getNextPC() {
return pc + length;
}
public Instruction next() {
int npc = pc + length;
if (npc == bytes.length)
return null;
else
return Instruction.at(bytes, npc, this);
}
public boolean isNonstandard() {
return isNonstandard(bc);
}
public void setNonstandardLength(int length) {
assert(isNonstandard());
this.length = length;
}
/** A fake instruction at this pc whose next() will be at nextpc. */
public Instruction forceNextPC(int nextpc) {
int llength = nextpc - pc;
return new Instruction(bytes, pc, -1, -1, llength);
}
public static Instruction at(byte[] bytes, int pc) {
return Instruction.at(bytes, pc, null);
}
public static Instruction at(byte[] bytes, int pc, Instruction reuse) {
int bc = getByte(bytes, pc);
int prefix = -1;
int w = 0;
int length = BC_LENGTH[w][bc];
if (length == 0) {
// Hard cases:
switch (bc) {
case _wide:
bc = getByte(bytes, pc+1);
w = 1;
length = BC_LENGTH[w][bc];
if (length == 0) {
// unknown instruction; treat as one byte
length = 1;
}
break;
case _tableswitch:
return new TableSwitch(bytes, pc);
case _lookupswitch:
return new LookupSwitch(bytes, pc);
default:
// unknown instruction; treat as one byte
length = 1;
break;
}
}
assert(length > 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.
static private final byte[][] BC_LENGTH = new byte[2][0x100];
static private final byte[][] BC_INDEX = new byte[2][0x100];
static private final byte[][] BC_TAG = new byte[2][0x100];
static private final byte[][] BC_BRANCH = new byte[2][0x100];
static private final byte[][] BC_SLOT = new byte[2][0x100];
static private final byte[][] BC_CON = new byte[2][0x100];
static private final String[] BC_NAME = new String[0x100]; // debug only
static private 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);
}
}
}
@@ -0,0 +1,410 @@
/*
* 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 com.sun.java.util.jar.pack;
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.LinkedList;
import java.util.List;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
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.
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 static final int MAX_BUFFER_SIZE = 1 << 20; // 1 MB byte[]
private byte[] _buf = new byte[1 << 14]; // 16 KB byte[] initially
private List<byte[]> _extra_buf = new LinkedList<>(); // extra buffers
// for large files
private byte[] _current_buf; // buffer being filled
private int _current_buf_pos; // position to fill in more data
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<Void>() {
public Void run() {
System.loadLibrary("unpack");
return null;
}
});
initIDs();
}
NativeUnpack(UnpackerImpl p200) {
super();
_p200 = p200;
_props = p200.props;
p200._nunp = this;
}
// for JNI callbacks
static private 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), consumed;
try {
_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();
} finally {
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 {
if (lsize < 0 || lsize > Integer.MAX_VALUE) {
throw new IOException("file too large: " + lsize);
}
int size = (int) lsize;
if (_verbose > 1) {
Utils.log.fine("Writing entry: " + name + " size=" + size +
(deflateHint ? " deflated" : ""));
}
ZipEntry z = new ZipEntry(name);
z.setTime(mtime * 1000);
z.setSize(size);
if (size == 0) {
z.setMethod(ZipOutputStream.STORED);
z.setCompressedSize(size);
z.setCrc(0);
j.putNextEntry(z);
} else if (!deflateHint) {
z.setMethod(ZipOutputStream.STORED);
z.setCompressedSize(size);
writeEntryData(j, z, data0, data1, size, true);
} else {
z.setMethod(Deflater.DEFLATED);
writeEntryData(j, z, data0, data1, size, false);
}
j.closeEntry();
if (_verbose > 0) Utils.log.info("Writing " + Utils.zeString(z));
}
private void writeEntryData(JarOutputStream j, ZipEntry z, ByteBuffer data0,
ByteBuffer data1, int size, boolean computeCrc32)
throws IOException {
prepareReadBuffers(size);
try {
int inBytes = size;
inBytes -= readDataByteBuffer(data0);
inBytes -= readDataByteBuffer(data1);
inBytes -= readDataInputStream(inBytes);
if (inBytes != 0L) {
throw new IOException("invalid size: " + size);
}
if (computeCrc32) {
_crc32.reset();
processReadData((byte[] buff, int offset, int len) -> {
_crc32.update(buff, offset, len);
});
z.setCrc(_crc32.getValue());
}
j.putNextEntry(z);
processReadData((byte[] buff, int offset, int len) -> {
j.write(buff, offset, len);
});
} finally {
resetReadBuffers();
}
}
private void prepareReadBuffers(int size) {
if (_buf.length < size && _buf.length < MAX_BUFFER_SIZE) {
// Grow the regular buffer to accomodate lsize up to a limit.
long newIdealSize = _buf.length;
while (newIdealSize < size && newIdealSize < MAX_BUFFER_SIZE) {
// Never overflows: size is [0, 0x7FFFFFFF].
newIdealSize <<= 1;
}
int newSize = (int) Long.min(newIdealSize, MAX_BUFFER_SIZE);
_buf = new byte[newSize];
}
resetReadBuffers();
}
private void resetReadBuffers() {
_extra_buf.clear();
_current_buf = _buf;
_current_buf_pos = 0;
}
private int readDataByteBuffer(ByteBuffer data) throws IOException {
if (data == null) {
return 0;
}
return readData(data.remaining(),
(byte[] buff, int offset, int len) -> {
data.get(buff, offset, len);
return len;
});
}
private int readDataInputStream(int inBytes) throws IOException {
return readData(inBytes, (byte[] buff, int offset, int len) -> {
return in.read(buff, offset, len);
});
}
private static interface ReadDataCB {
public int read(byte[] buff, int offset, int len) throws IOException;
}
private int readData(int bytesToRead, ReadDataCB readDataCb)
throws IOException {
int bytesRemaining = bytesToRead;
while (bytesRemaining > 0) {
if (_current_buf_pos == _current_buf.length) {
byte[] newBuff = new byte[Integer.min(bytesRemaining,
MAX_BUFFER_SIZE)];
_extra_buf.add(newBuff);
_current_buf = newBuff;
_current_buf_pos = 0;
}
int current_buffer_space = _current_buf.length - _current_buf_pos;
int nextRead = Integer.min(current_buffer_space, bytesRemaining);
int bytesRead = readDataCb.read(_current_buf, _current_buf_pos,
nextRead);
if (bytesRead <= 0) {
throw new IOException("EOF at end of archive");
}
_current_buf_pos += bytesRead;
bytesRemaining -= bytesRead;
}
return bytesToRead - bytesRemaining;
}
private static interface ProcessDataCB {
public void apply(byte[] buff, int offset, int len) throws IOException;
}
private void processReadData(ProcessDataCB processDataCB)
throws IOException {
processDataCB.apply(_buf, 0, _buf == _current_buf ? _current_buf_pos :
_buf.length);
for (byte[] buff : _extra_buf) {
// Extra buffers are allocated of a size such that they are always
// full, including the last one.
processDataCB.apply(buff, 0, buff.length);
};
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,638 @@
/*
* 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 com.sun.java.util.jar.pack;
import com.sun.java.util.jar.pack.Attribute.Layout;
import java.beans.PropertyChangeListener;
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.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.TimeZone;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Pack200;
/*
* Implementation of the Pack provider.
* </pre></blockquote>
* @author John Rose
* @author Kumar Srinivasan
*/
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<String, String> properties() {
return props;
}
//Driver routines
/**
* Takes a JarFile and converts into a pack-stream.
* <p>
* 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);
boolean needUTC = !props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE);
try {
Utils.currentInstance.set(this);
if (needUTC) {
Utils.changeDefaultTimeZoneToUtc();
}
if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) {
Utils.copyJarFile(in, out);
} else {
(new DoPack()).run(in, out);
}
} finally {
Utils.currentInstance.set(null);
if (needUTC) {
Utils.restoreDefaultTimeZone();
}
in.close();
}
}
/**
* Takes a JarInputStream and converts into a pack-stream.
* <p>
* Closes its input but not its output. (Pack200 archives are appendable.)
* <p>
* 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);
boolean needUTC = !props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE);
try {
Utils.currentInstance.set(this);
if (needUTC) {
Utils.changeDefaultTimeZoneToUtc();
}
if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) {
Utils.copyJarFile(in, out);
} else {
(new DoPack()).run(in, out);
}
} finally {
Utils.currentInstance.set(null);
if (needUTC) {
Utils.restoreDefaultTimeZone();
}
in.close();
}
}
/**
* Register a listener for changes to options.
* @param listener An object to be invoked when a property is changed.
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
props.addListener(listener);
}
/**
* Remove a listener for the PropertyChange event.
* @param listener The PropertyChange listener to be removed.
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
props.removeListener(listener);
}
// 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<Attribute.Layout, Attribute> attrDefs;
final Map<Attribute.Layout, String> attrCommands;
{
Map<Attribute.Layout, Attribute> lattrDefs = new HashMap<>();
Map<Attribute.Layout, String> 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<String, String> 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<String> passFiles; // parsed pack.pass.file options
{
// Which class files will be passed through?
passFiles = props.getProperties(Pack200.Packer.PASS_FILE_PFX);
for (ListIterator<String> 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.)
boolean isClassFile(String name) {
if (!name.endsWith(".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(String name) {
return name.startsWith("/" + Utils.METAINF) ||
name.startsWith(Utils.METAINF);
}
// 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 = getModtime(je.getTime());
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);
}
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 = (isMetaInfFile(name))
? 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 (isClassFile(name)) {
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<InFile> 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 = (isMetaInfFile(name))
? 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 (isClassFile(name)) {
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<Package.File> 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<InFile> scanJar(JarFile jf) throws IOException {
// Collect jar entries, preserving order.
List<InFile> 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;
}
}
}
@@ -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 com.sun.java.util.jar.pack;
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 com.sun.java.util.jar.pack.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<Integer> 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();
}
}
@@ -0,0 +1,453 @@
/*
* 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 com.sun.java.util.jar.pack;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
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;
import java.util.jar.Pack200;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Control block for publishing Pack200 options to the other classes.
*/
final class PropMap implements SortedMap<String, String> {
private final TreeMap<String, String> theMap = new TreeMap<>();;
// type is erased, elements are of type java.beans.PropertyChangeListener
private final List<Object> listenerList = new ArrayList<>(1);
void addListener(Object listener) {
assert Beans.isPropertyChangeListener(listener);
listenerList.add(listener);
}
void removeListener(Object listener) {
assert Beans.isPropertyChangeListener(listener);
listenerList.remove(listener);
}
// Override:
public String put(String key, String value) {
String oldValue = theMap.put(key, value);
if (value != oldValue && !listenerList.isEmpty()) {
assert Beans.isBeansPresent();
// Post the property change event.
Object event = Beans.newPropertyChangeEvent(this, key, oldValue, value);
for (Object listener : listenerList) {
Beans.invokePropertyChange(listener, event);
}
}
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<String, String> defaultProps;
static {
Properties props = new Properties();
// Allow implementation selected via -Dpack.disable.native=true
props.put(Utils.DEBUG_DISABLE_NATIVE,
String.valueOf(Boolean.getBoolean(Utils.DEBUG_DISABLE_NATIVE)));
// Set the DEBUG_VERBOSE from system
props.put(Utils.DEBUG_VERBOSE,
String.valueOf(Integer.getInteger(Utils.DEBUG_VERBOSE,0)));
// Set the PACK_TIMEZONE_NO_UTC
props.put(Utils.PACK_DEFAULT_TIMEZONE,
String.valueOf(Boolean.getBoolean(Utils.PACK_DEFAULT_TIMEZONE)));
// 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,
System.getProperty(Utils.CLASS_FORMAT_ERROR, Pack200.Packer.PASS));
// Default effort is 5, midway between 1 and 9.
props.put(Pack200.Packer.EFFORT, "5");
// Define certain attribute layouts by default.
// Do this after the previous props are put in place,
// to allow override if necessary.
String propFile = "intrinsic.properties";
try (InputStream propStr = PackerImpl.class.getResourceAsStream(propFile)) {
if (propStr == null) {
throw new RuntimeException(propFile + " cannot be loaded");
}
props.load(propStr);
} catch (IOException ee) {
throw new RuntimeException(ee);
}
for (Map.Entry<Object, Object> 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<String, String> temp = new HashMap(props); // shrink to fit
defaultProps = temp;
}
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<String, String> 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<String> getProperties(String prefix) {
Collection<String> values = prefixMap(prefix).values();
List<String> 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<Map.Entry<String, String>> defaults = defaultProps.entrySet();
for (Map.Entry<String, String> 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<? extends String, ? extends String> m) {
theMap.putAll(m);
}
@Override
public void clear() {
theMap.clear();
}
@Override
public Set<String> keySet() {
return theMap.keySet();
}
@Override
public Collection<String> values() {
return theMap.values();
}
@Override
public Set<Map.Entry<String, String>> entrySet() {
return theMap.entrySet();
}
@Override
public Comparator<? super String> comparator() {
return theMap.comparator();
}
@Override
public SortedMap<String, String> subMap(String fromKey, String toKey) {
return theMap.subMap(fromKey, toKey);
}
@Override
public SortedMap<String, String> headMap(String toKey) {
return theMap.headMap(toKey);
}
@Override
public SortedMap<String, String> tailMap(String fromKey) {
return theMap.tailMap(fromKey);
}
@Override
public String firstKey() {
return theMap.firstKey();
}
@Override
public String lastKey() {
return theMap.lastKey();
}
/**
* A class that provides access to the java.beans.PropertyChangeListener
* and java.beans.PropertyChangeEvent without creating a static dependency
* on java.beans. This class can be removed once the addPropertyChangeListener
* and removePropertyChangeListener methods are removed from Packer and
* Unpacker.
*/
private static class Beans {
private static final Class<?> propertyChangeListenerClass =
getClass("java.beans.PropertyChangeListener");
private static final Class<?> propertyChangeEventClass =
getClass("java.beans.PropertyChangeEvent");
private static final Method propertyChangeMethod =
getMethod(propertyChangeListenerClass,
"propertyChange",
propertyChangeEventClass);
private static final Constructor<?> propertyEventCtor =
getConstructor(propertyChangeEventClass,
Object.class,
String.class,
Object.class,
Object.class);
private static Class<?> getClass(String name) {
try {
return Class.forName(name, true, Beans.class.getClassLoader());
} catch (ClassNotFoundException e) {
return null;
}
}
private static Constructor<?> getConstructor(Class<?> c, Class<?>... types) {
try {
return (c == null) ? null : c.getDeclaredConstructor(types);
} catch (NoSuchMethodException x) {
throw new AssertionError(x);
}
}
private static Method getMethod(Class<?> c, String name, Class<?>... types) {
try {
return (c == null) ? null : c.getMethod(name, types);
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
/**
* Returns {@code true} if java.beans is present.
*/
static boolean isBeansPresent() {
return propertyChangeListenerClass != null &&
propertyChangeEventClass != null;
}
/**
* Returns {@code true} if the given object is a PropertyChangeListener
*/
static boolean isPropertyChangeListener(Object obj) {
if (propertyChangeListenerClass == null) {
return false;
} else {
return propertyChangeListenerClass.isInstance(obj);
}
}
/**
* Returns a new PropertyChangeEvent with the given source, property
* name, old and new values.
*/
static Object newPropertyChangeEvent(Object source, String prop,
Object oldValue, Object newValue)
{
try {
return propertyEventCtor.newInstance(source, prop, oldValue, newValue);
} catch (InstantiationException | IllegalAccessException x) {
throw new AssertionError(x);
} catch (InvocationTargetException x) {
Throwable cause = x.getCause();
if (cause instanceof Error)
throw (Error)cause;
if (cause instanceof RuntimeException)
throw (RuntimeException)cause;
throw new AssertionError(x);
}
}
/**
* Invokes the given PropertyChangeListener's propertyChange method
* with the given event.
*/
static void invokePropertyChange(Object listener, Object ev) {
try {
propertyChangeMethod.invoke(listener, ev);
} catch (IllegalAccessException x) {
throw new AssertionError(x);
} catch (InvocationTargetException x) {
Throwable cause = x.getCause();
if (cause instanceof Error)
throw (Error)cause;
if (cause instanceof RuntimeException)
throw (RuntimeException)cause;
throw new AssertionError(x);
}
}
}
}
@@ -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 com.sun.java.util.jar.pack;
import com.sun.java.util.jar.pack.ConstantPool.ClassEntry;
import com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry;
import com.sun.java.util.jar.pack.ConstantPool.LiteralEntry;
import com.sun.java.util.jar.pack.ConstantPool.MemberEntry;
import com.sun.java.util.jar.pack.ConstantPool.MethodHandleEntry;
import com.sun.java.util.jar.pack.ConstantPool.MethodTypeEntry;
import com.sun.java.util.jar.pack.ConstantPool.InvokeDynamicEntry;
import com.sun.java.util.jar.pack.ConstantPool.BootstrapMethodEntry;
import com.sun.java.util.jar.pack.ConstantPool.SignatureEntry;
import com.sun.java.util.jar.pack.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<String, Utf8Entry> utf8Entries;
private final Map<String, ClassEntry> classEntries;
private final Map<Object, LiteralEntry> literalEntries;
private final Map<String, SignatureEntry> signatureEntries;
private final Map<String, DescriptorEntry> descriptorEntries;
private final Map<String, MemberEntry> memberEntries;
private final Map<String, MethodHandleEntry> methodHandleEntries;
private final Map<String, MethodTypeEntry> methodTypeEntries;
private final Map<String, InvokeDynamicEntry> invokeDynamicEntries;
private final Map<String, BootstrapMethodEntry> 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<String, String> getPropMap() {
return props;
}
Map<String, Utf8Entry> getUtf8Entries() {
return utf8Entries;
}
Map<String, ClassEntry> getClassEntries() {
return classEntries;
}
Map<Object, LiteralEntry> getLiteralEntries() {
return literalEntries;
}
Map<String, DescriptorEntry> getDescriptorEntries() {
return descriptorEntries;
}
Map<String, SignatureEntry> getSignatureEntries() {
return signatureEntries;
}
Map<String, MemberEntry> getMemberEntries() {
return memberEntries;
}
Map<String, MethodHandleEntry> getMethodHandleEntries() {
return methodHandleEntries;
}
Map<String, MethodTypeEntry> getMethodTypeEntries() {
return methodTypeEntries;
}
Map<String, InvokeDynamicEntry> getInvokeDynamicEntries() {
return invokeDynamicEntries;
}
Map<String, BootstrapMethodEntry> getBootstrapMethodEntries() {
return bootstrapMethodEntries;
}
}
@@ -0,0 +1,295 @@
/*
* 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 com.sun.java.util.jar.pack;
import java.beans.PropertyChangeListener;
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.util.HashSet;
import java.util.Set;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
/*
* Implementation of the Pack provider.
* </pre></blockquote>
* @author John Rose
* @author Kumar Srinivasan
*/
public class UnpackerImpl extends TLGlobals implements Pack200.Unpacker {
/**
* Register a listener for changes to options.
* @param listener An object to be invoked when a property is changed.
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
props.addListener(listener);
}
/**
* Remove a listener for the PropertyChange event.
* @param listener The PropertyChange listener to be removed.
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
props.removeListener(listener);
}
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<String, String> 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.
* <p>
* 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);
boolean needUTC = !props.getBoolean(Utils.PACK_DEFAULT_TIMEZONE);
try {
Utils.currentInstance.set(this);
if (needUTC) {
Utils.changeDefaultTimeZoneToUtc();
}
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);
} finally {
if (_nunp != null) {
// Free up native memory and JNI handles to prevent leaks
((NativeUnpack) _nunp).finish();
}
}
in0.close();
Utils.markJarFile(out);
}
} finally {
_nunp = null;
Utils.currentInstance.set(null);
if (needUTC) {
Utils.restoreDefaultTimeZone();
}
}
}
/**
* Takes an input File containing the pack file, and generates a JarOutputStream.
* <p>
* 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(java.util.jar.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(java.util.jar.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(java.util.jar.Pack200.Unpacker.PROGRESS,"50");
pkg.ensureAllClassFiles();
// Now write out the files.
Set<Package.Class> 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) {
je.setTime(file.modtime);
// Convert back to milliseconds
je.setTime((long)file.modtime * 1000);
} 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(java.util.jar.Pack200.Unpacker.PROGRESS,"100");
pkg.reset(); // reset for the next segment, if any
}
}
}
@@ -0,0 +1,351 @@
/*
* 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 com.sun.java.util.jar.pack;
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.TimeZone;
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 = "com.sun.java.util.jar.pack.";
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";
/*
* Use the default working TimeZone instead of UTC.
* Note: This has installer unpacker implications.
* see: zip.cpp which uses gmtime vs. localtime.
*/
static final String PACK_DEFAULT_TIMEZONE = COM_PREFIX+"default.timezone";
/*
* 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.
* <p>
* 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<TLGlobals> currentInstance = new ThreadLocal<>();
private static TimeZone tz;
private static int workingPackerCount = 0;
// 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 synchronized void changeDefaultTimeZoneToUtc() {
if (workingPackerCount++ == 0) {
// only first thread saves default TZ
tz = TimeZone.getDefault();
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
}
static synchronized void restoreDefaultTimeZone() {
if (--workingPackerCount == 0) {
// reset timezone when all the packer/unpacker instances have terminated
if (tz != null) {
TimeZone.setDefault(tz);
}
tz = null;
}
}
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; ) {
je.setCompressedSize(-1);
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())) {
je.setCompressedSize(-1);
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.
static private
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
}