diff --git a/core/pom.xml b/core/pom.xml
index 56c80d364..b942865fa 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -40,6 +40,11 @@
teavm-interop
${project.version}
+
+ org.teavm
+ teavm-metaprogramming-api
+ ${project.version}
+
commons-io
commons-io
diff --git a/core/src/main/java/org/teavm/vm/TeaVMBootstrapPluginLoader.java b/core/src/main/java/org/teavm/vm/TeaVMBootstrapPluginLoader.java
new file mode 100644
index 000000000..0b84c2080
--- /dev/null
+++ b/core/src/main/java/org/teavm/vm/TeaVMBootstrapPluginLoader.java
@@ -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 loadPlugins(boolean ignore);
+
+ private static void loadPlugins(Value ignore) {
+ Value> plugins = emit(() -> new ArrayList<>());
+ List pluginClassNames = new ArrayList<>();
+ TeaVMPluginReader.load(Metaprogramming.getClassLoader(), pluginClassNames::add);
+ for (String pluginClassName : pluginClassNames) {
+ ReflectClass> cls = findClass(pluginClassName);
+ ReflectMethod constructor = cls.getMethod("");
+ emit(() -> plugins.get().add((TeaVMPlugin) constructor.construct()));
+ }
+ exit(() -> plugins.get());
+ }
+}
diff --git a/core/src/main/java/org/teavm/vm/TeaVMPluginLoader.java b/core/src/main/java/org/teavm/vm/TeaVMPluginLoader.java
index d4e6f40fe..fccd286fe 100644
--- a/core/src/main/java/org/teavm/vm/TeaVMPluginLoader.java
+++ b/core/src/main/java/org/teavm/vm/TeaVMPluginLoader.java
@@ -15,191 +15,28 @@
*/
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.vm.spi.After;
-import org.teavm.vm.spi.Before;
-import org.teavm.vm.spi.Requires;
+import org.teavm.interop.PlatformMarker;
import org.teavm.vm.spi.TeaVMPlugin;
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() {
}
public static List load(ClassLoader classLoader) {
- Set unorderedPlugins = new HashSet<>();
- try {
- Enumeration 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);
- }
-
- return orderPlugins(classLoader, unorderedPlugins).stream().map(p -> instantiate(classLoader, p))
- .collect(Collectors.toList());
- }
-
- static List orderPlugins(ClassLoader classLoader, Set classNames) {
- Map 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 plugins = new ArrayList<>();
- Set visited = new HashSet<>();
- Set 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 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);
+ if (isBootstrap()) {
+ return TeaVMBootstrapPluginLoader.loadPlugins(false);
+ } else {
+ List plugins = new ArrayList<>();
+ TeaVMPluginReader.load(classLoader, className -> plugins.add(instantiate(classLoader, className)));
+ return plugins;
}
}
- 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 descriptors) {
- Set visited = new HashSet<>();
- for (String plugin : descriptors.keySet()) {
- isReachable(plugin, visited, descriptors);
- }
- }
-
- private static boolean isReachable(String plugin, Set visited, Map 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 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 descriptors,
- List list, Set visited, Set emmited, List 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 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);
+ @PlatformMarker
+ private static boolean isBootstrap() {
+ return false;
}
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);
}
}
-
- 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 resultConsumer) {
- return new AnnotationVisitor(Opcodes.ASM5) {
- @Override
- public AnnotationVisitor visitArray(String name) {
- List 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 afterList = new ArrayList<>();
- boolean reachable;
- }
}
diff --git a/core/src/main/java/org/teavm/vm/TeaVMPluginReader.java b/core/src/main/java/org/teavm/vm/TeaVMPluginReader.java
new file mode 100644
index 000000000..360e746d2
--- /dev/null
+++ b/core/src/main/java/org/teavm/vm/TeaVMPluginReader.java
@@ -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 consumer) {
+ Set unorderedPlugins = new HashSet<>();
+ try {
+ Enumeration 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 orderPlugins(ClassLoader classLoader, Set classNames) {
+ Map 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 plugins = new ArrayList<>();
+ Set visited = new HashSet<>();
+ Set emmited = new HashSet<>();
+ for (PluginDescriptor descriptor : descriptors.values()) {
+ orderDescriptors(descriptor, descriptors, plugins, visited, emmited, new ArrayList<>());
+ }
+
+ return plugins;
+ }
+
+ static void readPlugins(BufferedReader input, Set 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 descriptors) {
+ Set visited = new HashSet<>();
+ for (String plugin : descriptors.keySet()) {
+ isReachable(plugin, visited, descriptors);
+ }
+ }
+
+ private static boolean isReachable(String plugin, Set visited, Map 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 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 descriptors,
+ List list, Set visited, Set emmited, List 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 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 resultConsumer) {
+ return new AnnotationVisitor(Opcodes.ASM5) {
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ List 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 afterList = new ArrayList<>();
+ boolean reachable;
+ }
+}
diff --git a/core/src/test/java/org/teavm/vm/PluginLoaderTest.java b/core/src/test/java/org/teavm/vm/PluginLoaderTest.java
index 5870ff301..ad81e5a2f 100644
--- a/core/src/test/java/org/teavm/vm/PluginLoaderTest.java
+++ b/core/src/test/java/org/teavm/vm/PluginLoaderTest.java
@@ -56,7 +56,7 @@ public class PluginLoaderTest {
}
private List order(Class>... classes) {
- return TeaVMPluginLoader.orderPlugins(PluginLoaderTest.class.getClassLoader(),
+ return TeaVMPluginReader.orderPlugins(PluginLoaderTest.class.getClassLoader(),
Arrays.stream(classes).map(Class::getName).collect(Collectors.toSet()));
}