org.wildfly.common.archive.Archive Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
package org.wildfly.common.archive;
import static java.lang.Math.min;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import org.wildfly.common.Assert;
/**
*/
public final class Archive implements Closeable {
public static final int GP_ENCRYPTED = 1 << 0;
// only if method == implode
public static final int GP_IMPLODE_8K_DICTIONARY = 1 << 1;
public static final int GP_IMPLODE_3_TREES = 1 << 2;
// only if method == deflate
public static final int GP_DEFLATE_COMP_OPT_MASK = 0b11 << 1;
public static final int GP_DEFLATE_COMP_OPT_NORMAL = 0b00 << 1;
public static final int GP_DEFLATE_COMP_OPT_MAXIMUM = 0b01 << 1;
public static final int GP_DEFLATE_COMP_OPT_FAST = 0b10 << 1;
public static final int GP_DEFLATE_COMP_OPT_SUPER_FAST = 0b11 << 1;
// only if method == lzma
public static final int GP_LZMA_EOS_USED = 1 << 1;
public static final int GP_LATE_SIZES = 1 << 3;
// reserved 1 << 4
public static final int GP_COMPRESSED_PATCHED = 1 << 5;
public static final int GP_STRONG_ENCRYPTION = 1 << 6;
// reserved 1 << 7
// reserved 1 << 8
// reserved 1 << 9
// reserved 1 << 10
public static final int GP_UTF_8 = 1 << 11;
// reserved 1 << 12
public static final int GP_CD_MASKED = 1 << 13;
// reserved 1 << 14
// reserved 1 << 15
public static final int METHOD_STORED = 0;
public static final int METHOD_SHRINK = 1;
public static final int METHOD_REDUCE_1 = 2;
public static final int METHOD_REDUCE_2 = 3;
public static final int METHOD_REDUCE_3 = 4;
public static final int METHOD_REDUCE_4 = 5;
public static final int METHOD_IMPLODE = 6;
public static final int METHOD_DEFLATE = 8;
public static final int METHOD_DEFLATE64 = 9;
public static final int METHOD_BZIP2 = 12;
public static final int METHOD_LZMA = 14;
public static final int MADE_BY_MS_DOS = 0;
public static final int MADE_BY_UNIX = 3;
public static final int MADE_BY_NTFS = 10;
public static final int MADE_BY_OS_X = 19;
public static final int SIG_LH = 0x04034b50;
public static final int LH_SIGNATURE = 0;
public static final int LH_MIN_VERSION = 4;
public static final int LH_GP_BITS = 6;
public static final int LH_COMP_METHOD = 8;
public static final int LH_MOD_TIME = 10;
public static final int LH_MOD_DATE = 12;
public static final int LH_CRC_32 = 14;
public static final int LH_COMPRESSED_SIZE = 18;
public static final int LH_UNCOMPRESSED_SIZE = 22;
public static final int LH_FILE_NAME_LENGTH = 26;
public static final int LH_EXTRA_LENGTH = 28;
public static final int LH_END = 30;
public static final int SIG_DD = 0x08074b50;
public static final int DD_SIGNATURE = 0;
public static final int DD_CRC_32 = 4;
public static final int DD_COMPRESSED_SIZE = 8;
public static final int DD_UNCOMPRESSED_SIZE = 12;
public static final int DD_END = 16;
public static final int DD_ZIP64_COMPRESSED_SIZE = 8;
public static final int DD_ZIP64_UNCOMPRESSED_SIZE = 16;
public static final int DD_ZIP64_END = 24;
public static final int SIG_CDE = 0x02014b50;
public static final int CDE_SIGNATURE = 0;
public static final int CDE_VERSION_MADE_BY = 4;
public static final int CDE_VERSION_NEEDED = 6;
public static final int CDE_GP_BITS = 8;
public static final int CDE_COMP_METHOD = 10;
public static final int CDE_MOD_TIME = 12;
public static final int CDE_MOD_DATE = 14;
public static final int CDE_CRC_32 = 16;
public static final int CDE_COMPRESSED_SIZE = 20;
public static final int CDE_UNCOMPRESSED_SIZE = 24;
public static final int CDE_FILE_NAME_LENGTH = 28;
public static final int CDE_EXTRA_LENGTH = 30;
public static final int CDE_COMMENT_LENGTH = 32;
public static final int CDE_FIRST_DISK_NUMBER = 34;
public static final int CDE_INTERNAL_ATTRIBUTES = 36;
public static final int CDE_EXTERNAL_ATTRIBUTES = 38;
public static final int CDE_LOCAL_HEADER_OFFSET = 42; // relative to the start of the above first disk number
public static final int CDE_END = 46;
public static final int SIG_EOCD = 0x06054b50;
public static final int EOCD_SIGNATURE = 0;
public static final int EOCD_DISK_NUMBER = 4;
public static final int EOCD_CD_FIRST_DISK_NUMBER = 6;
public static final int EOCD_CDE_COUNT_THIS_DISK = 8;
public static final int EOCD_CDE_COUNT_ALL = 10;
public static final int EOCD_CD_SIZE = 12;
public static final int EOCD_CD_START_OFFSET = 16;
public static final int EOCD_COMMENT_LENGTH = 20;
public static final int EOCD_END = 22;
public static final int EXT_ID_ZIP64 = 0x0001;
public static final int ZIP64_UNCOMPRESSED_SIZE = 0;
public static final int ZIP64_COMPRESSED_SIZE = 8;
public static final int ZIP64_LOCAL_HEADER_OFFSET = 16;
public static final int ZIP64_FIRST_DISK_NUMBER = 24; // 4 bytes
public static final int ZIP64_END = 28;
public static final int EXT_ID_UNIX = 0x000d;
public static final int UNIX_ACCESS_TIME = 0;
public static final int UNIX_MODIFIED_TIME = 4;
public static final int UNIX_UID = 8;
public static final int UNIX_GID = 10;
public static final int UNIX_END = 12; // symlink target
public static final int UNIX_DEV_MAJOR = 12; // if it's a device node
public static final int UNIX_DEV_MINOR = 16;
public static final int UNIX_DEV_END = 20;
public static final int SIG_EOCD_ZIP64 = 0x06064b50;
public static final int EOCD_ZIP64_SIGNATURE = 0;
public static final int EOCD_ZIP64_SIZE = 4;
public static final int EOCD_ZIP64_VERSION_MADE_BY = 12;
public static final int EOCD_ZIP64_VERSION_NEEDED = 14;
public static final int EOCD_ZIP64_DISK_NUMBER = 16;
public static final int EOCD_ZIP64_CD_FIRST_DISK_NUMBER = 20;
public static final int EOCD_ZIP64_CDE_COUNT_THIS_DISK = 24;
public static final int EOCD_ZIP64_CDE_COUNT_ALL = 28;
public static final int EOCD_ZIP64_CD_SIZE = 36;
public static final int EOCD_ZIP64_CD_START_OFFSET = 44;
public static final int EOCD_ZIP64_END = 52;
public static final int SIG_EOCDL_ZIP64 = 0x07064b50;
public static final int EOCDL_ZIP64_SIGNATURE = 0;
public static final int EOCDL_ZIP64_EOCD_DISK_NUMBER = 4;
public static final int EOCDL_ZIP64_EOCD_OFFSET = 8;
public static final int EOCDL_ZIP64_DISK_COUNT = 16;
public static final int EOCDL_ZIP64_END = 20;
/**
* Maximum size of a single buffer.
*/
private static final int BUF_SIZE_MAX = 0x4000_0000;
private static final int BUF_SHIFT = Integer.numberOfTrailingZeros(BUF_SIZE_MAX);
private static final int BUF_SIZE_MASK = BUF_SIZE_MAX - 1;
private final ByteBuffer[] bufs;
private final long offset;
private final long length;
private final long cd;
private final Index index;
private Archive(final ByteBuffer[] bufs, final long offset, final long length, final long cd, final Index index) {
this.bufs = bufs;
this.offset = offset;
this.length = length;
this.cd = cd;
this.index = index;
}
public static Archive open(Path path) throws IOException {
try (FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)) {
long size = fc.size();
final int bufCnt = Math.toIntExact((size + BUF_SIZE_MASK) >> BUF_SHIFT);
final ByteBuffer[] array = new ByteBuffer[bufCnt];
long offs = 0;
int idx = 0;
while (size > BUF_SIZE_MASK) {
array[idx++] = fc.map(FileChannel.MapMode.READ_ONLY, offs, BUF_SIZE_MAX).order(ByteOrder.LITTLE_ENDIAN);
size -= BUF_SIZE_MAX;
offs += BUF_SIZE_MAX;
}
array[idx] = fc.map(FileChannel.MapMode.READ_ONLY, offs, size).order(ByteOrder.LITTLE_ENDIAN);
return open(array);
}
}
public static Archive open(ByteBuffer buf) throws IOException {
Assert.checkNotNullParam("buf", buf);
if (buf.order() == ByteOrder.BIG_ENDIAN) {
buf = buf.duplicate().order(ByteOrder.LITTLE_ENDIAN);
}
return open(new ByteBuffer[] { buf });
}
static Archive open(ByteBuffer[] bufs) throws IOException {
return open(bufs, 0, capacity(bufs));
}
static Archive open(ByteBuffer[] bufs, long offset, long length) throws IOException {
// find the directory by looking first at its expected location and working our way backwards
long eocd = length - EOCD_END;
// todo: this could be optimized a bit (boyer-moore for example)
while (getUnsignedInt(bufs, offset + eocd) != SIG_EOCD) {
if (eocd == 0) {
throw new IOException("Invalid archive");
}
eocd--;
}
int entries = getUnsignedShort(bufs, offset + eocd + EOCD_CDE_COUNT_ALL);
// validate the EOCD record
if (getUnsignedShort(bufs, offset + eocd + EOCD_CD_FIRST_DISK_NUMBER) != 0 || getUnsignedShort(bufs, offset + eocd + EOCD_DISK_NUMBER) != 0 || entries != getUnsignedShort(bufs, offset + eocd + EOCD_CDE_COUNT_THIS_DISK)) {
throw new IOException("Multi-disk archives are not supported");
}
// check now for zip64
long eocdLocZip64 = eocd - EOCDL_ZIP64_END;
long eocdZip64 = -1;
if (getInt(bufs, offset + eocdLocZip64 + EOCDL_ZIP64_SIGNATURE) == SIG_EOCDL_ZIP64) {
// probably zip64
// validate the EOCDL_ZIP64
if (getInt(bufs, offset + eocdLocZip64 + EOCDL_ZIP64_DISK_COUNT) != 1 || getInt(bufs, offset + eocdLocZip64 + EOCDL_ZIP64_EOCD_DISK_NUMBER) != 0) {
throw new IOException("Multi-disk archives are not supported");
}
eocdZip64 = getLong(bufs, offset + eocdLocZip64 + EOCDL_ZIP64_EOCD_OFFSET);
if (getUnsignedInt(bufs, offset + eocdZip64 + EOCD_ZIP64_SIGNATURE) != SIG_EOCD_ZIP64) {
// oops, zip64 isn't really present, just bad luck
eocdZip64 = -1;
}
}
long cd;
cd = getUnsignedInt(bufs, offset + eocd + EOCD_CD_START_OFFSET);
if (cd == 0xffffffffL && eocdZip64 != -1) {
cd = getLong(bufs, offset + eocdZip64 + EOCD_ZIP64_CD_START_OFFSET);
}
if (entries == 0xffff && eocdZip64 != -1) {
final long cnt = getUnsignedInt(bufs, offset + eocdZip64 + EOCD_ZIP64_CDE_COUNT_ALL);
if (cnt > 0x07ff_ffffL) {
throw new IOException("Archive has too many entries");
}
entries = (int) cnt;
}
// generate the index
Index index;
if (length <= 0xfffe) {
index = new TinyIndex(entries);
} else if (length <= 0xffff_ffffeL) {
index = new LargeIndex(entries);
} else {
index = new HugeIndex(entries);
}
// iterate the directory
final int mask = index.getMask();
long cde = cd;
for (int i = 0; i < entries; i ++) {
if (getInt(bufs, offset + cde + CDE_SIGNATURE) != SIG_CDE) {
throw new IOException("Archive appears to be corrupted");
}
int hc = getHashCodeOfEntry(bufs, offset + cde);
index.put(hc & mask, cde);
cde = cde + CDE_END + getUnsignedShort(bufs, offset + cde + CDE_FILE_NAME_LENGTH) + getUnsignedShort(bufs, offset + cde + CDE_EXTRA_LENGTH) + getUnsignedShort(bufs, offset + cde + CDE_COMMENT_LENGTH);
}
return new Archive(bufs, offset, length, cd, index);
}
private static String getNameOfEntry(ByteBuffer[] bufs, long cde) {
long name = cde + CDE_END;
int nameLen = getUnsignedShort(bufs, cde + CDE_FILE_NAME_LENGTH);
boolean utf8 = (getUnsignedShort(bufs, cde + CDE_GP_BITS) & GP_UTF_8) != 0;
if (utf8) {
return new String(getBytes(bufs, name, nameLen), StandardCharsets.UTF_8);
} else {
char[] nameChars = new char[nameLen];
for (int i = 0; i < nameLen; i ++) {
nameChars[i] = Cp437.charFor(getUnsignedByte(bufs, name + i));
}
return new String(nameChars);
}
}
private static int getHashCodeOfEntry(ByteBuffer[] bufs, long cde) {
long name = cde + CDE_END;
int nameLen = getUnsignedShort(bufs, cde + CDE_FILE_NAME_LENGTH);
boolean utf8 = (getUnsignedShort(bufs, cde + CDE_GP_BITS) & GP_UTF_8) != 0;
int hc = 0;
if (utf8) {
int cp;
for (int i = 0; i < nameLen; i += Utf8.getByteCount(getUnsignedByte(bufs, name + i))) {
cp = Utf8.codePointAt(bufs, name + i);
if (Character.isSupplementaryCodePoint(cp)) {
hc = hc * 31 + Character.highSurrogate(cp);
hc = hc * 31 + Character.lowSurrogate(cp);
} else {
hc = hc * 31 + cp;
}
}
} else {
for (int i = 0; i < nameLen; i ++) {
hc = hc * 31 + Cp437.charFor(getUnsignedByte(bufs, name + i));
}
}
return hc;
}
public long getFirstEntryHandle() {
return cd;
}
public long getNextEntryHandle(long entryHandle) {
final long next = entryHandle + CDE_END + getUnsignedShort(bufs, offset + entryHandle + CDE_FILE_NAME_LENGTH) + getUnsignedShort(bufs, offset + entryHandle + CDE_EXTRA_LENGTH);
if (next >= length || getInt(bufs, offset + next + CDE_SIGNATURE) != SIG_CDE) {
return -1;
}
return next;
}
public long getEntryHandle(String fileName) {
final int mask = index.getMask();
final int base = fileName.hashCode();
long entryHandle;
for (int i = 0; i < mask; i ++) {
entryHandle = index.get(base + i & mask);
if (entryHandle == -1) {
return -1;
}
if (entryNameEquals(entryHandle, fileName)) {
return entryHandle;
}
}
return -1;
}
public boolean entryNameEquals(final long entryHandle, final String fileName) {
long name = entryHandle + CDE_END;
int nameLen = getUnsignedShort(bufs, offset + entryHandle + CDE_FILE_NAME_LENGTH);
boolean utf8 = (getUnsignedShort(bufs, offset + entryHandle + CDE_GP_BITS) & GP_UTF_8) != 0;
final int length = fileName.length();
if (utf8) {
long i;
int j;
for (i = 0, j = 0; i < nameLen && j < length; i += Utf8.getByteCount(getUnsignedByte(bufs, offset + name + i)), j = fileName.offsetByCodePoints(j, 1)) {
if (Utf8.codePointAt(bufs, offset + name + i) != fileName.codePointAt(j)) {
return false;
}
}
return i == nameLen && j == length;
} else {
int i, j;
for (i = 0, j = 0; i < nameLen && j < length; i++, j = fileName.offsetByCodePoints(j, 1)) {
if (Cp437.charFor(getUnsignedByte(bufs, offset + i + entryHandle + CDE_END)) != fileName.codePointAt(j)) {
return false;
}
}
return i == nameLen && j == length;
}
}
private long getLocalHeader(long entryHandle) {
long lh = getUnsignedInt(bufs, offset + entryHandle + CDE_LOCAL_HEADER_OFFSET);
if (lh == 0xffff_ffffL) {
long zip64 = getExtraRecord(entryHandle, EXT_ID_ZIP64);
if (zip64 != -1) {
lh = getLong(bufs, offset + zip64 + ZIP64_LOCAL_HEADER_OFFSET);
}
}
return lh;
}
public String getEntryName(long entryHandle) {
return getNameOfEntry(bufs, entryHandle);
}
public ByteBuffer getEntryContents(long entryHandle) throws IOException {
long size = getUncompressedSize(entryHandle);
long compSize = getCompressedSize(entryHandle);
if (size > 0x1000_0000 || compSize > 0x1000_0000) {
throw new IOException("Entry is too large to read into RAM");
}
long localHeader = getLocalHeader(entryHandle);
if ((getUnsignedShort(bufs, offset + localHeader + LH_GP_BITS) & (GP_ENCRYPTED | GP_STRONG_ENCRYPTION)) != 0) {
throw new IOException("Cannot read encrypted entries");
}
final long offset = getDataOffset(localHeader);
final int method = getCompressionMethod(entryHandle);
switch (method) {
case METHOD_STORED: {
return bufferOf(bufs, this.offset + offset, (int) size);
}
case METHOD_DEFLATE: {
final Inflater inflater = new Inflater(true);
try {
return JDKSpecific.inflate(inflater, bufs, this.offset + offset, (int) compSize, (int) size);
} catch (DataFormatException e) {
throw new IOException(e);
} finally {
inflater.end();
}
}
default: {
throw new IOException("Unsupported compression scheme");
}
}
}
private long getDataOffset(final long localHeader) {
return localHeader + LH_END + getUnsignedShort(bufs, offset + localHeader + LH_FILE_NAME_LENGTH) + getUnsignedShort(bufs, offset + localHeader + LH_EXTRA_LENGTH);
}
public InputStream getEntryStream(final long entryHandle) throws IOException {
long size = getCompressedSize(entryHandle);
long localHeader = getLocalHeader(entryHandle);
if ((getUnsignedShort(bufs, offset + localHeader + LH_GP_BITS) & (GP_ENCRYPTED | GP_STRONG_ENCRYPTION)) != 0) {
throw new IOException("Cannot read encrypted entries");
}
final long offset = getDataOffset(localHeader);
final int method = getCompressionMethod(entryHandle);
switch (method) {
case METHOD_STORED: {
return new ByteBufferInputStream(bufs, this.offset + offset, size);
}
case METHOD_DEFLATE: {
return new InflaterInputStream(new ByteBufferInputStream(bufs, this.offset + offset, size));
}
default: {
throw new IOException("Unsupported compression scheme");
}
}
}
public Archive getNestedArchive(long entryHandle) throws IOException {
long localHeader = getLocalHeader(entryHandle);
if ((getUnsignedShort(bufs, this.offset + localHeader + LH_GP_BITS) & (GP_ENCRYPTED | GP_STRONG_ENCRYPTION)) != 0) {
throw new IOException("Cannot read encrypted entries");
}
final long offset = getDataOffset(localHeader);
final int method = getCompressionMethod(entryHandle);
if (method != METHOD_STORED) {
throw new IOException("Cannot open compressed nested archive");
}
long size = getUncompressedSize(entryHandle);
if (size < Integer.MAX_VALUE) {
final ByteBuffer slice = sliceOf(bufs, this.offset + offset, (int) size);
if (slice != null) {
return Archive.open(slice);
}
}
return Archive.open(bufs, this.offset + offset, size);
}
public boolean isCompressed(long entryHandle) {
return getCompressionMethod(entryHandle) != METHOD_STORED;
}
private int getCompressionMethod(final long entryHandle) {
return getUnsignedShort(bufs, offset + entryHandle + CDE_COMP_METHOD);
}
private long getExtraRecord(final long entryHandle, final int headerId) {
long extra = entryHandle + CDE_END + getUnsignedShort(bufs, offset + entryHandle + CDE_FILE_NAME_LENGTH) + getUnsignedShort(bufs, offset + entryHandle + CDE_COMMENT_LENGTH);
int extraLen = getUnsignedShort(bufs, offset + entryHandle + CDE_EXTRA_LENGTH);
for (int i = 0; i < extraLen; i = i + getUnsignedShort(bufs, offset + extra + i + 2)) {
if (getUnsignedShort(bufs, offset + extra + i) == headerId) {
return extra + i + 4;
}
}
return -1;
}
public long getUncompressedSize(long entryHandle) {
long size = getUnsignedInt(bufs, offset + entryHandle + CDE_UNCOMPRESSED_SIZE);
if (size == 0xffffffff) {
long zip64 = getExtraRecord(entryHandle, EXT_ID_ZIP64);
if (zip64 != -1) {
size = getLong(bufs, offset + zip64 + ZIP64_UNCOMPRESSED_SIZE);
}
}
return size;
}
public long getCompressedSize(long entryHandle) {
long size = getUnsignedInt(bufs, offset + entryHandle + CDE_COMPRESSED_SIZE);
if (size == 0xffffffff) {
long zip64 = getExtraRecord(entryHandle, EXT_ID_ZIP64);
if (zip64 != -1) {
size = getLong(bufs, offset + zip64 + ZIP64_COMPRESSED_SIZE);
}
}
return size;
}
public long getModifiedTime(long entryHandle) {
long unix = getExtraRecord(entryHandle, EXT_ID_UNIX);
if (unix != -1) {
long unixTime = getUnsignedInt(bufs, offset + unix + UNIX_MODIFIED_TIME);
if (unixTime != 0) {
return unixTime * 1000;
}
}
// todo: NTFS (0x000a) sub-tag ID 0x0001 contains an 8-byte mtime
return dosTimeStamp(getUnsignedShort(bufs, offset + entryHandle + CDE_MOD_TIME), getUnsignedShort(bufs, offset + entryHandle + CDE_MOD_DATE));
}
public void close() {
}
private static long dosTimeStamp(int modTime, int modDate) {
int year = 1980 + (modDate >> 9);
int month = 1 + ((modDate >> 5) & 0b1111);
int day = modDate & 0b11111;
int hour = modTime >> 11;
int minute = (modTime >> 5) & 0b111111;
int second = (modTime & 0b11111) << 1;
return LocalDateTime.of(year, month, day, hour, minute, second).toInstant(ZoneOffset.UTC).toEpochMilli();
}
public boolean isDirectory(final long entryHandle) {
final int madeBy = getUnsignedShort(bufs, offset + entryHandle + CDE_VERSION_MADE_BY);
final int extAttr = getInt(bufs, entryHandle + CDE_EXTERNAL_ATTRIBUTES);
switch (madeBy) {
case MADE_BY_UNIX: {
//noinspection OctalInteger
return (extAttr & 0170000) == 0040000;
}
default: {
return (extAttr & 0b10000) != 0;
}
}
}
/**
* Only loaded if CP-437 zip entries exist, which is unlikely but allowed.
*/
static final class Cp437 {
static final char[] codePoints = {
'\0','☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '◙', '♂', '♀', '♪', '♫', '☼',
'►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼',
' ', '!', '"', '#', '$', '%', '&', '\'','(', ')', '*', '+', ',', '-', '.', '/',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\',']', '^', '_',
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '⌂',
'Ç', 'ü', 'é', 'â', 'ä', 'à', 'å', 'ç', 'ê', 'ë', 'è', 'ï', 'î', 'ì', 'Ä', 'Å',
'É', 'æ', 'Æ', 'ô', 'ö', 'ò', 'û', 'ù', 'ÿ', 'Ö', 'Ü', '¢', '£', '¥', '₧', 'ƒ',
'á', 'í', 'ó', 'ú', 'ñ', 'Ñ', 'ª', 'º', '¿', '⌐', '¬', '½', '¼', '¡', '«', '»',
'░', '▒', '▓', '│', '┤', '╡', '╢', '╖', '╕', '╣', '║', '╗', '╝', '╜', '╛', '┐',
'└', '┴', '┬', '├', '─', '┼', '╞', '╟', '╚', '╔', '╩', '╦', '╠', '═', '╬', '╧',
'╨', '╤', '╥', '╙', '╘', '╒', '╓', '╫', '╪', '┘', '┌', '█', '▄', '▌', '▐', '▀',
'α', 'ß', 'Γ', 'π', 'Σ', 'σ', 'µ', 'τ', 'Φ', 'Θ', 'Ω', 'δ', '∞', 'φ', 'ε', '∩',
'≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', 0xA0
};
private Cp437() {}
static char charFor(int c) {
return codePoints[c];
}
}
static final class Utf8 {
private Utf8() {}
static int getByteCount(final int a) {
if (a <= 0b0111_1111) {
return 1;
} else if (a <= 0b1011_1111) {
// invalid garbage (1 byte)
return 1;
} else if (a <= 0b1101_1111) {
return 2;
} else if (a <= 0b1110_1111) {
return 3;
} else if (a <= 0b1111_0111) {
return 4;
} else {
// invalid garbage (1 byte)
return 1;
}
}
static int codePointAt(final ByteBuffer[] bufs, final long i) {
final int a = getUnsignedByte(bufs, i);
if (a <= 0b0111_1111) {
return a;
} else if (a <= 0b1011_1111) {
// invalid garbage (1 byte)
return '�';
}
int b = getUnsignedByte(bufs, i + 1);
if ((b & 0b11_000000) != 0b10_000000) {
// second byte is invalid; return � instead
return '�';
}
if (a <= 0b1101_1111) {
// two bytes
return (a & 0b000_11111) << 6 | b & 0b00_111111;
}
int c = getUnsignedByte(bufs, i + 2);
if ((c & 0b11_000000) != 0b10_000000) {
// third byte is invalid; return � instead
return '�';
}
if (a <= 0b1110_1111) {
// three bytes
return (a & 0b0000_1111) << 12 | (b & 0b00_111111) << 6 | c & 0b00_111111;
}
int d = getUnsignedByte(bufs, i + 3);
if ((d & 0b11_000000) != 0b10_000000) {
// fourth byte is invalid; return � instead
return '�';
}
if (a <= 0b1111_0111) {
// four bytes
return (a & 0b00000_111) << 18 | (b & 0b00_111111) << 12 | (c & 0b00_111111) << 6 | d & 0b00_111111;
}
// invalid garbage (1 byte)
return '�';
}
}
static int bufIdx(long idx) {
return (int) (idx >>> BUF_SHIFT);
}
static int bufOffs(long idx) {
return ((int)idx) & BUF_SIZE_MASK;
}
static byte getByte(ByteBuffer[] bufs, long idx) {
return bufs[bufIdx(idx)].get(bufOffs(idx));
}
static int getUnsignedByte(ByteBuffer[] bufs, long idx) {
return getByte(bufs, idx) & 0xff;
}
static int getUnsignedByte(ByteBuffer buf, int idx) {
return buf.get(idx) & 0xff;
}
static short getShort(ByteBuffer[] bufs, long idx) {
final int bi = bufIdx(idx);
return bi == bufIdx(idx + 1) ? bufs[bi].getShort(bufOffs(idx)) : (short) (getUnsignedByte(bufs, idx) | getByte(bufs, idx + 1) << 8);
}
static int getUnsignedShort(ByteBuffer[] bufs, long idx) {
return getShort(bufs, idx) & 0xffff;
}
static int getMedium(ByteBuffer[] bufs, long idx) {
return getUnsignedByte(bufs, idx) | getUnsignedShort(bufs, idx + 1) << 8;
}
static long getUnsignedMedium(ByteBuffer[] bufs, long idx) {
return getUnsignedByte(bufs, idx) | getUnsignedShort(bufs, idx + 1) << 8;
}
static int getInt(ByteBuffer[] bufs, long idx) {
final int bi = bufIdx(idx);
return bi == bufIdx(idx + 3) ? bufs[bi].getInt(bufOffs(idx)) : getUnsignedShort(bufs, idx) | getShort(bufs, idx + 2) << 16;
}
static long getUnsignedInt(ByteBuffer[] bufs, long idx) {
return getInt(bufs, idx) & 0xffff_ffffL;
}
static long getLong(ByteBuffer[] bufs, long idx) {
final int bi = bufIdx(idx);
return bi == bufIdx(idx + 7) ? bufs[bi].getLong(bufOffs(idx)) : getUnsignedInt(bufs, idx) | (long)getInt(bufs, idx + 4) << 32;
}
static void readBytes(ByteBuffer[] bufs, long idx, byte[] dest, int off, int len) {
while (len > 0) {
final int bi = bufIdx(idx);
final int bo = bufOffs(idx);
ByteBuffer buf = bufs[bi].duplicate();
buf.position(bo);
final int cnt = min(len, buf.remaining());
buf.get(dest, 0, cnt);
len -= cnt;
off += cnt;
idx += cnt;
}
}
static byte[] getBytes(ByteBuffer[] bufs, long idx, int len) {
final byte[] bytes = new byte[len];
readBytes(bufs, idx, bytes, 0, len);
return bytes;
}
private static final ByteBuffer EMPTY_BUF = ByteBuffer.allocateDirect(0);
static ByteBuffer sliceOf(ByteBuffer[] bufs, long idx, int len) {
if (len == 0) return EMPTY_BUF;
final int biStart = bufIdx(idx);
final int biEnd = bufIdx(idx + len - 1);
if (biStart == biEnd) {
final ByteBuffer buf = bufs[biStart].duplicate();
buf.position(bufOffs(idx));
buf.limit(buf.position() + len);
return buf.slice();
} else {
return null;
}
}
static ByteBuffer bufferOf(ByteBuffer[] bufs, long idx, int len) {
ByteBuffer buf = sliceOf(bufs, idx, len);
if (buf == null) {
buf = ByteBuffer.wrap(getBytes(bufs, idx, len));
}
return buf;
}
static long capacity(ByteBuffer[] bufs) {
final int lastIdx = bufs.length - 1;
return ((long) lastIdx) * BUF_SIZE_MAX + bufs[lastIdx].capacity();
}
}