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