/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2013-15 The Processing Foundation Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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 */ package processing.app.contrib; import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import processing.app.Base; import processing.app.Messages; import processing.app.Mode; import processing.app.Util; public class ModeContribution extends LocalContribution { private Mode mode; static public ModeContribution load(Base base, File folder) { return load(base, folder, null); } static public ModeContribution load(Base base, File folder, String searchName) { try { return new ModeContribution(base, folder, searchName); } catch (IgnorableException ig) { Messages.log(ig.getMessage()); } catch (Throwable err) { System.out.println("err is " + err); // Throwable to catch Exceptions or UnsupportedClassVersionError et al if (searchName == null) { //err.printStackTrace(System.out); // for 3.0b1, pass this through to the Contribution Manager so that // we can provide better error messages throw new RuntimeException(err); } else { // For the built-in modes, don't print the exception, just log it // for debugging. This should be impossible for most users to reach, // but it helps us load experimental mode when it's available. Messages.loge("ModeContribution.load() failed for " + searchName, err); } } return null; } /** * * @param base the base object that this will be tied to * @param folder location inside the sketchbook modes folder or contrib * @param className name of class and full package, or null to use default * @throws Exception */ public ModeContribution(Base base, File folder, String className) throws Exception { super(folder); className = initLoader(base, className); if (className != null) { Class<?> modeClass = loader.loadClass(className); Messages.log("Got mode class " + modeClass); Constructor con = modeClass.getConstructor(Base.class, File.class); mode = (Mode) con.newInstance(base, folder); mode.setClassLoader(loader); if (base != null) { mode.setupGUI(); } } } /** * Method to close the ClassLoader so that the archives are no longer "locked" * and a mode can be removed without restart. */ public void clearClassLoader(Base base) { List<ModeContribution> contribModes = base.getModeContribs(); int botherToRemove = contribModes.indexOf(this); // The poor thing isn't even loaded, and we're trying to remove it... if (botherToRemove != -1) { contribModes.remove(botherToRemove); try { // This cast should be safe, since the only case when loader is not a // URLClassLoader is when no archives were found in the first place. ((URLClassLoader) loader).close(); } catch (IOException e) { e.printStackTrace(); } } } public Mode getMode() { return mode; } public ContributionType getType() { return ContributionType.MODE; } public String initLoader(Base base, String className) throws Exception { File modeDirectory = new File(folder, getTypeName()); if (modeDirectory.exists()) { Messages.log("checking mode folder regarding class name " + className); // If no class name specified, search the main <modename>.jar for the // full name package and mode name. if (className == null) { String shortName = folder.getName(); File mainJar = new File(modeDirectory, shortName + ".jar"); if (mainJar.exists()) { className = findClassInZipFile(shortName, mainJar); } else { throw new IgnorableException(mainJar.getAbsolutePath() + " does not exist."); } if (className == null) { throw new IgnorableException("Could not find " + shortName + " class inside " + mainJar.getAbsolutePath()); } } ArrayList<URL> extraUrls = new ArrayList<>(); if (imports != null && imports.size() > 0) { // if the mode has any dependencies (defined as imports in // mode.properties), add the dependencies to the classloader HashMap<String, Mode> installedModes = new HashMap<>(); for(Mode m: base.getModeList()){ // Base.log("Mode contrib: " + m.getClass().getName() + " : "+ m.getFolder()); installedModes.put(m.getClass().getName(), m); } for (String modeImport: imports) { if (installedModes.containsKey(modeImport)) { Messages.log("Found mode dependency " + modeImport); File modeFolder = installedModes.get(modeImport).getFolder(); File[] archives = Util.listJarFiles(new File(modeFolder, "mode")); if (archives != null && archives.length > 0) { for (int i = 0; i < archives.length; i++) { // Base.log("Adding jar dependency: " + archives[i].getAbsolutePath()); extraUrls.add(archives[i].toURI().toURL()); } } } else { throw new IgnorableException("Can't load " + className + " because the import " + modeImport + " could not be found. "); } } } // Add .jar and .zip files from the "mode" folder into the classpath File[] archives = Util.listJarFiles(modeDirectory); if (archives != null && archives.length > 0) { int arrLen = archives.length + extraUrls.size(); URL[] urlList = new URL[arrLen]; int j = 0; for (; j < extraUrls.size(); j++) { //Base.log("Found archive " + archives[j] + " for " + getName()); urlList[j] = extraUrls.get(j); } for (int k = 0; k < archives.length; k++,j++) { Messages.log("Found archive " + archives[k] + " for " + getName()); urlList[j] = archives[k].toURI().toURL(); } loader = new URLClassLoader(urlList); Messages.log("loading above JARs with loader " + loader); // System.out.println("listing classes for loader " + loader); // listClasses(loader); } } // If no archives were found, just use the regular ClassLoader if (loader == null) { loader = Thread.currentThread().getContextClassLoader(); } return className; } }