Startup system using Guice and plugin loading

This commit is contained in:
Sampo Niskanen 2012-10-14 17:51:10 +03:00
parent 5622b4a2b5
commit 4670a010be
22 changed files with 725 additions and 309 deletions

View File

@ -25,7 +25,6 @@
<classpathentry kind="lib" path="lib-test/junit-dep-4.8.2.jar"/>
<classpathentry kind="lib" path="lib-test/uispec4j-2.3-jdk16.jar"/>
<classpathentry kind="lib" path="resources"/>
<classpathentry kind="lib" path="lib/jspf.core-1.0.2.jar" sourcepath="/home/sampo/Projects/lib/jspf/documentation/api"/>
<classpathentry kind="lib" path="lib/opencsv-2.3.jar"/>
<classpathentry kind="lib" path="lib/jogl/gluegen-rt.jar"/>
<classpathentry kind="lib" path="lib/jogl/jogl.all.jar"/>

View File

@ -24,6 +24,7 @@
<!-- The main class of the application -->
<property name="main-class" value="net.sf.openrocket.startup.Startup"/>
<property name="expanded-libs" value="lib/miglayout15-swing.jar"/>
<!-- Classpath definitions -->
<path id="classpath">
@ -64,37 +65,40 @@
<target name="build">
<mkdir dir="${classes.dir}"/>
<echo level="info">Compiling main classes</echo>
<javac debug="true" srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/>
<javac debug="true" srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath" includeantruntime="false"/>
</target>
<!-- Executible Eclipse-Jar-In-Jar style JAR -->
<target name="jar" depends="core-jar" description="Create the OpenRocket jar-in-jar Executable">
<target name="jar" depends="build" description="Create the OpenRocket executable JAR">
<mkdir dir="${jar.dir}" />
<jar destfile="${jar.file}">
<jar destfile="${jar.file}" basedir="${dist.dir}">
<manifest>
<attribute name="Main-Class" value="org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader" />
<attribute name="Rsrc-Main-Class" value="${main-class}" />
<attribute name="Main-Class" value="${main-class}" />
<attribute name="SplashScreen-Image" value="pix/splashscreen.png" />
<attribute name="Class-Path" value="." />
<attribute name="Rsrc-Class-Path" value="./ main/${ant.project.name}-Core.jar lib/jfreechart-1.0.13.jar lib/jcommon-1.0.16.jar lib/gluegen-rt.jar lib/miglayout15-swing.jar lib/iText-5.0.2.jar lib/jogl.all.jar lib/opencsv-2.3.jar lib/OrangeExtensions-1.2.jar" />
<attribute name="Classpath-Jars" value="lib/gluegen-rt.jar lib/jogl.all.jar" />
</manifest>
<!-- Unzip the Eclipse JIJ Loader -->
<zipfileset src="lib/jar-in-jar-loader.jar" />
<!-- Include, in the root of the JAR, the resources needed by OR -->
<fileset dir="src/" includes="META-INF/" />
<fileset dir="resources/" />
<!-- Include the core OpenRocket JAR -->
<zipfileset dir="${build.dir}/" prefix="main">
<include name="${ant.project.name}-Core.jar"/>
</zipfileset>
<!-- Include libraries needed by OR -->
<zipfileset dir="${lib.dir}" prefix="lib">
<include name="*.jar"/>
</zipfileset>
<!-- Libraries to extract into base JAR -->
<zipfileset src="lib/miglayout15-swing.jar" />
<zipfileset src="lib/guice-3.0.jar" />
<zipfileset src="lib/aopalliance.jar"/>
<zipfileset src="lib/guice-3.0.jar"/>
<zipfileset src="lib/guice-multibindings-3.0.jar"/>
<zipfileset src="lib/iText-5.0.2.jar"/>
<zipfileset src="lib/javax.inject.jar"/>
<zipfileset src="lib/jcommon-1.0.16.jar"/>
<zipfileset src="lib/jfreechart-1.0.13.jar"/>
<zipfileset src="lib/miglayout15-swing.jar"/>
<zipfileset src="lib/opencsv-2.3.jar"/>
<zipfileset src="lib/OrangeExtensions-1.2.jar"/>
<!-- JOGL libraries need to be jar-in-jar -->
<zipfileset dir="${lib.dir}/jogl" prefix="lib">
<include name="*.jar"/>
</zipfileset>
@ -105,16 +109,6 @@
</target>
<!-- Core OpenRocket JAR -->
<target name="core-jar" depends="build" description="Create the OpenRocket code-only jar file">
<jar destfile="${build.dir}/${ant.project.name}-Core.jar" basedir="${dist.dir}">
<manifest>
<attribute name="Main-Class" value="${main-class}"/>
<attribute name="SplashScreen-Image" value="pix/splashscreen.png"/>
</manifest>
</jar>
</target>
<!-- CONVERT vendor csv to ORC files -->
<macrodef name="build-orc-file">
<attribute name="dir"/>

Binary file not shown.

Binary file not shown.

View File

@ -1,10 +0,0 @@
package net.sf.openrocket.plugin;
public class CopyOfExamplePluginImpl implements ExamplePlugin {
@Override
public void doit() {
System.out.println("CopyOfExamplePluginImpl.doit() called");
}
}

View File

@ -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);
}
}

View File

@ -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<File> 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);
}
}
}

View File

@ -21,7 +21,7 @@ import com.google.inject.multibindings.Multibinder;
*/
public class PluginModule extends AbstractModule {
private final File[] jars;
private final List<File> jars;
private final ClassLoader classLoader;
private Map<Class<?>, Multibinder<?>> binders = new HashMap<Class<?>, 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<File> jars, ClassLoader classLoader) {
this.jars = jars;
this.classLoader = classLoader;
}

View File

@ -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<Object> 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<File> 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();
}

View File

@ -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]));

View File

@ -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());
}
}

View File

@ -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 <sampo.niskanen@iki.fi>
*/
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;
}
}

View File

@ -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 <sampo.niskanen@iki.fi>
*/
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);
}
}

View File

@ -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) {

View File

@ -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.
* <p>
* 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 <sampo.niskanen@iki.fi>
*/
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));
}
}

View File

@ -0,0 +1,10 @@
package net.sf.openrocket.startup.jij;
import java.net.URL;
import java.util.List;
public interface ClasspathProvider {
public List<URL> getUrls();
}

View File

@ -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();
}
}

View File

@ -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<String, URLStreamHandler> protocolHandlers;
public ConfigurableStreamHandlerFactory() {
protocolHandlers = new HashMap<String, URLStreamHandler>();
}
public void addHandler(String protocol, URLStreamHandler urlHandler) {
protocolHandlers.put(protocol, urlHandler);
}
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
return protocolHandlers.get(protocol);
}
}

View File

@ -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<URL> getUrls() {
List<URL> urls = new ArrayList<URL>();
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;
}
}

View File

@ -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<URL> urls = new ArrayList<URL>();
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);
}
}
}

View File

@ -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<URL> getUrls() {
try {
List<String> manifest = readManifestLine(MANIFEST_ATTRIBUTE);
List<URL> urls = new ArrayList<URL>();
for (String s : manifest) {
parseManifestLine(urls, s);
}
return urls;
} catch (IOException e) {
throw new BugException(e);
}
}
private List<String> readManifestLine(String name) throws IOException {
List<String> lines = new ArrayList<String>();
Enumeration<URL> 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<URL> 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.");
}
}
}
}
}

View File

@ -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<URL> getUrls() {
List<URL> urls = new ArrayList<URL>();
findPluginDirectoryUrls(urls);
findCustomPlugins(urls);
return urls;
}
private void findPluginDirectoryUrls(List<URL> urls) {
List<File> files = PluginHelper.getPluginJars();
for (File f : files) {
try {
urls.add(f.toURI().toURL());
} catch (MalformedURLException e) {
throw new BugException(e);
}
}
}
private void findCustomPlugins(List<URL> 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);
}
}
}
}
}