All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.teavm.vm.TeaVM Maven / Gradle / Ivy

There is a newer version: 0.10.2
Show newest version
/*
 *  Copyright 2013 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.*;
import java.util.*;
import org.teavm.cache.NoCache;
import org.teavm.codegen.*;
import org.teavm.common.ServiceRepository;
import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.debugging.information.SourceLocation;
import org.teavm.dependency.*;
import org.teavm.diagnostics.AccumulationDiagnostics;
import org.teavm.diagnostics.ProblemProvider;
import org.teavm.javascript.*;
import org.teavm.javascript.ast.ClassNode;
import org.teavm.javascript.spi.GeneratedBy;
import org.teavm.javascript.spi.Generator;
import org.teavm.javascript.spi.InjectedBy;
import org.teavm.javascript.spi.Injector;
import org.teavm.model.*;
import org.teavm.model.instructions.*;
import org.teavm.model.util.*;
import org.teavm.optimization.*;
import org.teavm.vm.spi.RendererListener;
import org.teavm.vm.spi.TeaVMHost;
import org.teavm.vm.spi.TeaVMPlugin;

/**
 * 

TeaVM itself. This class builds a JavaScript VM that runs a certain code. * Here you can specify entry points into your code (such like {@code main} method). * TeaVM guarantees that all required classes and methods will be provided by * built VM.

* *

Here is a typical code snippet:

* *
{@code
 *ClassLoader classLoader = ...; // obtain ClassLoader somewhere
 *ClassHolderSource classSource = new ClasspathClassHolderSource(classLoader);
 *TeaVM vm = new TeaVMBuilder()
 *        .setClassLoader(classLoader)
 *        .setClassSource(classSource)
 *        .build();
 *vm.setMinifying(false); // optionally disable obfuscation
 *vm.installPlugins();    // install all default plugins
 *                        // that are found in a classpath
 *vm.entryPoint("main", new MethodReference(
 *        "fully.qualified.ClassName",  "main",
 *         ValueType.array(ValueType.object("java.lang.String")),
 *         ValueType.VOID));
 *StringBuilder sb = new StringBuilder();
 *vm.build(sb, null);
 *vm.checkForMissingItems();
 *}
* * @author Alexey Andreev */ public class TeaVM implements TeaVMHost, ServiceRepository { private ClassReaderSource classSource; private DependencyChecker dependencyChecker; private AccumulationDiagnostics diagnostics = new AccumulationDiagnostics(); private ClassLoader classLoader; private boolean minifying = true; private boolean bytecodeLogging; private OutputStream logStream = System.out; private Map entryPoints = new HashMap<>(); private Map exportedClasses = new HashMap<>(); private Map methodGenerators = new HashMap<>(); private Map methodInjectors = new HashMap<>(); private List rendererListeners = new ArrayList<>(); private Map, Object> services = new HashMap<>(); private Properties properties = new Properties(); private DebugInformationEmitter debugEmitter; private ProgramCache programCache; private MethodNodeCache astCache = new EmptyRegularMethodNodeCache(); private boolean incremental; private TeaVMProgressListener progressListener; private boolean cancelled; private ListableClassHolderSource writtenClasses; private Set asyncMethods = new HashSet<>(); private Set asyncFamilyMethods = new HashSet<>(); TeaVM(ClassReaderSource classSource, ClassLoader classLoader) { this.classSource = classSource; this.classLoader = classLoader; dependencyChecker = new DependencyChecker(this.classSource, classLoader, this, diagnostics); progressListener = new TeaVMProgressListener() { @Override public TeaVMProgressFeedback progressReached(int progress) { return TeaVMProgressFeedback.CONTINUE; } @Override public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) { return TeaVMProgressFeedback.CONTINUE; } }; } @Override public void add(DependencyListener listener) { dependencyChecker.addDependencyListener(listener); } @Override public void add(ClassHolderTransformer transformer) { dependencyChecker.addClassTransformer(transformer); } @Override public void add(MethodReference methodRef, Generator generator) { methodGenerators.put(methodRef, generator); } @Override public void add(MethodReference methodRef, Injector injector) { methodInjectors.put(methodRef, injector); } @Override public void add(RendererListener listener) { rendererListeners.add(listener); } @Override public ClassLoader getClassLoader() { return classLoader; } /** * Reports whether this TeaVM instance uses obfuscation when generating the JavaScript code. * * @see #setMinifying(boolean) * @return whether TeaVM produces obfuscated code. */ public boolean isMinifying() { return minifying; } /** * Specifies whether this TeaVM instance uses obfuscation when generating the JavaScript code. * * @see #isMinifying() * @param minifying whether TeaVM should obfuscate code. */ public void setMinifying(boolean minifying) { this.minifying = minifying; } public boolean isBytecodeLogging() { return bytecodeLogging; } public void setBytecodeLogging(boolean bytecodeLogging) { this.bytecodeLogging = bytecodeLogging; } /** * Specifies configuration properties for TeaVM and its plugins. You should call this method before * installing any plugins or interceptors. * * @param properties configuration properties to set. These properties will be copied into this VM instance, * so VM won't see any further changes in this object. */ public void setProperties(Properties properties) { this.properties.clear(); if (properties != null) { this.properties.putAll(properties); } } @Override public Properties getProperties() { return new Properties(properties); } public MethodNodeCache getAstCache() { return astCache; } public void setAstCache(MethodNodeCache methodAstCache) { this.astCache = methodAstCache; } public ProgramCache getProgramCache() { return programCache; } public void setProgramCache(ProgramCache programCache) { this.programCache = programCache; } public boolean isIncremental() { return incremental; } public void setIncremental(boolean incremental) { this.incremental = incremental; } public TeaVMProgressListener getProgressListener() { return progressListener; } public void setProgressListener(TeaVMProgressListener progressListener) { this.progressListener = progressListener; } public boolean wasCancelled() { return cancelled; } public ProblemProvider getProblemProvider() { return diagnostics; } /** *

Adds an entry point. TeaVM guarantees, that all methods that are required by the entry point * will be available at run-time in browser. Also you need to specify for each parameter of entry point * which actual types will be passed here by calling {@link TeaVMEntryPoint#withValue(int, String)}. * It is highly recommended to read explanation on {@link TeaVMEntryPoint} class documentation.

* *

You should call this method after installing all plugins and interceptors, but before * doing the actual build.

* * @param name the name under which this entry point will be available for JavaScript code. * @param ref a full reference to the method which is an entry point. * @return an entry point that you can additionally adjust. */ public TeaVMEntryPoint entryPoint(String name, MethodReference ref) { if (name != null) { if (entryPoints.containsKey(name)) { throw new IllegalArgumentException("Entry point with public name `" + name + "' already defined " + "for method " + ref); } } TeaVMEntryPoint entryPoint = new TeaVMEntryPoint(name, ref, dependencyChecker.linkMethod(ref, null)); dependencyChecker.linkClass(ref.getClassName(), null).initClass(null); if (name != null) { entryPoints.put(name, entryPoint); } return entryPoint; } /** *

Adds an entry point. TeaVM guarantees, that all methods that are required by the entry point * will be available at run-time in browser. Also you need to specify for each parameter of entry point * which actual types will be passed here by calling {@link TeaVMEntryPoint#withValue(int, String)}. * It is highly recommended to read explanation on {@link TeaVMEntryPoint} class documentation.

* *

You should call this method after installing all plugins and interceptors, but before * doing the actual build.

* * @param ref a full reference to the method which is an entry point. * @return an entry point that you can additionally adjust. */ public TeaVMEntryPoint entryPoint(MethodReference ref) { return entryPoint(null, ref); } public TeaVMEntryPoint linkMethod(MethodReference ref) { TeaVMEntryPoint entryPoint = new TeaVMEntryPoint("", ref, dependencyChecker.linkMethod(ref, null)); dependencyChecker.linkClass(ref.getClassName(), null).initClass(null); return entryPoint; } public void exportType(String name, String className) { if (exportedClasses.containsKey(name)) { throw new IllegalArgumentException("Class with public name `" + name + "' already defined for class " + className); } dependencyChecker.linkClass(className, null).initClass(null); exportedClasses.put(name, className); } public void linkType(String className) { dependencyChecker.linkClass(className, null).initClass(null); } /** * Gets a {@link ClassReaderSource} which is used by this TeaVM instance. It is exactly what was * passed to {@link TeaVMBuilder#setClassSource(ClassHolderSource)}. * * @return class source. */ public ClassReaderSource getClassSource() { return classSource; } /** * Gets a {@link ClassReaderSource} which is similar to that of {@link #getClassSource()}, * except that it also contains classes with applied transformations together with * classes, generated via {@link DependencyChecker#submitClass(ClassHolder)}. */ public ClassReaderSource getDependencyClassSource() { return dependencyChecker.getClassSource(); } public Collection getClasses() { return dependencyChecker.getAchievableClasses(); } public Collection getMethods() { return dependencyChecker.getAchievableMethods(); } public DependencyInfo getDependencyInfo() { return dependencyChecker; } public ListableClassReaderSource getWrittenClasses() { return writtenClasses; } public DebugInformationEmitter getDebugEmitter() { return debugEmitter; } public void setDebugEmitter(DebugInformationEmitter debugEmitter) { this.debugEmitter = debugEmitter; } /** *

Does actual build. Call this method after TeaVM is fully configured and all entry points * are specified. This method may fail if there are items (classes, methods and fields) * that are required by entry points, but weren't found in classpath. In this case no * actual generation happens and no exceptions thrown, but you can further call * {@link #getProblemProvider()} to learn the build state.

* * @param writer where to generate JavaScript. Should not be null. * @param target where to generate additional resources. Can be null, but if there are * plugins or inteceptors that generate additional resources, the build process will fail. * * @throws RenderingException when something went wrong during rendering phase. */ public void build(Appendable writer, BuildTarget target) throws RenderingException { // Check dependencies reportPhase(TeaVMPhase.DEPENDENCY_CHECKING, 1); if (wasCancelled()) { return; } AliasProvider aliasProvider = minifying ? new MinifyingAliasProvider() : new DefaultAliasProvider(); dependencyChecker.setInterruptor(new DependencyCheckerInterruptor() { @Override public boolean shouldContinue() { return progressListener.progressReached(0) == TeaVMProgressFeedback.CONTINUE; } }); dependencyChecker.linkMethod(new MethodReference(Class.class.getName(), "getClass", ValueType.object("org.teavm.platform.PlatformClass"), ValueType.parse(Class.class)), null).use(); dependencyChecker.linkMethod(new MethodReference(String.class, "", char[].class, void.class), null).use(); dependencyChecker.linkMethod(new MethodReference(String.class, "getChars", int.class, int.class, char[].class, int.class, void.class), null).use(); MethodDependency internDep = dependencyChecker.linkMethod(new MethodReference(String.class, "intern", String.class), null); internDep.getVariable(0).propagate(dependencyChecker.getType("java.lang.String")); internDep.use(); dependencyChecker.linkMethod(new MethodReference(String.class, "length", int.class), null).use(); dependencyChecker.linkMethod(new MethodReference(Object.class, "clone", Object.class), null).use(); dependencyChecker.linkMethod(new MethodReference(Thread.class, "currentThread", Thread.class), null).use(); dependencyChecker.linkMethod(new MethodReference(Thread.class, "getMainThread", Thread.class), null).use(); dependencyChecker.linkMethod( new MethodReference(Thread.class, "setCurrentThread", Thread.class, void.class), null).use(); MethodDependency exceptionCons = dependencyChecker.linkMethod(new MethodReference( NoClassDefFoundError.class, "", String.class, void.class), null); exceptionCons.use(); exceptionCons.getVariable(0).propagate(dependencyChecker.getType(NoClassDefFoundError.class.getName())); exceptionCons.getVariable(1).propagate(dependencyChecker.getType("java.lang.String")); exceptionCons = dependencyChecker.linkMethod(new MethodReference(NoSuchFieldError.class, "", String.class, void.class), null); exceptionCons.use(); exceptionCons.getVariable(0).propagate(dependencyChecker.getType(NoSuchFieldError.class.getName())); exceptionCons.getVariable(1).propagate(dependencyChecker.getType("java.lang.String")); exceptionCons = dependencyChecker.linkMethod(new MethodReference(NoSuchMethodError.class, "", String.class, void.class), null); exceptionCons.use(); exceptionCons.getVariable(0).propagate(dependencyChecker.getType(NoSuchMethodError.class.getName())); exceptionCons.getVariable(1).propagate(dependencyChecker.getType("java.lang.String")); dependencyChecker.processDependencies(); if (wasCancelled() || !diagnostics.getSevereProblems().isEmpty()) { return; } // Link reportPhase(TeaVMPhase.LINKING, 1); if (wasCancelled()) { return; } ListableClassHolderSource classSet = link(dependencyChecker); writtenClasses = classSet; if (wasCancelled()) { return; } // Optimize and allocate registers if (!incremental) { devirtualize(classSet, dependencyChecker); if (wasCancelled()) { return; } } List clsNodes = modelToAst(classSet); // Render reportPhase(TeaVMPhase.RENDERING, classSet.getClassNames().size()); if (wasCancelled()) { return; } DefaultNamingStrategy naming = new DefaultNamingStrategy(aliasProvider, dependencyChecker.getClassSource()); naming.setMinifying(minifying); SourceWriterBuilder builder = new SourceWriterBuilder(naming); builder.setMinified(minifying); SourceWriter sourceWriter = builder.build(writer); Renderer renderer = new Renderer(sourceWriter, classSet, classLoader, this, asyncMethods, asyncFamilyMethods, diagnostics); renderer.setProperties(properties); renderer.setMinifying(minifying); if (debugEmitter != null) { int classIndex = 0; for (String className : classSet.getClassNames()) { ClassHolder cls = classSet.get(className); for (MethodHolder method : cls.getMethods()) { if (method.getProgram() != null) { emitCFG(debugEmitter, method.getProgram()); } } reportProgress(++classIndex); if (wasCancelled()) { return; } } renderer.setDebugEmitter(debugEmitter); } renderer.getDebugEmitter().setLocationProvider(sourceWriter); for (Map.Entry entry : methodInjectors.entrySet()) { renderer.addInjector(entry.getKey(), entry.getValue()); } try { for (RendererListener listener : rendererListeners) { listener.begin(renderer, target); } sourceWriter.append("\"use strict\";").newLine(); renderer.renderRuntime(); renderer.render(clsNodes); renderer.renderStringPool(); for (Map.Entry entry : entryPoints.entrySet()) { sourceWriter.append("var ").append(entry.getKey()).ws().append("=").ws(); MethodReference ref = entry.getValue().reference; sourceWriter.append(naming.getFullNameFor(ref)); sourceWriter.append(";").newLine(); } for (Map.Entry entry : exportedClasses.entrySet()) { sourceWriter.append("var ").append(entry.getKey()).ws().append("=").ws() .appendClass(entry.getValue()).append(";").newLine(); } for (RendererListener listener : rendererListeners) { listener.complete(); } } catch (IOException e) { throw new RenderingException("IO Error occured", e); } } public ListableClassHolderSource link(DependencyInfo dependency) { reportPhase(TeaVMPhase.LINKING, dependency.getAchievableClasses().size()); Linker linker = new Linker(); MutableClassHolderSource cutClasses = new MutableClassHolderSource(); MissingItemsProcessor missingItemsProcessor = new MissingItemsProcessor(dependency, diagnostics); if (wasCancelled()) { return cutClasses; } int index = 0; for (String className : dependency.getAchievableClasses()) { ClassReader clsReader = dependency.getClassSource().get(className); if (clsReader == null) { continue; } ClassHolder cls = ModelUtils.copyClass(clsReader); cutClasses.putClassHolder(cls); missingItemsProcessor.processClass(cls); linker.link(dependency, cls); progressListener.progressReached(++index); } return cutClasses; } private void reportPhase(TeaVMPhase phase, int progressLimit) { if (progressListener.phaseStarted(phase, progressLimit) == TeaVMProgressFeedback.CANCEL) { cancelled = true; } } private void reportProgress(int progress) { if (progressListener.progressReached(progress) == TeaVMProgressFeedback.CANCEL) { cancelled = true; } } private void emitCFG(DebugInformationEmitter emitter, Program program) { Map cfg = ProgramUtils.getLocationCFG(program); for (Map.Entry entry : cfg.entrySet()) { SourceLocation location = map(entry.getKey()); SourceLocation[] successors = new SourceLocation[entry.getValue().length]; for (int i = 0; i < entry.getValue().length; ++i) { successors[i] = map(entry.getValue()[i]); } emitter.addSuccessors(location, successors); } } private static SourceLocation map(InstructionLocation location) { if (location == null) { return null; } return new SourceLocation(location.getFileName(), location.getLine()); } private void devirtualize(ListableClassHolderSource classes, DependencyInfo dependency) { reportPhase(TeaVMPhase.DEVIRTUALIZATION, classes.getClassNames().size()); if (wasCancelled()) { return; } final Devirtualization devirtualization = new Devirtualization(dependency, classes); int index = 0; for (String className : classes.getClassNames()) { ClassHolder cls = classes.get(className); for (final MethodHolder method : cls.getMethods()) { if (method.getProgram() != null) { devirtualization.apply(method); } } reportProgress(++index); if (wasCancelled()) { return; } } } private List modelToAst(ListableClassHolderSource classes) { AsyncMethodFinder asyncFinder = new AsyncMethodFinder(dependencyChecker.getCallGraph(), diagnostics); asyncFinder.find(classes); asyncMethods.addAll(asyncFinder.getAsyncMethods()); asyncFamilyMethods.addAll(asyncFinder.getAsyncFamilyMethods()); progressListener.phaseStarted(TeaVMPhase.DECOMPILATION, classes.getClassNames().size()); Decompiler decompiler = new Decompiler(classes, classLoader, asyncMethods, asyncFamilyMethods); decompiler.setRegularMethodCache(incremental ? astCache : null); for (Map.Entry entry : methodGenerators.entrySet()) { decompiler.addGenerator(entry.getKey(), entry.getValue()); } for (MethodReference injectedMethod : methodInjectors.keySet()) { decompiler.addMethodToPass(injectedMethod); } List classOrder = decompiler.getClassOrdering(classes.getClassNames()); List classNodes = new ArrayList<>(); int index = 0; try (PrintWriter bytecodeLogger = bytecodeLogging ? new PrintWriter(new OutputStreamWriter(logStream, "UTF-8")) : null) { for (String className : classOrder) { ClassHolder cls = classes.get(className); for (MethodHolder method : cls.getMethods()) { processMethod(method); preprocessNativeMethod(method); if (bytecodeLogging) { logMethodBytecode(bytecodeLogger, method); } } classNodes.add(decompiler.decompile(cls)); progressListener.progressReached(++index); } } catch (UnsupportedEncodingException e) { throw new AssertionError("UTF-8 is expected to be supported"); } return classNodes; } private void preprocessNativeMethod(MethodHolder method) { if (!method.getModifiers().contains(ElementModifier.NATIVE) || methodGenerators.get(method.getReference()) != null || methodInjectors.get(method.getReference()) != null || method.getAnnotations().get(GeneratedBy.class.getName()) != null || method.getAnnotations().get(InjectedBy.class.getName()) != null) { return; } method.getModifiers().remove(ElementModifier.NATIVE); Program program = new Program(); method.setProgram(program); BasicBlock block = program.createBasicBlock(); Variable exceptionVar = program.createVariable(); ConstructInstruction newExceptionInsn = new ConstructInstruction(); newExceptionInsn.setType(NoSuchMethodError.class.getName()); newExceptionInsn.setReceiver(exceptionVar); block.getInstructions().add(newExceptionInsn); Variable constVar = program.createVariable(); StringConstantInstruction constInsn = new StringConstantInstruction(); constInsn.setConstant("Native method implementation not found: " + method.getReference()); constInsn.setReceiver(constVar); block.getInstructions().add(constInsn); InvokeInstruction initExceptionInsn = new InvokeInstruction(); initExceptionInsn.setInstance(exceptionVar); initExceptionInsn.setMethod(new MethodReference(NoSuchMethodError.class, "", String.class, void.class)); initExceptionInsn.setType(InvocationType.SPECIAL); initExceptionInsn.getArguments().add(constVar); block.getInstructions().add(initExceptionInsn); RaiseInstruction raiseInsn = new RaiseInstruction(); raiseInsn.setException(exceptionVar); block.getInstructions().add(raiseInsn); diagnostics.error(new CallLocation(method.getReference()), "Native method {{m0}} has no implementation", method.getReference()); } private void processMethod(MethodHolder method) { if (method.getProgram() == null) { return; } boolean noCache = method.getAnnotations().get(NoCache.class.getName()) != null; Program optimizedProgram = incremental && !noCache && programCache != null ? programCache.get(method.getReference()) : null; if (optimizedProgram == null) { optimizedProgram = ProgramUtils.copy(method.getProgram()); if (optimizedProgram.basicBlockCount() > 0) { for (MethodOptimization optimization : getOptimizations()) { optimization.optimize(method, optimizedProgram); } RegisterAllocator allocator = new RegisterAllocator(); allocator.allocateRegisters(method, optimizedProgram); } if (incremental && programCache != null) { programCache.store(method.getReference(), optimizedProgram); } } method.setProgram(optimizedProgram); } private List getOptimizations() { return Arrays.asList(new ArrayUnwrapMotion(), new LoopInvariantMotion(), new GlobalValueNumbering(), new UnusedVariableElimination()); } private void logMethodBytecode(PrintWriter writer, MethodHolder method) { writer.print(" "); printModifiers(writer, method); writer.print(method.getName() + "("); ValueType[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; ++i) { if (i > 0) { writer.print(", "); } printType(writer, parameterTypes[i]); } writer.println(")"); Program program = method.getProgram(); if (program != null && program.basicBlockCount() > 0) { ListingBuilder builder = new ListingBuilder(); writer.print(builder.buildListing(program, " ")); writer.print(" Register allocation:"); for (int i = 0; i < program.variableCount(); ++i) { writer.print(i + ":" + program.variableAt(i).getRegister() + " "); } writer.println(); writer.println(); writer.flush(); } else { writer.println(); } } private void printType(PrintWriter writer, ValueType type) { if (type instanceof ValueType.Object) { writer.print(((ValueType.Object)type).getClassName()); } else if (type instanceof ValueType.Array) { printType(writer, ((ValueType.Array)type).getItemType()); writer.print("[]"); } else if (type instanceof ValueType.Primitive) { switch (((ValueType.Primitive)type).getKind()) { case BOOLEAN: writer.print("boolean"); break; case SHORT: writer.print("short"); break; case BYTE: writer.print("byte"); break; case CHARACTER: writer.print("char"); break; case DOUBLE: writer.print("double"); break; case FLOAT: writer.print("float"); break; case INTEGER: writer.print("int"); break; case LONG: writer.print("long"); break; } } } private void printModifiers(PrintWriter writer, ElementHolder element) { switch (element.getLevel()) { case PRIVATE: writer.print("private "); break; case PUBLIC: writer.print("public "); break; case PROTECTED: writer.print("protected "); break; default: break; } Set modifiers = element.getModifiers(); if (modifiers.contains(ElementModifier.ABSTRACT)) { writer.print("abstract "); } if (modifiers.contains(ElementModifier.FINAL)) { writer.print("final "); } if (modifiers.contains(ElementModifier.STATIC)) { writer.print("static "); } if (modifiers.contains(ElementModifier.NATIVE)) { writer.print("native "); } } public void build(File dir, String fileName) throws RenderingException { try (Writer writer = new OutputStreamWriter(new FileOutputStream(new File(dir, fileName)), "UTF-8")) { build(writer, new DirectoryBuildTarget(dir)); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Platform does not support UTF-8", e); } catch (IOException e) { throw new RenderingException("IO error occured", e); } } /** *

Finds and install all plugins in the current class path. The standard {@link ServiceLoader} * approach is used to find plugins. So this method scans all * META-INF/services/org.teavm.vm.spi.TeaVMPlugin resources and * obtains all implementation classes that are enumerated there.

*/ public void installPlugins() { for (TeaVMPlugin plugin : ServiceLoader.load(TeaVMPlugin.class, classLoader)) { plugin.install(this); } } @Override public T getService(Class type) { Object service = services.get(type); if (service == null) { throw new IllegalArgumentException("Service not registered: " + type.getName()); } return type.cast(service); } @Override public void registerService(Class type, T instance) { services.put(type, instance); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy