Fix bootstrap mode

This commit is contained in:
Alexey Andreev 2019-01-20 17:50:46 +03:00
parent b754c7401e
commit 48227b24a0
12 changed files with 241 additions and 110 deletions

View File

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

View File

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

View File

@ -19,17 +19,21 @@ import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.Set;
import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.classlib.ServiceLoaderFilter;
import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.AbstractDependencyListener;
import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyNode; import org.teavm.dependency.DependencyNode;
@ -85,8 +89,48 @@ public class ServiceLoaderSupport extends AbstractDependencyListener implements
writer.append("return result;").softNewLine(); writer.append("return result;").softNewLine();
} }
private void parseServiceFile(DependencyAgent agent, DependencyNode targetNode, String service, @Override
InputStream input, CallLocation location) throws IOException { public void methodReached(DependencyAgent agent, MethodDependency method) {
MethodReference ref = method.getReference();
if (ref.getClassName().equals("java.util.ServiceLoader") && ref.getName().equals("loadServices")) {
List<ServiceLoaderFilter> 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("<init>", ValueType.VOID));
agent.linkMethod(ctor).addLocation(location).use();
method.getResult().getArrayItem().propagate(agent.getType(implementationType));
}
});
}
}
private Set<String> getImplementations(String type) {
Set<String> result = new LinkedHashSet<>();
try {
Enumeration<URL> 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<String> consumer) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
while (true) { while (true) {
String line = reader.readLine(); String line = reader.readLine();
@ -97,38 +141,37 @@ public class ServiceLoaderSupport extends AbstractDependencyListener implements
if (line.startsWith("#") || line.isEmpty()) { if (line.startsWith("#") || line.isEmpty()) {
continue; continue;
} }
serviceMap.computeIfAbsent(service, k -> new ArrayList<>()).add(line);
MethodReference ctor = new MethodReference(line, new MethodDescriptor("<init>", ValueType.VOID)); consumer.add(line);
agent.linkMethod(ctor).addLocation(location).use();
targetNode.propagate(agent.getType(line));
} }
} }
@Override private List<ServiceLoaderFilter> getFilters(DependencyAgent agent) {
public void methodReached(DependencyAgent agent, MethodDependency method) { List<ServiceLoaderFilter> filters = new ArrayList<>();
MethodReference ref = method.getReference(); for (String filterTypeName : getImplementations(ServiceLoaderFilter.class.getName())) {
if (ref.getClassName().equals("java.util.ServiceLoader") && ref.getName().equals("loadServices")) { Class<?> filterType;
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 { try {
Enumeration<URL> resources = classLoader.getResources("META-INF/services/" + type); filterType = Class.forName(filterTypeName, true, classLoader);
while (resources.hasMoreElements()) { } catch (ClassNotFoundException e) {
URL resource = resources.nextElement(); agent.getDiagnostics().error(null, "Could not load ServiceLoader filter class '{{c0}}'",
try (InputStream stream = resource.openStream()) { filterTypeName);
parseServiceFile(agent, targetNode, type, stream, location); 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) { return filters;
throw new RuntimeException(e);
}
} }
} }

View File

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

View File

@ -18,12 +18,6 @@ package org.teavm.classlib.java.util;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
/**
*
* @author Alexey Andreev
* @param <K>
* @param <V>
*/
public interface TMap<K, V> { public interface TMap<K, V> {
interface Entry<K1, V1> { interface Entry<K1, V1> {
K1 getKey(); K1 getKey();
@ -43,6 +37,10 @@ public interface TMap<K, V> {
V get(Object key); V get(Object key);
default V getOrDefault(K key, V defaultValue) {
return containsKey(key) ? get(key) : defaultValue;
}
V put(K key, V value); V put(K key, V value);
V remove(Object key); V remove(Object key);

View File

@ -453,9 +453,25 @@ public class Renderer implements RenderingManager {
int start = writer.getOffset(); int start = writer.getOffset();
try { try {
writer.append("$rt_metadata(["); writer.append("$rt_packages([");
ObjectIntMap<String> packageIndexes = generatePackageMetadata(classes, classesRequiringName); ObjectIntMap<String> packageIndexes = generatePackageMetadata(classes, classesRequiringName);
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);
}
metadataSize = writer.getOffset() - start;
}
private void renderClassMetadataPortion(List<ClassNode> classes, ObjectIntMap<String> packageIndexes,
Set<String> classesRequiringName) throws IOException {
writer.append("$rt_metadata([");
boolean first = true; boolean first = true;
for (ClassNode cls : classes) { for (ClassNode cls : classes) {
if (!first) { if (!first) {
@ -515,11 +531,6 @@ public class Renderer implements RenderingManager {
renderVirtualDeclarations(virtualMethods); renderVirtualDeclarations(virtualMethods);
} }
writer.append("]);").newLine(); writer.append("]);").newLine();
} catch (IOException e) {
throw new RenderingException("IO error occurred", e);
}
metadataSize = writer.getOffset() - start;
} }
private ObjectIntMap<String> generatePackageMetadata(List<ClassNode> classes, Set<String> classesRequiringName) private ObjectIntMap<String> generatePackageMetadata(List<ClassNode> classes, Set<String> classesRequiringName)
@ -541,7 +552,6 @@ public class Renderer implements RenderingManager {
} }
ObjectIntMap<String> indexes = new ObjectIntHashMap<>(); ObjectIntMap<String> indexes = new ObjectIntHashMap<>();
writer.append(String.valueOf(root.count())).append(",").ws();
writePackageStructure(root, -1, "", indexes); writePackageStructure(root, -1, "", indexes);
writer.softNewLine(); writer.softNewLine();
return indexes; return indexes;
@ -551,8 +561,11 @@ public class Renderer implements RenderingManager {
throws IOException { throws IOException {
int index = startIndex; int index = startIndex;
for (PackageNode child : node.children.values()) { for (PackageNode child : node.children.values()) {
if (index >= 0) {
writer.append(",").ws();
}
writer.append(String.valueOf(startIndex)).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 + "."; String fullName = prefix + child.name + ".";
++index; ++index;
indexes.put(fullName, index); indexes.put(fullName, index);

View File

@ -419,16 +419,20 @@ var $rt_putStderr = typeof $rt_putStderrCustom === "function" ? $rt_putStderrCus
$rt_stderrBuffer += String.fromCharCode(ch); $rt_stderrBuffer += String.fromCharCode(ch);
} }
}; };
function $rt_metadata(data) { var $rt_packageData = null;
function $rt_packages(data) {
var i = 0; var i = 0;
var packageCount = data[i++]; var packages = new Array(data.length);
var packages = new Array(packageCount); for (var j = 0; j < data.length; ++j) {
for (var j = 0; j < packageCount; ++j) {
var prefixIndex = data[i++]; var prefixIndex = data[i++];
var prefix = prefixIndex >= 0 ? packages[prefixIndex] : ""; var prefix = prefixIndex >= 0 ? packages[prefixIndex] : "";
packages[j] = prefix + data[i++] + "."; packages[j] = prefix + data[i++] + ".";
} }
$rt_packageData = packages;
}
function $rt_metadata(data) {
var packages = $rt_packageData;
var i = 0;
while (i < data.length) { while (i < data.length) {
var cls = data[i++]; var cls = data[i++];
cls.$meta = {}; cls.$meta = {};
@ -468,7 +472,7 @@ function $rt_metadata(data) {
var virtualMethods = data[i++]; var virtualMethods = data[i++];
if (virtualMethods !== 0) { 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 name = virtualMethods[j];
var func = virtualMethods[j + 1]; var func = virtualMethods[j + 1];
if (typeof name === 'string') { if (typeof name === 'string') {

View File

@ -23,7 +23,7 @@ class BuildTimeResourceGetter implements BuildTimeResourceMethod {
} }
@Override @Override
public Object invoke(BuildTimeResourceProxy proxy, Object[] args) throws Throwable { public Object invoke(BuildTimeResourceProxy proxy, Object[] args) {
return proxy.data[index]; return proxy.data[index];
} }
} }

View File

@ -24,7 +24,7 @@ class BuildTimeResourceProxy implements InvocationHandler {
private Map<Method, BuildTimeResourceMethod> methods; private Map<Method, BuildTimeResourceMethod> methods;
Object[] data; Object[] data;
public BuildTimeResourceProxy(Map<Method, BuildTimeResourceMethod> methods, Object[] initialData) { BuildTimeResourceProxy(Map<Method, BuildTimeResourceMethod> methods, Object[] initialData) {
this.methods = methods; this.methods = methods;
data = Arrays.copyOf(initialData, initialData.length); data = Arrays.copyOf(initialData, initialData.length);
} }

View File

@ -51,7 +51,7 @@ class BuildTimeResourceProxyBuilder {
private Object[] initialData; private Object[] initialData;
private ResourceTypeDescriptor descriptor; private ResourceTypeDescriptor descriptor;
public ProxyFactoryCreation(ResourceTypeDescriptor descriptor) { ProxyFactoryCreation(ResourceTypeDescriptor descriptor) {
this.descriptor = descriptor; this.descriptor = descriptor;
int index = 0; int index = 0;
for (String propertyName : descriptor.getPropertyTypes().keySet()) { for (String propertyName : descriptor.getPropertyTypes().keySet()) {

View File

@ -23,7 +23,7 @@ class BuildTimeResourceProxyFactory {
private Object[] initialData; private Object[] initialData;
ResourceTypeDescriptor typeDescriptor; ResourceTypeDescriptor typeDescriptor;
public BuildTimeResourceProxyFactory(Map<Method, BuildTimeResourceMethod> methods, Object[] initialData, BuildTimeResourceProxyFactory(Map<Method, BuildTimeResourceMethod> methods, Object[] initialData,
ResourceTypeDescriptor typeDescriptor) { ResourceTypeDescriptor typeDescriptor) {
this.methods = methods; this.methods = methods;
this.initialData = initialData; this.initialData = initialData;

View File

@ -31,7 +31,7 @@ class DefaultMetadataGeneratorContext implements MetadataGeneratorContext {
private ClassReaderSource classSource; private ClassReaderSource classSource;
private ClassLoader classLoader; private ClassLoader classLoader;
private Properties properties; private Properties properties;
private BuildTimeResourceProxyBuilder proxyBuilder = new BuildTimeResourceProxyBuilder(); private BuildTimeResourceProxyBuilder proxyBuilder;
private ServiceRepository services; private ServiceRepository services;
DefaultMetadataGeneratorContext(ClassReaderSource classSource, ClassLoader classLoader, DefaultMetadataGeneratorContext(ClassReaderSource classSource, ClassLoader classLoader,
@ -42,6 +42,13 @@ class DefaultMetadataGeneratorContext implements MetadataGeneratorContext {
this.services = services; this.services = services;
} }
private BuildTimeResourceProxyBuilder getProxyBuilder() {
if (proxyBuilder == null) {
proxyBuilder = new BuildTimeResourceProxyBuilder();
}
return proxyBuilder;
}
@Override @Override
public ClassReaderSource getClassSource() { public ClassReaderSource getClassSource() {
return classSource; return classSource;
@ -61,12 +68,12 @@ class DefaultMetadataGeneratorContext implements MetadataGeneratorContext {
public <T extends Resource> T createResource(Class<T> resourceType) { public <T extends Resource> T createResource(Class<T> resourceType) {
return resourceType.cast(Proxy.newProxyInstance(classLoader, return resourceType.cast(Proxy.newProxyInstance(classLoader,
new Class<?>[] { resourceType, ResourceWriter.class, ResourceTypeDescriptorProvider.class }, new Class<?>[] { resourceType, ResourceWriter.class, ResourceTypeDescriptorProvider.class },
proxyBuilder.buildProxy(resourceType))); getProxyBuilder().buildProxy(resourceType)));
} }
@Override @Override
public ResourceTypeDescriptor getTypeDescriptor(Class<? extends Resource> type) { public ResourceTypeDescriptor getTypeDescriptor(Class<? extends Resource> type) {
return proxyBuilder.getProxyFactory(type).typeDescriptor; return getProxyBuilder().getProxyFactory(type).typeDescriptor;
} }
@Override @Override