/* * This file is part of Sponge, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered <https://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 org.spongepowered.server.plugin; import static com.google.common.base.Objects.firstNonNull; import static com.google.common.base.Preconditions.checkNotNull; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Singleton; import org.spongepowered.api.Platform; import org.spongepowered.api.Sponge; import org.spongepowered.api.plugin.PluginContainer; import org.spongepowered.api.plugin.PluginManager; import org.spongepowered.common.SpongeImpl; import org.spongepowered.plugin.meta.PluginMetadata; import org.spongepowered.server.SpongeVanilla; import org.spongepowered.server.launch.plugin.PluginCandidate; import org.spongepowered.server.launch.plugin.VanillaLaunchPluginManager; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @Singleton public class VanillaPluginManager implements PluginManager { private final Injector rootInjector; private final Map<String, PluginContainer> plugins = new HashMap<>(); private final Map<Object, PluginContainer> pluginInstances = new IdentityHashMap<>(); @Inject public VanillaPluginManager(Injector injector, SpongeVanilla impl, MetadataContainer metadata) { this.rootInjector = injector.getParent(); this.registerPlugin(SpongeImpl.getMinecraftPlugin()); this.registerPlugin(metadata.createContainer(Platform.API_ID, SpongeImpl.API_NAME, impl.getSource())); this.registerPlugin(impl); this.registerPlugin(metadata.createContainer("mcp", "Mod Coder Pack", impl.getSource())); } private void registerPlugin(PluginContainer plugin) { this.plugins.put(plugin.getId(), plugin); plugin.getInstance().ifPresent(instance -> this.pluginInstances.put(instance, plugin)); } public void loadPlugins() throws IOException { Map<String, PluginCandidate> candidateMap = VanillaLaunchPluginManager.getPlugins(); if (candidateMap.isEmpty()) { return; // Nothing to do } try { PluginSorter.sort(checkRequirements(candidateMap)).forEach(this::loadPlugin); } catch (Throwable e) { throw PluginReporter.crash(e, candidateMap.values()); } } private Set<PluginCandidate> checkRequirements(Map<String, PluginCandidate> candidates) { // Collect all versions of already loaded plugins Map<String, String> loadedPlugins = new HashMap<>(); for (PluginContainer container : this.plugins.values()) { loadedPlugins.put(container.getId(), container.getVersion().orElse(null)); } Set<PluginCandidate> successfulCandidates = new HashSet<>(candidates.size()); List<PluginCandidate> failedCandidates = new ArrayList<>(); for (PluginCandidate candidate : candidates.values()) { if (candidate.collectDependencies(loadedPlugins, candidates)) { successfulCandidates.add(candidate); } else { failedCandidates.add(candidate); } } if (failedCandidates.isEmpty()) { return successfulCandidates; // Nothing to do, all requirements satisfied } PluginCandidate candidate; boolean updated; while (true) { updated = false; Iterator<PluginCandidate> itr = successfulCandidates.iterator(); while (itr.hasNext()) { candidate = itr.next(); if (candidate.updateRequirements()) { updated = true; itr.remove(); failedCandidates.add(candidate); } } if (updated) { // Update failed candidates as well failedCandidates.forEach(PluginCandidate::updateRequirements); } else { break; } } for (PluginCandidate failed : failedCandidates) { if (failed.isInvalid()) { SpongeImpl.getLogger().error("Plugin '{}' from {} cannot be loaded because it is invalid", failed.getId(), failed.getSource()); } else { SpongeImpl.getLogger().error("Cannot load plugin '{}' from {} because it is missing the required dependencies {}", failed.getId(), failed.getSource(), PluginReporter.formatRequirements(failed.getMissingRequirements())); } } return successfulCandidates; } private void loadPlugin(PluginCandidate candidate) { final String id = candidate.getId(); candidate.getSource().addToClasspath(); final PluginMetadata metadata = candidate.getMetadata(); final String name = firstNonNull(metadata.getName(), id); final String version = firstNonNull(metadata.getVersion(), "unknown"); try { Class<?> pluginClass = Class.forName(candidate.getPluginClass()); PluginContainer container = new VanillaPluginContainer(this.rootInjector, pluginClass, metadata, candidate.getSource().getPath()); registerPlugin(container); Sponge.getEventManager().registerListeners(container, container.getInstance().get()); SpongeImpl.getLogger().info("Loaded plugin: {} {} (from {})", name, version, candidate.getSource()); } catch (Throwable e) { SpongeImpl.getLogger().error("Failed to load plugin: {} {} (from {})", name, version, candidate.getSource(), e); } } @Override public Optional<PluginContainer> fromInstance(Object instance) { checkNotNull(instance, "instance"); if (instance instanceof PluginContainer) { return Optional.of((PluginContainer) instance); } return Optional.ofNullable(this.pluginInstances.get(instance)); } @Override public Optional<PluginContainer> getPlugin(String id) { return Optional.ofNullable(this.plugins.get(id)); } @Override public Collection<PluginContainer> getPlugins() { return Collections.unmodifiableCollection(this.plugins.values()); } @Override public boolean isLoaded(String id) { return this.plugins.containsKey(id); } }