</accessrules>
</classpathentry>
<classpathentry kind="lib" path="lib-extra/RXTXcomm.jar"/>
- <classpathentry kind="lib" path="lib-test/junit-4.7.jar"/>
<classpathentry kind="lib" path="lib/jfreechart-1.0.13.jar" sourcepath="/home/sampo/Projects/lib/jfreechart-1.0.13/source"/>
<classpathentry kind="lib" path="lib/jcommon-1.0.16.jar">
<accessrules>
</accessrules>
</classpathentry>
<classpathentry kind="lib" path="lib/miglayout15-swing.jar"/>
+ <classpathentry kind="lib" path="lib-test/hamcrest-core-1.3.0RC1.jar"/>
+ <classpathentry kind="lib" path="lib-test/hamcrest-library-1.3.0RC1.jar"/>
+ <classpathentry kind="lib" path="lib-test/jmock-2.6.0-RC2.jar"/>
+ <classpathentry kind="lib" path="lib-test/jmock-junit4-2.6.0-RC2.jar"/>
+ <classpathentry kind="lib" path="lib-test/junit-dep-4.8.2.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+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
<pathelement location="${build-test.dir}"/>
<pathelement location="${classes.dir}"/>
<pathelement location="${src-test.dir}"/>
-<!-- <pathelement location="${ant.library.dir}/junit4.jar"/> -->
- <pathelement location="lib-test/junit-4.7.jar"/>
+ <pathelement location="lib-test/junit-dep-4.8.2.jar"/>
+ <pathelement location="lib-test/hamcrest-core-1.3.0RC1.jar"/>
+ <pathelement location="lib-test/hamcrest-library-1.3.0RC1.jar"/>
+ <pathelement location="lib-test/jmock-2.6.0-RC2.jar"/>
+ <pathelement location="lib-test/jmock-junit4-2.6.0-RC2.jar"/>
</path>
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.
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.ChangeSource;
+import net.sf.openrocket.util.SafetyMutex;
-
+/**
+ * A class defining a simulation, its conditions and simulated data.
+ * <p>
+ * This class is not thread-safe and enforces single-threaded access with a
+ * SafetyMutex.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
public class Simulation implements ChangeSource, Cloneable {
private static final LogHelper log = Application.getLogger();
NOT_SIMULATED
}
+ private final SafetyMutex mutex = new SafetyMutex();
private final Rocket rocket;
* @return the rocket.
*/
public Rocket getRocket() {
+ mutex.verify();
return rocket;
}
* @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();
* @return the simulation conditions.
*/
public GUISimulationConditions getConditions() {
+ mutex.verify();
return conditions;
}
* @return the actual list of simulation listeners.
*/
public List<String> getSimulationListeners() {
+ mutex.verify();
return simulationListeners;
}
* @return the name for the simulation.
*/
public String getName() {
+ mutex.verify();
return name;
}
* @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");
+ }
}
* @see Status
*/
public Status getStatus() {
+ mutex.verify();
+
if (status == Status.UPTODATE || status == Status.LOADED) {
if (rocket.getFunctionalModID() != simulatedRocketID ||
!conditions.equals(simulatedConditions))
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();
}
* @return the conditions used in the previous simulation, or <code>null</code>.
*/
public GUISimulationConditions getSimulatedConditions() {
+ mutex.verify();
return simulatedConditions;
}
* @see FlightData#getWarningSet()
*/
public WarningSet getSimulatedWarnings() {
+ mutex.verify();
if (simulatedData == null)
return null;
return simulatedData.getWarningSet();
* @see Rocket#getMotorConfigurationNameOrDescription(String)
*/
public String getSimulatedMotorDescription() {
+ mutex.verify();
return simulatedMotors;
}
* @return the flight data of the previous simulation, or <code>null</code>.
*/
public FlightData getSimulatedData() {
+ mutex.verify();
return simulatedData;
}
*/
@SuppressWarnings("unchecked")
public Simulation copy() {
+ mutex.lock("copy");
try {
Simulation copy = (Simulation) super.clone();
return copy;
-
} catch (CloneNotSupportedException e) {
throw new BugException("Clone not supported, BUG", e);
+ } finally {
+ mutex.unlock("copy");
}
}
*/
@SuppressWarnings("unchecked")
public Simulation duplicateSimulation(Rocket newRocket) {
- Simulation copy = new Simulation(newRocket);
-
- copy.name = this.name;
- copy.conditions.copyFrom(this.conditions);
- copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
- copy.simulationStepperClass = this.simulationStepperClass;
- copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
-
- return copy;
+ mutex.lock("duplicateSimulation");
+ try {
+ Simulation copy = new Simulation(newRocket);
+
+ copy.name = this.name;
+ copy.conditions.copyFrom(this.conditions);
+ copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
+ copy.simulationStepperClass = this.simulationStepperClass;
+ copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
+
+ return copy;
+ } finally {
+ mutex.unlock("duplicateSimulation");
+ }
}
@Override
public void addChangeListener(ChangeListener listener) {
+ mutex.verify();
listeners.add(listener);
}
@Override
public void removeChangeListener(ChangeListener listener) {
+ mutex.verify();
listeners.remove(listener);
}
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) {
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 " +
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);
+ }
}
-
+
}
import javax.swing.JDialog;
import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationParameter;
-import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationParameterService;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameterService;
import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
import net.sf.openrocket.optimization.rocketoptimization.SimulationModifierService;
import net.sf.openrocket.util.BugException;
public class GeneralOptimizationDialog extends JDialog {
- private final List<RocketOptimizationParameter> optimizationParameters = new ArrayList<RocketOptimizationParameter>();
+ private final List<OptimizableParameter> optimizationParameters = new ArrayList<OptimizableParameter>();
private final Map<Object, List<SimulationModifier>> simulationModifiers =
new HashMap<Object, List<SimulationModifier>>();
private void loadOptimizationParameters() {
- ServiceLoader<RocketOptimizationParameterService> loader =
- ServiceLoader.load(RocketOptimizationParameterService.class);
+ ServiceLoader<OptimizableParameterService> loader =
+ ServiceLoader.load(OptimizableParameterService.class);
- for (RocketOptimizationParameterService g : loader) {
+ for (OptimizableParameterService g : loader) {
optimizationParameters.addAll(g.getParameters(document));
}
throw new BugException("No rocket optimization parameters found, distribution built wrong.");
}
- Collections.sort(optimizationParameters, new Comparator<RocketOptimizationParameter>() {
+ Collections.sort(optimizationParameters, new Comparator<OptimizableParameter>() {
@Override
- public int compare(RocketOptimizationParameter o1, RocketOptimizationParameter o2) {
+ public int compare(OptimizableParameter o1, OptimizableParameter o2) {
return o1.getName().compareTo(o2.getName());
}
});
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;
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;
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;
});
menu.add(item);
+ menu.addSeparator();
+
+
+ item = new JMenuItem("Memory statistics");
+ item.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Memory statistics selected");
+
+ // Get discarded but remaining objects (this also runs System.gc multiple times)
+ List<MemoryData> objects = MemoryManagement.getRemainingObjects();
+ StringBuilder sb = new StringBuilder();
+ sb.append("Objects that should have been garbage-collected but have not been:\n");
+ int count = 0;
+ for (MemoryData data : objects) {
+ Object o = data.getReference().get();
+ if (o == null)
+ continue;
+ sb.append("Age ").append(System.currentTimeMillis() - data.getRegistrationTime())
+ .append(" ms: ").append(o).append('\n');
+ count++;
+ // Explicitly null the strong reference to avoid possibility of invisible references
+ o = null;
+ }
+ sb.append("Total: " + count);
+
+ // Get basic memory stats
+ System.gc();
+ long max = Runtime.getRuntime().maxMemory();
+ long free = Runtime.getRuntime().freeMemory();
+ long used = max - free;
+ String[] stats = new String[4];
+ stats[0] = "Memory usage:";
+ stats[1] = String.format(" Max memory: %.1f MB", max / 1024.0 / 1024.0);
+ stats[2] = String.format(" Used memory: %.1f MB (%.0f%%)", used / 1024.0 / 1024.0, 100.0 * used / max);
+ stats[3] = String.format(" Free memory: %.1f MB (%.0f%%)", free / 1024.0 / 1024.0, 100.0 * free / max);
+
+
+ DetailDialog.showDetailedMessageDialog(BasicFrame.this, stats, sb.toString(),
+ "Memory statistics", JOptionPane.INFORMATION_MESSAGE);
+ }
+ });
+ menu.add(item);
+
+
+ item = new JMenuItem("Exhaust memory");
+ item.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Exhaust memory selected");
+ LinkedList<byte[]> data = new LinkedList<byte[]>();
+ int count = 0;
+ final int bytesPerArray = 10240;
+ try {
+ while (true) {
+ byte[] array = new byte[bytesPerArray];
+ for (int i = 0; i < bytesPerArray; i++) {
+ array[i] = (byte) i;
+ }
+ data.add(array);
+ count++;
+ }
+ } catch (OutOfMemoryError error) {
+ data = null;
+ long size = bytesPerArray * (long) count;
+ String s = String.format("OutOfMemory occurred after %d iterations (approx. %.1f MB consumed)",
+ count, size / 1024.0 / 1024.0);
+ log.debug(s, error);
+ JOptionPane.showMessageDialog(BasicFrame.this, s);
+ }
+ }
+ });
+ menu.add(item);
+
+
menu.addSeparator();
item = new JMenuItem("Exception here");
item.addActionListener(new ActionListener() {
+ @Override
public void actionPerformed(ActionEvent e) {
log.user("Exception here selected");
throw new RuntimeException("Testing exception from menu action listener");
});
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.
*
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 {
} else {
log.info("Exception handler not on EDT, invoking dialog on EDT");
SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
public void run() {
showDialog(thread, throwable);
}
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 {
/**
* Handle an error condition programmatically without throwing an exception.
* This can be used in cases where recovery of the error is desirable.
+ * <p>
+ * This method is guaranteed never to throw an exception, and can thus be safely
+ * used in finally blocks.
*
* @param message the error message.
*/
public static void handleErrorCondition(String message) {
- log.error(1, message);
+ log.error(1, message, new TraceException());
handleErrorCondition(new InternalException(message));
}
/**
* Handle an error condition programmatically without throwing an exception.
* This can be used in cases where recovery of the error is desirable.
+ * <p>
+ * This method is guaranteed never to throw an exception, and can thus be safely
+ * used in finally blocks.
*
* @param message the error message.
* @param exception the exception that occurred.
/**
* Handle an error condition programmatically without throwing an exception.
* This can be used in cases where recovery of the error is desirable.
+ * <p>
+ * This method is guaranteed never to throw an exception, and can thus be safely
+ * used in finally blocks.
*
* @param exception the exception that occurred.
*/
public static void handleErrorCondition(final Exception exception) {
- if (!(exception instanceof InternalException)) {
- log.error(1, "Error occurred", exception);
- }
- final Thread thread = Thread.currentThread();
- final ExceptionHandler handler = instance;
-
- if (handler == null) {
- // Not initialized, throw the exception
- throw new BugException("Error condition before exception handling has been initialized", exception);
- }
-
try {
+ if (!(exception instanceof InternalException)) {
+ log.error(1, "Error occurred", exception);
+ }
+ final Thread thread = Thread.currentThread();
+ final ExceptionHandler handler = instance;
+
+ if (handler == null) {
+ log.error("Error condition occurred before exception handling has been initialized", exception);
+ return;
+ }
+
if (SwingUtilities.isEventDispatchThread()) {
log.info("Running in EDT, showing dialog");
handler.showDialog(thread, exception);
} else {
log.info("Not in EDT, invoking and waiting for dialog");
SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
public void run() {
handler.showDialog(thread, exception);
}
});
}
} catch (Exception e) {
- log.error("Exception occurred while showing error dialog", e);
- e.printStackTrace();
+ log.error("Exception occurred in error handler", e);
}
}
*/
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.
*/
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;
}
// 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));
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;
// Initialize the simulations
int n = simulations.length;
+ simulationNames = new String[n];
simulationWorkers = new SimulationWorker[n];
simulationStatuses = new SimulationStatus[n];
simulationMaxAltitude = new double[n];
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]);
}
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("");
try {
simulation.simulate(listeners);
} catch (Throwable e) {
- // System.out.println("Simulation interrupted:");
- // e.printStackTrace();
throwable = e;
return null;
}
/**
* Called after a simulation is successfully simulated. This method is not
* called if the simulation ends in an exception.
- *
- * @param sim the simulation including the flight data
*/
protected abstract void simulationDone();
@Override
protected void simulationDone() {
// Do nothing if cancelled
- if (isCancelled() || backgroundSimulationWorker != this) // Double-check
+ if (isCancelled() || backgroundSimulationWorker != this)
return;
backgroundSimulationWorker = null;
* Updates the selection in the FigureParameters and repaints the figure.
* Ignores the event itself.
*/
+ @Override
public void valueChanged(TreeSelectionEvent e) {
TreePath[] paths = selectionModel.getSelectionPaths();
if (paths == null) {
figure.addChangeListener(this);
}
+ @Override
public void actionPerformed(ActionEvent e) {
boolean state = (Boolean) getValue(Action.SELECTED_KEY);
if (state == true) {
stateChanged(null);
}
+ @Override
public void stateChanged(ChangeEvent e) {
putValue(Action.SELECTED_KEY, figure.getType() == type);
}
* <li><code>message</code> the String message (may be null).
* <li><code>cause</code> the exception that caused this log (may be null).
* </ul>
+ * <p>
+ * The logging methods are guaranteed never to throw an exception, and can thus be safely
+ * used in finally blocks.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
/**
* @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();
+ }
}
*/
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];
}
/**
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);
private volatile String formattedMessage = null;
-
+
+ /**
+ * Construct a LogLine at the current moment. The next log line count number is selected
+ * and the current run time set to the timestamp.
+ *
+ * @param level the logging level
+ * @param trace the trace exception for the log line, <code>null</code> permitted
+ * @param message the log message
+ * @param cause the causing throwable, <code>null</code> permitted
+ */
public LogLine(LogLevel level, TraceException trace, String message, Throwable cause) {
this(level, logCount.getAndIncrement(), System.currentTimeMillis() - startTime, trace, message, cause);
}
-
- public LogLine(LogLevel level, int count, long timestamp,
+ /**
+ * Construct a LogLine with all parameters. This should only be used in special conditions,
+ * for example to insert a log line at a specific point within normal logs.
+ *
+ * @param level the logging level
+ * @param count the log line count number
+ * @param timestamp the log line timestamp
+ * @param trace the trace exception for the log line, <code>null</code> permitted
+ * @param message the log message
+ * @param cause the causing throwable, <code>null</code> permitted
+ */
+ public LogLine(LogLevel level, int count, long timestamp,
TraceException trace, String message, Throwable cause) {
this.level = level;
this.count = count;
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;
}
-
-
+
+
/**
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) {
}
return formattedMessage;
}
-
-
+
+
/**
* Compare against another log line based on the log line count number.
*/
}
+ /**
+ * 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.
*/
* @param point the point at which to evaluate the function.
* @return the function value.
* @throws InterruptedException if the thread was interrupted before function evaluation was completed.
+ * @throws OptimizationException if an error occurs that prevents the optimization
*/
- public double evaluate(Point point) throws InterruptedException;
-
-
- /**
- * Return a cached value of the function at the specified point. This allows efficient
- * caching of old values even between calls to optimization methods. This method should
- * NOT evaluate the function except in special cases (e.g. the point is outside of the
- * function domain).
- * <p>
- * Note that it is allowed to always allowed to return <code>Double.NaN</code>, especially
- * for functions that are fast to evaluate.
- *
- * @param point the point of function evaluation.
- * @return the function value, or <code>Double.NaN</code> if the function value has not been
- * evaluated at this point.
- */
- public double preComputed(Point point);
+ public double evaluate(Point point) throws InterruptedException, OptimizationException;
}
/**
* A storage of cached values of a function. The purpose of this class is to
- * cache function values
+ * cache function values between optimization runs. Subinterfaces may provide
+ * additional functionality.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public interface FunctionCache {
+ /**
+ * Compute and return the value of the function at the specified point.
+ *
+ * @param point the point at which to evaluate.
+ * @return the value of the function at that point.
+ */
public double getValue(Point point);
+ /**
+ * Clear the cache.
+ */
public void clearCache();
+ /**
+ * Return the function that is evaluated by this cache implementation.
+ *
+ * @return the function that is being evaluated.
+ */
public Function getFunction();
+ /**
+ * Set the function that is evaluated by this cache implementation.
+ *
+ * @param function the function that is being evaluated.
+ */
public void setFunction(Function function);
}
*
* @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;
/**
--- /dev/null
+package net.sf.openrocket.optimization.general;
+
+/**
+ * An exception that prevents optimization from continuing.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class OptimizationException extends Exception {
+
+ public OptimizationException(String message) {
+ super(message);
+ }
+
+ public OptimizationException(Throwable cause) {
+ super(cause);
+ }
+
+ public OptimizationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import net.sf.openrocket.util.BugException;
+
/**
* An implementation of a ParallelFunctionCache that evaluates function values
* in parallel and caches them. This allows pre-calculating possibly required
* function values beforehand. If values are not required after all, the
* computation can be aborted assuming the function evaluation supports it.
+ * <p>
+ * Note that while this class handles threads and abstracts background execution,
+ * the public methods themselves are NOT thread-safe and should be called from
+ * only one thread at a time.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
private Function function;
-
+ /**
+ * Construct a cache that uses the same number of computational threads as there are
+ * processors available.
+ */
public ParallelExecutorCache() {
this(Runtime.getRuntime().availableProcessors());
}
+ /**
+ * Construct a cache that uses the specified number of computational threads for background
+ * computation. The threads that are created are marked as daemon threads.
+ *
+ * @param threadCount the number of threads to use in the executor.
+ */
public ParallelExecutorCache(int threadCount) {
- executor = new ThreadPoolExecutor(threadCount, threadCount, 60, TimeUnit.SECONDS,
+ this(new ThreadPoolExecutor(threadCount, threadCount, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
new ThreadFactory() {
@Override
t.setDaemon(true);
return t;
}
- });
+ }));
}
+ /**
+ * Construct a cache that uses the specified ExecutorService for managing
+ * computational threads.
+ *
+ * @param executor the executor to use for function evaluations.
+ */
public ParallelExecutorCache(ExecutorService executor) {
this.executor = executor;
}
- /**
- * Queue a list of function evaluations at the specified points.
- *
- * @param points the points at which to evaluate the function.
- */
+ @Override
public void compute(Collection<Point> points) {
for (Point p : points) {
compute(p);
}
- /**
- * 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
return;
}
- double value = function.preComputed(point);
- if (!Double.isNaN(value)) {
- // Function value was in function cache
- functionCache.put(point, value);
- return;
- }
-
// Submit point for evaluation
FunctionCallable callable = new FunctionCallable(function, point);
Future<Double> future = executor.submit(callable);
}
- /**
- * Wait for a collection of points to be computed. After calling this method
- * the function values are available by calling XXX
- *
- * @param points the points to wait for.
- * @throws InterruptedException if this thread was interrupted while waiting.
- */
- public void waitFor(Collection<Point> points) throws InterruptedException {
+ @Override
+ public void waitFor(Collection<Point> points) throws InterruptedException, OptimizationException {
for (Point p : points) {
waitFor(p);
}
}
- /**
- * Wait for a point to be computed. After calling this method
- * the function values are available by calling XXX
- *
- * @param point the point to wait for.
- * @throws InterruptedException if this thread was interrupted while waiting.
- */
- public void waitFor(Point point) throws InterruptedException {
+
+ @Override
+ public void waitFor(Point point) throws InterruptedException, OptimizationException {
if (functionCache.containsKey(point)) {
return;
}
double value = future.get();
functionCache.put(point, value);
} catch (ExecutionException e) {
- throw new IllegalStateException("Function threw exception while processing", e.getCause());
+ Throwable cause = e.getCause();
+ if (cause instanceof InterruptedException) {
+ throw (InterruptedException) cause;
+ }
+ if (cause instanceof OptimizationException) {
+ throw (OptimizationException) cause;
+ }
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ }
+
+ throw new BugException("Function threw unknown exception while processing", e);
}
}
- /**
- * Abort the computation of the specified point. If computation has ended,
- * the result is stored in the function cache anyway.
- *
- * @param points the points to abort.
- * @return a list of the points that have been computed anyway
- */
+
+ @Override
public List<Point> abort(Collection<Point> points) {
List<Point> computed = new ArrayList<Point>(Math.min(points.size(), 10));
}
- /**
- * Abort the computation of the specified point. If computation has ended,
- * the result is stored in the function cache anyway.
- *
- * @param point the point to abort.
- * @return <code>true</code> if the point has been computed anyway, <code>false</code> if not.
- */
+
+ @Override
public boolean abort(Point point) {
if (functionCache.containsKey(point)) {
return true;
}
+ @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;
functionCache.clear();
}
+
public ExecutorService getExecutor() {
return executor;
}
}
@Override
- public Double call() throws InterruptedException {
+ public Double call() throws InterruptedException, OptimizationException {
return calledFunction.evaluate(point);
}
}
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.
*
public interface ParallelFunctionCache extends FunctionCache {
/**
- * Queue a list of function evaluations at the specified points.
+ * Schedule a list of function evaluations at the specified points.
+ * The points are added to the end of the computation queue in the order
+ * they are returned by the iterator.
*
* @param points the points at which to evaluate the function.
*/
public void compute(Collection<Point> points);
/**
- * Queue function evaluation for the specified point.
+ * Schedule function evaluation for the specified point. The point is
+ * added to the end of the computation queue.
*
* @param point the point at which to evaluate the function.
*/
/**
* Wait for a collection of points to be computed. After calling this method
- * the function values are available by calling XXX
+ * the function values are available by calling {@link #getValue(Point)}.
*
* @param points the points to wait for.
- * @throws InterruptedException if this thread was interrupted while waiting.
+ * @throws InterruptedException if this thread or the computing thread was interrupted while waiting.
*/
- public void waitFor(Collection<Point> points) throws InterruptedException;
+ public void waitFor(Collection<Point> points) throws InterruptedException, OptimizationException;
/**
* Wait for a point to be computed. After calling this method
- * the function values are available by calling XXX
+ * the function value is available by calling {@link #getValue(Point)}.
*
* @param point the point to wait for.
- * @throws InterruptedException if this thread was interrupted while waiting.
+ * @throws InterruptedException if this thread or the computing thread was interrupted while waiting.
+ * @throws OptimizationException
*/
- public void waitFor(Point point) throws InterruptedException;
+ public void waitFor(Point point) throws InterruptedException, OptimizationException;
/**
- * Abort the computation of the specified point. If computation has ended,
+ * Abort the computation of the specified points. If computation has ended,
* the result is stored in the function cache anyway.
*
* @param points the points to abort.
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;
@Override
- public void optimize(Point initial, OptimizationController control) {
+ public void optimize(Point initial, OptimizationController control) throws OptimizationException {
FunctionCacheComparator comparator = new FunctionCacheComparator(functionExecutor);
final List<Point> pattern = SearchPattern.square(initial.dim());
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
+
+/**
+ * A parameter of a rocket or simulation that can be optimized
+ * (for example max. altitude or velocity).
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface OptimizableParameter {
+
+ /**
+ * Return the label name for this optimization parameter.
+ *
+ * @return the name for the optimization parameter (e.g. "Flight altitude")
+ */
+ public String getName();
+
+ /**
+ * Compute the value for this optimization parameter for the simulation.
+ * The return value can be any double value.
+ * <p>
+ * This method can return NaN in case of a problem computing
+ *
+ * @param simulation the simulation
+ * @return the parameter value (any double value)
+ * @throws OptimizationException if an error occurs preventing the optimization from continuing
+ */
+ public double computeValue(Simulation simulation) throws OptimizationException;
+
+}
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization;
+
+import java.util.Collection;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+
+/**
+ * A service for generating rocket optimization parameters.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface OptimizableParameterService {
+
+ /**
+ * Return all available rocket optimization parameters for this document.
+ * These should be new instances unless the parameter implementation is stateless.
+ *
+ * @param document the design document
+ * @return a collection of the rocket optimization parameters.
+ */
+ public Collection<OptimizableParameter> getParameters(OpenRocketDocument document);
+
+}
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;
public class RocketOptimizationFunction implements Function {
private static final LogHelper log = Application.getLogger();
+ private static final double OUTSIDE_DOMAIN_SCALE = 1.0e200;
+
/*
* NOTE: This class must be thread-safe!!!
*/
private final Simulation baseSimulation;
- private final RocketOptimizationParameter parameter;
+ private final OptimizableParameter parameter;
private final OptimizationGoal goal;
+ private final SimulationDomain domain;
private final SimulationModifier[] modifiers;
private final Map<Point, Double> parameterValueCache = new ConcurrentHashMap<Point, Double>();
* @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");
@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.
/**
* Returns a new deep copy of the simulation and rocket. This methods performs
* synchronization on the simulation for thread protection.
+ * <p>
+ * Note: This method is package-private for unit testing purposes.
*
- * @return
+ * @return a new deep copy of the simulation and rocket
*/
- private Simulation newSimulationInstance() {
+ Simulation newSimulationInstance(Simulation simulation) {
synchronized (baseSimulation) {
- Rocket newRocket = (Rocket) baseSimulation.getRocket().copy();
- Simulation newSimulation = baseSimulation.duplicateSimulation(newRocket);
+ Rocket newRocket = (Rocket) simulation.getRocket().copy();
+ Simulation newSimulation = simulation.duplicateSimulation(newRocket);
return newSimulation;
}
}
+++ /dev/null
-package net.sf.openrocket.optimization.rocketoptimization;
-
-import net.sf.openrocket.document.Simulation;
-
-/**
- * A parameter of a rocket or simulation that can be optimized
- * (for example max. altitude or velocity).
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public interface RocketOptimizationParameter {
-
- /**
- * Return the label name for this optimization parameter.
- *
- * @return the name for the optimization parameter (e.g. "Flight altitude")
- */
- public String getName();
-
- /**
- * Compute the value for this optimization parameter for the simulation.
- * The return value can be any double value.
- *
- * @param simulation the simulation
- * @return the parameter value (any double value)
- */
- public double computeValue(Simulation simulation);
-
-}
+++ /dev/null
-package net.sf.openrocket.optimization.rocketoptimization;
-
-import java.util.Collection;
-
-import net.sf.openrocket.document.OpenRocketDocument;
-
-/**
- * A service for generating rocket optimization parameters.
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public interface RocketOptimizationParameterService {
-
- /**
- * Return all available rocket optimization parameters for this document.
- * These should be new instances unless the parameter implementation is stateless.
- *
- * @param document the design document
- * @return a collection of the rocket optimization parameters.
- */
- public Collection<RocketOptimizationParameter> getParameters(OpenRocketDocument document);
-
-}
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization;
+
+import net.sf.openrocket.document.Simulation;
+
+/**
+ * An interface defining a function domain which limits allowed function values.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface SimulationDomain {
+
+ /**
+ * Return a value determining whether the simulation is within the domain limits
+ * of an optimization process. If the returned value is negative or zero, the
+ * simulation is within the domain; if the value is positive, the returned value
+ * is an indication of how far from the domain the value is; if the returned value
+ * is NaN, the simulation is outside of the domain.
+ *
+ * @param simulation the simulation to check.
+ * @return a negative value or zero if the simulation is in the domain;
+ * a positive value or NaN if not.
+ */
+ public double getDistanceToDomain(Simulation simulation);
+
+}
/**
* 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();
--- /dev/null
+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
+
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.exception.SimulationException;
+import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
+
+/**
+ * An optimization parameter that computes the maximum altitude of a simulated flight.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class MaximumAltitudeParameter implements OptimizableParameter {
+
+ @Override
+ public String getName() {
+ return "Maximum altitude";
+ }
+
+ @Override
+ public double computeValue(Simulation simulation) throws OptimizationException {
+ try {
+ simulation.simulate(new ApogeeEndListener());
+ return simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ALTITUDE);
+ } catch (SimulationException e) {
+ throw new OptimizationException(e);
+ }
+ }
+
+}
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;
*/
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;
/**
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());
}
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());
}
functionalModID = modID;
}
- if (DEBUG_LISTENERS)
- System.out.println("FIRING " + e);
-
// Check whether frozen
if (freezeList != null) {
+ log.debug("Rocket is in frozen state, adding event " + e + " info freeze list");
freezeList.add(e);
return;
}
+ log.debug("Firing rocket change event " + e);
+
// Notify all components first
Iterator<RocketComponent> iterator = this.deepIterator(true);
while (iterator.hasNext()) {
checkState();
if (freezeList == null) {
freezeList = new LinkedList<ComponentChangeEvent>();
+ log.debug("Freezing Rocket");
+ } else {
+ ExceptionHandler.handleErrorCondition("Attempting to freeze Rocket when it is already frozen, " +
+ "freezeList=" + freezeList);
}
}
*/
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) {
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;
public abstract class RocketComponent implements ChangeSource, Cloneable,
Iterable<RocketComponent> {
+ private static final LogHelper log = Application.getLogger();
/*
* Text is suitable to the form
checkState();
if (parent == null) {
/* Ignore if root invalid. */
+ log.debug("Attempted firing event " + e + " with root " + this.getComponentName() + ", ignoring event");
return;
}
getRoot().fireComponentChangeEvent(e);
private SimulationStatus status;
+ @Override
public FlightData simulate(SimulationConditions simulationConditions) throws SimulationException {
Set<MotorId> motorBurntOut = new HashSet<MotorId>();
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);
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.");
}
}
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();
+ 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
--- /dev/null
+package net.sf.openrocket.util;
+
+/**
+ * An exception that indicates a concurrency bug in the software.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ConcurrencyException extends FatalException {
+
+ public ConcurrencyException() {
+ super();
+ }
+
+ public ConcurrencyException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ConcurrencyException(String message) {
+ super(message);
+ }
+
+ public ConcurrencyException(Throwable cause) {
+ super(cause);
+ }
+
+}
*/
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));
@Override
public void windowClosed(WindowEvent e) {
setNullModels(window);
+ MemoryManagement.collectable(window);
}
});
}
JTree tree = (JTree) c;
tree.setModel(new DefaultTreeModel(new TreeNode() {
- @SuppressWarnings("unchecked")
+ @SuppressWarnings("rawtypes")
@Override
public Enumeration children() {
return new Vector().elements();
--- /dev/null
+package net.sf.openrocket.util;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A class that performs certain memory-management operations for debugging purposes.
+ * For example, complex objects that are being discarded and that should be garbage-collectable
+ * (such as dialog windows) should be registered for monitoring by calling
+ * {@link #collectable(Object)}. This will allow monitoring whether the object really is
+ * garbage-collected or whether it is retained in memory due to a memory leak.
+ * Only complex objects should be registered due to the overhead of the monitoring.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public final class MemoryManagement {
+ private static final LogHelper log = Application.getLogger();
+
+ /** Purge cleared references every this many calls to {@link #collectable(Object)} */
+ private static final int PURGE_CALL_COUNT = 100;
+
+
+ /**
+ * Storage of the objects. This is basically a mapping from the objects (using weak references)
+ * to
+ */
+ private static List<MemoryData> objects = new LinkedList<MemoryData>();
+ private static int callCount = 0;
+
+
+ private MemoryManagement() {
+ }
+
+
+ /**
+ * Mark an object that should be garbage-collectable by the GC. This class will monitor
+ * whether the object actually gets garbage-collected or not by holding a weak reference
+ * to the object.
+ *
+ * @param o the object to monitor.
+ */
+ public static synchronized void collectable(Object o) {
+ if (o == null) {
+ throw new IllegalArgumentException("object is null");
+ }
+ log.debug("Adding object into collectable list: " + o);
+ objects.add(new MemoryData(o));
+ callCount++;
+ if (callCount % PURGE_CALL_COUNT == 0) {
+ purge();
+ }
+ }
+
+
+ /**
+ * Return the number of times {@link #collectable(Object)} has been called.
+ * @return the number of times {@link #collectable(Object)} has been called.
+ */
+ public static synchronized int getCallCount() {
+ return callCount;
+ }
+
+
+ /**
+ * Return a list of MemoryData objects corresponding to the objects that have been
+ * registered by {@link #collectable(Object)} and have not been garbage-collected properly.
+ * This method first calls <code>System.gc()</code> multiple times to attempt to
+ * force any remaining garbage collection.
+ *
+ * @return a list of MemoryData objects for objects that have not yet been garbage-collected.
+ */
+ public static synchronized ArrayList<MemoryData> getRemainingObjects() {
+ for (int i = 0; i < 5; i++) {
+ System.runFinalization();
+ System.gc();
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ }
+ }
+ purge();
+ return new ArrayList<MemoryData>(objects);
+ }
+
+
+
+ /**
+ * Purge all cleared references from the object list.
+ */
+ private static void purge() {
+ int origCount = objects.size();
+ Iterator<MemoryData> iterator = objects.iterator();
+ while (iterator.hasNext()) {
+ MemoryData data = iterator.next();
+ if (data.getReference().get() == null) {
+ iterator.remove();
+ }
+ }
+ log.debug(objects.size() + " of " + origCount + " objects remaining in discarded objects list after purge.");
+ }
+
+
+ /**
+ * A value object class containing data of a discarded object reference.
+ */
+ public static final class MemoryData {
+ private final WeakReference<Object> reference;
+ private final long registrationTime;
+
+ private MemoryData(Object object) {
+ this.reference = new WeakReference<Object>(object);
+ this.registrationTime = System.currentTimeMillis();
+ }
+
+ /**
+ * Return the weak reference to the discarded object.
+ */
+ public WeakReference<Object> getReference() {
+ return reference;
+ }
+
+ /**
+ * Return the time when the object was discarded.
+ * @return a millisecond timestamp of when the object was discarded.
+ */
+ public long getRegistrationTime() {
+ return registrationTime;
+ }
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.util;
+
+import java.util.LinkedList;
+
+import net.sf.openrocket.gui.main.ExceptionHandler;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A mutex that can be used for verifying thread safety. This class cannot be
+ * used to perform synchronization, only to detect concurrency issues. This
+ * class can be used by the main methods of non-thread-safe classes to ensure
+ * the class is not wrongly used from multiple threads concurrently.
+ * <p>
+ * This mutex is not reentrant even for the same thread that has locked it.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class SafetyMutex {
+ private static final LogHelper log = Application.getLogger();
+
+ // Package-private for unit testing
+ static volatile boolean errorReported = false;
+
+ // lockingThread is set when this mutex is locked.
+ Thread lockingThread = null;
+ // Stack of places that have locked this mutex
+ final LinkedList<String> locations = new LinkedList<String>();
+
+
+ /**
+ * Verify that this mutex is unlocked, but don't lock it. This has the same effect
+ * as <code>mutex.lock(); mutex.unlock();</code> and is useful for methods that return
+ * immediately (e.g. getters).
+ *
+ * @throws ConcurrencyException if this mutex is already locked.
+ */
+ public synchronized void verify() {
+ checkState(true);
+ if (lockingThread != null && lockingThread != Thread.currentThread()) {
+ error("Mutex is already locked", true);
+ }
+ }
+
+
+ /**
+ * Lock this mutex. If this mutex is already locked an error is raised and
+ * a ConcurrencyException is thrown. The location parameter is used to distinguish
+ * the locking location, and it should be e.g. the method name.
+ *
+ * @param location a string describing the location where this mutex was locked (cannot be null).
+ *
+ * @throws ConcurrencyException if this mutex is already locked.
+ */
+ public synchronized void lock(String location) {
+ if (location == null) {
+ throw new IllegalArgumentException("location is null");
+ }
+ checkState(true);
+
+ Thread currentThread = Thread.currentThread();
+ if (lockingThread != null && lockingThread != currentThread) {
+ error("Mutex is already locked", true);
+ }
+
+ lockingThread = currentThread;
+ locations.push(location);
+ }
+
+
+
+ /**
+ * Unlock this mutex. If this mutex is not locked at the position of the parameter
+ * or was locked by another thread than the current thread an error is raised,
+ * but an exception is not thrown.
+ * <p>
+ * This method is guaranteed never to throw an exception, so it can safely be used in finally blocks.
+ *
+ * @param location a location string matching that which locked the mutex
+ * @return whether the unlocking was successful (this normally doesn't need to be checked)
+ */
+ public synchronized boolean unlock(String location) {
+ try {
+
+ if (location == null) {
+ ExceptionHandler.handleErrorCondition("location is null");
+ location = "";
+ }
+ checkState(false);
+
+
+ // Check that the mutex is locked
+ if (lockingThread == null) {
+ error("Mutex was not locked", false);
+ return false;
+ }
+
+ // Check that the mutex is locked by the current thread
+ if (lockingThread != Thread.currentThread()) {
+ error("Mutex is being unlocked from differerent thread than where it was locked", false);
+ return false;
+ }
+
+ // Check that the unlock location is correct
+ String lastLocation = locations.pop();
+ if (!location.equals(lastLocation)) {
+ locations.push(lastLocation);
+ error("Mutex unlocking location does not match locking location, location=" + location, false);
+ return false;
+ }
+
+ // Unlock the mutex if the last one
+ if (locations.isEmpty()) {
+ lockingThread = null;
+ }
+ return true;
+ } catch (Exception e) {
+ ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " +
+ "locking thread=" + lockingThread + " locations=" + locations, e);
+ return false;
+ }
+ }
+
+
+
+ /**
+ * Check that the internal state of the mutex (lockingThread vs. locations) is correct.
+ */
+ private void checkState(boolean throwException) {
+ /*
+ * Disallowed states:
+ * lockingThread == null && !locations.isEmpty()
+ * lockingThread != null && locations.isEmpty()
+ */
+ if ((lockingThread == null) ^ (locations.isEmpty())) {
+ // Clear the mutex only after error() has executed (and possibly thrown an exception)
+ try {
+ error("Mutex data inconsistency occurred - unlocking mutex", throwException);
+ } finally {
+ lockingThread = null;
+ locations.clear();
+ }
+ }
+ }
+
+
+ /**
+ * Raise an error. The first occurrence is passed directly to the exception handler,
+ * later errors are simply logged.
+ */
+ private void error(String message, boolean throwException) {
+ message = message +
+ ", current thread = " + Thread.currentThread() +
+ ", locking thread=" + lockingThread +
+ ", locking locations=" + locations;
+
+ ConcurrencyException ex = new ConcurrencyException(message);
+
+ if (!errorReported) {
+ errorReported = true;
+ ExceptionHandler.handleErrorCondition(ex);
+ } else {
+ log.error(message, ex);
+ }
+
+ if (throwException) {
+ throw ex;
+ }
+ }
+
+}
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;
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
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() {
}
- 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());
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;
- 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
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() {
}
- public static void main(String[] args) {
+ public static void main(String[] args) throws OptimizationException {
System.err.println("PRECISION = " + PRECISION);
--- /dev/null
+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());
+ }
+
+}
--- /dev/null
+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) {
+ }
+ }
+ }
+
+}