001 /* PAVLOV -- Multiple Choice Study System
002 * Copyright (C) 2000 - 2004 T.J. Willis
003 *
004 * This program is free software; you can redistribute it and/or
005 * modify it under the terms of the GNU General Public License
006 * as published by the Free Software Foundation; either version 2
007 * of the License, or (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program; if not, write to the Free Software
016 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
017 *
018 * $Header: /cvsroot/pavlov/net/sourceforge/pavlov/pluglets/PlugletLoader.java,v 1.9 2004/07/01 05:50:20 tj_willis Exp $
019 */
020 package net.sourceforge.pavlov.pluglets;
021
022
023
024
025 import java.io.File;
026 import java.util.*;
027 import java.net.*;
028 import javax.swing.*;
029 import java.awt.Container;
030 import net.sourceforge.pavlov.event.*;
031 import org.apache.log4j.*;
032
033
034 /**
035 * <code>PlugletLoader</code>
036 *
037 * Generates a vector of objects that are a subclass of a given class.
038 * These objects must have a no-arg constructor and be in a given directory.
039 *
040 * If a serious error occurs (no such directory, can't read directory, no
041 * files) the vector will be null. Otherwise, individual classes are skipped.
042 * Current implementation prints status and error/exception information
043 * to stdout.
044 *
045 * There is, at this time, little support for JarFiles or subdirectories.
046 *
047 * @author <a href="mailto:tj_willis@users.sourceforge.net"></a>
048 * @version $Revision: 1.9 $
049 */
050 public class PlugletLoader {
051 /**
052 * Vector of pluglets that have been successfully loaded.
053 *
054 */
055 protected Vector<Pluglet> pluglets = null;
056 protected static final Category cat = Category.getInstance(PlugletLoader.class.getName());
057
058 protected PlugletLoader()
059 {
060
061 }
062
063 /**
064 * Creates a new <code>PlugletLoader</code> instance.
065 *
066 * @param baseClassName a <code>String</code> value
067 * @param directory a <code>String</code> value
068 */
069 public PlugletLoader(String baseClassName, String directory) {
070 cat.setLevel(Level.WARN);
071 if(baseClassName == null)
072 return;
073 if(directory == null)
074 return;
075
076 File f = new File(directory);
077 if( !f.exists())
078 return;
079 if( !f.canRead())
080 return;
081 if( !f.isDirectory())
082 return;
083
084 File fs[] = f.listFiles();
085 //List<File> files = Arrays.asList(fs);
086 int nFiles = java.lang.reflect.Array.getLength(fs);
087 if(nFiles<1)
088 return;
089 //if ( files.isEmpty()) return;
090
091 Vector<URL> urls = new Vector<URL>();//files.size());
092 ArrayList<URL> jars = new ArrayList<URL>();//files.size());
093
094 URL d;
095
096 for(File z : fs) {
097 try {
098 d = z.toURL();
099 urls.add((URL)d);
100 if(isAJarFile(z))
101 jars.add((URL)d);
102 } catch (java.net.MalformedURLException e) {
103 cat.info("Can't load pluglet: " + e);
104 }
105 }
106
107 if(urls.isEmpty())
108 return;
109
110
111 URL urlsarr[] = new URL[jars.size()+1];
112 URL base = null;
113 try {
114 base = new URL("file:" + directory);
115 cat.info("Base URL for pluglets is :" + base);
116 } catch (Exception e) {
117 cat.error("Exception creating base directory : " + base);
118 }
119
120 if(!jars.isEmpty()) {
121 jars.add(0,base);
122 //urlsarr = (URL[])jars.toArray(((Object[])urlsarr));
123 urlsarr = (URL[])jars.toArray(urlsarr);
124 } else {
125 //urlsarr = new URL[1];
126 urlsarr[0] = base;
127 }
128
129 URLClassLoader cl = null;
130 try {
131 cl = URLClassLoader.newInstance(urlsarr);
132 } catch (Exception e) {
133 return;
134 }
135
136
137 pluglets = new Vector<Pluglet>(nFiles);//files.size());
138
139 for(URL u : urls) {
140 try {
141 String t = u.toString();
142 String s = mungeName(t);
143 if (s==null)
144 continue;
145 cat.info("Loading pluglet: " + s);
146 Class lala = Class.forName(baseClassName);
147 Class c = cl.loadClass(s);
148 if( lala.isAssignableFrom (c) && lala!=c ) {
149 try {
150 //Object bar = (AbstractFeedbackApplet)c.newInstance();
151 Pluglet bar = (Pluglet)c.newInstance();
152 pluglets.add(bar);
153 } catch (java.lang.InstantiationException e) {
154 cat.warn("Cannot load pluglet " ,e);
155 } catch (java.lang.IllegalAccessException e) {
156 cat.warn("Cannot load pluglet " ,e);
157 }
158 }
159 } catch (Exception e) {
160 cat.warn( "Cannot load pluglet " , e );
161 } catch (Error er) {
162 cat.warn( "Cannot load pluglet " , er );
163 }
164 }
165 }
166
167 /**
168 * Given a JMenu, will add to it each loaded pluglet as a
169 * JCheckboxMenuItem. The item's name will be the pluglet's getText()
170 * and the item's tooltip will be the pluglet's getDescription().
171 *
172 * @param menu a <code>JMenu</code> value
173 */
174 public void addToJMenuAsCheckboxes(JMenu menu) {
175 if(pluglets==null)
176 return;
177 if(menu==null)
178 return;
179 for(Pluglet ap: pluglets) {
180 JCheckBoxMenuItem mi = new JCheckBoxMenuItem(ap.getName());
181 mi.setToolTipText(ap.getDescription());
182 menu.add(mi);
183 //addInternalFrame(ap);
184 mi.addActionListener(ap);
185 }
186 }
187
188 /**
189 * Given a JMenu, will add to it each loaded pluglet as a
190 * JRadioButtonMenuItem. The item's name will be the pluglet's getText()
191 * and the item's tooltip will be the pluglet's getDescription().
192 * Returns the ButtonGroup to which the items belong.
193 * @param menu a <code>JMenu</code> value
194 * @return a <code>ButtonGroup</code> value
195 */
196 public ButtonGroup addToJMenuAsRadioButtons(JMenu menu) {
197 if(pluglets==null)
198 return null;
199 if(menu==null)
200 return null;
201 ButtonGroup radioButtons = new ButtonGroup();
202 for(Pluglet ap: pluglets) {
203 JRadioButtonMenuItem mi
204 = new JRadioButtonMenuItem(ap.getName());
205 mi.setToolTipText(ap.getDescription());
206 menu.add(mi);
207 //addInternalFrame(ap);
208 mi.addActionListener(ap);
209 }
210 return radioButtons;
211 }
212
213
214 /**
215 * Add each of my pluglets that are a subclass of java.awt.Component,
216 * to the given java.awt.Container.
217 *
218 * @param cont a <code>Container</code> value
219 */
220 public void addToContainer(Container cont) {
221 if(pluglets==null)
222 return;
223 if(cont==null)
224 return;
225 for(Pluglet ap: pluglets) {
226 try {
227 Class comp = Class.forName("java.awt.Component");
228 Class wind = Class.forName("java.awt.Component");
229 Class c = ap.getClass();
230 if( comp.isAssignableFrom (c) && !wind.isAssignableFrom(c) )
231 cont.add( (java.awt.Component)ap );
232 } catch (Exception e) {
233 cat.warn("Can't add pluglet " + ap + " to container " + cont,e);
234 return;
235 }
236 }
237 }
238
239
240 // FIXME: if large number of pluglets, reimp with hashtable vs. vector
241 /**
242 * Returns the named plugin if it exists, otherwise null.
243 *
244 * @param name a <code>String</code> value
245 * @return a <code>Pluglet</code> value
246 */
247 public Pluglet getPluginByName(String name) {
248 if(name==null)
249 return null;
250 for(Pluglet ap: pluglets) {
251 String targ = ap.getName();
252 if(targ==null)
253 continue;
254 if(targ.equals(name))
255 return ap;
256 }
257 return null;
258 }
259
260 // TOFIX: this should really be in a subclass...
261 /**
262 * Describe <code>addToAnswerEventProducer</code> method here.
263 *
264 * @param cont an <code>AnswerEventProducer</code> value
265 * @deprecated This should be in FeedbackPluglet
266 */
267 @Deprecated public void addToAnswerEventProducer(AnswerEventProducer cont) {
268 if(pluglets==null)
269 return;
270 if(cont==null)
271 return;
272 for(Pluglet ap: pluglets) {
273 cont.addAnswerListener( (AnswerListener)ap ); //addInternalFrame(ap);
274 }
275 }
276
277
278
279 /**
280 * Returns the filename, sans extention, of a given string.
281 * FIXME: this can be implemented with java.io.File methods
282 *
283 * @param in a <code>String</code> value
284 * @return a <code>String</code> value
285 */
286 protected String mungeName(String in) {
287 String f = new String(in);
288 int x = f.lastIndexOf('/')+1;
289 if(x==-1)
290 return null;
291 int y = f.indexOf('.');
292 if(y==-1)
293 return null;
294 String g = f.substring(x,y);
295 return g;
296 }
297
298 /**
299 * Returns true if the file is recognized as a Jar file.
300 *
301 * @param f a <code>File</code> value
302 * @return a <code>boolean</code> value
303 */
304 public boolean isAJarFile(File f) {
305 if(f==null)
306 return false;
307 if(f.toString().endsWith(".jar"))
308 return true;
309 if(f.toString().endsWith(".JAR"))
310 return true;
311 if(f.toString().endsWith(".Jar"))
312 return true;
313 return false;
314 }
315
316
317 }
318