/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI for * visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions Suite #1A 2328 Government Street Victoria BC V8T 5G5 Canada * * (250)385-6040 www.vividsolutions.com */ package com.vividsolutions.jump.workbench.plugin; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import org.apache.log4j.Logger; import com.vividsolutions.jts.util.Assert; import com.vividsolutions.jump.I18N; import com.vividsolutions.jump.task.TaskMonitor; import com.vividsolutions.jump.util.LangUtil; import com.vividsolutions.jump.util.StringUtil; import com.vividsolutions.jump.workbench.WorkbenchContext; /** * Loads plug-ins (or more precisely, Extensions), and any JAR files that they * depend on, from the plug-in directory. */ public class PlugInManager { private static Logger LOG = Logger.getLogger(PlugInManager.class); private TaskMonitor monitor; private WorkbenchContext context; private Collection configurations = new ArrayList(); private File plugInDirectory; /** * @param plugInDirectory * null to leave unspecified */ public PlugInManager(WorkbenchContext context, File plugInDirectory, TaskMonitor monitor) throws Exception { this.monitor = monitor; Assert.isTrue((plugInDirectory == null) || plugInDirectory.isDirectory()); classLoader = plugInDirectory != null ? new URLClassLoader( toURLs((File[]) findFilesRecursively(plugInDirectory).toArray( new File[] {}))) : getClass().getClassLoader(); I18N.setClassLoader(classLoader); this.context = context; //Find the configurations right away so they get reported to the splash //screen ASAP. [Jon Aquino] configurations .addAll(plugInDirectory != null ? findConfigurations(plugInDirectory) : new ArrayList()); configurations.addAll(findConfigurations(context.getWorkbench() .getProperties().getConfigurationClasses())); this.plugInDirectory = plugInDirectory; } public void load() throws Exception { loadPlugInClasses(context.getWorkbench().getProperties() .getPlugInClasses(getClassLoader())); loadConfigurations(); } private void loadConfigurations() throws Exception { for (Iterator i = configurations.iterator(); i.hasNext();) { Configuration configuration = (Configuration) i.next(); configuration.configure(new PlugInContext(context, null, null, null, null)); } } public static String name(Configuration configuration) { if (configuration instanceof Extension) { return ((Extension) configuration).getName(); } return StringUtil.toFriendlyName(configuration.getClass().getName(), "Configuration") + " (" + configuration.getClass().getPackage().getName() + ")"; } public static String version(Configuration configuration) { if (configuration instanceof Extension) { return ((Extension) configuration).getVersion(); } return ""; } private Collection findConfigurations(List classes) throws Exception { ArrayList configurations = new ArrayList(); for (Iterator i = classes.iterator(); i.hasNext();) { Class c = (Class) i.next(); if (!Configuration.class.isAssignableFrom(c)) { continue; } LOG.debug("Loading " + c.getName()); System.out.println("Loading " + c.getName()); Configuration configuration = (Configuration) c.newInstance(); configurations.add(configuration); monitor.report("Loading " + name(configuration) + " " + version(configuration)); } return configurations; } private void loadPlugInClasses(List plugInClasses) throws Exception { for (Iterator i = plugInClasses.iterator(); i.hasNext();) { Class plugInClass = (Class) i.next(); PlugIn plugIn = (PlugIn) plugInClass.newInstance(); plugIn.initialize(new PlugInContext(context, null, null, null, null)); } } private ClassLoader classLoader; private Collection findFilesRecursively(File directory) { Assert.isTrue(directory.isDirectory()); Collection files = new ArrayList(); for (Iterator i = Arrays.asList(directory.listFiles()).iterator(); i .hasNext();) { File file = (File) i.next(); if (file.isDirectory()) { files.addAll(findFilesRecursively(file)); } if (!file.isFile()) { continue; } files.add(file); } return files; } private Collection findConfigurations(File plugInDirectory) throws Exception { ArrayList configurations = new ArrayList(); for (Iterator i = findFilesRecursively(plugInDirectory).iterator(); i .hasNext();) { File file = (File) i.next(); try { configurations.addAll(findConfigurations(classes(new ZipFile( file), classLoader))); } catch (ZipException e) { //Might not be a zipfile. Eat it. [Jon Aquino] } } return configurations; } private URL[] toURLs(File[] files) { URL[] urls = new URL[files.length]; for (int i = 0; i < files.length; i++) { try { urls[i] = new URL("jar:file:" + files[i].getPath() + "!/"); } catch (MalformedURLException e) { Assert.shouldNeverReachHere(e.toString()); } } return urls; } private List classes(ZipFile zipFile, ClassLoader classLoader) { ArrayList classes = new ArrayList(); for (Enumeration e = zipFile.entries(); e.hasMoreElements();) { ZipEntry entry = (ZipEntry) e.nextElement(); //Filter by filename; otherwise we'll be loading all the classes, // which takes //significantly longer [Jon Aquino] if (!(entry.getName().endsWith("Extension.class") || entry .getName().endsWith("Configuration.class"))) { //Include "Configuration" for backwards compatibility. [Jon // Aquino] continue; } Class c = toClass(entry, classLoader); if (c != null) { classes.add(c); } } return classes; } private Class toClass(ZipEntry entry, ClassLoader classLoader) { if (entry.isDirectory()) { return null; } if (!entry.getName().endsWith(".class")) { return null; } if (entry.getName().indexOf("$") != -1) { //I assume it's not necessary to load inner classes explicitly. // [Jon Aquino] return null; } String className = entry.getName(); className = className.substring(0, className.length() - ".class".length()); className = StringUtil.replaceAll(className, "/", "."); Class candidate; try { candidate = classLoader.loadClass(className); } catch (ClassNotFoundException e) { Assert.shouldNeverReachHere("Class not found: " + className + ". Refine class name algorithm."); return null; } catch (Throwable t) { LOG.error("Throwable encountered loading " + className + ":"); //e.g. java.lang.VerifyError: class // org.apache.xml.serialize.XML11Serializer //overrides final method [Jon Aquino] t.printStackTrace(System.out); return null; } return candidate; } public Collection getConfigurations() { return Collections.unmodifiableCollection(configurations); } /** * To access extension classes, use this ClassLoader rather than the default * ClassLoader. Extension classes will not be present in the latter. */ public ClassLoader getClassLoader() { return classLoader; } /** * @return possibly null */ public File getPlugInDirectory() { return plugInDirectory; } }