From d34e26e970301dcfa33e3aca01726046e985282a Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Mon, 24 Feb 2014 00:50:06 +0400 Subject: [PATCH] Adds java.util.ServiceLoader support --- .../org/teavm/classlib/impl/JCLPlugin.java | 9 ++ .../classlib/impl/ServiceLoaderSupport.java | 144 ++++++++++++++++++ .../org/teavm/classlib/java/lang/TClass.java | 2 +- .../classlib/java/util/TServiceLoader.java | 63 ++++++++ .../java/lang/util/ServiceLoaderTest.java | 34 +++++ .../classlib/java/lang/util/TestService.java | 24 +++ .../java/lang/util/TestServiceImpl.java | 33 ++++ ....teavm.classlib.java.lang.util.TestService | 1 + .../java/org/teavm/javascript/Decompiler.java | 36 +++-- .../teavm/javascript/JavascriptBuilder.java | 15 ++ .../javascript/JavascriptBuilderHost.java | 6 + 11 files changed, 352 insertions(+), 15 deletions(-) create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/util/TServiceLoader.java create mode 100644 teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/ServiceLoaderTest.java create mode 100644 teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/TestService.java create mode 100644 teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/TestServiceImpl.java create mode 100644 teavm-classlib/src/test/resources/META-INF/services/org.teavm.classlib.java.lang.util.TestService diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java index 12ef7fd3d..29fa0a65a 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -17,6 +17,9 @@ package org.teavm.classlib.impl; import org.teavm.javascript.JavascriptBuilderHost; import org.teavm.javascript.JavascriptBuilderPlugin; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; /** * @@ -28,5 +31,11 @@ public class JCLPlugin implements JavascriptBuilderPlugin { host.add(new EnumDependencySupport()); host.add(new EnumTransformer()); host.add(new NewInstanceDependencySupport()); + ServiceLoaderSupport serviceLoaderSupp = new ServiceLoaderSupport(host.getClassLoader()); + host.add(serviceLoaderSupp); + MethodReference loadServicesMethod = new MethodReference("java.util.ServiceLoader", new MethodDescriptor( + "loadServices", ValueType.object("java.lang.Class"), + ValueType.arrayOf(ValueType.object("java.lang.Object")))); + host.add(loadServicesMethod, serviceLoaderSupp); } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java new file mode 100644 index 000000000..852148ed5 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java @@ -0,0 +1,144 @@ +/* + * Copyright 2014 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.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.*; +import org.teavm.codegen.SourceWriter; +import org.teavm.dependency.*; +import org.teavm.javascript.ni.Generator; +import org.teavm.javascript.ni.GeneratorContext; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +/** + * + * @author Alexey Andreev + */ +public class ServiceLoaderSupport implements Generator, DependencyListener { + private Map> serviceMap = new HashMap<>(); + private DependencyNode allClassesNode; + private ClassLoader classLoader; + private DependencyStack stack; + + public ServiceLoaderSupport(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + writer.append("if (!").appendClass("java.util.ServiceLoader").append(".$$services$$) {").indent() + .softNewLine(); + writer.appendClass("java.util.ServiceLoader").append("$$services$$ = true;").softNewLine(); + for (Map.Entry> entry : serviceMap.entrySet()) { + writer.appendClass(entry.getKey()).append(".$$serviceList$$ = ["); + List implementations = entry.getValue(); + for (int i = 0; i < implementations.size(); ++i) { + if (i > 0) { + writer.append(", "); + } + String implName = implementations.get(i); + writer.append("[").appendClass(implName).append(", ").appendMethodBody( + new MethodReference(implName, new MethodDescriptor("", ValueType.VOID))) + .append("]"); + } + writer.append("];").softNewLine(); + } + writer.outdent().append("}").softNewLine(); + String param = context.getParameterName(1); + writer.append("var cls = " + param + ".$data;").softNewLine(); + writer.append("if (!cls.$$serviceList$$) {").indent().softNewLine(); + writer.append("return $rt_createArray($rt_objcls(), 0);").softNewLine(); + writer.outdent().append("}").softNewLine(); + writer.append("var result = $rt_createArray($rt_objcls(), cls.$$serviceList$$.length);").softNewLine(); + writer.append("for (var i = 0; i < result.data.length; ++i) {").indent().softNewLine(); + writer.append("var serviceDesc = cls.$$serviceList$$[i];").softNewLine(); + writer.append("result.data[i] = new serviceDesc[0]();").softNewLine(); + writer.append("serviceDesc[1](result.data[i]);").softNewLine(); + writer.outdent().append("}").softNewLine(); + writer.append("return result;").softNewLine(); + } + + @Override + public void started(DependencyChecker dependencyChecker) { + allClassesNode = dependencyChecker.createNode(); + } + + @Override + public void classAchieved(DependencyChecker dependencyChecker, String className) { + try { + Enumeration resources = classLoader.getResources("META-INF/services/" + className); + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + try (InputStream stream = resource.openStream()) { + parseServiceFile(className, stream); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void parseServiceFile(String service, InputStream input) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + line = line.trim(); + if (line.startsWith("#") || line.isEmpty()) { + continue; + } + List implementors = serviceMap.get(service); + if (implementors == null) { + implementors = new ArrayList<>(); + serviceMap.put(service, implementors); + } + implementors.add(line); + allClassesNode.propagate(line); + } + } + + @Override + public void methodAchieved(final DependencyChecker dependencyChecker, MethodDependency method) { + MethodReference ref = method.getReference(); + if (ref.getClassName().equals("java.util.ServiceLoader") && ref.getName().equals("loadServices")) { + method.getResult().propagate("[java.lang.Object"); + stack = method.getStack(); + allClassesNode.connect(method.getResult().getArrayItem()); + method.getResult().getArrayItem().addConsumer(new DependencyConsumer() { + @Override public void consume(String type) { + initConstructor(dependencyChecker, type); + } + }); + } + } + + private void initConstructor(DependencyChecker dependencyChecker, String type) { + MethodReference ctor = new MethodReference(type, new MethodDescriptor("", ValueType.VOID)); + dependencyChecker.linkMethod(ctor, stack).use(); + } + + @Override + public void fieldAchieved(DependencyChecker dependencyChecker, FieldDependency field) { + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index a96bdc74d..a2c9984ee 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -23,7 +23,7 @@ import org.teavm.javascript.ni.InjectedBy; * * @author Alexey Andreev */ -public class TClass extends TObject { +public class TClass extends TObject { TString name; boolean primitive; boolean array; diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TServiceLoader.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TServiceLoader.java new file mode 100644 index 000000000..2d323d79c --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TServiceLoader.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014 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; + +import org.teavm.classlib.java.lang.*; + +/** + * + * @author Alexey Andreev + */ +public final class TServiceLoader extends TObject implements TIterable { + private Object[] services; + + private TServiceLoader(Object[] services) { + this.services = services; + } + + @Override + public TIterator iterator() { + return new TIterator() { + private int index; + @Override public boolean hasNext() { + return index < services.length; + } + @SuppressWarnings("unchecked") @Override public S next() { + if (index == services.length) { + throw new TNoSuchElementException(); + } + return (S)services[index++]; + } + @Override public void remove() { + throw new TUnsupportedOperationException(); + } + }; + } + + public static TServiceLoader load(TClass service) { + return new TServiceLoader<>(loadServices(service)); + } + + public static TServiceLoader load(TClass service, @SuppressWarnings("unused") TClassLoader loader) { + return load(service); + } + + public static TServiceLoader loadInstalled(TClass service) { + return load(service); + } + + private static native T[] loadServices(TClass serviceType); +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/ServiceLoaderTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/ServiceLoaderTest.java new file mode 100644 index 000000000..9093fdc88 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/ServiceLoaderTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 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.util; + +import static org.junit.Assert.*; +import java.util.ServiceLoader; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class ServiceLoaderTest { + @Test + public void loadsService() { + TestService instance = ServiceLoader.load(TestService.class).iterator().next(); + instance.foo(); + assertEquals(TestServiceImpl.class, instance.getClass()); + assertEquals(1, ((TestServiceImpl)instance).getCounter()); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/TestService.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/TestService.java new file mode 100644 index 000000000..540f9787f --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/TestService.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 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.util; + +/** + * + * @author Alexey Andreev + */ +public interface TestService { + void foo(); +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/TestServiceImpl.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/TestServiceImpl.java new file mode 100644 index 000000000..1e3a05ceb --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/util/TestServiceImpl.java @@ -0,0 +1,33 @@ +/* + * Copyright 2014 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.util; + +/** + * + * @author Alexey Andreev + */ +public class TestServiceImpl implements TestService { + private int counter; + + @Override + public void foo() { + ++counter; + } + + public int getCounter() { + return counter; + } +} diff --git a/teavm-classlib/src/test/resources/META-INF/services/org.teavm.classlib.java.lang.util.TestService b/teavm-classlib/src/test/resources/META-INF/services/org.teavm.classlib.java.lang.util.TestService new file mode 100644 index 000000000..277015e69 --- /dev/null +++ b/teavm-classlib/src/test/resources/META-INF/services/org.teavm.classlib.java.lang.util.TestService @@ -0,0 +1 @@ +org.teavm.classlib.java.lang.util.TestServiceImpl \ No newline at end of file diff --git a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java index 86a1e48a6..80377fd47 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -43,6 +43,7 @@ public class Decompiler { private RangeTree.Node currentNode; private RangeTree.Node parentNode; private FiniteExecutor executor; + private Map generators = new HashMap<>(); public Decompiler(ClassHolderSource classSource, ClassLoader classLoader, FiniteExecutor executor) { this.classSource = classSource; @@ -82,6 +83,7 @@ public class Decompiler { executor.execute(new Runnable() { @Override public void run() { Decompiler copy = new Decompiler(classSource, classLoader, executor); + copy.generators = generators; result.set(index, copy.decompile(classSource.get(className))); } }); @@ -90,6 +92,10 @@ public class Decompiler { return result; } + public void addGenerator(MethodReference method, Generator generator) { + generators.put(method, generator); + } + private void orderClasses(String className, Set visited, List order) { if (!visited.add(className)) { return; @@ -139,20 +145,22 @@ public class Decompiler { } public NativeMethodNode decompileNative(MethodHolder method) { - AnnotationHolder annotHolder = method.getAnnotations().get(GeneratedBy.class.getName()); - if (annotHolder == null) { - throw new DecompilationException("Method " + method.getOwnerName() + "." + method.getDescriptor() + - " is native, but no " + GeneratedBy.class.getName() + " annotation found"); - } - ValueType annotValue = annotHolder.getValues().get("value").getJavaClass(); - String generatorClassName = ((ValueType.Object)annotValue).getClassName(); - Generator generator; - try { - Class generatorClass = Class.forName(generatorClassName, true, classLoader); - generator = (Generator)generatorClass.newInstance(); - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { - throw new DecompilationException("Error instantiating generator " + generatorClassName + - " for native method " + method.getOwnerName() + "." + method.getDescriptor()); + Generator generator = generators.get(method.getReference()); + if (generator == null) { + AnnotationHolder annotHolder = method.getAnnotations().get(GeneratedBy.class.getName()); + if (annotHolder == null) { + throw new DecompilationException("Method " + method.getOwnerName() + "." + method.getDescriptor() + + " is native, but no " + GeneratedBy.class.getName() + " annotation found"); + } + ValueType annotValue = annotHolder.getValues().get("value").getJavaClass(); + String generatorClassName = ((ValueType.Object)annotValue).getClassName(); + try { + Class generatorClass = Class.forName(generatorClassName, true, classLoader); + generator = (Generator)generatorClass.newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + throw new DecompilationException("Error instantiating generator " + generatorClassName + + " for native method " + method.getOwnerName() + "." + method.getDescriptor()); + } } NativeMethodNode methodNode = new NativeMethodNode(new MethodReference(method.getOwnerName(), method.getDescriptor())); diff --git a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java index a0fd24f46..1c68c8607 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java +++ b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java @@ -21,6 +21,7 @@ import org.teavm.codegen.*; import org.teavm.common.FiniteExecutor; import org.teavm.dependency.*; import org.teavm.javascript.ast.ClassNode; +import org.teavm.javascript.ni.Generator; import org.teavm.model.*; import org.teavm.model.util.ListingBuilder; import org.teavm.model.util.ProgramUtils; @@ -43,6 +44,7 @@ public class JavascriptBuilder implements JavascriptBuilderHost { private Map entryPoints = new HashMap<>(); private Map exportedClasses = new HashMap<>(); private List ressourceRenderers = new ArrayList<>(); + private Map methodGenerators = new HashMap<>(); JavascriptBuilder(ClassHolderSource classSource, ClassLoader classLoader, FiniteExecutor executor) { this.classSource = new JavascriptProcessedClassSource(classSource); @@ -66,6 +68,16 @@ public class JavascriptBuilder implements JavascriptBuilderHost { ressourceRenderers.add(resourceRenderer); } + @Override + public void add(MethodReference methodRef, Generator generator) { + methodGenerators.put(methodRef, generator); + } + + @Override + public ClassLoader getClassLoader() { + return classLoader; + } + public boolean isMinifying() { return minifying; } @@ -160,6 +172,9 @@ public class JavascriptBuilder implements JavascriptBuilderHost { } Renderer renderer = new Renderer(sourceWriter, classSet, classLoader); renderer.renderRuntime(); + for (Map.Entry entry : methodGenerators.entrySet()) { + decompiler.addGenerator(entry.getKey(), entry.getValue()); + } List clsNodes = decompiler.decompile(classSet.getClassNames()); for (ClassNode clsNode : clsNodes) { renderer.render(clsNode); diff --git a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderHost.java b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderHost.java index bbc0f8714..fd417a0cf 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderHost.java +++ b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilderHost.java @@ -16,7 +16,9 @@ package org.teavm.javascript; import org.teavm.dependency.DependencyListener; +import org.teavm.javascript.ni.Generator; import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.MethodReference; /** * @@ -28,4 +30,8 @@ public interface JavascriptBuilderHost { void add(ClassHolderTransformer classTransformer); void add(JavascriptResourceRenderer resourceRenderer); + + void add(MethodReference methodRef, Generator generator); + + ClassLoader getClassLoader(); }