PluginUtil.java |
1 /** 2 * Distribution License: 3 * JSword is free software; you can redistribute it and/or modify it under 4 * the terms of the GNU Lesser General Public License, version 2.1 or later 5 * as published by the Free Software Foundation. This program is distributed 6 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even 7 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 8 * See the GNU Lesser General Public License for more details. 9 * 10 * The License is available on the internet at: 11 * http://www.gnu.org/copyleft/lgpl.html 12 * or by writing to: 13 * Free Software Foundation, Inc. 14 * 59 Temple Place - Suite 330 15 * Boston, MA 02111-1307, USA 16 * 17 * © CrossWire Bible Society, 2008 - 2016 18 * 19 */ 20 package org.crosswire.common.util; 21 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.net.MalformedURLException; 25 import java.util.ArrayList; 26 import java.util.HashMap; 27 import java.util.List; 28 import java.util.Map; 29 import java.util.MissingResourceException; 30 31 import org.crosswire.jsword.JSOtherMsg; 32 import org.slf4j.LoggerFactory; 33 34 /** 35 * A plugin maps one or more implementations to an interface or abstract class 36 * via a properties file whose suffix is "plugin". When there is more than one 37 * implementation, one is marked as a default. 38 * 39 * @see gnu.lgpl.License The GNU Lesser General Public License for details. 40 * @author DM Smith 41 */ 42 public final class PluginUtil { 43 /** 44 * Prevent instantiation 45 */ 46 private PluginUtil() { 47 } 48 49 /** 50 * Get the known implementors of some interface or abstract class. This is 51 * currently done by looking up a plugin file by the name of the given 52 * class, and assuming that values are implementors of said class. Those 53 * that are not are warned, but ignored. 54 * 55 * @param <T> the implementor's type 56 * @param clazz 57 * The class or interface to find implementors of. 58 * @return The list of implementing classes. 59 */ 60 public static <T> Class<T>[] getImplementors(Class<T> clazz) { 61 try { 62 List<Class<T>> matches = new ArrayList<Class<T>>(); 63 PropertyMap props = getPlugin(clazz); 64 for (String key : props.keySet()) { 65 String name = props.get(key); 66 try { 67 Class<T> impl = (Class<T>) ClassUtil.forName(name); 68 if (clazz.isAssignableFrom(impl)) { 69 matches.add(impl); 70 } else { 71 log.warn("Class {} does not implement {}. Ignoring.", impl.getName(), clazz.getName()); 72 } 73 } catch (ClassNotFoundException ex) { 74 log.warn("Failed to add class to list: {}", clazz.getName(), ex); 75 } 76 } 77 78 log.debug("Found {} implementors of {}", Integer.toString(matches.size()), clazz.getName()); 79 return matches.toArray(new Class[matches.size()]); 80 } catch (IOException ex) { 81 log.error("Failed to get any classes.", ex); 82 return new Class[0]; 83 } 84 } 85 86 /** 87 * Get a map of known implementors of some interface or abstract class. This 88 * is currently done by looking up a plugins file by the name of the given 89 * class, and assuming that values are implementors of said class. Those 90 * that are not are warned, but ignored. The reply is in the form of a map 91 * of keys=strings, and values=classes in case you need to get at the names 92 * given to the classes in the plugin file. 93 * 94 * @param <T> the implementor's type 95 * @param clazz 96 * The class or interface to find implementors of. 97 * @return The map of implementing classes. 98 * @see PluginUtil#getImplementors(Class) 99 */ 100 public static <T> Map<String, Class<T>> getImplementorsMap(Class<T> clazz) { 101 Map<String, Class<T>> matches = new HashMap<String, Class<T>>(); 102 103 try { 104 PropertyMap props = getPlugin(clazz); 105 for (String key : props.keySet()) { 106 try { 107 String value = props.get(key); 108 Class<T> impl = (Class<T>) ClassUtil.forName(value); 109 if (clazz.isAssignableFrom(impl)) { 110 matches.put(key, impl); 111 } else { 112 log.warn("Class {} does not implement {}. Ignoring.", impl.getName(), clazz.getName()); 113 } 114 } catch (ClassNotFoundException ex) { 115 log.warn("Failed to add class to list: {}", clazz.getName(), ex); 116 } 117 } 118 119 log.debug("Found {} implementors of {}", Integer.toString(matches.size()), clazz.getName()); 120 } catch (IOException ex) { 121 log.error("Failed to get any classes.", ex); 122 } 123 124 return matches; 125 } 126 127 /** 128 * Get the preferred implementor of some interface or abstract class. This 129 * is currently done by looking up a plugins file by the name of the given 130 * class, and assuming that the "default" key is an implementation of said 131 * class. Warnings are given otherwise. 132 * 133 * @param <T> the implementor's type 134 * @param clazz 135 * The class or interface to find an implementation of. 136 * @return The configured implementing class. 137 * @throws MalformedURLException 138 * if the plugin file can not be found 139 * @throws IOException 140 * if there is a problem reading the found file 141 * @throws ClassNotFoundException 142 * if the read contents are not found 143 * @throws ClassCastException 144 * if the read contents are not valid 145 * @see PluginUtil#getImplementors(Class) 146 */ 147 public static <T> Class<T> getImplementor(Class<T> clazz) throws IOException, ClassNotFoundException, ClassCastException { 148 PropertyMap props = getPlugin(clazz); 149 String name = props.get(DEFAULT); 150 151 Class<T> impl = (Class<T>) ClassUtil.forName(name); 152 if (!clazz.isAssignableFrom(impl)) { 153 throw new ClassCastException(JSOtherMsg.lookupText("Class {0} does not implement {1}.", impl.getName(), clazz.getName())); 154 } 155 156 return impl; 157 } 158 159 /** 160 * Get and instantiate the preferred implementor of some interface or 161 * abstract class. 162 * 163 * @param <T> the implementor's type 164 * @param clazz 165 * The class or interface to find an implementation of. 166 * @return The configured implementing class. 167 * @throws MalformedURLException 168 * if the plugin file can not be found 169 * @throws IOException 170 * if there is a problem reading the found file 171 * @throws ClassNotFoundException 172 * if the read contents are not found 173 * @throws ClassCastException 174 * if the read contents are not valid 175 * @throws InstantiationException 176 * if the new object can not be instantiated 177 * @throws IllegalAccessException 178 * if the new object can not be instantiated 179 * @see PluginUtil#getImplementors(Class) 180 */ 181 public static <T> T getImplementation(Class<T> clazz) throws MalformedURLException, ClassCastException, IOException, ClassNotFoundException, 182 InstantiationException, IllegalAccessException 183 { 184 return getImplementor(clazz).newInstance(); 185 } 186 187 /** 188 * Get and load a plugin file by looking it up as a resource. 189 * 190 * @param <T> the implementor's type 191 * @param clazz 192 * The name of the desired resource 193 * @return The found and loaded plugin file 194 * @throws IOException 195 * if the resource can not be loaded 196 * @throws MissingResourceException 197 * if the resource can not be found 198 */ 199 public static <T> PropertyMap getPlugin(Class<T> clazz) throws IOException { 200 String subject = ClassUtil.getShortClassName(clazz); 201 202 try { 203 String lookup = subject + PluginUtil.EXTENSION_PLUGIN; 204 InputStream in = ResourceUtil.getResourceAsStream(clazz, lookup); 205 206 PropertyMap prop = new PropertyMap(); 207 prop.load(in); 208 return prop; 209 } catch (MissingResourceException e) { 210 return new PropertyMap(); 211 } 212 } 213 214 /** 215 * Extension for properties files 216 */ 217 public static final String EXTENSION_PLUGIN = ".plugin"; 218 219 /** 220 * The string for default implementations 221 */ 222 private static final String DEFAULT = "default"; 223 224 /** 225 * The log stream 226 */ 227 private static final org.slf4j.Logger log = LoggerFactory.getLogger(PluginUtil.class); 228 229 } 230