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 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * 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   * Copyright: 2008
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: PluginUtil.java 1505 2007-07-21 19:40:19Z dmsmith $
21   */
22  package org.crosswire.common.util;
23  
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.net.MalformedURLException;
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.MissingResourceException;
32  
33  import org.crosswire.jsword.JSOtherMsg;
34  
35  /**
36   * A plugin maps one or more implementations to an interface or abstract class
37   * via a properties file whose suffix is "plugin". When there is more than one
38   * implementation, one is marked as a default.
39   * 
40   * @see gnu.lgpl.License for license details.<br>
41   *      The copyright to this program is held by it's authors.
42   * @author DM Smith [dmsmith555 at yahoo dot com]
43   */
44  public final class PluginUtil {
45      /**
46       * Prevent instantiation
47       */
48      private PluginUtil() {
49      }
50  
51      /**
52       * Get the known implementors of some interface or abstract class. This is
53       * currently done by looking up a plugin file by the name of the given
54       * class, and assuming that values are implementors of said class. Those
55       * that are not are warned, but ignored.
56       * 
57       * @param clazz
58       *            The class or interface to find implementors of.
59       * @return The list of implementing classes.
60       */
61      public static <T> Class<T>[] getImplementors(Class<T> clazz) {
62          try {
63              List<Class<T>> matches = new ArrayList<Class<T>>();
64              PropertyMap props = getPlugin(clazz);
65              for (String key : props.keySet()) {
66                  String name = props.get(key);
67                  try {
68                      Class<T> impl = (Class<T>) ClassUtil.forName(name);
69                      if (clazz.isAssignableFrom(impl)) {
70                          matches.add(impl);
71                      } else {
72                          log.warn("Class " + impl.getName() + " does not implement " + clazz.getName() + ". Ignoring.");
73                      }
74                  } catch (ClassNotFoundException ex) {
75                      log.warn("Failed to add class to list: " + clazz.getName(), ex);
76                  }
77              }
78  
79              log.debug("Found " + matches.size() + " implementors of " + clazz.getName());
80              return matches.toArray(new Class[matches.size()]);
81          } catch (IOException ex) {
82              log.error("Failed to get any classes.", ex);
83              return new Class[0];
84          }
85      }
86  
87      /**
88       * Get a map of known implementors of some interface or abstract class. This
89       * is currently done by looking up a plugins file by the name of the given
90       * class, and assuming that values are implementors of said class. Those
91       * that are not are warned, but ignored. The reply is in the form of a map
92       * of keys=strings, and values=classes in case you need to get at the names
93       * given to the classes in the plugin file.
94       * 
95       * @see PluginUtil#getImplementors(Class)
96       * @param clazz
97       *            The class or interface to find implementors of.
98       * @return The map of implementing classes.
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 " + impl.getName() + " does not implement " + clazz.getName() + ". Ignoring.");
113                     }
114                 } catch (ClassNotFoundException ex) {
115                     log.warn("Failed to add class to list: " + clazz.getName(), ex);
116                 }
117             }
118 
119             log.debug("Found " + matches.size() + " implementors of " + 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 clazz
134      *            The class or interface to find an implementation of.
135      * @return The configured implementing class.
136      * @throws MalformedURLException
137      *             if the plugin file can not be found
138      * @throws IOException
139      *             if there is a problem reading the found file
140      * @throws ClassNotFoundException
141      *             if the read contents are not found
142      * @throws ClassCastException
143      *             if the read contents are not valid
144      * @see PluginUtil#getImplementors(Class)
145      */
146     public static <T> Class<T> getImplementor(Class<T> clazz) throws IOException, ClassNotFoundException, ClassCastException {
147         PropertyMap props = getPlugin(clazz);
148         String name = props.get(DEFAULT);
149 
150         Class<T> impl = (Class<T>) ClassUtil.forName(name);
151         if (!clazz.isAssignableFrom(impl)) {
152             throw new ClassCastException(JSOtherMsg.lookupText("Class {0} does not implement {1}.", impl.getName(), clazz.getName()));
153         }
154 
155         return impl;
156     }
157 
158     /**
159      * Get and instantiate the preferred implementor of some interface or
160      * abstract class.
161      * 
162      * @param clazz
163      *            The class or interface to find an implementation of.
164      * @return The configured implementing class.
165      * @throws MalformedURLException
166      *             if the plugin file can not be found
167      * @throws IOException
168      *             if there is a problem reading the found file
169      * @throws ClassNotFoundException
170      *             if the read contents are not found
171      * @throws ClassCastException
172      *             if the read contents are not valid
173      * @throws InstantiationException
174      *             if the new object can not be instantiated
175      * @throws IllegalAccessException
176      *             if the new object can not be instantiated
177      * @see PluginUtil#getImplementors(Class)
178      */
179     public static <T> T getImplementation(Class<T> clazz) throws MalformedURLException, ClassCastException, IOException, ClassNotFoundException,
180             InstantiationException, IllegalAccessException
181     {
182         return getImplementor(clazz).newInstance();
183     }
184 
185     /**
186      * Get and load a plugin file by looking it up as a resource.
187      * 
188      * @param clazz
189      *            The name of the desired resource
190      * @return The found and loaded plugin file
191      * @throws IOException
192      *             if the resource can not be loaded
193      * @throws MissingResourceException
194      *             if the resource can not be found
195      */
196     public static <T> PropertyMap getPlugin(Class<T> clazz) throws IOException {
197         String subject = ClassUtil.getShortClassName(clazz);
198 
199         try {
200             String lookup = subject + PluginUtil.EXTENSION_PLUGIN;
201             InputStream in = ResourceUtil.getResourceAsStream(clazz, lookup);
202 
203             PropertyMap prop = new PropertyMap();
204             prop.load(in);
205             return prop;
206         } catch (MissingResourceException e) {
207             return new PropertyMap();
208         }
209     }
210 
211     /**
212      * Extension for properties files
213      */
214     public static final String EXTENSION_PLUGIN = ".plugin";
215 
216     /**
217      * The string for default implementations
218      */
219     private static final String DEFAULT = "default";
220 
221     /**
222      * The log stream
223      */
224     private static final Logger log = Logger.getLogger(PluginUtil.class);
225 
226 }
227