blob: 776a170ec449bd0edcf794086af090d2a6112489 [file] [log] [blame]
/*
* 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));
}
}
}
//*/
}