diff --git a/classlib/src/main/java/org/teavm/classlib/ServiceLoaderFilter.java b/classlib/src/main/java/org/teavm/classlib/ServiceLoaderFilter.java new file mode 100644 index 000000000..cdc7f3acb --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/ServiceLoaderFilter.java @@ -0,0 +1,20 @@ +/* + * Copyright 2019 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; + +public interface ServiceLoaderFilter { + boolean apply(String serviceType, String implementationType); +} diff --git a/classlib/src/main/java/org/teavm/classlib/ServiceLoaderFilterContext.java b/classlib/src/main/java/org/teavm/classlib/ServiceLoaderFilterContext.java new file mode 100644 index 000000000..da9f74bf5 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/ServiceLoaderFilterContext.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 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; + +import org.teavm.model.ClassReaderSource; + +public interface ServiceLoaderFilterContext { + ClassLoader getClassLoader(); + + ClassReaderSource getClassSource(); +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java b/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java index ffdbe2abf..361084df4 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java @@ -19,17 +19,21 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.ServiceLoader; +import java.util.Set; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.GeneratorContext; +import org.teavm.classlib.ServiceLoaderFilter; import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyNode; @@ -85,8 +89,48 @@ public class ServiceLoaderSupport extends AbstractDependencyListener implements writer.append("return result;").softNewLine(); } - private void parseServiceFile(DependencyAgent agent, DependencyNode targetNode, String service, - InputStream input, CallLocation location) throws IOException { + @Override + public void methodReached(DependencyAgent agent, MethodDependency method) { + MethodReference ref = method.getReference(); + if (ref.getClassName().equals("java.util.ServiceLoader") && ref.getName().equals("loadServices")) { + List filters = getFilters(agent); + method.getResult().propagate(agent.getType("[Ljava/lang/Object;")); + DependencyNode sourceNode = agent.linkMethod(LOAD_METHOD).getVariable(1).getClassValueNode(); + sourceNode.connect(method.getResult().getArrayItem()); + sourceNode.addConsumer(type -> { + CallLocation location = new CallLocation(LOAD_METHOD); + for (String implementationType : getImplementations(type.getName())) { + if (filters.stream().anyMatch(filter -> !filter.apply(type.getName(), implementationType))) { + continue; + } + serviceMap.computeIfAbsent(type.getName(), k -> new ArrayList<>()).add(implementationType); + + MethodReference ctor = new MethodReference(implementationType, + new MethodDescriptor("", ValueType.VOID)); + agent.linkMethod(ctor).addLocation(location).use(); + method.getResult().getArrayItem().propagate(agent.getType(implementationType)); + } + }); + } + } + + private Set getImplementations(String type) { + Set result = new LinkedHashSet<>(); + try { + Enumeration resources = classLoader.getResources("META-INF/services/" + type); + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + try (InputStream stream = resource.openStream()) { + parseServiceFile(stream, result); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return result; + } + + private void parseServiceFile(InputStream input, Set consumer) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); while (true) { String line = reader.readLine(); @@ -97,38 +141,37 @@ public class ServiceLoaderSupport extends AbstractDependencyListener implements if (line.startsWith("#") || line.isEmpty()) { continue; } - serviceMap.computeIfAbsent(service, k -> new ArrayList<>()).add(line); - MethodReference ctor = new MethodReference(line, new MethodDescriptor("", ValueType.VOID)); - agent.linkMethod(ctor).addLocation(location).use(); - targetNode.propagate(agent.getType(line)); + consumer.add(line); } } - @Override - public void methodReached(DependencyAgent agent, MethodDependency method) { - MethodReference ref = method.getReference(); - if (ref.getClassName().equals("java.util.ServiceLoader") && ref.getName().equals("loadServices")) { - method.getResult().propagate(agent.getType("[Ljava/lang/Object;")); - DependencyNode sourceNode = agent.linkMethod(LOAD_METHOD).getVariable(1).getClassValueNode(); - sourceNode.connect(method.getResult().getArrayItem()); - sourceNode.addConsumer(type -> initConstructor(agent, method.getResult().getArrayItem(), - type.getName(), new CallLocation(LOAD_METHOD))); - } - } - - private void initConstructor(DependencyAgent agent, DependencyNode targetNode, String type, - CallLocation location) { - try { - Enumeration resources = classLoader.getResources("META-INF/services/" + type); - while (resources.hasMoreElements()) { - URL resource = resources.nextElement(); - try (InputStream stream = resource.openStream()) { - parseServiceFile(agent, targetNode, type, stream, location); - } + private List getFilters(DependencyAgent agent) { + List filters = new ArrayList<>(); + for (String filterTypeName : getImplementations(ServiceLoaderFilter.class.getName())) { + Class filterType; + try { + filterType = Class.forName(filterTypeName, true, classLoader); + } catch (ClassNotFoundException e) { + agent.getDiagnostics().error(null, "Could not load ServiceLoader filter class '{{c0}}'", + filterTypeName); + continue; + } + + if (!ServiceLoaderFilter.class.isAssignableFrom(filterType)) { + agent.getDiagnostics().error(null, "Class '{{c0}}' does not implement ServiceLoaderFilter interface", + filterTypeName); + continue; + } + + try { + filters.add((ServiceLoaderFilter) filterType.getConstructor().newInstance()); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException + | InstantiationException e) { + agent.getDiagnostics().error(null, "Could not instantiate ServiceLoader filter '{{c0}}'", + filterTypeName); } - } catch (IOException e) { - throw new RuntimeException(e); } + return filters; } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TInvocationHandler.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TInvocationHandler.java new file mode 100644 index 000000000..9521df9ac --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TInvocationHandler.java @@ -0,0 +1,22 @@ +/* + * Copyright 2019 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.reflect; + +import java.lang.reflect.Method; + +public interface TInvocationHandler { + Object invoke(Object proxy, Method method, Object[] args) throws Throwable; +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java index a97ef4243..3338fbd54 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java @@ -18,12 +18,6 @@ package org.teavm.classlib.java.util; import java.util.function.BiFunction; import java.util.function.Function; -/** - * - * @author Alexey Andreev - * @param - * @param - */ public interface TMap { interface Entry { K1 getKey(); @@ -43,6 +37,10 @@ public interface TMap { V get(Object key); + default V getOrDefault(K key, V defaultValue) { + return containsKey(key) ? get(key) : defaultValue; + } + V put(K key, V value); V remove(Object key); diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index 9b771522b..1973a9cf4 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -453,68 +453,15 @@ public class Renderer implements RenderingManager { int start = writer.getOffset(); try { - writer.append("$rt_metadata(["); + writer.append("$rt_packages(["); ObjectIntMap packageIndexes = generatePackageMetadata(classes, classesRequiringName); - - boolean first = true; - for (ClassNode cls : classes) { - if (!first) { - writer.append(',').softNewLine(); - } - first = false; - writer.appendClass(cls.getName()).append(",").ws(); - - if (classesRequiringName.contains(cls.getName())) { - String className = cls.getName(); - int dotIndex = className.lastIndexOf('.') + 1; - String packageName = className.substring(0, dotIndex); - className = className.substring(dotIndex); - writer.append("\"").append(RenderingUtil.escapeString(className)).append("\"").append(",").ws(); - writer.append(String.valueOf(packageIndexes.getOrDefault(packageName, -1))); - } else { - writer.append("0"); - } - writer.append(",").ws(); - - if (cls.getParentName() != null) { - writer.appendClass(cls.getParentName()); - } else { - writer.append("0"); - } - writer.append(',').ws(); - writer.append("["); - for (int i = 0; i < cls.getInterfaces().size(); ++i) { - String iface = cls.getInterfaces().get(i); - if (i > 0) { - writer.append(",").ws(); - } - writer.appendClass(iface); - } - writer.append("],").ws(); - - writer.append(ElementModifier.pack(cls.getModifiers())).append(',').ws(); - writer.append(cls.getAccessLevel().ordinal()).append(',').ws(); - - MethodReader clinit = classSource.get(cls.getName()).getMethod( - new MethodDescriptor("", ValueType.VOID)); - if (clinit != null) { - writer.append(naming.getNameForClassInit(cls.getName())); - } else { - writer.append('0'); - } - writer.append(',').ws(); - - List virtualMethods = new ArrayList<>(); - for (MethodNode method : cls.getMethods()) { - if (!method.getModifiers().contains(ElementModifier.STATIC)) { - virtualMethods.add(method.getReference()); - } - } - collectMethodsToCopyFromInterfaces(classSource.get(cls.getName()), virtualMethods); - - renderVirtualDeclarations(virtualMethods); - } writer.append("]);").newLine(); + + for (int i = 0; i < classes.size(); i += 50) { + int j = Math.min(i + 50, classes.size()); + renderClassMetadataPortion(classes.subList(i, j), packageIndexes, classesRequiringName); + } + } catch (IOException e) { throw new RenderingException("IO error occurred", e); } @@ -522,6 +469,70 @@ public class Renderer implements RenderingManager { metadataSize = writer.getOffset() - start; } + private void renderClassMetadataPortion(List classes, ObjectIntMap packageIndexes, + Set classesRequiringName) throws IOException { + writer.append("$rt_metadata(["); + boolean first = true; + for (ClassNode cls : classes) { + if (!first) { + writer.append(',').softNewLine(); + } + first = false; + writer.appendClass(cls.getName()).append(",").ws(); + + if (classesRequiringName.contains(cls.getName())) { + String className = cls.getName(); + int dotIndex = className.lastIndexOf('.') + 1; + String packageName = className.substring(0, dotIndex); + className = className.substring(dotIndex); + writer.append("\"").append(RenderingUtil.escapeString(className)).append("\"").append(",").ws(); + writer.append(String.valueOf(packageIndexes.getOrDefault(packageName, -1))); + } else { + writer.append("0"); + } + writer.append(",").ws(); + + if (cls.getParentName() != null) { + writer.appendClass(cls.getParentName()); + } else { + writer.append("0"); + } + writer.append(',').ws(); + writer.append("["); + for (int i = 0; i < cls.getInterfaces().size(); ++i) { + String iface = cls.getInterfaces().get(i); + if (i > 0) { + writer.append(",").ws(); + } + writer.appendClass(iface); + } + writer.append("],").ws(); + + writer.append(ElementModifier.pack(cls.getModifiers())).append(',').ws(); + writer.append(cls.getAccessLevel().ordinal()).append(',').ws(); + + MethodReader clinit = classSource.get(cls.getName()).getMethod( + new MethodDescriptor("", ValueType.VOID)); + if (clinit != null) { + writer.append(naming.getNameForClassInit(cls.getName())); + } else { + writer.append('0'); + } + writer.append(',').ws(); + + List virtualMethods = new ArrayList<>(); + for (MethodNode method : cls.getMethods()) { + if (!method.getModifiers().contains(ElementModifier.STATIC)) { + virtualMethods.add(method.getReference()); + } + } + collectMethodsToCopyFromInterfaces(classSource.get(cls.getName()), virtualMethods); + + renderVirtualDeclarations(virtualMethods); + } + writer.append("]);").newLine(); + } + private ObjectIntMap generatePackageMetadata(List classes, Set classesRequiringName) throws IOException { PackageNode root = new PackageNode(null); @@ -541,7 +552,6 @@ public class Renderer implements RenderingManager { } ObjectIntMap indexes = new ObjectIntHashMap<>(); - writer.append(String.valueOf(root.count())).append(",").ws(); writePackageStructure(root, -1, "", indexes); writer.softNewLine(); return indexes; @@ -551,8 +561,11 @@ public class Renderer implements RenderingManager { throws IOException { int index = startIndex; for (PackageNode child : node.children.values()) { + if (index >= 0) { + writer.append(",").ws(); + } writer.append(String.valueOf(startIndex)).append(",").ws() - .append("\"").append(RenderingUtil.escapeString(child.name)).append("\",").ws(); + .append("\"").append(RenderingUtil.escapeString(child.name)).append("\""); String fullName = prefix + child.name + "."; ++index; indexes.put(fullName, index); diff --git a/core/src/main/resources/org/teavm/backend/javascript/runtime.js b/core/src/main/resources/org/teavm/backend/javascript/runtime.js index 300fd0657..831e6ee46 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/runtime.js +++ b/core/src/main/resources/org/teavm/backend/javascript/runtime.js @@ -419,16 +419,20 @@ var $rt_putStderr = typeof $rt_putStderrCustom === "function" ? $rt_putStderrCus $rt_stderrBuffer += String.fromCharCode(ch); } }; -function $rt_metadata(data) { +var $rt_packageData = null; +function $rt_packages(data) { var i = 0; - var packageCount = data[i++]; - var packages = new Array(packageCount); - for (var j = 0; j < packageCount; ++j) { + var packages = new Array(data.length); + for (var j = 0; j < data.length; ++j) { var prefixIndex = data[i++]; var prefix = prefixIndex >= 0 ? packages[prefixIndex] : ""; packages[j] = prefix + data[i++] + "."; } - + $rt_packageData = packages; +} +function $rt_metadata(data) { + var packages = $rt_packageData; + var i = 0; while (i < data.length) { var cls = data[i++]; cls.$meta = {}; @@ -468,7 +472,7 @@ function $rt_metadata(data) { var virtualMethods = data[i++]; if (virtualMethods !== 0) { - for (j = 0; j < virtualMethods.length; j += 2) { + for (var j = 0; j < virtualMethods.length; j += 2) { var name = virtualMethods[j]; var func = virtualMethods[j + 1]; if (typeof name === 'string') { diff --git a/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceGetter.java b/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceGetter.java index 970d1e84d..b7f1fea1c 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceGetter.java +++ b/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceGetter.java @@ -23,7 +23,7 @@ class BuildTimeResourceGetter implements BuildTimeResourceMethod { } @Override - public Object invoke(BuildTimeResourceProxy proxy, Object[] args) throws Throwable { + public Object invoke(BuildTimeResourceProxy proxy, Object[] args) { return proxy.data[index]; } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxy.java b/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxy.java index 4efb281f5..7c71f5f52 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxy.java +++ b/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxy.java @@ -24,7 +24,7 @@ class BuildTimeResourceProxy implements InvocationHandler { private Map methods; Object[] data; - public BuildTimeResourceProxy(Map methods, Object[] initialData) { + BuildTimeResourceProxy(Map methods, Object[] initialData) { this.methods = methods; data = Arrays.copyOf(initialData, initialData.length); } diff --git a/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java b/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java index 1adf69bc6..4ab75570d 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java +++ b/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java @@ -51,7 +51,7 @@ class BuildTimeResourceProxyBuilder { private Object[] initialData; private ResourceTypeDescriptor descriptor; - public ProxyFactoryCreation(ResourceTypeDescriptor descriptor) { + ProxyFactoryCreation(ResourceTypeDescriptor descriptor) { this.descriptor = descriptor; int index = 0; for (String propertyName : descriptor.getPropertyTypes().keySet()) { diff --git a/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyFactory.java b/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyFactory.java index 176e51673..5277f80eb 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyFactory.java +++ b/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyFactory.java @@ -23,7 +23,7 @@ class BuildTimeResourceProxyFactory { private Object[] initialData; ResourceTypeDescriptor typeDescriptor; - public BuildTimeResourceProxyFactory(Map methods, Object[] initialData, + BuildTimeResourceProxyFactory(Map methods, Object[] initialData, ResourceTypeDescriptor typeDescriptor) { this.methods = methods; this.initialData = initialData; diff --git a/platform/src/main/java/org/teavm/platform/plugin/DefaultMetadataGeneratorContext.java b/platform/src/main/java/org/teavm/platform/plugin/DefaultMetadataGeneratorContext.java index b3860a68b..65724932d 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/DefaultMetadataGeneratorContext.java +++ b/platform/src/main/java/org/teavm/platform/plugin/DefaultMetadataGeneratorContext.java @@ -31,7 +31,7 @@ class DefaultMetadataGeneratorContext implements MetadataGeneratorContext { private ClassReaderSource classSource; private ClassLoader classLoader; private Properties properties; - private BuildTimeResourceProxyBuilder proxyBuilder = new BuildTimeResourceProxyBuilder(); + private BuildTimeResourceProxyBuilder proxyBuilder; private ServiceRepository services; DefaultMetadataGeneratorContext(ClassReaderSource classSource, ClassLoader classLoader, @@ -42,6 +42,13 @@ class DefaultMetadataGeneratorContext implements MetadataGeneratorContext { this.services = services; } + private BuildTimeResourceProxyBuilder getProxyBuilder() { + if (proxyBuilder == null) { + proxyBuilder = new BuildTimeResourceProxyBuilder(); + } + return proxyBuilder; + } + @Override public ClassReaderSource getClassSource() { return classSource; @@ -61,12 +68,12 @@ class DefaultMetadataGeneratorContext implements MetadataGeneratorContext { public T createResource(Class resourceType) { return resourceType.cast(Proxy.newProxyInstance(classLoader, new Class[] { resourceType, ResourceWriter.class, ResourceTypeDescriptorProvider.class }, - proxyBuilder.buildProxy(resourceType))); + getProxyBuilder().buildProxy(resourceType))); } @Override public ResourceTypeDescriptor getTypeDescriptor(Class type) { - return proxyBuilder.getProxyFactory(type).typeDescriptor; + return getProxyBuilder().getProxyFactory(type).typeDescriptor; } @Override