mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Add java.util.jar.* classes
This commit is contained in:
parent
aa48a097d2
commit
e96df3ef7e
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user