SafetyMutex and rocket optimization updates
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Mon, 25 Oct 2010 19:02:31 +0000 (19:02 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Mon, 25 Oct 2010 19:02:31 +0000 (19:02 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@93 180e2498-e6e9-4542-8430-84ac67f01cd8

53 files changed:
.classpath
ChangeLog
build.xml
doc/properties.txt
lib-test/hamcrest-core-1.3.0RC1.jar [new file with mode: 0644]
lib-test/hamcrest-library-1.3.0RC1.jar [new file with mode: 0644]
lib-test/jmock-2.6.0-RC2.jar [new file with mode: 0644]
lib-test/jmock-junit4-2.6.0-RC2.jar [new file with mode: 0644]
lib-test/junit-4.7.jar [deleted file]
lib-test/junit-dep-4.8.2.jar [new file with mode: 0644]
src/net/sf/openrocket/document/Simulation.java
src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java
src/net/sf/openrocket/gui/dialogs/DetailDialog.java
src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java
src/net/sf/openrocket/gui/main/BasicFrame.java
src/net/sf/openrocket/gui/main/ExceptionHandler.java
src/net/sf/openrocket/gui/main/SimulationPanel.java
src/net/sf/openrocket/gui/main/SimulationRunDialog.java
src/net/sf/openrocket/gui/main/SimulationWorker.java
src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
src/net/sf/openrocket/logging/LogHelper.java
src/net/sf/openrocket/logging/LogLevel.java
src/net/sf/openrocket/logging/LogLevelBufferLogger.java
src/net/sf/openrocket/logging/LogLine.java
src/net/sf/openrocket/logging/TraceException.java
src/net/sf/openrocket/optimization/general/Function.java
src/net/sf/openrocket/optimization/general/FunctionCache.java
src/net/sf/openrocket/optimization/general/FunctionOptimizer.java
src/net/sf/openrocket/optimization/general/OptimizationException.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/general/ParallelExecutorCache.java
src/net/sf/openrocket/optimization/general/ParallelFunctionCache.java
src/net/sf/openrocket/optimization/general/multidim/MultidirectionalSearchOptimizer.java
src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameter.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameterService.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationFunction.java
src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationParameter.java [deleted file]
src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationParameterService.java [deleted file]
src/net/sf/openrocket/optimization/rocketoptimization/SimulationDomain.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/SimulationModifier.java
src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericModifier.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAltitudeParameter.java [new file with mode: 0644]
src/net/sf/openrocket/rocketcomponent/Rocket.java
src/net/sf/openrocket/rocketcomponent/RocketComponent.java
src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
src/net/sf/openrocket/startup/Startup.java
src/net/sf/openrocket/util/ConcurrencyException.java [new file with mode: 0644]
src/net/sf/openrocket/util/GUIUtil.java
src/net/sf/openrocket/util/MemoryManagement.java [new file with mode: 0644]
src/net/sf/openrocket/util/SafetyMutex.java [new file with mode: 0644]
src/net/sf/openrocket/utils/TestFunctionOptimizer.java
src/net/sf/openrocket/utils/TestFunctionOptimizerLoop.java
test/net/sf/openrocket/optimization/rocketoptimization/TestRocketOptimizationFunction.java [new file with mode: 0644]
test/net/sf/openrocket/util/TestMutex.java [new file with mode: 0644]

index c71fbb9c8c6e4b56d985972e2a1b8e4240cb0df6..31166a946917c3fcbb56fcd379775e7f5fb734a3 100644 (file)
@@ -10,7 +10,6 @@
                </accessrules>
        </classpathentry>
        <classpathentry kind="lib" path="lib-extra/RXTXcomm.jar"/>
-       <classpathentry kind="lib" path="lib-test/junit-4.7.jar"/>
        <classpathentry kind="lib" path="lib/jfreechart-1.0.13.jar" sourcepath="/home/sampo/Projects/lib/jfreechart-1.0.13/source"/>
        <classpathentry kind="lib" path="lib/jcommon-1.0.16.jar">
                <accessrules>
                </accessrules>
        </classpathentry>
        <classpathentry kind="lib" path="lib/miglayout15-swing.jar"/>
+       <classpathentry kind="lib" path="lib-test/hamcrest-core-1.3.0RC1.jar"/>
+       <classpathentry kind="lib" path="lib-test/hamcrest-library-1.3.0RC1.jar"/>
+       <classpathentry kind="lib" path="lib-test/jmock-2.6.0-RC2.jar"/>
+       <classpathentry kind="lib" path="lib-test/jmock-junit4-2.6.0-RC2.jar"/>
+       <classpathentry kind="lib" path="lib-test/junit-dep-4.8.2.jar"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>
index 0f4e934f3b1e493e7228da50bd2251a235c26200..efbb97935316e44bb18b597160f7b61bc078867b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2010-10-25  Doug Pedrick
+
+       * [BUG] Take launch lug radial angle into account when loading rkt file
+
+2010-10-24  Sampo Niskane
+
+       * Added SafetyMutex and took into use in Simulation
+
+2010-10-18  Sampo Niskanen
+
+       * Ignore Sun JRE bug in D3D
+
 2010-10-09  Sampo Niskanen
 
        * [BUG] Fixed conversion to freeform fin set
index 6262bc0da7d1423f4f43e4e4c2924807febb814f..0540b3dc611aa451d74ee2562ad63465ee2834a2 100644 (file)
--- a/build.xml
+++ b/build.xml
                <pathelement location="${build-test.dir}"/>
                <pathelement location="${classes.dir}"/>
                <pathelement location="${src-test.dir}"/>
-<!--           <pathelement location="${ant.library.dir}/junit4.jar"/> -->
-               <pathelement location="lib-test/junit-4.7.jar"/>
+               <pathelement location="lib-test/junit-dep-4.8.2.jar"/>
+               <pathelement location="lib-test/hamcrest-core-1.3.0RC1.jar"/>
+               <pathelement location="lib-test/hamcrest-library-1.3.0RC1.jar"/>
+               <pathelement location="lib-test/jmock-2.6.0-RC2.jar"/>
+               <pathelement location="lib-test/jmock-junit4-2.6.0-RC2.jar"/>
        </path>
        
 
index ed0690416a760e28b6be8575e62a9a5d7ae05d09..cbddc55fbf6a8ab0ab5fe6a8b33cd72cd5389208 100644 (file)
@@ -25,6 +25,14 @@ openrocket.log.tracelevel
 Debugging options
 -----------------
 
+openrocket.debug
+       Turns on various options useful for debugging purposes.  The parameters defined are:
+               openrocket.log.stdout=VBOSE
+               openrocket.log.tracelevel=VBOSE
+               openrocket.debug.menu=true
+               openrocket.debug.motordigest=true
+
+
 openrocket.debug.menu
        If defined the "Debug" menu will be displayed in the main application window.
 
diff --git a/lib-test/hamcrest-core-1.3.0RC1.jar b/lib-test/hamcrest-core-1.3.0RC1.jar
new file mode 100644 (file)
index 0000000..1195cb7
Binary files /dev/null and b/lib-test/hamcrest-core-1.3.0RC1.jar differ
diff --git a/lib-test/hamcrest-library-1.3.0RC1.jar b/lib-test/hamcrest-library-1.3.0RC1.jar
new file mode 100644 (file)
index 0000000..8e6568b
Binary files /dev/null and b/lib-test/hamcrest-library-1.3.0RC1.jar differ
diff --git a/lib-test/jmock-2.6.0-RC2.jar b/lib-test/jmock-2.6.0-RC2.jar
new file mode 100644 (file)
index 0000000..a846450
Binary files /dev/null and b/lib-test/jmock-2.6.0-RC2.jar differ
diff --git a/lib-test/jmock-junit4-2.6.0-RC2.jar b/lib-test/jmock-junit4-2.6.0-RC2.jar
new file mode 100644 (file)
index 0000000..129e561
Binary files /dev/null and b/lib-test/jmock-junit4-2.6.0-RC2.jar differ
diff --git a/lib-test/junit-4.7.jar b/lib-test/junit-4.7.jar
deleted file mode 100644 (file)
index 700ad69..0000000
Binary files a/lib-test/junit-4.7.jar and /dev/null differ
diff --git a/lib-test/junit-dep-4.8.2.jar b/lib-test/junit-dep-4.8.2.jar
new file mode 100644 (file)
index 0000000..f28b4ef
Binary files /dev/null and b/lib-test/junit-dep-4.8.2.jar differ
index db462aad8f158b182aacc57b2bf9c70b4ff96cfb..9d5d0cd35169c62b6ce1fcbeab492e0987943ead 100644 (file)
@@ -27,8 +27,16 @@ import net.sf.openrocket.simulation.listeners.SimulationListener;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.ChangeSource;
+import net.sf.openrocket.util.SafetyMutex;
 
-
+/**
+ * A class defining a simulation, its conditions and simulated data.
+ * <p>
+ * This class is not thread-safe and enforces single-threaded access with a
+ * SafetyMutex.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
 public class Simulation implements ChangeSource, Cloneable {
        private static final LogHelper log = Application.getLogger();
        
@@ -49,6 +57,7 @@ public class Simulation implements ChangeSource, Cloneable {
                NOT_SIMULATED
        }
        
+       private final SafetyMutex mutex = new SafetyMutex();
        
        private final Rocket rocket;
        
@@ -146,6 +155,7 @@ public class Simulation implements ChangeSource, Cloneable {
         * @return      the rocket.
         */
        public Rocket getRocket() {
+               mutex.verify();
                return rocket;
        }
        
@@ -157,6 +167,7 @@ public class Simulation implements ChangeSource, Cloneable {
         * @return      a newly created Configuration of the launch conditions.
         */
        public Configuration getConfiguration() {
+               mutex.verify();
                Configuration c = new Configuration(rocket);
                c.setMotorConfigurationID(conditions.getMotorConfigurationID());
                c.setAllStages();
@@ -171,6 +182,7 @@ public class Simulation implements ChangeSource, Cloneable {
         * @return the simulation conditions.
         */
        public GUISimulationConditions getConditions() {
+               mutex.verify();
                return conditions;
        }
        
@@ -182,6 +194,7 @@ public class Simulation implements ChangeSource, Cloneable {
         * @return      the actual list of simulation listeners.
         */
        public List<String> getSimulationListeners() {
+               mutex.verify();
                return simulationListeners;
        }
        
@@ -192,6 +205,7 @@ public class Simulation implements ChangeSource, Cloneable {
         * @return      the name for the simulation.
         */
        public String getName() {
+               mutex.verify();
                return name;
        }
        
@@ -202,15 +216,20 @@ public class Simulation implements ChangeSource, Cloneable {
         * @param name  the name of the simulation.
         */
        public void setName(String name) {
-               if (this.name.equals(name))
-                       return;
-               
-               if (name == null)
-                       this.name = "";
-               else
-                       this.name = name;
-               
-               fireChangeEvent();
+               mutex.lock("setName");
+               try {
+                       if (this.name.equals(name))
+                               return;
+                       
+                       if (name == null)
+                               this.name = "";
+                       else
+                               this.name = name;
+                       
+                       fireChangeEvent();
+               } finally {
+                       mutex.unlock("setName");
+               }
        }
        
        
@@ -222,6 +241,8 @@ public class Simulation implements ChangeSource, Cloneable {
         * @see Status
         */
        public Status getStatus() {
+               mutex.verify();
+               
                if (status == Status.UPTODATE || status == Status.LOADED) {
                        if (rocket.getFunctionalModID() != simulatedRocketID ||
                                        !conditions.equals(simulatedConditions))
@@ -236,54 +257,59 @@ public class Simulation implements ChangeSource, Cloneable {
 
        public void simulate(SimulationListener... additionalListeners)
                                                throws SimulationException {
-               
-               if (this.status == Status.EXTERNAL) {
-                       throw new SimulationException("Cannot simulate imported simulation.");
-               }
-               
-               SimulationEngine simulator;
-               
+               mutex.lock("simulate");
                try {
-                       simulator = simulationEngineClass.newInstance();
-               } catch (InstantiationException e) {
-                       throw new IllegalStateException("Cannot instantiate simulator.", e);
-               } catch (IllegalAccessException e) {
-                       throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
-               } catch (NullPointerException e) {
-                       throw new IllegalStateException("Simulator null", e);
-               }
-               
-               SimulationConditions simulationConditions = conditions.toSimulationConditions();
-               for (SimulationListener l : additionalListeners) {
-                       simulationConditions.getSimulationListenerList().add(l);
-               }
-               
-               for (String className : simulationListeners) {
-                       SimulationListener l = null;
+                       
+                       if (this.status == Status.EXTERNAL) {
+                               throw new SimulationException("Cannot simulate imported simulation.");
+                       }
+                       
+                       SimulationEngine simulator;
+                       
                        try {
-                               Class<?> c = Class.forName(className);
-                               l = (SimulationListener) c.newInstance();
-                       } catch (Exception e) {
-                               throw new SimulationListenerException("Could not instantiate listener of " +
-                                               "class: " + className, e);
+                               simulator = simulationEngineClass.newInstance();
+                       } catch (InstantiationException e) {
+                               throw new IllegalStateException("Cannot instantiate simulator.", e);
+                       } catch (IllegalAccessException e) {
+                               throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
+                       } catch (NullPointerException e) {
+                               throw new IllegalStateException("Simulator null", e);
+                       }
+                       
+                       SimulationConditions simulationConditions = conditions.toSimulationConditions();
+                       for (SimulationListener l : additionalListeners) {
+                               simulationConditions.getSimulationListenerList().add(l);
+                       }
+                       
+                       for (String className : simulationListeners) {
+                               SimulationListener l = null;
+                               try {
+                                       Class<?> c = Class.forName(className);
+                                       l = (SimulationListener) c.newInstance();
+                               } catch (Exception e) {
+                                       throw new SimulationListenerException("Could not instantiate listener of " +
+                                                       "class: " + className, e);
+                               }
+                               simulationConditions.getSimulationListenerList().add(l);
                        }
-                       simulationConditions.getSimulationListenerList().add(l);
+                       
+                       long t1, t2;
+                       log.debug("Simulation: calling simulator");
+                       t1 = System.currentTimeMillis();
+                       simulatedData = simulator.simulate(simulationConditions);
+                       t2 = System.currentTimeMillis();
+                       log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
+                       
+                       // Set simulated info after simulation, will not be set in case of exception
+                       simulatedConditions = conditions.clone();
+                       simulatedMotors = getConfiguration().getMotorConfigurationDescription();
+                       simulatedRocketID = rocket.getFunctionalModID();
+                       
+                       status = Status.UPTODATE;
+                       fireChangeEvent();
+               } finally {
+                       mutex.unlock("simulate");
                }
-               
-               long t1, t2;
-               log.debug("Simulation: calling simulator");
-               t1 = System.currentTimeMillis();
-               simulatedData = simulator.simulate(simulationConditions);
-               t2 = System.currentTimeMillis();
-               log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
-               
-               // Set simulated info after simulation, will not be set in case of exception
-               simulatedConditions = conditions.clone();
-               simulatedMotors = getConfiguration().getMotorConfigurationDescription();
-               simulatedRocketID = rocket.getFunctionalModID();
-               
-               status = Status.UPTODATE;
-               fireChangeEvent();
        }
        
        
@@ -294,6 +320,7 @@ public class Simulation implements ChangeSource, Cloneable {
         * @return      the conditions used in the previous simulation, or <code>null</code>.
         */
        public GUISimulationConditions getSimulatedConditions() {
+               mutex.verify();
                return simulatedConditions;
        }
        
@@ -306,6 +333,7 @@ public class Simulation implements ChangeSource, Cloneable {
         * @see         FlightData#getWarningSet()
         */
        public WarningSet getSimulatedWarnings() {
+               mutex.verify();
                if (simulatedData == null)
                        return null;
                return simulatedData.getWarningSet();
@@ -321,6 +349,7 @@ public class Simulation implements ChangeSource, Cloneable {
         * @see         Rocket#getMotorConfigurationNameOrDescription(String)
         */
        public String getSimulatedMotorDescription() {
+               mutex.verify();
                return simulatedMotors;
        }
        
@@ -331,6 +360,7 @@ public class Simulation implements ChangeSource, Cloneable {
         * @return      the flight data of the previous simulation, or <code>null</code>.
         */
        public FlightData getSimulatedData() {
+               mutex.verify();
                return simulatedData;
        }
        
@@ -344,6 +374,7 @@ public class Simulation implements ChangeSource, Cloneable {
         */
        @SuppressWarnings("unchecked")
        public Simulation copy() {
+               mutex.lock("copy");
                try {
                        
                        Simulation copy = (Simulation) super.clone();
@@ -359,9 +390,10 @@ public class Simulation implements ChangeSource, Cloneable {
                        
                        return copy;
                        
-
                } catch (CloneNotSupportedException e) {
                        throw new BugException("Clone not supported, BUG", e);
+               } finally {
+                       mutex.unlock("copy");
                }
        }
        
@@ -375,26 +407,33 @@ public class Simulation implements ChangeSource, Cloneable {
         */
        @SuppressWarnings("unchecked")
        public Simulation duplicateSimulation(Rocket newRocket) {
-               Simulation copy = new Simulation(newRocket);
-               
-               copy.name = this.name;
-               copy.conditions.copyFrom(this.conditions);
-               copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
-               copy.simulationStepperClass = this.simulationStepperClass;
-               copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
-               
-               return copy;
+               mutex.lock("duplicateSimulation");
+               try {
+                       Simulation copy = new Simulation(newRocket);
+                       
+                       copy.name = this.name;
+                       copy.conditions.copyFrom(this.conditions);
+                       copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
+                       copy.simulationStepperClass = this.simulationStepperClass;
+                       copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
+                       
+                       return copy;
+               } finally {
+                       mutex.unlock("duplicateSimulation");
+               }
        }
        
        
 
        @Override
        public void addChangeListener(ChangeListener listener) {
+               mutex.verify();
                listeners.add(listener);
        }
        
        @Override
        public void removeChangeListener(ChangeListener listener) {
+               mutex.verify();
                listeners.remove(listener);
        }
        
index c67568fc78f1e7ed17fc23c5822a46561627ef3a..927cc54838f867b9235cb40514881ec8da8333eb 100644 (file)
@@ -141,7 +141,8 @@ public class DebugLogDialog extends JDialog {
                panel.add(new JLabel("Display log lines:"), "gapright para, split");
                for (LogLevel l : LogLevel.values()) {
                        JCheckBox box = new JCheckBox(l.toString());
-                       box.setSelected(true);
+                       // By default display DEBUG and above
+                       box.setSelected(l.atLeast(LogLevel.DEBUG));
                        box.addActionListener(new ActionListener() {
                                @Override
                                public void actionPerformed(ActionEvent e) {
@@ -279,6 +280,7 @@ public class DebugLogDialog extends JDialog {
                sorter.setComparator(1, NumericComparator.INSTANCE);
                sorter.setComparator(4, new LocationComparator());
                table.setRowSorter(sorter);
+               sorter.setRowFilter(new LogFilter());
                
 
                panel.add(new JScrollPane(table), "span, grow, width " +
index 05a19950dd56b4765579096dd479d99130f39c65..87e36e67852418682510f1367e992e63994f9881 100644 (file)
@@ -3,16 +3,31 @@ package net.sf.openrocket.gui.dialogs;
 import java.awt.Component;
 
 import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
 
-public class DetailDialog {
+import net.sf.openrocket.util.GUIUtil;
 
-       public static void showDetailedMessageDialog(Component parentComponent, Object message, 
-                       String details, String title, int messageType)  {
+public class DetailDialog {
+       
+       public static void showDetailedMessageDialog(Component parentComponent, Object message,
+                       String details, String title, int messageType) {
                
-               // TODO: HIGH: Detailed dialog
-               JOptionPane.showMessageDialog(parentComponent, message, title, messageType, null);
+               if (details != null) {
+                       JTextArea textArea = null;
+                       textArea = new JTextArea(5, 40);
+                       textArea.setText(details);
+                       textArea.setCaretPosition(0);
+                       textArea.setEditable(false);
+                       GUIUtil.changeFontSize(textArea, -2);
+                       JOptionPane.showMessageDialog(parentComponent,
+                                       new Object[] { message, new JScrollPane(textArea) },
+                                       title, messageType, null);
+               } else {
+                       JOptionPane.showMessageDialog(parentComponent, message, title, messageType, null);
+               }
                
        }
        
-       
+
 }
index 39f9d2b2f47919c4b5903c418a4f457a8b3e4216..ed55079f0659a6628c50e2865ea5bfe82206648a 100644 (file)
@@ -12,15 +12,15 @@ import java.util.ServiceLoader;
 import javax.swing.JDialog;
 
 import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationParameter;
-import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationParameterService;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameterService;
 import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
 import net.sf.openrocket.optimization.rocketoptimization.SimulationModifierService;
 import net.sf.openrocket.util.BugException;
 
 public class GeneralOptimizationDialog extends JDialog {
        
-       private final List<RocketOptimizationParameter> optimizationParameters = new ArrayList<RocketOptimizationParameter>();
+       private final List<OptimizableParameter> optimizationParameters = new ArrayList<OptimizableParameter>();
        private final Map<Object, List<SimulationModifier>> simulationModifiers =
                        new HashMap<Object, List<SimulationModifier>>();
        
@@ -36,10 +36,10 @@ public class GeneralOptimizationDialog extends JDialog {
        
        
        private void loadOptimizationParameters() {
-               ServiceLoader<RocketOptimizationParameterService> loader =
-                               ServiceLoader.load(RocketOptimizationParameterService.class);
+               ServiceLoader<OptimizableParameterService> loader =
+                               ServiceLoader.load(OptimizableParameterService.class);
                
-               for (RocketOptimizationParameterService g : loader) {
+               for (OptimizableParameterService g : loader) {
                        optimizationParameters.addAll(g.getParameters(document));
                }
                
@@ -47,9 +47,9 @@ public class GeneralOptimizationDialog extends JDialog {
                        throw new BugException("No rocket optimization parameters found, distribution built wrong.");
                }
                
-               Collections.sort(optimizationParameters, new Comparator<RocketOptimizationParameter>() {
+               Collections.sort(optimizationParameters, new Comparator<OptimizableParameter>() {
                        @Override
-                       public int compare(RocketOptimizationParameter o1, RocketOptimizationParameter o2) {
+                       public int compare(OptimizableParameter o1, OptimizableParameter o2) {
                                return o1.getName().compareTo(o2.getName());
                        }
                });
index 4031be8aabc259ec9a1d69ea1931d0f376b242fb..4f1561cef5cb624e60d4c1dc567b9694a7133faf 100644 (file)
@@ -25,6 +25,8 @@ import java.net.URL;
 import java.net.URLDecoder;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 
 import javax.swing.Action;
@@ -69,6 +71,7 @@ import net.sf.openrocket.gui.dialogs.AboutDialog;
 import net.sf.openrocket.gui.dialogs.BugReportDialog;
 import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog;
 import net.sf.openrocket.gui.dialogs.DebugLogDialog;
+import net.sf.openrocket.gui.dialogs.DetailDialog;
 import net.sf.openrocket.gui.dialogs.ExampleDesignDialog;
 import net.sf.openrocket.gui.dialogs.LicenseDialog;
 import net.sf.openrocket.gui.dialogs.MotorDatabaseLoadingDialog;
@@ -87,6 +90,8 @@ import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.GUIUtil;
 import net.sf.openrocket.util.Icons;
+import net.sf.openrocket.util.MemoryManagement;
+import net.sf.openrocket.util.MemoryManagement.MemoryData;
 import net.sf.openrocket.util.OpenFileWorker;
 import net.sf.openrocket.util.Prefs;
 import net.sf.openrocket.util.Reflection;
@@ -728,10 +733,86 @@ public class BasicFrame extends JFrame {
                });
                menu.add(item);
                
+               menu.addSeparator();
+               
+
+               item = new JMenuItem("Memory statistics");
+               item.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.user("Memory statistics selected");
+                               
+                               // Get discarded but remaining objects (this also runs System.gc multiple times)
+                               List<MemoryData> objects = MemoryManagement.getRemainingObjects();
+                               StringBuilder sb = new StringBuilder();
+                               sb.append("Objects that should have been garbage-collected but have not been:\n");
+                               int count = 0;
+                               for (MemoryData data : objects) {
+                                       Object o = data.getReference().get();
+                                       if (o == null)
+                                               continue;
+                                       sb.append("Age ").append(System.currentTimeMillis() - data.getRegistrationTime())
+                                                       .append(" ms:  ").append(o).append('\n');
+                                       count++;
+                                       // Explicitly null the strong reference to avoid possibility of invisible references
+                                       o = null;
+                               }
+                               sb.append("Total: " + count);
+                               
+                               // Get basic memory stats
+                               System.gc();
+                               long max = Runtime.getRuntime().maxMemory();
+                               long free = Runtime.getRuntime().freeMemory();
+                               long used = max - free;
+                               String[] stats = new String[4];
+                               stats[0] = "Memory usage:";
+                               stats[1] = String.format("   Max memory:  %.1f MB", max / 1024.0 / 1024.0);
+                               stats[2] = String.format("   Used memory: %.1f MB (%.0f%%)", used / 1024.0 / 1024.0, 100.0 * used / max);
+                               stats[3] = String.format("   Free memory: %.1f MB (%.0f%%)", free / 1024.0 / 1024.0, 100.0 * free / max);
+                               
+
+                               DetailDialog.showDetailedMessageDialog(BasicFrame.this, stats, sb.toString(),
+                                               "Memory statistics", JOptionPane.INFORMATION_MESSAGE);
+                       }
+               });
+               menu.add(item);
+               
+
+               item = new JMenuItem("Exhaust memory");
+               item.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.user("Exhaust memory selected");
+                               LinkedList<byte[]> data = new LinkedList<byte[]>();
+                               int count = 0;
+                               final int bytesPerArray = 10240;
+                               try {
+                                       while (true) {
+                                               byte[] array = new byte[bytesPerArray];
+                                               for (int i = 0; i < bytesPerArray; i++) {
+                                                       array[i] = (byte) i;
+                                               }
+                                               data.add(array);
+                                               count++;
+                                       }
+                               } catch (OutOfMemoryError error) {
+                                       data = null;
+                                       long size = bytesPerArray * (long) count;
+                                       String s = String.format("OutOfMemory occurred after %d iterations (approx. %.1f MB consumed)",
+                                                       count, size / 1024.0 / 1024.0);
+                                       log.debug(s, error);
+                                       JOptionPane.showMessageDialog(BasicFrame.this, s);
+                               }
+                       }
+               });
+               menu.add(item);
+               
+
                menu.addSeparator();
                
                item = new JMenuItem("Exception here");
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Exception here selected");
                                throw new RuntimeException("Testing exception from menu action listener");
@@ -769,13 +850,22 @@ public class BasicFrame extends JFrame {
                });
                menu.add(item);
                
+               item = new JMenuItem("OutOfMemoryError here");
+               item.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.user("OutOfMemoryError here selected");
+                               throw new OutOfMemoryError("Testing OutOfMemoryError from menu action listener");
+                       }
+               });
+               menu.add(item);
+               
 
 
                return menu;
        }
        
        
-
        /**
         * Select the tab on the main pane.
         * 
index 384aee185471c7969735eb2a8ff5c8e38fac8362..9fbc9a848f6b184ed797a252d0f8c2401c67c222 100644 (file)
@@ -5,8 +5,8 @@ import javax.swing.SwingUtilities;
 
 import net.sf.openrocket.gui.dialogs.BugReportDialog;
 import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.logging.TraceException;
 import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.util.BugException;
 
 
 public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
@@ -63,6 +63,7 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
                        } else {
                                log.info("Exception handler not on EDT, invoking dialog on EDT");
                                SwingUtilities.invokeAndWait(new Runnable() {
+                                       @Override
                                        public void run() {
                                                showDialog(thread, throwable);
                                        }
@@ -76,7 +77,7 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
                                log.error("Caught exception while handling exception", ex);
                                System.err.println("Exception in exception handler, dumping exception:");
                                ex.printStackTrace();
-                       } catch (Throwable ignore) {
+                       } catch (Exception ignore) {
                        }
                        
                } finally {
@@ -90,11 +91,14 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
        /**
         * Handle an error condition programmatically without throwing an exception.
         * This can be used in cases where recovery of the error is desirable.
+        * <p>
+        * This method is guaranteed never to throw an exception, and can thus be safely
+        * used in finally blocks.
         * 
         * @param message       the error message.
         */
        public static void handleErrorCondition(String message) {
-               log.error(1, message);
+               log.error(1, message, new TraceException());
                handleErrorCondition(new InternalException(message));
        }
        
@@ -102,6 +106,9 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
        /**
         * Handle an error condition programmatically without throwing an exception.
         * This can be used in cases where recovery of the error is desirable.
+        * <p>
+        * This method is guaranteed never to throw an exception, and can thus be safely
+        * used in finally blocks.
         * 
         * @param message       the error message.
         * @param exception     the exception that occurred.
@@ -115,36 +122,39 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
        /**
         * Handle an error condition programmatically without throwing an exception.
         * This can be used in cases where recovery of the error is desirable.
+        * <p>
+        * This method is guaranteed never to throw an exception, and can thus be safely
+        * used in finally blocks.
         * 
         * @param exception             the exception that occurred.
         */
        public static void handleErrorCondition(final Exception exception) {
-               if (!(exception instanceof InternalException)) {
-                       log.error(1, "Error occurred", exception);
-               }
-               final Thread thread = Thread.currentThread();
-               final ExceptionHandler handler = instance;
-               
-               if (handler == null) {
-                       // Not initialized, throw the exception
-                       throw new BugException("Error condition before exception handling has been initialized", exception);
-               }
-               
                try {
+                       if (!(exception instanceof InternalException)) {
+                               log.error(1, "Error occurred", exception);
+                       }
+                       final Thread thread = Thread.currentThread();
+                       final ExceptionHandler handler = instance;
+                       
+                       if (handler == null) {
+                               log.error("Error condition occurred before exception handling has been initialized", exception);
+                               return;
+                       }
+                       
                        if (SwingUtilities.isEventDispatchThread()) {
                                log.info("Running in EDT, showing dialog");
                                handler.showDialog(thread, exception);
                        } else {
                                log.info("Not in EDT, invoking and waiting for dialog");
                                SwingUtilities.invokeAndWait(new Runnable() {
+                                       @Override
                                        public void run() {
                                                handler.showDialog(thread, exception);
                                        }
                                });
                        }
                } catch (Exception e) {
-                       log.error("Exception occurred while showing error dialog", e);
-                       e.printStackTrace();
+                       log.error("Exception occurred in error handler", e);
                }
        }
        
@@ -289,6 +299,8 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
         */
        private static boolean isNonFatalJREBug(Throwable t) {
                
+               // NOTE:  Calling method logs the entire throwable, so log only message here
+               
                /*
                 * Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
                 */
@@ -346,6 +358,18 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
                                return true;
                        }
                }
+               
+               /*
+                * Detect Sun JRE bug in D3D
+                */
+               if (t instanceof ClassCastException) {
+                       if (t.getMessage().equals("sun.awt.Win32GraphicsConfig cannot be cast to sun.java2d.d3d.D3DGraphicsConfig")) {
+                               log.warn("Ignoring Sun JRE bug " +
+                                               "(see http://forums.sun.com/thread.jspa?threadID=5440525): " + t);
+                               return true;
+                       }
+               }
+               
                return false;
        }
        
index 5fd4e1a92732af313cb05ee990030aa29f56abe3..644605db8fd1529b1a599c5e3510322dbb8fd7a0 100644 (file)
@@ -226,7 +226,6 @@ public class SimulationPanel extends JPanel {
                                                
                                                // Set simulation status icon
                                                Simulation.Status status = document.getSimulation(row).getStatus();
-                                               System.out.println("status=" + status);
                                                label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
                                                
 
index c37ceef22d6b8ba9c05182a1e35b5a858ad515bf..9b94ec615c58ae282b792121109b816e3a0cb651 100644 (file)
@@ -69,7 +69,13 @@ public class SimulationRunDialog extends JDialog {
        private final JProgressBar progressBar;
        
 
+       /*
+        * NOTE:  Care must be used when accessing the simulation parameters, since they
+        * are being run in another thread.  Mutexes are used to avoid concurrent usage, which
+        * will result in an exception being thrown!
+        */
        private final Simulation[] simulations;
+       private final String[] simulationNames;
        private final SimulationWorker[] simulationWorkers;
        private final SimulationStatus[] simulationStatuses;
        private final double[] simulationMaxAltitude;
@@ -87,6 +93,7 @@ public class SimulationRunDialog extends JDialog {
                
                // Initialize the simulations
                int n = simulations.length;
+               simulationNames = new String[n];
                simulationWorkers = new SimulationWorker[n];
                simulationStatuses = new SimulationStatus[n];
                simulationMaxAltitude = new double[n];
@@ -94,6 +101,7 @@ public class SimulationRunDialog extends JDialog {
                simulationDone = new boolean[n];
                
                for (int i = 0; i < n; i++) {
+                       simulationNames[i] = simulations[i].getName();
                        simulationWorkers[i] = new InteractiveSimulationWorker(simulations[i], i);
                        executor.execute(simulationWorkers[i]);
                }
@@ -201,7 +209,7 @@ public class SimulationRunDialog extends JDialog {
                log.debug("Progressbar value " + progress);
                
                // Update the simulation fields
-               simLabel.setText("Running " + simulations[index].getName());
+               simLabel.setText("Running " + simulationNames[index]);
                if (simulationStatuses[index] == null) {
                        log.debug("No simulation status data available, setting empty labels");
                        timeLabel.setText("");
index 9e6f62bc1ca5b05765a9e1e78219211759593a43..f348fbcdbe0d22306aaac190416a878abe458b80 100644 (file)
@@ -54,8 +54,6 @@ public abstract class SimulationWorker extends SwingWorker<FlightData, Simulatio
                try {
                        simulation.simulate(listeners);
                } catch (Throwable e) {
-                       //                      System.out.println("Simulation interrupted:");
-                       //                      e.printStackTrace();
                        throwable = e;
                        return null;
                }
@@ -77,8 +75,6 @@ public abstract class SimulationWorker extends SwingWorker<FlightData, Simulatio
        /**
         * Called after a simulation is successfully simulated.  This method is not
         * called if the simulation ends in an exception.
-        * 
-        * @param sim   the simulation including the flight data
         */
        protected abstract void simulationDone();
        
index 71ac8a5921b99a0cd2aa47c6ccfd59e2d692ae33..04d658cecffe86cd9d3ef11795fb9f4b940cf22d 100644 (file)
@@ -608,7 +608,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                @Override
                protected void simulationDone() {
                        // Do nothing if cancelled
-                       if (isCancelled() || backgroundSimulationWorker != this) // Double-check
+                       if (isCancelled() || backgroundSimulationWorker != this)
                                return;
                        
                        backgroundSimulationWorker = null;
@@ -658,6 +658,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
         * Updates the selection in the FigureParameters and repaints the figure.  
         * Ignores the event itself.
         */
+       @Override
        public void valueChanged(TreeSelectionEvent e) {
                TreePath[] paths = selectionModel.getSelectionPaths();
                if (paths == null) {
@@ -688,6 +689,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        figure.addChangeListener(this);
                }
                
+               @Override
                public void actionPerformed(ActionEvent e) {
                        boolean state = (Boolean) getValue(Action.SELECTED_KEY);
                        if (state == true) {
@@ -698,6 +700,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        stateChanged(null);
                }
                
+               @Override
                public void stateChanged(ChangeEvent e) {
                        putValue(Action.SELECTED_KEY, figure.getType() == type);
                }
index 77809e07de59334594ce8c502aaf923ecb9eebfe..d6b52104ba8307966504c489f886556d008c73b3 100644 (file)
@@ -16,6 +16,9 @@ import net.sf.openrocket.util.BugException;
  *  <li><code>message</code>   the String message (may be null).
  *  <li><code>cause</code>             the exception that caused this log (may be null).
  * </ul>
+ * <p>
+ * The logging methods are guaranteed never to throw an exception, and can thus be safely
+ * used in finally blocks.
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
@@ -56,7 +59,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void verbose(String message) {
-               log(createLogLine(0, LogLevel.VBOSE, message, null));
+               try {
+                       log(createLogLine(0, LogLevel.VBOSE, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -66,7 +73,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void verbose(String message, Throwable cause) {
-               log(createLogLine(0, LogLevel.VBOSE, message, cause));
+               try {
+                       log(createLogLine(0, LogLevel.VBOSE, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -76,7 +87,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void verbose(int levels, String message) {
-               log(createLogLine(levels, LogLevel.VBOSE, message, null));
+               try {
+                       log(createLogLine(levels, LogLevel.VBOSE, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -87,7 +102,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void verbose(int levels, String message, Throwable cause) {
-               log(createLogLine(levels, LogLevel.VBOSE, message, cause));
+               try {
+                       log(createLogLine(levels, LogLevel.VBOSE, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        
@@ -97,7 +116,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void debug(String message) {
-               log(createLogLine(0, LogLevel.DEBUG, message, null));
+               try {
+                       log(createLogLine(0, LogLevel.DEBUG, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -107,7 +130,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void debug(String message, Throwable cause) {
-               log(createLogLine(0, LogLevel.DEBUG, message, cause));
+               try {
+                       log(createLogLine(0, LogLevel.DEBUG, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -117,7 +144,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void debug(int levels, String message) {
-               log(createLogLine(levels, LogLevel.DEBUG, message, null));
+               try {
+                       log(createLogLine(levels, LogLevel.DEBUG, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -128,7 +159,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void debug(int levels, String message, Throwable cause) {
-               log(createLogLine(levels, LogLevel.DEBUG, message, cause));
+               try {
+                       log(createLogLine(levels, LogLevel.DEBUG, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        
@@ -138,7 +173,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void info(String message) {
-               log(createLogLine(0, LogLevel.INFO, message, null));
+               try {
+                       log(createLogLine(0, LogLevel.INFO, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -148,7 +187,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void info(String message, Throwable cause) {
-               log(createLogLine(0, LogLevel.INFO, message, cause));
+               try {
+                       log(createLogLine(0, LogLevel.INFO, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -158,7 +201,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void info(int levels, String message) {
-               log(createLogLine(levels, LogLevel.INFO, message, null));
+               try {
+                       log(createLogLine(levels, LogLevel.INFO, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -169,7 +216,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void info(int levels, String message, Throwable cause) {
-               log(createLogLine(levels, LogLevel.INFO, message, cause));
+               try {
+                       log(createLogLine(levels, LogLevel.INFO, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        
@@ -179,7 +230,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void user(String message) {
-               log(createLogLine(0, LogLevel.USER, message, null));
+               try {
+                       log(createLogLine(0, LogLevel.USER, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -189,7 +244,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void user(String message, Throwable cause) {
-               log(createLogLine(0, LogLevel.USER, message, cause));
+               try {
+                       log(createLogLine(0, LogLevel.USER, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -199,7 +258,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void user(int levels, String message) {
-               log(createLogLine(levels, LogLevel.USER, message, null));
+               try {
+                       log(createLogLine(levels, LogLevel.USER, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -210,7 +273,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void user(int levels, String message, Throwable cause) {
-               log(createLogLine(levels, LogLevel.USER, message, cause));
+               try {
+                       log(createLogLine(levels, LogLevel.USER, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        
@@ -220,7 +287,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void warn(String message) {
-               log(createLogLine(0, LogLevel.WARN, message, null));
+               try {
+                       log(createLogLine(0, LogLevel.WARN, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -230,7 +301,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void warn(String message, Throwable cause) {
-               log(createLogLine(0, LogLevel.WARN, message, cause));
+               try {
+                       log(createLogLine(0, LogLevel.WARN, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -240,7 +315,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void warn(int levels, String message) {
-               log(createLogLine(levels, LogLevel.WARN, message, null));
+               try {
+                       log(createLogLine(levels, LogLevel.WARN, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -251,7 +330,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void warn(int levels, String message, Throwable cause) {
-               log(createLogLine(levels, LogLevel.WARN, message, cause));
+               try {
+                       log(createLogLine(levels, LogLevel.WARN, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        
@@ -261,7 +344,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void error(String message) {
-               log(createLogLine(0, LogLevel.ERROR, message, null));
+               try {
+                       log(createLogLine(0, LogLevel.ERROR, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -271,7 +358,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void error(String message, Throwable cause) {
-               log(createLogLine(0, LogLevel.ERROR, message, cause));
+               try {
+                       log(createLogLine(0, LogLevel.ERROR, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -281,7 +372,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void error(int levels, String message) {
-               log(createLogLine(levels, LogLevel.ERROR, message, null));
+               try {
+                       log(createLogLine(levels, LogLevel.ERROR, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -292,7 +387,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void error(int levels, String message, Throwable cause) {
-               log(createLogLine(levels, LogLevel.ERROR, message, cause));
+               try {
+                       log(createLogLine(levels, LogLevel.ERROR, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        
@@ -304,7 +403,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void log(LogLevel level, String message) {
-               log(createLogLine(0, level, message, null));
+               try {
+                       log(createLogLine(0, level, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -315,7 +418,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void log(LogLevel level, String message, Throwable cause) {
-               log(createLogLine(0, level, message, cause));
+               try {
+                       log(createLogLine(0, level, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -326,7 +433,11 @@ public abstract class LogHelper {
         * @param message       the logged message (may be null).
         */
        public void log(int levels, LogLevel level, String message) {
-               log(createLogLine(levels, level, message, null));
+               try {
+                       log(createLogLine(levels, level, message, null));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        /**
@@ -338,7 +449,11 @@ public abstract class LogHelper {
         * @param cause         the causing exception (may be null).
         */
        public void log(int levels, LogLevel level, String message, Throwable cause) {
-               log(createLogLine(levels, level, message, cause));
+               try {
+                       log(createLogLine(levels, level, message, cause));
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
        }
        
        
index 74235b360f7d5027db9f3d82732b4494ab9a7d4f..b52d14536a383c6bf1aa18d1092b8517298d127a 100644 (file)
@@ -53,15 +53,23 @@ public enum LogLevel {
         */
        VBOSE;
        
-
+       /** The log level with highest priority */
+       public static final LogLevel HIGHEST;
+       /** The log level with lowest priority */
+       public static final LogLevel LOWEST;
        /** The maximum length of a level textual description */
        public static final int LENGTH;
+       
        static {
                int length = 0;
                for (LogLevel l : LogLevel.values()) {
                        length = Math.max(length, l.toString().length());
                }
                LENGTH = length;
+               
+               LogLevel[] values = LogLevel.values();
+               HIGHEST = values[0];
+               LOWEST = values[values.length - 1];
        }
        
        /**
index 3b2a2c4341abc67b9a53a913a6520ce06116c0b9..d902106563fa5f7e23b3b44d91fabf4bdd05d526 100644 (file)
@@ -54,12 +54,12 @@ public class LogLevelBufferLogger extends LogHelper {
                        
                        if (misses > 0) {
                                if (logs.isEmpty()) {
-                                       result.add(new LogLine(level, 0, 0, new TraceException(),
-                                                       "--- " + misses + " " + level + " lines removed but log is empty! ---",
+                                       result.add(new LogLine(level, 0, 0, null,
+                                                       "===== " + misses + " " + level + " lines removed but log is empty! =====",
                                                        null));
                                } else {
-                                       result.add(new LogLine(level, logs.get(0).getLogCount(), 0, new TraceException(),
-                                                       "--- " + misses + " " + level + " lines removed ---", null));
+                                       result.add(new LogLine(level, logs.get(0).getLogCount(), 0, null,
+                                                       "===== " + misses + " " + level + " lines removed =====", null));
                                }
                        }
                        result.addAll(logs);
index 71b92d48f8452db2e24f477ae5c104c6b397e630..c2dc4fa6c6323e779d9da001b3021203c280d95c 100644 (file)
@@ -31,13 +31,32 @@ public class LogLine implements Comparable<LogLine> {
        
        private volatile String formattedMessage = null;
        
-
+       
+       /**
+        * Construct a LogLine at the current moment.  The next log line count number is selected
+        * and the current run time set to the timestamp.
+        * 
+        * @param level         the logging level
+        * @param trace         the trace exception for the log line, <code>null</code> permitted
+        * @param message       the log message
+        * @param cause         the causing throwable, <code>null</code> permitted
+        */
        public LogLine(LogLevel level, TraceException trace, String message, Throwable cause) {
                this(level, logCount.getAndIncrement(), System.currentTimeMillis() - startTime, trace, message, cause);
        }
        
-       
-       public LogLine(LogLevel level, int count, long timestamp, 
+       /**
+        * Construct a LogLine with all parameters.  This should only be used in special conditions,
+        * for example to insert a log line at a specific point within normal logs.
+        * 
+        * @param level         the logging level
+        * @param count         the log line count number
+        * @param timestamp     the log line timestamp
+        * @param trace         the trace exception for the log line, <code>null</code> permitted
+        * @param message       the log message
+        * @param cause         the causing throwable, <code>null</code> permitted
+        */
+       public LogLine(LogLevel level, int count, long timestamp,
                        TraceException trace, String message, Throwable cause) {
                this.level = level;
                this.count = count;
@@ -46,57 +65,57 @@ public class LogLine implements Comparable<LogLine> {
                this.message = message;
                this.cause = cause;
        }
-
        
        
+
        /**
         * @return the level
         */
        public LogLevel getLevel() {
                return level;
        }
-
-
+       
+       
        /**
         * @return the count
         */
        public int getLogCount() {
                return count;
        }
-
-
+       
+       
        /**
         * @return the timestamp
         */
        public long getTimestamp() {
                return timestamp;
        }
-
-
+       
+       
        /**
         * @return the trace
         */
        public TraceException getTrace() {
                return trace;
        }
-
-
+       
+       
        /**
         * @return the message
         */
        public String getMessage() {
                return message;
        }
-
-
+       
+       
        /**
         * @return the error
         */
        public Throwable getCause() {
                return cause;
        }
-
-
+       
+       
 
 
        /**
@@ -109,7 +128,7 @@ public class LogLine implements Comparable<LogLine> {
                if (formattedMessage == null) {
                        String str;
                        str = String.format("%4d %10.3f %-" + LogLevel.LENGTH + "s %s %s",
-                                       count, timestamp/1000.0, (level != null) ? level.toString() : "NULL",
+                                       count, timestamp / 1000.0, (level != null) ? level.toString() : "NULL",
                                        (trace != null) ? trace.getMessage() : "(-)",
                                        message);
                        if (cause != null) {
@@ -123,8 +142,8 @@ public class LogLine implements Comparable<LogLine> {
                }
                return formattedMessage;
        }
-
-
+       
+       
        /**
         * Compare against another log line based on the log line count number.
         */
index 8468a1ed4fc8a9bb20230d54815293a92c1edd01..b08109a35f1645ce187fe20bba2393c1d13cb55f 100644 (file)
@@ -62,6 +62,17 @@ public class TraceException extends Exception {
        }
        
        
+       /**
+        * Construct an exception with the specified message.
+        * 
+        * @param message       the message for the exception.
+        */
+       public TraceException(String message) {
+               this(0, 0);
+               this.message = message;
+       }
+       
+       
        /**
         * Get the description of the code position as provided in the constructor.
         */
index 97404e2a78635361ab396bce73c71d49713256b0..a7be5668dcc615cd682db4ac3a3fc8eb37e38515 100644 (file)
@@ -18,23 +18,8 @@ public interface Function {
         * @param point         the point at which to evaluate the function.
         * @return                      the function value.
         * @throws InterruptedException         if the thread was interrupted before function evaluation was completed.
+        * @throws OptimizationException        if an error occurs that prevents the optimization
         */
-       public double evaluate(Point point) throws InterruptedException;
-       
-       
-       /**
-        * Return a cached value of the function at the specified point.  This allows efficient
-        * caching of old values even between calls to optimization methods.  This method should
-        * NOT evaluate the function except in special cases (e.g. the point is outside of the
-        * function domain).
-        * <p>
-        * Note that it is allowed to always allowed to return <code>Double.NaN</code>, especially
-        * for functions that are fast to evaluate.
-        * 
-        * @param point         the point of function evaluation.
-        * @return                      the function value, or <code>Double.NaN</code> if the function value has not been
-        *                                      evaluated at this point.
-        */
-       public double preComputed(Point point);
+       public double evaluate(Point point) throws InterruptedException, OptimizationException;
        
 }
index c280fbaca52d1cf7fe3fc5a15d193855fc2da012..69c42d790514fc600207ab9058091df5d137673e 100644 (file)
@@ -2,18 +2,38 @@ package net.sf.openrocket.optimization.general;
 
 /**
  * A storage of cached values of a function.  The purpose of this class is to
- * cache function values 
+ * cache function values between optimization runs.  Subinterfaces may provide
+ * additional functionality.
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 public interface FunctionCache {
        
+       /**
+        * Compute and return the value of the function at the specified point.
+        * 
+        * @param point         the point at which to evaluate.
+        * @return                      the value of the function at that point.
+        */
        public double getValue(Point point);
        
+       /**
+        * Clear the cache.
+        */
        public void clearCache();
        
+       /**
+        * Return the function that is evaluated by this cache implementation.
+        * 
+        * @return      the function that is being evaluated.
+        */
        public Function getFunction();
        
+       /**
+        * Set the function that is evaluated by this cache implementation.
+        * 
+        * @param function      the function that is being evaluated.
+        */
        public void setFunction(Function function);
        
 }
index 2caf7fa9cc9c17ca6d4b46a084f8404e3f322442..485843f9667f610d8eaa8a8ee735997fcf3e60e2 100644 (file)
@@ -14,8 +14,9 @@ public interface FunctionOptimizer {
         * 
         * @param initial       the initial start point of the optimization.
         * @param control       the optimization control.
+        * @throws OptimizationException        if an error occurs that prevents optimization
         */
-       public void optimize(Point initial, OptimizationController control);
+       public void optimize(Point initial, OptimizationController control) throws OptimizationException;
        
        
        /**
diff --git a/src/net/sf/openrocket/optimization/general/OptimizationException.java b/src/net/sf/openrocket/optimization/general/OptimizationException.java
new file mode 100644 (file)
index 0000000..7c0c067
--- /dev/null
@@ -0,0 +1,22 @@
+package net.sf.openrocket.optimization.general;
+
+/**
+ * An exception that prevents optimization from continuing.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class OptimizationException extends Exception {
+       
+       public OptimizationException(String message) {
+               super(message);
+       }
+       
+       public OptimizationException(Throwable cause) {
+               super(cause);
+       }
+       
+       public OptimizationException(String message, Throwable cause) {
+               super(message, cause);
+       }
+       
+}
index 4f84dccb8ac3882eda42e609d6194c96088f3995..780786007319224b8af7338fd2fb552771d5c37a 100644 (file)
@@ -14,11 +14,17 @@ import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
+import net.sf.openrocket.util.BugException;
+
 /**
  * An implementation of a ParallelFunctionCache that evaluates function values
  * in parallel and caches them.  This allows pre-calculating possibly required
  * function values beforehand.  If values are not required after all, the
  * computation can be aborted assuming the function evaluation supports it.
+ * <p>
+ * Note that while this class handles threads and abstracts background execution,
+ * the public methods themselves are NOT thread-safe and should be called from
+ * only one thread at a time.
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
@@ -32,13 +38,22 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
        private Function function;
        
        
-
+       /**
+        * Construct a cache that uses the same number of computational threads as there are
+        * processors available.
+        */
        public ParallelExecutorCache() {
                this(Runtime.getRuntime().availableProcessors());
        }
        
+       /**
+        * Construct a cache that uses the specified number of computational threads for background
+        * computation.  The threads that are created are marked as daemon threads.
+        * 
+        * @param threadCount   the number of threads to use in the executor.
+        */
        public ParallelExecutorCache(int threadCount) {
-               executor = new ThreadPoolExecutor(threadCount, threadCount, 60, TimeUnit.SECONDS,
+               this(new ThreadPoolExecutor(threadCount, threadCount, 60, TimeUnit.SECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                new ThreadFactory() {
                                        @Override
@@ -47,20 +62,22 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
                                                t.setDaemon(true);
                                                return t;
                                        }
-                               });
+                               }));
        }
        
+       /**
+        * Construct a cache that uses the specified ExecutorService for managing
+        * computational threads.
+        * 
+        * @param executor      the executor to use for function evaluations.
+        */
        public ParallelExecutorCache(ExecutorService executor) {
                this.executor = executor;
        }
        
        
 
-       /**
-        * Queue a list of function evaluations at the specified points.
-        * 
-        * @param points        the points at which to evaluate the function.
-        */
+       @Override
        public void compute(Collection<Point> points) {
                for (Point p : points) {
                        compute(p);
@@ -68,11 +85,7 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
        }
        
        
-       /**
-        * Queue function evaluation for the specified point.
-        * 
-        * @param point         the point at which to evaluate the function.
-        */
+       @Override
        public void compute(Point point) {
                if (functionCache.containsKey(point)) {
                        // Function has already been evaluated at the point
@@ -84,13 +97,6 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
                        return;
                }
                
-               double value = function.preComputed(point);
-               if (!Double.isNaN(value)) {
-                       // Function value was in function cache
-                       functionCache.put(point, value);
-                       return;
-               }
-               
                // Submit point for evaluation
                FunctionCallable callable = new FunctionCallable(function, point);
                Future<Double> future = executor.submit(callable);
@@ -98,27 +104,16 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
        }
        
        
-       /**
-        * Wait for a collection of points to be computed.  After calling this method
-        * the function values are available by calling XXX
-        * 
-        * @param points        the points to wait for.
-        * @throws InterruptedException         if this thread was interrupted while waiting.
-        */
-       public void waitFor(Collection<Point> points) throws InterruptedException {
+       @Override
+       public void waitFor(Collection<Point> points) throws InterruptedException, OptimizationException {
                for (Point p : points) {
                        waitFor(p);
                }
        }
        
-       /**
-        * Wait for a point to be computed.  After calling this method
-        * the function values are available by calling XXX
-        * 
-        * @param point         the point to wait for.
-        * @throws InterruptedException         if this thread was interrupted while waiting.
-        */
-       public void waitFor(Point point) throws InterruptedException {
+       
+       @Override
+       public void waitFor(Point point) throws InterruptedException, OptimizationException {
                if (functionCache.containsKey(point)) {
                        return;
                }
@@ -132,18 +127,24 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
                        double value = future.get();
                        functionCache.put(point, value);
                } catch (ExecutionException e) {
-                       throw new IllegalStateException("Function threw exception while processing", e.getCause());
+                       Throwable cause = e.getCause();
+                       if (cause instanceof InterruptedException) {
+                               throw (InterruptedException) cause;
+                       }
+                       if (cause instanceof OptimizationException) {
+                               throw (OptimizationException) cause;
+                       }
+                       if (cause instanceof RuntimeException) {
+                               throw (RuntimeException) cause;
+                       }
+                       
+                       throw new BugException("Function threw unknown exception while processing", e);
                }
        }
        
        
-       /**
-        * Abort the computation of the specified point.  If computation has ended,
-        * the result is stored in the function cache anyway.
-        * 
-        * @param points        the points to abort.
-        * @return                      a list of the points that have been computed anyway
-        */
+
+       @Override
        public List<Point> abort(Collection<Point> points) {
                List<Point> computed = new ArrayList<Point>(Math.min(points.size(), 10));
                
@@ -157,13 +158,8 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
        }
        
        
-       /**
-        * Abort the computation of the specified point.  If computation has ended,
-        * the result is stored in the function cache anyway.
-        * 
-        * @param point         the point to abort.
-        * @return                      <code>true</code> if the point has been computed anyway, <code>false</code> if not.
-        */
+
+       @Override
        public boolean abort(Point point) {
                if (functionCache.containsKey(point)) {
                        return true;
@@ -191,17 +187,17 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
        }
        
        
+       @Override
        public double getValue(Point point) {
                Double d = functionCache.get(point);
                if (d == null) {
-                       throw new IllegalStateException(point.toString() + " is not in function cache.  " +
+                       throw new IllegalStateException(point + " is not in function cache.  " +
                                        "functionCache=" + functionCache + "  futureMap=" + futureMap);
                }
                return d;
        }
        
        
-
        @Override
        public Function getFunction() {
                return function;
@@ -220,6 +216,7 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
                functionCache.clear();
        }
        
+       
        public ExecutorService getExecutor() {
                return executor;
        }
@@ -239,7 +236,7 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
                }
                
                @Override
-               public Double call() throws InterruptedException {
+               public Double call() throws InterruptedException, OptimizationException {
                        return calledFunction.evaluate(point);
                }
        }
index 9b4a0ebab97dc3e64b2e63ae9d1a9de9603e0c22..c5b34a5246d2c23aa44929abcf6cbabe748cb85c 100644 (file)
@@ -4,7 +4,7 @@ import java.util.Collection;
 import java.util.List;
 
 /**
- * A FunctionCache that allows queuing points to be computed in the background,
+ * A FunctionCache that allows scheduling points to be computed in the background,
  * waiting for specific points to become computed or aborting the computation of
  * points.
  * 
@@ -13,14 +13,17 @@ import java.util.List;
 public interface ParallelFunctionCache extends FunctionCache {
        
        /**
-        * Queue a list of function evaluations at the specified points.
+        * Schedule a list of function evaluations at the specified points.
+        * The points are added to the end of the computation queue in the order
+        * they are returned by the iterator.
         * 
         * @param points        the points at which to evaluate the function.
         */
        public void compute(Collection<Point> points);
        
        /**
-        * Queue function evaluation for the specified point.
+        * Schedule function evaluation for the specified point.  The point is
+        * added to the end of the computation queue.
         * 
         * @param point         the point at which to evaluate the function.
         */
@@ -28,25 +31,26 @@ public interface ParallelFunctionCache extends FunctionCache {
        
        /**
         * Wait for a collection of points to be computed.  After calling this method
-        * the function values are available by calling XXX
+        * the function values are available by calling {@link #getValue(Point)}.
         * 
         * @param points        the points to wait for.
-        * @throws InterruptedException         if this thread was interrupted while waiting.
+        * @throws InterruptedException         if this thread or the computing thread was interrupted while waiting.
         */
-       public void waitFor(Collection<Point> points) throws InterruptedException;
+       public void waitFor(Collection<Point> points) throws InterruptedException, OptimizationException;
        
        /**
         * Wait for a point to be computed.  After calling this method
-        * the function values are available by calling XXX
+        * the function value is available by calling {@link #getValue(Point)}.
         * 
         * @param point         the point to wait for.
-        * @throws InterruptedException         if this thread was interrupted while waiting.
+        * @throws InterruptedException         if this thread or the computing thread was interrupted while waiting.
+        * @throws OptimizationException 
         */
-       public void waitFor(Point point) throws InterruptedException;
+       public void waitFor(Point point) throws InterruptedException, OptimizationException;
        
        
        /**
-        * Abort the computation of the specified point.  If computation has ended,
+        * Abort the computation of the specified points.  If computation has ended,
         * the result is stored in the function cache anyway.
         * 
         * @param points        the points to abort.
index 80a194b853340a4d8b252b4664a0656500e10e85..0e70bf13c239390bba236168b34b00d1bb13ffb8 100644 (file)
@@ -9,6 +9,7 @@ import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.optimization.general.FunctionCache;
 import net.sf.openrocket.optimization.general.FunctionOptimizer;
 import net.sf.openrocket.optimization.general.OptimizationController;
+import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.general.ParallelFunctionCache;
 import net.sf.openrocket.optimization.general.Point;
 import net.sf.openrocket.startup.Application;
@@ -48,7 +49,7 @@ public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Stati
        
 
        @Override
-       public void optimize(Point initial, OptimizationController control) {
+       public void optimize(Point initial, OptimizationController control) throws OptimizationException {
                FunctionCacheComparator comparator = new FunctionCacheComparator(functionExecutor);
                
                final List<Point> pattern = SearchPattern.square(initial.dim());
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameter.java
new file mode 100644 (file)
index 0000000..e8506e8
--- /dev/null
@@ -0,0 +1,33 @@
+package net.sf.openrocket.optimization.rocketoptimization;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
+
+/**
+ * A parameter of a rocket or simulation that can be optimized
+ * (for example max. altitude or velocity).
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface OptimizableParameter {
+       
+       /**
+        * Return the label name for this optimization parameter.
+        * 
+        * @return      the name for the optimization parameter (e.g. "Flight altitude")
+        */
+       public String getName();
+       
+       /**
+        * Compute the value for this optimization parameter for the simulation.
+        * The return value can be any double value.
+        * <p>
+        * This method can return NaN in case of a problem computing
+        * 
+        * @param simulation    the simulation
+        * @return                              the parameter value (any double value)
+        * @throws OptimizationException        if an error occurs preventing the optimization from continuing
+        */
+       public double computeValue(Simulation simulation) throws OptimizationException;
+       
+}
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameterService.java b/src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameterService.java
new file mode 100644 (file)
index 0000000..b227917
--- /dev/null
@@ -0,0 +1,23 @@
+package net.sf.openrocket.optimization.rocketoptimization;
+
+import java.util.Collection;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+
+/**
+ * A service for generating rocket optimization parameters.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface OptimizableParameterService {
+       
+       /**
+        * Return all available rocket optimization parameters for this document.
+        * These should be new instances unless the parameter implementation is stateless.
+        * 
+        * @param document      the design document
+        * @return                      a collection of the rocket optimization parameters.
+        */
+       public Collection<OptimizableParameter> getParameters(OpenRocketDocument document);
+       
+}
index c975a8a4fc41bc71b6f4f2c57376883ddcb2a5ba..a9fb500f5492d63ea32fb3464a254ef0f4be7ffd 100644 (file)
@@ -7,6 +7,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.optimization.general.Function;
+import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.general.Point;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.startup.Application;
@@ -20,13 +21,16 @@ import net.sf.openrocket.startup.Application;
 public class RocketOptimizationFunction implements Function {
        private static final LogHelper log = Application.getLogger();
        
+       private static final double OUTSIDE_DOMAIN_SCALE = 1.0e200;
+       
        /*
         * NOTE:  This class must be thread-safe!!!
         */
 
        private final Simulation baseSimulation;
-       private final RocketOptimizationParameter parameter;
+       private final OptimizableParameter parameter;
        private final OptimizationGoal goal;
+       private final SimulationDomain domain;
        private final SimulationModifier[] modifiers;
        
        private final Map<Point, Double> parameterValueCache = new ConcurrentHashMap<Point, Double>();
@@ -44,11 +48,12 @@ public class RocketOptimizationFunction implements Function {
         * @param goal                          the goal of the rocket parameter
         * @param modifiers                     the modifiers that modify the simulation
         */
-       public RocketOptimizationFunction(Simulation baseSimulation, RocketOptimizationParameter parameter,
-                       OptimizationGoal goal, SimulationModifier... modifiers) {
+       public RocketOptimizationFunction(Simulation baseSimulation, OptimizableParameter parameter,
+                       OptimizationGoal goal, SimulationDomain domain, SimulationModifier... modifiers) {
                this.baseSimulation = baseSimulation;
                this.parameter = parameter;
                this.goal = goal;
+               this.domain = domain;
                this.modifiers = modifiers.clone();
                if (modifiers.length == 0) {
                        throw new IllegalArgumentException("No SimulationModifiers specified");
@@ -57,52 +62,71 @@ public class RocketOptimizationFunction implements Function {
        
        
        @Override
-       public double evaluate(Point point) throws InterruptedException {
+       public double evaluate(Point point) throws InterruptedException, OptimizationException {
+               /*
+                * parameterValue is the computed parameter value (e.g. altitude)
+                * goalValue is the value that needs to be minimized
+                */
+               double goalValue, parameterValue;
                
                // Check for precomputed value
-               double value = preComputed(point);
-               if (!Double.isNaN(value)) {
-                       return value;
+               Double d = goalValueCache.get(point);
+               if (d != null && !Double.isNaN(d)) {
+                       log.verbose("Optimization function value at point " + point + " was found in cache: " + d);
+                       return d;
                }
                
+               log.verbose("Computing optimization function value at point " + point);
+               
                // Create the new simulation based on the point
                double[] p = point.asArray();
                if (p.length != modifiers.length) {
                        throw new IllegalArgumentException("Point has length " + p.length + " while function has " +
                                        modifiers.length + " simulation modifiers");
                }
-               Simulation simulation = newSimulationInstance();
+               
+               Simulation simulation = newSimulationInstance(baseSimulation);
                for (int i = 0; i < modifiers.length; i++) {
                        modifiers[i].modify(simulation, p[i]);
                }
                
+
+               // Check whether the point is within the simulation domain
+               double distance = domain.getDistanceToDomain(simulation);
+               if (distance > 0 || Double.isNaN(distance)) {
+                       if (Double.isNaN(distance)) {
+                               goalValue = Double.MAX_VALUE;
+                       } else {
+                               goalValue = (distance + 1) * OUTSIDE_DOMAIN_SCALE;
+                       }
+                       parameterValueCache.put(point, Double.NaN);
+                       goalValueCache.put(point, goalValue);
+                       log.verbose("Optimization point is outside of domain, distance=" + distance + " goal function value=" + goalValue);
+                       return goalValue;
+               }
+               
+
                // Compute the optimization value
-               value = parameter.computeValue(simulation);
-               parameterValueCache.put(point, value);
+               parameterValue = parameter.computeValue(simulation);
+               parameterValueCache.put(point, parameterValue);
                
-               value = goal.getMinimizationParameter(value);
-               if (Double.isNaN(value)) {
-                       log.warn("Computed value was NaN, baseSimulation=" + baseSimulation + " parameter=" + parameter +
-                                       " goal=" + goal + " modifiers=" + Arrays.toString(modifiers) + " simulation=" + simulation);
-                       value = Double.MAX_VALUE;
+               goalValue = goal.getMinimizationParameter(parameterValue);
+               if (Double.isNaN(goalValue)) {
+                       log.warn("Computed goal value was NaN, baseSimulation=" + baseSimulation + " parameter=" + parameter +
+                                       " goal=" + goal + " modifiers=" + Arrays.toString(modifiers) + " simulation=" + simulation +
+                                       " parameter value=" + parameterValue);
+                       goalValue = Double.MAX_VALUE;
                }
-               goalValueCache.put(point, value);
+               goalValueCache.put(point, goalValue);
                
-               return value;
-       }
-       
-       @Override
-       public double preComputed(Point point) {
-               Double value = goalValueCache.get(point);
-               if (value != null) {
-                       return value;
-               }
+               log.verbose("Parameter value at point " + point + " is " + goalValue + ", goal function value=" + goalValue);
                
-               // TODO: : is in domain?
-               return 0;
+               return goalValue;
        }
        
        
+
+
        /**
         * Return the parameter value at a point that has been computed.  The purpose is
         * to allow retrieving the parameter value corresponding to the found minimum value.
@@ -123,13 +147,15 @@ public class RocketOptimizationFunction implements Function {
        /**
         * Returns a new deep copy of the simulation and rocket.  This methods performs
         * synchronization on the simulation for thread protection.
+        * <p>
+        * Note:  This method is package-private for unit testing purposes.
         * 
-        * @return
+        * @return      a new deep copy of the simulation and rocket
         */
-       private Simulation newSimulationInstance() {
+       Simulation newSimulationInstance(Simulation simulation) {
                synchronized (baseSimulation) {
-                       Rocket newRocket = (Rocket) baseSimulation.getRocket().copy();
-                       Simulation newSimulation = baseSimulation.duplicateSimulation(newRocket);
+                       Rocket newRocket = (Rocket) simulation.getRocket().copy();
+                       Simulation newSimulation = simulation.duplicateSimulation(newRocket);
                        return newSimulation;
                }
        }
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationParameter.java
deleted file mode 100644 (file)
index 61b1a80..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-package net.sf.openrocket.optimization.rocketoptimization;
-
-import net.sf.openrocket.document.Simulation;
-
-/**
- * A parameter of a rocket or simulation that can be optimized
- * (for example max. altitude or velocity).
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public interface RocketOptimizationParameter {
-       
-       /**
-        * Return the label name for this optimization parameter.
-        * 
-        * @return      the name for the optimization parameter (e.g. "Flight altitude")
-        */
-       public String getName();
-       
-       /**
-        * Compute the value for this optimization parameter for the simulation.
-        * The return value can be any double value.
-        * 
-        * @param simulation    the simulation
-        * @return                              the parameter value (any double value)
-        */
-       public double computeValue(Simulation simulation);
-       
-}
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationParameterService.java b/src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationParameterService.java
deleted file mode 100644 (file)
index ecff025..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.sf.openrocket.optimization.rocketoptimization;
-
-import java.util.Collection;
-
-import net.sf.openrocket.document.OpenRocketDocument;
-
-/**
- * A service for generating rocket optimization parameters.
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public interface RocketOptimizationParameterService {
-       
-       /**
-        * Return all available rocket optimization parameters for this document.
-        * These should be new instances unless the parameter implementation is stateless.
-        * 
-        * @param document      the design document
-        * @return                      a collection of the rocket optimization parameters.
-        */
-       public Collection<RocketOptimizationParameter> getParameters(OpenRocketDocument document);
-       
-}
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/SimulationDomain.java b/src/net/sf/openrocket/optimization/rocketoptimization/SimulationDomain.java
new file mode 100644 (file)
index 0000000..b03be65
--- /dev/null
@@ -0,0 +1,25 @@
+package net.sf.openrocket.optimization.rocketoptimization;
+
+import net.sf.openrocket.document.Simulation;
+
+/**
+ * An interface defining a function domain which limits allowed function values.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface SimulationDomain {
+       
+       /**
+        * Return a value determining whether the simulation is within the domain limits
+        * of an optimization process.  If the returned value is negative or zero, the
+        * simulation is within the domain; if the value is positive, the returned value
+        * is an indication of how far from the domain the value is; if the returned value
+        * is NaN, the simulation is outside of the domain.
+        * 
+        * @param simulation    the simulation to check.
+        * @return                              a negative value or zero if the simulation is in the domain;
+        *                                              a positive value or NaN if not.
+        */
+       public double getDistanceToDomain(Simulation simulation);
+       
+}
index a22507d042957f74d8755ba31a679c7d17fa9b5e..147ac0d4fc0a5fbb0a09adc35357661a9750b962 100644 (file)
@@ -72,7 +72,7 @@ public interface SimulationModifier extends ChangeSource {
        /**
         * Return the current scaled value.  This is normally within the range [0...1], but
         * can be outside the range if the current value is outside of the min and max values.
-        * @return
+        * @return      the current value of this parameter (normally between [0 ... 1])
         */
        public double getCurrentScaledValue();
        
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericModifier.java b/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericModifier.java
new file mode 100644 (file)
index 0000000..ac62dc6
--- /dev/null
@@ -0,0 +1,162 @@
+package net.sf.openrocket.optimization.rocketoptimization.modifiers;
+
+import javax.swing.event.ChangeListener;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Reflection.Method;
+
+public class GenericModifier implements SimulationModifier {
+       
+       private final String name;
+       private final Object relatedObject;
+       private final UnitGroup unitGroup;
+       private final double multiplier;
+       private final Object modifiable;
+       
+       private final Method getter;
+       private final Method setter;
+       
+       private double minValue;
+       private double maxValue;
+       
+       
+
+
+
+       public GenericModifier(String modifierName, Object relatedObject, UnitGroup unitGroup, double multiplier,
+                       Object modifiable, String methodName) {
+               this.name = modifierName;
+               this.relatedObject = relatedObject;
+               this.unitGroup = unitGroup;
+               this.multiplier = multiplier;
+               this.modifiable = modifiable;
+               
+               if (MathUtil.equals(multiplier, 0)) {
+                       throw new IllegalArgumentException("multiplier is zero");
+               }
+               
+               try {
+                       methodName = methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
+                       getter = new Method(modifiable.getClass().getMethod("get" + methodName));
+                       setter = new Method(modifiable.getClass().getMethod("set" + methodName, double.class));
+               } catch (SecurityException e) {
+                       throw new BugException("Trying to find method get/set" + methodName + " in class " + modifiable.getClass(), e);
+               } catch (NoSuchMethodException e) {
+                       throw new BugException("Trying to find method get/set" + methodName + " in class " + modifiable.getClass(), e);
+               }
+       }
+       
+       
+       @Override
+       public String getName() {
+               return name;
+       }
+       
+       @Override
+       public Object getRelatedObject() {
+               return relatedObject;
+       }
+       
+       @Override
+       public double getCurrentValue() {
+               return ((Double) getter.invoke(modifiable)) * multiplier;
+       }
+       
+       
+       @Override
+       public double getCurrentScaledValue() {
+               double value = getCurrentValue();
+               return toScaledValue(value);
+       }
+       
+       @Override
+       public void modify(Simulation simulation, double scaledValue) {
+               double siValue = toBaseValue(scaledValue) / multiplier;
+               setter.invoke(modifiable, siValue);
+       }
+       
+       
+       /**
+        * Returns the scaled value (normally within [0...1]).
+        */
+       private double toScaledValue(double value) {
+               if (MathUtil.equals(minValue, maxValue)) {
+                       if (value > maxValue)
+                               return 1.0;
+                       if (value < minValue)
+                               return 0.0;
+                       return 0.5;
+               }
+               
+               return MathUtil.map(value, minValue, maxValue, 0.0, 1.0);
+       }
+       
+       
+       /**
+        * Returns the base value (in SI units).
+        */
+       private double toBaseValue(double value) {
+               return MathUtil.map(value, 0.0, 1.0, minValue, maxValue);
+       }
+       
+       
+
+       @Override
+       public double getMinValue() {
+               return minValue;
+       }
+       
+       @Override
+       public void setMinValue(double value) {
+               if (MathUtil.equals(minValue, value))
+                       return;
+               this.minValue = value;
+               if (maxValue < minValue)
+                       maxValue = minValue;
+               fireChangeEvent();
+       }
+       
+       @Override
+       public double getMaxValue() {
+               return maxValue;
+       }
+       
+       @Override
+       public void setMaxValue(double value) {
+               if (MathUtil.equals(maxValue, value))
+                       return;
+               this.maxValue = value;
+               if (minValue > maxValue)
+                       minValue = maxValue;
+               fireChangeEvent();
+       }
+       
+       @Override
+       public UnitGroup getUnitGroup() {
+               return unitGroup;
+       }
+       
+       
+       @Override
+       public void addChangeListener(ChangeListener listener) {
+               // TODO Auto-generated method stub
+               
+       }
+       
+       @Override
+       public void removeChangeListener(ChangeListener listener) {
+               // TODO Auto-generated method stub
+               
+       }
+       
+       
+       private void fireChangeEvent() {
+               // TODO Auto-generated method stub
+               
+       }
+       
+}
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAltitudeParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAltitudeParameter.java
new file mode 100644 (file)
index 0000000..d8a2543
--- /dev/null
@@ -0,0 +1,32 @@
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.exception.SimulationException;
+import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
+
+/**
+ * An optimization parameter that computes the maximum altitude of a simulated flight.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class MaximumAltitudeParameter implements OptimizableParameter {
+       
+       @Override
+       public String getName() {
+               return "Maximum altitude";
+       }
+       
+       @Override
+       public double computeValue(Simulation simulation) throws OptimizationException {
+               try {
+                       simulation.simulate(new ApogeeEndListener());
+                       return simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ALTITUDE);
+               } catch (SimulationException e) {
+                       throw new OptimizationException(e);
+               }
+       }
+       
+}
index a347bc07102b8f7d12a01a149c2ef3bbdd8c1613..7ba088b9fad1a452c8b9e89cf9cf400525a7fc9e 100644 (file)
@@ -12,7 +12,10 @@ import java.util.UUID;
 import javax.swing.event.ChangeListener;
 import javax.swing.event.EventListenerList;
 
+import net.sf.openrocket.gui.main.ExceptionHandler;
+import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.Chars;
 import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.MathUtil;
@@ -30,9 +33,9 @@ import net.sf.openrocket.util.UniqueID;
  */
 
 public class Rocket extends RocketComponent {
-       public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
+       private static final LogHelper log = Application.getLogger();
        
-       private static final boolean DEBUG_LISTENERS = false;
+       public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
        
 
        /**
@@ -343,17 +346,15 @@ public class Rocket extends RocketComponent {
        public void addComponentChangeListener(ComponentChangeListener l) {
                checkState();
                listenerList.add(ComponentChangeListener.class, l);
-               if (DEBUG_LISTENERS)
-                       System.out.println(this + ": Added listner (now " + listenerList.getListenerCount() +
-                                       " listeners): " + l);
+               log.verbose("Added ComponentChangeListener " + l + ", current number of listeners is " +
+                               listenerList.getListenerCount());
        }
        
        @Override
        public void removeComponentChangeListener(ComponentChangeListener l) {
                listenerList.remove(ComponentChangeListener.class, l);
-               if (DEBUG_LISTENERS)
-                       System.out.println(this + ": Removed listner (now " + listenerList.getListenerCount() +
-                                       " listeners): " + l);
+               log.verbose("Removed ComponentChangeListener " + l + ", current number of listeners is " +
+                               listenerList.getListenerCount());
        }
        
        
@@ -361,17 +362,15 @@ public class Rocket extends RocketComponent {
        public void addChangeListener(ChangeListener l) {
                checkState();
                listenerList.add(ChangeListener.class, l);
-               if (DEBUG_LISTENERS)
-                       System.out.println(this + ": Added listner (now " + listenerList.getListenerCount() +
-                                       " listeners): " + l);
+               log.verbose("Added ChangeListener " + l + ", current number of listeners is " +
+                               listenerList.getListenerCount());
        }
        
        @Override
        public void removeChangeListener(ChangeListener l) {
                listenerList.remove(ChangeListener.class, l);
-               if (DEBUG_LISTENERS)
-                       System.out.println(this + ": Removed listner (now " + listenerList.getListenerCount() +
-                                       " listeners): " + l);
+               log.verbose("Removed ChangeListener " + l + ", current number of listeners is " +
+                               listenerList.getListenerCount());
        }
        
        
@@ -392,15 +391,15 @@ public class Rocket extends RocketComponent {
                                functionalModID = modID;
                }
                
-               if (DEBUG_LISTENERS)
-                       System.out.println("FIRING " + e);
-               
                // Check whether frozen
                if (freezeList != null) {
+                       log.debug("Rocket is in frozen state, adding event " + e + " info freeze list");
                        freezeList.add(e);
                        return;
                }
                
+               log.debug("Firing rocket change event " + e);
+               
                // Notify all components first
                Iterator<RocketComponent> iterator = this.deepIterator(true);
                while (iterator.hasNext()) {
@@ -441,6 +440,10 @@ public class Rocket extends RocketComponent {
                checkState();
                if (freezeList == null) {
                        freezeList = new LinkedList<ComponentChangeEvent>();
+                       log.debug("Freezing Rocket");
+               } else {
+                       ExceptionHandler.handleErrorCondition("Attempting to freeze Rocket when it is already frozen, " +
+                                       "freezeList=" + freezeList);
                }
        }
        
@@ -453,13 +456,18 @@ public class Rocket extends RocketComponent {
         */
        public void thaw() {
                checkState();
-               if (freezeList == null)
+               if (freezeList == null) {
+                       ExceptionHandler.handleErrorCondition("Attempting to thaw Rocket when it is not frozen");
                        return;
+               }
                if (freezeList.size() == 0) {
+                       log.warn("Thawing rocket with no changes made");
                        freezeList = null;
                        return;
                }
                
+               log.debug("Thawing rocket, freezeList=" + freezeList);
+               
                int type = 0;
                Object c = null;
                for (ComponentChangeEvent e : freezeList) {
index 2e0a784cdcbbb4a74989187a9c98a031d9b64cdf..34ae962f2ba15a4550a86a677544be9a915904dd 100644 (file)
@@ -12,7 +12,9 @@ import java.util.Stack;
 
 import javax.swing.event.ChangeListener;
 
+import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.logging.TraceException;
+import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.ChangeSource;
 import net.sf.openrocket.util.Coordinate;
@@ -23,6 +25,7 @@ import net.sf.openrocket.util.UniqueID;
 
 public abstract class RocketComponent implements ChangeSource, Cloneable,
                Iterable<RocketComponent> {
+       private static final LogHelper log = Application.getLogger();
        
        /*
         * Text is suitable to the form
@@ -1304,6 +1307,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
                checkState();
                if (parent == null) {
                        /* Ignore if root invalid. */
+                       log.debug("Attempted firing event " + e + " with root " + this.getComponentName() + ", ignoring event");
                        return;
                }
                getRoot().fireComponentChangeEvent(e);
index ef80f4979f9b14cc6507383eb8ff2688de3b3513..655cdb52b780b3d2e0132c319be9ae884c4d2735 100644 (file)
@@ -41,6 +41,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
        private SimulationStatus status;
        
        
+       @Override
        public FlightData simulate(SimulationConditions simulationConditions) throws SimulationException {
                Set<MotorId> motorBurntOut = new HashSet<MotorId>();
                
@@ -409,7 +410,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
                                
                        case STAGE_SEPARATION: {
                                // TODO: HIGH: Store lower stages to be simulated later
-                               RocketComponent stage = (RocketComponent) event.getSource();
+                               RocketComponent stage = event.getSource();
                                int n = stage.getStageNumber();
                                status.getConfiguration().setToStage(n);
                                status.getFlightData().addEvent(event);
@@ -542,6 +543,14 @@ public class BasicEventSimulationEngine implements SimulationEngine {
                d += status.getEffectiveLaunchRodLength();
                
                if (Double.isNaN(d) || b) {
+                       log.error("Simulation resulted in NaN value:" +
+                                       " simulationTime=" + status.getSimulationTime() +
+                                       " previousTimeStep=" + status.getPreviousTimeStep() +
+                                       " rocketPosition=" + status.getRocketPosition() +
+                                       " rocketVelocity=" + status.getRocketVelocity() +
+                                       " rocketOrientationQuaternion=" + status.getRocketOrientationQuaternion() +
+                                       " rocketRotationVelocity=" + status.getRocketRotationVelocity() +
+                                       " effectiveLaunchRodLength=" + status.getEffectiveLaunchRodLength());
                        throw new SimulationException("Simulation resulted in not-a-number (NaN) value, please report a bug.");
                }
        }
index 2d2d492e0e9d8c09c81f210b51ef196cc88a4349..658f70c0b05a42d6ecd47765302eac39d13bc556 100644 (file)
@@ -62,6 +62,9 @@ public class Startup {
        
        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();
                
@@ -91,6 +94,18 @@ public class Startup {
        
 
 
+       private static void checkDebugStatus() {
+               if (System.getProperty("openrocket.debug") != null) {
+                       System.setProperty("openrocket.log.stdout", "VBOSE");
+                       System.setProperty("openrocket.log.tracelevel", "VBOSE");
+                       System.setProperty("openrocket.debug.menu", "true");
+                       System.setProperty("openrocket.debug.motordigest", "true");
+               }
+       }
+       
+       
+
+
        private static void runMain(String[] args) {
                
                // Initialize the splash screen with version info
diff --git a/src/net/sf/openrocket/util/ConcurrencyException.java b/src/net/sf/openrocket/util/ConcurrencyException.java
new file mode 100644 (file)
index 0000000..252267c
--- /dev/null
@@ -0,0 +1,26 @@
+package net.sf.openrocket.util;
+
+/**
+ * An exception that indicates a concurrency bug in the software.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ConcurrencyException extends FatalException {
+       
+       public ConcurrencyException() {
+               super();
+       }
+       
+       public ConcurrencyException(String message, Throwable cause) {
+               super(message, cause);
+       }
+       
+       public ConcurrencyException(String message) {
+               super(message);
+       }
+       
+       public ConcurrencyException(Throwable cause) {
+               super(cause);
+       }
+       
+}
index 54047e35ec0c30f3949c8fff87dbdd8a0a801aab..ff97d9a8e1853df824d396f81654e74ebb05459b 100644 (file)
@@ -128,6 +128,7 @@ public class GUIUtil {
         */
        public static void installEscapeCloseOperation(final JDialog dialog) {
                Action dispatchClosing = new AbstractAction() {
+                       @Override
                        public void actionPerformed(ActionEvent event) {
                                log.user("Closing dialog " + dialog);
                                dialog.dispatchEvent(new WindowEvent(dialog, WindowEvent.WINDOW_CLOSING));
@@ -194,6 +195,7 @@ public class GUIUtil {
                        @Override
                        public void windowClosed(WindowEvent e) {
                                setNullModels(window);
+                               MemoryManagement.collectable(window);
                        }
                });
        }
@@ -341,7 +343,7 @@ public class GUIUtil {
                        
                        JTree tree = (JTree) c;
                        tree.setModel(new DefaultTreeModel(new TreeNode() {
-                               @SuppressWarnings("unchecked")
+                               @SuppressWarnings("rawtypes")
                                @Override
                                public Enumeration children() {
                                        return new Vector().elements();
diff --git a/src/net/sf/openrocket/util/MemoryManagement.java b/src/net/sf/openrocket/util/MemoryManagement.java
new file mode 100644 (file)
index 0000000..34589b5
--- /dev/null
@@ -0,0 +1,137 @@
+package net.sf.openrocket.util;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A class that performs certain memory-management operations for debugging purposes.
+ * For example, complex objects that are being discarded and that should be garbage-collectable
+ * (such as dialog windows) should be registered for monitoring by calling
+ * {@link #collectable(Object)}.  This will allow monitoring whether the object really is
+ * garbage-collected or whether it is retained in memory due to a memory leak.
+ * Only complex objects should be registered due to the overhead of the monitoring.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public final class MemoryManagement {
+       private static final LogHelper log = Application.getLogger();
+       
+       /** Purge cleared references every this many calls to {@link #collectable(Object)} */
+       private static final int PURGE_CALL_COUNT = 100;
+       
+
+       /**
+        * Storage of the objects.  This is basically a mapping from the objects (using weak references)
+        * to 
+        */
+       private static List<MemoryData> objects = new LinkedList<MemoryData>();
+       private static int callCount = 0;
+       
+       
+       private MemoryManagement() {
+       }
+       
+       
+       /**
+        * Mark an object that should be garbage-collectable by the GC.  This class will monitor
+        * whether the object actually gets garbage-collected or not by holding a weak reference
+        * to the object.
+        * 
+        * @param o             the object to monitor.
+        */
+       public static synchronized void collectable(Object o) {
+               if (o == null) {
+                       throw new IllegalArgumentException("object is null");
+               }
+               log.debug("Adding object into collectable list: " + o);
+               objects.add(new MemoryData(o));
+               callCount++;
+               if (callCount % PURGE_CALL_COUNT == 0) {
+                       purge();
+               }
+       }
+       
+       
+       /**
+        * Return the number of times {@link #collectable(Object)} has been called.
+        * @return      the number of times {@link #collectable(Object)} has been called.
+        */
+       public static synchronized int getCallCount() {
+               return callCount;
+       }
+       
+       
+       /**
+        * Return a list of MemoryData objects corresponding to the objects that have been
+        * registered by {@link #collectable(Object)} and have not been garbage-collected properly.
+        * This method first calls <code>System.gc()</code> multiple times to attempt to
+        * force any remaining garbage collection.
+        * 
+        * @return      a list of MemoryData objects for objects that have not yet been garbage-collected.
+        */
+       public static synchronized ArrayList<MemoryData> getRemainingObjects() {
+               for (int i = 0; i < 5; i++) {
+                       System.runFinalization();
+                       System.gc();
+                       try {
+                               Thread.sleep(1);
+                       } catch (InterruptedException e) {
+                       }
+               }
+               purge();
+               return new ArrayList<MemoryData>(objects);
+       }
+       
+       
+
+       /**
+        * Purge all cleared references from the object list.
+        */
+       private static void purge() {
+               int origCount = objects.size();
+               Iterator<MemoryData> iterator = objects.iterator();
+               while (iterator.hasNext()) {
+                       MemoryData data = iterator.next();
+                       if (data.getReference().get() == null) {
+                               iterator.remove();
+                       }
+               }
+               log.debug(objects.size() + " of " + origCount + " objects remaining in discarded objects list after purge.");
+       }
+       
+       
+       /**
+        * A value object class containing data of a discarded object reference.
+        */
+       public static final class MemoryData {
+               private final WeakReference<Object> reference;
+               private final long registrationTime;
+               
+               private MemoryData(Object object) {
+                       this.reference = new WeakReference<Object>(object);
+                       this.registrationTime = System.currentTimeMillis();
+               }
+               
+               /**
+                * Return the weak reference to the discarded object.
+                */
+               public WeakReference<Object> getReference() {
+                       return reference;
+               }
+               
+               /**
+                * Return the time when the object was discarded.
+                * @return      a millisecond timestamp of when the object was discarded.
+                */
+               public long getRegistrationTime() {
+                       return registrationTime;
+               }
+       }
+       
+}
diff --git a/src/net/sf/openrocket/util/SafetyMutex.java b/src/net/sf/openrocket/util/SafetyMutex.java
new file mode 100644 (file)
index 0000000..fb202a9
--- /dev/null
@@ -0,0 +1,171 @@
+package net.sf.openrocket.util;
+
+import java.util.LinkedList;
+
+import net.sf.openrocket.gui.main.ExceptionHandler;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A mutex that can be used for verifying thread safety.  This class cannot be
+ * used to perform synchronization, only to detect concurrency issues.  This
+ * class can be used by the main methods of non-thread-safe classes to ensure
+ * the class is not wrongly used from multiple threads concurrently.
+ * <p>
+ * This mutex is not reentrant even for the same thread that has locked it.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class SafetyMutex {
+       private static final LogHelper log = Application.getLogger();
+       
+       // Package-private for unit testing
+       static volatile boolean errorReported = false;
+       
+       // lockingThread is set when this mutex is locked.
+       Thread lockingThread = null;
+       // Stack of places that have locked this mutex
+       final LinkedList<String> locations = new LinkedList<String>();
+       
+       
+       /**
+        * Verify that this mutex is unlocked, but don't lock it.  This has the same effect
+        * as <code>mutex.lock(); mutex.unlock();</code> and is useful for methods that return
+        * immediately (e.g. getters).
+        * 
+        * @throws ConcurrencyException if this mutex is already locked.
+        */
+       public synchronized void verify() {
+               checkState(true);
+               if (lockingThread != null && lockingThread != Thread.currentThread()) {
+                       error("Mutex is already locked", true);
+               }
+       }
+       
+       
+       /**
+        * Lock this mutex.  If this mutex is already locked an error is raised and
+        * a ConcurrencyException is thrown.  The location parameter is used to distinguish
+        * the locking location, and it should be e.g. the method name.
+        * 
+        * @param location      a string describing the location where this mutex was locked (cannot be null).
+        * 
+        * @throws ConcurrencyException         if this mutex is already locked.
+        */
+       public synchronized void lock(String location) {
+               if (location == null) {
+                       throw new IllegalArgumentException("location is null");
+               }
+               checkState(true);
+               
+               Thread currentThread = Thread.currentThread();
+               if (lockingThread != null && lockingThread != currentThread) {
+                       error("Mutex is already locked", true);
+               }
+               
+               lockingThread = currentThread;
+               locations.push(location);
+       }
+       
+       
+
+       /**
+        * Unlock this mutex.  If this mutex is not locked at the position of the parameter
+        * or was locked by another thread than the current thread an error is raised,
+        * but an exception is not thrown.
+        * <p>
+        * This method is guaranteed never to throw an exception, so it can safely be used in finally blocks.
+        * 
+        * @param location      a location string matching that which locked the mutex
+        * @return                      whether the unlocking was successful (this normally doesn't need to be checked)
+        */
+       public synchronized boolean unlock(String location) {
+               try {
+                       
+                       if (location == null) {
+                               ExceptionHandler.handleErrorCondition("location is null");
+                               location = "";
+                       }
+                       checkState(false);
+                       
+
+                       // Check that the mutex is locked
+                       if (lockingThread == null) {
+                               error("Mutex was not locked", false);
+                               return false;
+                       }
+                       
+                       // Check that the mutex is locked by the current thread
+                       if (lockingThread != Thread.currentThread()) {
+                               error("Mutex is being unlocked from differerent thread than where it was locked", false);
+                               return false;
+                       }
+                       
+                       // Check that the unlock location is correct
+                       String lastLocation = locations.pop();
+                       if (!location.equals(lastLocation)) {
+                               locations.push(lastLocation);
+                               error("Mutex unlocking location does not match locking location, location=" + location, false);
+                               return false;
+                       }
+                       
+                       // Unlock the mutex if the last one
+                       if (locations.isEmpty()) {
+                               lockingThread = null;
+                       }
+                       return true;
+               } catch (Exception e) {
+                       ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " +
+                                       "locking thread=" + lockingThread + " locations=" + locations, e);
+                       return false;
+               }
+       }
+       
+       
+
+       /**
+        * Check that the internal state of the mutex (lockingThread vs. locations) is correct.
+        */
+       private void checkState(boolean throwException) {
+               /*
+                * Disallowed states:
+                *   lockingThread == null  &&  !locations.isEmpty()
+                *   lockingThread != null  &&  locations.isEmpty()
+                */
+               if ((lockingThread == null) ^ (locations.isEmpty())) {
+                       // Clear the mutex only after error() has executed (and possibly thrown an exception)
+                       try {
+                               error("Mutex data inconsistency occurred - unlocking mutex", throwException);
+                       } finally {
+                               lockingThread = null;
+                               locations.clear();
+                       }
+               }
+       }
+       
+       
+       /**
+        * Raise an error.  The first occurrence is passed directly to the exception handler,
+        * later errors are simply logged.
+        */
+       private void error(String message, boolean throwException) {
+               message = message +
+                               ", current thread = " + Thread.currentThread() +
+                               ", locking thread=" + lockingThread +
+                               ", locking locations=" + locations;
+               
+               ConcurrencyException ex = new ConcurrencyException(message);
+               
+               if (!errorReported) {
+                       errorReported = true;
+                       ExceptionHandler.handleErrorCondition(ex);
+               } else {
+                       log.error(message, ex);
+               }
+               
+               if (throwException) {
+                       throw ex;
+               }
+       }
+       
+}
index a57ea78a71125577eac7075514575acbc9be0455..e5f9da71ae7287fed84935ed6be85c52c0a56deb 100644 (file)
@@ -3,6 +3,7 @@ package net.sf.openrocket.utils;
 import net.sf.openrocket.optimization.general.Function;
 import net.sf.openrocket.optimization.general.FunctionOptimizer;
 import net.sf.openrocket.optimization.general.OptimizationController;
+import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.general.ParallelExecutorCache;
 import net.sf.openrocket.optimization.general.ParallelFunctionCache;
 import net.sf.openrocket.optimization.general.Point;
@@ -22,7 +23,7 @@ public class TestFunctionOptimizer {
        
 
        private void go(final ParallelFunctionCache functionCache,
-                       final FunctionOptimizer optimizer, final Point optimum, final int maxSteps) {
+                       final FunctionOptimizer optimizer, final Point optimum, final int maxSteps) throws OptimizationException {
                
                Function function = new Function() {
                        @Override
@@ -35,15 +36,6 @@ public class TestFunctionOptimizer {
                                        return Double.NaN;
                                }
                        }
-                       
-                       @Override
-                       public double preComputed(Point p) {
-                               for (double d : p.asArray()) {
-                                       if (d < 0 || d > 1)
-                                               return Double.MAX_VALUE;
-                               }
-                               return Double.NaN;
-                       }
                };
                
                OptimizationController control = new OptimizationController() {
@@ -83,7 +75,7 @@ public class TestFunctionOptimizer {
        }
        
        
-       public static void main(String[] args) throws InterruptedException {
+       public static void main(String[] args) throws InterruptedException, OptimizationException {
                
                System.err.println("Number of processors: " + Runtime.getRuntime().availableProcessors());
                
index b60bb3d83eaae9ccb3fe1e644d806054fb28e57b..e09a0ee85f21ff52966a27f27e3b6236293e1446 100644 (file)
@@ -10,6 +10,7 @@ import java.util.concurrent.TimeUnit;
 import net.sf.openrocket.optimization.general.Function;
 import net.sf.openrocket.optimization.general.FunctionOptimizer;
 import net.sf.openrocket.optimization.general.OptimizationController;
+import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.general.ParallelExecutorCache;
 import net.sf.openrocket.optimization.general.Point;
 import net.sf.openrocket.optimization.general.multidim.MultidirectionalSearchOptimizer;
@@ -26,7 +27,7 @@ public class TestFunctionOptimizerLoop {
        
        
 
-       private void go(final FunctionOptimizer optimizer, final Point optimum, final int maxSteps, ExecutorService executor) {
+       private void go(final FunctionOptimizer optimizer, final Point optimum, final int maxSteps, ExecutorService executor) throws OptimizationException {
                
                Function function = new Function() {
                        @Override
@@ -34,15 +35,6 @@ public class TestFunctionOptimizerLoop {
                                evaluations++;
                                return p.sub(optimum).length2();
                        }
-                       
-                       @Override
-                       public double preComputed(Point p) {
-                               for (double d : p.asArray()) {
-                                       if (d < 0 || d > 1)
-                                               return Double.MAX_VALUE;
-                               }
-                               return Double.NaN;
-                       }
                };
                
                OptimizationController control = new OptimizationController() {
@@ -67,7 +59,7 @@ public class TestFunctionOptimizerLoop {
        }
        
        
-       public static void main(String[] args) {
+       public static void main(String[] args) throws OptimizationException {
                
                System.err.println("PRECISION = " + PRECISION);
                
diff --git a/test/net/sf/openrocket/optimization/rocketoptimization/TestRocketOptimizationFunction.java b/test/net/sf/openrocket/optimization/rocketoptimization/TestRocketOptimizationFunction.java
new file mode 100644 (file)
index 0000000..53ee37d
--- /dev/null
@@ -0,0 +1,214 @@
+package net.sf.openrocket.optimization.rocketoptimization;
+
+import static org.junit.Assert.*;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.general.Point;
+import net.sf.openrocket.rocketcomponent.Rocket;
+
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.auto.Mock;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(JMock.class)
+public class TestRocketOptimizationFunction {
+       Mockery context = new JUnit4Mockery();
+       
+       @Mock
+       OptimizableParameter parameter;
+       @Mock
+       OptimizationGoal goal;
+       @Mock
+       SimulationDomain domain;
+       @Mock
+       SimulationModifier modifier1;
+       @Mock
+       SimulationModifier modifier2;
+       
+       
+       @Test
+       public void testNormalEvaluation() throws InterruptedException, OptimizationException {
+               final Rocket rocket = new Rocket();
+               final Simulation simulation = new Simulation(rocket);
+               
+               final double p1 = 0.4;
+               final double p2 = 0.7;
+               final double ddist = -0.43;
+               final double pvalue = 9.81;
+               final double gvalue = 8.81;
+               
+               // @formatter:off
+               context.checking(new Expectations() {{
+                               oneOf(modifier1).modify(simulation, p1);
+                               oneOf(modifier2).modify(simulation, p2);
+                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(ddist));
+                               oneOf(parameter).computeValue(simulation); will(returnValue(pvalue));
+                               oneOf(goal).getMinimizationParameter(pvalue); will(returnValue(gvalue));
+               }});
+               // @formatter:on
+               
+
+               RocketOptimizationFunction function = new RocketOptimizationFunction(simulation,
+                               parameter, goal, domain, modifier1, modifier2) {
+                       @Override
+                       Simulation newSimulationInstance(Simulation sim) {
+                               return sim;
+                       }
+               };
+               
+
+               assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
+               
+               double value = function.evaluate(new Point(p1, p2));
+               assertEquals(gvalue, value, 0);
+               
+               assertEquals(pvalue, function.getComputedParameterValue(new Point(p1, p2)), 0);
+               
+               // Re-evaluate the point to verify parameter is not recomputed
+               value = function.evaluate(new Point(p1, p2));
+               assertEquals(gvalue, value, 0);
+       }
+       
+       
+       @Test
+       public void testNaNValue() throws InterruptedException, OptimizationException {
+               final Rocket rocket = new Rocket();
+               final Simulation simulation = new Simulation(rocket);
+               
+               final double p1 = 0.4;
+               final double p2 = 0.7;
+               final double ddist = -0.43;
+               final double pvalue = 9.81;
+               
+               // @formatter:off
+               context.checking(new Expectations() {{
+                               oneOf(modifier1).modify(simulation, p1);
+                               oneOf(modifier2).modify(simulation, p2);
+                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(ddist));
+                               oneOf(parameter).computeValue(simulation); will(returnValue(pvalue));
+                               oneOf(goal).getMinimizationParameter(pvalue); will(returnValue(Double.NaN));
+               }});
+               // @formatter:on
+               
+
+               RocketOptimizationFunction function = new RocketOptimizationFunction(simulation,
+                               parameter, goal, domain, modifier1, modifier2) {
+                       @Override
+                       Simulation newSimulationInstance(Simulation sim) {
+                               return sim;
+                       }
+               };
+               
+
+               assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
+               
+               double value = function.evaluate(new Point(p1, p2));
+               assertEquals(Double.MAX_VALUE, value, 0);
+               
+               assertEquals(pvalue, function.getComputedParameterValue(new Point(p1, p2)), 0);
+               
+               value = function.evaluate(new Point(p1, p2));
+               assertEquals(Double.MAX_VALUE, value, 0);
+       }
+       
+       
+       @Test
+       public void testOutsideDomain() throws InterruptedException, OptimizationException {
+               final Rocket rocket = new Rocket();
+               final Simulation simulation = new Simulation(rocket);
+               
+               final double p1 = 0.4;
+               final double p2 = 0.7;
+               final double ddist = 0.98;
+               
+               // @formatter:off
+               context.checking(new Expectations() {{
+                               oneOf(modifier1).modify(simulation, p1);
+                               oneOf(modifier2).modify(simulation, p2);
+                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(ddist));
+               }});
+               // @formatter:on
+               
+
+               RocketOptimizationFunction function = new RocketOptimizationFunction(simulation,
+                               parameter, goal, domain, modifier1, modifier2) {
+                       @Override
+                       Simulation newSimulationInstance(Simulation sim) {
+                               return sim;
+                       }
+               };
+               
+
+               assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
+               
+               double value = function.evaluate(new Point(p1, p2));
+               assertTrue(value > 1e100);
+               
+               assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
+               
+               value = function.evaluate(new Point(p1, p2));
+               assertTrue(value > 1e100);
+       }
+       
+       @Test
+       public void testOutsideDomain2() throws InterruptedException, OptimizationException {
+               final Rocket rocket = new Rocket();
+               final Simulation simulation = new Simulation(rocket);
+               
+               final double p1 = 0.4;
+               final double p2 = 0.7;
+               final double ddist = Double.NaN;
+               
+               // @formatter:off
+               context.checking(new Expectations() {{
+                               oneOf(modifier1).modify(simulation, p1);
+                               oneOf(modifier2).modify(simulation, p2);
+                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(ddist));
+               }});
+               // @formatter:on
+               
+
+               RocketOptimizationFunction function = new RocketOptimizationFunction(simulation,
+                               parameter, goal, domain, modifier1, modifier2) {
+                       @Override
+                       Simulation newSimulationInstance(Simulation sim) {
+                               return sim;
+                       }
+               };
+               
+
+               assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
+               
+               double value = function.evaluate(new Point(p1, p2));
+               assertEquals(Double.MAX_VALUE, value, 0);
+               
+               assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
+               
+               value = function.evaluate(new Point(p1, p2));
+               assertEquals(Double.MAX_VALUE, value, 0);
+       }
+       
+       
+       @Test
+       public void testNewSimulationInstance() {
+               final Rocket rocket = new Rocket();
+               rocket.setName("Foobar");
+               final Simulation simulation = new Simulation(rocket);
+               simulation.setName("MySim");
+               
+               RocketOptimizationFunction function = new RocketOptimizationFunction(simulation,
+                               parameter, goal, domain, modifier1, modifier2);
+               
+               Simulation sim = function.newSimulationInstance(simulation);
+               assertFalse(simulation == sim);
+               assertEquals("MySim", sim.getName());
+               assertFalse(rocket == sim.getRocket());
+               assertEquals("Foobar", sim.getRocket().getName());
+       }
+       
+}
diff --git a/test/net/sf/openrocket/util/TestMutex.java b/test/net/sf/openrocket/util/TestMutex.java
new file mode 100644 (file)
index 0000000..dd01848
--- /dev/null
@@ -0,0 +1,156 @@
+package net.sf.openrocket.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestMutex {
+       
+       @Test
+       public void testSingleLocking() {
+               SafetyMutex m = new SafetyMutex();
+               
+               // Test single locking
+               assertNull(m.lockingThread);
+               m.verify();
+               m.lock("here");
+               assertNotNull(m.lockingThread);
+               assertTrue(m.unlock("here"));
+               
+       }
+       
+       @Test
+       public void testDoubleLocking() {
+               SafetyMutex m = new SafetyMutex();
+               
+               // Test double locking
+               m.verify();
+               m.lock("foobar");
+               m.verify();
+               m.lock("bazqux");
+               m.verify();
+               assertTrue(m.unlock("bazqux"));
+               m.verify();
+               assertTrue(m.unlock("foobar"));
+               m.verify();
+       }
+       
+       @Test
+       public void testDoubleUnlocking() {
+               SafetyMutex m = new SafetyMutex();
+               // Mark error reported to not init exception handler
+               SafetyMutex.errorReported = true;
+               
+               m.lock("here");
+               assertTrue(m.unlock("here"));
+               assertFalse(m.unlock("here"));
+       }
+       
+       
+
+       private volatile int testState = 0;
+       private volatile String failure = null;
+       
+       @Test(timeout = 1000)
+       public void testThreadingErrors() {
+               final SafetyMutex m = new SafetyMutex();
+               
+               // Initialize and start the thread
+               Thread thread = new Thread() {
+                       @Override
+                       public void run() {
+                               try {
+                                       
+                                       // Test locking a locked mutex
+                                       waitFor(1);
+                                       try {
+                                               m.lock("in thread one");
+                                               failure = "Succeeded in locking a mutex locked by a different thread";
+                                               return;
+                                       } catch (ConcurrencyException e) {
+                                               // OK
+                                       }
+                                       
+                                       // Test unlocking a mutex locked by a different thread
+                                       if (m.unlock("in thread two")) {
+                                               failure = "Succeeded in unlocking a mutex locked by a different thread";
+                                               return;
+                                       }
+                                       
+                                       // Test verifying a locked mutex that already has an error
+                                       try {
+                                               m.verify();
+                                               failure = "Succeeded in verifying a mutex locked by a different thread";
+                                               return;
+                                       } catch (ConcurrencyException e) {
+                                               // OK
+                                       }
+                                       
+                                       // Test locking a mutex after it's been unlocked
+                                       testState = 2;
+                                       waitFor(3);
+                                       m.lock("in thread three");
+                                       m.verify();
+                                       
+                                       // Wait for other side to test
+                                       testState = 4;
+                                       waitFor(5);
+                                       
+                                       // Exit code
+                                       testState = 6;
+                                       
+                               } catch (Exception e) {
+                                       failure = "Exception occurred in thread: " + e;
+                                       return;
+                               }
+                               
+                       }
+               };
+               thread.setDaemon(true);
+               thread.start();
+               
+               m.lock("one");
+               testState = 1;
+               
+               waitFor(2);
+               assertNull("Thread error: " + failure, failure);
+               
+               m.verify();
+               m.unlock("one");
+               testState = 3;
+               
+               waitFor(4);
+               assertNull("Thread error: " + failure, failure);
+               
+               try {
+                       m.lock("two");
+                       fail("Succeeded in locking a locked mutex in main thread");
+               } catch (ConcurrencyException e) {
+                       // OK
+               }
+               
+               // Test unlocking a mutex locked by a different thread
+               assertFalse(m.unlock("here"));
+               
+               try {
+                       m.verify();
+                       fail("Succeeded in verifying a locked mutex in main thread");
+               } catch (ConcurrencyException e) {
+                       // OK
+               }
+               
+               testState = 5;
+               waitFor(6);
+               assertNull("Thread error: " + failure, failure);
+       }
+       
+       private void waitFor(int state) {
+               while (testState != state && failure == null) {
+                       try {
+                               Thread.sleep(1);
+                       } catch (InterruptedException e) {
+                       }
+               }
+       }
+       
+}