+ */
+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);
+ }
+ }
+ }
+ }
+
+}