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