/*
* Syncany, www.syncany.org
* Copyright (C) 2011-2015 Philipp C. Heckel <philipp.heckel@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.syncany.plugins;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.syncany.util.StringUtil;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
/**
* This class loads and manages all the {@link Plugin}s loaded in the classpath.
* It provides two public methods:
*
* <ul>
* <li>{@link #list()} returns a list of all loaded plugins (as per classpath)</li>
* <li>{@link #get(String) get()} returns a specific plugin, defined by a name</li>
* </ul>
*
* @see Plugin
* @author Philipp C. Heckel <philipp.heckel@gmail.com>
*/
public class Plugins {
private static final Logger logger = Logger.getLogger(Plugins.class.getSimpleName());
private static final String PLUGIN_PACKAGE_NAME = Plugin.class.getPackage().getName();
private static final String PLUGIN_CLASS_SUFFIX = Plugin.class.getSimpleName();
private static final Map<String, Plugin> plugins = new TreeMap<String, Plugin>();
/**
* Loads and returns a list of all available
* {@link Plugin}s.
*/
public static List<Plugin> list() {
loadPlugins();
return new ArrayList<Plugin>(plugins.values());
}
/**
* Loads and returns a list of all {@link Plugin}s
* matching the given subclass.
*/
public static <T extends Plugin> List<T> list(Class<T> pluginClass) {
loadPlugins();
List<T> matchingPlugins = new ArrayList<T>();
for (Plugin plugin : plugins.values()) {
if (pluginClass.isInstance(plugin)) {
matchingPlugins.add(pluginClass.cast(plugin));
}
}
return matchingPlugins;
}
/**
* Loads the {@link Plugin} by a given identifier.
*
* <p>Note: Unlike the {@link #list()} method, this method is not expected
* to take long, because there is no need to read all JARs in the classpath.
*
* @param pluginId Identifier of the plugin, as defined by {@link Plugin#getId() the plugin ID)
* @return Returns an instance of a plugin, or <tt>null</tt> if no plugin with the given identifier can be found
*/
public static Plugin get(String pluginId) {
if (pluginId == null) {
return null;
}
loadPlugin(pluginId);
if (plugins.containsKey(pluginId)) {
return plugins.get(pluginId);
}
else {
return null;
}
}
public static <T extends Plugin> T get(String pluginId, Class<T> pluginClass) {
Plugin plugin = get(pluginId);
if (pluginId == null || !pluginClass.isInstance(plugin)) {
return null;
}
else {
return pluginClass.cast(plugin);
}
}
private static void loadPlugin(String pluginId) {
if (plugins.containsKey(pluginId)) {
return;
}
loadPlugins();
if (plugins.containsKey(pluginId)) {
return;
}
else {
logger.log(Level.WARNING, "Could not load plugin (1): " + pluginId + " (not found or issues with loading)");
}
}
public static void refresh() {
plugins.clear();
}
/**
* Loads all plugins in the classpath.
*
* <p>First loads all classes in the 'org.syncany.plugins' package.
* For all classes ending with the 'Plugin' suffix, it tries to load
* them, checks whether they inherit from {@link Plugin} and whether
* they can be instantiated.
*/
private static void loadPlugins() {
try {
ImmutableSet<ClassInfo> pluginPackageSubclasses = ClassPath
.from(Thread.currentThread().getContextClassLoader())
.getTopLevelClassesRecursive(PLUGIN_PACKAGE_NAME);
for (ClassInfo classInfo : pluginPackageSubclasses) {
boolean classNameEndWithPluginSuffix = classInfo.getName().endsWith(PLUGIN_CLASS_SUFFIX);
if (classNameEndWithPluginSuffix) {
Class<?> pluginClass = classInfo.load();
String camelCasePluginId = pluginClass.getSimpleName().replace(Plugin.class.getSimpleName(), "");
String pluginId = StringUtil.toSnakeCase(camelCasePluginId);
boolean isSubclassOfPlugin = Plugin.class.isAssignableFrom(pluginClass);
boolean canInstantiate = !Modifier.isAbstract(pluginClass.getModifiers());
boolean pluginAlreadyLoaded = plugins.containsKey(pluginId);
if (isSubclassOfPlugin && canInstantiate && !pluginAlreadyLoaded) {
logger.log(Level.INFO, "- " + pluginClass.getName());
try {
Plugin plugin = (Plugin) pluginClass.newInstance();
plugins.put(plugin.getId(), plugin);
}
catch (Exception e) {
logger.log(Level.WARNING, "Could not load plugin (2): " + pluginClass.getName(), e);
}
}
}
}
}
catch (Exception e) {
throw new RuntimeException("Unable to load plugins.", e);
}
}
}