From: plaa Date: Mon, 25 Oct 2010 19:02:31 +0000 (+0000) Subject: SafetyMutex and rocket optimization updates X-Git-Tag: upstream/1.1.4^2~11 X-Git-Url: https://git.gag.com/?a=commitdiff_plain;h=90e98a95b548c88efd882e8e74bc4b36dd9af8b7;hp=e86c584b8adccd32ff6b1536ffdb3ff408970f9d;p=debian%2Fopenrocket SafetyMutex and rocket optimization updates git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@93 180e2498-e6e9-4542-8430-84ac67f01cd8 --- diff --git a/.classpath b/.classpath index c71fbb9c..31166a94 100644 --- a/.classpath +++ b/.classpath @@ -10,7 +10,6 @@ - @@ -18,5 +17,10 @@ + + + + + diff --git a/ChangeLog b/ChangeLog index 0f4e934f..efbb9793 100644 --- 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 diff --git a/build.xml b/build.xml index 6262bc0d..0540b3dc 100644 --- a/build.xml +++ b/build.xml @@ -36,8 +36,11 @@ - - + + + + + diff --git a/doc/properties.txt b/doc/properties.txt index ed069041..cbddc55f 100644 --- a/doc/properties.txt +++ b/doc/properties.txt @@ -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 index 00000000..1195cb7f 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 index 00000000..8e6568b5 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 index 00000000..a846450f 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 index 00000000..129e5613 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 index 700ad695..00000000 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 index 00000000..f28b4ef0 Binary files /dev/null and b/lib-test/junit-dep-4.8.2.jar differ diff --git a/src/net/sf/openrocket/document/Simulation.java b/src/net/sf/openrocket/document/Simulation.java index db462aad..9d5d0cd3 100644 --- a/src/net/sf/openrocket/document/Simulation.java +++ b/src/net/sf/openrocket/document/Simulation.java @@ -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. + *

+ * This class is not thread-safe and enforces single-threaded access with a + * SafetyMutex. + * + * @author Sampo Niskanen + */ 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 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 null. */ 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 null. */ 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) 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) 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); } diff --git a/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java b/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java index c67568fc..927cc548 100644 --- a/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java @@ -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 " + diff --git a/src/net/sf/openrocket/gui/dialogs/DetailDialog.java b/src/net/sf/openrocket/gui/dialogs/DetailDialog.java index 05a19950..87e36e67 100644 --- a/src/net/sf/openrocket/gui/dialogs/DetailDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/DetailDialog.java @@ -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); + } } - + } diff --git a/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java b/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java index 39f9d2b2..ed55079f 100644 --- a/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java @@ -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 optimizationParameters = new ArrayList(); + private final List optimizationParameters = new ArrayList(); private final Map> simulationModifiers = new HashMap>(); @@ -36,10 +36,10 @@ public class GeneralOptimizationDialog extends JDialog { private void loadOptimizationParameters() { - ServiceLoader loader = - ServiceLoader.load(RocketOptimizationParameterService.class); + ServiceLoader 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() { + Collections.sort(optimizationParameters, new Comparator() { @Override - public int compare(RocketOptimizationParameter o1, RocketOptimizationParameter o2) { + public int compare(OptimizableParameter o1, OptimizableParameter o2) { return o1.getName().compareTo(o2.getName()); } }); diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java index 4031be8a..4f1561ce 100644 --- a/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -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 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 data = new LinkedList(); + 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. * diff --git a/src/net/sf/openrocket/gui/main/ExceptionHandler.java b/src/net/sf/openrocket/gui/main/ExceptionHandler.java index 384aee18..9fbc9a84 100644 --- a/src/net/sf/openrocket/gui/main/ExceptionHandler.java +++ b/src/net/sf/openrocket/gui/main/ExceptionHandler.java @@ -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. + *

+ * 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. + *

+ * 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. + *

+ * 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; } diff --git a/src/net/sf/openrocket/gui/main/SimulationPanel.java b/src/net/sf/openrocket/gui/main/SimulationPanel.java index 5fd4e1a9..644605db 100644 --- a/src/net/sf/openrocket/gui/main/SimulationPanel.java +++ b/src/net/sf/openrocket/gui/main/SimulationPanel.java @@ -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)); diff --git a/src/net/sf/openrocket/gui/main/SimulationRunDialog.java b/src/net/sf/openrocket/gui/main/SimulationRunDialog.java index c37ceef2..9b94ec61 100644 --- a/src/net/sf/openrocket/gui/main/SimulationRunDialog.java +++ b/src/net/sf/openrocket/gui/main/SimulationRunDialog.java @@ -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(""); diff --git a/src/net/sf/openrocket/gui/main/SimulationWorker.java b/src/net/sf/openrocket/gui/main/SimulationWorker.java index 9e6f62bc..f348fbcd 100644 --- a/src/net/sf/openrocket/gui/main/SimulationWorker.java +++ b/src/net/sf/openrocket/gui/main/SimulationWorker.java @@ -54,8 +54,6 @@ public abstract class SimulationWorker extends SwingWorkermessage the String message (may be null). *

  • cause the exception that caused this log (may be null). * + *

    + * The logging methods are guaranteed never to throw an exception, and can thus be safely + * used in finally blocks. * * @author Sampo Niskanen */ @@ -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(); + } } diff --git a/src/net/sf/openrocket/logging/LogLevel.java b/src/net/sf/openrocket/logging/LogLevel.java index 74235b36..b52d1453 100644 --- a/src/net/sf/openrocket/logging/LogLevel.java +++ b/src/net/sf/openrocket/logging/LogLevel.java @@ -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]; } /** diff --git a/src/net/sf/openrocket/logging/LogLevelBufferLogger.java b/src/net/sf/openrocket/logging/LogLevelBufferLogger.java index 3b2a2c43..d9021065 100644 --- a/src/net/sf/openrocket/logging/LogLevelBufferLogger.java +++ b/src/net/sf/openrocket/logging/LogLevelBufferLogger.java @@ -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); diff --git a/src/net/sf/openrocket/logging/LogLine.java b/src/net/sf/openrocket/logging/LogLine.java index 71b92d48..c2dc4fa6 100644 --- a/src/net/sf/openrocket/logging/LogLine.java +++ b/src/net/sf/openrocket/logging/LogLine.java @@ -31,13 +31,32 @@ public class LogLine implements Comparable { 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, null permitted + * @param message the log message + * @param cause the causing throwable, null 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, null permitted + * @param message the log message + * @param cause the causing throwable, null 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 { 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 { 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 { } return formattedMessage; } - - + + /** * Compare against another log line based on the log line count number. */ diff --git a/src/net/sf/openrocket/logging/TraceException.java b/src/net/sf/openrocket/logging/TraceException.java index 8468a1ed..b08109a3 100644 --- a/src/net/sf/openrocket/logging/TraceException.java +++ b/src/net/sf/openrocket/logging/TraceException.java @@ -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. */ diff --git a/src/net/sf/openrocket/optimization/general/Function.java b/src/net/sf/openrocket/optimization/general/Function.java index 97404e2a..a7be5668 100644 --- a/src/net/sf/openrocket/optimization/general/Function.java +++ b/src/net/sf/openrocket/optimization/general/Function.java @@ -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). - *

    - * Note that it is allowed to always allowed to return Double.NaN, especially - * for functions that are fast to evaluate. - * - * @param point the point of function evaluation. - * @return the function value, or Double.NaN if the function value has not been - * evaluated at this point. - */ - public double preComputed(Point point); + public double evaluate(Point point) throws InterruptedException, OptimizationException; } diff --git a/src/net/sf/openrocket/optimization/general/FunctionCache.java b/src/net/sf/openrocket/optimization/general/FunctionCache.java index c280fbac..69c42d79 100644 --- a/src/net/sf/openrocket/optimization/general/FunctionCache.java +++ b/src/net/sf/openrocket/optimization/general/FunctionCache.java @@ -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 */ 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); } diff --git a/src/net/sf/openrocket/optimization/general/FunctionOptimizer.java b/src/net/sf/openrocket/optimization/general/FunctionOptimizer.java index 2caf7fa9..485843f9 100644 --- a/src/net/sf/openrocket/optimization/general/FunctionOptimizer.java +++ b/src/net/sf/openrocket/optimization/general/FunctionOptimizer.java @@ -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 index 00000000..7c0c0674 --- /dev/null +++ b/src/net/sf/openrocket/optimization/general/OptimizationException.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.optimization.general; + +/** + * An exception that prevents optimization from continuing. + * + * @author Sampo Niskanen + */ +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); + } + +} diff --git a/src/net/sf/openrocket/optimization/general/ParallelExecutorCache.java b/src/net/sf/openrocket/optimization/general/ParallelExecutorCache.java index 4f84dccb..78078600 100644 --- a/src/net/sf/openrocket/optimization/general/ParallelExecutorCache.java +++ b/src/net/sf/openrocket/optimization/general/ParallelExecutorCache.java @@ -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. + *

    + * 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 */ @@ -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(), 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 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 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 points) throws InterruptedException { + @Override + public void waitFor(Collection 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 abort(Collection points) { List computed = new ArrayList(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 true if the point has been computed anyway, false 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); } } diff --git a/src/net/sf/openrocket/optimization/general/ParallelFunctionCache.java b/src/net/sf/openrocket/optimization/general/ParallelFunctionCache.java index 9b4a0eba..c5b34a52 100644 --- a/src/net/sf/openrocket/optimization/general/ParallelFunctionCache.java +++ b/src/net/sf/openrocket/optimization/general/ParallelFunctionCache.java @@ -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 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 points) throws InterruptedException; + public void waitFor(Collection 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. diff --git a/src/net/sf/openrocket/optimization/general/multidim/MultidirectionalSearchOptimizer.java b/src/net/sf/openrocket/optimization/general/multidim/MultidirectionalSearchOptimizer.java index 80a194b8..0e70bf13 100644 --- a/src/net/sf/openrocket/optimization/general/multidim/MultidirectionalSearchOptimizer.java +++ b/src/net/sf/openrocket/optimization/general/multidim/MultidirectionalSearchOptimizer.java @@ -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 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 index 00000000..e8506e8d --- /dev/null +++ b/src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameter.java @@ -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 + */ +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. + *

    + * 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 index 00000000..b227917e --- /dev/null +++ b/src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameterService.java @@ -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 + */ +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 getParameters(OpenRocketDocument document); + +} diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationFunction.java b/src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationFunction.java index c975a8a4..a9fb500f 100644 --- a/src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationFunction.java +++ b/src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationFunction.java @@ -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 parameterValueCache = new ConcurrentHashMap(); @@ -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. + *

    + * 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 index 61b1a801..00000000 --- a/src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationParameter.java +++ /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 - */ -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 index ecff025b..00000000 --- a/src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationParameterService.java +++ /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 - */ -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 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 index 00000000..b03be658 --- /dev/null +++ b/src/net/sf/openrocket/optimization/rocketoptimization/SimulationDomain.java @@ -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 + */ +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); + +} diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/SimulationModifier.java b/src/net/sf/openrocket/optimization/rocketoptimization/SimulationModifier.java index a22507d0..147ac0d4 100644 --- a/src/net/sf/openrocket/optimization/rocketoptimization/SimulationModifier.java +++ b/src/net/sf/openrocket/optimization/rocketoptimization/SimulationModifier.java @@ -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 index 00000000..ac62dc62 --- /dev/null +++ b/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericModifier.java @@ -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 index 00000000..d8a2543c --- /dev/null +++ b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAltitudeParameter.java @@ -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 + */ +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); + } + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/Rocket.java b/src/net/sf/openrocket/rocketcomponent/Rocket.java index a347bc07..7ba088b9 100644 --- a/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -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 iterator = this.deepIterator(true); while (iterator.hasNext()) { @@ -441,6 +440,10 @@ public class Rocket extends RocketComponent { checkState(); if (freezeList == null) { freezeList = new LinkedList(); + 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) { diff --git a/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 2e0a784c..34ae962f 100644 --- a/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -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 { + 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); diff --git a/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index ef80f497..655cdb52 100644 --- a/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -41,6 +41,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { private SimulationStatus status; + @Override public FlightData simulate(SimulationConditions simulationConditions) throws SimulationException { Set motorBurntOut = new HashSet(); @@ -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."); } } diff --git a/src/net/sf/openrocket/startup/Startup.java b/src/net/sf/openrocket/startup/Startup.java index 2d2d492e..658f70c0 100644 --- a/src/net/sf/openrocket/startup/Startup.java +++ b/src/net/sf/openrocket/startup/Startup.java @@ -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 index 00000000..252267c8 --- /dev/null +++ b/src/net/sf/openrocket/util/ConcurrencyException.java @@ -0,0 +1,26 @@ +package net.sf.openrocket.util; + +/** + * An exception that indicates a concurrency bug in the software. + * + * @author Sampo Niskanen + */ +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); + } + +} diff --git a/src/net/sf/openrocket/util/GUIUtil.java b/src/net/sf/openrocket/util/GUIUtil.java index 54047e35..ff97d9a8 100644 --- a/src/net/sf/openrocket/util/GUIUtil.java +++ b/src/net/sf/openrocket/util/GUIUtil.java @@ -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 index 00000000..34589b58 --- /dev/null +++ b/src/net/sf/openrocket/util/MemoryManagement.java @@ -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 + */ +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 objects = new LinkedList(); + 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 System.gc() 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 getRemainingObjects() { + for (int i = 0; i < 5; i++) { + System.runFinalization(); + System.gc(); + try { + Thread.sleep(1); + } catch (InterruptedException e) { + } + } + purge(); + return new ArrayList(objects); + } + + + + /** + * Purge all cleared references from the object list. + */ + private static void purge() { + int origCount = objects.size(); + Iterator 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 reference; + private final long registrationTime; + + private MemoryData(Object object) { + this.reference = new WeakReference(object); + this.registrationTime = System.currentTimeMillis(); + } + + /** + * Return the weak reference to the discarded object. + */ + public WeakReference 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 index 00000000..fb202a91 --- /dev/null +++ b/src/net/sf/openrocket/util/SafetyMutex.java @@ -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. + *

    + * This mutex is not reentrant even for the same thread that has locked it. + * + * @author Sampo Niskanen + */ +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 locations = new LinkedList(); + + + /** + * Verify that this mutex is unlocked, but don't lock it. This has the same effect + * as mutex.lock(); mutex.unlock(); 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. + *

    + * 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; + } + } + +} diff --git a/src/net/sf/openrocket/utils/TestFunctionOptimizer.java b/src/net/sf/openrocket/utils/TestFunctionOptimizer.java index a57ea78a..e5f9da71 100644 --- a/src/net/sf/openrocket/utils/TestFunctionOptimizer.java +++ b/src/net/sf/openrocket/utils/TestFunctionOptimizer.java @@ -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()); diff --git a/src/net/sf/openrocket/utils/TestFunctionOptimizerLoop.java b/src/net/sf/openrocket/utils/TestFunctionOptimizerLoop.java index b60bb3d8..e09a0ee8 100644 --- a/src/net/sf/openrocket/utils/TestFunctionOptimizerLoop.java +++ b/src/net/sf/openrocket/utils/TestFunctionOptimizerLoop.java @@ -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 index 00000000..53ee37de --- /dev/null +++ b/test/net/sf/openrocket/optimization/rocketoptimization/TestRocketOptimizationFunction.java @@ -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 index 00000000..dd01848d --- /dev/null +++ b/test/net/sf/openrocket/util/TestMutex.java @@ -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) { + } + } + } + +}