# HG changeset patch # User Jaroslav Tulach # Date 1360066807 -3600 # Node ID add357fd6c5c8ab50ceac66fe47949225a264a96 # Parent 99fa4fe6b9808824adb6474a494cf20d305d62ba Understands Class-Path: attribute in manifest diff -r 99fa4fe6b980 -r add357fd6c5c emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/lang/ManifestInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/lang/ManifestInputStream.java Tue Feb 05 13:20:07 2013 +0100 @@ -0,0 +1,228 @@ +/* + * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.apidesign.bck2brwsr.emul.lang; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/* + * A fast buffered input stream for parsing manifest files. + * + * Taken from java.util.jar.Manifest.FastInputStream and modified to be + * independent of other Manifest functionality. + */ +public abstract class ManifestInputStream extends FilterInputStream { + private byte[] buf; + private int count = 0; + private int pos = 0; + + protected ManifestInputStream(InputStream in) { + this(in, 8192); + } + + protected ManifestInputStream(InputStream in, int size) { + super(in); + buf = new byte[size]; + } + + public int read() throws IOException { + if (pos >= count) { + fill(); + if (pos >= count) { + return -1; + } + } + return buf[pos++] & 0xff; + } + + public int read(byte[] b, int off, int len) throws IOException { + int avail = count - pos; + if (avail <= 0) { + if (len >= buf.length) { + return in.read(b, off, len); + } + fill(); + avail = count - pos; + if (avail <= 0) { + return -1; + } + } + if (len > avail) { + len = avail; + } + System.arraycopy(buf, pos, b, off, len); + pos += len; + return len; + } + + /* + * Reads 'len' bytes from the input stream, or until an end-of-line + * is reached. Returns the number of bytes read. + */ + public int readLine(byte[] b, int off, int len) throws IOException { + byte[] tbuf = this.buf; + int total = 0; + while (total < len) { + int avail = count - pos; + if (avail <= 0) { + fill(); + avail = count - pos; + if (avail <= 0) { + return -1; + } + } + int n = len - total; + if (n > avail) { + n = avail; + } + int tpos = pos; + int maxpos = tpos + n; + while (tpos < maxpos && tbuf[tpos++] != '\n') { + ; + } + n = tpos - pos; + System.arraycopy(tbuf, pos, b, off, n); + off += n; + total += n; + pos = tpos; + if (tbuf[tpos - 1] == '\n') { + break; + } + } + return total; + } + + public byte peek() throws IOException { + if (pos == count) { + fill(); + } + if (pos == count) { + return -1; // nothing left in buffer + } + return buf[pos]; + } + + public int readLine(byte[] b) throws IOException { + return readLine(b, 0, b.length); + } + + public long skip(long n) throws IOException { + if (n <= 0) { + return 0; + } + long avail = count - pos; + if (avail <= 0) { + return in.skip(n); + } + if (n > avail) { + n = avail; + } + pos += n; + return n; + } + + public int available() throws IOException { + return (count - pos) + in.available(); + } + + public void close() throws IOException { + if (in != null) { + in.close(); + in = null; + buf = null; + } + } + + private void fill() throws IOException { + count = pos = 0; + int n = in.read(buf, 0, buf.length); + if (n > 0) { + count = n; + } + } + + protected abstract String putValue(String key, String value); + + public void readAttributes(byte[] lbuf) throws IOException { + ManifestInputStream is = this; + + String name = null; + String value = null; + byte[] lastline = null; + int len; + while ((len = is.readLine(lbuf)) != -1) { + boolean lineContinued = false; + if (lbuf[--len] != '\n') { + throw new IOException("line too long"); + } + if (len > 0 && lbuf[len - 1] == '\r') { + --len; + } + if (len == 0) { + break; + } + int i = 0; + if (lbuf[0] == ' ') { + if (name == null) { + throw new IOException("misplaced continuation line"); + } + lineContinued = true; + byte[] buf = new byte[lastline.length + len - 1]; + System.arraycopy(lastline, 0, buf, 0, lastline.length); + System.arraycopy(lbuf, 1, buf, lastline.length, len - 1); + if (is.peek() == ' ') { + lastline = buf; + continue; + } + value = new String(buf, 0, buf.length, "UTF8"); + lastline = null; + } else { + while (lbuf[i++] != ':') { + if (i >= len) { + throw new IOException("invalid header field"); + } + } + if (lbuf[i++] != ' ') { + throw new IOException("invalid header field"); + } + name = new String(lbuf, 0, 0, i - 2); + if (is.peek() == ' ') { + lastline = new byte[len - i]; + System.arraycopy(lbuf, i, lastline, 0, len - i); + continue; + } + value = new String(lbuf, i, len - i, "UTF8"); + } + try { + if ((putValue(name, value) != null) && (!lineContinued)) { + throw new IOException("Duplicate name in Manifest: " + name + ".\n" + "Ensure that the manifest does not " + "have duplicate entries, and\n" + "that blank lines separate " + "individual sections in both your\n" + "manifest and in the META-INF/MANIFEST.MF " + "entry in the jar file."); + } + } catch (IllegalArgumentException e) { + throw new IOException("invalid header field name: " + name); + } + } + } +} diff -r 99fa4fe6b980 -r add357fd6c5c vm/pom.xml --- a/vm/pom.xml Tue Feb 05 13:19:06 2013 +0100 +++ b/vm/pom.xml Tue Feb 05 13:20:07 2013 +0100 @@ -85,19 +85,19 @@ ${project.groupId} core - 0.3-SNAPSHOT + ${project.version} jar ${project.groupId} emul.mini - 0.3-SNAPSHOT - test + ${project.version} + compile ${project.groupId} javap - 0.3-SNAPSHOT + ${project.version} diff -r 99fa4fe6b980 -r add357fd6c5c vm/src/main/java/org/apidesign/vm4brwsr/ParseCPAttr.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm/src/main/java/org/apidesign/vm4brwsr/ParseCPAttr.java Tue Feb 05 13:20:07 2013 +0100 @@ -0,0 +1,49 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.vm4brwsr; + +import java.io.IOException; +import java.io.InputStream; +import org.apidesign.bck2brwsr.emul.lang.ManifestInputStream; + +/** + * + * @author Jaroslav Tulach + */ +final class ParseCPAttr extends ManifestInputStream { + private String cp; + + public ParseCPAttr(InputStream is) throws IOException { + super(is); + readAttributes(new byte[512]); + } + + @Override + protected String putValue(String key, String value) { + if ("Class-Path".equals(key)) { + cp = value; + } + return null; + } + + @Override + public String toString() { + return cp; + } + +} diff -r 99fa4fe6b980 -r add357fd6c5c vm/src/main/java/org/apidesign/vm4brwsr/Zips.java --- a/vm/src/main/java/org/apidesign/vm4brwsr/Zips.java Tue Feb 05 13:19:06 2013 +0100 +++ b/vm/src/main/java/org/apidesign/vm4brwsr/Zips.java Tue Feb 05 13:20:07 2013 +0100 @@ -17,7 +17,9 @@ */ package org.apidesign.vm4brwsr; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -39,7 +41,13 @@ Object c = classpath[i]; if (c instanceof String) { try { - c = classpath[i] = toZip((String)c); + String url = (String)c; + final Zips z = toZip(url); + c = classpath[i] = z; + final byte[] man = z.findRes("META-INF/MANIFEST.MF"); + if (man != null) { + processClassPathAttr(man, url, classpath); + } } catch (IOException ex) { classpath[i] = ex; } @@ -87,6 +95,38 @@ return z; } + private static void processClassPathAttr(final byte[] man, String url, Object[] classpath) throws IOException { + try (InputStream is = initIS(new ByteArrayInputStream(man))) { + String cp = is.toString(); + if (cp == null) { + return; + } + for (int p = 0; p < cp.length();) { + int n = cp.indexOf(':', p); + if (n == -1) { + n = cp.length(); + } + String el = cp.substring(p, n); + URL u = new URL(new URL(url), el); + classpath = addToArray(classpath, u.toString()); + p = n; + } + } + } + + private static InputStream initIS(InputStream is) throws IOException { + return new ParseCPAttr(is); + } + + private static Object[] addToArray(Object[] arr, String value) { + final int last = arr.length; + Object[] ret = enlargeArray(arr, last + 1); + ret[last] = value; + return ret; + } + + @JavaScriptBody(args = { "arr", "len" }, body = "while (arr.length < len) arr.push(null); return arr;throw('Arr: ' + arr);") + private static native Object[] enlargeArray(Object[] arr, int len); @JavaScriptBody(args = { "arr", "len" }, body = "while (arr.length < len) arr.push(0);") private static native void enlargeArray(byte[] arr, int len); diff -r 99fa4fe6b980 -r add357fd6c5c vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/GenerateZipProcessor.java --- a/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/GenerateZipProcessor.java Tue Feb 05 13:19:06 2013 +0100 +++ b/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/GenerateZipProcessor.java Tue Feb 05 13:20:07 2013 +0100 @@ -25,6 +25,7 @@ import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; @@ -73,7 +74,8 @@ } private void generateJar(PackageElement pe, GenerateZip gz, Element e) throws IOException { - FileObject res = processingEnv.getFiler().createResource( + final Filer filer = processingEnv.getFiler(); + FileObject res = filer.createResource( StandardLocation.CLASS_OUTPUT, pe.getQualifiedName().toString(), gz.name(), e @@ -93,6 +95,7 @@ jar.write(arr[i + 1].getBytes("UTF-8")); jar.closeEntry(); } + jar.close(); } - + } diff -r 99fa4fe6b980 -r add357fd6c5c vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/ZipFileTest.java --- a/vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/ZipFileTest.java Tue Feb 05 13:19:06 2013 +0100 +++ b/vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/ZipFileTest.java Tue Feb 05 13:20:07 2013 +0100 @@ -76,6 +76,28 @@ assertEquals(ret, "Hello World!", "Can read the bytes"); } + @GenerateZip(name = "cpattr.zip", contents = { + "META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" + + "Created-By: hand\n" + + "Class-Path: realJar.jar\n\n\n" + }) + @Http({ + @Http.Resource(path = "/readComplexEntry.jar", mimeType = "x-application/zip", content = "", resource="cpattr.zip"), + @Http.Resource(path = "/realJar.jar", mimeType = "x-application/zip", content = "", resource="readAnEntry.zip"), + }) + @BrwsrTest public void understandsClassPathAttr() throws IOException { + Object res = loadVMResource("/my/main/file.txt", "/readComplexEntry.jar"); + assert res instanceof InputStream : "Got array of bytes: " + res; + InputStream is = (InputStream)res; + + byte[] arr = new byte[4096]; + int len = is.read(arr); + + final String ret = new String(arr, 0, len, "UTF-8"); + + assertEquals(ret, "Hello World!", "Can read the bytes from secondary JAR"); + } + private static void assertEquals(Object real, Object exp, String msg) { assert Objects.equals(exp, real) : msg + " exp: " + exp + " real: " + real; }