/* * Quartz * Copyright (c) 2015, Minecrell <https://github.com/Minecrell> * * Based on Sponge and SpongeAPI, licensed under the MIT License (MIT). * Copyright (c) SpongePowered.org <http://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package net.minecrell.quartz.plugin; import static java.util.Objects.requireNonNull; import com.google.common.base.Optional; import net.minecraft.launchwrapper.Launch; import net.minecrell.quartz.Quartz; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.slf4j.Logger; import org.spongepowered.api.plugin.Plugin; import org.spongepowered.api.plugin.PluginContainer; import org.spongepowered.api.plugin.PluginManager; import java.io.IOException; import java.io.InputStream; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; @Singleton public class QuartzPluginManager implements PluginManager { private static final String PLUGIN_DESCRIPTOR = Type.getDescriptor(Plugin.class); private final Quartz quartz; private final Map<String, PluginContainer> plugins = new HashMap<>(); private final Map<Object, PluginContainer> pluginInstances = new IdentityHashMap<>(); @Inject public QuartzPluginManager(Quartz quartz) { this.quartz = requireNonNull(quartz, "quartz"); plugins.put(quartz.getId(), quartz); pluginInstances.put(quartz, quartz); } public void loadPlugins() throws IOException { try (DirectoryStream<Path> dir = Files.newDirectoryStream(quartz.getPluginsDirectory(), "*.jar")) { for (Path jar : dir) { String pluginClassName = null; try (ZipFile zip = new ZipFile(jar.toFile())) { Enumeration<? extends ZipEntry> entries = zip.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); if (entry.isDirectory() || !entry.getName().endsWith(".class")) { continue; } try (InputStream in = zip.getInputStream(entry)) { if ((pluginClassName = findPlugin(in)) != null) { break; } } } } catch (IOException e) { quartz.getLogger().error("Failed to load plugin JAR: " + jar, e); continue; } // Load the plugin if (pluginClassName != null) { try { Launch.classLoader.addURL(jar.toUri().toURL()); Class<?> pluginClass = Class.forName(pluginClassName); QuartzPluginContainer container = new QuartzPluginContainer(pluginClass); plugins.put(container.getId(), container); pluginInstances.put(container.getInstance(), container); quartz.getGame().getEventManager().register(container, container.getInstance()); quartz.getLogger().info("Loaded plugin: {} (from {})", container.getName(), jar); } catch (Throwable e) { quartz.getLogger().error("Failed to load plugin: " + pluginClassName + " (from " + jar + ')', e); } } } } } @Nullable private String findPlugin(InputStream in) throws IOException { ClassReader reader = new ClassReader(in); ClassNode classNode = new ClassNode(); reader.accept(classNode, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); if (classNode.visibleAnnotations != null) { for (AnnotationNode node : classNode.visibleAnnotations) { if (node.desc.equals(PLUGIN_DESCRIPTOR)) { return classNode.name.replace('/', '.'); } } } return null; } @Override public Optional<PluginContainer> fromInstance(Object instance) { requireNonNull(instance, "instance"); if (instance instanceof QuartzPluginContainer) { return Optional.of((PluginContainer) instance); } return Optional.fromNullable(pluginInstances.get(instance)); } @Override public Optional<PluginContainer> getPlugin(String id) { return Optional.fromNullable(plugins.get(id)); } @Override public Logger getLogger(PluginContainer plugin) { return ((QuartzPluginContainer) plugin).getLogger(); } @Override public Collection<PluginContainer> getPlugins() { return Collections.unmodifiableCollection(plugins.values()); } @Override public boolean isLoaded(String id) { return plugins.containsKey(id); } }