diff --git a/classlib/src/main/java/org/teavm/classlib/ResourceSupplier.java b/classlib/src/main/java/org/teavm/classlib/ResourceSupplier.java new file mode 100644 index 000000000..7daec97c0 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/ResourceSupplier.java @@ -0,0 +1,11 @@ +package org.teavm.classlib; + +import org.teavm.model.ListableClassReaderSource; + +/** + * + * @author Alexey Andreev + */ +public interface ResourceSupplier { + String[] supplyResources(ClassLoader classLoader, ListableClassReaderSource classSource); +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/Base64.java b/classlib/src/main/java/org/teavm/classlib/impl/Base64.java new file mode 100644 index 000000000..665469e87 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/Base64.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015 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.impl; + +import java.util.Arrays; + +/** + * + * @author Alexey Andreev + */ +public class Base64 { + private static char[] alphabet = new char[64]; + private static int[] reverse = new int[256]; + + static { + int i = 0; + for (char c = 'A'; c <= 'Z'; ++c) { + alphabet[i++] = c; + } + for (char c = 'a'; c <= 'z'; ++c) { + alphabet[i++] = c; + } + for (char c = '0'; c <= '9'; ++c) { + alphabet[i++] = c; + } + alphabet[i++] = '+'; + alphabet[i++] = '/'; + + Arrays.fill(reverse, -1); + for (i = 0; i < alphabet.length; ++i) { + reverse[alphabet[i]] = i; + } + } + + public static byte[] decode(String text) { + int outputSize = ((text.length() - 1) / 4 + 1) * 3; + int i, j; + for (i = text.length() - 1; i >= 0 && text.charAt(i) == '='; --i) { + --outputSize; + } + byte[] output = new byte[outputSize]; + + int triples = (outputSize / 3) * 3; + i = 0; + for (j = 0; i < triples;) { + int a = decode(text.charAt(i++)); + int b = decode(text.charAt(i++)); + int c = decode(text.charAt(i++)); + int d = decode(text.charAt(i++)); + int out = (a << 18) | (b << 12) | (c << 6) | d; + output[j++] = (byte) (out >>> 16); + output[j++] = (byte) (out >>> 8); + output[j++] = (byte) (out); + } + int rem = output.length - j; + if (rem == 1) { + int a = decode(text.charAt(i)); + int b = decode(text.charAt(i + 1)); + output[j] = (byte) ((a << 2) | (b >>> 4)); + } else if (rem == 2) { + int a = decode(text.charAt(i)); + int b = decode(text.charAt(i + 1)); + int c = decode(text.charAt(i + 2)); + output[j] = (byte) ((a << 2) | (b >>> 4)); + output[j + 1] = (byte) ((b << 4) | (c >>> 2)); + } + + return output; + } + + private static int decode(char c) { + return c < 256 ? reverse[c] : -1; + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/ClassLoaderNativeGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassLoaderNativeGenerator.java new file mode 100644 index 000000000..83cad8cd1 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/ClassLoaderNativeGenerator.java @@ -0,0 +1,68 @@ +package org.teavm.classlib.java.lang; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashSet; +import java.util.ServiceLoader; +import java.util.Set; +import org.apache.commons.io.IOUtils; +import org.teavm.classlib.ResourceSupplier; +import org.teavm.codegen.SourceWriter; +import org.teavm.javascript.Renderer; +import org.teavm.javascript.spi.Injector; +import org.teavm.javascript.spi.InjectorContext; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class ClassLoaderNativeGenerator implements Injector { + @Override + public void generate(InjectorContext context, MethodReference methodRef) throws IOException { + switch (methodRef.getName()) { + case "supplyResources": + generateSupplyResources(context); + break; + } + } + + private void generateSupplyResources(InjectorContext context) throws IOException { + SourceWriter writer = context.getWriter(); + writer.append("{").indent(); + + ClassLoader classLoader = context.getClassLoader(); + Set resourceSet = new HashSet<>(); + for (ResourceSupplier supplier : ServiceLoader.load(ResourceSupplier.class, classLoader)) { + String[] resources = supplier.supplyResources(classLoader, context.getClassSource()); + if (resources != null) { + resourceSet.addAll(Arrays.asList(resources)); + } + } + + boolean first = true; + for (String resource : resourceSet) { + try (InputStream input = classLoader.getResourceAsStream(resource)) { + if (input == null) { + continue; + } + if (!first) { + writer.append(','); + } + first = false; + writer.newLine(); + String data = Base64.getEncoder().encodeToString(IOUtils.toByteArray(input)); + writer.append("\"").append(Renderer.escapeString(resource)).append("\""); + writer.ws().append(':').ws(); + writer.append("\"").append(data).append("\""); + } + } + + if (!first) { + writer.newLine(); + } + writer.outdent().append('}'); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index 4ea2a9129..3e8ef824b 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -15,15 +15,12 @@ */ package org.teavm.classlib.java.lang; -import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.HashMap; import java.util.Map; - import org.teavm.classlib.impl.DeclaringClassMetadataGenerator; import org.teavm.classlib.java.lang.annotation.TAnnotation; import org.teavm.classlib.java.lang.reflect.TAnnotatedElement; -import org.teavm.jso.JSBody; import org.teavm.platform.Platform; import org.teavm.platform.PlatformClass; import org.teavm.platform.metadata.ClassResource; @@ -270,23 +267,21 @@ public class TClass extends TObject implements TAnnotatedElement { } } - @JSBody(params = "res", script = - "if (!window.teaVMResources) return null;\n" - + "var data = window.teaVMResources[res];\n" - + "return data ? window.atob(data) : null;\n" - ) - private static native String readResource(String message); - public InputStream getResourceAsStream(String name) { - TString clazzName = getName(); - int lastDot = clazzName.lastIndexOf('.'); - String resName; - if (lastDot == -1) { - resName = name; - } else { - resName = clazzName.substring(0, lastDot).replace('.', '/') + "/" + name; + if (name.startsWith("/")) { + return getClassLoader().getResourceAsStream(name.substring(1)); } - String data = readResource(resName); - return data == null ? null : new ByteArrayInputStream(data.getBytes()); + + TClass cls = this; + while (cls.isArray()) { + cls = cls.getComponentType(); + } + String prefix = cls.getName().toString(); + int index = prefix.lastIndexOf('.'); + if (index >= 0) { + name = prefix.substring(0, index + 1).replace('.', '/') + name; + } + + return getClassLoader().getResourceAsStream(name); } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java index f35b1266a..4f139d982 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClassLoader.java @@ -15,6 +15,13 @@ */ package org.teavm.classlib.java.lang; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import org.teavm.classlib.impl.Base64; +import org.teavm.javascript.spi.InjectedBy; +import org.teavm.jso.JSIndexer; +import org.teavm.jso.JSObject; + /** * * @author Alexey Andreev @@ -22,6 +29,7 @@ package org.teavm.classlib.java.lang; public abstract class TClassLoader extends TObject { private TClassLoader parent; private static TSystemClassLoader systemClassLoader = new TSystemClassLoader(); + private static ResourceContainer resources; protected TClassLoader() { this(null); @@ -38,4 +46,20 @@ public abstract class TClassLoader extends TObject { public static TClassLoader getSystemClassLoader() { return systemClassLoader; } + + public InputStream getResourceAsStream(String name) { + if (resources == null) { + resources = supplyResources(); + } + String data = resources.getResource(name); + return data == null ? null : new ByteArrayInputStream(Base64.decode(data)); + } + + @InjectedBy(ClassLoaderNativeGenerator.class) + private static native ResourceContainer supplyResources(); + + static interface ResourceContainer extends JSObject { + @JSIndexer + String getResource(String name); + } } diff --git a/classlib/src/test/java/org/teavm/classlib/impl/Base64Test.java b/classlib/src/test/java/org/teavm/classlib/impl/Base64Test.java new file mode 100644 index 000000000..0c2ef0818 --- /dev/null +++ b/classlib/src/test/java/org/teavm/classlib/impl/Base64Test.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 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.impl; + +import static org.junit.Assert.*; +import java.io.UnsupportedEncodingException; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class Base64Test { + @Test + public void decoderWorks() { + assertEquals("q", decode("cQ==")); + assertEquals("qw", decode("cXc=")); + assertEquals("qwe", decode("cXdl")); + assertEquals("qwer", decode("cXdlcg==")); + assertEquals("qwert", decode("cXdlcnQ=")); + assertEquals("qwerty", decode("cXdlcnR5")); + assertEquals("qwertyu", decode("cXdlcnR5dQ==")); + } + + private String decode(String text) { + try { + return new String(Base64.decode(text), "UTF-8"); + } catch (UnsupportedEncodingException e) { + return ""; + } + } +} diff --git a/core/src/main/java/org/teavm/javascript/Renderer.java b/core/src/main/java/org/teavm/javascript/Renderer.java index 7c39c4c57..fe476c56f 100644 --- a/core/src/main/java/org/teavm/javascript/Renderer.java +++ b/core/src/main/java/org/teavm/javascript/Renderer.java @@ -2377,6 +2377,16 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext public Precedence getPrecedence() { return precedence; } + + @Override + public ClassLoader getClassLoader() { + return classLoader; + } + + @Override + public ListableClassReaderSource getClassSource() { + return classSource; + } } @Override diff --git a/core/src/main/java/org/teavm/javascript/spi/InjectorContext.java b/core/src/main/java/org/teavm/javascript/spi/InjectorContext.java index 15af2fcaf..361f36c95 100644 --- a/core/src/main/java/org/teavm/javascript/spi/InjectorContext.java +++ b/core/src/main/java/org/teavm/javascript/spi/InjectorContext.java @@ -21,6 +21,7 @@ import org.teavm.codegen.SourceWriter; import org.teavm.common.ServiceRepository; import org.teavm.javascript.Precedence; import org.teavm.javascript.ast.Expr; +import org.teavm.model.ListableClassReaderSource; import org.teavm.model.ValueType; /** @@ -47,4 +48,8 @@ public interface InjectorContext extends ServiceRepository { void writeExpr(Expr expr, Precedence precedence) throws IOException; Precedence getPrecedence(); + + ClassLoader getClassLoader(); + + ListableClassReaderSource getClassSource(); } diff --git a/html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.java b/html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.java index 118a3ab80..1530d308d 100644 --- a/html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.java +++ b/html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.java @@ -29,6 +29,5 @@ public class HTML4JPlugin implements TeaVMPlugin { host.add(new JavaScriptBodyTransformer()); host.add(new JCLHacks()); host.add(new JavaScriptResourceInterceptor()); - host.add(new ResourcesInterceptor()); } } diff --git a/html4j/src/main/java/org/teavm/html4j/HTML4jResourceSupplier.java b/html4j/src/main/java/org/teavm/html4j/HTML4jResourceSupplier.java new file mode 100644 index 000000000..279f894e3 --- /dev/null +++ b/html4j/src/main/java/org/teavm/html4j/HTML4jResourceSupplier.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 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.html4j; + +import java.util.HashSet; +import java.util.Set; +import org.teavm.classlib.ResourceSupplier; +import org.teavm.model.ListableClassReaderSource; + +/** + * + * @author Alexey Andreev + */ +public class HTML4jResourceSupplier implements ResourceSupplier { + @Override + public String[] supplyResources(ClassLoader classLoader, ListableClassReaderSource classSource) { + Set resources = new HashSet<>(); + for (String className : classSource.getClassNames()) { + final int lastDot = className.lastIndexOf('.'); + if (lastDot == -1) { + continue; + } + String packageName = className.substring(0, lastDot); + String resourceName = packageName.replace('.', '/') + "/" + "jvm.txt"; + resources.add(resourceName); + } + + return resources.toArray(new String[0]); + } +} diff --git a/html4j/src/main/resources/META-INF/services/org.teavm.classlib.ResourceSupplier b/html4j/src/main/resources/META-INF/services/org.teavm.classlib.ResourceSupplier new file mode 100644 index 000000000..be4d39dbe --- /dev/null +++ b/html4j/src/main/resources/META-INF/services/org.teavm.classlib.ResourceSupplier @@ -0,0 +1 @@ +org.teavm.html4j.HTML4jResourceSupplier \ No newline at end of file diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/ClassLoaderTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/ClassLoaderTest.java new file mode 100644 index 000000000..b20323ce1 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/lang/ClassLoaderTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015 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.lang; + +import static org.junit.Assert.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class ClassLoaderTest { + @Test + public void loadsResources() { + assertEquals("q", loadResource("1")); + assertEquals("qw", loadResource("2")); + assertEquals("qwe", loadResource("3")); + assertEquals("qwer", loadResource("4")); + assertEquals("qwert", loadResource("5")); + assertEquals("qwerty", loadResource("6")); + assertEquals("qwertyu", loadResource("7")); + assertEquals("qwertyui", loadResource("8")); + } + + private static String loadResource(String name) { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream( + "resources-for-test/" + name), "UTF-8"))) { + return reader.readLine(); + } catch (IOException e) { + return ""; + } + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/TestResourcesSupplier.java b/tests/src/test/java/org/teavm/classlib/java/lang/TestResourcesSupplier.java new file mode 100644 index 000000000..3540e9ff1 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/lang/TestResourcesSupplier.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 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.lang; + +import org.teavm.classlib.ResourceSupplier; +import org.teavm.model.ListableClassReaderSource; + +/** + * + * @author Alexey Andreev + */ +public class TestResourcesSupplier implements ResourceSupplier { + @Override + public String[] supplyResources(ClassLoader classLoader, ListableClassReaderSource classSource) { + String[] result = { "1", "2", "3", "4", "5", "6", "7", "8" }; + for (int i = 0; i < result.length; ++i) { + result[i] = "resources-for-test/" + result[i]; + } + return result; + } +} diff --git a/tests/src/test/resources/META-INF/services/org.teavm.classlib.ResourceSupplier b/tests/src/test/resources/META-INF/services/org.teavm.classlib.ResourceSupplier new file mode 100644 index 000000000..ebc9333cf --- /dev/null +++ b/tests/src/test/resources/META-INF/services/org.teavm.classlib.ResourceSupplier @@ -0,0 +1 @@ +org.teavm.classlib.java.lang.TestResourcesSupplier \ No newline at end of file diff --git a/tests/src/test/resources/resources-for-test/1 b/tests/src/test/resources/resources-for-test/1 new file mode 100644 index 000000000..ea0c8a85c --- /dev/null +++ b/tests/src/test/resources/resources-for-test/1 @@ -0,0 +1 @@ +q \ No newline at end of file diff --git a/tests/src/test/resources/resources-for-test/2 b/tests/src/test/resources/resources-for-test/2 new file mode 100644 index 000000000..81f4282e8 --- /dev/null +++ b/tests/src/test/resources/resources-for-test/2 @@ -0,0 +1 @@ +qw \ No newline at end of file diff --git a/tests/src/test/resources/resources-for-test/3 b/tests/src/test/resources/resources-for-test/3 new file mode 100644 index 000000000..5dda67495 --- /dev/null +++ b/tests/src/test/resources/resources-for-test/3 @@ -0,0 +1 @@ +qwe \ No newline at end of file diff --git a/tests/src/test/resources/resources-for-test/4 b/tests/src/test/resources/resources-for-test/4 new file mode 100644 index 000000000..3a2eb438a --- /dev/null +++ b/tests/src/test/resources/resources-for-test/4 @@ -0,0 +1 @@ +qwer \ No newline at end of file diff --git a/tests/src/test/resources/resources-for-test/5 b/tests/src/test/resources/resources-for-test/5 new file mode 100644 index 000000000..b5f07e257 --- /dev/null +++ b/tests/src/test/resources/resources-for-test/5 @@ -0,0 +1 @@ +qwert \ No newline at end of file diff --git a/tests/src/test/resources/resources-for-test/6 b/tests/src/test/resources/resources-for-test/6 new file mode 100644 index 000000000..f2289097d --- /dev/null +++ b/tests/src/test/resources/resources-for-test/6 @@ -0,0 +1 @@ +qwerty \ No newline at end of file diff --git a/tests/src/test/resources/resources-for-test/7 b/tests/src/test/resources/resources-for-test/7 new file mode 100644 index 000000000..21887eb8b --- /dev/null +++ b/tests/src/test/resources/resources-for-test/7 @@ -0,0 +1 @@ +qwertyu \ No newline at end of file diff --git a/tests/src/test/resources/resources-for-test/8 b/tests/src/test/resources/resources-for-test/8 new file mode 100644 index 000000000..b59c9f53c --- /dev/null +++ b/tests/src/test/resources/resources-for-test/8 @@ -0,0 +1 @@ +qwertyui \ No newline at end of file