diff --git a/core/.classpath b/core/.classpath index 5a8600e02..c4cc74f89 100644 --- a/core/.classpath +++ b/core/.classpath @@ -25,7 +25,6 @@ - diff --git a/core/build.xml b/core/build.xml index 9b178defe..77435582e 100644 --- a/core/build.xml +++ b/core/build.xml @@ -24,6 +24,7 @@ + @@ -64,37 +65,40 @@ Compiling main classes - + - + - + - - + - - + - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -105,16 +109,6 @@ - - - - - - - - - - diff --git a/core/lib/jar-in-jar-loader.jar b/core/lib/jar-in-jar-loader.jar deleted file mode 100644 index ebb128790..000000000 Binary files a/core/lib/jar-in-jar-loader.jar and /dev/null differ diff --git a/core/lib/jspf.core-1.0.2.jar b/core/lib/jspf.core-1.0.2.jar deleted file mode 100644 index 02dec5bc7..000000000 Binary files a/core/lib/jspf.core-1.0.2.jar and /dev/null differ diff --git a/core/src/net/sf/openrocket/plugin/CopyOfExamplePluginImpl.java b/core/src/net/sf/openrocket/plugin/CopyOfExamplePluginImpl.java deleted file mode 100644 index 8abe0020d..000000000 --- a/core/src/net/sf/openrocket/plugin/CopyOfExamplePluginImpl.java +++ /dev/null @@ -1,10 +0,0 @@ -package net.sf.openrocket.plugin; - -public class CopyOfExamplePluginImpl implements ExamplePlugin { - - @Override - public void doit() { - System.out.println("CopyOfExamplePluginImpl.doit() called"); - } - -} diff --git a/core/src/net/sf/openrocket/plugin/JIJ.java b/core/src/net/sf/openrocket/plugin/JIJ.java new file mode 100644 index 000000000..34499f2ab --- /dev/null +++ b/core/src/net/sf/openrocket/plugin/JIJ.java @@ -0,0 +1,29 @@ +package net.sf.openrocket.plugin; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; + +public class JIJ { + + public static void main(String[] args) throws Exception { + String cp = System.getProperty("java.class.path"); + String[] cps = cp.split(File.pathSeparator); + + URL[] urls = new URL[cps.length + 1]; + for (int i = 0; i < cps.length; i++) { + urls[i] = new File(cps[i]).toURI().toURL(); + } + urls[cps.length] = new File("/home/sampo/Projects/OpenRocket/core/example.jar").toURL(); + + System.out.println("Classpath: " + Arrays.toString(urls)); + + URLClassLoader loader = new URLClassLoader(urls, null); + Class c = loader.loadClass("net.sf.openrocket.plugin.Test"); + Method m = c.getMethod("main", args.getClass()); + m.invoke(null, (Object) args); + } + +} diff --git a/core/src/net/sf/openrocket/plugin/PluginHelper.java b/core/src/net/sf/openrocket/plugin/PluginHelper.java new file mode 100644 index 000000000..ad7f4c605 --- /dev/null +++ b/core/src/net/sf/openrocket/plugin/PluginHelper.java @@ -0,0 +1,37 @@ +package net.sf.openrocket.plugin; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import net.sf.openrocket.arch.SystemInfo; + +public class PluginHelper { + + private static final String PLUGIN_DIRECTORY = "Plugins"; + private static final String PLUGIN_EXTENSION = ".jar"; + + public static List getPluginJars() { + File userDir = SystemInfo.getUserApplicationDirectory(); + File pluginDir = new File(userDir, PLUGIN_DIRECTORY); + if (!pluginDir.exists()) { + pluginDir.mkdirs(); + } + + File[] files = pluginDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.toLowerCase().endsWith(PLUGIN_EXTENSION); + } + }); + + if (files == null) { + return Collections.emptyList(); + } else { + return Arrays.asList(files); + } + } + +} diff --git a/core/src/net/sf/openrocket/plugin/PluginModule.java b/core/src/net/sf/openrocket/plugin/PluginModule.java index 88daae2e0..5aaa24404 100644 --- a/core/src/net/sf/openrocket/plugin/PluginModule.java +++ b/core/src/net/sf/openrocket/plugin/PluginModule.java @@ -21,7 +21,7 @@ import com.google.inject.multibindings.Multibinder; */ public class PluginModule extends AbstractModule { - private final File[] jars; + private final List jars; private final ClassLoader classLoader; private Map, Multibinder> binders = new HashMap, Multibinder>(); @@ -33,8 +33,8 @@ public class PluginModule extends AbstractModule { * @param jars the JAR files to search for plugins * @param classLoader the class loader used to load classes from the JAR files */ - public PluginModule(File[] jars, ClassLoader classLoader) { - this.jars = jars.clone(); + public PluginModule(List jars, ClassLoader classLoader) { + this.jars = jars; this.classLoader = classLoader; } diff --git a/core/src/net/sf/openrocket/plugin/Test.java b/core/src/net/sf/openrocket/plugin/Test.java index e49e189a7..863ac107b 100644 --- a/core/src/net/sf/openrocket/plugin/Test.java +++ b/core/src/net/sf/openrocket/plugin/Test.java @@ -4,6 +4,8 @@ import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.List; import java.util.Set; import com.google.inject.Guice; @@ -25,10 +27,20 @@ public class Test { public static void main(String[] args) throws MalformedURLException { + // Properties p = System.getProperties(); + // Enumeration e = p.keys(); + // while (e.hasMoreElements()) { + // Object key = e.nextElement(); + // Object value = p.get(key); + // System.out.println(key + " = " + value); + // } - File[] jars = { new File("/home/sampo/Projects/OpenRocket/core/example.jar") }; + List jars = Arrays.asList(new File("/home/sampo/Projects/OpenRocket/core/example.jar")); URL[] urls = { new File("/home/sampo/Projects/OpenRocket/core/example.jar").toURL() }; - URLClassLoader classLoader = new URLClassLoader(urls); + ClassLoader classLoader = new URLClassLoader(urls); + + classLoader = Test.class.getClassLoader(); + Injector injector = Guice.createInjector(new PluginModule(jars, classLoader)); injector.getInstance(Test.class).run(); } diff --git a/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileTranslator.java b/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileTranslator.java index d84fa1d8d..5e10c2af9 100644 --- a/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileTranslator.java +++ b/core/src/net/sf/openrocket/preset/loader/RocksimComponentFileTranslator.java @@ -5,7 +5,7 @@ import net.sf.openrocket.material.Material; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.preset.xml.OpenRocketComponentSaver; import net.sf.openrocket.startup.Application; -import net.sf.openrocket.startup.Startup; +import net.sf.openrocket.startup.GuiceStartup; import net.sf.openrocket.util.ArrayList; import java.io.File; @@ -37,7 +37,7 @@ public class RocksimComponentFileTranslator { LOGGER.println("Loading csv files from directory " + args[0]); - Startup.initializeLogging(); + GuiceStartup.initializeLogging(); Application.setPreferences(new SwingPreferences()); MaterialHolder materialMap = loadAll(allPresets, new File(args[0])); diff --git a/core/src/net/sf/openrocket/startup/ApplicationModule.java b/core/src/net/sf/openrocket/startup/ApplicationModule.java new file mode 100644 index 000000000..51b40f27b --- /dev/null +++ b/core/src/net/sf/openrocket/startup/ApplicationModule.java @@ -0,0 +1,15 @@ +package net.sf.openrocket.startup; + +import net.sf.openrocket.logging.LogHelper; + +import com.google.inject.AbstractModule; + +public class ApplicationModule extends AbstractModule { + + @Override + protected void configure() { + bind(LogHelper.class).toInstance(Application.getLogger()); + bind(Preferences.class).toInstance(Application.getPreferences()); + } + +} diff --git a/core/src/net/sf/openrocket/startup/Startup2.java b/core/src/net/sf/openrocket/startup/ApplicationStartup.java similarity index 81% rename from core/src/net/sf/openrocket/startup/Startup2.java rename to core/src/net/sf/openrocket/startup/ApplicationStartup.java index 82bc0d3c1..c897ec112 100644 --- a/core/src/net/sf/openrocket/startup/Startup2.java +++ b/core/src/net/sf/openrocket/startup/ApplicationStartup.java @@ -25,41 +25,45 @@ import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.util.BuildProperties; +import com.google.inject.Inject; + /** * The second class in the OpenRocket startup sequence. This class can assume the * Application class to be properly set up, and can use any classes safely. * * @author Sampo Niskanen */ -public class Startup2 { - private static final LogHelper log = Application.getLogger(); - - +public class ApplicationStartup { + + @Inject + private LogHelper log; + + private static final String THRUSTCURVE_DIRECTORY = "datafiles/thrustcurves/"; - + /** * Run when starting up OpenRocket after Application has been set up. * * @param args command line arguments */ - static void runMain(final String[] args) throws Exception { - + public void runMain(final String[] args) throws Exception { + log.info("Starting up OpenRocket version " + BuildProperties.getVersion()); - + // Check that we're not running headless log.info("Checking for graphics head"); checkHead(); - + // Check that we're running a good version of a JRE log.info("Checking JRE compatibility"); VersionHelper.checkVersion(); VersionHelper.checkOpenJDK(); - + // If running on a MAC set up OSX UI Elements. - if ( SystemInfo.getPlatform() == Platform.MAC_OS ){ + if (SystemInfo.getPlatform() == Platform.MAC_OS) { OSXStartup.setupOSX(); } - + // Run the actual startup method in the EDT since it can use progress dialogs etc. log.info("Moving startup to EDT"); SwingUtilities.invokeAndWait(new Runnable() { @@ -68,50 +72,50 @@ public class Startup2 { runInEDT(args); } }); - + log.info("Startup complete"); } - - + + /** * Run in the EDT when starting up OpenRocket. * * @param args command line arguments */ - private static void runInEDT(String[] args) { - + private void runInEDT(String[] args) { + // Initialize the splash screen with version info log.info("Initializing the splash screen"); Splash.init(); - + // Must be done after localization is initialized ComponentPresetDatabase componentPresetDao = new ComponentPresetDatabase(true) { - + @Override protected void load() { - ConcurrentComponentPresetDatabaseLoader presetLoader = new ConcurrentComponentPresetDatabaseLoader( this ); + ConcurrentComponentPresetDatabaseLoader presetLoader = new ConcurrentComponentPresetDatabaseLoader(this); presetLoader.load(); try { presetLoader.await(); - } catch ( InterruptedException iex) { - + } catch (InterruptedException iex) { + } } - + }; - Application.setComponentPresetDao( componentPresetDao ); - + Application.setComponentPresetDao(componentPresetDao); + componentPresetDao.startLoading(); - + // Setup the uncaught exception handler log.info("Registering exception handler"); SwingExceptionHandler exceptionHandler = new SwingExceptionHandler(); Application.setExceptionHandler(exceptionHandler); exceptionHandler.registerExceptionHandler(); - + // Start update info fetching final UpdateInfoRetriever updateInfo; - if ( Application.getPreferences().getCheckUpdates()) { + if (Application.getPreferences().getCheckUpdates()) { log.info("Starting update check"); updateInfo = new UpdateInfoRetriever(); updateInfo.start(); @@ -119,68 +123,68 @@ public class Startup2 { log.info("Update check disabled"); updateInfo = null; } - + // Set the best available look-and-feel log.info("Setting best LAF"); GUIUtil.setBestLAF(); - + // Set tooltip delay time. Tooltips are used in MotorChooserDialog extensively. ToolTipManager.sharedInstance().setDismissDelay(30000); - + // Load defaults ((SwingPreferences) Application.getPreferences()).loadDefaultUnits(); - + // Load motors etc. log.info("Loading databases"); - + loadMotor(); - + Databases.fakeMethod(); - - + + // Starting action (load files or open new document) log.info("Opening main application window"); if (!handleCommandLine(args)) { - if (!Application.getPreferences().isAutoOpenLastDesignOnStartupEnabled()) { - BasicFrame.newAction(); - } - else { - String lastFile = MRUDesignFile.getInstance().getLastEditedDesignFile(); - if (lastFile != null) { - if (!BasicFrame.open(new File(lastFile), null)) { - MRUDesignFile.getInstance().removeFile(lastFile); - BasicFrame.newAction(); - } - else { - MRUDesignFile.getInstance().addFile(lastFile); - } - } - else { - BasicFrame.newAction(); - } - } - } - + if (!Application.getPreferences().isAutoOpenLastDesignOnStartupEnabled()) { + BasicFrame.newAction(); + } + else { + String lastFile = MRUDesignFile.getInstance().getLastEditedDesignFile(); + if (lastFile != null) { + if (!BasicFrame.open(new File(lastFile), null)) { + MRUDesignFile.getInstance().removeFile(lastFile); + BasicFrame.newAction(); + } + else { + MRUDesignFile.getInstance().addFile(lastFile); + } + } + else { + BasicFrame.newAction(); + } + } + } + // Check whether update info has been fetched or whether it needs more time log.info("Checking update status"); checkUpdateStatus(updateInfo); - + } - + /** * this method is useful for the python bindings. */ - public static void loadMotor() { + public void loadMotor() { ConcurrentLoadingThrustCurveMotorSetDatabase motorLoader = new ConcurrentLoadingThrustCurveMotorSetDatabase(THRUSTCURVE_DIRECTORY); motorLoader.startLoading(); Application.setMotorSetDatabase(motorLoader); } - + /** * Check that the JRE is not running headless. */ - private static void checkHead() { - + private void checkHead() { + if (GraphicsEnvironment.isHeadless()) { log.error("Application is headless."); System.err.println(); @@ -189,36 +193,36 @@ public class Startup2 { System.err.println(); System.exit(1); } - + } - - - private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) { + + + private void checkUpdateStatus(final UpdateInfoRetriever updateInfo) { if (updateInfo == null) return; - + int delay = 1000; if (!updateInfo.isRunning()) delay = 100; - + final Timer timer = new Timer(delay, null); - + ActionListener listener = new ActionListener() { private int count = 5; - + @Override public void actionPerformed(ActionEvent e) { if (!updateInfo.isRunning()) { timer.stop(); - + String current = BuildProperties.getVersion(); String last = Application.getPreferences().getString(Preferences.LAST_UPDATE, ""); - + UpdateInfo info = updateInfo.getUpdateInfo(); if (info != null && info.getLatestVersion() != null && !current.equals(info.getLatestVersion()) && !last.equals(info.getLatestVersion())) { - + UpdateInfoDialog infoDialog = new UpdateInfoDialog(info); infoDialog.setVisible(true); if (infoDialog.isReminderSelected()) { @@ -236,7 +240,7 @@ public class Startup2 { timer.addActionListener(listener); timer.start(); } - + /** * Handles arguments passed from the command line. This may be used either * when starting the first instance of OpenRocket or later when OpenRocket is @@ -246,8 +250,8 @@ public class Startup2 { * @return whether a new frame was opened or similar user desired action was * performed as a result. */ - private static boolean handleCommandLine(String[] args) { - + private boolean handleCommandLine(String[] args) { + // Check command-line for files boolean opened = false; for (String file : args) { @@ -257,5 +261,5 @@ public class Startup2 { } return opened; } - + } diff --git a/core/src/net/sf/openrocket/startup/GuiceStartup.java b/core/src/net/sf/openrocket/startup/GuiceStartup.java new file mode 100644 index 000000000..51ba42f8d --- /dev/null +++ b/core/src/net/sf/openrocket/startup/GuiceStartup.java @@ -0,0 +1,219 @@ +package net.sf.openrocket.startup; + +import java.io.PrintStream; +import java.util.Locale; +import java.util.prefs.Preferences; + +import net.sf.openrocket.gui.util.SwingPreferences; +import net.sf.openrocket.l10n.DebugTranslator; +import net.sf.openrocket.l10n.L10N; +import net.sf.openrocket.l10n.ResourceBundleTranslator; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.logging.DelegatorLogger; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.logging.LogLevel; +import net.sf.openrocket.logging.LogLevelBufferLogger; +import net.sf.openrocket.logging.PrintStreamLogger; +import net.sf.openrocket.plugin.PluginHelper; +import net.sf.openrocket.plugin.PluginModule; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; + + +/** + * Second class in the OpenRocket startup sequence, responsible for + * IoC initialization. + * + * This class is responsible for initializing the Guice dependency injection + * mechanism, and the legacy Application class. + * + * This class must be very cautious about what classes it calls. This is because + * the loggers/translators for classes are initialized as static final members during + * class initialization. For example, this class MUST NOT use the Prefs class, because + * using it will cause LineStyle to be initialized, which then receives an invalid + * (not-yet-initialized) translator. + * + * @author Sampo Niskanen + */ +public class GuiceStartup { + + static LogHelper log; + + private static final String LOG_STDERR_PROPERTY = "openrocket.log.stderr"; + private static final String LOG_STDOUT_PROPERTY = "openrocket.log.stdout"; + + private static final int LOG_BUFFER_LENGTH = 50; + + /** + * OpenRocket startup main method. + */ + public static void main(final String[] args) throws Exception { + + // Check for "openrocket.debug" property before anything else + checkDebugStatus(); + + // Initialize logging first so we can use it + initializeLogging(); + + // Setup the translations + initializeL10n(); + + // Initialize preferences (*after* translator setup!) + Application.setPreferences(new SwingPreferences()); + + + Injector injector = initializeGuice(); + injector.getInstance(ApplicationStartup.class).runMain(args); + + } + + + /** + * Set proper system properties if openrocket.debug is defined. + */ + private static void checkDebugStatus() { + if (System.getProperty("openrocket.debug") != null) { + setPropertyIfNotSet("openrocket.log.stdout", "VBOSE"); + setPropertyIfNotSet("openrocket.log.tracelevel", "VBOSE"); + setPropertyIfNotSet("openrocket.debug.menu", "true"); + setPropertyIfNotSet("openrocket.debug.mutexlocation", "true"); + setPropertyIfNotSet("openrocket.debug.motordigest", "true"); + } + } + + private static void setPropertyIfNotSet(String key, String value) { + if (System.getProperty(key) == null) { + System.setProperty(key, value); + } + } + + + + /** + * Initializes the loggins system. + */ + public static void initializeLogging() { + DelegatorLogger delegator = new DelegatorLogger(); + + // Log buffer + LogLevelBufferLogger buffer = new LogLevelBufferLogger(LOG_BUFFER_LENGTH); + delegator.addLogger(buffer); + + // Check whether to log to stdout/stderr + PrintStreamLogger printer = new PrintStreamLogger(); + boolean logout = setLogOutput(printer, System.out, System.getProperty(LOG_STDOUT_PROPERTY), null); + boolean logerr = setLogOutput(printer, System.err, System.getProperty(LOG_STDERR_PROPERTY), LogLevel.ERROR); + if (logout || logerr) { + delegator.addLogger(printer); + } + + // Set the loggers + Application.setLogger(delegator); + Application.setLogBuffer(buffer); + + // Initialize the log for this class + log = Application.getLogger(); + log.info("Logging subsystem initialized"); + String str = "Console logging output:"; + for (LogLevel l : LogLevel.values()) { + PrintStream ps = printer.getOutput(l); + str += " " + l.name() + ":"; + if (ps == System.err) { + str += "stderr"; + } else if (ps == System.out) { + str += "stdout"; + } else { + str += "none"; + } + } + str += " (" + LOG_STDOUT_PROPERTY + "=" + System.getProperty(LOG_STDOUT_PROPERTY) + + " " + LOG_STDERR_PROPERTY + "=" + System.getProperty(LOG_STDERR_PROPERTY) + ")"; + log.info(str); + } + + private static boolean setLogOutput(PrintStreamLogger logger, PrintStream stream, String level, LogLevel defaultLevel) { + LogLevel minLevel = LogLevel.fromString(level, defaultLevel); + if (minLevel == null) { + return false; + } + + for (LogLevel l : LogLevel.values()) { + if (l.atLeast(minLevel)) { + logger.setOutput(l, stream); + } + } + return true; + } + + + + + /** + * Initializes the localization system. + */ + private static void initializeL10n() { + + // Check for locale propery + String langcode = System.getProperty("openrocket.locale"); + + if (langcode != null) { + + Locale l = L10N.toLocale(langcode); + log.info("Setting custom locale " + l); + Locale.setDefault(l); + + } else { + + // Check user-configured locale + Locale l = getUserLocale(); + if (l != null) { + log.info("Setting user-selected locale " + l); + Locale.setDefault(l); + } else { + log.info("Using default locale " + Locale.getDefault()); + } + + } + + // Setup the translator + Translator t; + t = new ResourceBundleTranslator("l10n.messages"); + if (Locale.getDefault().getLanguage().equals("xx")) { + t = new DebugTranslator(t); + } + + log.info("Set up translation for locale " + Locale.getDefault() + + ", debug.currentFile=" + t.get("debug.currentFile")); + + Application.setBaseTranslator(t); + } + + + + + private static Locale getUserLocale() { + /* + * This method MUST NOT use the Prefs class, since is causes a multitude + * of classes to be initialized. Therefore this duplicates the functionality + * of the Prefs class locally. + */ + + if (System.getProperty("openrocket.debug.prefs") != null) { + return null; + } + + return L10N.toLocale(Preferences.userRoot().node("OpenRocket").get("locale", null)); + } + + + + private static Injector initializeGuice() { + Module applicationModule = new ApplicationModule(); + Module pluginModule = new PluginModule(PluginHelper.getPluginJars(), GuiceStartup.class.getClassLoader()); + + return Guice.createInjector(applicationModule, pluginModule); + } + +} diff --git a/core/src/net/sf/openrocket/startup/OSXStartup.java b/core/src/net/sf/openrocket/startup/OSXStartup.java index ad14186b6..62715abfd 100644 --- a/core/src/net/sf/openrocket/startup/OSXStartup.java +++ b/core/src/net/sf/openrocket/startup/OSXStartup.java @@ -102,7 +102,7 @@ final class OSXStartup { // Set the dock icon to the largest icon final Image dockIcon = Toolkit.getDefaultToolkit().getImage( - Startup2.class.getResource(ICON_RSRC)); + ApplicationStartup.class.getResource(ICON_RSRC)); osxApp.setDockIconImage(dockIcon); } catch (final Throwable t) { diff --git a/core/src/net/sf/openrocket/startup/Startup.java b/core/src/net/sf/openrocket/startup/Startup.java index 31f83184a..56e91cf42 100644 --- a/core/src/net/sf/openrocket/startup/Startup.java +++ b/core/src/net/sf/openrocket/startup/Startup.java @@ -1,201 +1,43 @@ package net.sf.openrocket.startup; -import java.io.PrintStream; -import java.util.Locale; -import java.util.prefs.Preferences; - -import net.sf.openrocket.gui.util.SwingPreferences; -import net.sf.openrocket.l10n.DebugTranslator; -import net.sf.openrocket.l10n.L10N; -import net.sf.openrocket.l10n.ResourceBundleTranslator; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.logging.DelegatorLogger; -import net.sf.openrocket.logging.LogHelper; -import net.sf.openrocket.logging.LogLevel; -import net.sf.openrocket.logging.LogLevelBufferLogger; -import net.sf.openrocket.logging.PrintStreamLogger; +import java.net.URL; +import net.sf.openrocket.startup.jij.ClasspathUrlStreamHandler; +import net.sf.openrocket.startup.jij.ConfigurableStreamHandlerFactory; +import net.sf.openrocket.startup.jij.CurrentClasspathProvider; +import net.sf.openrocket.startup.jij.JarInJarStarter; +import net.sf.openrocket.startup.jij.ManifestClasspathProvider; +import net.sf.openrocket.startup.jij.PluginClasspathProvider; /** - * The first class in the OpenRocket startup sequence. This class is responsible - * for setting up the Application class with the statically used subsystems - * (logging and translation) and then delegating to Startup2 class. - *

- * This class must be very cautious about what classes it calls. This is because - * the loggers/translators for classes are initialized as static final members during - * class initialization. For example, this class MUST NOT use the Prefs class, because - * using it will cause LineStyle to be initialized, which then receives an invalid - * (not-yet-initialized) translator. + * First step in the OpenRocket startup sequence, responsible for + * classpath setup. + * + * The startup class sequence is the following: + * 1. Startup + * 2. GuiceStartup + * 3. ApplicationStartup + * + * This class changes the current classpath to contain the jar-in-jar + * library dependencies and plugins in the current classpath, and + * then launches the next step of the startup sequence. * * @author Sampo Niskanen */ public class Startup { - static LogHelper log; + private static final String STARTUP_CLASS = "net.sf.openrocket.startup.GuiceStartup"; - private static final String LOG_STDERR_PROPERTY = "openrocket.log.stderr"; - private static final String LOG_STDOUT_PROPERTY = "openrocket.log.stdout"; - - private static final int LOG_BUFFER_LENGTH = 50; - - /** - * OpenRocket startup main method. - */ - public static void main(final String[] args) throws Exception { - - // Check for "openrocket.debug" property before anything else - checkDebugStatus(); - - // Initialize logging first so we can use it - initializeLogging(); - - Application.setPreferences( new SwingPreferences() ); - - // Setup the translations - initializeL10n(); - - // Continue startup in Startup2 class (where Application is already set up) - Startup2.runMain(args); - + public static void main(String[] args) { + addClasspathUrlHandler(); + JarInJarStarter.runMain(STARTUP_CLASS, args, new CurrentClasspathProvider(), + new ManifestClasspathProvider(), new PluginClasspathProvider()); } - - /** - * Set proper system properties if openrocket.debug is defined. - */ - private static void checkDebugStatus() { - if (System.getProperty("openrocket.debug") != null) { - setPropertyIfNotSet("openrocket.log.stdout", "VBOSE"); - setPropertyIfNotSet("openrocket.log.tracelevel", "VBOSE"); - setPropertyIfNotSet("openrocket.debug.menu", "true"); - setPropertyIfNotSet("openrocket.debug.mutexlocation", "true"); - setPropertyIfNotSet("openrocket.debug.motordigest", "true"); - } + private static void addClasspathUrlHandler() { + ConfigurableStreamHandlerFactory factory = new ConfigurableStreamHandlerFactory(); + factory.addHandler("classpath", new ClasspathUrlStreamHandler()); + URL.setURLStreamHandlerFactory(factory); } - private static void setPropertyIfNotSet(String key, String value) { - if (System.getProperty(key) == null) { - System.setProperty(key, value); - } - } - - - - /** - * Initializes the loggins system. - */ - public static void initializeLogging() { - DelegatorLogger delegator = new DelegatorLogger(); - - // Log buffer - LogLevelBufferLogger buffer = new LogLevelBufferLogger(LOG_BUFFER_LENGTH); - delegator.addLogger(buffer); - - // Check whether to log to stdout/stderr - PrintStreamLogger printer = new PrintStreamLogger(); - boolean logout = setLogOutput(printer, System.out, System.getProperty(LOG_STDOUT_PROPERTY), null); - boolean logerr = setLogOutput(printer, System.err, System.getProperty(LOG_STDERR_PROPERTY), LogLevel.ERROR); - if (logout || logerr) { - delegator.addLogger(printer); - } - - // Set the loggers - Application.setLogger(delegator); - Application.setLogBuffer(buffer); - - // Initialize the log for this class - log = Application.getLogger(); - log.info("Logging subsystem initialized"); - String str = "Console logging output:"; - for (LogLevel l : LogLevel.values()) { - PrintStream ps = printer.getOutput(l); - str += " " + l.name() + ":"; - if (ps == System.err) { - str += "stderr"; - } else if (ps == System.out) { - str += "stdout"; - } else { - str += "none"; - } - } - str += " (" + LOG_STDOUT_PROPERTY + "=" + System.getProperty(LOG_STDOUT_PROPERTY) + - " " + LOG_STDERR_PROPERTY + "=" + System.getProperty(LOG_STDERR_PROPERTY) + ")"; - log.info(str); - } - - private static boolean setLogOutput(PrintStreamLogger logger, PrintStream stream, String level, LogLevel defaultLevel) { - LogLevel minLevel = LogLevel.fromString(level, defaultLevel); - if (minLevel == null) { - return false; - } - - for (LogLevel l : LogLevel.values()) { - if (l.atLeast(minLevel)) { - logger.setOutput(l, stream); - } - } - return true; - } - - - - - /** - * Initializes the localization system. - */ - private static void initializeL10n() { - - // Check for locale propery - String langcode = System.getProperty("openrocket.locale"); - - if (langcode != null) { - - Locale l = L10N.toLocale(langcode); - log.info("Setting custom locale " + l); - Locale.setDefault(l); - - } else { - - // Check user-configured locale - Locale l = getUserLocale(); - if (l != null) { - log.info("Setting user-selected locale " + l); - Locale.setDefault(l); - } else { - log.info("Using default locale " + Locale.getDefault()); - } - - } - - // Setup the translator - Translator t; - t = new ResourceBundleTranslator("l10n.messages"); - if (Locale.getDefault().getLanguage().equals("xx")) { - t = new DebugTranslator(t); - } - - log.info("Set up translation for locale " + Locale.getDefault() + - ", debug.currentFile=" + t.get("debug.currentFile")); - - Application.setBaseTranslator(t); - } - - - - - private static Locale getUserLocale() { - /* - * This method MUST NOT use the Prefs class, since is causes a multitude - * of classes to be initialized. Therefore this duplicates the functionality - * of the Prefs class locally. - */ - - if (System.getProperty("openrocket.debug.prefs") != null) { - return null; - } - - return L10N.toLocale(Preferences.userRoot().node("OpenRocket").get("locale", null)); - } - - } diff --git a/core/src/net/sf/openrocket/startup/jij/ClasspathProvider.java b/core/src/net/sf/openrocket/startup/jij/ClasspathProvider.java new file mode 100644 index 000000000..408af4a2e --- /dev/null +++ b/core/src/net/sf/openrocket/startup/jij/ClasspathProvider.java @@ -0,0 +1,10 @@ +package net.sf.openrocket.startup.jij; + +import java.net.URL; +import java.util.List; + +public interface ClasspathProvider { + + public List getUrls(); + +} diff --git a/core/src/net/sf/openrocket/startup/jij/ClasspathUrlStreamHandler.java b/core/src/net/sf/openrocket/startup/jij/ClasspathUrlStreamHandler.java new file mode 100644 index 000000000..0d0fdc413 --- /dev/null +++ b/core/src/net/sf/openrocket/startup/jij/ClasspathUrlStreamHandler.java @@ -0,0 +1,32 @@ +package net.sf.openrocket.startup.jij; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +/** + * A URL handler for classpath:// URLs. + * + * From http://stackoverflow.com/questions/861500/url-to-load-resources-from-the-classpath-in-java + */ +public class ClasspathUrlStreamHandler extends URLStreamHandler { + + /** The classloader to find resources from. */ + private final ClassLoader classLoader; + + public ClasspathUrlStreamHandler() { + this.classLoader = getClass().getClassLoader(); + } + + public ClasspathUrlStreamHandler(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + protected URLConnection openConnection(URL u) throws IOException { + final URL resourceUrl = classLoader.getResource(u.getPath()); + return resourceUrl.openConnection(); + } + +} diff --git a/core/src/net/sf/openrocket/startup/jij/ConfigurableStreamHandlerFactory.java b/core/src/net/sf/openrocket/startup/jij/ConfigurableStreamHandlerFactory.java new file mode 100644 index 000000000..7367bd024 --- /dev/null +++ b/core/src/net/sf/openrocket/startup/jij/ConfigurableStreamHandlerFactory.java @@ -0,0 +1,29 @@ +package net.sf.openrocket.startup.jij; + +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import java.util.HashMap; +import java.util.Map; + +/** + * A URLStreamHandlerFactory that can be configured with various + * handlers. + * + * From http://stackoverflow.com/questions/861500/url-to-load-resources-from-the-classpath-in-java + */ +public class ConfigurableStreamHandlerFactory implements URLStreamHandlerFactory { + private final Map protocolHandlers; + + public ConfigurableStreamHandlerFactory() { + protocolHandlers = new HashMap(); + } + + public void addHandler(String protocol, URLStreamHandler urlHandler) { + protocolHandlers.put(protocol, urlHandler); + } + + @Override + public URLStreamHandler createURLStreamHandler(String protocol) { + return protocolHandlers.get(protocol); + } +} diff --git a/core/src/net/sf/openrocket/startup/jij/CurrentClasspathProvider.java b/core/src/net/sf/openrocket/startup/jij/CurrentClasspathProvider.java new file mode 100644 index 000000000..83472d9aa --- /dev/null +++ b/core/src/net/sf/openrocket/startup/jij/CurrentClasspathProvider.java @@ -0,0 +1,29 @@ +package net.sf.openrocket.startup.jij; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +public class CurrentClasspathProvider implements ClasspathProvider { + + @Override + public List getUrls() { + List urls = new ArrayList(); + + String classpath = System.getProperty("java.class.path"); + String[] cps = classpath.split(File.pathSeparator); + + for (String cp : cps) { + try { + urls.add(new File(cp).toURI().toURL()); + } catch (MalformedURLException e) { + System.err.println("Error initializing classpath " + e); + } + } + + return urls; + } + +} diff --git a/core/src/net/sf/openrocket/startup/jij/JarInJarStarter.java b/core/src/net/sf/openrocket/startup/jij/JarInJarStarter.java new file mode 100644 index 000000000..8865cd19f --- /dev/null +++ b/core/src/net/sf/openrocket/startup/jij/JarInJarStarter.java @@ -0,0 +1,42 @@ +package net.sf.openrocket.startup.jij; + +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; + +public class JarInJarStarter { + + /** + * Runs a main class with an alternative classpath. + * + * @param mainClass the class containing the main method to call. + * @param args the arguments to the main method. + * @param providers the classpath sources. + */ + public static void runMain(String mainClass, String[] args, ClasspathProvider... providers) { + List urls = new ArrayList(); + + for (ClasspathProvider p : providers) { + urls.addAll(p.getUrls()); + } + + System.out.println("New classpath:"); + for (URL u : urls) { + System.out.println(" " + u); + } + + URL[] urlArray = urls.toArray(new URL[0]); + ClassLoader loader = new URLClassLoader(urlArray, null); + try { + Class c = loader.loadClass(mainClass); + Method m = c.getMethod("main", args.getClass()); + m.invoke(null, (Object) args); + } catch (Exception e) { + throw new RuntimeException("Error starting OpenRocket", e); + } + + } + +} diff --git a/core/src/net/sf/openrocket/startup/jij/ManifestClasspathProvider.java b/core/src/net/sf/openrocket/startup/jij/ManifestClasspathProvider.java new file mode 100644 index 000000000..2f8dc3146 --- /dev/null +++ b/core/src/net/sf/openrocket/startup/jij/ManifestClasspathProvider.java @@ -0,0 +1,77 @@ +package net.sf.openrocket.startup.jij; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import net.sf.openrocket.util.BugException; + +public class ManifestClasspathProvider implements ClasspathProvider { + + private static final String MANIFEST_ATTRIBUTE = "Classpath-Jars"; + + @Override + public List getUrls() { + try { + List manifest = readManifestLine(MANIFEST_ATTRIBUTE); + + List urls = new ArrayList(); + for (String s : manifest) { + parseManifestLine(urls, s); + } + + return urls; + } catch (IOException e) { + throw new BugException(e); + } + } + + + private List readManifestLine(String name) throws IOException { + List lines = new ArrayList(); + + Enumeration resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF"); + + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + InputStream stream = url.openStream(); + Manifest manifest = new Manifest(stream); + stream.close(); + + Attributes attr = manifest.getMainAttributes(); + if (attr == null) { + continue; + } + + String value = attr.getValue(name); + if (value == null) { + continue; + } + + lines.add(value); + } + return lines; + } + + + + private void parseManifestLine(List urls, String manifest) throws MalformedURLException { + String[] array = manifest.split("\\s"); + for (String s : array) { + if (s.length() > 0) { + if (getClass().getClassLoader().getResource(s) != null) { + urls.add(new URL("classpath:" + s)); + } else { + System.err.println("Library " + s + " not found on classpath, ignoring."); + } + } + } + } + +} diff --git a/core/src/net/sf/openrocket/startup/jij/PluginClasspathProvider.java b/core/src/net/sf/openrocket/startup/jij/PluginClasspathProvider.java new file mode 100644 index 000000000..19d8f9f36 --- /dev/null +++ b/core/src/net/sf/openrocket/startup/jij/PluginClasspathProvider.java @@ -0,0 +1,56 @@ +package net.sf.openrocket.startup.jij; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import net.sf.openrocket.plugin.PluginHelper; +import net.sf.openrocket.util.BugException; + +public class PluginClasspathProvider implements ClasspathProvider { + + private static final String CUSTOM_PLUGIN_PROPERTY = "openrocket.plugins"; + + @Override + public List getUrls() { + List urls = new ArrayList(); + + findPluginDirectoryUrls(urls); + findCustomPlugins(urls); + + return urls; + } + + private void findPluginDirectoryUrls(List urls) { + List files = PluginHelper.getPluginJars(); + for (File f : files) { + try { + urls.add(f.toURI().toURL()); + } catch (MalformedURLException e) { + throw new BugException(e); + } + } + } + + private void findCustomPlugins(List urls) { + String prop = System.getProperty(CUSTOM_PLUGIN_PROPERTY); + if (prop == null) { + return; + } + + String[] array = prop.split(File.pathSeparator); + for (String s : array) { + s = s.trim(); + if (s.length() > 0) { + try { + urls.add(new File(s).toURI().toURL()); + } catch (MalformedURLException e) { + throw new BugException(e); + } + } + } + } + +}