mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
Support plugin loading from bootstrap environment
This commit is contained in:
parent
cb355a85f5
commit
256814a60a
|
@ -40,6 +40,11 @@
|
||||||
<artifactId>teavm-interop</artifactId>
|
<artifactId>teavm-interop</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.teavm</groupId>
|
||||||
|
<artifactId>teavm-metaprogramming-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-io</groupId>
|
<groupId>commons-io</groupId>
|
||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-io</artifactId>
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 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.vm;
|
||||||
|
|
||||||
|
import static org.teavm.metaprogramming.Metaprogramming.emit;
|
||||||
|
import static org.teavm.metaprogramming.Metaprogramming.exit;
|
||||||
|
import static org.teavm.metaprogramming.Metaprogramming.findClass;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.teavm.metaprogramming.CompileTime;
|
||||||
|
import org.teavm.metaprogramming.Meta;
|
||||||
|
import org.teavm.metaprogramming.Metaprogramming;
|
||||||
|
import org.teavm.metaprogramming.ReflectClass;
|
||||||
|
import org.teavm.metaprogramming.Value;
|
||||||
|
import org.teavm.metaprogramming.reflect.ReflectMethod;
|
||||||
|
import org.teavm.vm.spi.TeaVMPlugin;
|
||||||
|
|
||||||
|
@CompileTime
|
||||||
|
public final class TeaVMBootstrapPluginLoader {
|
||||||
|
private TeaVMBootstrapPluginLoader() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Meta
|
||||||
|
public native static List<TeaVMPlugin> loadPlugins(boolean ignore);
|
||||||
|
|
||||||
|
private static void loadPlugins(Value<Boolean> ignore) {
|
||||||
|
Value<List<TeaVMPlugin>> plugins = emit(() -> new ArrayList<>());
|
||||||
|
List<String> pluginClassNames = new ArrayList<>();
|
||||||
|
TeaVMPluginReader.load(Metaprogramming.getClassLoader(), pluginClassNames::add);
|
||||||
|
for (String pluginClassName : pluginClassNames) {
|
||||||
|
ReflectClass<?> cls = findClass(pluginClassName);
|
||||||
|
ReflectMethod constructor = cls.getMethod("<init>");
|
||||||
|
emit(() -> plugins.get().add((TeaVMPlugin) constructor.construct()));
|
||||||
|
}
|
||||||
|
exit(() -> plugins.get());
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,191 +15,28 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.vm;
|
package org.teavm.vm;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import org.teavm.interop.PlatformMarker;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import org.objectweb.asm.AnnotationVisitor;
|
|
||||||
import org.objectweb.asm.ClassReader;
|
|
||||||
import org.objectweb.asm.ClassVisitor;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.teavm.vm.spi.After;
|
|
||||||
import org.teavm.vm.spi.Before;
|
|
||||||
import org.teavm.vm.spi.Requires;
|
|
||||||
import org.teavm.vm.spi.TeaVMPlugin;
|
import org.teavm.vm.spi.TeaVMPlugin;
|
||||||
|
|
||||||
public final class TeaVMPluginLoader {
|
public final class TeaVMPluginLoader {
|
||||||
private static final String DESCRIPTOR_LOCATION = "META-INF/services/org.teavm.vm.spi.TeaVMPlugin";
|
|
||||||
private static final String REQUIRES_DESC = Type.getDescriptor(Requires.class);
|
|
||||||
private static final String BEFORE_DESC = Type.getDescriptor(Before.class);
|
|
||||||
private static final String AFTER_DESC = Type.getDescriptor(After.class);
|
|
||||||
|
|
||||||
private TeaVMPluginLoader() {
|
private TeaVMPluginLoader() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<TeaVMPlugin> load(ClassLoader classLoader) {
|
public static List<TeaVMPlugin> load(ClassLoader classLoader) {
|
||||||
Set<String> unorderedPlugins = new HashSet<>();
|
if (isBootstrap()) {
|
||||||
try {
|
return TeaVMBootstrapPluginLoader.loadPlugins(false);
|
||||||
Enumeration<URL> resourceFiles = classLoader.getResources(DESCRIPTOR_LOCATION);
|
} else {
|
||||||
while (resourceFiles.hasMoreElements()) {
|
List<TeaVMPlugin> plugins = new ArrayList<>();
|
||||||
URL resourceFile = resourceFiles.nextElement();
|
TeaVMPluginReader.load(classLoader, className -> plugins.add(instantiate(classLoader, className)));
|
||||||
try (BufferedReader input = new BufferedReader(
|
return plugins;
|
||||||
new InputStreamReader(resourceFile.openStream(), "UTF-8"))) {
|
|
||||||
readPlugins(input, unorderedPlugins);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException("Error loading plugins", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return orderPlugins(classLoader, unorderedPlugins).stream().map(p -> instantiate(classLoader, p))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<String> orderPlugins(ClassLoader classLoader, Set<String> classNames) {
|
|
||||||
Map<String, PluginDescriptor> descriptors = new HashMap<>();
|
|
||||||
try {
|
|
||||||
for (String className : classNames) {
|
|
||||||
PluginDescriptor descriptor = new PluginDescriptor();
|
|
||||||
descriptor.name = className;
|
|
||||||
if (readDescriptor(classLoader, className, descriptor)) {
|
|
||||||
descriptors.put(className, descriptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException("Error ordering plugins", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
findReachableDescriptors(descriptors);
|
|
||||||
processDescriptors(descriptors);
|
|
||||||
List<String> plugins = new ArrayList<>();
|
|
||||||
Set<String> visited = new HashSet<>();
|
|
||||||
Set<String> emmited = new HashSet<>();
|
|
||||||
for (PluginDescriptor descriptor : descriptors.values()) {
|
|
||||||
orderDescriptors(descriptor, descriptors, plugins, visited, emmited, new ArrayList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return plugins;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void readPlugins(BufferedReader input, Set<String> plugins) throws IOException {
|
|
||||||
while (true) {
|
|
||||||
String line = input.readLine();
|
|
||||||
if (line == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
line = line.trim();
|
|
||||||
if (line.isEmpty() || line.startsWith("#")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins.add(line);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean readDescriptor(ClassLoader classLoader, String className, PluginDescriptor descriptor)
|
@PlatformMarker
|
||||||
throws IOException {
|
private static boolean isBootstrap() {
|
||||||
try (InputStream input = classLoader.getResourceAsStream(className.replace('.', '/') + ".class")) {
|
return false;
|
||||||
if (input == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ClassReader reader = new ClassReader(new BufferedInputStream(input));
|
|
||||||
PluginDescriptorFiller filler = new PluginDescriptorFiller(descriptor);
|
|
||||||
reader.accept(filler, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void findReachableDescriptors(Map<String, PluginDescriptor> descriptors) {
|
|
||||||
Set<String> visited = new HashSet<>();
|
|
||||||
for (String plugin : descriptors.keySet()) {
|
|
||||||
isReachable(plugin, visited, descriptors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isReachable(String plugin, Set<String> visited, Map<String, PluginDescriptor> descriptors) {
|
|
||||||
PluginDescriptor descriptor = descriptors.get(plugin);
|
|
||||||
if (descriptor == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!visited.add(plugin)) {
|
|
||||||
return descriptor.reachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean reachable = true;
|
|
||||||
for (String required : descriptor.requires) {
|
|
||||||
if (!isReachable(required, visited, descriptors)) {
|
|
||||||
reachable = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
descriptor.reachable = reachable;
|
|
||||||
return reachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void processDescriptors(Map<String, PluginDescriptor> descriptors) {
|
|
||||||
for (PluginDescriptor descriptor : descriptors.values()) {
|
|
||||||
if (descriptor.after.length > 0 && descriptor.before.length > 0) {
|
|
||||||
throw new IllegalStateException("Plugin " + descriptor.name
|
|
||||||
+ " has both before and after annotations");
|
|
||||||
}
|
|
||||||
descriptor.afterList.addAll(Arrays.asList(descriptor.after));
|
|
||||||
for (String before : descriptor.before) {
|
|
||||||
PluginDescriptor beforeDescriptor = descriptors.get(before);
|
|
||||||
if (beforeDescriptor != null && beforeDescriptor.reachable) {
|
|
||||||
beforeDescriptor.afterList.add(descriptor.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void orderDescriptors(PluginDescriptor descriptor, Map<String, PluginDescriptor> descriptors,
|
|
||||||
List<String> list, Set<String> visited, Set<String> emmited, List<String> path) {
|
|
||||||
if (!descriptor.reachable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!visited.add(descriptor.name)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
path.add(descriptor.name);
|
|
||||||
|
|
||||||
for (String after : descriptor.afterList) {
|
|
||||||
PluginDescriptor afterDescriptor = descriptors.get(after);
|
|
||||||
if (afterDescriptor != null && afterDescriptor.reachable) {
|
|
||||||
if (visited.contains(after) && !emmited.contains(after)) {
|
|
||||||
List<String> loop = new ArrayList<>();
|
|
||||||
Collections.reverse(path);
|
|
||||||
for (String pathElem : path) {
|
|
||||||
loop.add(pathElem);
|
|
||||||
if (pathElem.equals(afterDescriptor.name)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collections.reverse(loop);
|
|
||||||
throw new IllegalStateException("Circular dependency found: " + loop.stream()
|
|
||||||
.collect(Collectors.joining(" -> ")));
|
|
||||||
}
|
|
||||||
orderDescriptors(afterDescriptor, descriptors, list, visited, emmited, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emmited.add(descriptor.name);
|
|
||||||
path.remove(descriptor.name);
|
|
||||||
list.add(descriptor.name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TeaVMPlugin instantiate(ClassLoader classLoader, String className) {
|
private static TeaVMPlugin instantiate(ClassLoader classLoader, String className) {
|
||||||
|
@ -209,56 +46,4 @@ public final class TeaVMPluginLoader {
|
||||||
throw new IllegalStateException("Can't instantiate plugin " + className, e);
|
throw new IllegalStateException("Can't instantiate plugin " + className, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class PluginDescriptorFiller extends ClassVisitor {
|
|
||||||
PluginDescriptor descriptor;
|
|
||||||
|
|
||||||
public PluginDescriptorFiller(PluginDescriptor descriptor) {
|
|
||||||
super(Opcodes.ASM5);
|
|
||||||
this.descriptor = descriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
|
||||||
if (desc.equals(REQUIRES_DESC)) {
|
|
||||||
return readClassArray(arr -> descriptor.requires = arr);
|
|
||||||
} else if (desc.equals(BEFORE_DESC)) {
|
|
||||||
return readClassArray(arr -> descriptor.before = arr);
|
|
||||||
} else if (desc.equals(AFTER_DESC)) {
|
|
||||||
return readClassArray(arr -> descriptor.after = arr);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AnnotationVisitor readClassArray(Consumer<String[]> resultConsumer) {
|
|
||||||
return new AnnotationVisitor(Opcodes.ASM5) {
|
|
||||||
@Override
|
|
||||||
public AnnotationVisitor visitArray(String name) {
|
|
||||||
List<String> values = new ArrayList<>();
|
|
||||||
if (name.equals("value")) {
|
|
||||||
return new AnnotationVisitor(Opcodes.ASM5) {
|
|
||||||
@Override
|
|
||||||
public void visit(String name, Object value) {
|
|
||||||
values.add(((Type) value).getClassName());
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void visitEnd() {
|
|
||||||
resultConsumer.accept(values.toArray(new String[0]));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class PluginDescriptor {
|
|
||||||
String name;
|
|
||||||
String[] requires = new String[0];
|
|
||||||
String[] before = new String[0];
|
|
||||||
String[] after = new String[0];
|
|
||||||
List<String> afterList = new ArrayList<>();
|
|
||||||
boolean reachable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
256
core/src/main/java/org/teavm/vm/TeaVMPluginReader.java
Normal file
256
core/src/main/java/org/teavm/vm/TeaVMPluginReader.java
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 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.vm;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.objectweb.asm.AnnotationVisitor;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.teavm.metaprogramming.CompileTime;
|
||||||
|
import org.teavm.vm.spi.After;
|
||||||
|
import org.teavm.vm.spi.Before;
|
||||||
|
import org.teavm.vm.spi.Requires;
|
||||||
|
|
||||||
|
@CompileTime
|
||||||
|
final class TeaVMPluginReader {
|
||||||
|
static final String DESCRIPTOR_LOCATION = "META-INF/services/org.teavm.vm.spi.TeaVMPlugin";
|
||||||
|
static final String REQUIRES_DESC = Type.getDescriptor(Requires.class);
|
||||||
|
static final String BEFORE_DESC = Type.getDescriptor(Before.class);
|
||||||
|
static final String AFTER_DESC = Type.getDescriptor(After.class);
|
||||||
|
|
||||||
|
private TeaVMPluginReader() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void load(ClassLoader classLoader, Consumer<String> consumer) {
|
||||||
|
Set<String> unorderedPlugins = new HashSet<>();
|
||||||
|
try {
|
||||||
|
Enumeration<URL> resourceFiles = classLoader.getResources(DESCRIPTOR_LOCATION);
|
||||||
|
while (resourceFiles.hasMoreElements()) {
|
||||||
|
URL resourceFile = resourceFiles.nextElement();
|
||||||
|
try (BufferedReader input = new BufferedReader(
|
||||||
|
new InputStreamReader(resourceFile.openStream(), "UTF-8"))) {
|
||||||
|
readPlugins(input, unorderedPlugins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Error loading plugins", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
orderPlugins(classLoader, unorderedPlugins).forEach(consumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> orderPlugins(ClassLoader classLoader, Set<String> classNames) {
|
||||||
|
Map<String, PluginDescriptor> descriptors = new HashMap<>();
|
||||||
|
try {
|
||||||
|
for (String className : classNames) {
|
||||||
|
PluginDescriptor descriptor = new PluginDescriptor();
|
||||||
|
descriptor.name = className;
|
||||||
|
if (readDescriptor(classLoader, className, descriptor)) {
|
||||||
|
descriptors.put(className, descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Error ordering plugins", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
findReachableDescriptors(descriptors);
|
||||||
|
processDescriptors(descriptors);
|
||||||
|
List<String> plugins = new ArrayList<>();
|
||||||
|
Set<String> visited = new HashSet<>();
|
||||||
|
Set<String> emmited = new HashSet<>();
|
||||||
|
for (PluginDescriptor descriptor : descriptors.values()) {
|
||||||
|
orderDescriptors(descriptor, descriptors, plugins, visited, emmited, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readPlugins(BufferedReader input, Set<String> plugins) throws IOException {
|
||||||
|
while (true) {
|
||||||
|
String line = input.readLine();
|
||||||
|
if (line == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
line = line.trim();
|
||||||
|
if (line.isEmpty() || line.startsWith("#")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean readDescriptor(ClassLoader classLoader, String className, PluginDescriptor descriptor)
|
||||||
|
throws IOException {
|
||||||
|
try (InputStream input = classLoader.getResourceAsStream(className.replace('.', '/') + ".class")) {
|
||||||
|
if (input == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ClassReader reader = new ClassReader(new BufferedInputStream(input));
|
||||||
|
PluginDescriptorFiller filler = new PluginDescriptorFiller(descriptor);
|
||||||
|
reader.accept(filler, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void findReachableDescriptors(Map<String, PluginDescriptor> descriptors) {
|
||||||
|
Set<String> visited = new HashSet<>();
|
||||||
|
for (String plugin : descriptors.keySet()) {
|
||||||
|
isReachable(plugin, visited, descriptors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isReachable(String plugin, Set<String> visited, Map<String, PluginDescriptor> descriptors) {
|
||||||
|
PluginDescriptor descriptor = descriptors.get(plugin);
|
||||||
|
if (descriptor == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!visited.add(plugin)) {
|
||||||
|
return descriptor.reachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean reachable = true;
|
||||||
|
for (String required : descriptor.requires) {
|
||||||
|
if (!isReachable(required, visited, descriptors)) {
|
||||||
|
reachable = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descriptor.reachable = reachable;
|
||||||
|
return reachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processDescriptors(Map<String, PluginDescriptor> descriptors) {
|
||||||
|
for (PluginDescriptor descriptor : descriptors.values()) {
|
||||||
|
if (descriptor.after.length > 0 && descriptor.before.length > 0) {
|
||||||
|
throw new IllegalStateException("Plugin " + descriptor.name
|
||||||
|
+ " has both before and after annotations");
|
||||||
|
}
|
||||||
|
descriptor.afterList.addAll(Arrays.asList(descriptor.after));
|
||||||
|
for (String before : descriptor.before) {
|
||||||
|
PluginDescriptor beforeDescriptor = descriptors.get(before);
|
||||||
|
if (beforeDescriptor != null && beforeDescriptor.reachable) {
|
||||||
|
beforeDescriptor.afterList.add(descriptor.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void orderDescriptors(PluginDescriptor descriptor, Map<String, PluginDescriptor> descriptors,
|
||||||
|
List<String> list, Set<String> visited, Set<String> emmited, List<String> path) {
|
||||||
|
if (!descriptor.reachable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!visited.add(descriptor.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
path.add(descriptor.name);
|
||||||
|
|
||||||
|
for (String after : descriptor.afterList) {
|
||||||
|
PluginDescriptor afterDescriptor = descriptors.get(after);
|
||||||
|
if (afterDescriptor != null && afterDescriptor.reachable) {
|
||||||
|
if (visited.contains(after) && !emmited.contains(after)) {
|
||||||
|
List<String> loop = new ArrayList<>();
|
||||||
|
Collections.reverse(path);
|
||||||
|
for (String pathElem : path) {
|
||||||
|
loop.add(pathElem);
|
||||||
|
if (pathElem.equals(afterDescriptor.name)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.reverse(loop);
|
||||||
|
throw new IllegalStateException("Circular dependency found: " + loop.stream()
|
||||||
|
.collect(Collectors.joining(" -> ")));
|
||||||
|
}
|
||||||
|
orderDescriptors(afterDescriptor, descriptors, list, visited, emmited, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emmited.add(descriptor.name);
|
||||||
|
path.remove(descriptor.name);
|
||||||
|
list.add(descriptor.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PluginDescriptorFiller extends ClassVisitor {
|
||||||
|
PluginDescriptor descriptor;
|
||||||
|
|
||||||
|
public PluginDescriptorFiller(PluginDescriptor descriptor) {
|
||||||
|
super(Opcodes.ASM5);
|
||||||
|
this.descriptor = descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||||
|
if (desc.equals(REQUIRES_DESC)) {
|
||||||
|
return readClassArray(arr -> descriptor.requires = arr);
|
||||||
|
} else if (desc.equals(BEFORE_DESC)) {
|
||||||
|
return readClassArray(arr -> descriptor.before = arr);
|
||||||
|
} else if (desc.equals(AFTER_DESC)) {
|
||||||
|
return readClassArray(arr -> descriptor.after = arr);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AnnotationVisitor readClassArray(Consumer<String[]> resultConsumer) {
|
||||||
|
return new AnnotationVisitor(Opcodes.ASM5) {
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitArray(String name) {
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
if (name.equals("value")) {
|
||||||
|
return new AnnotationVisitor(Opcodes.ASM5) {
|
||||||
|
@Override
|
||||||
|
public void visit(String name, Object value) {
|
||||||
|
values.add(((Type) value).getClassName());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void visitEnd() {
|
||||||
|
resultConsumer.accept(values.toArray(new String[0]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PluginDescriptor {
|
||||||
|
String name;
|
||||||
|
String[] requires = new String[0];
|
||||||
|
String[] before = new String[0];
|
||||||
|
String[] after = new String[0];
|
||||||
|
List<String> afterList = new ArrayList<>();
|
||||||
|
boolean reachable;
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,7 +56,7 @@ public class PluginLoaderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> order(Class<?>... classes) {
|
private List<String> order(Class<?>... classes) {
|
||||||
return TeaVMPluginLoader.orderPlugins(PluginLoaderTest.class.getClassLoader(),
|
return TeaVMPluginReader.orderPlugins(PluginLoaderTest.class.getClassLoader(),
|
||||||
Arrays.stream(classes).map(Class::getName).collect(Collectors.toSet()));
|
Arrays.stream(classes).map(Class::getName).collect(Collectors.toSet()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user