Adds java.util.ServiceLoader support

This commit is contained in:
konsoletyper 2014-02-24 00:50:06 +04:00
parent 3195879467
commit d34e26e970
11 changed files with 352 additions and 15 deletions

View File

@ -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);
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
public class ServiceLoaderSupport implements Generator, DependencyListener {
private Map<String, List<String>> 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<String, List<String>> entry : serviceMap.entrySet()) {
writer.appendClass(entry.getKey()).append(".$$serviceList$$ = [");
List<String> 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("<init>", 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<URL> 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<String> 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("<init>", ValueType.VOID));
dependencyChecker.linkMethod(ctor, stack).use();
}
@Override
public void fieldAchieved(DependencyChecker dependencyChecker, FieldDependency field) {
}
}

View File

@ -23,7 +23,7 @@ import org.teavm.javascript.ni.InjectedBy;
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public class TClass<T extends TObject> extends TObject {
public class TClass<T> extends TObject {
TString name;
boolean primitive;
boolean array;

View File

@ -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 <konsoletyper@gmail.com>
*/
public final class TServiceLoader<S> extends TObject implements TIterable<S> {
private Object[] services;
private TServiceLoader(Object[] services) {
this.services = services;
}
@Override
public TIterator<S> iterator() {
return new TIterator<S>() {
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 <S> TServiceLoader<S> load(TClass<S> service) {
return new TServiceLoader<>(loadServices(service));
}
public static <S> TServiceLoader<S> load(TClass<S> service, @SuppressWarnings("unused") TClassLoader loader) {
return load(service);
}
public static <S> TServiceLoader<S> loadInstalled(TClass<S> service) {
return load(service);
}
private static native <T> T[] loadServices(TClass<T> serviceType);
}

View File

@ -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 <konsoletyper@gmail.com>
*/
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());
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
public interface TestService {
void foo();
}

View File

@ -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 <konsoletyper@gmail.com>
*/
public class TestServiceImpl implements TestService {
private int counter;
@Override
public void foo() {
++counter;
}
public int getCounter() {
return counter;
}
}

View File

@ -0,0 +1 @@
org.teavm.classlib.java.lang.util.TestServiceImpl

View File

@ -43,6 +43,7 @@ public class Decompiler {
private RangeTree.Node currentNode;
private RangeTree.Node parentNode;
private FiniteExecutor executor;
private Map<MethodReference, Generator> 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<String> visited, List<String> 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()));

View File

@ -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<String, JavascriptEntryPoint> entryPoints = new HashMap<>();
private Map<String, String> exportedClasses = new HashMap<>();
private List<JavascriptResourceRenderer> ressourceRenderers = new ArrayList<>();
private Map<MethodReference, Generator> 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<MethodReference, Generator> entry : methodGenerators.entrySet()) {
decompiler.addGenerator(entry.getKey(), entry.getValue());
}
List<ClassNode> clsNodes = decompiler.decompile(classSet.getClassNames());
for (ClassNode clsNode : clsNodes) {
renderer.render(clsNode);

View File

@ -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();
}