| 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