001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI for
003: * visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or modify it under
008: * the terms of the GNU General Public License as published by the Free Software
009: * Foundation; either version 2 of the License, or (at your option) any later
010: * version.
011: *
012: * This program is distributed in the hope that it will be useful, but WITHOUT
013: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
014: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
015: * details.
016: *
017: * You should have received a copy of the GNU General Public License along with
018: * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
019: * Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions Suite #1A 2328 Government Street Victoria BC V8T 5G5 Canada
024: *
025: * (250)385-6040 www.vividsolutions.com
026: */
027: package com.vividsolutions.jump.workbench.plugin;
028:
029: import java.io.File;
030: import java.net.MalformedURLException;
031: import java.net.URL;
032: import java.net.URLClassLoader;
033: import java.util.*;
034: import java.util.zip.ZipEntry;
035: import java.util.zip.ZipException;
036: import java.util.zip.ZipFile;
037:
038: import org.apache.log4j.Logger;
039:
040: import com.vividsolutions.jts.util.Assert;
041: import com.vividsolutions.jump.task.TaskMonitor;
042: import com.vividsolutions.jump.util.LangUtil;
043: import com.vividsolutions.jump.util.StringUtil;
044: import com.vividsolutions.jump.workbench.WorkbenchContext;
045:
046: /**
047: * Loads plug-ins (or more precisely, Extensions), and any JAR files that they
048: * depend on, from the plug-in directory.
049: */
050: public class PlugInManager {
051: private static Logger LOG = Logger.getLogger(PlugInManager.class);
052: private TaskMonitor monitor;
053: private WorkbenchContext context;
054: private Collection configurations = new ArrayList();
055:
056: private File plugInDirectory;
057:
058: /**
059: * @param plugInDirectory
060: * null to leave unspecified
061: */
062: public PlugInManager(WorkbenchContext context,
063: File plugInDirectory, TaskMonitor monitor) throws Exception {
064: this .monitor = monitor;
065: Assert.isTrue((plugInDirectory == null)
066: || plugInDirectory.isDirectory());
067: classLoader = plugInDirectory != null ? new URLClassLoader(
068: toURLs((File[]) findFilesRecursively(plugInDirectory)
069: .toArray(new File[] {}))) : getClass()
070: .getClassLoader();
071: this .context = context;
072: //Find the configurations right away so they get reported to the splash
073: //screen ASAP. [Jon Aquino]
074: configurations
075: .addAll(plugInDirectory != null ? findConfigurations(plugInDirectory)
076: : new ArrayList());
077: configurations.addAll(findConfigurations(context.getWorkbench()
078: .getProperties().getConfigurationClasses()));
079: this .plugInDirectory = plugInDirectory;
080: }
081:
082: public void load() throws Exception {
083: loadPlugInClasses(context.getWorkbench().getProperties()
084: .getPlugInClasses(getClassLoader()));
085: loadConfigurations();
086: }
087:
088: private void loadConfigurations() throws Exception {
089: for (Iterator i = configurations.iterator(); i.hasNext();) {
090: Configuration configuration = (Configuration) i.next();
091: configuration.configure(new PlugInContext(context, null,
092: null, null, null));
093: }
094: }
095:
096: public static String name(Configuration configuration) {
097: if (configuration instanceof Extension) {
098: return ((Extension) configuration).getName();
099: }
100: return StringUtil.toFriendlyName(configuration.getClass()
101: .getName(), "Configuration")
102: + " ("
103: + configuration.getClass().getPackage().getName() + ")";
104: }
105:
106: public static String version(Configuration configuration) {
107: if (configuration instanceof Extension) {
108: return ((Extension) configuration).getVersion();
109: }
110: return "";
111: }
112:
113: private Collection findConfigurations(List classes)
114: throws Exception {
115: ArrayList configurations = new ArrayList();
116: for (Iterator i = classes.iterator(); i.hasNext();) {
117: Class c = (Class) i.next();
118: if (!Configuration.class.isAssignableFrom(c)) {
119: continue;
120: }
121: LOG.debug("Loading " + c.getName());
122: System.out.println("Loading " + c.getName());
123: Configuration configuration = (Configuration) c
124: .newInstance();
125: configurations.add(configuration);
126: monitor.report("Loading " + name(configuration) + " "
127: + version(configuration));
128: }
129: return configurations;
130: }
131:
132: private void loadPlugInClasses(List plugInClasses) throws Exception {
133: for (Iterator i = plugInClasses.iterator(); i.hasNext();) {
134: Class plugInClass = (Class) i.next();
135: PlugIn plugIn = (PlugIn) plugInClass.newInstance();
136: plugIn.initialize(new PlugInContext(context, null, null,
137: null, null));
138: }
139: }
140:
141: private ClassLoader classLoader;
142:
143: private Collection findFilesRecursively(File directory) {
144: Assert.isTrue(directory.isDirectory());
145: Collection files = new ArrayList();
146: for (Iterator i = Arrays.asList(directory.listFiles())
147: .iterator(); i.hasNext();) {
148: File file = (File) i.next();
149: if (file.isDirectory()) {
150: files.addAll(findFilesRecursively(file));
151: }
152: if (!file.isFile()) {
153: continue;
154: }
155: files.add(file);
156: }
157: return files;
158: }
159:
160: private Collection findConfigurations(File plugInDirectory)
161: throws Exception {
162: ArrayList configurations = new ArrayList();
163: for (Iterator i = findFilesRecursively(plugInDirectory)
164: .iterator(); i.hasNext();) {
165: File file = (File) i.next();
166: try {
167: configurations.addAll(findConfigurations(classes(
168: new ZipFile(file), classLoader)));
169: } catch (ZipException e) {
170: //Might not be a zipfile. Eat it. [Jon Aquino]
171: }
172: }
173: return configurations;
174: }
175:
176: private URL[] toURLs(File[] files) {
177: URL[] urls = new URL[files.length];
178: for (int i = 0; i < files.length; i++) {
179: try {
180: urls[i] = new URL("jar:file:" + files[i].getPath()
181: + "!/");
182: } catch (MalformedURLException e) {
183: Assert.shouldNeverReachHere(e.toString());
184: }
185: }
186: return urls;
187: }
188:
189: private List classes(ZipFile zipFile, ClassLoader classLoader) {
190: ArrayList classes = new ArrayList();
191: for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
192: ZipEntry entry = (ZipEntry) e.nextElement();
193: //Filter by filename; otherwise we'll be loading all the classes,
194: // which takes
195: //significantly longer [Jon Aquino]
196: if (!(entry.getName().endsWith("Extension.class") || entry
197: .getName().endsWith("Configuration.class"))) {
198: //Include "Configuration" for backwards compatibility. [Jon
199: // Aquino]
200: continue;
201: }
202: Class c = toClass(entry, classLoader);
203: if (c != null) {
204: classes.add(c);
205: }
206: }
207: return classes;
208: }
209:
210: private Class toClass(ZipEntry entry, ClassLoader classLoader) {
211: if (entry.isDirectory()) {
212: return null;
213: }
214: if (!entry.getName().endsWith(".class")) {
215: return null;
216: }
217: if (entry.getName().indexOf("$") != -1) {
218: //I assume it's not necessary to load inner classes explicitly.
219: // [Jon Aquino]
220: return null;
221: }
222: String className = entry.getName();
223: className = className.substring(0, className.length()
224: - ".class".length());
225: className = StringUtil.replaceAll(className, "/", ".");
226: Class candidate;
227: try {
228: candidate = classLoader.loadClass(className);
229: } catch (ClassNotFoundException e) {
230: Assert.shouldNeverReachHere("Class not found: " + className
231: + ". Refine class name algorithm.");
232: return null;
233: } catch (Throwable t) {
234: LOG.error("Throwable encountered loading " + className
235: + ":");
236: //e.g. java.lang.VerifyError: class
237: // org.apache.xml.serialize.XML11Serializer
238: //overrides final method [Jon Aquino]
239: t.printStackTrace(System.out);
240: return null;
241: }
242: return candidate;
243: }
244:
245: public Collection getConfigurations() {
246: return Collections.unmodifiableCollection(configurations);
247: }
248:
249: /**
250: * To access extension classes, use this ClassLoader rather than the default
251: * ClassLoader. Extension classes will not be present in the latter.
252: */
253: public ClassLoader getClassLoader() {
254: return classLoader;
255: }
256:
257: /**
258: * @return possibly null
259: */
260: public File getPlugInDirectory() {
261: return plugInDirectory;
262: }
263: }
|