svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.framework / org.gvsig.andami / src / main / java / org / gvsig / andami / plugins / PluginClassLoader.java @ 41073
History | View | Annotate | Download (17.9 KB)
1 | 40559 | jjdelcerro | /**
|
---|---|---|---|
2 | * gvSIG. Desktop Geographic Information System.
|
||
3 | 40435 | jjdelcerro | *
|
4 | 40559 | jjdelcerro | * Copyright (C) 2007-2013 gvSIG Association.
|
5 | 40435 | jjdelcerro | *
|
6 | * This program is free software; you can redistribute it and/or
|
||
7 | * modify it under the terms of the GNU General Public License
|
||
8 | 40559 | jjdelcerro | * as published by the Free Software Foundation; either version 3
|
9 | 40435 | jjdelcerro | * of the License, or (at your option) any later version.
|
10 | *
|
||
11 | * This program is distributed in the hope that it will be useful,
|
||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
14 | * GNU General Public License for more details.
|
||
15 | *
|
||
16 | * You should have received a copy of the GNU General Public License
|
||
17 | * along with this program; if not, write to the Free Software
|
||
18 | 40559 | jjdelcerro | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19 | * MA 02110-1301, USA.
|
||
20 | 40435 | jjdelcerro | *
|
21 | 40559 | jjdelcerro | * For any additional information, do not hesitate to contact us
|
22 | * at info AT gvsig.com, or visit our website www.gvsig.com.
|
||
23 | 40435 | jjdelcerro | */
|
24 | package org.gvsig.andami.plugins; |
||
25 | |||
26 | import java.io.DataInputStream; |
||
27 | import java.io.File; |
||
28 | import java.io.FileInputStream; |
||
29 | import java.io.IOException; |
||
30 | import java.io.InputStream; |
||
31 | import java.net.MalformedURLException; |
||
32 | import java.net.URL; |
||
33 | import java.net.URLClassLoader; |
||
34 | import java.security.AllPermission; |
||
35 | import java.security.CodeSource; |
||
36 | import java.security.PermissionCollection; |
||
37 | import java.util.ArrayList; |
||
38 | import java.util.Enumeration; |
||
39 | import java.util.Hashtable; |
||
40 | 40566 | jjdelcerro | import java.util.Iterator; |
41 | 40435 | jjdelcerro | import java.util.List; |
42 | 40566 | jjdelcerro | import java.util.Map; |
43 | import java.util.Map.Entry; |
||
44 | 40435 | jjdelcerro | import java.util.StringTokenizer; |
45 | import java.util.zip.ZipEntry; |
||
46 | import java.util.zip.ZipException; |
||
47 | import java.util.zip.ZipFile; |
||
48 | |||
49 | 40566 | jjdelcerro | import org.gvsig.andami.messages.Messages; |
50 | 40435 | jjdelcerro | import org.slf4j.Logger; |
51 | import org.slf4j.LoggerFactory; |
||
52 | |||
53 | |||
54 | |||
55 | /**
|
||
56 | * <p>Class loader which loads the classes requested by the
|
||
57 | * plugins. It first tries to search in the classpath, then it requests
|
||
58 | * the class to the parent classloader, then it searches in the owns
|
||
59 | * plugins' library dir, and if all these methods fail, it tries to load
|
||
60 | * the class from any of the depended plugins. Finally, if this also
|
||
61 | * fails, the other classloaders provided in the <code>addLoaders</code> method
|
||
62 | * are requested to load the class.</p>
|
||
63 | *
|
||
64 | * <p>The class loader can also be used to load resources from the
|
||
65 | * plugin's directory by using the <code>getResource()</code> method.</p>
|
||
66 | *
|
||
67 | * @author Fernando Gonz�lez Cort�s
|
||
68 | */
|
||
69 | public class PluginClassLoader extends URLClassLoader { |
||
70 | /** DOCUMENT ME! */
|
||
71 | 40599 | jjdelcerro | private static Logger logger = LoggerFactory.getLogger(PluginClassLoader.class.getName()); |
72 | 40435 | jjdelcerro | |
73 | /** DOCUMENT ME! */
|
||
74 | 40566 | jjdelcerro | private Hashtable<String, ZipFile> clasesJar = new Hashtable<String, ZipFile>(); |
75 | 40435 | jjdelcerro | |
76 | /** DOCUMENT ME! */
|
||
77 | private File baseDir; |
||
78 | private PluginClassLoader[] pluginLoaders; |
||
79 | 40566 | jjdelcerro | private static List<ClassLoader> otherLoaders=new ArrayList<ClassLoader>(); |
80 | 40435 | jjdelcerro | private boolean isOtherLoader=false; |
81 | |||
82 | /**
|
||
83 | * Creates a new PluginClassLoader object.
|
||
84 | *
|
||
85 | * @param jars Array with the search paths where classes will be searched
|
||
86 | * @param baseDir Base directory for this plugin. This is the directory
|
||
87 | * which will be used as basedir in the <code>getResources</code> method.
|
||
88 | * @param cl The parent classloader of this classloader. It will be used to
|
||
89 | * search classes before trying to search in the plugin's directory
|
||
90 | * @param pluginLoaders The classloaders of the depended plugins.
|
||
91 | *
|
||
92 | * @throws IOException
|
||
93 | */
|
||
94 | public PluginClassLoader(URL[] jars, String baseDir, ClassLoader cl, |
||
95 | 41025 | jjdelcerro | List<PluginClassLoader> pluginLoaders) throws IOException { |
96 | this(jars, baseDir, cl, (PluginClassLoader[]) pluginLoaders.toArray(new PluginClassLoader[pluginLoaders.size()]) ); |
||
97 | } |
||
98 | |||
99 | public PluginClassLoader(URL[] jars, String baseDir, ClassLoader cl, |
||
100 | 40435 | jjdelcerro | PluginClassLoader[] pluginLoaders) throws IOException { |
101 | super(jars, cl);
|
||
102 | 40566 | jjdelcerro | logger.debug("Creating PluginClassLoader for {}.", baseDir);
|
103 | 40435 | jjdelcerro | this.baseDir = new File(new File(baseDir).getAbsolutePath()); |
104 | this.pluginLoaders = pluginLoaders;
|
||
105 | |||
106 | 40566 | jjdelcerro | if( jars==null || jars.length<1 ) { |
107 | debugDump(); |
||
108 | 40562 | jjdelcerro | return;
|
109 | } |
||
110 | 40435 | jjdelcerro | ZipFile[] jarFiles = new ZipFile[jars.length]; |
111 | |||
112 | for (int i = 0; i < jars.length; i++) { |
||
113 | try {
|
||
114 | jarFiles[i] = new ZipFile(jars[i].getPath()); |
||
115 | |||
116 | 40566 | jjdelcerro | Enumeration<? extends ZipEntry> entradas = jarFiles[i].entries(); |
117 | 40435 | jjdelcerro | |
118 | while (entradas.hasMoreElements()) {
|
||
119 | ZipEntry file = (ZipEntry) entradas.nextElement(); |
||
120 | String fileName = file.getName();
|
||
121 | |||
122 | if (!fileName.toLowerCase().endsWith(".class")) { //$NON-NLS-1$ |
||
123 | |||
124 | continue;
|
||
125 | } |
||
126 | |||
127 | fileName = fileName.substring(0, fileName.length() - 6) |
||
128 | .replace('/', '.'); |
||
129 | |||
130 | if (clasesJar.get(fileName) != null) { |
||
131 | 40566 | jjdelcerro | logger.warn("Duplicated class {} in {} and {}",
|
132 | new Object[] { |
||
133 | fileName, |
||
134 | jarFiles[i].getName(), |
||
135 | clasesJar.get(fileName).getName() |
||
136 | } |
||
137 | ); |
||
138 | 40435 | jjdelcerro | } |
139 | else {
|
||
140 | clasesJar.put(fileName, jarFiles[i]); |
||
141 | } |
||
142 | } |
||
143 | } catch (ZipException e) { |
||
144 | throw new IOException(e.getMessage() + " Jar: " + |
||
145 | jars[i].getPath() + ": " + jarFiles[i]);
|
||
146 | } catch (IOException e) { |
||
147 | throw e;
|
||
148 | 40566 | jjdelcerro | } finally {
|
149 | debugDump(); |
||
150 | 40435 | jjdelcerro | } |
151 | } |
||
152 | } |
||
153 | |||
154 | 40566 | jjdelcerro | private void debugDump() { |
155 | if( !logger.isDebugEnabled() ) {
|
||
156 | return;
|
||
157 | } |
||
158 | logger.debug(this.toString());
|
||
159 | logger.debug(" baseDir: " + this.baseDir); |
||
160 | logger.debug(" parent: " + this.getParent()); |
||
161 | logger.debug(" depends:");
|
||
162 | for( int n=0; n<this.pluginLoaders.length; n++ ) { |
||
163 | logger.debug(" {}", this.pluginLoaders[n].toString() ); |
||
164 | } |
||
165 | logger.debug(" urls:");
|
||
166 | URL[] urls = this.getURLs(); |
||
167 | for( int n=0 ; n<urls.length; n++ ) { |
||
168 | logger.debug(" " + urls[n].toString());
|
||
169 | } |
||
170 | logger.debug(" classes:");
|
||
171 | Iterator<Map.Entry<String, ZipFile>>it = this.clasesJar.entrySet().iterator(); |
||
172 | while( it.hasNext() ) {
|
||
173 | Entry<String, ZipFile> entry = it.next(); |
||
174 | logger.debug(" "+ entry.getKey() + "("+entry.getValue().getName()+")"); |
||
175 | } |
||
176 | } |
||
177 | |||
178 | 40435 | jjdelcerro | protected Class singleLoadClass(String name) throws ClassNotFoundException { |
179 | // Buscamos en las clases de las librer�as del plugin
|
||
180 | 40566 | jjdelcerro | Class<?> c = findLoadedClass(name);
|
181 | 40435 | jjdelcerro | |
182 | if (c != null) { |
||
183 | return c;
|
||
184 | } |
||
185 | |||
186 | 40566 | jjdelcerro | logger.debug("Searching class '{}' in {}", new Object[] {name, this.toString()}); |
187 | 40435 | jjdelcerro | try {
|
188 | ZipFile jar = (ZipFile) clasesJar.get(name); |
||
189 | |||
190 | 40566 | jjdelcerro | //No esta en ningun jar
|
191 | 40435 | jjdelcerro | if (jar == null) { |
192 | //Buscamos en el directorio de clases
|
||
193 | String classFileName = baseDir + "/classes/" + |
||
194 | name.replace('.', '/') + ".class"; |
||
195 | File f = new File(classFileName); |
||
196 | if (f.exists()){
|
||
197 | byte[] data = loadClassData(f); |
||
198 | c = defineClass(name, data, 0, data.length);
|
||
199 | 40566 | jjdelcerro | logger.debug("Found class {} in classes-folder of plugin {}",
|
200 | new Object[] {name, baseDir}); |
||
201 | 40435 | jjdelcerro | |
202 | 40566 | jjdelcerro | } else {
|
203 | 40435 | jjdelcerro | //Buscamos en los otros plugins
|
204 | 40566 | jjdelcerro | logger.debug("Searching in depends pluginLoaders");
|
205 | 40435 | jjdelcerro | for (int i = 0; i < pluginLoaders.length; i++) { |
206 | 40566 | jjdelcerro | c = null;
|
207 | if (pluginLoaders[i] != null) { |
||
208 | try {
|
||
209 | 40435 | jjdelcerro | c = pluginLoaders[i].singleLoadClass(name); |
210 | 40566 | jjdelcerro | } catch (ClassNotFoundException e) { |
211 | // Si no la encontramos en el primer plugin, capturamos la exceptcion
|
||
212 | // porque es probable que la encontremos en el resto de plugins.
|
||
213 | } |
||
214 | } |
||
215 | 40435 | jjdelcerro | if (c != null) { |
216 | break;
|
||
217 | } |
||
218 | } |
||
219 | } |
||
220 | } else {
|
||
221 | String fileName = name.replace('.', '/') + ".class"; |
||
222 | ZipEntry classFile = jar.getEntry(fileName);
|
||
223 | byte[] data = loadClassData(classFile, |
||
224 | jar.getInputStream(classFile)); |
||
225 | |||
226 | c = defineClass(name, data, 0, data.length);
|
||
227 | |||
228 | 40566 | jjdelcerro | logger.debug("Found class {} in jar {} of plugin {}",
|
229 | new Object[] { name, jar.getName(), baseDir} ); |
||
230 | 40435 | jjdelcerro | } |
231 | |||
232 | if (c == null) { |
||
233 | throw new ClassNotFoundException(name); |
||
234 | } |
||
235 | |||
236 | return c;
|
||
237 | } catch (IOException e) { |
||
238 | throw new ClassNotFoundException(Messages.getString( |
||
239 | "PluginClassLoader.Error_reading_file") + name);
|
||
240 | } |
||
241 | } |
||
242 | |||
243 | /**
|
||
244 | * Carga la clase
|
||
245 | *
|
||
246 | * @param name Nombre de la clase
|
||
247 | * @param resolve Si se ha de resolver la clase o no
|
||
248 | *
|
||
249 | * @return Clase cargada
|
||
250 | *
|
||
251 | * @throws ClassNotFoundException Si no se pudo encontrar la clase
|
||
252 | */
|
||
253 | protected Class loadClass(String name, boolean resolve) |
||
254 | throws ClassNotFoundException { |
||
255 | 40566 | jjdelcerro | Class<?> c = null; |
256 | 40435 | jjdelcerro | |
257 | // Intentamos cargar con el system classloader
|
||
258 | try {
|
||
259 | if (!isOtherLoader)
|
||
260 | c = super.loadClass(name, resolve);
|
||
261 | 40566 | jjdelcerro | logger.debug("Found class {} in system-classloader", name);
|
262 | 40435 | jjdelcerro | } catch (ClassNotFoundException e1) { |
263 | try {
|
||
264 | c = singleLoadClass(name); |
||
265 | } catch (ClassNotFoundException e2) { |
||
266 | try {
|
||
267 | isOtherLoader=true;
|
||
268 | c = loadOtherClass(name); |
||
269 | }catch (ClassNotFoundException e3) { |
||
270 | // throw new ClassNotFoundException(Messages.getString(
|
||
271 | // "PluginClassLoader.Error_reading_file")
|
||
272 | // + name, e3);
|
||
273 | throw new ClassNotFoundException("Class " + name |
||
274 | + " not found through the plugin " + baseDir, e3);
|
||
275 | }finally {
|
||
276 | isOtherLoader=false;
|
||
277 | } |
||
278 | } |
||
279 | } |
||
280 | if (c==null) |
||
281 | throw new ClassNotFoundException(Messages.getString( |
||
282 | "PluginClassLoader.Error_reading_file") + name);
|
||
283 | if (resolve) {
|
||
284 | resolveClass(c); |
||
285 | } |
||
286 | return c;
|
||
287 | } |
||
288 | 40566 | jjdelcerro | private Class<?> loadOtherClass(String name) |
289 | 40435 | jjdelcerro | throws ClassNotFoundException |
290 | { |
||
291 | ClassLoader[] ocl=(ClassLoader[])otherLoaders.toArray(new ClassLoader[0]); |
||
292 | 40566 | jjdelcerro | Class<?> c=null; |
293 | 40435 | jjdelcerro | for (int i=0;i<ocl.length;i++) { |
294 | c=ocl[i].loadClass(name); |
||
295 | if (c != null) { |
||
296 | 40566 | jjdelcerro | logger.debug("Found class {} by the alternative classloaders of plugin {}",
|
297 | 40435 | jjdelcerro | name, baseDir); |
298 | |||
299 | return c;
|
||
300 | } |
||
301 | } |
||
302 | throw new ClassNotFoundException(name); |
||
303 | } |
||
304 | |||
305 | /**
|
||
306 | * obtiene el array de bytes de la clase
|
||
307 | *
|
||
308 | * @param classFile Entrada dentro del jar contiene los bytecodes de la
|
||
309 | * clase (el .class)
|
||
310 | * @param is InputStream para leer la entrada del jar
|
||
311 | *
|
||
312 | * @return Bytes de la clase
|
||
313 | *
|
||
314 | * @throws IOException Si no se puede obtener el .class del jar
|
||
315 | */
|
||
316 | private byte[] loadClassData(ZipEntry classFile, InputStream is) |
||
317 | throws IOException { |
||
318 | // Get size of class file
|
||
319 | int size = (int) classFile.getSize(); |
||
320 | |||
321 | // Reserve space to read
|
||
322 | byte[] buff = new byte[size]; |
||
323 | |||
324 | // Get stream to read from
|
||
325 | DataInputStream dis = new DataInputStream(is); |
||
326 | |||
327 | // Read in data
|
||
328 | dis.readFully(buff); |
||
329 | |||
330 | // close stream
|
||
331 | dis.close(); |
||
332 | |||
333 | // return data
|
||
334 | return buff;
|
||
335 | } |
||
336 | |||
337 | /**
|
||
338 | * Gets the bytes of a File
|
||
339 | *
|
||
340 | * @param file File
|
||
341 | *
|
||
342 | * @return bytes of file
|
||
343 | *
|
||
344 | * @throws IOException If the operation fails
|
||
345 | */
|
||
346 | private byte[] loadClassData(File file) throws IOException { |
||
347 | InputStream is = new FileInputStream(file); |
||
348 | |||
349 | // Get the size of the file
|
||
350 | long length = file.length();
|
||
351 | |||
352 | // You cannot create an array using a long type.
|
||
353 | // It needs to be an int type.
|
||
354 | // Before converting to an int type, check
|
||
355 | // to ensure that file is not larger than Integer.MAX_VALUE.
|
||
356 | if (length > Integer.MAX_VALUE) { |
||
357 | // File is too large
|
||
358 | } |
||
359 | |||
360 | // Create the byte array to hold the data
|
||
361 | byte[] bytes = new byte[(int) length]; |
||
362 | |||
363 | // Read in the bytes
|
||
364 | int offset = 0; |
||
365 | int numRead = 0; |
||
366 | |||
367 | while ((offset < bytes.length) &&
|
||
368 | ((numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)) {
|
||
369 | offset += numRead; |
||
370 | } |
||
371 | |||
372 | 40566 | jjdelcerro | // Close the input stream and return bytes
|
373 | is.close(); |
||
374 | |||
375 | 40435 | jjdelcerro | // Ensure all the bytes have been read in
|
376 | if (offset < bytes.length) {
|
||
377 | throw new IOException("Could not completely read file " + |
||
378 | file.getName()); |
||
379 | } |
||
380 | |||
381 | return bytes;
|
||
382 | } |
||
383 | |||
384 | /**
|
||
385 | * Gets the requested resource. If the path is relative, its base directory
|
||
386 | * will be the one provided in the PluginClassLoader's constructor.
|
||
387 | 41073 | jjdelcerro | * If the resource is not found, search in dependents plugins, otherwise
|
388 | * the parent classloader will be invoked to try to get it.
|
||
389 | * If it is not found, it will return null.
|
||
390 | 40435 | jjdelcerro | *
|
391 | * @param res An absolute or relative path to the requested resource.
|
||
392 | *
|
||
393 | * @return Resource's URL if it was found, nul otherwise.
|
||
394 | */
|
||
395 | public URL getResource(String res) { |
||
396 | 40566 | jjdelcerro | URL ret = null; |
397 | 41073 | jjdelcerro | //
|
398 | // Primero buscamos en el directorio del plugin.
|
||
399 | try {
|
||
400 | 40566 | jjdelcerro | logger.debug("Search resource {} in {}", res, this.baseDir.toString()); |
401 | List<String> resource = new ArrayList<String>(); |
||
402 | 40435 | jjdelcerro | StringTokenizer st = new StringTokenizer(res, "\\/"); |
403 | while (st.hasMoreTokens()) {
|
||
404 | String token = st.nextToken();
|
||
405 | resource.add(token); |
||
406 | } |
||
407 | 40566 | jjdelcerro | ret = getResource(baseDir, resource); |
408 | 40435 | jjdelcerro | if (ret != null) { |
409 | return ret;
|
||
410 | } |
||
411 | } catch (Exception e) { |
||
412 | 40566 | jjdelcerro | logger.info("Error getting resource {} in {}'", new Object[] {res,this.baseDir.toString()}, e); |
413 | 40435 | jjdelcerro | } |
414 | 40566 | jjdelcerro | |
415 | 41073 | jjdelcerro | //
|
416 | // Luego en los plugins de los que depende
|
||
417 | logger.debug("Searching in depends pluginLoaders");
|
||
418 | for (int i = 0; i < this.pluginLoaders.length; i++) { |
||
419 | PluginClassLoader pluginClassLoader = pluginLoaders[i]; |
||
420 | if (pluginClassLoader != null) { |
||
421 | try {
|
||
422 | ret = pluginClassLoader.getResource(res); |
||
423 | if (ret != null) { |
||
424 | logger.info("Found resource in plugin '"+pluginClassLoader.getPluginName()+"' ("+res+")."); |
||
425 | return ret;
|
||
426 | } |
||
427 | } catch (Exception e) { |
||
428 | // Ignore, try in next classloader
|
||
429 | } |
||
430 | } |
||
431 | } |
||
432 | |||
433 | //
|
||
434 | // Por ultimo en el class loader padre, se supone que es el del sistema.
|
||
435 | 40566 | jjdelcerro | try {
|
436 | ret = super.getResource(res);
|
||
437 | } catch (Exception e) { |
||
438 | logger.info("Error getting resource {} in {}'", new Object[] {res,this.baseDir.toString()}, e); |
||
439 | } |
||
440 | 41073 | jjdelcerro | |
441 | |||
442 | 40566 | jjdelcerro | if( ret == null ) { |
443 | 41025 | jjdelcerro | logger.info("Error getting resource {} in {}'", new Object[] {res,this.baseDir.toString()}); |
444 | 40566 | jjdelcerro | } |
445 | return ret;
|
||
446 | 40435 | jjdelcerro | } |
447 | |||
448 | /**
|
||
449 | * Gets the requested resource. If the path is relative, its base directory
|
||
450 | * will be the one provided in the PluginClassLoader's constructor.
|
||
451 | * If the resource is not found, the parent classloader will be invoked
|
||
452 | * to try to get it. If it is not found, it will return null.
|
||
453 | *
|
||
454 | * @param res An absolute or relative path to the requested resource.
|
||
455 | *
|
||
456 | * @return Resource's URL if it was found, nul otherwise.
|
||
457 | */
|
||
458 | 40566 | jjdelcerro | private URL getResource(File base, List<String> res) { |
459 | 40435 | jjdelcerro | File[] files = base.listFiles(); |
460 | |||
461 | 40566 | jjdelcerro | String parte = res.get(0); |
462 | 40435 | jjdelcerro | |
463 | for (int i = 0; i < files.length; i++) { |
||
464 | if (files[i].getName().compareTo(parte) == 0) { |
||
465 | if (res.size() == 1) { |
||
466 | try {
|
||
467 | return new URL("file:" + files[i].toString()); |
||
468 | } catch (MalformedURLException e) { |
||
469 | return null; |
||
470 | } |
||
471 | } else {
|
||
472 | return getResource(files[i], res.subList(1, res.size())); |
||
473 | } |
||
474 | } |
||
475 | } |
||
476 | |||
477 | return null; |
||
478 | } |
||
479 | |||
480 | /**
|
||
481 | * Returns the name of the plugin (the name of the directory containing
|
||
482 | * the plugin).
|
||
483 | *
|
||
484 | * @return An String containing the plugin's name.
|
||
485 | */
|
||
486 | public String getPluginName() { |
||
487 | String ret = baseDir.getAbsolutePath().substring(baseDir.getAbsolutePath()
|
||
488 | .lastIndexOf(File.separatorChar) +
|
||
489 | 1);
|
||
490 | |||
491 | return ret;
|
||
492 | } |
||
493 | |||
494 | /*
|
||
495 | * @see java.security.SecureClassLoader#getPermissions(java.security.CodeSource)
|
||
496 | */
|
||
497 | protected PermissionCollection getPermissions(CodeSource codesource) { |
||
498 | PermissionCollection perms = super.getPermissions(codesource); |
||
499 | perms.add(new AllPermission()); |
||
500 | |||
501 | return perms;
|
||
502 | } |
||
503 | |||
504 | /**
|
||
505 | 40566 | jjdelcerro | * Gets the plugin's base Iterator<Map.Entry<Integer, Integer>>dir, the directory which will be used to
|
506 | 40435 | jjdelcerro | * search resources.
|
507 | *
|
||
508 | * @return Returns the baseDir.
|
||
509 | */
|
||
510 | public String getBaseDir() { |
||
511 | return baseDir.getAbsolutePath();
|
||
512 | } |
||
513 | |||
514 | /**
|
||
515 | * Adds other classloader to use when all the normal methods fail.
|
||
516 | *
|
||
517 | * @param classLoaders An ArrayList of ClassLoaders which will
|
||
518 | * be used to load classes when all the normal methods fail.
|
||
519 | */
|
||
520 | public static void addLoaders(ArrayList classLoaders) { |
||
521 | otherLoaders.addAll(classLoaders); |
||
522 | } |
||
523 | |||
524 | @Override
|
||
525 | public String toString() { |
||
526 | return super.toString() + " (" + getPluginName() + ")"; |
||
527 | } |
||
528 | } |