Add java.util.jar.* classes

This commit is contained in:
Alexey Andreev 2017-11-16 15:11:36 +03:00
parent aa48a097d2
commit e96df3ef7e
9 changed files with 1237 additions and 0 deletions

View File

@ -0,0 +1,234 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.jar;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class TAttributes implements Cloneable, Map<Object, Object> {
protected Map<Object, Object> map;
public static class Name {
private final byte[] name;
private int hashCode;
public static final Name CLASS_PATH = new Name("Class-Path");
public static final Name MANIFEST_VERSION = new Name("Manifest-Version");
public static final Name MAIN_CLASS = new Name("Main-Class");
public static final Name SIGNATURE_VERSION = new Name("Signature-Version");
public static final Name CONTENT_TYPE = new Name("Content-Type");
public static final Name SEALED = new Name("Sealed");
public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title");
public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version");
public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor");
public static final Name SPECIFICATION_TITLE = new Name("Specification-Title");
public static final Name SPECIFICATION_VERSION = new Name("Specification-Version");
public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor");
public static final Name EXTENSION_LIST = new Name("Extension-List");
public static final Name EXTENSION_NAME = new Name("Extension-Name");
public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation");
public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id");
public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL");
static final Name NAME = new Name("Name");
public Name(String s) {
int i = s.length();
if (i == 0 || i > TManifest.LINE_LENGTH_LIMIT - 2) {
throw new IllegalArgumentException();
}
name = new byte[i];
for (; --i >= 0;) {
char ch = s.charAt(i);
if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
|| ch == '_' || ch == '-' || (ch >= '0' && ch <= '9'))) {
throw new IllegalArgumentException(s);
}
name[i] = (byte) ch;
}
}
/**
* A private constructor for a trusted attribute name.
*/
Name(byte[] buf) {
name = buf;
}
byte[] getBytes() {
return name;
}
@Override
public String toString() {
try {
return new String(name, "ISO-8859-1"); //$NON-NLS-1$
} catch (UnsupportedEncodingException iee) {
throw new InternalError(iee.getLocalizedMessage());
}
}
@Override
public boolean equals(Object object) {
if (object == null || object.getClass() != getClass() || object.hashCode() != hashCode()) {
return false;
}
return TJarUtils.asciiEqualsIgnoreCase(name, ((Name) object).name);
}
@Override
public int hashCode() {
if (hashCode == 0) {
int hash = 0;
int multiplier = 1;
for (int i = name.length - 1; i >= 0; i--) {
// 'A' & 0xDF == 'a' & 0xDF, ..., 'Z' & 0xDF == 'z' & 0xDF
hash += (name[i] & 0xDF) * multiplier;
int shifted = multiplier << 5;
multiplier = shifted - multiplier;
}
hashCode = hash;
}
return hashCode;
}
}
public TAttributes() {
map = new HashMap<>();
}
@SuppressWarnings("unchecked")
public TAttributes(TAttributes attrib) {
map = (Map<Object, Object>) ((HashMap<Object, Object>) attrib.map).clone();
}
public TAttributes(int size) {
map = new HashMap<>(size);
}
@Override
public void clear() {
map.clear();
}
@Override
public boolean containsKey(Object key) {
return map.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return map.containsValue(value);
}
@Override
public Set<Entry<Object, Object>> entrySet() {
return map.entrySet();
}
@Override
public Object get(Object key) {
return map.get(key);
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public Set<Object> keySet() {
return map.keySet();
}
@Override
@SuppressWarnings("cast")
// Require cast to force ClassCastException
public Object put(Object key, Object value) {
return map.put(key, value);
}
@Override
public void putAll(Map<?, ?> attrib) {
if (attrib == null || !(attrib instanceof TAttributes)) {
throw new ClassCastException();
}
this.map.putAll(attrib);
}
@Override
public Object remove(Object key) {
return map.remove(key);
}
@Override
public int size() {
return map.size();
}
@Override
public Collection<Object> values() {
return map.values();
}
@SuppressWarnings("unchecked")
@Override
public Object clone() {
TAttributes clone;
try {
clone = (TAttributes) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
clone.map = (Map<Object, Object>) ((HashMap<?, ?>) map).clone();
return clone;
}
@Override
public int hashCode() {
return map.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof TAttributes) {
return map.equals(((TAttributes) obj).map);
}
return false;
}
public String getValue(Name name) {
return (String) map.get(name);
}
public String getValue(String name) {
return (String) map.get(new Name(name));
}
public String putValue(String name, String val) {
return (String) map.put(new Name(name), val);
}
}

View File

@ -0,0 +1,213 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.jar;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.util.Map;
class TInitManifest {
private byte[] buf;
private int pos;
TAttributes.Name name;
String value;
CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
CharBuffer cBuf = CharBuffer.allocate(512);
TInitManifest(byte[] buf, TAttributes main, TAttributes.Name ver) throws IOException {
this.buf = buf;
// check a version attribute
if (!readHeader() || (ver != null && !name.equals(ver))) {
throw new IOException();
}
main.put(name, value);
while (readHeader()) {
main.put(name, value);
}
}
void initEntries(Map<String, TAttributes> entries, Map<String, TManifest.Chunk> chunks) throws IOException {
int mark = pos;
while (readHeader()) {
if (!TAttributes.Name.NAME.equals(name)) {
throw new IOException();
}
String entryNameValue = value;
TAttributes entry = entries.get(entryNameValue);
if (entry == null) {
entry = new TAttributes(12);
}
while (readHeader()) {
entry.put(name, value);
}
if (chunks != null) {
if (chunks.get(entryNameValue) != null) {
// TODO A bug: there might be several verification chunks for
// the same name. I believe they should be used to update
// signature in order of appearance; there are two ways to fix
// this: either use a list of chunks, or decide on used
// signature algorithm in advance and reread the chunks while
// updating the signature; for now a defensive error is thrown
throw new IOException();
}
chunks.put(entryNameValue, new TManifest.Chunk(mark, pos));
mark = pos;
}
entries.put(entryNameValue, entry);
}
}
int getPos() {
return pos;
}
/**
* Number of subsequent line breaks.
*/
int linebreak;
/**
* Read a single line from the manifest buffer.
*/
private boolean readHeader() throws IOException {
if (linebreak > 1) {
// break a section on an empty line
linebreak = 0;
return false;
}
readName();
linebreak = 0;
readValue();
// if the last line break is missed, the line
// is ignored by the reference implementation
return linebreak > 0;
}
private byte[] wrap(int mark, int pos) {
byte[] buffer = new byte[pos - mark];
System.arraycopy(buf, mark, buffer, 0, pos - mark);
return buffer;
}
private void readName() throws IOException {
int i = 0;
int mark = pos;
while (pos < buf.length) {
byte b = buf[pos++];
if (b == ':') {
byte[] nameBuffer = wrap(mark, pos - 1);
if (buf[pos++] != ' ') {
throw new IOException();
}
name = new TAttributes.Name(nameBuffer);
return;
}
if (!((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_'
|| b == '-' || (b >= '0' && b <= '9'))) {
throw new IOException();
}
}
if (i > 0) {
throw new IOException();
}
}
private void readValue() throws IOException {
byte next;
boolean lastCr = false;
int mark = pos;
int last = pos;
decoder.reset();
cBuf.clear();
while (pos < buf.length) {
next = buf[pos++];
switch (next) {
case 0:
throw new IOException();
case '\n':
if (lastCr) {
lastCr = false;
} else {
linebreak++;
}
continue;
case '\r':
lastCr = true;
linebreak++;
continue;
case ' ':
if (linebreak == 1) {
decode(mark, last, false);
mark = pos;
last = mark;
linebreak = 0;
continue;
}
}
if (linebreak >= 1) {
pos--;
break;
}
last = pos;
}
decode(mark, last, true);
while (CoderResult.OVERFLOW == decoder.flush(cBuf)) {
enlargeBuffer();
}
value = new String(cBuf.array(), cBuf.arrayOffset(), cBuf.position());
}
private void decode(int mark, int pos, boolean endOfInput)
throws IOException {
ByteBuffer bBuf = ByteBuffer.wrap(buf, mark, pos - mark);
while (CoderResult.OVERFLOW == decoder.decode(bBuf, cBuf, endOfInput)) {
enlargeBuffer();
}
}
private void enlargeBuffer() {
CharBuffer newBuf = CharBuffer.allocate(cBuf.capacity() * 2);
newBuf.put(cBuf.array(), cBuf.arrayOffset(), cBuf.position());
cBuf = newBuf;
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.jar;
import java.io.IOException;
import org.teavm.classlib.java.util.zip.TZipEntry;
public class TJarEntry extends TZipEntry {
private TAttributes attributes;
TJarFile parentJar;
private boolean isFactoryChecked;
public TJarEntry(String name) {
super(name);
}
public TJarEntry(TZipEntry entry) {
super(entry);
}
public TAttributes getAttributes() throws IOException {
if (attributes != null || parentJar == null) {
return attributes;
}
TManifest manifest = parentJar.getManifest();
if (manifest == null) {
return null;
}
attributes = manifest.getAttributes(getName());
return attributes;
}
void setAttributes(TAttributes attrib) {
attributes = attrib;
}
public TJarEntry(TJarEntry je) {
super(je);
parentJar = je.parentJar;
attributes = je.attributes;
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.jar;
import org.teavm.classlib.java.util.zip.TZipException;
public class TJarException extends TZipException {
public TJarException() {
super();
}
public TJarException(String detailMessage) {
super(detailMessage);
}
}

View File

@ -0,0 +1,250 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.jar;
import java.io.File;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.teavm.classlib.java.util.zip.TZipEntry;
import org.teavm.classlib.java.util.zip.TZipFile;
public class TJarFile extends TZipFile {
public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
static final String META_DIR = "META-INF/";
private TManifest manifest;
private TZipEntry manifestEntry;
private boolean closed;
static final class JarFileInputStream extends FilterInputStream {
private long count;
private TZipEntry zipEntry;
private boolean done;
JarFileInputStream(InputStream is, TZipEntry ze) {
super(is);
zipEntry = ze;
count = zipEntry.getSize();
}
@Override
public int read() throws IOException {
if (done) {
return -1;
}
if (count > 0) {
int r = super.read();
if (r != -1) {
count--;
} else {
count = 0;
}
if (count == 0) {
done = true;
}
return r;
} else {
done = true;
return -1;
}
}
@Override
public int read(byte[] buf, int off, int nbytes) throws IOException {
if (done) {
return -1;
}
if (count > 0) {
int r = super.read(buf, off, nbytes);
if (r != -1) {
int size = r;
if (count < size) {
size = (int) count;
}
count -= size;
} else {
count = 0;
}
if (count == 0) {
done = true;
}
return r;
} else {
done = true;
return -1;
}
}
@Override
public int available() throws IOException {
if (done) {
return 0;
}
return super.available();
}
}
public TJarFile(File file) throws IOException {
this(file, true);
}
public TJarFile(File file, boolean verify) throws IOException {
super(file);
readMetaEntries();
}
public TJarFile(File file, boolean verify, int mode) throws IOException {
super(file, mode);
readMetaEntries();
}
public TJarFile(String filename) throws IOException {
this(filename, true);
}
public TJarFile(String filename, boolean verify) throws IOException {
super(filename);
readMetaEntries();
}
@Override
public Enumeration<TJarEntry> entries() {
class JarFileEnumerator implements Enumeration<TJarEntry> {
Enumeration<? extends TZipEntry> ze;
TJarFile jf;
JarFileEnumerator(Enumeration<? extends TZipEntry> zenum, TJarFile jf) {
ze = zenum;
this.jf = jf;
}
@Override
public boolean hasMoreElements() {
return ze.hasMoreElements();
}
@Override
public TJarEntry nextElement() {
TJarEntry je = new TJarEntry(ze.nextElement());
je.parentJar = jf;
return je;
}
}
return new JarFileEnumerator(super.entries(), this);
}
public TJarEntry getJarEntry(String name) {
return (TJarEntry) getEntry(name);
}
public TManifest getManifest() throws IOException {
if (closed) {
throw new IllegalStateException("JarFile has been closed");
}
if (manifest != null) {
return manifest;
}
try {
InputStream is = super.getInputStream(manifestEntry);
try {
manifest = new TManifest(is, false);
} finally {
is.close();
}
manifestEntry = null; // Can discard the entry now.
} catch (NullPointerException e) {
manifestEntry = null;
}
return manifest;
}
private void readMetaEntries() throws IOException {
// Get all meta directory entries
TZipEntry[] metaEntries = getMetaEntriesImpl();
if (metaEntries == null) {
return;
}
boolean signed = false;
for (TZipEntry entry : metaEntries) {
String entryName = entry.getName();
// Is this the entry for META-INF/MANIFEST.MF ?
if (manifestEntry == null && MANIFEST_NAME.equalsIgnoreCase(entryName)) {
manifestEntry = entry;
break;
}
}
}
/**
* Return an {@code InputStream} for reading the decompressed contents of
* ZIP entry.
*
* @param ze
* the ZIP entry to be read.
* @return the input stream to read from.
* @throws IOException
* if an error occurred while creating the input stream.
*/
@Override
public InputStream getInputStream(TZipEntry ze) throws IOException {
if (manifestEntry != null) {
getManifest();
}
return super.getInputStream(ze);
}
@Override
public TZipEntry getEntry(String name) {
TZipEntry ze = super.getEntry(name);
if (ze == null) {
return ze;
}
TJarEntry je = new TJarEntry(ze);
je.parentJar = this;
return je;
}
private TZipEntry[] getMetaEntriesImpl() {
List<TZipEntry> list = new ArrayList<>(8);
Enumeration<? extends TZipEntry> allEntries = entries();
while (allEntries.hasMoreElements()) {
TZipEntry ze = allEntries.nextElement();
if (ze.getName().startsWith(META_DIR) && ze.getName().length() > META_DIR.length()) {
list.add(ze);
}
}
if (list.size() == 0) {
return null;
}
TZipEntry[] result = new TZipEntry[list.size()];
list.toArray(result);
return result;
}
@Override
public void close() throws IOException {
super.close();
closed = true;
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.jar;
import java.io.IOException;
import java.io.InputStream;
import org.teavm.classlib.java.util.zip.TZipEntry;
import org.teavm.classlib.java.util.zip.TZipInputStream;
public class TJarInputStream extends TZipInputStream {
private TManifest manifest;
private TJarEntry mEntry;
private TJarEntry jarEntry;
public TJarInputStream(InputStream stream, boolean verify) throws IOException {
super(stream);
mEntry = getNextJarEntry();
if (mEntry == null) {
return;
}
String name = mEntry.getName().toUpperCase();
if (name.equals(TJarFile.META_DIR)) {
mEntry = null; // modifies behavior of getNextJarEntry()
closeEntry();
mEntry = getNextJarEntry();
name = mEntry.getName().toUpperCase();
}
if (name.equals(TJarFile.MANIFEST_NAME)) {
mEntry = null;
manifest = new TManifest(this, verify);
closeEntry();
} else {
TAttributes temp = new TAttributes(3);
temp.map.put("hidden", null);
mEntry.setAttributes(temp);
}
}
public TJarInputStream(InputStream stream) throws IOException {
this(stream, true);
}
public TManifest getManifest() {
return manifest;
}
public TJarEntry getNextJarEntry() throws IOException {
return (TJarEntry) getNextEntry();
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
if (mEntry != null) {
return -1;
}
return super.read(buffer, offset, length);
}
@Override
public TZipEntry getNextEntry() throws IOException {
if (mEntry != null) {
jarEntry = mEntry;
mEntry = null;
jarEntry.setAttributes(null);
} else {
jarEntry = (TJarEntry) super.getNextEntry();
if (jarEntry == null) {
return null;
}
}
return jarEntry;
}
@Override
protected TZipEntry createZipEntry(String name) {
TJarEntry entry = new TJarEntry(name);
if (manifest != null) {
entry.setAttributes(manifest.getAttributes(name));
}
return entry;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.jar;
import java.io.IOException;
import java.io.OutputStream;
import org.teavm.classlib.java.util.zip.TZipEntry;
import org.teavm.classlib.java.util.zip.TZipOutputStream;
public class TJarOutputStream extends TZipOutputStream {
private TManifest manifest;
public TJarOutputStream(OutputStream os, TManifest mf) throws IOException {
super(os);
if (mf == null) {
throw new NullPointerException();
}
manifest = mf;
TZipEntry ze = new TZipEntry(TJarFile.MANIFEST_NAME);
putNextEntry(ze);
manifest.write(this);
closeEntry();
}
public TJarOutputStream(OutputStream os) throws IOException {
super(os);
}
@Override
public void putNextEntry(TZipEntry ze) throws IOException {
super.putNextEntry(ze);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.jar;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
final class TJarUtils {
private TJarUtils() {
}
static boolean asciiEqualsIgnoreCase(byte[] a, byte[] b) {
if (a.length != b.length) {
return false;
}
for (int i = 0; i < a.length; ++i) {
if (Character.toLowerCase((char) a[i]) != Character.toLowerCase((char) b[i])) {
return false;
}
}
return true;
}
static byte[] readFullyAndClose(InputStream input) throws IOException {
ByteBuffer result = ByteBuffer.wrap(new byte[Math.min(512, input.available())]);
while (true) {
if (result.remaining() == 0) {
result = ByteBuffer.wrap(Arrays.copyOf(result.array(), result.capacity() * 2));
}
int actuallyRead = input.read(result.array(), result.position(), result.remaining());
if (actuallyRead == -1) {
break;
}
result.position(result.position() + actuallyRead);
}
byte[] b = Arrays.copyOf(result.array(), result.position());
input.close();
return b;
}
}

View File

@ -0,0 +1,253 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.jar;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.util.HashMap;
import java.util.Map;
public class TManifest implements Cloneable {
static final int LINE_LENGTH_LIMIT = 72;
private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
private static final TAttributes.Name NAME_ATTRIBUTE = new TAttributes.Name("Name");
private TAttributes mainAttributes = new TAttributes();
private HashMap<String, TAttributes> entries = new HashMap<>();
static class Chunk {
int start;
int end;
Chunk(int start, int end) {
this.start = start;
this.end = end;
}
}
private HashMap<String, Chunk> chunks;
private TInitManifest im;
private int mainEnd;
public TManifest() {
super();
}
public TManifest(InputStream is) throws IOException {
super();
read(is);
}
@SuppressWarnings("unchecked")
public TManifest(TManifest man) {
mainAttributes = (TAttributes) man.mainAttributes.clone();
entries = (HashMap<String, TAttributes>) ((HashMap<String, TAttributes>) man.getEntries()).clone();
}
TManifest(InputStream is, boolean readChunks) throws IOException {
if (readChunks) {
chunks = new HashMap<>();
}
read(is);
}
public void clear() {
im = null;
entries.clear();
mainAttributes.clear();
}
public TAttributes getAttributes(String name) {
return getEntries().get(name);
}
public Map<String, TAttributes> getEntries() {
return entries;
}
public TAttributes getMainAttributes() {
return mainAttributes;
}
@Override
public Object clone() {
return new TManifest(this);
}
public void write(OutputStream os) throws IOException {
write(this, os);
}
public void read(InputStream is) throws IOException {
byte[] buf = readFully(is);
if (buf.length == 0) {
return;
}
// a workaround for HARMONY-5662
// replace EOF and NUL with another new line
// which does not trigger an error
byte b = buf[buf.length - 1];
if (0 == b || 26 == b) {
buf[buf.length - 1] = '\n';
}
// Attributes.Name.MANIFEST_VERSION is not used for
// the second parameter for RI compatibility
im = new TInitManifest(buf, mainAttributes, null);
mainEnd = im.getPos();
// FIXME
im.initEntries(entries, chunks);
im = null;
}
private byte[] readFully(InputStream is) throws IOException {
// Initial read
byte[] buffer = new byte[4096];
int count = is.read(buffer);
int nextByte = is.read();
// Did we get it all in one read?
if (nextByte == -1) {
byte[] dest = new byte[count];
System.arraycopy(buffer, 0, dest, 0, count);
return dest;
}
// Does it look like a manifest?
if (!containsLine(buffer, count)) {
throw new IOException("Manifest is too long");
}
// Requires additional reads
ByteArrayOutputStream baos = new ByteArrayOutputStream(count * 2);
baos.write(buffer, 0, count);
baos.write(nextByte);
while (true) {
count = is.read(buffer);
if (count == -1) {
return baos.toByteArray();
}
baos.write(buffer, 0, count);
}
}
private boolean containsLine(byte[] buffer, int length) {
for (int i = 0; i < length; i++) {
if (buffer[i] == 0x0A || buffer[i] == 0x0D) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return mainAttributes.hashCode() ^ getEntries().hashCode();
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o.getClass() != this.getClass()) {
return false;
}
if (!mainAttributes.equals(((TManifest) o).mainAttributes)) {
return false;
}
return getEntries().equals(((TManifest) o).getEntries());
}
Chunk getChunk(String name) {
return chunks.get(name);
}
void removeChunks() {
chunks = null;
}
int getMainAttributesEnd() {
return mainEnd;
}
static void write(TManifest manifest, OutputStream out) throws IOException {
CharsetEncoder encoder = Charset.defaultCharset().newEncoder();
ByteBuffer buffer = ByteBuffer.allocate(512);
String version = manifest.mainAttributes.getValue(TAttributes.Name.MANIFEST_VERSION);
if (version != null) {
writeEntry(out, TAttributes.Name.MANIFEST_VERSION, version, encoder, buffer);
for (Object o : manifest.mainAttributes.keySet()) {
TAttributes.Name name = (TAttributes.Name) o;
if (!name.equals(TAttributes.Name.MANIFEST_VERSION)) {
writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
}
}
}
out.write(LINE_SEPARATOR);
for (String key : manifest.getEntries().keySet()) {
writeEntry(out, NAME_ATTRIBUTE, key, encoder, buffer);
TAttributes attrib = manifest.entries.get(key);
for (Object o : attrib.keySet()) {
TAttributes.Name name = (TAttributes.Name) o;
writeEntry(out, name, attrib.getValue(name), encoder, buffer);
}
out.write(LINE_SEPARATOR);
}
}
private static void writeEntry(OutputStream os, TAttributes.Name name,
String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException {
byte[] out = name.getBytes();
if (out.length > LINE_LENGTH_LIMIT) {
throw new IOException();
}
os.write(out);
os.write(VALUE_SEPARATOR);
encoder.reset();
bBuf.clear().limit(LINE_LENGTH_LIMIT - out.length - 2);
CharBuffer cBuf = CharBuffer.wrap(value);
CoderResult r;
while (true) {
r = encoder.encode(cBuf, bBuf, true);
if (CoderResult.UNDERFLOW == r) {
r = encoder.flush(bBuf);
}
os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position());
os.write(LINE_SEPARATOR);
if (CoderResult.UNDERFLOW == r) {
break;
}
os.write(' ');
bBuf.clear().limit(LINE_LENGTH_LIMIT - 1);
}
}
}