mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -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