001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.plugins; 019 020import java.lang.reflect.Modifier; 021import java.util.ArrayList; 022import java.util.List; 023import java.util.Map; 024import java.util.TreeMap; 025import java.util.logging.Level; 026import java.util.logging.Logger; 027 028import org.syncany.util.StringUtil; 029 030import com.google.common.collect.ImmutableSet; 031import com.google.common.reflect.ClassPath; 032import com.google.common.reflect.ClassPath.ClassInfo; 033 034/** 035 * This class loads and manages all the {@link Plugin}s loaded in the classpath. 036 * It provides two public methods: 037 * 038 * <ul> 039 * <li>{@link #list()} returns a list of all loaded plugins (as per classpath)</li> 040 * <li>{@link #get(String) get()} returns a specific plugin, defined by a name</li> 041 * </ul> 042 * 043 * @see Plugin 044 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 045 */ 046public class Plugins { 047 private static final Logger logger = Logger.getLogger(Plugins.class.getSimpleName()); 048 049 private static final String PLUGIN_PACKAGE_NAME = Plugin.class.getPackage().getName(); 050 private static final String PLUGIN_CLASS_SUFFIX = Plugin.class.getSimpleName(); 051 052 private static final Map<String, Plugin> plugins = new TreeMap<String, Plugin>(); 053 054 /** 055 * Loads and returns a list of all available 056 * {@link Plugin}s. 057 */ 058 public static List<Plugin> list() { 059 loadPlugins(); 060 return new ArrayList<Plugin>(plugins.values()); 061 } 062 063 /** 064 * Loads and returns a list of all {@link Plugin}s 065 * matching the given subclass. 066 */ 067 public static <T extends Plugin> List<T> list(Class<T> pluginClass) { 068 loadPlugins(); 069 List<T> matchingPlugins = new ArrayList<T>(); 070 071 for (Plugin plugin : plugins.values()) { 072 if (pluginClass.isInstance(plugin)) { 073 matchingPlugins.add(pluginClass.cast(plugin)); 074 } 075 } 076 077 return matchingPlugins; 078 } 079 080 /** 081 * Loads the {@link Plugin} by a given identifier. 082 * 083 * <p>Note: Unlike the {@link #list()} method, this method is not expected 084 * to take long, because there is no need to read all JARs in the classpath. 085 * 086 * @param pluginId Identifier of the plugin, as defined by {@link Plugin#getId() the plugin ID} 087 * @return Returns an instance of a plugin, or <code>null</code> if no plugin with the given identifier can be found 088 */ 089 public static Plugin get(String pluginId) { 090 if (pluginId == null) { 091 return null; 092 } 093 094 loadPlugin(pluginId); 095 096 if (plugins.containsKey(pluginId)) { 097 return plugins.get(pluginId); 098 } 099 else { 100 return null; 101 } 102 } 103 104 public static <T extends Plugin> T get(String pluginId, Class<T> pluginClass) { 105 Plugin plugin = get(pluginId); 106 107 if (pluginId == null || !pluginClass.isInstance(plugin)) { 108 return null; 109 } 110 else { 111 return pluginClass.cast(plugin); 112 } 113 } 114 115 private static void loadPlugin(String pluginId) { 116 if (plugins.containsKey(pluginId)) { 117 return; 118 } 119 120 loadPlugins(); 121 122 if (plugins.containsKey(pluginId)) { 123 return; 124 } 125 else { 126 logger.log(Level.WARNING, "Could not load plugin (1): " + pluginId + " (not found or issues with loading)"); 127 } 128 } 129 130 public static void refresh() { 131 plugins.clear(); 132 } 133 134 /** 135 * Loads all plugins in the classpath. 136 * 137 * <p>First loads all classes in the 'org.syncany.plugins' package. 138 * For all classes ending with the 'Plugin' suffix, it tries to load 139 * them, checks whether they inherit from {@link Plugin} and whether 140 * they can be instantiated. 141 */ 142 private static void loadPlugins() { 143 try { 144 ImmutableSet<ClassInfo> pluginPackageSubclasses = ClassPath 145 .from(Thread.currentThread().getContextClassLoader()) 146 .getTopLevelClassesRecursive(PLUGIN_PACKAGE_NAME); 147 148 for (ClassInfo classInfo : pluginPackageSubclasses) { 149 boolean classNameEndWithPluginSuffix = classInfo.getName().endsWith(PLUGIN_CLASS_SUFFIX); 150 151 if (classNameEndWithPluginSuffix) { 152 Class<?> pluginClass = classInfo.load(); 153 154 String camelCasePluginId = pluginClass.getSimpleName().replace(Plugin.class.getSimpleName(), ""); 155 String pluginId = StringUtil.toSnakeCase(camelCasePluginId); 156 157 boolean isSubclassOfPlugin = Plugin.class.isAssignableFrom(pluginClass); 158 boolean canInstantiate = !Modifier.isAbstract(pluginClass.getModifiers()); 159 boolean pluginAlreadyLoaded = plugins.containsKey(pluginId); 160 161 if (isSubclassOfPlugin && canInstantiate && !pluginAlreadyLoaded) { 162 logger.log(Level.INFO, "- " + pluginClass.getName()); 163 164 try { 165 Plugin plugin = (Plugin) pluginClass.newInstance(); 166 plugins.put(plugin.getId(), plugin); 167 } 168 catch (Exception e) { 169 logger.log(Level.WARNING, "Could not load plugin (2): " + pluginClass.getName(), e); 170 } 171 } 172 } 173 } 174 } 175 catch (Exception e) { 176 throw new RuntimeException("Unable to load plugins.", e); 177 } 178 } 179}