SafetyMutex and rocket optimization updates
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 9 Jan 2011 09:01:02 +0000 (09:01 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 9 Jan 2011 09:01:02 +0000 (09:01 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@96 180e2498-e6e9-4542-8430-84ac67f01cd8

95 files changed:
.classpath
ChangeLog
TODO
build.xml
doc/properties.txt
l10n/messages.properties [new file with mode: 0644]
lib-test/uispec4j-2.3-jdk16.jar [new file with mode: 0644]
lib/iText-5.0.2.jar [new file with mode: 0644]
pix/icons/copyright.txt
pix/icons/document-print.png [new file with mode: 0644]
src/net/sf/openrocket/aerodynamics/AbstractAerodynamicCalculator.java
src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java
src/net/sf/openrocket/aerodynamics/WarningSet.java
src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java
src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java
src/net/sf/openrocket/communication/UpdateInfo.java
src/net/sf/openrocket/database/ThrustCurveMotorSet.java
src/net/sf/openrocket/document/OpenRocketDocument.java
src/net/sf/openrocket/document/Simulation.java
src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
src/net/sf/openrocket/file/simplesax/DelegatorHandler.java
src/net/sf/openrocket/gui/adaptors/BooleanModel.java
src/net/sf/openrocket/gui/adaptors/DoubleModel.java
src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java
src/net/sf/openrocket/gui/configdialog/FinSetConfig.java
src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java
src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java
src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java
src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java
src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java
src/net/sf/openrocket/gui/main/BasicFrame.java
src/net/sf/openrocket/gui/main/ExceptionHandler.java
src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java
src/net/sf/openrocket/gui/plot/PlotConfiguration.java
src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
src/net/sf/openrocket/l10n/DebugTranslator.java [new file with mode: 0644]
src/net/sf/openrocket/l10n/ResourceBundleTranslator.java [new file with mode: 0644]
src/net/sf/openrocket/l10n/Translator.java [new file with mode: 0644]
src/net/sf/openrocket/logging/DelegatorLogger.java
src/net/sf/openrocket/logging/TraceException.java
src/net/sf/openrocket/masscalc/AbstractMassCalculator.java
src/net/sf/openrocket/masscalc/BasicMassCalculator.java
src/net/sf/openrocket/masscalc/MassCalculator.java
src/net/sf/openrocket/models/gravity/BasicGravityModel.java
src/net/sf/openrocket/motor/MotorInstance.java
src/net/sf/openrocket/motor/ThrustCurveMotor.java
src/net/sf/openrocket/optimization/rocketoptimization/SimulationModifier.java
src/net/sf/openrocket/optimization/rocketoptimization/modifiers/AbstractSimulationModifier.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericComponentModifier.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericModifier.java
src/net/sf/openrocket/optimization/rocketoptimization/services/DefaultSimulationModifierService.java [new file with mode: 0644]
src/net/sf/openrocket/rocketcomponent/BodyTube.java
src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java
src/net/sf/openrocket/rocketcomponent/Configuration.java
src/net/sf/openrocket/rocketcomponent/ExternalComponent.java
src/net/sf/openrocket/rocketcomponent/FinSet.java
src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java
src/net/sf/openrocket/rocketcomponent/LaunchLug.java
src/net/sf/openrocket/rocketcomponent/MassObject.java
src/net/sf/openrocket/rocketcomponent/RingComponent.java
src/net/sf/openrocket/rocketcomponent/Rocket.java
src/net/sf/openrocket/rocketcomponent/RocketComponent.java
src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java
src/net/sf/openrocket/rocketcomponent/Visitable.java
src/net/sf/openrocket/rocketcomponent/Visitor.java
src/net/sf/openrocket/simulation/AbstractSimulationStepper.java
src/net/sf/openrocket/simulation/FlightDataBranch.java
src/net/sf/openrocket/simulation/FlightDataType.java
src/net/sf/openrocket/simulation/MassData.java
src/net/sf/openrocket/simulation/RK4SimulationStepper.java
src/net/sf/openrocket/simulation/listeners/example/CSVSaveListener.java
src/net/sf/openrocket/startup/Application.java
src/net/sf/openrocket/startup/Startup.java
src/net/sf/openrocket/unit/CaliberUnit.java
src/net/sf/openrocket/unit/UnitGroup.java
src/net/sf/openrocket/util/ArrayList.java [new file with mode: 0644]
src/net/sf/openrocket/util/GUIUtil.java
src/net/sf/openrocket/util/Icons.java
src/net/sf/openrocket/util/Inertia.java
src/net/sf/openrocket/util/Invalidatable.java [new file with mode: 0644]
src/net/sf/openrocket/util/Invalidator.java [new file with mode: 0644]
src/net/sf/openrocket/util/ListenerList.java [new file with mode: 0644]
src/net/sf/openrocket/util/MemoryManagement.java
src/net/sf/openrocket/util/Monitorable.java
src/net/sf/openrocket/util/Prefs.java
src/net/sf/openrocket/util/Reflection.java
src/net/sf/openrocket/util/SafetyMutex.java
test/net/sf/openrocket/file/rocksim/RocksimTestBase.java
test/net/sf/openrocket/gui/TestGUI.java [new file with mode: 0644]
test/net/sf/openrocket/logging/LogLevelBufferLoggerTest.java
test/net/sf/openrocket/motor/ThrustCurveMotorTest.java
test/net/sf/openrocket/optimization/rocketoptimization/modifiers/TestGenericModifier.java [new file with mode: 0644]
test/net/sf/openrocket/rocketcomponent/ComponentCompare.java
test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java
test/net/sf/openrocket/util/TestMutex.java

index 4a68d2c2c56e9eae26d6da2542cdcfb0598172fb..dafa0a187dcd16e2b108961a56273049b8f30589 100644 (file)
        </classpathentry>
        <classpathentry kind="lib" path="lib/miglayout15-swing.jar"/>
        <classpathentry kind="lib" path="lib/iText-5.0.2.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="lib" path="lib-test/uispec4j-2.3-jdk16.jar"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>
index efbb97935316e44bb18b597160f7b61bc078867b..dfe86bb8436bca3b479ae410a4fa175685e4d130 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2010-10-30  Sampo Niskanen
+
+       * [BUG] Invalid refereces to components used in caches
+
 2010-10-25  Doug Pedrick
 
        * [BUG] Take launch lug radial angle into account when loading rkt file
diff --git a/TODO b/TODO
index f5ceebe8cde2a2c3795444949664ef79c2dd4f9e..f3f97c5b5ff56bd3891b44d2ea5c4c3ba380d374 100644 (file)
--- a/TODO
+++ b/TODO
@@ -76,6 +76,7 @@ Running:
 UI issues:
 
 - Easy/intuitive zooming of plots
+- Open recent designs
 - Only schedule rocket figure update instead of each time updating it
 - Importing flight data (file/altimeter)
 - Saving as SVG
@@ -116,6 +117,7 @@ Component support:
 - Screw weights for nose cones / transitions
 - Support for external pods
 - Support for tube fins
+- Allow ejecting mass components (or all components?) at specific flight events
 
 
 File support:
index 0540b3dc611aa451d74ee2562ad63465ee2834a2..ac382d249edf4c0d8116c5e86c497d1304ce7216 100644 (file)
--- a/build.xml
+++ b/build.xml
                <pathelement location="${build-test.dir}"/>
                <pathelement location="${classes.dir}"/>
                <pathelement location="${src-test.dir}"/>
-               <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"/>
+               <fileset dir="lib-test/" includes="*.jar"/>
        </path>
        
 
@@ -77,6 +73,7 @@
                        <zipfileset src="lib/miglayout15-swing.jar" />
                        <zipfileset src="lib/jcommon-1.0.16.jar" />
                        <zipfileset src="lib/jfreechart-1.0.13.jar" />
+                       <zipfileset src="lib/iText-5.0.2.jar" />
                </jar>
        </target>
        
index cbddc55fbf6a8ab0ab5fe6a8b33cd72cd5389208..77c0501e6f8f269d1cc0196002f9d969b952f60b 100644 (file)
@@ -31,6 +31,7 @@ openrocket.debug
                openrocket.log.tracelevel=VBOSE
                openrocket.debug.menu=true
                openrocket.debug.motordigest=true
+               openrocket.debug.mutexlocation=true
 
 
 openrocket.debug.menu
@@ -39,6 +40,10 @@ openrocket.debug.menu
 openrocket.debug.prefs
        If defined a new, clean set of preferences will be used (does not overwrite the existing preferences).
 
+openrocket.debug.mutexlocation
+       Store a stack trace of the location where safety mutexes are locked.  This slows down usage of the
+       mutexes a bit.
+
 openrocket.debug.bugurl
        URL used for sending bug reports.
 
@@ -56,3 +61,9 @@ openrocket.debug.quaternioncount
        If defined, the number of instantiations of the Quaternion class are counted and reported
        every 1M instantiations, or as often as defined by this parameter.
 
+openrocket.debug.safetycheck
+       If defined (and not "false" or "off") then additional safety checks will be performed
+       in the code to prevent e.g. unsafe concurrent access to objects.  Currently disabled by
+       default, this will later be enabled by default.
+
+
diff --git a/l10n/messages.properties b/l10n/messages.properties
new file mode 100644 (file)
index 0000000..0071165
--- /dev/null
@@ -0,0 +1,43 @@
+
+#
+# English base translation file
+#
+
+
+! Labels used in buttons of dialog windows
+button.ok = OK
+button.cancel = Cancel
+button.close = Close
+
+
+! "main" prefix is used for the main application dialog
+
+main.menu.file = File
+main.menu.file.new = New
+main.menu.file.open = Open...
+main.menu.file.openExample = Open example...
+main.menu.file.save = Save
+main.menu.file.saveAs = Save as...
+main.menu.file.close = Close
+main.menu.file.quit = Quit
+
+main.menu.edit = Edit
+main.menu.edit.undo = Undo
+main.menu.edit.redo = Redo
+main.menu.edit.cut = Cut
+main.menu.edit.copy = Copy
+main.menu.edit.paste = Paste
+main.menu.edit.delete = Delete
+main.menu.edit.preferences = Preferences
+
+main.menu.analyze = Analyze
+main.menu.analyze.componentAnalysis = Component analysis
+
+main.menu.help = Help
+main.menu.help.license = License
+main.menu.help.bugReport = Bug report
+main.menu.help.debugLog = Debug log
+main.menu.help. = About
+
+
+
diff --git a/lib-test/uispec4j-2.3-jdk16.jar b/lib-test/uispec4j-2.3-jdk16.jar
new file mode 100644 (file)
index 0000000..eb39676
Binary files /dev/null and b/lib-test/uispec4j-2.3-jdk16.jar differ
diff --git a/lib/iText-5.0.2.jar b/lib/iText-5.0.2.jar
new file mode 100644 (file)
index 0000000..ed95653
Binary files /dev/null and b/lib/iText-5.0.2.jar differ
index 8b8e2670faf9e9f241d5cc7bb6e6be9dfcf98989..c70dca5d379bb7938f4f822da63772f7acc2c5c0 100644 (file)
@@ -11,6 +11,7 @@ document-close.png
 document-new.png
 document-open.png
 document-open-example.png (modified)
+document-print.png
 document-save-as.png
 document-save.png
 edit-copy.png
diff --git a/pix/icons/document-print.png b/pix/icons/document-print.png
new file mode 100644 (file)
index 0000000..3a87543
Binary files /dev/null and b/pix/icons/document-print.png differ
index cf730b6ab3b75d03040372ec662abf503d517b95..a92d0ab4ad381b8fe1d7fdfed4fb9fa85211067b 100644 (file)
@@ -2,8 +2,10 @@ package net.sf.openrocket.aerodynamics;
 
 import java.util.Map;
 
+import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.rocketcomponent.Configuration;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.Coordinate;
 
 
@@ -15,6 +17,7 @@ import net.sf.openrocket.util.Coordinate;
  */
 
 public abstract class AbstractAerodynamicCalculator implements AerodynamicCalculator {
+       private static final LogHelper log = Application.getLogger();
        
        /** Number of divisions used when calculating worst CP. */
        public static final int DIVISIONS = 360;
@@ -27,19 +30,22 @@ public abstract class AbstractAerodynamicCalculator implements AerodynamicCalcul
        
        /** The aerodynamic modification ID of the latest rocket */
        private int rocketAeroModID = -1;
-       private int stageCount = -1;
+       private int rocketTreeModID = -1;
        
        
 
 
        ////////////////  Aerodynamic calculators  ////////////////
        
+       @Override
        public abstract Coordinate getCP(Configuration configuration, FlightConditions conditions,
                        WarningSet warnings);
        
+       @Override
        public abstract Map<RocketComponent, AerodynamicForces> getForceAnalysis(Configuration configuration, FlightConditions conditions,
                                WarningSet warnings);
        
+       @Override
        public abstract AerodynamicForces getAerodynamicForces(Configuration configuration,
                        FlightConditions conditions, WarningSet warnings);
        
@@ -48,6 +54,7 @@ public abstract class AbstractAerodynamicCalculator implements AerodynamicCalcul
        /*
         * The worst theta angle is stored in conditions.
         */
+       @Override
        public Coordinate getWorstCP(Configuration configuration, FlightConditions conditions,
                        WarningSet warnings) {
                FlightConditions cond = conditions.clone();
@@ -84,9 +91,10 @@ public abstract class AbstractAerodynamicCalculator implements AerodynamicCalcul
         */
        protected final void checkCache(Configuration configuration) {
                if (rocketAeroModID != configuration.getRocket().getAerodynamicModID() ||
-                               stageCount != configuration.getStageCount()) {
+                               rocketTreeModID != configuration.getRocket().getTreeModID()) {
                        rocketAeroModID = configuration.getRocket().getAerodynamicModID();
-                       stageCount = configuration.getStageCount();
+                       rocketTreeModID = configuration.getRocket().getTreeModID();
+                       log.debug("Voiding the aerodynamic cache");
                        voidAerodynamicCache();
                }
        }
index 4618fc8778230bfb8ee07ca766b9702316629c01..4b243389a418f8132cc3e4a1e59bf962c075fbc9 100644 (file)
@@ -725,7 +725,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
                
                calcMap = new HashMap<RocketComponent, RocketComponentCalc>();
                
-               iterator = configuration.getRocket().deepIterator();
+               iterator = configuration.getRocket().iterator();
                while (iterator.hasNext()) {
                        RocketComponent c = iterator.next();
                        
index 40eab61b912e2ecfe11a6efbf86a3bd13947a11c..c4e81c62867ae2afab872d42e27d8ab999b01022 100644 (file)
@@ -1,9 +1,9 @@
 package net.sf.openrocket.aerodynamics;
 
 import java.util.AbstractSet;
-import java.util.ArrayList;
 import java.util.Iterator;
 
+import net.sf.openrocket.util.ArrayList;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.Monitorable;
 import net.sf.openrocket.util.Mutable;
@@ -102,13 +102,12 @@ public class WarningSet extends AbstractSet<Warning> implements Cloneable, Monit
        }
        
        
-       @SuppressWarnings("unchecked")
        @Override
        public WarningSet clone() {
                try {
                        
                        WarningSet newSet = (WarningSet) super.clone();
-                       newSet.warnings = (ArrayList<Warning>) this.warnings.clone();
+                       newSet.warnings = this.warnings.clone();
                        newSet.mutable = this.mutable.clone();
                        return newSet;
                        
index 46bc327a3902ab9fa4078083f9a3e9740ea3f7c1..2b3d7a6a80739583a39702c76d91f87cfe20ceaf 100644 (file)
@@ -19,29 +19,25 @@ import net.sf.openrocket.util.PolyInterpolator;
 
 
 public class FinSetCalc extends RocketComponentCalc {
-       private static final double STALL_ANGLE = (20 * Math.PI/180);
+       private static final double STALL_ANGLE = (20 * Math.PI / 180);
        
        /** Number of divisions in the fin chords. */
        protected static final int DIVISIONS = 48;
        
-       
-       
 
-       private FinSet component;
-       
 
-       protected double macLength = Double.NaN;    // MAC length
-       protected double macLead = Double.NaN;      // MAC leading edge position
-       protected double macSpan = Double.NaN;      // MAC spanwise position
-       protected double finArea = Double.NaN;      // Fin area
-       protected double ar = Double.NaN;           // Fin aspect ratio
-       protected double span = Double.NaN;                 // Fin span
-       protected double cosGamma = Double.NaN;     // Cosine of midchord sweep angle
+       protected double macLength = Double.NaN; // MAC length
+       protected double macLead = Double.NaN; // MAC leading edge position
+       protected double macSpan = Double.NaN; // MAC spanwise position
+       protected double finArea = Double.NaN; // Fin area
+       protected double ar = Double.NaN; // Fin aspect ratio
+       protected double span = Double.NaN; // Fin span
+       protected double cosGamma = Double.NaN; // Cosine of midchord sweep angle
        protected double cosGammaLead = Double.NaN; // Cosine of leading edge sweep angle
-       protected double rollSum = Double.NaN;      // Roll damping sum term
+       protected double rollSum = Double.NaN; // Roll damping sum term
+       
+       protected int interferenceFinCount = -1; // No. of fins in interference
        
-       private int interferenceFinCount = -1;      // No. of fins in interference
-
        protected double[] chordLead = new double[DIVISIONS];
        protected double[] chordTrail = new double[DIVISIONS];
        protected double[] chordLength = new double[DIVISIONS];
@@ -50,31 +46,45 @@ public class FinSetCalc extends RocketComponentCalc {
        protected final WarningSet geometryWarnings = new WarningSet();
        
        private double[] poly = new double[6];
-
+       
+       private final double thickness;
+       private final double bodyRadius;
+       private final int finCount;
+       private final double baseRotation;
+       private final double cantAngle;
+       private final FinSet.CrossSection crossSection;
        
        public FinSetCalc(RocketComponent component) {
                super(component);
                if (!(component instanceof FinSet)) {
-                       throw new IllegalArgumentException("Illegal component type "+component);
+                       throw new IllegalArgumentException("Illegal component type " + component);
                }
-               this.component = (FinSet) component;
+               
+               FinSet fin = (FinSet) component;
+               thickness = fin.getThickness();
+               bodyRadius = fin.getBodyRadius();
+               finCount = fin.getFinCount();
+               baseRotation = fin.getBaseRotation();
+               cantAngle = fin.getCantAngle();
+               span = fin.getSpan();
+               finArea = fin.getFinArea();
+               crossSection = fin.getCrossSection();
+               
+               calculateFinGeometry(fin);
+               calculatePoly();
+               calculateInterferenceFinCount(fin);
        }
        
-
+       
        /*
         * Calculates the non-axial forces produced by the fins (normal and side forces,
         * pitch, yaw and roll moments, CP position, CNa).
         */
        @Override
-       public void calculateNonaxialForces(FlightConditions conditions, 
+       public void calculateNonaxialForces(FlightConditions conditions,
                        AerodynamicForces forces, WarningSet warnings) {
                
-               // Compute and cache the fin geometry
-               if (Double.isNaN(macLength)) {
-                       calculateFinGeometry();
-                       calculatePoly();
-               }
-               
+
                if (span < 0.001) {
                        forces.setCm(0);
                        forces.setCN(0);
@@ -87,73 +97,69 @@ public class FinSetCalc extends RocketComponentCalc {
                        forces.setCyaw(0);
                        return;
                }
-
                
+
                // Add warnings  (radius/2 == diameter/4)
-               if (component.getThickness() > component.getBodyRadius()/2) {
+               if (thickness > bodyRadius / 2) {
                        warnings.add(Warning.THICK_FIN);
                }
                warnings.addAll(geometryWarnings);
-
-               
                
-               //////// Calculate CNa.  /////////
 
+
+               //////// Calculate CNa.  /////////
+               
                // One fin without interference (both sub- and supersonic):
                double cna1 = calculateFinCNa1(conditions);
                
-               
-               
+
+
                // Multiple fins with fin-fin interference
                double cna;
-               
-               // TODO: MEDIUM:  Take into account multiple fin sets
-               int fins = component.getFinCount();
-               int interferenceFins = getInterferenceFinCount();
                double theta = conditions.getTheta();
-               double angle = component.getBaseRotation();
-
+               double angle = baseRotation;
                
+
                // Compute basic CNa without interference effects
-               if (fins == 1 || fins == 2) {
+               if (finCount == 1 || finCount == 2) {
                        // Basic CNa from geometry
                        double mul = 0;
-                       for (int i=0; i < fins; i++) {
+                       for (int i = 0; i < finCount; i++) {
                                mul += MathUtil.pow2(Math.sin(theta - angle));
-                               angle += 2 * Math.PI / fins;
+                               angle += 2 * Math.PI / finCount;
                        }
-                       cna = cna1*mul;
+                       cna = cna1 * mul;
                } else {
                        // Basic CNa assuming full efficiency
-                       cna = cna1 * fins/2.0;
+                       cna = cna1 * finCount / 2.0;
                }
                
-               
+
                // Take into account fin-fin interference effects
-               switch (interferenceFins) {
+               switch (interferenceFinCount) {
                case 1:
                case 2:
                case 3:
                case 4:
                        // No interference effect
                        break;
-                       
+               
                case 5:
                        cna *= 0.948;
                        break;
-                       
+               
                case 6:
                        cna *= 0.913;
                        break;
-                       
+               
                case 7:
                        cna *= 0.854;
                        break;
-                       
+               
                case 8:
                        cna *= 0.81;
                        break;
-                       
+               
                default:
                        // Assume 75% efficiency
                        cna *= 0.75;
@@ -209,60 +215,59 @@ public class FinSetCalc extends RocketComponentCalc {
                        break;
                }
                */
-               
+
                // Body-fin interference effect
-               double r = component.getBodyRadius();
-               double tau = r / (span+r);
+               double r = bodyRadius;
+               double tau = r / (span + r);
                if (Double.isNaN(tau) || Double.isInfinite(tau))
                        tau = 0;
-               cna *= 1 + tau;                 // Classical Barrowman
-//             cna *= pow2(1 + tau);   // Barrowman thesis (too optimistic??)
-               
-               
+               cna *= 1 + tau; // Classical Barrowman
+               //              cna *= pow2(1 + tau);   // Barrowman thesis (too optimistic??)
                
+
+
                // TODO: LOW: check for fin tip mach cone interference
                // (Barrowman thesis pdf-page 40)
-
-               // TODO: LOW: fin-fin mach cone effect, MIL-HDBK page 5-25
                
+               // TODO: LOW: fin-fin mach cone effect, MIL-HDBK page 5-25
                
 
+
                // Calculate CP position
                double x = macLead + calculateCPPos(conditions) * macLength;
                
 
-               
+
                // Calculate roll forces, reduce forcing above stall angle
                
                // Without body-fin interference effect:
-//             forces.CrollForce = fins * (macSpan+r) * cna1 * component.getCantAngle() / 
-//                     conditions.getRefLength();
+               //              forces.CrollForce = fins * (macSpan+r) * cna1 * component.getCantAngle() / 
+               //                      conditions.getRefLength();
                // With body-fin interference effect:
-               forces.setCrollForce(fins * (macSpan+r) * cna1 * (1+tau) * component.getCantAngle() / 
-                       conditions.getRefLength());
+               forces.setCrollForce(finCount * (macSpan + r) * cna1 * (1 + tau) * cantAngle / conditions.getRefLength());
+               
+
 
 
-               
-               
                if (conditions.getAOA() > STALL_ANGLE) {
-//                     System.out.println("Fin stalling in roll");
+                       //                      System.out.println("Fin stalling in roll");
                        forces.setCrollForce(forces.getCrollForce() * MathUtil.clamp(
-                                       1-(conditions.getAOA() - STALL_ANGLE)/(STALL_ANGLE/2), 0, 1));
+                                       1 - (conditions.getAOA() - STALL_ANGLE) / (STALL_ANGLE / 2), 0, 1));
                }
                forces.setCrollDamp(calculateDampingMoment(conditions));
                forces.setCroll(forces.getCrollForce() - forces.getCrollDamp());
                
-                               
-               
-//             System.out.printf(component.getName() + ":  roll rate:%.3f  force:%.3f  damp:%.3f  " +
-//                             "total:%.3f\n",
-//                             conditions.getRollRate(), forces.CrollForce, forces.CrollDamp, forces.Croll);
+
+
+               //              System.out.printf(component.getName() + ":  roll rate:%.3f  force:%.3f  damp:%.3f  " +
+               //                              "total:%.3f\n",
+               //                              conditions.getRollRate(), forces.CrollForce, forces.CrollDamp, forces.Croll);
                
                forces.setCNa(cna);
                forces.setCN(cna * MathUtil.min(conditions.getAOA(), STALL_ANGLE));
                forces.setCP(new Coordinate(x, 0, 0, cna));
                forces.setCm(forces.getCN() * x / conditions.getRefLength());
-
+               
                /*
                 * TODO: HIGH:  Compute actual side force and yaw moment.
                 * This is not currently performed because it produces strange results for
@@ -270,17 +275,17 @@ public class FinSetCalc extends RocketComponentCalc {
                 * where the rocket flies at an ever-increasing angle of attack.  This may
                 * be due to incorrect computation of pitch/yaw damping moments.
                 */
-//             if (fins == 1 || fins == 2) {
-//                     forces.Cside = fins * cna1 * Math.cos(theta-angle) * Math.sin(theta-angle);
-//                     forces.Cyaw = fins * forces.Cside * x / conditions.getRefLength();
-//             } else {
-//                     forces.Cside = 0;
-//                     forces.Cyaw = 0;
-//             }
+               //              if (fins == 1 || fins == 2) {
+               //                      forces.Cside = fins * cna1 * Math.cos(theta-angle) * Math.sin(theta-angle);
+               //                      forces.Cyaw = fins * forces.Cside * x / conditions.getRefLength();
+               //              } else {
+               //                      forces.Cside = 0;
+               //                      forces.Cyaw = 0;
+               //              }
                forces.setCside(0);
                forces.setCyaw(0);
                
-               
+
        }
        
        
@@ -291,71 +296,59 @@ public class FinSetCalc extends RocketComponentCalc {
         * @return  the MAC length of the fin.
         */
        public double getMACLength() {
-               // Compute and cache the fin geometry
-               if (Double.isNaN(macLength)) {
-                       calculateFinGeometry();
-                       calculatePoly();
-               }
-               
                return macLength;
        }
        
        public double getMidchordPos() {
-               // Compute and cache the fin geometry
-               if (Double.isNaN(macLength)) {
-                       calculateFinGeometry();
-                       calculatePoly();
-               }
-               
                return macLead + 0.5 * macLength;
        }
        
        
-       
+
        /**
         * Pre-calculates the fin geometry values.
         */
-       protected void calculateFinGeometry() {
+       protected void calculateFinGeometry(FinSet component) {
                
                span = component.getSpan();
                finArea = component.getFinArea();
                ar = 2 * pow2(span) / finArea;
-
+               
                Coordinate[] points = component.getFinPoints();
                
                // Check for jagged edges
                geometryWarnings.clear();
                boolean down = false;
-               for (int i=1; i < points.length; i++) {
-                       if ((points[i].y > points[i-1].y + 0.001)  &&  down) {
+               for (int i = 1; i < points.length; i++) {
+                       if ((points[i].y > points[i - 1].y + 0.001) && down) {
                                geometryWarnings.add(Warning.JAGGED_EDGED_FIN);
                                break;
                        }
-                       if (points[i].y < points[i-1].y - 0.001) {
+                       if (points[i].y < points[i - 1].y - 0.001) {
                                down = true;
                        }
                }
                
-               
+
                // Calculate the chord lead and trail positions and length
                
                Arrays.fill(chordLead, Double.POSITIVE_INFINITY);
                Arrays.fill(chordTrail, Double.NEGATIVE_INFINITY);
                Arrays.fill(chordLength, 0);
-
-               for (int point=1; point < points.length; point++) {
-                       double x1 = points[point-1].x;
-                       double y1 = points[point-1].y;
+               
+               for (int point = 1; point < points.length; point++) {
+                       double x1 = points[point - 1].x;
+                       double y1 = points[point - 1].y;
                        double x2 = points[point].x;
                        double y2 = points[point].y;
                        
                        if (MathUtil.equals(y1, y2))
                                continue;
                        
-                       int i1 = (int)(y1*1.0001/span*(DIVISIONS-1));
-                       int i2 = (int)(y2*1.0001/span*(DIVISIONS-1));
-                       i1 = MathUtil.clamp(i1, 0, DIVISIONS-1);
-                       i2 = MathUtil.clamp(i2, 0, DIVISIONS-1);
+                       int i1 = (int) (y1 * 1.0001 / span * (DIVISIONS - 1));
+                       int i2 = (int) (y2 * 1.0001 / span * (DIVISIONS - 1));
+                       i1 = MathUtil.clamp(i1, 0, DIVISIONS - 1);
+                       i2 = MathUtil.clamp(i2, 0, DIVISIONS - 1);
                        if (i1 > i2) {
                                int tmp = i2;
                                i2 = i1;
@@ -364,8 +357,8 @@ public class FinSetCalc extends RocketComponentCalc {
                        
                        for (int i = i1; i <= i2; i++) {
                                // Intersection point (x,y)
-                               double y = i*span/(DIVISIONS-1);
-                               double x = (y-y2)/(y1-y2)*x1 + (y1-y)/(y1-y2)*x2;
+                               double y = i * span / (DIVISIONS - 1);
+                               double x = (y - y2) / (y1 - y2) * x1 + (y1 - y) / (y1 - y2) * x2;
                                if (x < chordLead[i])
                                        chordLead[i] = x;
                                if (x > chordTrail[i])
@@ -381,7 +374,7 @@ public class FinSetCalc extends RocketComponentCalc {
                }
                
                // Check and correct any inconsistencies
-               for (int i=0; i < DIVISIONS; i++) {
+               for (int i = 0; i < DIVISIONS; i++) {
                        if (Double.isInfinite(chordLead[i]) || Double.isInfinite(chordTrail[i]) ||
                                        Double.isNaN(chordLead[i]) || Double.isNaN(chordTrail[i])) {
                                chordLead[i] = 0;
@@ -395,7 +388,7 @@ public class FinSetCalc extends RocketComponentCalc {
                        }
                }
                
-               
+
                /* Calculate fin properties:
                 * 
                 * macLength // MAC length
@@ -412,24 +405,24 @@ public class FinSetCalc extends RocketComponentCalc {
                rollSum = 0;
                double area = 0;
                double radius = component.getBodyRadius();
-
-               final double dy = span/(DIVISIONS-1);
-               for (int i=0; i < DIVISIONS; i++) {
+               
+               final double dy = span / (DIVISIONS - 1);
+               for (int i = 0; i < DIVISIONS; i++) {
                        double length = chordTrail[i] - chordLead[i];
-                       double y = i*dy;
-
+                       double y = i * dy;
+                       
                        macLength += length * length;
                        macSpan += y * length;
                        macLead += chordLead[i] * length;
                        area += length;
                        rollSum += chordLength[i] * pow2(radius + y);
                        
-                       if (i>0) {
-                               double dx = (chordTrail[i]+chordLead[i])/2 - (chordTrail[i-1]+chordLead[i-1])/2;
-                               cosGamma += dy/MathUtil.hypot(dx, dy);
+                       if (i > 0) {
+                               double dx = (chordTrail[i] + chordLead[i]) / 2 - (chordTrail[i - 1] + chordLead[i - 1]) / 2;
+                               cosGamma += dy / MathUtil.hypot(dx, dy);
                                
-                               dx = chordLead[i] - chordLead[i-1];
-                               cosGammaLead += dy/MathUtil.hypot(dx, dy);
+                               dx = chordLead[i] - chordLead[i - 1];
+                               cosGammaLead += dy / MathUtil.hypot(dx, dy);
                        }
                }
                
@@ -442,8 +435,8 @@ public class FinSetCalc extends RocketComponentCalc {
                macLength /= area;
                macSpan /= area;
                macLead /= area;
-               cosGamma /= (DIVISIONS-1);
-               cosGammaLead /= (DIVISIONS-1);
+               cosGamma /= (DIVISIONS - 1);
+               cosGammaLead /= (DIVISIONS - 1);
        }
        
        
@@ -451,122 +444,121 @@ public class FinSetCalc extends RocketComponentCalc {
        
        private static final double CNA_SUBSONIC = 0.9;
        private static final double CNA_SUPERSONIC = 1.5;
-       private static final double CNA_SUPERSONIC_B = pow(pow2(CNA_SUPERSONIC)-1, 1.5);
+       private static final double CNA_SUPERSONIC_B = pow(pow2(CNA_SUPERSONIC) - 1, 1.5);
        private static final double GAMMA = 1.4;
        private static final LinearInterpolator K1, K2, K3;
        private static final PolyInterpolator cnaInterpolator = new PolyInterpolator(
                        new double[] { CNA_SUBSONIC, CNA_SUPERSONIC },
                        new double[] { CNA_SUBSONIC, CNA_SUPERSONIC },
                        new double[] { CNA_SUBSONIC }
-       );
+                       );
        /* Pre-calculate the values for K1, K2 and K3 */
        static {
                // Up to Mach 5
-               int n = (int)((5.0-CNA_SUPERSONIC)*10);
+               int n = (int) ((5.0 - CNA_SUPERSONIC) * 10);
                double[] x = new double[n];
                double[] k1 = new double[n];
                double[] k2 = new double[n];
                double[] k3 = new double[n];
-               for (int i=0; i<n; i++) {
-                       double M = CNA_SUPERSONIC + i*0.1;
-                       double beta = sqrt(M*M - 1);
+               for (int i = 0; i < n; i++) {
+                       double M = CNA_SUPERSONIC + i * 0.1;
+                       double beta = sqrt(M * M - 1);
                        x[i] = M;
-                       k1[i] = 2.0/beta;
-                       k2[i] = ((GAMMA+1)*pow(M, 4) - 4*pow2(beta)) / (4*pow(beta,4));
-                       k3[i] = ((GAMMA+1)*pow(M, 8) + (2*pow2(GAMMA) - 7*GAMMA - 5) * pow(M,6) +
-                                       10*(GAMMA+1)*pow(M,4) + 8) / (6*pow(beta,7));
+                       k1[i] = 2.0 / beta;
+                       k2[i] = ((GAMMA + 1) * pow(M, 4) - 4 * pow2(beta)) / (4 * pow(beta, 4));
+                       k3[i] = ((GAMMA + 1) * pow(M, 8) + (2 * pow2(GAMMA) - 7 * GAMMA - 5) * pow(M, 6) +
+                                       10 * (GAMMA + 1) * pow(M, 4) + 8) / (6 * pow(beta, 7));
                }
-               K1 = new LinearInterpolator(x,k1);
-               K2 = new LinearInterpolator(x,k2);
-               K3 = new LinearInterpolator(x,k3);
+               K1 = new LinearInterpolator(x, k1);
+               K2 = new LinearInterpolator(x, k2);
+               K3 = new LinearInterpolator(x, k3);
                
-//             System.out.println("K1[m="+CNA_SUPERSONIC+"] = "+k1[0]);
-//             System.out.println("K2[m="+CNA_SUPERSONIC+"] = "+k2[0]);
-//             System.out.println("K3[m="+CNA_SUPERSONIC+"] = "+k3[0]);
+               //              System.out.println("K1[m="+CNA_SUPERSONIC+"] = "+k1[0]);
+               //              System.out.println("K2[m="+CNA_SUPERSONIC+"] = "+k2[0]);
+               //              System.out.println("K3[m="+CNA_SUPERSONIC+"] = "+k3[0]);
        }
        
-
+       
        protected double calculateFinCNa1(FlightConditions conditions) {
                double mach = conditions.getMach();
                double ref = conditions.getRefArea();
-               double alpha = MathUtil.min(conditions.getAOA(), 
+               double alpha = MathUtil.min(conditions.getAOA(),
                                Math.PI - conditions.getAOA(), STALL_ANGLE);
                
                // Subsonic case
                if (mach <= CNA_SUBSONIC) {
-                       return 2*Math.PI*pow2(span)/(1 + sqrt(1 + (1-pow2(mach))*
-                                       pow2(pow2(span)/(finArea*cosGamma)))) / ref;
+                       return 2 * Math.PI * pow2(span) / (1 + sqrt(1 + (1 - pow2(mach)) *
+                                       pow2(pow2(span) / (finArea * cosGamma)))) / ref;
                }
                
                // Supersonic case
                if (mach >= CNA_SUPERSONIC) {
-                       return finArea * (K1.getValue(mach) + K2.getValue(mach)*alpha +
-                                       K3.getValue(mach)*pow2(alpha)) / ref;
+                       return finArea * (K1.getValue(mach) + K2.getValue(mach) * alpha +
+                                       K3.getValue(mach) * pow2(alpha)) / ref;
                }
                
                // Transonic case, interpolate
                double subV, superV;
                double subD, superD;
                
-               double sq = sqrt(1 + (1-pow2(CNA_SUBSONIC)) * pow2(span*span/(finArea*cosGamma)));
-               subV = 2*Math.PI*pow2(span)/ref / (1+sq);
-               subD = 2*mach*Math.PI*pow(span,6) / (pow2(finArea*cosGamma) * ref * 
-                               sq * pow2(1+sq));
+               double sq = sqrt(1 + (1 - pow2(CNA_SUBSONIC)) * pow2(span * span / (finArea * cosGamma)));
+               subV = 2 * Math.PI * pow2(span) / ref / (1 + sq);
+               subD = 2 * mach * Math.PI * pow(span, 6) / (pow2(finArea * cosGamma) * ref *
+                               sq * pow2(1 + sq));
                
-               superV = finArea * (K1.getValue(CNA_SUPERSONIC) + K2.getValue(CNA_SUPERSONIC)*alpha +
-               K3.getValue(CNA_SUPERSONIC)*pow2(alpha)) / ref;
-               superD = -finArea/ref * 2*CNA_SUPERSONIC / CNA_SUPERSONIC_B; 
+               superV = finArea * (K1.getValue(CNA_SUPERSONIC) + K2.getValue(CNA_SUPERSONIC) * alpha +
+                               K3.getValue(CNA_SUPERSONIC) * pow2(alpha)) / ref;
+               superD = -finArea / ref * 2 * CNA_SUPERSONIC / CNA_SUPERSONIC_B;
                
-//             System.out.println("subV="+subV+" superV="+superV+" subD="+subD+" superD="+superD);
+               //              System.out.println("subV="+subV+" superV="+superV+" subD="+subD+" superD="+superD);
                
                return cnaInterpolator.interpolate(mach, subV, superV, subD, superD, 0);
        }
        
        
 
-       
+
        private double calculateDampingMoment(FlightConditions conditions) {
                double rollRate = conditions.getRollRate();
-
+               
                if (Math.abs(rollRate) < 0.1)
                        return 0;
                
                double mach = conditions.getMach();
-               double radius = component.getBodyRadius();
                double absRate = Math.abs(rollRate);
                
-               
+
                /*
                 * At low speeds and relatively large roll rates (i.e. near apogee) the
                 * fin tips rotate well above stall angle.  In this case sum the chords
                 * separately.
                 */
-               if (absRate * (radius + span) / conditions.getVelocity() > 15*Math.PI/180) {
+               if (absRate * (bodyRadius + span) / conditions.getVelocity() > 15 * Math.PI / 180) {
                        double sum = 0;
-                       for (int i=0; i < DIVISIONS; i++) {
-                               double dist = radius + span*i/DIVISIONS;
-                               double aoa = Math.min(absRate*dist/conditions.getVelocity(), 15*Math.PI/180);
+                       for (int i = 0; i < DIVISIONS; i++) {
+                               double dist = bodyRadius + span * i / DIVISIONS;
+                               double aoa = Math.min(absRate * dist / conditions.getVelocity(), 15 * Math.PI / 180);
                                sum += chordLength[i] * dist * aoa;
                        }
-                       sum = sum * (span/DIVISIONS) * 2*Math.PI/conditions.getBeta() /
+                       sum = sum * (span / DIVISIONS) * 2 * Math.PI / conditions.getBeta() /
                                        (conditions.getRefArea() * conditions.getRefLength());
                        
-//                     System.out.println("SPECIAL: " + 
-//                                     (MathUtil.sign(rollRate) *component.getFinCount() * sum));
-                       return MathUtil.sign(rollRate) * component.getFinCount() * sum;
+                       //                      System.out.println("SPECIAL: " + 
+                       //                                      (MathUtil.sign(rollRate) *component.getFinCount() * sum));
+                       return MathUtil.sign(rollRate) * finCount * sum;
                }
                
-               
-               
+
+
                if (mach <= CNA_SUBSONIC) {
-//                     System.out.println("BASIC:   "+
-//                                     (component.getFinCount() * 2*Math.PI * rollRate * rollSum / 
-//                     (conditions.getRefArea() * conditions.getRefLength() * 
-//                                     conditions.getVelocity() * conditions.getBeta())));
+                       //                      System.out.println("BASIC:   "+
+                       //                                      (component.getFinCount() * 2*Math.PI * rollRate * rollSum / 
+                       //                      (conditions.getRefArea() * conditions.getRefLength() * 
+                       //                                      conditions.getVelocity() * conditions.getBeta())));
                        
-                       return component.getFinCount() * 2*Math.PI * rollRate * rollSum / 
-                       (conditions.getRefArea() * conditions.getRefLength() * 
-                                       conditions.getVelocity() * conditions.getBeta());
+                       return finCount * 2 * Math.PI * rollRate * rollSum /
+                                       (conditions.getRefArea() * conditions.getRefLength() *
+                                                       conditions.getVelocity() * conditions.getBeta());
                }
                if (mach >= CNA_SUPERSONIC) {
                        
@@ -574,19 +566,19 @@ public class FinSetCalc extends RocketComponentCalc {
                        double k1 = K1.getValue(mach);
                        double k2 = K2.getValue(mach);
                        double k3 = K3.getValue(mach);
-
+                       
                        double sum = 0;
                        
-                       for (int i=0; i < DIVISIONS; i++) {
-                               double y = i*span/(DIVISIONS-1);
-                               double angle = rollRate * (radius+y) / vel;
+                       for (int i = 0; i < DIVISIONS; i++) {
+                               double y = i * span / (DIVISIONS - 1);
+                               double angle = rollRate * (bodyRadius + y) / vel;
                                
-                               sum += (k1 * angle + k2 * angle*angle + k3 * angle*angle*angle) 
-                                       * chordLength[i] * (radius+y);
+                               sum += (k1 * angle + k2 * angle * angle + k3 * angle * angle * angle)
+                                               * chordLength[i] * (bodyRadius + y);
                        }
                        
-                       return component.getFinCount() * sum * span/(DIVISIONS-1) / 
-                               (conditions.getRefArea() * conditions.getRefLength());
+                       return finCount * sum * span / (DIVISIONS - 1) /
+                                       (conditions.getRefArea() * conditions.getRefLength());
                }
                
                // Transonic, do linear interpolation
@@ -597,13 +589,13 @@ public class FinSetCalc extends RocketComponentCalc {
                cond.setMach(CNA_SUPERSONIC + 0.01);
                double supersonic = calculateDampingMoment(cond);
                
-               return subsonic * (CNA_SUPERSONIC - mach)/(CNA_SUPERSONIC - CNA_SUBSONIC) +
-                          supersonic * (mach - CNA_SUBSONIC)/(CNA_SUPERSONIC - CNA_SUBSONIC);
+               return subsonic * (CNA_SUPERSONIC - mach) / (CNA_SUPERSONIC - CNA_SUBSONIC) +
+                               supersonic * (mach - CNA_SUBSONIC) / (CNA_SUPERSONIC - CNA_SUBSONIC);
        }
        
        
-       
-       
+
+
        /**
         * Return the relative position of the CP along the mean aerodynamic chord.
         * Below mach 0.5 it is at the quarter chord, above mach 2 calculated using an
@@ -621,14 +613,14 @@ public class FinSetCalc extends RocketComponentCalc {
                if (m >= 2) {
                        // At supersonic speeds use empirical formula
                        double beta = cond.getBeta();
-                       return (ar * beta - 0.67) / (2*ar*beta - 1);
+                       return (ar * beta - 0.67) / (2 * ar * beta - 1);
                }
                
                // In between use interpolation polynomial
                double x = 1.0;
                double val = 0;
                
-               for (int i=0; i < poly.length; i++) {
+               for (int i = 0; i < poly.length; i++) {
                        val += poly[i] * x;
                        x *= m;
                }
@@ -643,7 +635,7 @@ public class FinSetCalc extends RocketComponentCalc {
         * p(0.5)=0.25
         * p'(0.5)=0
         * p(2) = f(2)
-        * p'(2) = f'(2)
+        * p'(2) = f'(2)
         * p''(2) = 0
         * p'''(2) = 0
         * 
@@ -653,7 +645,7 @@ public class FinSetCalc extends RocketComponentCalc {
         * are used as poly[0] + poly[1]*x + poly[2]*x^2 + ...
         */
        private void calculatePoly() {
-               double denom = pow2(1 - 3.4641*ar);  // common denominator
+               double denom = pow2(1 - 3.4641 * ar); // common denominator
                
                poly[5] = (-1.58025 * (-0.728769 + ar) * (-0.192105 + ar)) / denom;
                poly[4] = (12.8395 * (-0.725688 + ar) * (-0.19292 + ar)) / denom;
@@ -664,134 +656,121 @@ public class FinSetCalc extends RocketComponentCalc {
        }
        
        
-//     @SuppressWarnings("null")
-//     public static void main(String arg[]) {
-//             Rocket rocket = TestRocket.makeRocket();
-//             FinSet finset = null;
-//             
-//             Iterator<RocketComponent> iter = rocket.deepIterator();
-//             while (iter.hasNext()) {
-//                     RocketComponent c = iter.next();
-//                     if (c instanceof FinSet) {
-//                             finset = (FinSet)c;
-//                             break;
-//                     }
-//             }
-//             
-//             ((TrapezoidFinSet)finset).setHeight(0.10);
-//             ((TrapezoidFinSet)finset).setRootChord(0.10);
-//             ((TrapezoidFinSet)finset).setTipChord(0.10);
-//             ((TrapezoidFinSet)finset).setSweep(0.0);
-//
-//             
-//             FinSetCalc calc = new FinSetCalc(finset);
-//             
-//             calc.calculateFinGeometry();
-//             FlightConditions cond = new FlightConditions(new Configuration(rocket));
-//             for (double m=0; m < 3; m+=0.05) {
-//                     cond.setMach(m);
-//                     cond.setAOA(0.0*Math.PI/180);
-//                     double cna = calc.calculateFinCNa1(cond);
-//                     System.out.printf("%5.2f "+cna+"\n", m);
-//             }
-//             
-//     }
-
+       //      @SuppressWarnings("null")
+       //      public static void main(String arg[]) {
+       //              Rocket rocket = TestRocket.makeRocket();
+       //              FinSet finset = null;
+       //              
+       //              Iterator<RocketComponent> iter = rocket.deepIterator();
+       //              while (iter.hasNext()) {
+       //                      RocketComponent c = iter.next();
+       //                      if (c instanceof FinSet) {
+       //                              finset = (FinSet)c;
+       //                              break;
+       //                      }
+       //              }
+       //              
+       //              ((TrapezoidFinSet)finset).setHeight(0.10);
+       //              ((TrapezoidFinSet)finset).setRootChord(0.10);
+       //              ((TrapezoidFinSet)finset).setTipChord(0.10);
+       //              ((TrapezoidFinSet)finset).setSweep(0.0);
+       //
+       //              
+       //              FinSetCalc calc = new FinSetCalc(finset);
+       //              
+       //              calc.calculateFinGeometry();
+       //              FlightConditions cond = new FlightConditions(new Configuration(rocket));
+       //              for (double m=0; m < 3; m+=0.05) {
+       //                      cond.setMach(m);
+       //                      cond.setAOA(0.0*Math.PI/180);
+       //                      double cna = calc.calculateFinCNa1(cond);
+       //                      System.out.printf("%5.2f "+cna+"\n", m);
+       //              }
+       //              
+       //      }
+       
 
        @Override
        public double calculatePressureDragForce(FlightConditions conditions,
                        double stagnationCD, double baseCD, WarningSet warnings) {
-
-               // Compute and cache the fin geometry
-               if (Double.isNaN(cosGammaLead)) {
-                       calculateFinGeometry();
-                       calculatePoly();
-               }
-
                
-               FinSet.CrossSection profile = component.getCrossSection();
                double mach = conditions.getMach();
                double drag = 0;
-
+               
                // Pressure fore-drag
-               if (profile == FinSet.CrossSection.AIRFOIL ||
-                               profile == FinSet.CrossSection.ROUNDED) {
+               if (crossSection == FinSet.CrossSection.AIRFOIL ||
+                               crossSection == FinSet.CrossSection.ROUNDED) {
                        
                        // Round leading edge
                        if (mach < 0.9) {
                                drag = Math.pow(1 - pow2(mach), -0.417) - 1;
                        } else if (mach < 1) {
-                               drag = 1 - 1.785 * (mach-0.9);
+                               drag = 1 - 1.785 * (mach - 0.9);
                        } else {
-                               drag = 1.214 - 0.502/pow2(mach) + 0.1095/pow2(pow2(mach));
+                               drag = 1.214 - 0.502 / pow2(mach) + 0.1095 / pow2(pow2(mach));
                        }
                        
-               } else if (profile == FinSet.CrossSection.SQUARE) {
+               } else if (crossSection == FinSet.CrossSection.SQUARE) {
                        drag = stagnationCD;
                } else {
-                       throw new UnsupportedOperationException("Unsupported fin profile: "+profile);
+                       throw new UnsupportedOperationException("Unsupported fin profile: " + crossSection);
                }
                
                // Slanted leading edge
                drag *= pow2(cosGammaLead);
                
                // Trailing edge drag
-               if (profile == FinSet.CrossSection.SQUARE) {
+               if (crossSection == FinSet.CrossSection.SQUARE) {
                        drag += baseCD;
-               } else if (profile == FinSet.CrossSection.ROUNDED) {
-                       drag += baseCD/2;
+               } else if (crossSection == FinSet.CrossSection.ROUNDED) {
+                       drag += baseCD / 2;
                }
                // Airfoil assumed to have zero base drag
                
-               
+
                // Scale to correct reference area
-               drag *= component.getFinCount() * component.getSpan() * component.getThickness() /
-                               conditions.getRefArea();
+               drag *= finCount * span * thickness / conditions.getRefArea();
                
                return drag;
        }
        
        
-       private int getInterferenceFinCount() {
-               if (interferenceFinCount < 1) {
-
-                       RocketComponent parent = component.getParent();
-                       if (parent == null) {
-                               throw new IllegalStateException("fin set without parent component");
-                       }
-                       
-                       double lead = component.toRelative(Coordinate.NUL, parent)[0].x;
-                       double trail = component.toRelative(new Coordinate(component.getLength()), 
+       private void calculateInterferenceFinCount(FinSet component) {
+               RocketComponent parent = component.getParent();
+               if (parent == null) {
+                       throw new IllegalStateException("fin set without parent component");
+               }
+               
+               double lead = component.toRelative(Coordinate.NUL, parent)[0].x;
+               double trail = component.toRelative(new Coordinate(component.getLength()),
                                        parent)[0].x;
-                       
-                       /*
-                        * The counting fails if the fin root chord is very small, in that case assume
-                        * no other fin interference than this fin set.
-                        */
-                       if (trail-lead < 0.007) {
-                               interferenceFinCount = component.getFinCount();
-                       } else {
-                               interferenceFinCount = 0;
-                               for (RocketComponent c: parent.getChildren()) {
-                                       if (c instanceof FinSet) {
-                                               double finLead = c.toRelative(Coordinate.NUL, parent)[0].x;
-                                               double finTrail = c.toRelative(new Coordinate(c.getLength()), parent)[0].x;
-
-                                               // Compute overlap of the fins
-
-                                               if ((finLead < trail - 0.005) && (finTrail > lead + 0.005)) {
-                                                       interferenceFinCount += ((FinSet)c).getFinCount();
-                                               }
+               
+               /*
+                * The counting fails if the fin root chord is very small, in that case assume
+                * no other fin interference than this fin set.
+                */
+               if (trail - lead < 0.007) {
+                       interferenceFinCount = finCount;
+               } else {
+                       interferenceFinCount = 0;
+                       for (RocketComponent c : parent.getChildren()) {
+                               if (c instanceof FinSet) {
+                                       double finLead = c.toRelative(Coordinate.NUL, parent)[0].x;
+                                       double finTrail = c.toRelative(new Coordinate(c.getLength()), parent)[0].x;
+                                       
+                                       // Compute overlap of the fins
+                                       
+                                       if ((finLead < trail - 0.005) && (finTrail > lead + 0.005)) {
+                                               interferenceFinCount += ((FinSet) c).getFinCount();
                                        }
                                }
                        }
-                       if (interferenceFinCount < component.getFinCount()) {
-                               throw new BugException("Counted " + interferenceFinCount + " parallel fins, " +
-                                               "when component itself has " + component.getFinCount() + 
+               }
+               if (interferenceFinCount < component.getFinCount()) {
+                       throw new BugException("Counted " + interferenceFinCount + " parallel fins, " +
+                                               "when component itself has " + component.getFinCount() +
                                                ", fin points=" + Arrays.toString(component.getFinPoints()));
-                       }
                }
-               return interferenceFinCount;
        }
        
 }
index 4f74bf1ecbe1d2600f7d2f59c830334c06269a6f..f1f73f6057681d6ae987bbb698b996005cfb54b4 100644 (file)
@@ -32,37 +32,45 @@ public class SymmetricComponentCalc extends RocketComponentCalc {
        
        public static final double BODY_LIFT_K = 1.1;
        
-       private final SymmetricComponent component;
-       
        private final double length;
-       private final double r1, r2;
+       private final double foreRadius, aftRadius;
        private final double fineness;
        private final Transition.Shape shape;
        private final double param;
-       private final double area;
+       private final double frontalArea;
+       private final double fullVolume;
+       private final double planformArea, planformCenter;
+       private final double sinphi;
        
        public SymmetricComponentCalc(RocketComponent c) {
                super(c);
                if (!(c instanceof SymmetricComponent)) {
                        throw new IllegalArgumentException("Illegal component type " + c);
                }
-               this.component = (SymmetricComponent) c;
+               SymmetricComponent component = (SymmetricComponent) c;
                
 
                length = component.getLength();
-               r1 = component.getForeRadius();
-               r2 = component.getAftRadius();
+               foreRadius = component.getForeRadius();
+               aftRadius = component.getAftRadius();
                
-               fineness = length / (2 * Math.abs(r2 - r1));
+               fineness = length / (2 * Math.abs(aftRadius - foreRadius));
+               fullVolume = component.getFullVolume();
+               planformArea = component.getComponentPlanformArea();
+               planformCenter = component.getComponentPlanformCenter();
                
                if (component instanceof BodyTube) {
                        shape = null;
                        param = 0;
-                       area = 0;
+                       frontalArea = 0;
+                       sinphi = 0;
                } else if (component instanceof Transition) {
                        shape = ((Transition) component).getType();
                        param = ((Transition) component).getShapeParameter();
-                       area = Math.abs(Math.PI * (r1 * r1 - r2 * r2));
+                       frontalArea = Math.abs(Math.PI * (foreRadius * foreRadius - aftRadius * aftRadius));
+                       
+                       double r = component.getRadius(0.99 * length);
+                       sinphi = (aftRadius - r) / MathUtil.hypot(aftRadius - r, 0.01 * length);
                } else {
                        throw new UnsupportedOperationException("Unknown component type " +
                                        component.getComponentName());
@@ -91,8 +99,8 @@ public class SymmetricComponentCalc extends RocketComponentCalc {
                
                // Pre-calculate and store the results
                if (Double.isNaN(cnaCache)) {
-                       final double r0 = component.getForeRadius();
-                       final double r1 = component.getAftRadius();
+                       final double r0 = foreRadius;
+                       final double r1 = aftRadius;
                        
                        if (MathUtil.equals(r0, r1)) {
                                isTube = true;
@@ -105,7 +113,7 @@ public class SymmetricComponentCalc extends RocketComponentCalc {
                                
                                cnaCache = 2 * (A1 - A0);
                                System.out.println("cnaCache = " + cnaCache);
-                               cpCache = (component.getLength() * A1 - component.getFullVolume()) / (A1 - A0);
+                               cpCache = (length * A1 - fullVolume) / (A1 - A0);
                        }
                }
                
@@ -143,8 +151,6 @@ public class SymmetricComponentCalc extends RocketComponentCalc {
         * Calculate the body lift effect according to Galejs.
         */
        protected Coordinate getLiftCP(FlightConditions conditions, WarningSet warnings) {
-               double area = component.getComponentPlanformArea();
-               double center = component.getComponentPlanformCenter();
                
                /*
                 * Without this extra multiplier the rocket may become unstable at apogee
@@ -159,7 +165,7 @@ public class SymmetricComponentCalc extends RocketComponentCalc {
                        mul = pow2(conditions.getMach() / 0.05);
                }
                
-               return new Coordinate(center, 0, 0, mul * BODY_LIFT_K * area / conditions.getRefArea() *
+               return new Coordinate(planformCenter, 0, 0, mul * BODY_LIFT_K * planformArea / conditions.getRefArea() *
                                conditions.getSinAOA() * conditions.getSincAOA()); // sin(aoa)^2 / aoa
        }
        
@@ -171,32 +177,24 @@ public class SymmetricComponentCalc extends RocketComponentCalc {
        public double calculatePressureDragForce(FlightConditions conditions,
                        double stagnationCD, double baseCD, WarningSet warnings) {
                
-               if (component instanceof BodyTube)
-                       return 0;
-               
-               if (!(component instanceof Transition)) {
-                       throw new BugException("Pressure calculation of unknown type: " +
-                                       component.getComponentName());
-               }
-               
                // Check for simple cases first
-               if (r1 == r2)
+               if (MathUtil.equals(foreRadius, aftRadius))
                        return 0;
                
                if (length < 0.001) {
-                       if (r1 < r2) {
-                               return stagnationCD * area / conditions.getRefArea();
+                       if (foreRadius < aftRadius) {
+                               return stagnationCD * frontalArea / conditions.getRefArea();
                        } else {
-                               return baseCD * area / conditions.getRefArea();
+                               return baseCD * frontalArea / conditions.getRefArea();
                        }
                }
                
 
                // Boattail drag computed directly from base drag
-               if (r2 < r1) {
+               if (aftRadius < foreRadius) {
                        if (fineness >= 3)
                                return 0;
-                       double cd = baseCD * area / conditions.getRefArea();
+                       double cd = baseCD * frontalArea / conditions.getRefArea();
                        if (fineness <= 1)
                                return cd;
                        return cd * (3 - fineness) / 2;
@@ -208,7 +206,7 @@ public class SymmetricComponentCalc extends RocketComponentCalc {
                        calculateNoseInterpolator();
                }
                
-               return interpolator.getValue(conditions.getMach()) * area / conditions.getRefArea();
+               return interpolator.getValue(conditions.getMach()) * frontalArea / conditions.getRefArea();
        }
        
        
@@ -284,9 +282,7 @@ public class SymmetricComponentCalc extends RocketComponentCalc {
                
                interpolator = new LinearInterpolator();
                
-               double r = component.getRadius(0.99 * length);
-               double sinphi = (r2 - r) / MathUtil.hypot(r2 - r, 0.01 * length);
-               
+
                /*
                 * Take into account nose cone shape.  Conical and ogive generate the interpolator
                 * directly.  Others store a interpolator for fineness ratio 3 into int1, or
index a9b0d0445e237097d0e27abe99c76bc71731fb08..99cf30b64f2a7bdd0997134497cb2109ce618795 100644 (file)
@@ -1,13 +1,13 @@
 package net.sf.openrocket.communication;
 
-import java.util.ArrayList;
 import java.util.List;
 
+import net.sf.openrocket.util.ArrayList;
 import net.sf.openrocket.util.ComparablePair;
 import net.sf.openrocket.util.Prefs;
 
 public class UpdateInfo {
-
+       
        private final String latestVersion;
        
        private final ArrayList<ComparablePair<Integer, String>> updates;
@@ -22,8 +22,8 @@ public class UpdateInfo {
                this.latestVersion = version;
                this.updates = new ArrayList<ComparablePair<Integer, String>>(updates);
        }
-
-
+       
+       
 
        /**
         * Get the latest OpenRocket version.  If it is the current version, then the value
@@ -34,17 +34,16 @@ public class UpdateInfo {
        public String getLatestVersion() {
                return latestVersion;
        }
-
-
+       
+       
        /**
         * Return a list of the new features/updates that are available.  The list has a
         * priority for each update and a message text.  The returned list may be modified.
         * 
         * @return      a modifiable list of the updates.
         */
-       @SuppressWarnings("unchecked")
        public List<ComparablePair<Integer, String>> getUpdates() {
-               return (List<ComparablePair<Integer, String>>) updates.clone();
+               return updates.clone();
        }
        
        @Override
index dfd7cb55cb497d26f6271cd6e7c8c1c2db9b0bdf..99c422e314c1a515217e5e758539e98ce799cac4 100644 (file)
@@ -1,7 +1,6 @@
 package net.sf.openrocket.database;
 
 import java.text.Collator;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.IdentityHashMap;
@@ -14,9 +13,10 @@ import java.util.regex.Pattern;
 import net.sf.openrocket.motor.DesignationComparator;
 import net.sf.openrocket.motor.Manufacturer;
 import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.Motor.Type;
 import net.sf.openrocket.motor.MotorDigest;
 import net.sf.openrocket.motor.ThrustCurveMotor;
-import net.sf.openrocket.motor.Motor.Type;
+import net.sf.openrocket.util.ArrayList;
 import net.sf.openrocket.util.MathUtil;
 
 public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> {
@@ -153,9 +153,8 @@ public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> {
        }
        
        
-       @SuppressWarnings("unchecked")
        public List<ThrustCurveMotor> getMotors() {
-               return (List<ThrustCurveMotor>) motors.clone();
+               return motors.clone();
        }
        
        
index 02a91e82053d8d2f62a29013a750624434fc84f0..25b149e45374eb9ca60f1aa7e4d8a516cc552eeb 100644 (file)
@@ -2,7 +2,6 @@ package net.sf.openrocket.document;
 
 import java.awt.event.ActionEvent;
 import java.io.File;
-import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -20,6 +19,7 @@ import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
 import net.sf.openrocket.rocketcomponent.Configuration;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.ArrayList;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.Icons;
 
@@ -155,9 +155,8 @@ public class OpenRocketDocument implements ComponentChangeListener {
 
 
 
-       @SuppressWarnings("unchecked")
        public List<Simulation> getSimulations() {
-               return (ArrayList<Simulation>) simulations.clone();
+               return simulations.clone();
        }
        
        public int getSimulationCount() {
@@ -444,7 +443,11 @@ public class OpenRocketDocument implements ComponentChangeListener {
                        undoDescription.add(null);
                }
                
+               rocket.checkComponentStructure();
+               undoHistory.get(undoPosition).checkComponentStructure();
+               undoHistory.get(undoPosition).copyWithOriginalID().checkComponentStructure();
                rocket.loadFrom(undoHistory.get(undoPosition).copyWithOriginalID());
+               rocket.checkComponentStructure();
        }
        
        
@@ -531,6 +534,7 @@ public class OpenRocketDocument implements ComponentChangeListener {
                
                
                // Actual action to make
+               @Override
                public void actionPerformed(ActionEvent e) {
                        switch (type) {
                        case UNDO:
index 9d5d0cd35169c62b6ce1fcbeab492e0987943ead..7b4e0f251fc02541f57a2dd4c56e58fafce8da89 100644 (file)
@@ -1,6 +1,5 @@
 package net.sf.openrocket.document;
 
-import java.util.ArrayList;
 import java.util.List;
 
 import javax.swing.event.ChangeEvent;
@@ -25,6 +24,7 @@ import net.sf.openrocket.simulation.exception.SimulationException;
 import net.sf.openrocket.simulation.exception.SimulationListenerException;
 import net.sf.openrocket.simulation.listeners.SimulationListener;
 import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.ArrayList;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.ChangeSource;
 import net.sf.openrocket.util.SafetyMutex;
@@ -57,7 +57,7 @@ public class Simulation implements ChangeSource, Cloneable {
                NOT_SIMULATED
        }
        
-       private final SafetyMutex mutex = new SafetyMutex();
+       private SafetyMutex mutex = SafetyMutex.newInstance();
        
        private final Rocket rocket;
        
@@ -372,16 +372,16 @@ public class Simulation implements ChangeSource, Cloneable {
         *  
         * @return      a copy of this simulation and its conditions.
         */
-       @SuppressWarnings("unchecked")
        public Simulation copy() {
                mutex.lock("copy");
                try {
                        
                        Simulation copy = (Simulation) super.clone();
                        
+                       copy.mutex = SafetyMutex.newInstance();
                        copy.status = Status.NOT_SIMULATED;
                        copy.conditions = this.conditions.clone();
-                       copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
+                       copy.simulationListeners = this.simulationListeners.clone();
                        copy.listeners = new ArrayList<ChangeListener>();
                        copy.simulatedConditions = null;
                        copy.simulatedMotors = null;
@@ -405,7 +405,6 @@ public class Simulation implements ChangeSource, Cloneable {
         * @param newRocket             the rocket for the new simulation.
         * @return                              a new simulation with the same conditions and properties.
         */
-       @SuppressWarnings("unchecked")
        public Simulation duplicateSimulation(Rocket newRocket) {
                mutex.lock("duplicateSimulation");
                try {
@@ -413,7 +412,7 @@ public class Simulation implements ChangeSource, Cloneable {
                        
                        copy.name = this.name;
                        copy.conditions.copyFrom(this.conditions);
-                       copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
+                       copy.simulationListeners = this.simulationListeners.clone();
                        copy.simulationStepperClass = this.simulationStepperClass;
                        copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
                        
index 7cadcf4bcf2a8043b9f6c30fb7eaab31f895d36c..85085259809919d9d4f4d92e102cd8c131b07f67 100644 (file)
@@ -130,7 +130,7 @@ public class OpenRocketSaver extends RocketSaver {
                // Size per component
                int componentCount = 0;
                Rocket rocket = doc.getRocket();
-               Iterator<RocketComponent> iterator = rocket.deepIterator(true);
+               Iterator<RocketComponent> iterator = rocket.iterator(true);
                while (iterator.hasNext()) {
                        iterator.next();
                        componentCount++;
@@ -194,7 +194,7 @@ public class OpenRocketSaver extends RocketSaver {
                 */
 
                // Check for motor definitions (version 1.2)
-               Iterator<RocketComponent> iterator = document.getRocket().deepIterator();
+               Iterator<RocketComponent> iterator = document.getRocket().iterator();
                while (iterator.hasNext()) {
                        RocketComponent c = iterator.next();
                        if (!(c instanceof MotorMount))
@@ -209,7 +209,7 @@ public class OpenRocketSaver extends RocketSaver {
                }
                
                // Check for fin tabs (version 1.1)
-               iterator = document.getRocket().deepIterator();
+               iterator = document.getRocket().iterator();
                while (iterator.hasNext()) {
                        RocketComponent c = iterator.next();
                        
@@ -274,7 +274,7 @@ public class OpenRocketSaver extends RocketSaver {
                        writeln("<subcomponents>");
                        indent++;
                        boolean emptyline = false;
-                       for (RocketComponent subcomponent : component) {
+                       for (RocketComponent subcomponent : component.getChildren()) {
                                if (emptyline)
                                        writeln("");
                                emptyline = true;
@@ -290,7 +290,6 @@ public class OpenRocketSaver extends RocketSaver {
        }
        
        
-
        private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
                GUISimulationConditions cond = simulation.getConditions();
                
index 08b9ef0ab28bcf632452f0542bd03a3a284bd13e..3ec6bdc11d57a0295ae6edefd82a21aeea705947 100644 (file)
@@ -1,7 +1,8 @@
 package net.sf.openrocket.file.simplesax;
 
+import java.util.ArrayDeque;
+import java.util.Deque;
 import java.util.HashMap;
-import java.util.Stack;
 
 import net.sf.openrocket.aerodynamics.Warning;
 import net.sf.openrocket.aerodynamics.WarningSet;
@@ -16,36 +17,35 @@ import org.xml.sax.helpers.DefaultHandler;
  */
 class DelegatorHandler extends DefaultHandler {
        private final WarningSet warnings;
-
-       private final Stack<ElementHandler> handlerStack = new Stack<ElementHandler>();
-       private final Stack<StringBuilder> elementData = new Stack<StringBuilder>();
-       private final Stack<HashMap<String, String>> elementAttributes = 
-               new Stack<HashMap<String, String>>();
-
+       
+       private final Deque<ElementHandler> handlerStack = new ArrayDeque<ElementHandler>();
+       private final Deque<StringBuilder> elementData = new ArrayDeque<StringBuilder>();
+       private final Deque<HashMap<String, String>> elementAttributes = new ArrayDeque<HashMap<String, String>>();
+       
 
        // Ignore all elements as long as ignore > 0
        private int ignore = 0;
-
-
+       
+       
        public DelegatorHandler(ElementHandler initialHandler, WarningSet warnings) {
                this.warnings = warnings;
                handlerStack.add(initialHandler);
                elementData.add(new StringBuilder()); // Just in case
        }
-
-
+       
+       
        /////////  SAX handlers
-
+       
        @Override
        public void startElement(String uri, String localName, String name,
                        Attributes attributes) throws SAXException {
-
+               
                // Check for ignore
                if (ignore > 0) {
                        ignore++;
                        return;
                }
-
+               
                // Check for unknown namespace
                if (!uri.equals("")) {
                        warnings.add(Warning.fromString("Unknown namespace element '" + uri
@@ -53,11 +53,11 @@ class DelegatorHandler extends DefaultHandler {
                        ignore++;
                        return;
                }
-
+               
                // Add layer to data stacks
                elementData.push(new StringBuilder());
                elementAttributes.push(copyAttributes(attributes));
-
+               
                // Call the handler
                ElementHandler h = handlerStack.peek();
                h = h.openElement(localName, elementAttributes.peek(), warnings);
@@ -68,8 +68,8 @@ class DelegatorHandler extends DefaultHandler {
                        ignore++;
                }
        }
-
-
+       
+       
        /**
         * Stores encountered characters in the elementData stack.
         */
@@ -78,28 +78,28 @@ class DelegatorHandler extends DefaultHandler {
                // Check for ignore
                if (ignore > 0)
                        return;
-
+               
                StringBuilder sb = elementData.peek();
                sb.append(chars, start, length);
        }
-
-
+       
+       
        /**
         * Removes the last layer from the stack.
         */
        @Override
        public void endElement(String uri, String localName, String name) throws SAXException {
-
+               
                // Check for ignore
                if (ignore > 0) {
                        ignore--;
                        return;
                }
-
+               
                // Remove data from stack
                String data = elementData.pop().toString(); // throws on error
                HashMap<String, String> attr = elementAttributes.pop();
-
+               
                // Remove last handler and call the next one
                ElementHandler h;
                
@@ -109,8 +109,8 @@ class DelegatorHandler extends DefaultHandler {
                h = handlerStack.peek();
                h.closeElement(localName, attr, data, warnings);
        }
-
-
+       
+       
        private static HashMap<String, String> copyAttributes(Attributes atts) {
                HashMap<String, String> ret = new HashMap<String, String>();
                for (int i = 0; i < atts.getLength(); i++) {
@@ -119,4 +119,3 @@ class DelegatorHandler extends DefaultHandler {
                return ret;
        }
 }
-
index 2aa651c642d670233499a2c8bded21997683d043..a88f4b892d8a56aadbb779781ff41b60710e114f 100644 (file)
@@ -2,6 +2,7 @@ package net.sf.openrocket.gui.adaptors;
 
 import java.awt.Component;
 import java.awt.event.ActionEvent;
+import java.beans.PropertyChangeListener;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -15,6 +16,9 @@ import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.ChangeSource;
+import net.sf.openrocket.util.Invalidatable;
+import net.sf.openrocket.util.Invalidator;
+import net.sf.openrocket.util.MemoryManagement;
 import net.sf.openrocket.util.Reflection;
 
 
@@ -34,7 +38,7 @@ import net.sf.openrocket.util.Reflection;
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 
-public class BooleanModel extends AbstractAction implements ChangeListener {
+public class BooleanModel extends AbstractAction implements ChangeListener, Invalidatable {
        private static final LogHelper log = Application.getLogger();
        
        private final ChangeSource source;
@@ -54,6 +58,9 @@ public class BooleanModel extends AbstractAction implements ChangeListener {
        private boolean oldValue;
        private boolean oldEnabled;
        
+       private Invalidator invalidator = new Invalidator(this);
+       
+       
        public BooleanModel(ChangeSource source, String valueName) {
                this.source = source;
                this.valueName = valueName;
@@ -112,9 +119,10 @@ public class BooleanModel extends AbstractAction implements ChangeListener {
        }
        
        public void setValue(boolean b) {
+               checkState(true);
                log.debug("Setting value of " + this + " to " + b);
                try {
-                       setMethod.invoke(source, new Object[] { (Boolean) b });
+                       setMethod.invoke(source, new Object[] { b });
                } catch (IllegalAccessException e) {
                        throw new BugException("setMethod execution error for source " + source, e);
                } catch (InvocationTargetException e) {
@@ -132,6 +140,7 @@ public class BooleanModel extends AbstractAction implements ChangeListener {
         * @param enableState   the state in which the component should be enabled.
         */
        public void addEnableComponent(Component component, boolean enableState) {
+               checkState(true);
                components.add(component);
                componentEnableState.add(enableState);
                updateEnableStatus();
@@ -145,6 +154,7 @@ public class BooleanModel extends AbstractAction implements ChangeListener {
         * @see #addEnableComponent(Component, boolean)
         */
        public void addEnableComponent(Component component) {
+               checkState(true);
                addEnableComponent(component, true);
        }
        
@@ -174,6 +184,8 @@ public class BooleanModel extends AbstractAction implements ChangeListener {
        
        @Override
        public void stateChanged(ChangeEvent event) {
+               checkState(true);
+               
                if (firing > 0) {
                        log.debug("Ignoring stateChanged of " + this + ", currently firing events");
                        return;
@@ -219,6 +231,43 @@ public class BooleanModel extends AbstractAction implements ChangeListener {
                }
        }
        
+       
+       @Override
+       public void addPropertyChangeListener(PropertyChangeListener listener) {
+               checkState(true);
+               super.addPropertyChangeListener(listener);
+       }
+       
+       
+       /**
+        * Invalidates this model by removing all listeners and removing this from
+        * listening to the source.  After invalidation no listeners can be added to this
+        * model and the value cannot be set.
+        */
+       @Override
+       public void invalidate() {
+               invalidator.invalidate();
+               
+               PropertyChangeListener[] listeners = this.getPropertyChangeListeners();
+               if (listeners.length > 0) {
+                       log.warn("Invalidating " + this + " while still having listeners " + listeners);
+                       for (PropertyChangeListener l : listeners) {
+                               this.removePropertyChangeListener(l);
+                       }
+               }
+               if (source != null) {
+                       source.removeChangeListener(this);
+               }
+               MemoryManagement.collectable(this);
+       }
+       
+       
+       private void checkState(boolean error) {
+               invalidator.check(error);
+       }
+       
+       
+
        @Override
        public String toString() {
                if (toString == null) {
index a81381dca284088a41cd9ec4b6ff32feb0231474..7b3783a03cea624fb3ddc0bdc17b1e3b50701e1f 100644 (file)
@@ -21,7 +21,10 @@ import net.sf.openrocket.unit.Unit;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.ChangeSource;
+import net.sf.openrocket.util.Invalidatable;
+import net.sf.openrocket.util.Invalidator;
 import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.MemoryManagement;
 import net.sf.openrocket.util.Reflection;
 
 
@@ -39,7 +42,7 @@ import net.sf.openrocket.util.Reflection;
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 
-public class DoubleModel implements ChangeListener, ChangeSource {
+public class DoubleModel implements ChangeListener, ChangeSource, Invalidatable {
        private static final LogHelper log = Application.getLogger();
        
 
@@ -51,7 +54,7 @@ public class DoubleModel implements ChangeListener, ChangeSource {
         * Model suitable for JSpinner using JSpinner.NumberEditor.  It extends SpinnerNumberModel
         * to be compatible with the NumberEditor, but only has the necessary methods defined.
         */
-       private class ValueSpinnerModel extends SpinnerNumberModel {
+       private class ValueSpinnerModel extends SpinnerNumberModel implements Invalidatable {
                
                @Override
                public Object getValue() {
@@ -121,6 +124,11 @@ public class DoubleModel implements ChangeListener, ChangeSource {
                public void removeChangeListener(ChangeListener l) {
                        DoubleModel.this.removeChangeListener(l);
                }
+               
+               @Override
+               public void invalidate() {
+                       DoubleModel.this.invalidate();
+               }
        }
        
        /**
@@ -139,7 +147,7 @@ public class DoubleModel implements ChangeListener, ChangeSource {
 
        ////////////  JSlider model  ////////////
        
-       private class ValueSliderModel implements BoundedRangeModel, ChangeListener {
+       private class ValueSliderModel implements BoundedRangeModel, ChangeListener, Invalidatable {
                private static final int MAX = 1000;
                
                /*
@@ -234,6 +242,7 @@ public class DoubleModel implements ChangeListener, ChangeSource {
                        return x * x;
                }
                
+               @Override
                public int getValue() {
                        double value = DoubleModel.this.getValue();
                        if (value <= min.getValue())
@@ -258,6 +267,7 @@ public class DoubleModel implements ChangeListener, ChangeSource {
                }
                
                
+               @Override
                public void setValue(int newValue) {
                        if (firing > 0) {
                                // Ignore loops
@@ -290,52 +300,67 @@ public class DoubleModel implements ChangeListener, ChangeSource {
                // Static get-methods
                private boolean isAdjusting;
                
+               @Override
                public int getExtent() {
                        return 0;
                }
                
+               @Override
                public int getMaximum() {
                        return MAX;
                }
                
+               @Override
                public int getMinimum() {
                        return 0;
                }
                
+               @Override
                public boolean getValueIsAdjusting() {
                        return isAdjusting;
                }
                
                // Ignore set-values
+               @Override
                public void setExtent(int newExtent) {
                }
                
+               @Override
                public void setMaximum(int newMaximum) {
                }
                
+               @Override
                public void setMinimum(int newMinimum) {
                }
                
+               @Override
                public void setValueIsAdjusting(boolean b) {
                        isAdjusting = b;
                }
                
+               @Override
                public void setRangeProperties(int value, int extent, int min, int max, boolean adjusting) {
                        setValueIsAdjusting(adjusting);
                        setValue(value);
                }
                
                // Pass change listeners to the underlying model
+               @Override
                public void addChangeListener(ChangeListener l) {
                        DoubleModel.this.addChangeListener(l);
                }
                
+               @Override
                public void removeChangeListener(ChangeListener l) {
                        DoubleModel.this.removeChangeListener(l);
                }
                
+               @Override
+               public void invalidate() {
+                       DoubleModel.this.invalidate();
+               }
                
-
+               @Override
                public void stateChanged(ChangeEvent e) {
                        // Min or max range has changed.
                        // Fire if not already firing
@@ -367,7 +392,7 @@ public class DoubleModel implements ChangeListener, ChangeSource {
 
        ////////////  Action model  ////////////
        
-       private class AutomaticActionModel extends AbstractAction implements ChangeListener {
+       private class AutomaticActionModel extends AbstractAction implements ChangeListener, Invalidatable {
                private boolean oldValue = false;
                
                public AutomaticActionModel() {
@@ -427,6 +452,7 @@ public class DoubleModel implements ChangeListener, ChangeSource {
                }
                
                // If the value has changed, generate an event to the listeners
+               @Override
                public void stateChanged(ChangeEvent e) {
                        boolean newValue = isAutomatic();
                        if (oldValue == newValue)
@@ -440,10 +466,15 @@ public class DoubleModel implements ChangeListener, ChangeSource {
                        }
                }
                
+               @Override
                public void actionPerformed(ActionEvent e) {
                        // Setting performed in putValue
                }
                
+               @Override
+               public void invalidate() {
+                       DoubleModel.this.invalidate();
+               }
        }
        
        /**
@@ -494,6 +525,8 @@ public class DoubleModel implements ChangeListener, ChangeSource {
        private double lastValue = 0;
        private boolean lastAutomatic = false;
        
+       private Invalidator invalidator = new Invalidator(this);
+       
        
        public DoubleModel(double value) {
                this(value, UnitGroup.UNITS_NONE, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
@@ -639,6 +672,8 @@ public class DoubleModel implements ChangeListener, ChangeSource {
         * @param v New value for parameter in SI units.
         */
        public void setValue(double v) {
+               checkState(true);
+               
                log.debug("Setting value " + v + " for " + this);
                if (setMethod == null) {
                        if (getMethod != null) {
@@ -693,6 +728,8 @@ public class DoubleModel implements ChangeListener, ChangeSource {
         * state change event if automatic setting is not available.
         */
        public void setAutomatic(boolean auto) {
+               checkState(true);
+               
                if (setAutoMethod == null) {
                        log.debug("Setting automatic to " + auto + " for " + this + ", automatic not available");
                        fireStateChanged(); // in case something is out-of-sync
@@ -726,6 +763,7 @@ public class DoubleModel implements ChangeListener, ChangeSource {
         * @param u  The unit to set active.
         */
        public void setCurrentUnit(Unit u) {
+               checkState(true);
                if (currentUnit == u)
                        return;
                log.debug("Setting unit for " + this + " to '" + u + "'");
@@ -750,7 +788,10 @@ public class DoubleModel implements ChangeListener, ChangeSource {
         * is the first listener.
         * @param l Listener to add.
         */
+       @Override
        public void addChangeListener(ChangeListener l) {
+               checkState(true);
+               
                if (listeners.isEmpty()) {
                        if (source != null) {
                                source.addChangeListener(this);
@@ -768,7 +809,10 @@ public class DoubleModel implements ChangeListener, ChangeSource {
         * if this was the last listener of the model.
         * @param l Listener to remove.
         */
+       @Override
        public void removeChangeListener(ChangeListener l) {
+               checkState(false);
+               
                listeners.remove(l);
                if (listeners.isEmpty() && source != null) {
                        source.removeChangeListener(this);
@@ -777,6 +821,32 @@ public class DoubleModel implements ChangeListener, ChangeSource {
        }
        
        
+       /**
+        * Invalidates this model by removing all listeners and removing this from
+        * listening to the source.  After invalidation no listeners can be added to this
+        * model and the value cannot be set.
+        */
+       @Override
+       public void invalidate() {
+               log.verbose("Invalidating " + this);
+               invalidator.invalidate();
+               
+               if (!listeners.isEmpty()) {
+                       log.warn("Invalidating " + this + " while still having listeners " + listeners);
+               }
+               listeners.clear();
+               if (source != null) {
+                       source.removeChangeListener(this);
+               }
+               MemoryManagement.collectable(this);
+       }
+       
+       
+       private void checkState(boolean error) {
+               invalidator.check(error);
+       }
+       
+       
        @Override
        protected void finalize() throws Throwable {
                super.finalize();
@@ -790,6 +860,8 @@ public class DoubleModel implements ChangeListener, ChangeSource {
         * Fire a ChangeEvent to all listeners.
         */
        protected void fireStateChanged() {
+               checkState(true);
+               
                Object[] l = listeners.toArray();
                ChangeEvent event = new ChangeEvent(this);
                firing++;
@@ -802,7 +874,10 @@ public class DoubleModel implements ChangeListener, ChangeSource {
         * Called when the component changes.  Checks whether the modeled value has changed, and if
         * it has, updates lastValue and generates ChangeEvents for all listeners of the model.
         */
+       @Override
        public void stateChanged(ChangeEvent e) {
+               checkState(true);
+               
                double v = getValue();
                boolean b = isAutomatic();
                if (lastValue == v && lastAutomatic == b)
index e37d80ee93b4f9d612f6b0876dab8cae8c722bb7..4b4db2d8b05f4bb9208dfb052ae13b873c951767 100644 (file)
@@ -109,7 +109,7 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis
                                findDialogContentsConstructor(component);
                if (c != null) {
                        try {
-                               return (RocketComponentConfig) c.newInstance(component);
+                               return c.newInstance(component);
                        } catch (InstantiationException e) {
                                throw new BugException("BUG in constructor reflection", e);
                        } catch (IllegalAccessException e) {
@@ -124,6 +124,34 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis
                throw new BugException("Unable to find any configurator for " + component);
        }
        
+       
+       private void closeDialog() {
+               this.setVisible(false);
+               this.dispose();
+               this.configurator.invalidateModels();
+       }
+       
+       
+       @Override
+       public void componentChanged(ComponentChangeEvent e) {
+               if (e.isTreeChange() || e.isUndoChange()) {
+                       
+                       // Hide dialog in case of tree or undo change
+                       dialog.closeDialog();
+                       
+               } else {
+                       /*
+                        * TODO: HIGH:  The line below has caused a NullPointerException (without null check)
+                        * How is this possible?  The null check was added to avoid this, but the
+                        * root cause should be analyzed.
+                        * [Openrocket-bugs] 2009-12-12 19:23:22 Automatic bug report for OpenRocket 0.9.5
+                        */
+                       if (configurator != null)
+                               configurator.updateFields();
+               }
+       }
+       
+       
        /**
         * Finds the Constructor of the given component's config dialog panel in 
         * CONFIGDIALOGPACKAGE.
@@ -192,8 +220,9 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis
         * Hides the configuration dialog.  May be used even if not currently visible.
         */
        public static void hideDialog() {
-               if (dialog != null)
-                       dialog.setVisible(false);
+               if (dialog != null) {
+                       dialog.closeDialog();
+               }
        }
        
        
@@ -225,23 +254,4 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis
                return (dialog != null) && (dialog.isVisible());
        }
        
-       
-       public void componentChanged(ComponentChangeEvent e) {
-               if (e.isTreeChange() || e.isUndoChange()) {
-                       
-                       // Hide dialog in case of tree or undo change
-                       dialog.setVisible(false);
-                       
-               } else {
-                       /*
-                        * TODO: HIGH:  The line below has caused a NullPointerException (without null check)
-                        * How is this possible?  The null check was added to avoid this, but the
-                        * root cause should be analyzed.
-                        * [Openrocket-bugs] 2009-12-12 19:23:22 Automatic bug report for OpenRocket 0.9.5
-                        */
-                       if (configurator != null)
-                               configurator.updateFields();
-               }
-       }
-       
 }
index ccc1174f49fa96b4a9dcf1407c9e9233d23b7303..c4e859852d339893f3bc25f2f91b0899949bbed7 100644 (file)
@@ -16,8 +16,8 @@ import net.sf.openrocket.gui.adaptors.DoubleModel;
 import net.sf.openrocket.gui.adaptors.EnumModel;
 import net.sf.openrocket.gui.components.BasicSlider;
 import net.sf.openrocket.gui.components.StyledLabel;
-import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.gui.components.StyledLabel.Style;
+import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.rocketcomponent.FinSet;
 import net.sf.openrocket.rocketcomponent.FreeformFinSet;
@@ -131,6 +131,10 @@ public abstract class FinSetConfig extends RocketComponentConfig {
                length2 = new DoubleModel(component, "Length", 0.5, UnitGroup.UNITS_LENGTH, 0);
                length_2 = new DoubleModel(component, "Length", -0.5, UnitGroup.UNITS_LENGTH, 0);
                
+               register(length);
+               register(length2);
+               register(length_2);
+               
                ////  Tab length
                label = new JLabel("Tab length:");
                label.setToolTipText("The length of the fin tab.");
index 691fcf7f1d0d228c37da816d38cef59954655e5b..751aa86bac009d1b298b00e30aebe232bed09891 100644 (file)
@@ -13,7 +13,6 @@ import java.awt.event.ActionListener;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.geom.Ellipse2D;
-import java.util.Arrays;
 import java.util.List;
 
 import javax.swing.BorderFactory;
@@ -39,20 +38,21 @@ import net.sf.openrocket.rocketcomponent.InnerTube;
 import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.Coordinate;
 
 
 public class InnerTubeConfig extends ThicknessRingComponentConfig {
-
+       
 
        public InnerTubeConfig(RocketComponent c) {
                super(c);
                
                JPanel tab;
                
-               tab = new MotorConfig((MotorMount)c);
+               tab = new MotorConfig((MotorMount) c);
                tabbedPane.insertTab("Motor", null, tab, "Motor mount configuration", 1);
-
+               
                tab = clusterTab();
                tabbedPane.insertTab("Cluster", null, tab, "Cluster configuration", 2);
                
@@ -69,51 +69,51 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig {
                JPanel subPanel = new JPanel(new MigLayout());
                
                // Cluster type selection
-               subPanel.add(new JLabel("Select cluster configuration:"),"spanx, wrap");
-               subPanel.add(new ClusterSelectionPanel((InnerTube)component),"spanx, wrap");
-//             JPanel clusterSelection = new ClusterSelectionPanel((InnerTube)component);
-//             clusterSelection.setBackground(Color.blue);
-//             subPanel.add(clusterSelection);
+               subPanel.add(new JLabel("Select cluster configuration:"), "spanx, wrap");
+               subPanel.add(new ClusterSelectionPanel((InnerTube) component), "spanx, wrap");
+               //              JPanel clusterSelection = new ClusterSelectionPanel((InnerTube)component);
+               //              clusterSelection.setBackground(Color.blue);
+               //              subPanel.add(clusterSelection);
                
                panel.add(subPanel);
                
-               
-               subPanel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]"));
 
+               subPanel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]"));
+               
                // Tube separation scale
                JLabel l = new JLabel("Tube separation:");
                l.setToolTipText("The separation of the tubes, 1.0 = touching each other");
                subPanel.add(l);
-               DoubleModel dm  = new DoubleModel(component,"ClusterScale",1,UnitGroup.UNITS_NONE,0);
-
+               DoubleModel dm = new DoubleModel(component, "ClusterScale", 1, UnitGroup.UNITS_NONE, 0);
+               
                JSpinner spin = new JSpinner(dm.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText("The separation of the tubes, 1.0 = touching each other");
-               subPanel.add(spin,"growx");
+               subPanel.add(spin, "growx");
                
                BasicSlider bs = new BasicSlider(dm.getSliderModel(0, 1, 4));
                bs.setToolTipText("The separation of the tubes, 1.0 = touching each other");
-               subPanel.add(bs,"skip,w 100lp, wrap");
-
+               subPanel.add(bs, "skip,w 100lp, wrap");
+               
                // Rotation
                l = new JLabel("Rotation:");
                l.setToolTipText("Rotation angle of the cluster configuration");
                subPanel.add(l);
-               dm  = new DoubleModel(component,"ClusterRotation",1,UnitGroup.UNITS_ANGLE,
-                               -Math.PI,Math.PI);
-
+               dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE,
+                               -Math.PI, Math.PI);
+               
                spin = new JSpinner(dm.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                spin.setToolTipText("Rotation angle of the cluster configuration");
-               subPanel.add(spin,"growx");
+               subPanel.add(spin, "growx");
                
-               subPanel.add(new UnitSelector(dm),"growx");
+               subPanel.add(new UnitSelector(dm), "growx");
                bs = new BasicSlider(dm.getSliderModel(-Math.PI, 0, Math.PI));
                bs.setToolTipText("Rotation angle of the cluster configuration");
-               subPanel.add(bs,"w 100lp, wrap para");
-
-               
+               subPanel.add(bs, "w 100lp, wrap para");
                
+
+
                // Split button
                JButton split = new JButton("Split cluster");
                split.setToolTipText("<html>Split the cluster into separate components.<br>" +
@@ -128,29 +128,28 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig {
                                                RocketComponent parent = component.getParent();
                                                int index = parent.getChildPosition(component);
                                                if (index < 0) {
-                                                       throw new IllegalStateException("component="+component+
-                                                                       " parent="+parent+" parent.children=" + 
-                                                                       Arrays.toString(parent.getChildren()));
+                                                       throw new BugException("Inconsistent state: component=" + component +
+                                                                       " parent=" + parent + " parent.children=" + parent.getChildren());
                                                }
-
-                                               InnerTube tube = (InnerTube)component;
+                                               
+                                               InnerTube tube = (InnerTube) component;
                                                if (tube.getClusterCount() <= 1)
                                                        return;
-
+                                               
                                                ComponentConfigDialog.addUndoPosition("Split cluster");
-
+                                               
                                                Coordinate[] coords = { Coordinate.NUL };
                                                coords = component.shiftCoordinates(coords);
                                                parent.removeChild(index);
-                                               for (int i=0; i<coords.length; i++) {
-                                                       InnerTube copy = (InnerTube)component.copy();
+                                               for (int i = 0; i < coords.length; i++) {
+                                                       InnerTube copy = (InnerTube) component.copy();
                                                        copy.setClusterConfiguration(ClusterConfiguration.SINGLE);
                                                        copy.setClusterRotation(0.0);
                                                        copy.setClusterScale(1.0);
                                                        copy.setRadialShift(coords[i].y, coords[i].z);
-                                                       copy.setName(copy.getName() + " #" + (i+1));
+                                                       copy.setName(copy.getName() + " #" + (i + 1));
                                                        
-                                                       parent.addChild(copy, index+i);
+                                                       parent.addChild(copy, index + i);
                                                }
                                        }
                                });
@@ -158,22 +157,22 @@ public class InnerTubeConfig extends ThicknessRingComponentConfig {
                });
                subPanel.add(split, "spanx, split 2, gapright para, sizegroup buttons, right");
                
-               
+
                // Reset button
                JButton reset = new JButton("Reset settings");
                reset.setToolTipText("Reset the separation and rotation to the default values");
                reset.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent arg0) {
-                               ((InnerTube)component).setClusterScale(1.0);
-                               ((InnerTube)component).setClusterRotation(0.0);
+                               ((InnerTube) component).setClusterScale(1.0);
+                               ((InnerTube) component).setClusterRotation(0.0);
                        }
                });
-               subPanel.add(reset,"sizegroup buttons, right");
-               
-               panel.add(subPanel,"grow");
+               subPanel.add(reset, "sizegroup buttons, right");
                
+               panel.add(subPanel, "grow");
                
+
                return panel;
        }
 }
@@ -190,44 +189,44 @@ class ClusterSelectionPanel extends JPanel {
        
        public ClusterSelectionPanel(Clusterable component) {
                super(new MigLayout("gap 0 0",
-                               "["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]",
-                               "["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]"));
+                               "[" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!]",
+                               "[" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!]"));
                
-               for (int i=0; i<ClusterConfiguration.CONFIGURATIONS.length; i++) {
+               for (int i = 0; i < ClusterConfiguration.CONFIGURATIONS.length; i++) {
                        ClusterConfiguration config = ClusterConfiguration.CONFIGURATIONS[i];
                        
-                       JComponent button = new ClusterButton(component,config);
-                       if (i%4 == 3) 
-                               add(button,"wrap");
+                       JComponent button = new ClusterButton(component, config);
+                       if (i % 4 == 3)
+                               add(button, "wrap");
                        else
                                add(button);
                }
-
+               
        }
        
        
        private class ClusterButton extends JPanel implements ChangeListener, MouseListener,
-                                                                                                                 Resettable {
+                                                                                                                       Resettable {
                private Clusterable component;
                private ClusterConfiguration config;
                
                public ClusterButton(Clusterable c, ClusterConfiguration config) {
                        component = c;
                        this.config = config;
-                       setMinimumSize(new Dimension(BUTTON_SIZE,BUTTON_SIZE));
-                       setPreferredSize(new Dimension(BUTTON_SIZE,BUTTON_SIZE));
-                       setMaximumSize(new Dimension(BUTTON_SIZE,BUTTON_SIZE));
+                       setMinimumSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
+                       setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
+                       setMaximumSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
                        setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
-//                     setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
+                       //                      setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
                        component.addChangeListener(this);
                        addMouseListener(this);
                }
                
-
+               
                @Override
                public void paintComponent(Graphics g) {
                        super.paintComponent(g);
-                       Graphics2D g2 = (Graphics2D)g;
+                       Graphics2D g2 = (Graphics2D) g;
                        Rectangle area = g2.getClipBounds();
                        
                        if (component.getClusterConfiguration() == config)
@@ -237,22 +236,22 @@ class ClusterSelectionPanel extends JPanel {
                        
                        g2.fillRect(area.x, area.y, area.width, area.height);
                        
-                       g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 
+                       g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                                        RenderingHints.VALUE_STROKE_NORMALIZE);
-                       g2.setRenderingHint(RenderingHints.KEY_RENDERING, 
+                       g2.setRenderingHint(RenderingHints.KEY_RENDERING,
                                        RenderingHints.VALUE_RENDER_QUALITY);
-                       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
+                       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                        RenderingHints.VALUE_ANTIALIAS_ON);
                        
                        List<Double> points = config.getPoints();
                        Ellipse2D.Float circle = new Ellipse2D.Float();
-                       for (int i=0; i < points.size()/2; i++) {
-                               double x = points.get(i*2);
-                               double y = points.get(i*2+1);
+                       for (int i = 0; i < points.size() / 2; i++) {
+                               double x = points.get(i * 2);
+                               double y = points.get(i * 2 + 1);
                                
-                               double px = BUTTON_SIZE/2 + x*MOTOR_DIAMETER;
-                               double py = BUTTON_SIZE/2 - y*MOTOR_DIAMETER;
-                               circle.setFrameFromCenter(px,py,px+MOTOR_DIAMETER/2,py+MOTOR_DIAMETER/2);
+                               double px = BUTTON_SIZE / 2 + x * MOTOR_DIAMETER;
+                               double py = BUTTON_SIZE / 2 - y * MOTOR_DIAMETER;
+                               circle.setFrameFromCenter(px, py, px + MOTOR_DIAMETER / 2, py + MOTOR_DIAMETER / 2);
                                
                                g2.setColor(MOTOR_FILL_COLOR);
                                g2.fill(circle);
@@ -260,25 +259,39 @@ class ClusterSelectionPanel extends JPanel {
                                g2.draw(circle);
                        }
                }
-
-
+               
+               
+               @Override
                public void stateChanged(ChangeEvent e) {
                        repaint();
                }
-
-
+               
+               
+               @Override
                public void mouseClicked(MouseEvent e) {
                        if (e.getButton() == MouseEvent.BUTTON1) {
                                component.setClusterConfiguration(config);
                        }
                }
                
-               public void mouseEntered(MouseEvent e) { }
-               public void mouseExited(MouseEvent e) { }
-               public void mousePressed(MouseEvent e) { }
-               public void mouseReleased(MouseEvent e) { }
-
-
+               @Override
+               public void mouseEntered(MouseEvent e) {
+               }
+               
+               @Override
+               public void mouseExited(MouseEvent e) {
+               }
+               
+               @Override
+               public void mousePressed(MouseEvent e) {
+               }
+               
+               @Override
+               public void mouseReleased(MouseEvent e) {
+               }
+               
+               
+               @Override
                public void resetModel() {
                        component.removeChangeListener(this);
                        removeMouseListener(this);
index 4bca3bd6433da6400877c3b3caa753d27cdd45ed..d6d607463ebfec9e5cdb30ddc5f17b976e740bb6 100644 (file)
@@ -8,7 +8,9 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.FocusEvent;
 import java.awt.event.FocusListener;
+import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
 
 import javax.swing.BorderFactory;
 import javax.swing.Icon;
@@ -32,54 +34,57 @@ import net.sf.openrocket.gui.adaptors.EnumModel;
 import net.sf.openrocket.gui.adaptors.MaterialModel;
 import net.sf.openrocket.gui.components.BasicSlider;
 import net.sf.openrocket.gui.components.StyledLabel;
-import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.gui.components.StyledLabel.Style;
+import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.rocketcomponent.ComponentAssembly;
 import net.sf.openrocket.rocketcomponent.ExternalComponent;
+import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
 import net.sf.openrocket.rocketcomponent.NoseCone;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Invalidatable;
 import net.sf.openrocket.util.LineStyle;
 import net.sf.openrocket.util.Prefs;
 
 public class RocketComponentConfig extends JPanel {
-
+       
        protected final RocketComponent component;
        protected final JTabbedPane tabbedPane;
        
+       private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
        
+
        protected final JTextField componentNameField;
        protected JTextArea commentTextArea;
        private final TextFieldListener textFieldListener;
        private JButton colorButton;
        private JCheckBox colorDefault;
        private JPanel buttonPanel;
-
+       
        private JLabel massLabel;
        
        
        public RocketComponentConfig(RocketComponent component) {
-               setLayout(new MigLayout("fill","[grow, fill]"));
+               setLayout(new MigLayout("fill", "[grow, fill]"));
                this.component = component;
                
                JLabel label = new JLabel("Component name:");
                label.setToolTipText("The component name.");
-               this.add(label,"split, gapright 10");
+               this.add(label, "split, gapright 10");
                
                componentNameField = new JTextField(15);
                textFieldListener = new TextFieldListener();
                componentNameField.addActionListener(textFieldListener);
                componentNameField.addFocusListener(textFieldListener);
                componentNameField.setToolTipText("The component name.");
-               this.add(componentNameField,"growx, growy 0, wrap");
-               
+               this.add(componentNameField, "growx, growy 0, wrap");
                
+
                tabbedPane = new JTabbedPane();
-               this.add(tabbedPane,"growx, growy 1, wrap");
+               this.add(tabbedPane, "growx, growy 1, wrap");
                
                tabbedPane.addTab("Override", null, overrideTab(), "Mass and CG override options");
                if (component.isMassive())
@@ -102,7 +107,7 @@ public class RocketComponentConfig extends JPanel {
                massLabel = new StyledLabel("Mass: ", -1);
                buttonPanel.add(massLabel, "growx");
                
-               for (JButton b: buttons) {
+               for (JButton b : buttons) {
                        buttonPanel.add(b, "right, gap para");
                }
                
@@ -132,9 +137,9 @@ public class RocketComponentConfig extends JPanel {
                // Component color and "Use default color" checkbox
                if (colorButton != null && colorDefault != null) {
                        colorButton.setIcon(new ColorIcon(component.getColor()));
-               
-                       if ((component.getColor()==null) != colorDefault.isSelected())
-                               colorDefault.setSelected(component.getColor()==null);
+                       
+                       if ((component.getColor() == null) != colorDefault.isSelected())
+                               colorDefault.setSelected(component.getColor() == null);
                }
                
                // Mass label
@@ -146,7 +151,7 @@ public class RocketComponentConfig extends JPanel {
                        String overridetext = null;
                        if (component.isMassOverridden()) {
                                overridetext = "(overridden to " + UnitGroup.UNITS_MASS.getDefaultUnit().
-                                       toStringUnit(component.getOverrideMass()) + ")";
+                                               toStringUnit(component.getOverrideMass()) + ")";
                        }
                        
                        for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
@@ -173,31 +178,31 @@ public class RocketComponentConfig extends JPanel {
                        String materialString, String finishString) {
                JLabel label = new JLabel(materialString);
                label.setToolTipText("The component material affects the weight of the component.");
-               panel.add(label,"spanx 4, wrap rel");
+               panel.add(label, "spanx 4, wrap rel");
                
                JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
                combo.setToolTipText("The component material affects the weight of the component.");
-               panel.add(combo,"spanx 4, growx, wrap paragraph");
-               
+               panel.add(combo, "spanx 4, growx, wrap paragraph");
                
+
                if (component instanceof ExternalComponent) {
                        label = new JLabel(finishString);
-                       String tip = "<html>The component finish affects the aerodynamic drag of the " 
-                               +"component.<br>" 
-                               + "The value indicated is the average roughness height of the surface.";
+                       String tip = "<html>The component finish affects the aerodynamic drag of the "
+                                       + "component.<br>"
+                                       + "The value indicated is the average roughness height of the surface.";
                        label.setToolTipText(tip);
-                       panel.add(label,"spanx 4, wmin 220lp, wrap rel");
+                       panel.add(label, "spanx 4, wmin 220lp, wrap rel");
                        
-                       combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component,"Finish"));
+                       combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
                        combo.setToolTipText(tip);
-                       panel.add(combo,"spanx 4, growx, split");
+                       panel.add(combo, "spanx 4, growx, split");
                        
                        JButton button = new JButton("Set for all");
                        button.setToolTipText("Set this finish for all components of the rocket.");
                        button.addActionListener(new ActionListener() {
                                @Override
                                public void actionPerformed(ActionEvent e) {
-                                       Finish f = ((ExternalComponent)component).getFinish();
+                                       Finish f = ((ExternalComponent) component).getFinish();
                                        Rocket rocket = component.getRocket();
                                        try {
                                                rocket.freeze();
@@ -205,11 +210,11 @@ public class RocketComponentConfig extends JPanel {
                                                String desc = ComponentConfigDialog.getUndoDescription();
                                                ComponentConfigDialog.addUndoPosition("Set rocket finish");
                                                // Do changes
-                                               Iterator<RocketComponent> iter = rocket.deepIterator();
+                                               Iterator<RocketComponent> iter = rocket.iterator();
                                                while (iter.hasNext()) {
                                                        RocketComponent c = iter.next();
                                                        if (c instanceof ExternalComponent) {
-                                                               ((ExternalComponent)c).setFinish(f);
+                                                               ((ExternalComponent) c).setFinish(f);
                                                        }
                                                }
                                                // Restore undo description
@@ -228,51 +233,51 @@ public class RocketComponentConfig extends JPanel {
        
        private JPanel overrideTab() {
                JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
-                               "[][65lp::][30lp::][]",""));
+                               "[][65lp::][30lp::][]", ""));
                
                panel.add(new StyledLabel("Override the mass or center of gravity of the " +
-                               component.getComponentName() + ":", Style.BOLD),"spanx, wrap 20lp");
-
+                               component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
+               
                JCheckBox check;
                BooleanModel bm;
                UnitSelector us;
                BasicSlider bs;
-
+               
                ////  Mass
                bm = new BooleanModel(component, "MassOverridden");
                check = new JCheckBox(bm);
                check.setText("Override mass:");
                panel.add(check, "growx 1, gapright 20lp");
                
-               DoubleModel m = new DoubleModel(component,"OverrideMass",UnitGroup.UNITS_MASS,0);
+               DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
                
                JSpinner spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                bm.addEnableComponent(spin, true);
-               panel.add(spin,"growx 1");
+               panel.add(spin, "growx 1");
                
                us = new UnitSelector(m);
                bm.addEnableComponent(us, true);
-               panel.add(us,"growx 1");
+               panel.add(us, "growx 1");
                
                bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
                bm.addEnableComponent(bs);
-               panel.add(bs,"growx 5, w 100lp, wrap");
-               
+               panel.add(bs, "growx 5, w 100lp, wrap");
                
+
                ////  CG override
                bm = new BooleanModel(component, "CGOverridden");
                check = new JCheckBox(bm);
                check.setText("Override center of gravity:");
                panel.add(check, "growx 1, gapright 20lp");
                
-               m = new DoubleModel(component,"OverrideCGX",UnitGroup.UNITS_LENGTH,0);
+               m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
                // Calculate suitable length for slider
                DoubleModel length;
                if (component instanceof ComponentAssembly) {
-                       double l=0;
+                       double l = 0;
                        
-                       Iterator<RocketComponent> iterator = component.deepIterator();
+                       Iterator<RocketComponent> iterator = component.iterator(false);
                        while (iterator.hasNext()) {
                                RocketComponent c = iterator.next();
                                if (c.getRelativePosition() == RocketComponent.Position.AFTER)
@@ -280,23 +285,23 @@ public class RocketComponentConfig extends JPanel {
                        }
                        length = new DoubleModel(l);
                } else {
-                       length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH,0);
+                       length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
                }
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
                bm.addEnableComponent(spin, true);
-               panel.add(spin,"growx 1");
+               panel.add(spin, "growx 1");
                
                us = new UnitSelector(m);
                bm.addEnableComponent(us, true);
-               panel.add(us,"growx 1");
+               panel.add(us, "growx 1");
                
                bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
                bm.addEnableComponent(bs);
-               panel.add(bs,"growx 5, w 100lp, wrap 35lp");
-               
+               panel.add(bs, "growx 5, w 100lp, wrap 35lp");
                
+
                // Override subcomponents checkbox
                bm = new BooleanModel(component, "OverrideSubcomponents");
                check = new JCheckBox(bm);
@@ -306,7 +311,7 @@ public class RocketComponentConfig extends JPanel {
 
                panel.add(new StyledLabel("<html>The overridden mass does not include motors.<br>" +
                                "The center of gravity is measured from the front end of the " +
-                               component.getComponentName().toLowerCase()+".", -1),
+                               component.getComponentName().toLowerCase() + ".", -1),
                                "spanx, wrap, gap para, height 0::30lp");
                
                return panel;
@@ -316,7 +321,7 @@ public class RocketComponentConfig extends JPanel {
        private JPanel commentTab() {
                JPanel panel = new JPanel(new MigLayout("fill"));
                
-               panel.add(new StyledLabel("Comments on the "+component.getComponentName()+":", 
+               panel.add(new StyledLabel("Comments on the " + component.getComponentName() + ":",
                                Style.BOLD), "wrap");
                
                // TODO: LOW:  Changes in comment from other sources not reflected in component
@@ -332,18 +337,19 @@ public class RocketComponentConfig extends JPanel {
                return panel;
        }
        
-
+       
 
        private JPanel figureTab() {
                JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
                
                panel.add(new StyledLabel("Figure style:", Style.BOLD), "wrap para");
                
-               
+
                panel.add(new JLabel("Component color:"), "gapleft para, gapright 10lp");
                
                colorButton = new JButton(new ColorIcon(component.getColor()));
                colorButton.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                Color c = component.getColor();
                                if (c == null) {
@@ -351,7 +357,7 @@ public class RocketComponentConfig extends JPanel {
                                }
                                
                                c = JColorChooser.showDialog(tabbedPane, "Choose color", c);
-                               if (c!=null) {
+                               if (c != null) {
                                        component.setColor(c);
                                }
                        }
@@ -359,7 +365,7 @@ public class RocketComponentConfig extends JPanel {
                panel.add(colorButton, "gapright 10lp");
                
                colorDefault = new JCheckBox("Use default color");
-               if (component.getColor()==null)
+               if (component.getColor() == null)
                        colorDefault.setSelected(true);
                colorDefault.addActionListener(new ActionListener() {
                        @Override
@@ -372,17 +378,17 @@ public class RocketComponentConfig extends JPanel {
                });
                panel.add(colorDefault, "wrap para");
                
-               
-               panel.add(new JLabel("Component line style:"), "gapleft para, gapright 10lp");
 
-               LineStyle[] list = new LineStyle[LineStyle.values().length+1];
+               panel.add(new JLabel("Component line style:"), "gapleft para, gapright 10lp");
+               
+               LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
                System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
-
+               
                JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
                                list, "Default style"));
                panel.add(combo, "spanx 2, growx, wrap 50lp");
                
-               
+
                JButton button = new JButton("Save as default style");
                button.addActionListener(new ActionListener() {
                        @Override
@@ -401,10 +407,10 @@ public class RocketComponentConfig extends JPanel {
                
                return panel;
        }
-
        
        
 
+
        protected JPanel shoulderTab() {
                JPanel panel = new JPanel(new MigLayout("fill"));
                JPanel sub;
@@ -414,146 +420,153 @@ public class RocketComponentConfig extends JPanel {
                JCheckBox check;
                JSpinner spin;
                
-               
+
                ////  Fore shoulder, not for NoseCone
                
                if (!(component instanceof NoseCone)) {
-                       sub = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]",""));
+                       sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
                        
                        sub.setBorder(BorderFactory.createTitledBorder("Fore shoulder"));
-
                        
+
                        ////  Radius
                        sub.add(new JLabel("Diameter:"));
                        
-                       m = new DoubleModel(component,"ForeShoulderRadius",2,UnitGroup.UNITS_LENGTH,0);
-                       m2 = new DoubleModel(component,"ForeRadius",2,UnitGroup.UNITS_LENGTH);
+                       m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
+                       m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
                        
                        spin = new JSpinner(m.getSpinnerModel());
                        spin.setEditor(new SpinnerEditor(spin));
-                       sub.add(spin,"growx");
-                       
-                       sub.add(new UnitSelector(m),"growx");
-                       sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
+                       sub.add(spin, "growx");
                        
+                       sub.add(new UnitSelector(m), "growx");
+                       sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
                        
+
                        ////  Length
                        sub.add(new JLabel("Length:"));
                        
-                       m = new DoubleModel(component,"ForeShoulderLength",UnitGroup.UNITS_LENGTH,0);
+                       m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
                        
                        spin = new JSpinner(m.getSpinnerModel());
                        spin.setEditor(new SpinnerEditor(spin));
-                       sub.add(spin,"growx");
+                       sub.add(spin, "growx");
                        
-                       sub.add(new UnitSelector(m),"growx");
-                       sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)),"w 100lp, wrap");
+                       sub.add(new UnitSelector(m), "growx");
+                       sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
                        
 
                        ////  Thickness
                        sub.add(new JLabel("Thickness:"));
                        
-                       m = new DoubleModel(component,"ForeShoulderThickness",UnitGroup.UNITS_LENGTH,0);
-                       m2 = new DoubleModel(component,"ForeShoulderRadius",UnitGroup.UNITS_LENGTH);
-
+                       m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
+                       m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
+                       
                        spin = new JSpinner(m.getSpinnerModel());
                        spin.setEditor(new SpinnerEditor(spin));
-                       sub.add(spin,"growx");
-                       
-                       sub.add(new UnitSelector(m),"growx");
-                       sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
+                       sub.add(spin, "growx");
                        
+                       sub.add(new UnitSelector(m), "growx");
+                       sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
                        
+
                        ////  Capped
                        bm = new BooleanModel(component, "ForeShoulderCapped");
                        check = new JCheckBox(bm);
                        check.setText("End capped");
                        check.setToolTipText("Whether the end of the shoulder is capped.");
                        sub.add(check, "spanx");
-
                        
+
                        panel.add(sub);
                }
                
-               
+
                ////  Aft shoulder
-               sub = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]",""));
+               sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
                
                if (component instanceof NoseCone)
                        sub.setBorder(BorderFactory.createTitledBorder("Nose cone shoulder"));
                else
                        sub.setBorder(BorderFactory.createTitledBorder("Aft shoulder"));
-
                
+
                ////  Radius
                sub.add(new JLabel("Diameter:"));
                
-               m = new DoubleModel(component,"AftShoulderRadius",2,UnitGroup.UNITS_LENGTH,0);
-               m2 = new DoubleModel(component,"AftRadius",2,UnitGroup.UNITS_LENGTH);
+               m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
+               m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
-               sub.add(spin,"growx");
-               
-               sub.add(new UnitSelector(m),"growx");
-               sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
+               sub.add(spin, "growx");
                
+               sub.add(new UnitSelector(m), "growx");
+               sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
                
+
                ////  Length
                sub.add(new JLabel("Length:"));
                
-               m = new DoubleModel(component,"AftShoulderLength",UnitGroup.UNITS_LENGTH,0);
+               m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
-               sub.add(spin,"growx");
+               sub.add(spin, "growx");
                
-               sub.add(new UnitSelector(m),"growx");
-               sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)),"w 100lp, wrap");
+               sub.add(new UnitSelector(m), "growx");
+               sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
                
 
                ////  Thickness
                sub.add(new JLabel("Thickness:"));
                
-               m = new DoubleModel(component,"AftShoulderThickness",UnitGroup.UNITS_LENGTH,0);
-               m2 = new DoubleModel(component,"AftShoulderRadius",UnitGroup.UNITS_LENGTH);
-
+               m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
+               m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
+               
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
-               sub.add(spin,"growx");
-               
-               sub.add(new UnitSelector(m),"growx");
-               sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
+               sub.add(spin, "growx");
                
+               sub.add(new UnitSelector(m), "growx");
+               sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
                
+
                ////  Capped
                bm = new BooleanModel(component, "AftShoulderCapped");
                check = new JCheckBox(bm);
                check.setText("End capped");
                check.setToolTipText("Whether the end of the shoulder is capped.");
                sub.add(check, "spanx");
-
                
-               panel.add(sub);
 
+               panel.add(sub);
                
+
                return panel;
        }
        
        
-       
-       
+
+
        /*
         * Private inner class to handle events in componentNameField.
         */
        private class TextFieldListener implements ActionListener, FocusListener {
+               @Override
                public void actionPerformed(ActionEvent e) {
                        setName();
                }
-               public void focusGained(FocusEvent e) { }
+               
+               @Override
+               public void focusGained(FocusEvent e) {
+               }
+               
+               @Override
                public void focusLost(FocusEvent e) {
                        setName();
                }
+               
                private void setName() {
                        if (!component.getName().equals(componentNameField.getText())) {
                                component.setName(componentNameField.getText());
@@ -576,15 +589,15 @@ public class RocketComponentConfig extends JPanel {
                public int getIconHeight() {
                        return 15;
                }
-
+               
                @Override
                public int getIconWidth() {
                        return 25;
                }
-
+               
                @Override
                public void paintIcon(Component c, Graphics g, int x, int y) {
-                       if (color==null) {
+                       if (color == null) {
                                g.setColor(Prefs.getDefaultColor(component.getClass()));
                        } else {
                                g.setColor(color);
@@ -594,4 +607,14 @@ public class RocketComponentConfig extends JPanel {
                
        }
        
+       protected void register(Invalidatable model) {
+               this.invalidatables.add(model);
+       }
+       
+       public void invalidateModels() {
+               for (Invalidatable i : invalidatables) {
+                       i.invalidate();
+               }
+       }
+       
 }
index 927cc54838f867b9235cb40514881ec8da8333eb..e3e9d575a7caa2cc979e8eb34974ecf70192af96 100644 (file)
@@ -258,7 +258,7 @@ public class DebugLogDialog extends JDialog {
                };
                
                table = new JTable(model);
-               table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
                table.setSelectionBackground(Color.LIGHT_GRAY);
                table.setSelectionForeground(Color.BLACK);
                model.setColumnWidths(table.getColumnModel());
index a4390cf468dbbed59c65dc64d7f9300071e8cfe8..782afb761abdb78305ee8afa22ee778d7745600b 100644 (file)
@@ -67,7 +67,7 @@ public class EditMotorConfigurationDialog extends JDialog {
                this.rocket = rocket;
                
                ArrayList<MotorMount> mountList = new ArrayList<MotorMount>();
-               Iterator<RocketComponent> iterator = rocket.deepIterator();
+               Iterator<RocketComponent> iterator = rocket.iterator();
                while (iterator.hasNext()) {
                        RocketComponent c = iterator.next();
                        if (c instanceof MotorMount) {
index 58f5664bb3685ded37675aefb3fe3a842d7ae4f9..f086120897c8545ee87097304001a7c201ad7112 100644 (file)
@@ -308,16 +308,22 @@ public class PreferencesDialog extends JDialog {
                
 
 
-               panel.add(new JLabel("Stability:"));
-               combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY));
+               panel.add(new JLabel("Moment of inertia:"));
+               combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_INERTIA));
                panel.add(combo, "sizegroup boxes");
                
                panel.add(new JLabel("Pressure:"));
                combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_PRESSURE));
+               panel.add(combo, "sizegroup boxes, wrap");
+               
+
+               panel.add(new JLabel("Stability:"));
+               combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY));
                panel.add(combo, "sizegroup boxes, wrap para");
                
 
 
+
                JButton button = new JButton("Default metric");
                button.addActionListener(new ActionListener() {
                        @Override
index 3c8f4e2c7e76ed02230525f04147421db1776323..9d79c13b3b73e9de87b6b5953599df528bf6175c 100644 (file)
@@ -1,5 +1,62 @@
 package net.sf.openrocket.gui.main;
 
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+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;
+import javax.swing.InputMap;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSeparator;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.tree.DefaultTreeSelectionModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.aerodynamics.WarningSet;
 import net.sf.openrocket.document.OpenRocketDocument;
@@ -42,62 +99,6 @@ import net.sf.openrocket.util.Reflection;
 import net.sf.openrocket.util.SaveFileWorker;
 import net.sf.openrocket.util.TestRockets;
 
-import javax.swing.Action;
-import javax.swing.InputMap;
-import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JFileChooser;
-import javax.swing.JFrame;
-import javax.swing.JMenu;
-import javax.swing.JMenuBar;
-import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JSeparator;
-import javax.swing.JSplitPane;
-import javax.swing.JTabbedPane;
-import javax.swing.JTextField;
-import javax.swing.KeyStroke;
-import javax.swing.ListSelectionModel;
-import javax.swing.ScrollPaneConstants;
-import javax.swing.SwingUtilities;
-import javax.swing.border.TitledBorder;
-import javax.swing.event.TreeSelectionEvent;
-import javax.swing.event.TreeSelectionListener;
-import javax.swing.filechooser.FileFilter;
-import javax.swing.tree.DefaultTreeSelectionModel;
-import javax.swing.tree.TreePath;
-import javax.swing.tree.TreeSelectionModel;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.Toolkit;
-import java.awt.Window;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-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;
-
 public class BasicFrame extends JFrame {
        private static final LogHelper log = Application.getLogger();
        
@@ -177,6 +178,7 @@ public class BasicFrame extends JFrame {
 
                // Set replaceable flag to false at first modification
                rocket.addComponentChangeListener(new ComponentChangeListener() {
+                       @Override
                        public void componentChanged(ComponentChangeEvent e) {
                                replaceable = false;
                                BasicFrame.this.rocket.removeComponentChangeListener(this);
@@ -230,6 +232,7 @@ public class BasicFrame extends JFrame {
                
 
                rocket.addComponentChangeListener(new ComponentChangeListener() {
+                       @Override
                        public void componentChanged(ComponentChangeEvent e) {
                                setTitle();
                        }
@@ -319,6 +322,7 @@ public class BasicFrame extends JFrame {
                
                // Update dialog when selection is changed
                componentSelectionModel.addTreeSelectionListener(new TreeSelectionListener() {
+                       @Override
                        public void valueChanged(TreeSelectionEvent e) {
                                // Scroll tree to the selected item
                                TreePath path = componentSelectionModel.getSelectionPath();
@@ -404,6 +408,7 @@ public class BasicFrame extends JFrame {
                item.getAccessibleContext().setAccessibleDescription("Create a new rocket design");
                item.setIcon(Icons.FILE_NEW);
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("New... selected");
                                newAction();
@@ -420,6 +425,7 @@ public class BasicFrame extends JFrame {
                item.getAccessibleContext().setAccessibleDescription("Open a rocket design");
                item.setIcon(Icons.FILE_OPEN);
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Open... selected");
                                openAction();
@@ -433,6 +439,7 @@ public class BasicFrame extends JFrame {
                                ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK));
                item.setIcon(Icons.FILE_OPEN_EXAMPLE);
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Open example... selected");
                                URL[] urls = ExampleDesignDialog.selectExampleDesigns(BasicFrame.this);
@@ -453,6 +460,7 @@ public class BasicFrame extends JFrame {
                item.getAccessibleContext().setAccessibleDescription("Save the current rocket design");
                item.setIcon(Icons.FILE_SAVE);
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Save selected");
                                saveAction();
@@ -467,6 +475,7 @@ public class BasicFrame extends JFrame {
                                "to a new file");
                item.setIcon(Icons.FILE_SAVE_AS);
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Save as... selected");
                                saveAsAction();
@@ -474,14 +483,28 @@ public class BasicFrame extends JFrame {
                });
                menu.add(item);
                
-               //              menu.addSeparator();
-               menu.add(new JSeparator());
+
+               item = new JMenuItem("Print...", KeyEvent.VK_P);
+               item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.CTRL_MASK));
+               item.getAccessibleContext().setAccessibleDescription("Print parts list and fin template");
+               item.setIcon(Icons.FILE_PRINT);
+               item.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               printAction();
+                       }
+               });
+               menu.add(item);
+               
+
+               menu.addSeparator();
                
                item = new JMenuItem("Close", KeyEvent.VK_C);
                item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, ActionEvent.CTRL_MASK));
                item.getAccessibleContext().setAccessibleDescription("Close the current rocket design");
                item.setIcon(Icons.FILE_CLOSE);
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Close selected");
                                closeAction();
@@ -490,23 +513,13 @@ public class BasicFrame extends JFrame {
                menu.add(item);
                
                menu.addSeparator();
-
-
-        item = new JMenuItem("Print...", KeyEvent.VK_P);
-        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.CTRL_MASK));
-        item.getAccessibleContext().setAccessibleDescription("Print parts list and fin template");
-        item.addActionListener(new ActionListener() {
-            public void actionPerformed(ActionEvent e) {
-                printAction();
-            }
-        });
-        menu.add(item);
-        
+               
                item = new JMenuItem("Quit", KeyEvent.VK_Q);
                item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK));
                item.getAccessibleContext().setAccessibleDescription("Quit the program");
                item.setIcon(Icons.FILE_QUIT);
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Quit selected");
                                quitAction();
@@ -561,6 +574,7 @@ public class BasicFrame extends JFrame {
                item.getAccessibleContext().setAccessibleDescription("Setup the application " +
                                "preferences");
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Preferences selected");
                                PreferencesDialog.showPreferences();
@@ -581,6 +595,7 @@ public class BasicFrame extends JFrame {
                item.getAccessibleContext().setAccessibleDescription("Analyze the rocket components " +
                                "separately");
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Component analysis selected");
                                ComponentAnalysisDialog.showDialog(rocketpanel);
@@ -609,6 +624,7 @@ public class BasicFrame extends JFrame {
                item = new JMenuItem("License", KeyEvent.VK_L);
                item.getAccessibleContext().setAccessibleDescription("OpenRocket license information");
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("License selected");
                                new LicenseDialog(BasicFrame.this).setVisible(true);
@@ -622,6 +638,7 @@ public class BasicFrame extends JFrame {
                item.getAccessibleContext().setAccessibleDescription("Information about reporting " +
                                "bugs in OpenRocket");
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Bug report selected");
                                BugReportDialog.showBugReportDialog(BasicFrame.this);
@@ -632,6 +649,7 @@ public class BasicFrame extends JFrame {
                item = new JMenuItem("Debug log");
                item.getAccessibleContext().setAccessibleDescription("View the OpenRocket debug log");
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Debug log selected");
                                new DebugLogDialog(BasicFrame.this).setVisible(true);
@@ -644,6 +662,7 @@ public class BasicFrame extends JFrame {
                item = new JMenuItem("About", KeyEvent.VK_A);
                item.getAccessibleContext().setAccessibleDescription("About OpenRocket");
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("About selected");
                                new AboutDialog(BasicFrame.this).setVisible(true);
@@ -666,6 +685,7 @@ public class BasicFrame extends JFrame {
                
                item = new JMenuItem("What is this menu?");
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("What is this menu? selected");
                                JOptionPane.showMessageDialog(BasicFrame.this,
@@ -754,7 +774,7 @@ public class BasicFrame extends JFrame {
                                log.user("Memory statistics selected");
                                
                                // Get discarded but remaining objects (this also runs System.gc multiple times)
-                               List<MemoryData> objects = MemoryManagement.getRemainingObjects();
+                               List<MemoryData> objects = MemoryManagement.getRemainingCollectableObjects();
                                StringBuilder sb = new StringBuilder();
                                sb.append("Objects that should have been garbage-collected but have not been:\n");
                                int count = 0;
@@ -833,6 +853,7 @@ public class BasicFrame extends JFrame {
                
                item = new JMenuItem("Exception from EDT");
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Exception from EDT selected");
                                SwingUtilities.invokeLater(new Runnable() {
@@ -848,13 +869,13 @@ public class BasicFrame extends JFrame {
                
                item = new JMenuItem("Exception from other thread");
                item.addActionListener(new ActionListener() {
+                       @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Exception from other thread selected");
                                new Thread() {
                                        @Override
                                        public void run() {
-                                               throw new RuntimeException("Testing exception from " +
-                                                               "newly created thread");
+                                               throw new RuntimeException("Testing exception from newly created thread");
                                        }
                                }.start();
                        }
@@ -1278,13 +1299,13 @@ public class BasicFrame extends JFrame {
        
        
 
-    /**
-     * 
-     */
-    public void printAction() {
-        new PrintDialog(document);
-    }
-    
+       /**
+        
+        */
+       public void printAction() {
+               new PrintDialog(document);
+       }
+       
        /**
         * Open a new design window with a basic rocket+stage.
         */
index 9fbc9a848f6b184ed797a252d0f8c2401c67c222..cd4af70bad5fbfdc2bf97330383aaf34b4f79f0a 100644 (file)
@@ -145,8 +145,8 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
                                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() {
+                               log.info("Not in EDT, invoking dialog later");
+                               SwingUtilities.invokeLater(new Runnable() {
                                        @Override
                                        public void run() {
                                                handler.showDialog(thread, exception);
index f35222c132b26567f52e0e160c592964fc97d6a6..8cf5828285f30512f496c3902d3216e6c92d1535 100644 (file)
@@ -46,6 +46,7 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
        }
        
        
+       @Override
        public Object getChild(Object parent, int index) {
                RocketComponent component = (RocketComponent) parent;
                
@@ -57,6 +58,7 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
        }
        
        
+       @Override
        public int getChildCount(Object parent) {
                RocketComponent c = (RocketComponent) parent;
                
@@ -64,6 +66,7 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
        }
        
        
+       @Override
        public int getIndexOfChild(Object parent, Object child) {
                if (parent == null || child == null)
                        return -1;
@@ -74,18 +77,22 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
                return p.getChildPosition(c);
        }
        
+       @Override
        public Object getRoot() {
                return root;
        }
        
+       @Override
        public boolean isLeaf(Object node) {
                return !((RocketComponent) node).allowsChildren();
        }
        
+       @Override
        public void addTreeModelListener(TreeModelListener l) {
                listeners.add(l);
        }
        
+       @Override
        public void removeTreeModelListener(TreeModelListener l) {
                listeners.remove(l);
        }
@@ -158,15 +165,17 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
                }
        }
        
+       @Override
        public void valueForPathChanged(TreePath path, Object newValue) {
                System.err.println("ERROR: valueForPathChanged called?!");
        }
        
        
+       @Override
        public void componentChanged(ComponentChangeEvent e) {
                if (e.isTreeChange() || e.isUndoChange()) {
                        // Tree must be fully updated also in case of an undo change 
-                       fireTreeStructureChanged((RocketComponent) e.getSource());
+                       fireTreeStructureChanged(e.getSource());
                        if (e.isTreeChange() && e.isUndoChange()) {
                                // If the undo has changed the tree structure, some elements may be hidden
                                // unnecessarily
@@ -179,7 +188,7 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
        }
        
        public void expandAll() {
-               Iterator<RocketComponent> iterator = root.deepIterator();
+               Iterator<RocketComponent> iterator = root.iterator(false);
                while (iterator.hasNext()) {
                        tree.makeVisible(makeTreePath(iterator.next()));
                }
index ebe5e96c00e3b88b8673b60eea155b646771847e..1cf5e97474ae5bad6fb2b452a17718c1aace5c86 100644 (file)
@@ -1,14 +1,14 @@
 package net.sf.openrocket.gui.plot;
 
-import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Set;
 
 import net.sf.openrocket.simulation.FlightDataBranch;
-import net.sf.openrocket.simulation.FlightEvent;
 import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.FlightEvent;
 import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.util.ArrayList;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.MathUtil;
 import net.sf.openrocket.util.Pair;
@@ -54,7 +54,7 @@ public class PlotConfiguration implements Cloneable {
                config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
                config.setEvent(FlightEvent.Type.GROUND_HIT, true);
                configs.add(config);
-
+               
                config = new PlotConfiguration("Stability vs. time");
                config.addPlotDataType(FlightDataType.TYPE_STABILITY, 0);
                config.addPlotDataType(FlightDataType.TYPE_CP_LOCATION, 1);
@@ -67,14 +67,14 @@ public class PlotConfiguration implements Cloneable {
                config.setEvent(FlightEvent.Type.GROUND_HIT, true);
                configs.add(config);
                
-               config = new PlotConfiguration("Drag coefficients vs. Mach number", 
+               config = new PlotConfiguration("Drag coefficients vs. Mach number",
                                FlightDataType.TYPE_MACH_NUMBER);
                config.addPlotDataType(FlightDataType.TYPE_DRAG_COEFF, 0);
                config.addPlotDataType(FlightDataType.TYPE_FRICTION_DRAG_COEFF, 0);
                config.addPlotDataType(FlightDataType.TYPE_BASE_DRAG_COEFF, 0);
                config.addPlotDataType(FlightDataType.TYPE_PRESSURE_DRAG_COEFF, 0);
                configs.add(config);
-
+               
                config = new PlotConfiguration("Roll characteristics");
                config.addPlotDataType(FlightDataType.TYPE_ROLL_RATE, 0);
                config.addPlotDataType(FlightDataType.TYPE_ROLL_MOMENT_COEFF, 1);
@@ -88,7 +88,7 @@ public class PlotConfiguration implements Cloneable {
                config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
                config.setEvent(FlightEvent.Type.GROUND_HIT, true);
                configs.add(config);
-
+               
                config = new PlotConfiguration("Angle of attack and orientation vs. time");
                config.addPlotDataType(FlightDataType.TYPE_AOA, 0);
                config.addPlotDataType(FlightDataType.TYPE_ORIENTATION_PHI);
@@ -100,7 +100,7 @@ public class PlotConfiguration implements Cloneable {
                config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
                config.setEvent(FlightEvent.Type.GROUND_HIT, true);
                configs.add(config);
-
+               
                config = new PlotConfiguration("Simulation time step and computation time");
                config.addPlotDataType(FlightDataType.TYPE_TIME_STEP);
                config.addPlotDataType(FlightDataType.TYPE_COMPUTATION_TIME);
@@ -111,15 +111,15 @@ public class PlotConfiguration implements Cloneable {
                config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
                config.setEvent(FlightEvent.Type.GROUND_HIT, true);
                configs.add(config);
-
+               
                DEFAULT_CONFIGURATIONS = configs.toArray(new PlotConfiguration[0]);
        }
        
-       
-       
+
+
        /** Bonus given for the first type being on the first axis */
        private static final double BONUS_FIRST_TYPE_ON_FIRST_AXIS = 1.0;
-
+       
        /**
         * Bonus given if the first axis includes zero (to prefer first axis having zero over 
         * the others) 
@@ -132,11 +132,11 @@ public class PlotConfiguration implements Cloneable {
        /** Bonus given for only using a single axis. */
        private static final double BONUS_ONLY_ONE_AXIS = 50.0;
        
-       
-       private static final double INCLUDE_ZERO_DISTANCE = 0.3;  // 30% of total range
-       
+
+       private static final double INCLUDE_ZERO_DISTANCE = 0.3; // 30% of total range
        
 
+
        /** The data types to be plotted. */
        private ArrayList<FlightDataType> plotDataTypes = new ArrayList<FlightDataType>();
        
@@ -144,24 +144,24 @@ public class PlotConfiguration implements Cloneable {
        
        /** The corresponding Axis on which they will be plotted, or null to auto-select. */
        private ArrayList<Integer> plotDataAxes = new ArrayList<Integer>();
-
+       
        private EnumSet<FlightEvent.Type> events = EnumSet.noneOf(FlightEvent.Type.class);
        
        /** The domain (x) axis. */
        private FlightDataType domainAxisType = null;
        private Unit domainAxisUnit = null;
        
-       
+
        /** All available axes. */
        private final int axesCount;
        private ArrayList<Axis> allAxes = new ArrayList<Axis>();
        
-       
-       
+
+
        private String name = null;
        
        
-       
+
        public PlotConfiguration() {
                this(null, FlightDataType.TYPE_TIME);
        }
@@ -181,9 +181,9 @@ public class PlotConfiguration implements Cloneable {
        }
        
        
-       
-       
-       
+
+
+
        public FlightDataType getDomainAxisType() {
                return domainAxisType;
        }
@@ -191,7 +191,7 @@ public class PlotConfiguration implements Cloneable {
        public void setDomainAxisType(FlightDataType type) {
                boolean setUnit;
                
-               if (domainAxisType != null  &&  domainAxisType.getUnitGroup() == type.getUnitGroup())
+               if (domainAxisType != null && domainAxisType.getUnitGroup() == type.getUnitGroup())
                        setUnit = false;
                else
                        setUnit = true;
@@ -207,19 +207,19 @@ public class PlotConfiguration implements Cloneable {
        
        public void setDomainAxisUnit(Unit u) {
                if (!domainAxisType.getUnitGroup().contains(u)) {
-                       throw new IllegalArgumentException("Setting unit "+u+" to type "+domainAxisType);
+                       throw new IllegalArgumentException("Setting unit " + u + " to type " + domainAxisType);
                }
                domainAxisUnit = u;
        }
        
        
-       
+
        public void addPlotDataType(FlightDataType type) {
                plotDataTypes.add(type);
                plotDataUnits.add(type.getUnitGroup().getDefaultUnit());
                plotDataAxes.add(-1);
        }
-
+       
        public void addPlotDataType(FlightDataType type, int axis) {
                if (axis >= axesCount) {
                        throw new IllegalArgumentException("Axis index too large");
@@ -228,10 +228,10 @@ public class PlotConfiguration implements Cloneable {
                plotDataUnits.add(type.getUnitGroup().getDefaultUnit());
                plotDataAxes.add(axis);
        }
-
-
        
        
+
+
        public void setPlotDataType(int index, FlightDataType type) {
                FlightDataType origType = plotDataTypes.get(index);
                plotDataTypes.set(index, type);
@@ -243,7 +243,7 @@ public class PlotConfiguration implements Cloneable {
        
        public void setPlotDataUnit(int index, Unit unit) {
                if (!plotDataTypes.get(index).getUnitGroup().contains(unit)) {
-                       throw new IllegalArgumentException("Attempting to set unit "+unit+" to group "
+                       throw new IllegalArgumentException("Attempting to set unit " + unit + " to group "
                                        + plotDataTypes.get(index).getUnitGroup());
                }
                plotDataUnits.set(index, unit);
@@ -273,13 +273,15 @@ public class PlotConfiguration implements Cloneable {
        }
        
        
-       
-       public FlightDataType getType (int index) {
+
+       public FlightDataType getType(int index) {
                return plotDataTypes.get(index);
        }
+       
        public Unit getUnit(int index) {
                return plotDataUnits.get(index);
        }
+       
        public int getAxis(int index) {
                return plotDataAxes.get(index);
        }
@@ -292,7 +294,7 @@ public class PlotConfiguration implements Cloneable {
        /// Events
        
        public Set<FlightEvent.Type> getActiveEvents() {
-               return (Set<FlightEvent.Type>) events.clone();
+               return events.clone();
        }
        
        public void setEvent(FlightEvent.Type type, boolean active) {
@@ -308,9 +310,9 @@ public class PlotConfiguration implements Cloneable {
        }
        
        
-       
-       
-       
+
+
+
        public List<Axis> getAllAxes() {
                List<Axis> list = new ArrayList<Axis>();
                list.addAll(allAxes);
@@ -335,7 +337,7 @@ public class PlotConfiguration implements Cloneable {
        }
        
        
-       
+
        /**
         * Find the best combination of the auto-selectable axes.
         * 
@@ -350,8 +352,8 @@ public class PlotConfiguration implements Cloneable {
        }
        
        
-       
-       
+
+
        /**
         * Recursively search for the best combination of the auto-selectable axes.
         * This is a brute-force search method.
@@ -364,23 +366,23 @@ public class PlotConfiguration implements Cloneable {
                // Create copy to fill in
                PlotConfiguration copy = this.clone();
                
-               int autoindex; 
-               for (autoindex=0; autoindex < plotDataAxes.size(); autoindex++) {
+               int autoindex;
+               for (autoindex = 0; autoindex < plotDataAxes.size(); autoindex++) {
                        if (plotDataAxes.get(autoindex) < 0)
                                break;
                }
                
-               
+
                if (autoindex >= plotDataAxes.size()) {
                        // All axes have been assigned, just return since we are already the best
                        return new Pair<PlotConfiguration, Double>(copy, copy.getGoodnessValue(data));
                }
                
-               
+
                // Set the auto-selected index one at a time and choose the best one
                PlotConfiguration best = null;
                double bestValue = Double.NEGATIVE_INFINITY;
-               for (int i=0; i < axesCount; i++) {
+               for (int i = 0; i < axesCount; i++) {
                        copy.plotDataAxes.set(autoindex, i);
                        Pair<PlotConfiguration, Double> result = copy.recursiveFillAutoAxes(data);
                        if (result.getV() > bestValue) {
@@ -393,9 +395,9 @@ public class PlotConfiguration implements Cloneable {
        }
        
        
-       
-       
-       
+
+
+
        /**
         * Fit the axes to hold the provided data.  All of the plotDataAxis elements must
         * be non-negative.
@@ -405,13 +407,13 @@ public class PlotConfiguration implements Cloneable {
        protected void fitAxes(FlightDataBranch data) {
                
                // Reset axes
-               for (Axis a: allAxes) {
+               for (Axis a : allAxes) {
                        a.reset();
                }
                
                // Add full range to the axes
                int length = plotDataTypes.size();
-               for (int i=0; i<length; i++) {
+               for (int i = 0; i < length; i++) {
                        FlightDataType type = plotDataTypes.get(i);
                        Unit unit = plotDataUnits.get(i);
                        int index = plotDataAxes.get(i);
@@ -422,16 +424,16 @@ public class PlotConfiguration implements Cloneable {
                        
                        double min = unit.toUnit(data.getMinimum(type));
                        double max = unit.toUnit(data.getMaximum(type));
-
+                       
                        axis.addBound(min);
                        axis.addBound(max);
                }
                
                // Ensure non-zero (or NaN) range, add a few percent range, include zero if it is close
-               for (Axis a: allAxes) {
+               for (Axis a : allAxes) {
                        if (MathUtil.equals(a.getMinValue(), a.getMaxValue())) {
-                               a.addBound(a.getMinValue()-1);
-                               a.addBound(a.getMaxValue()+1);
+                               a.addBound(a.getMinValue() - 1);
+                               a.addBound(a.getMaxValue() + 1);
                        }
                        
                        double addition = a.getRangeLength() * 0.03;
@@ -444,8 +446,8 @@ public class PlotConfiguration implements Cloneable {
                                a.addBound(0);
                        }
                }
-
                
+
                // Check whether to use a common zero
                Axis left = allAxes.get(0);
                Axis right = allAxes.get(1);
@@ -455,11 +457,11 @@ public class PlotConfiguration implements Cloneable {
                                Double.isNaN(left.getMinValue()) || Double.isNaN(right.getMinValue()))
                        return;
                
-               
-               
+
+
                //// Compute common zero
                // TODO: MEDIUM: This algorithm may require tweaking
-
+               
                double min1 = left.getMinValue();
                double max1 = left.getMaxValue();
                double min2 = right.getMinValue();
@@ -469,14 +471,14 @@ public class PlotConfiguration implements Cloneable {
                double scale = Math.max(left.getRangeLength(), right.getRangeLength()) /
                                                Math.min(left.getRangeLength(), right.getRangeLength());
                
-               System.out.println("Scale: "+scale);
+               System.out.println("Scale: " + scale);
                
                scale = roundScale(scale);
                if (right.getRangeLength() > left.getRangeLength()) {
-                       scale = 1/scale;
+                       scale = 1 / scale;
                }
                System.out.println("Rounded scale: " + scale);
-
+               
                // Scale right axis, enlarge axes if necessary and scale back
                min2 *= scale;
                max2 *= scale;
@@ -487,53 +489,53 @@ public class PlotConfiguration implements Cloneable {
                min2 /= scale;
                max2 /= scale;
                
-               
-               
+
+
                // Scale to unit length
-//             double scale1 = left.getRangeLength();
-//             double scale2 = right.getRangeLength();
-//             
-//             double min1 = left.getMinValue() / scale1;
-//             double max1 = left.getMaxValue() / scale1;
-//             double min2 = right.getMinValue() / scale2;
-//             double max2 = right.getMaxValue() / scale2;
-//             
-//             // Combine unit ranges
-//             min1 = MathUtil.min(min1, min2);
-//             min2 = min1;
-//             max1 = MathUtil.max(max1, max2);
-//             max2 = max1;
-//             
-//             // Scale up
-//             min1 *= scale1;
-//             max1 *= scale1;
-//             min2 *= scale2;
-//             max2 *= scale2;
-//             
-//             // Compute common scale
-//             double range1 = max1-min1;
-//             double range2 = max2-min2;
-//             
-//             double scale = MathUtil.max(range1, range2) / MathUtil.min(range1, range2);
-//             double roundScale = roundScale(scale);
-//
-//             if (range2 < range1) {
-//                     if (roundScale < scale) {
-//                             min2 = min1 / roundScale;
-//                             max2 = max1 / roundScale;
-//                     } else {
-//                             min1 = min2 * roundScale;
-//                             max1 = max2 * roundScale;
-//                     }
-//             } else {
-//                     if (roundScale > scale) {
-//                             min2 = min1 * roundScale;
-//                             max2 = max1 * roundScale;
-//                     } else {
-//                             min1 = min2 / roundScale;
-//                             max1 = max2 / roundScale;
-//                     }
-//             }
+               //              double scale1 = left.getRangeLength();
+               //              double scale2 = right.getRangeLength();
+               //              
+               //              double min1 = left.getMinValue() / scale1;
+               //              double max1 = left.getMaxValue() / scale1;
+               //              double min2 = right.getMinValue() / scale2;
+               //              double max2 = right.getMaxValue() / scale2;
+               //              
+               //              // Combine unit ranges
+               //              min1 = MathUtil.min(min1, min2);
+               //              min2 = min1;
+               //              max1 = MathUtil.max(max1, max2);
+               //              max2 = max1;
+               //              
+               //              // Scale up
+               //              min1 *= scale1;
+               //              max1 *= scale1;
+               //              min2 *= scale2;
+               //              max2 *= scale2;
+               //              
+               //              // Compute common scale
+               //              double range1 = max1-min1;
+               //              double range2 = max2-min2;
+               //              
+               //              double scale = MathUtil.max(range1, range2) / MathUtil.min(range1, range2);
+               //              double roundScale = roundScale(scale);
+               //
+               //              if (range2 < range1) {
+               //                      if (roundScale < scale) {
+               //                              min2 = min1 / roundScale;
+               //                              max2 = max1 / roundScale;
+               //                      } else {
+               //                              min1 = min2 * roundScale;
+               //                              max1 = max2 * roundScale;
+               //                      }
+               //              } else {
+               //                      if (roundScale > scale) {
+               //                              min2 = min1 * roundScale;
+               //                              max2 = max1 * roundScale;
+               //                      } else {
+               //                              min1 = min2 / roundScale;
+               //                              max1 = max2 / roundScale;
+               //                      }
+               //              }
                
                // Apply scale
                left.addBound(min1);
@@ -569,11 +571,11 @@ public class PlotConfiguration implements Cloneable {
                } else {
                        scale = 1;
                }
-               return scale*mul;
+               return scale * mul;
        }
        
        
-       
+
        private double roundScaleUp(double scale) {
                double mul = 1;
                while (scale >= 10) {
@@ -596,10 +598,10 @@ public class PlotConfiguration implements Cloneable {
                } else {
                        scale = 1;
                }
-               return scale*mul;
+               return scale * mul;
        }
        
-
+       
        private double roundScaleDown(double scale) {
                double mul = 1;
                while (scale >= 10) {
@@ -620,11 +622,11 @@ public class PlotConfiguration implements Cloneable {
                } else {
                        scale = 1;
                }
-               return scale*mul;
+               return scale * mul;
        }
        
        
-       
+
        /**
         * Fits the axis ranges to the data and returns the "goodness value" of this 
         * selection of axes.  All plotDataAxis elements must be non-null.
@@ -661,12 +663,12 @@ public class PlotConfiguration implements Cloneable {
                        if (MathUtil.equals(min, max))
                                continue;
                        
-                       double d = (max-min) / axis.getRangeLength();
-                       d = Math.sqrt(d);  // Prioritize small ranges
+                       double d = (max - min) / axis.getRangeLength();
+                       d = Math.sqrt(d); // Prioritize small ranges
                        goodness += d * 100.0;
                }
                
-               
+
                /*
                 * Add extra points for specific things.
                 */
@@ -682,7 +684,7 @@ public class PlotConfiguration implements Cloneable {
                
                // A boost if a common zero was used in the ranging
                Axis right = allAxes.get(1);
-               if (left.getMinValue() <= 0 &&  left.getMaxValue() >= 0 &&
+               if (left.getMinValue() <= 0 && left.getMaxValue() >= 0 &&
                                right.getMinValue() <= 0 && right.getMaxValue() >= 0)
                        goodness += BONUS_COMMON_ZERO;
                
@@ -694,7 +696,7 @@ public class PlotConfiguration implements Cloneable {
        }
        
        
-       
+
        /**
         * Reset the units of this configuration to the default units. Returns this
         * PlotConfiguration.
@@ -702,16 +704,15 @@ public class PlotConfiguration implements Cloneable {
         * @return   this PlotConfiguration.
         */
        public PlotConfiguration resetUnits() {
-               for (int i=0; i < plotDataTypes.size(); i++) {
+               for (int i = 0; i < plotDataTypes.size(); i++) {
                        plotDataUnits.set(i, plotDataTypes.get(i).getUnitGroup().getDefaultUnit());
                }
                return this;
        }
        
        
-       
-       
-       @SuppressWarnings("unchecked")
+
+
        @Override
        public PlotConfiguration clone() {
                try {
@@ -719,20 +720,20 @@ public class PlotConfiguration implements Cloneable {
                        PlotConfiguration copy = (PlotConfiguration) super.clone();
                        
                        // Shallow-clone all immutable lists
-                       copy.plotDataTypes = (ArrayList<FlightDataType>) this.plotDataTypes.clone();
-                       copy.plotDataAxes = (ArrayList<Integer>) this.plotDataAxes.clone();
-                       copy.plotDataUnits = (ArrayList<Unit>) this.plotDataUnits.clone();
+                       copy.plotDataTypes = this.plotDataTypes.clone();
+                       copy.plotDataAxes = this.plotDataAxes.clone();
+                       copy.plotDataUnits = this.plotDataUnits.clone();
                        copy.events = this.events.clone();
                        
                        // Deep-clone all Axis since they are mutable
                        copy.allAxes = new ArrayList<Axis>();
-                       for (Axis a: this.allAxes) {
+                       for (Axis a : this.allAxes) {
                                copy.allAxes.add(a.clone());
                        }
                        
                        return copy;
                        
-                       
+
                } catch (CloneNotSupportedException e) {
                        throw new BugException("BUG! Could not clone().");
                }
index ed1293e0a0f5d1acad5516a0e235727b6ba407e5..774ad152cf873375ada55063c834f1eee99bd2ef 100644 (file)
@@ -105,8 +105,8 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
        
 
        private SimulationWorker backgroundSimulationWorker = null;
+       private boolean dirty = false;
        
-
        private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
        
 
diff --git a/src/net/sf/openrocket/l10n/DebugTranslator.java b/src/net/sf/openrocket/l10n/DebugTranslator.java
new file mode 100644 (file)
index 0000000..19e6c84
--- /dev/null
@@ -0,0 +1,16 @@
+package net.sf.openrocket.l10n;
+
+/**
+ * A translator implementation that returns the logical key in brackets instead
+ * of an actual translation.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class DebugTranslator implements Translator {
+       
+       @Override
+       public String get(String key) {
+               return "[" + key + "]";
+       }
+       
+}
diff --git a/src/net/sf/openrocket/l10n/ResourceBundleTranslator.java b/src/net/sf/openrocket/l10n/ResourceBundleTranslator.java
new file mode 100644 (file)
index 0000000..5bfdbd2
--- /dev/null
@@ -0,0 +1,60 @@
+package net.sf.openrocket.l10n;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A translator that obtains translated strings from a resource bundle.
+ * <p>
+ * If a message is not found in any resource bundle, an error is logged and the key itself
+ * is returned.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ResourceBundleTranslator implements Translator {
+       private static final LogHelper log = Application.getLogger();
+       
+       private final ResourceBundle bundle;
+       private final String baseName;
+       private final Locale locale;
+       
+       /**
+        * Create a ResourceBundleTranslator using the default Locale.
+        * 
+        * @param baseName      the base name of the resource bundle
+        */
+       public ResourceBundleTranslator(String baseName) {
+               this(baseName, Locale.getDefault());
+       }
+       
+       /**
+        * Create a ResourceBundleTranslator using the specified Locale.
+        * 
+        * @param baseName      the base name of the resource bundle
+        * @param locale        the locale to use
+        */
+       public ResourceBundleTranslator(String baseName, Locale locale) {
+               this.bundle = ResourceBundle.getBundle(baseName, locale);
+               this.baseName = baseName;
+               this.locale = locale;
+       }
+       
+       
+       /*
+        * NOTE:  This method must be thread-safe!
+        */
+       @Override
+       public synchronized String get(String key) {
+               try {
+                       return bundle.getString(key);
+               } catch (MissingResourceException e) {
+                       log.error("String not found for key '" + key + "' in bundle '" + baseName + "' with locale " + locale, e);
+               }
+               return key;
+       }
+       
+}
diff --git a/src/net/sf/openrocket/l10n/Translator.java b/src/net/sf/openrocket/l10n/Translator.java
new file mode 100644 (file)
index 0000000..c95d7be
--- /dev/null
@@ -0,0 +1,22 @@
+package net.sf.openrocket.l10n;
+
+/**
+ * An interface for obtaining translations from logical keys.
+ * <p>
+ * Translator implementations must be thread-safe.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface Translator {
+       
+       /**
+        * Retrieve a translated string based on a logical key.  This always returns
+        * some string, potentially falling back to the key itself.
+        * 
+        * @param key   the logical string key.
+        * @return              the translated string.
+        * @throws NullPointerException if key is null.
+        */
+       public String get(String key);
+       
+}
index 45ff9e55b963fa0699d920e8c4dd121eeac2a103..8c3a22dfdd93c51451be16777d9438d326205a62 100644 (file)
@@ -1,8 +1,9 @@
 package net.sf.openrocket.logging;
 
-import java.util.ArrayList;
 import java.util.List;
 
+import net.sf.openrocket.util.ArrayList;
+
 /**
  * A logger implementation that delegates logging to other logger implementations.
  * Multiple loggers can be added to the delegator, all of which will receive
@@ -11,7 +12,7 @@ import java.util.List;
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 public class DelegatorLogger extends LogHelper {
-
+       
        /**
         * List of loggers.  This list must not be modified, instead it should be
         * replaced every time the list is changed.
@@ -22,7 +23,7 @@ public class DelegatorLogger extends LogHelper {
        public void log(LogLine line) {
                // Must create local reference for thread safety
                List<LogHelper> list = loggers;
-               for (LogHelper l: list) {
+               for (LogHelper l : list) {
                        l.log(line);
                }
        }
@@ -32,9 +33,8 @@ public class DelegatorLogger extends LogHelper {
         * Add a logger from the delegation list.
         * @param logger        the logger to add.
         */
-       @SuppressWarnings("unchecked")
        public synchronized void addLogger(LogHelper logger) {
-               ArrayList<LogHelper> newList = (ArrayList<LogHelper>) loggers.clone();
+               ArrayList<LogHelper> newList = loggers.clone();
                newList.add(logger);
                this.loggers = newList;
        }
@@ -43,11 +43,10 @@ public class DelegatorLogger extends LogHelper {
         * Remove a logger from the delegation list.
         * @param logger        the logger to be removed.
         */
-       @SuppressWarnings("unchecked")
        public synchronized void removeLogger(LogHelper logger) {
-               ArrayList<LogHelper> newList = (ArrayList<LogHelper>) loggers.clone();
+               ArrayList<LogHelper> newList = loggers.clone();
                newList.remove(logger);
                this.loggers = newList;
        }
-
+       
 }
index b08109a35f1645ce187fe20bba2393c1d13cb55f..01029d7d34e06493b316b6f4c1e7ff5973ca4912 100644 (file)
@@ -73,6 +73,19 @@ public class TraceException extends Exception {
        }
        
        
+       /**
+        * Construct an exception with the specified message and cause.
+        * 
+        * @param message       the message for the exception.
+        * @param cause         the cause for this exception.
+        */
+       public TraceException(String message, Throwable cause) {
+               this(0, 0);
+               this.message = message;
+               this.initCause(cause);
+       }
+       
+       
        /**
         * Get the description of the code position as provided in the constructor.
         */
index cfcf496bcdb1afffd2e61a3714b114145d3f44c6..30b53de64022b0eaceb3b3290e82a86e07bf8504 100644 (file)
@@ -1,6 +1,8 @@
 package net.sf.openrocket.masscalc;
 
+import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.startup.Application;
 
 /**
  * Abstract base for mass calculators.  Provides functionality for cacheing mass data.
@@ -8,9 +10,10 @@ import net.sf.openrocket.rocketcomponent.Configuration;
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 public abstract class AbstractMassCalculator implements MassCalculator {
+       private static final LogHelper log = Application.getLogger();
        
        private int rocketMassModID = -1;
-       private int stageCount = -1;
+       private int rocketTreeModID = -1;
        
        
        /**
@@ -26,9 +29,10 @@ public abstract class AbstractMassCalculator implements MassCalculator {
         */
        protected final void checkCache(Configuration configuration) {
                if (rocketMassModID != configuration.getRocket().getMassModID() ||
-                               stageCount != configuration.getStageCount()) {
+                               rocketTreeModID != configuration.getRocket().getTreeModID()) {
                        rocketMassModID = configuration.getRocket().getMassModID();
-                       stageCount = configuration.getStageCount();
+                       rocketTreeModID = configuration.getRocket().getTreeModID();
+                       log.debug("Voiding the mass cache");
                        voidMassCache();
                }
        }
index 38d7b218314c7715e442994bd34e48ab981b42d7..4b5d8276f2f74ca4219c0ad9c0aaf901f8eef5fc 100644 (file)
@@ -26,7 +26,7 @@ public class BasicMassCalculator extends AbstractMassCalculator {
         * are relative to their respective CG.
         */
        private Coordinate[] cgCache = null;
-       private double longitudalInertiaCache[] = null;
+       private double longitudinalInertiaCache[] = null;
        private double rotationalInertiaCache[] = null;
        
        
@@ -105,13 +105,13 @@ public class BasicMassCalculator extends AbstractMassCalculator {
        
        
        /**
-        * Return the longitudal inertia of the rocket with the specified motor instance
+        * Return the longitudinal inertia of the rocket with the specified motor instance
         * configuration.
         * 
         * @param configuration         the current motor instance configuration
-        * @return                                      the longitudal inertia of the rocket
+        * @return                                      the longitudinal inertia of the rocket
         */
-       public double getLongitudalInertia(Configuration configuration, MotorInstanceConfiguration motors) {
+       public double getLongitudinalInertia(Configuration configuration, MotorInstanceConfiguration motors) {
                checkCache(configuration);
                calculateStageCache(configuration);
                
@@ -122,7 +122,7 @@ public class BasicMassCalculator extends AbstractMassCalculator {
                for (int stage : configuration.getActiveStages()) {
                        Coordinate stageCG = cgCache[stage];
                        
-                       totalInertia += (longitudalInertiaCache[stage] +
+                       totalInertia += (longitudinalInertiaCache[stage] +
                                        stageCG.weight * MathUtil.pow2(stageCG.x - totalCG.x));
                }
                
@@ -136,7 +136,7 @@ public class BasicMassCalculator extends AbstractMassCalculator {
                                        Coordinate position = motors.getMotorPosition(id);
                                        Coordinate cg = motor.getCG().add(position);
                                        
-                                       double inertia = motor.getLongitudalInertia();
+                                       double inertia = motor.getLongitudinalInertia();
                                        totalInertia += inertia + cg.weight * MathUtil.pow2(cg.x - totalCG.x);
                                }
                        }
@@ -221,14 +221,14 @@ public class BasicMassCalculator extends AbstractMassCalculator {
                        int stages = config.getRocket().getStageCount();
                        
                        cgCache = new Coordinate[stages];
-                       longitudalInertiaCache = new double[stages];
+                       longitudinalInertiaCache = new double[stages];
                        rotationalInertiaCache = new double[stages];
                        
                        for (int i = 0; i < stages; i++) {
                                RocketComponent stage = config.getRocket().getChild(i);
                                MassData data = calculateAssemblyMassData(stage);
                                cgCache[i] = stage.toAbsolute(data.cg)[0];
-                               longitudalInertiaCache[i] = data.longitudalInertia;
+                               longitudinalInertiaCache[i] = data.longitudinalInertia;
                                rotationalInertiaCache[i] = data.rotationalInetria;
                        }
                        
@@ -259,7 +259,7 @@ public class BasicMassCalculator extends AbstractMassCalculator {
                                parentData.cg = parentData.cg.setXYZ(parent.getOverrideCG());
                }
                
-               parentData.longitudalInertia = parent.getLongitudalUnitInertia() * parentData.cg.weight;
+               parentData.longitudinalInertia = parent.getLongitudinalUnitInertia() * parentData.cg.weight;
                parentData.rotationalInetria = parent.getRotationalUnitInertia() * parentData.cg.weight;
                
 
@@ -279,19 +279,19 @@ public class BasicMassCalculator extends AbstractMassCalculator {
                                
                                // Add effect of this CG change to parent inertia
                                dx2 = pow2(parentData.cg.x - combinedCG.x);
-                               parentData.longitudalInertia += parentData.cg.weight * dx2;
+                               parentData.longitudinalInertia += parentData.cg.weight * dx2;
                                
                                dr2 = pow2(parentData.cg.y - combinedCG.y) + pow2(parentData.cg.z - combinedCG.z);
                                parentData.rotationalInetria += parentData.cg.weight * dr2;
                                
 
                                // Add inertia of sibling
-                               parentData.longitudalInertia += siblingData.longitudalInertia;
+                               parentData.longitudinalInertia += siblingData.longitudinalInertia;
                                parentData.rotationalInetria += siblingData.rotationalInetria;
                                
                                // Add effect of sibling CG change
                                dx2 = pow2(siblingData.cg.x - combinedCG.x);
-                               parentData.longitudalInertia += siblingData.cg.weight * dx2;
+                               parentData.longitudinalInertia += siblingData.cg.weight * dx2;
                                
                                dr2 = pow2(siblingData.cg.y - combinedCG.y) + pow2(siblingData.cg.z - combinedCG.z);
                                parentData.rotationalInetria += siblingData.cg.weight * dr2;
@@ -306,14 +306,14 @@ public class BasicMassCalculator extends AbstractMassCalculator {
                        if (parent.isMassOverridden()) {
                                double oldMass = parentData.cg.weight;
                                double newMass = MathUtil.max(parent.getOverrideMass(), MIN_MASS);
-                               parentData.longitudalInertia = parentData.longitudalInertia * newMass / oldMass;
+                               parentData.longitudinalInertia = parentData.longitudinalInertia * newMass / oldMass;
                                parentData.rotationalInetria = parentData.rotationalInetria * newMass / oldMass;
                                parentData.cg = parentData.cg.setWeight(newMass);
                        }
                        if (parent.isCGOverridden()) {
                                double oldx = parentData.cg.x;
                                double newx = parent.getOverrideCGX();
-                               parentData.longitudalInertia += parentData.cg.weight * pow2(oldx - newx);
+                               parentData.longitudinalInertia += parentData.cg.weight * pow2(oldx - newx);
                                parentData.cg = parentData.cg.setX(newx);
                        }
                }
@@ -324,7 +324,7 @@ public class BasicMassCalculator extends AbstractMassCalculator {
        
        private static class MassData {
                public Coordinate cg = Coordinate.NUL;
-               public double longitudalInertia = 0;
+               public double longitudinalInertia = 0;
                public double rotationalInetria = 0;
        }
        
@@ -333,7 +333,7 @@ public class BasicMassCalculator extends AbstractMassCalculator {
        protected void voidMassCache() {
                super.voidMassCache();
                this.cgCache = null;
-               this.longitudalInertiaCache = null;
+               this.longitudinalInertiaCache = null;
                this.rotationalInertiaCache = null;
        }
        
index 732d2ec77ae38ae70aa7d46e45bc3e30a436c452..003650e11603ae6bbc5ae8b88ce82785dfa61557 100644 (file)
@@ -53,13 +53,13 @@ public interface MassCalculator extends Monitorable {
        public Coordinate getCG(Configuration configuration, MotorInstanceConfiguration motors);
        
        /**
-        * Compute the longitudal inertia of the provided configuration with specified motors.
+        * Compute the longitudinal inertia of the provided configuration with specified motors.
         * 
         * @param configuration         the rocket configuration
         * @param motors                        the motor configuration
-        * @return                                      the longitudal inertia of the configuration
+        * @return                                      the longitudinal inertia of the configuration
         */
-       public double getLongitudalInertia(Configuration configuration, MotorInstanceConfiguration motors);
+       public double getLongitudinalInertia(Configuration configuration, MotorInstanceConfiguration motors);
        
        /**
         * Compute the rotational inertia of the provided configuration with specified motors.
index 7811227f6039a469aace194fef492b44cb1edc1d..a216528bec7b6fc90fd987dd57773963c800e3c5 100644 (file)
@@ -16,6 +16,7 @@ public class BasicGravityModel implements GravityModel {
         * @param latitude      the latitude in degrees (-90 ... 90)
         */
        public BasicGravityModel(double latitude) {
+               // TODO: HIGH: This model is wrong!!  Increases monotonically from -90 to 90
                double sin = Math.sin(latitude * Math.PI / 180);
                double sin2 = Math.sin(2 * latitude * Math.PI / 180);
                g = 9.780327 * (1 + 0.0053024 * sin - 0.0000058 * sin2);
index 0122700f6205d01bfc7d7dd66e1046f48dcd754d..6b48e3a4b4c3286ec7288914f412b9e0dab4d1e1 100644 (file)
@@ -33,10 +33,10 @@ public interface MotorInstance extends Cloneable, Monitorable {
        public Coordinate getCG();
        
        /**
-        * Return the average longitudal moment of inertia during the last step.
+        * Return the average longitudinal moment of inertia during the last step.
         * This is the actual inertia, not the unit inertia!
         */
-       public double getLongitudalInertia();
+       public double getLongitudinalInertia();
        
        /**
         * Return the average rotational moment of inertia during the last step.
index 2bbddd14ad054ff2763564f919964c8cd2929cb8..a51283eaefd341c447d32c861c807b187ce0349c 100644 (file)
@@ -393,7 +393,7 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> {
                private Coordinate instCG;
                
                private final double unitRotationalInertia;
-               private final double unitLongitudalInertia;
+               private final double unitLongitudinalInertia;
                
                private int modID = 0;
                
@@ -406,7 +406,7 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> {
                        instCG = cg[0];
                        stepCG = cg[0];
                        unitRotationalInertia = Inertia.filledCylinderRotational(getDiameter() / 2);
-                       unitLongitudalInertia = Inertia.filledCylinderLongitudal(getDiameter() / 2, getLength());
+                       unitLongitudinalInertia = Inertia.filledCylinderLongitudinal(getDiameter() / 2, getLength());
                }
                
                @Override
@@ -420,8 +420,8 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> {
                }
                
                @Override
-               public double getLongitudalInertia() {
-                       return unitLongitudalInertia * stepCG.weight;
+               public double getLongitudinalInertia() {
+                       return unitLongitudinalInertia * stepCG.weight;
                }
                
                @Override
index 147ac0d4fc0a5fbb0a09adc35357661a9750b962..abaac19be757a825cb6a1c1bf477928a072b4340 100644 (file)
@@ -1,12 +1,16 @@
 package net.sf.openrocket.optimization.rocketoptimization;
 
 import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.ChangeSource;
 
 /**
  * An interface what modifies a single parameter in a rocket simulation
  * based on a double value in the range [0...1].
+ * <p>
+ * The implementation must fire change events when the minimum and maximum ranges
+ * are modified, NOT when the actual modified value changes.
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
@@ -32,8 +36,9 @@ public interface SimulationModifier extends ChangeSource {
        /**
         * Return the current value of the modifier in SI units.
         * @return      the current value of this parameter in SI units.
+        * @throws OptimizationException        if fetching the current value fails
         */
-       public double getCurrentValue();
+       public double getCurrentValue(Simulation simulation) throws OptimizationException;
        
        
        /**
@@ -63,7 +68,7 @@ public interface SimulationModifier extends ChangeSource {
        
        
        /**
-        * Return the unit group used for the values returned by {@link #getCurrentValue()} etc.
+        * Return the unit group used for the values returned by {@link #getCurrentValue(Simulation)} etc.
         * @return      the unit group
         */
        public UnitGroup getUnitGroup();
@@ -72,9 +77,11 @@ 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      the current value of this parameter (normally between [0 ... 1])
+        * @throws OptimizationException        if fetching the current value fails
         */
-       public double getCurrentScaledValue();
+       public double getCurrentScaledValue(Simulation simulation) throws OptimizationException;
        
        
 
@@ -83,7 +90,8 @@ public interface SimulationModifier extends ChangeSource {
         * 
         * @param simulation    the simulation to modify
         * @param scaledValue   the scaled value in the range [0...1]
+        * @throws OptimizationException        if the modification fails
         */
-       public void modify(Simulation simulation, double scaledValue);
+       public void modify(Simulation simulation, double scaledValue) throws OptimizationException;
        
 }
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/AbstractSimulationModifier.java b/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/AbstractSimulationModifier.java
new file mode 100644 (file)
index 0000000..04ba7bc
--- /dev/null
@@ -0,0 +1,157 @@
+package net.sf.openrocket.optimization.rocketoptimization.modifiers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.MathUtil;
+
+/**
+ * An abstract implementation of the SimulationModifier interface.  An implementation
+ * needs only to implement the {@link #getCurrentValue(Simulation)} and
+ * {@link #modify(net.sf.openrocket.document.Simulation, double)} methods.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class AbstractSimulationModifier implements SimulationModifier {
+       
+       private final String name;
+       private final Object relatedObject;
+       private final UnitGroup unitGroup;
+       
+       private double minValue = 0.0;
+       private double maxValue = 1.0;
+       
+       private final List<ChangeListener> listeners = new ArrayList<ChangeListener>();
+       
+       
+       /**
+        * Sole constructor.
+        * 
+        * @param modifierName          the name of this modifier (returned by {@link #getName()})
+        * @param relatedObject         the related object (returned by {@link #getRelatedObject()})
+        * @param unitGroup                     the unit group (returned by {@link #getUnitGroup()})
+        */
+       public AbstractSimulationModifier(String modifierName, Object relatedObject, UnitGroup unitGroup) {
+               this.name = modifierName;
+               this.relatedObject = relatedObject;
+               this.unitGroup = unitGroup;
+       }
+       
+       
+       @Override
+       public String getName() {
+               return name;
+       }
+       
+       @Override
+       public Object getRelatedObject() {
+               return relatedObject;
+       }
+       
+       @Override
+       public double getCurrentScaledValue(Simulation simulation) throws OptimizationException {
+               double value = getCurrentValue(simulation);
+               return toScaledValue(value);
+       }
+       
+       
+
+       /**
+        * Returns the scaled value (normally within [0...1]).  If the min...max range is singular,
+        * this method returns 0.0, 1.0 or 0.5 depending on whether the value is less than,
+        * greater than or equal to the limit.
+        * 
+        * @param value         the value in SI units
+        * @return                      the value in scaled range (normally within [0...1])
+        */
+       protected 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).
+        * 
+        * @param value         the value in scaled range (normally within [0...1])
+        * @return                      the value in SI units
+        */
+       protected 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) {
+               listeners.add(listener);
+       }
+       
+       @Override
+       public void removeChangeListener(ChangeListener listener) {
+               listeners.remove(listener);
+       }
+       
+       
+       /**
+        * Fire a change event to the listeners.
+        */
+       protected void fireChangeEvent() {
+               ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
+               ChangeEvent event = new ChangeEvent(this);
+               for (ChangeListener l : array) {
+                       l.stateChanged(event);
+               }
+       }
+       
+}
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericComponentModifier.java b/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericComponentModifier.java
new file mode 100644 (file)
index 0000000..c05454c
--- /dev/null
@@ -0,0 +1,49 @@
+package net.sf.openrocket.optimization.rocketoptimization.modifiers;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.unit.UnitGroup;
+
+/**
+ * A generic simulation modifier that modifies a value of a certain RocketComponent
+ * based on the component's ID and the value name.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class GenericComponentModifier extends GenericModifier<RocketComponent> {
+       
+       private final Class<RocketComponent> componentClass;
+       private final String componentId;
+       
+       /**
+        * Sole constructor.
+        * 
+        * @param modifierName          the name of this modifier (returned by {@link #getName()})
+        * @param relatedObject         the related object (returned by {@link #getRelatedObject()})
+        * @param unitGroup                     the unit group (returned by {@link #getUnitGroup()})
+        * @param multiplier            the multiplier by which the value returned by the getter is multiplied
+        *                                                      to obtain the desired value
+        * @param componentClass        the RocketComponent class type that is being modified
+        * @param componentId           the ID of the component to modify
+        * @param methodName            the base name of the getter/setter methods (without "get"/"set")
+        */
+       public GenericComponentModifier(String modifierName, Object relatedObject, UnitGroup unitGroup,
+                       double multiplier, Class<RocketComponent> componentClass, String componentId, String methodName) {
+               super(modifierName, relatedObject, unitGroup, multiplier, componentClass, methodName);
+               
+               this.componentClass = componentClass;
+               this.componentId = componentId;
+       }
+       
+       @Override
+       protected RocketComponent getModifiedObject(Simulation simulation) throws OptimizationException {
+               RocketComponent c = simulation.getRocket().findComponent(componentId);
+               if (c == null) {
+                       throw new OptimizationException("Could not find component of type " + componentClass.getSimpleName()
+                                       + " with correct ID");
+               }
+               return c;
+       }
+       
+}
index ac62dc62632e1d26477861e0209ecef4eb2ff1dd..6df312410938baec97b6e5a2af6921c57c7ec4bf 100644 (file)
@@ -1,39 +1,42 @@
 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.optimization.general.OptimizationException;
 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 {
+/**
+ * A generic SimulationModifier that uses reflection to get and set a double value.
+ * Implementations need to implement the {@link #getModifiedObject(Simulation)} method
+ * to return which object is modified.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class GenericModifier<T> extends AbstractSimulationModifier {
        
-       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;
-       
        
-
-
-
+       /**
+        * Sole constructor.
+        * 
+        * @param modifierName          the name of this modifier (returned by {@link #getName()})
+        * @param relatedObject         the related object (returned by {@link #getRelatedObject()})
+        * @param unitGroup                     the unit group (returned by {@link #getUnitGroup()})
+        * @param multiplier            the multiplier by which the value returned by the getter is multiplied
+        *                                                      to obtain the desired value
+        * @param modifiedClass         the class type that {@link #getModifiedObject(Simulation)} returns
+        * @param methodName            the base name of the getter/setter methods (without "get"/"set")
+        */
        public GenericModifier(String modifierName, Object relatedObject, UnitGroup unitGroup, double multiplier,
-                       Object modifiable, String methodName) {
-               this.name = modifierName;
-               this.relatedObject = relatedObject;
-               this.unitGroup = unitGroup;
+                       Class<T> modifiedClass, String methodName) {
+               super(modifierName, relatedObject, unitGroup);
                this.multiplier = multiplier;
-               this.modifiable = modifiable;
                
                if (MathUtil.equals(multiplier, 0)) {
                        throw new IllegalArgumentException("multiplier is zero");
@@ -41,122 +44,45 @@ public class GenericModifier implements SimulationModifier {
                
                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));
+                       getter = new Method(modifiedClass.getMethod("get" + methodName));
+                       setter = new Method(modifiedClass.getMethod("set" + methodName, double.class));
                } catch (SecurityException e) {
-                       throw new BugException("Trying to find method get/set" + methodName + " in class " + modifiable.getClass(), e);
+                       throw new BugException("Trying to find method get/set" + methodName + " in class " + modifiedClass, e);
                } catch (NoSuchMethodException e) {
-                       throw new BugException("Trying to find method get/set" + methodName + " in class " + modifiable.getClass(), e);
+                       throw new BugException("Trying to find method get/set" + methodName + " in class " + modifiedClass, e);
                }
        }
        
        
+
        @Override
-       public String getName() {
-               return name;
-       }
-       
-       @Override
-       public Object getRelatedObject() {
-               return relatedObject;
-       }
-       
-       @Override
-       public double getCurrentValue() {
+       public double getCurrentValue(Simulation simulation) throws OptimizationException {
+               T modifiable = getModifiedObject(simulation);
+               if (modifiable == null) {
+                       throw new OptimizationException("BUG: getModifiedObject() returned null");
+               }
                return ((Double) getter.invoke(modifiable)) * multiplier;
        }
        
        
        @Override
-       public double getCurrentScaledValue() {
-               double value = getCurrentValue();
-               return toScaledValue(value);
-       }
-       
-       @Override
-       public void modify(Simulation simulation, double scaledValue) {
+       public void modify(Simulation simulation, double scaledValue) throws OptimizationException {
+               T modifiable = getModifiedObject(simulation);
+               if (modifiable == null) {
+                       throw new OptimizationException("BUG: getModifiedObject() returned null");
+               }
                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).
+        * Return the object from the simulation that will be modified.
+        * @param simulation    the simulation
+        * @return                              the object to modify
+        * 
+        * @throws OptimizationException        if the object cannot be found
         */
-       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
-               
-       }
+       protected abstract T getModifiedObject(Simulation simulation) throws OptimizationException;
        
 }
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/services/DefaultSimulationModifierService.java b/src/net/sf/openrocket/optimization/rocketoptimization/services/DefaultSimulationModifierService.java
new file mode 100644 (file)
index 0000000..8bfd55c
--- /dev/null
@@ -0,0 +1,24 @@
+package net.sf.openrocket.optimization.rocketoptimization.services;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationModifierService;
+
+public class DefaultSimulationModifierService implements SimulationModifierService {
+       
+       @Override
+       public Collection<SimulationModifier> getModifiers(OpenRocketDocument document) {
+               // TODO: Should this really be OpenRocketDocument instead of Simulation?
+               List<SimulationModifier> list = new ArrayList<SimulationModifier>();
+               
+               // TODO: implement
+               
+
+               return null;
+       }
+       
+}
index 5656c72b32557bcc73a092242a6949e9180688c6..f45f352653f3cdc8d871d5f1dfbd1dd339354a5f 100644 (file)
@@ -258,7 +258,7 @@ public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial
        
        
        @Override
-       public double getLongitudalUnitInertia() {
+       public double getLongitudinalUnitInertia() {
                // 1/12 * (3 * (r1^2 + r2^2) + h^2)
                return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) +
                                MathUtil.pow2(getLength())) / 12;
index 1f60917eec5e942ba4da417ff643f01dfbf17c63..de5b78d26d86b10054d05c06854894c18f96842c 100644 (file)
@@ -53,7 +53,7 @@ public abstract class ComponentAssembly extends RocketComponent {
         * Null method (ComponentAssembly has no mass of itself).
         */
        @Override
-       public double getLongitudalUnitInertia() {
+       public double getLongitudinalUnitInertia() {
                return 0;
        }
        
index ca3e58fba3183ddc7b03392a5a1fb624e4e30f08..9ab9c0b3c8f5dbf62cdf591e3f10d35d5aba92b7 100644 (file)
@@ -1,6 +1,5 @@
 package net.sf.openrocket.rocketcomponent;
 
-import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
@@ -12,6 +11,7 @@ import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 import javax.swing.event.EventListenerList;
 
+import net.sf.openrocket.util.ArrayList;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.ChangeSource;
 import net.sf.openrocket.util.Coordinate;
@@ -260,7 +260,6 @@ public class Configuration implements Cloneable, ChangeSource, ComponentChangeLi
         * 
         * @return      a <code>Collection</code> containing coordinates bouding the rocket.
         */
-       @SuppressWarnings("unchecked")
        public Collection<Coordinate> getBounds() {
                if (rocket.getModID() != boundsModID) {
                        boundsModID = rocket.getModID();
@@ -285,7 +284,7 @@ public class Configuration implements Cloneable, ChangeSource, ComponentChangeLi
                                cachedLength = maxX - minX;
                        }
                }
-               return (ArrayList<Coordinate>) cachedBounds.clone();
+               return cachedBounds.clone();
        }
        
        
@@ -368,7 +367,7 @@ public class Configuration implements Cloneable, ChangeSource, ComponentChangeLi
                        
                        for (RocketComponent stage : rocket.getChildren()) {
                                if (isStageActive(stage)) {
-                                       list.add(stage.deepIterator());
+                                       list.add(stage.iterator(false));
                                }
                        }
                        
index 8a12090465f764ce457652df8117fb8f9708c68e..6fa4a4c2df903893b898e4b4fc5b26090924be31 100644 (file)
@@ -1,5 +1,7 @@
 package net.sf.openrocket.rocketcomponent;
 
+import java.util.List;
+
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.Prefs;
@@ -39,16 +41,16 @@ public abstract class ExternalComponent extends RocketComponent {
                }
        }
        
-
+       
        /**
         * The material of the component.
         */
-       protected Material material=null;
+       protected Material material = null;
        
        protected Finish finish = Finish.NORMAL;
        
        
-       
+
        /**
         * Constructor that sets the relative position of the component.
         */
@@ -56,13 +58,13 @@ public abstract class ExternalComponent extends RocketComponent {
                super(relativePosition);
                this.material = Prefs.getDefaultComponentMaterial(this.getClass(), Material.Type.BULK);
        }
-
+       
        /**
         * Returns the volume of the component.  This value is used in calculating the mass
         * of the object.
         */
        public abstract double getComponentVolume();
-
+       
        /**
         * Calculates the mass of the component as the product of the volume and interior density.
         */
@@ -70,7 +72,7 @@ public abstract class ExternalComponent extends RocketComponent {
        public double getComponentMass() {
                return material.getDensity() * getComponentVolume();
        }
-
+       
        /**
         * ExternalComponent has aerodynamic effect, so return true.
         */
@@ -95,9 +97,9 @@ public abstract class ExternalComponent extends RocketComponent {
        public void setMaterial(Material mat) {
                if (mat.getType() != Material.Type.BULK) {
                        throw new IllegalArgumentException("ExternalComponent requires a bulk material" +
-                                       " type="+mat.getType());
+                                       " type=" + mat.getType());
                }
-
+               
                if (material.equals(mat))
                        return;
                material = mat;
@@ -114,16 +116,15 @@ public abstract class ExternalComponent extends RocketComponent {
                this.finish = finish;
                fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
        }
-
        
        
+
        @Override
-       protected void copyFrom(RocketComponent c) {
-               super.copyFrom(c);
-               
-               ExternalComponent src = (ExternalComponent)c;
+       protected List<RocketComponent> copyFrom(RocketComponent c) {
+               ExternalComponent src = (ExternalComponent) c;
                this.finish = src.finish;
                this.material = src.material;
+               return super.copyFrom(c);
        }
        
     /**
index ea60d417c0c956848b9db166dff5341e04416614..8d6a77f490b7e027d3b14434fec5bf91bd1cf6fc 100644 (file)
@@ -465,7 +465,7 @@ public abstract class FinSet extends ExternalComponent {
        
        
        /*
-        * Return an approximation of the longitudal unitary inertia of the fin set.
+        * Return an approximation of the longitudinal unitary inertia of the fin set.
         * The process is the following:
         * 
         * 1. Approximate the fin with a rectangular fin
@@ -478,7 +478,7 @@ public abstract class FinSet extends ExternalComponent {
         *    set and multiplied by the number of fins.
         */
        @Override
-       public double getLongitudalUnitInertia() {
+       public double getLongitudinalUnitInertia() {
                double area = getFinArea();
                if (MathUtil.equals(area, 0))
                        return 0;
@@ -684,9 +684,7 @@ public abstract class FinSet extends ExternalComponent {
        
        
        @Override
-       protected void copyFrom(RocketComponent c) {
-               super.copyFrom(c);
-               
+       protected List<RocketComponent> copyFrom(RocketComponent c) {
                FinSet src = (FinSet) c;
                this.fins = src.fins;
                this.finRotation = src.finRotation;
@@ -700,5 +698,7 @@ public abstract class FinSet extends ExternalComponent {
                this.tabLength = src.tabLength;
                this.tabRelativePosition = src.tabRelativePosition;
                this.tabShift = src.tabShift;
+               
+               return super.copyFrom(c);
        }
 }
index ce6402151bc646972bd055b34235c9e63f4cc378..24ca9811e81aebd41bdc8b90b2d5e164754976bb 100644 (file)
@@ -1,13 +1,15 @@
 package net.sf.openrocket.rocketcomponent;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
 import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.ArrayList;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.Coordinate;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-
 
 public class FreeformFinSet extends FinSet {
        private static final LogHelper log = Application.getLogger();
@@ -56,6 +58,7 @@ public class FreeformFinSet extends FinSet {
                log.info("Converting " + finset.getComponentName() + " into freeform fin set");
                final RocketComponent root = finset.getRoot();
                FreeformFinSet freeform;
+               List<RocketComponent> toInvalidate = Collections.emptyList();
                
                try {
                        if (root instanceof Rocket) {
@@ -84,7 +87,7 @@ public class FreeformFinSet extends FinSet {
                        }
                        
                        // Copy component attributes
-                       freeform.copyFrom(finset);
+                       toInvalidate = freeform.copyFrom(finset);
                        
                        // Set name
                        final String componentTypeName = finset.getComponentName();
@@ -104,6 +107,10 @@ public class FreeformFinSet extends FinSet {
                        if (root instanceof Rocket) {
                                ((Rocket) root).thaw();
                        }
+                       // Invalidate components after events have been fired
+                       for (RocketComponent c : toInvalidate) {
+                               c.invalidate();
+                       }
                }
                return freeform;
        }
@@ -138,13 +145,12 @@ public class FreeformFinSet extends FinSet {
         * @param index   the fin point index to remove
         * @throws IllegalFinPointException if removing would result in invalid fin planform
         */
-       @SuppressWarnings("unchecked")
        public void removePoint(int index) throws IllegalFinPointException {
                if (index == 0 || index == points.size() - 1) {
                        throw new IllegalFinPointException("cannot remove first or last point");
                }
                
-               ArrayList<Coordinate> copy = (ArrayList<Coordinate>) this.points.clone();
+               ArrayList<Coordinate> copy = this.points.clone();
                copy.remove(index);
                validate(copy);
                this.points = copy;
@@ -301,23 +307,22 @@ public class FreeformFinSet extends FinSet {
        }
        
        
-       @SuppressWarnings("unchecked")
        @Override
        protected RocketComponent copyWithOriginalID() {
                RocketComponent c = super.copyWithOriginalID();
-               ((FreeformFinSet) c).points = (ArrayList<Coordinate>) this.points.clone();
+               ((FreeformFinSet) c).points = this.points.clone();
                return c;
        }
        
-    /**
-     * Accept a visitor to this FreeformFinSet in the component hierarchy.
-     * 
-     * @param theVisitor  the visitor that will be called back with a reference to this FreeformFinSet
-     */    
-    @Override
-    public void accept(ComponentVisitor theVisitor) {
-        theVisitor.visit(this);
-    }
+       /**
+        * Accept a visitor to this FreeformFinSet in the component hierarchy.
+        
+        * @param theVisitor  the visitor that will be called back with a reference to this FreeformFinSet
+        */
+       @Override
+       public void accept(ComponentVisitor theVisitor) {
+               theVisitor.visit(this);
+       }
        
        private void validate(ArrayList<Coordinate> points) throws IllegalFinPointException {
                final int n = points.size();
index 500e9063bd34b31675b3ba88197b29e7dd40ae5f..a51629a3693b305279e711e9ed32de8a44602c0f 100644 (file)
@@ -173,7 +173,7 @@ public class LaunchLug extends ExternalComponent implements Coaxial {
        }
        
        @Override
-       public double getLongitudalUnitInertia() {
+       public double getLongitudinalUnitInertia() {
                // 1/12 * (3 * (r1^2 + r2^2) + h^2)
                return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) +
                                MathUtil.pow2(getLength())) / 12;
index 0e49030671c72480b9525f65769653775992efb0..bbd9789db1b98a006985e00144ecc8308d944522 100644 (file)
@@ -1,12 +1,12 @@
 package net.sf.openrocket.rocketcomponent;
 
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.MathUtil;
+import static net.sf.openrocket.util.MathUtil.pow2;
 
 import java.util.ArrayList;
 import java.util.Collection;
 
-import static net.sf.openrocket.util.MathUtil.pow2;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
 
 
 /**
@@ -19,129 +19,130 @@ import static net.sf.openrocket.util.MathUtil.pow2;
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 public abstract class MassObject extends InternalComponent {
-
-    private double radius;
-
-    private double radialPosition;
-    private double radialDirection;
-
-    private double shiftY = 0;
-    private double shiftZ = 0;
-
-
-    public MassObject () {
-        this(0.03, 0.015);
-    }
-
-    public MassObject (double length, double radius) {
-        super();
-
-        this.length = length;
-        this.radius = radius;
-
-        this.setRelativePosition(Position.TOP);
-        this.setPositionValue(0.0);
-    }
-
-
-    public void setLength (double length) {
-        length = Math.max(length, 0);
-        if (MathUtil.equals(this.length, length)) {
-            return;
-        }
-        this.length = length;
-        fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
-    }
-
-
-    public final double getRadius () {
-        return radius;
-    }
-
-
-    public final void setRadius (double radius) {
-        radius = Math.max(radius, 0);
-        if (MathUtil.equals(this.radius, radius)) {
-            return;
-        }
-        this.radius = radius;
-        fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
-    }
-
-
-    public final double getRadialPosition () {
-        return radialPosition;
-    }
-
-    public final void setRadialPosition (double radialPosition) {
-        radialPosition = Math.max(radialPosition, 0);
-        if (MathUtil.equals(this.radialPosition, radialPosition)) {
-            return;
-        }
-        this.radialPosition = radialPosition;
-        shiftY = radialPosition * Math.cos(radialDirection);
-        shiftZ = radialPosition * Math.sin(radialDirection);
-        fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
-    }
-
-    public final double getRadialDirection () {
-        return radialDirection;
-    }
-
-    public final void setRadialDirection (double radialDirection) {
-        radialDirection = MathUtil.reduce180(radialDirection);
-        if (MathUtil.equals(this.radialDirection, radialDirection)) {
-            return;
-        }
-        this.radialDirection = radialDirection;
-        shiftY = radialPosition * Math.cos(radialDirection);
-        shiftZ = radialPosition * Math.sin(radialDirection);
-        fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
-    }
-
-
-    /**
-     * Shift the coordinates according to the radial position and direction.
-     */
-    @Override
-    public final Coordinate[] shiftCoordinates (Coordinate[] array) {
-        for (int i = 0; i < array.length; i++) {
-            array[i] = array[i].add(0, shiftY, shiftZ);
-        }
-        return array;
-    }
-
-    @Override
-    public final Coordinate getComponentCG () {
-        return new Coordinate(length / 2, shiftY, shiftZ, getComponentMass());
-    }
-
-    @Override
-    public final double getLongitudalUnitInertia () {
-        return (3 * pow2(radius) + pow2(length)) / 12;
-    }
-
-    @Override
-    public final double getRotationalUnitInertia () {
-        return pow2(radius) / 2;
-    }
-
-    @Override
-    public final Collection<Coordinate> getComponentBounds () {
-        Collection<Coordinate> c = new ArrayList<Coordinate>();
-        addBound(c, 0, radius);
-        addBound(c, length, radius);
-        return c;
-    }
-
-    /**
-     * Accept a visitor to this MassObject in the component hierarchy.
-     *
-     * @param theVisitor the visitor that will be called back with a reference to this MassObject
-     */
-    @Override
-    public void accept (final ComponentVisitor theVisitor) {
-        theVisitor.visit(this);
-    }
-
+       
+       private double radius;
+       
+       private double radialPosition;
+       private double radialDirection;
+       
+       private double shiftY = 0;
+       private double shiftZ = 0;
+       
+       
+       public MassObject() {
+               this(0.03, 0.015);
+       }
+       
+       public MassObject(double length, double radius) {
+               super();
+               
+               this.length = length;
+               this.radius = radius;
+               
+               this.setRelativePosition(Position.TOP);
+               this.setPositionValue(0.0);
+       }
+       
+       
+       public void setLength(double length) {
+               length = Math.max(length, 0);
+               if (MathUtil.equals(this.length, length)) {
+                       return;
+               }
+               this.length = length;
+               fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+       }
+       
+       
+       public final double getRadius() {
+               return radius;
+       }
+       
+       
+       public final void setRadius(double radius) {
+               radius = Math.max(radius, 0);
+               if (MathUtil.equals(this.radius, radius)) {
+                       return;
+               }
+               this.radius = radius;
+               fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+       }
+       
+       
+
+       public final double getRadialPosition() {
+               return radialPosition;
+       }
+       
+       public final void setRadialPosition(double radialPosition) {
+               radialPosition = Math.max(radialPosition, 0);
+               if (MathUtil.equals(this.radialPosition, radialPosition)) {
+                       return;
+               }
+               this.radialPosition = radialPosition;
+               shiftY = radialPosition * Math.cos(radialDirection);
+               shiftZ = radialPosition * Math.sin(radialDirection);
+               fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+       }
+       
+       public final double getRadialDirection() {
+               return radialDirection;
+       }
+       
+       public final void setRadialDirection(double radialDirection) {
+               radialDirection = MathUtil.reduce180(radialDirection);
+               if (MathUtil.equals(this.radialDirection, radialDirection)) {
+                       return;
+               }
+               this.radialDirection = radialDirection;
+               shiftY = radialPosition * Math.cos(radialDirection);
+               shiftZ = radialPosition * Math.sin(radialDirection);
+               fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+       }
+       
+       
+       /**
+        * Shift the coordinates according to the radial position and direction.
+        */
+       @Override
+       public final Coordinate[] shiftCoordinates(Coordinate[] array) {
+               for (int i = 0; i < array.length; i++) {
+                       array[i] = array[i].add(0, shiftY, shiftZ);
+               }
+               return array;
+       }
+       
+       @Override
+       public final Coordinate getComponentCG() {
+               return new Coordinate(length / 2, shiftY, shiftZ, getComponentMass());
+       }
+       
+       @Override
+       public final double getLongitudinalUnitInertia() {
+               return (3 * pow2(radius) + pow2(length)) / 12;
+       }
+       
+       @Override
+       public final double getRotationalUnitInertia() {
+               return pow2(radius) / 2;
+       }
+       
+       @Override
+       public final Collection<Coordinate> getComponentBounds() {
+               Collection<Coordinate> c = new ArrayList<Coordinate>();
+               addBound(c, 0, radius);
+               addBound(c, length, radius);
+               return c;
+       }
+       
+       /**
+        * Accept a visitor to this MassObject in the component hierarchy.
+        *
+        * @param theVisitor the visitor that will be called back with a reference to this MassObject
+        */
+       @Override
+       public void accept(final ComponentVisitor theVisitor) {
+               theVisitor.visit(this);
+       }
+       
 }
index 0450b4155addb667d212f6b8feee57851b33a510..b6c6cec60f729dac5ad8fca6952f5d390c820fc9 100644 (file)
@@ -204,8 +204,8 @@ public abstract class RingComponent extends StructuralComponent implements Coaxi
        
 
        @Override
-       public double getLongitudalUnitInertia() {
-               return ringLongitudalUnitInertia(getOuterRadius(), getInnerRadius(), getLength());
+       public double getLongitudinalUnitInertia() {
+               return ringLongitudinalUnitInertia(getOuterRadius(), getInnerRadius(), getLength());
        }
 
        @Override
index 509c9ff545e98e869b6f1f7601dd771d280a53a7..3a284534b36cf33638d04e4a1f446db8a3538338 100644 (file)
@@ -1,25 +1,26 @@
 package net.sf.openrocket.rocketcomponent;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+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.ArrayList;
 import net.sf.openrocket.util.Chars;
 import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.MathUtil;
 import net.sf.openrocket.util.UniqueID;
 
-import javax.swing.event.ChangeListener;
-import javax.swing.event.EventListenerList;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
-
 
 /**
  * Base for all rocket components.  This is the "starting point" for all rocket trees.
@@ -264,11 +265,11 @@ public class Rocket extends RocketComponent {
         * Make a deep copy of the Rocket structure.  This method is exposed as public to allow
         * for undo/redo system functionality.
         */
-       @Override
        @SuppressWarnings("unchecked")
+       @Override
        public Rocket copyWithOriginalID() {
                Rocket copy = (Rocket) super.copyWithOriginalID();
-               copy.motorConfigurationIDs = (ArrayList<String>) this.motorConfigurationIDs.clone();
+               copy.motorConfigurationIDs = this.motorConfigurationIDs.clone();
                copy.motorConfigurationNames =
                                (HashMap<String, String>) this.motorConfigurationNames.clone();
                copy.resetListeners();
@@ -288,15 +289,17 @@ public class Rocket extends RocketComponent {
         */
        @SuppressWarnings("unchecked")
        public void loadFrom(Rocket r) {
-               super.copyFrom(r);
+               
+               // Store list of components to invalidate after event has been fired
+               List<RocketComponent> toInvalidate = this.copyFrom(r);
                
                int type = ComponentChangeEvent.UNDO_CHANGE | ComponentChangeEvent.NONFUNCTIONAL_CHANGE;
                if (this.massModID != r.massModID)
                        type |= ComponentChangeEvent.MASS_CHANGE;
                if (this.aeroModID != r.aeroModID)
                        type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
-               if (this.treeModID != r.treeModID)
-                       type |= ComponentChangeEvent.TREE_CHANGE;
+               // Loading a rocket is always a tree change since the component objects change
+               type |= ComponentChangeEvent.TREE_CHANGE;
                
                this.modID = r.modID;
                this.massModID = r.massModID;
@@ -306,7 +309,7 @@ public class Rocket extends RocketComponent {
                this.refType = r.refType;
                this.customReferenceLength = r.customReferenceLength;
                
-               this.motorConfigurationIDs = (ArrayList<String>) r.motorConfigurationIDs.clone();
+               this.motorConfigurationIDs = r.motorConfigurationIDs.clone();
                this.motorConfigurationNames =
                                (HashMap<String, String>) r.motorConfigurationNames.clone();
                this.perfectFinish = r.perfectFinish;
@@ -315,7 +318,14 @@ public class Rocket extends RocketComponent {
                if (!this.motorConfigurationIDs.contains(id))
                        defaultConfiguration.setMotorConfigurationID(null);
                
+               this.checkComponentStructure();
+               
                fireComponentChangeEvent(type);
+               
+               // Invalidate obsolete components after event
+               for (RocketComponent c : toInvalidate) {
+                       c.invalidate();
+               }
        }
        
        
@@ -374,44 +384,49 @@ public class Rocket extends RocketComponent {
        
        @Override
        protected void fireComponentChangeEvent(ComponentChangeEvent e) {
-               checkState();
-               
-               // Update modification ID's only for normal (not undo/redo) events
-               if (!e.isUndoChange()) {
-                       modID = UniqueID.next();
-                       if (e.isMassChange())
-                               massModID = modID;
-                       if (e.isAerodynamicChange())
-                               aeroModID = modID;
-                       if (e.isTreeChange())
-                               treeModID = modID;
-                       if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
-                               functionalModID = modID;
-               }
-               
-               // 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()) {
-                       iterator.next().componentChanged(e);
-               }
-               
-               // Notify all listeners
-               Object[] listeners = listenerList.getListenerList();
-               for (int i = listeners.length - 2; i >= 0; i -= 2) {
-                       if (listeners[i] == ComponentChangeListener.class) {
-                               ((ComponentChangeListener) listeners[i + 1]).componentChanged(e);
-                       } else if (listeners[i] == ChangeListener.class) {
-                               ((ChangeListener) listeners[i + 1]).stateChanged(e);
+               mutex.lock("fireComponentChangeEvent");
+               try {
+                       checkState();
+                       
+                       // Update modification ID's only for normal (not undo/redo) events
+                       if (!e.isUndoChange()) {
+                               modID = UniqueID.next();
+                               if (e.isMassChange())
+                                       massModID = modID;
+                               if (e.isAerodynamicChange())
+                                       aeroModID = modID;
+                               if (e.isTreeChange())
+                                       treeModID = modID;
+                               if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
+                                       functionalModID = modID;
+                       }
+                       
+                       // 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.iterator(true);
+                       while (iterator.hasNext()) {
+                               iterator.next().componentChanged(e);
+                       }
+                       
+                       // Notify all listeners
+                       Object[] listeners = listenerList.getListenerList();
+                       for (int i = listeners.length - 2; i >= 0; i -= 2) {
+                               if (listeners[i] == ComponentChangeListener.class) {
+                                       ((ComponentChangeListener) listeners[i + 1]).componentChanged(e);
+                               } else if (listeners[i] == ChangeListener.class) {
+                                       ((ChangeListener) listeners[i + 1]).stateChanged(e);
+                               }
+                       }
+               } finally {
+                       mutex.unlock("fireComponentChangeEvent");
                }
        }
        
@@ -575,7 +590,7 @@ public class Rocket extends RocketComponent {
                if (id == null)
                        return false;
                
-               Iterator<RocketComponent> iterator = this.deepIterator();
+               Iterator<RocketComponent> iterator = this.iterator();
                while (iterator.hasNext()) {
                        RocketComponent c = iterator.next();
                        
@@ -660,7 +675,7 @@ public class Rocket extends RocketComponent {
                List<List<String>> list = new ArrayList<List<String>>();
                List<String> currentList = null;
                
-               Iterator<RocketComponent> iterator = this.deepIterator();
+               Iterator<RocketComponent> iterator = this.iterator();
                while (iterator.hasNext()) {
                        RocketComponent c = iterator.next();
                        
@@ -777,7 +792,7 @@ public class Rocket extends RocketComponent {
        }
        
        @Override
-       public double getLongitudalUnitInertia() {
+       public double getLongitudinalUnitInertia() {
                return 0;
        }
        
@@ -813,14 +828,14 @@ public class Rocket extends RocketComponent {
        public boolean isCompatible(Class<? extends RocketComponent> type) {
                return (Stage.class.isAssignableFrom(type));
        }
-
-    /**
-     * Accept a visitor to this Rocket in the component hierarchy.
-     * 
-     * @param theVisitor  the visitor that will be called back with a reference to this Rocket
-     */    
-       @Override 
-    public void accept (final ComponentVisitor theVisitor) {
-        theVisitor.visit(this);
-    }    
+       
+       /**
+        * Accept a visitor to this Rocket in the component hierarchy.
+        
+        * @param theVisitor  the visitor that will be called back with a reference to this Rocket
+        */
+       @Override
+       public void accept(final ComponentVisitor theVisitor) {
+               theVisitor.visit(this);
+       }
 }
index 02d4c1d02b2084df927434dfdf21c9a664b9f2a7..8573caa67f9b019ca8bbc4a5d6d41a2ef9b01fb8 100644 (file)
@@ -1,29 +1,30 @@
 package net.sf.openrocket.rocketcomponent;
 
+import java.awt.Color;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+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.ArrayList;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.ChangeSource;
 import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.Invalidator;
 import net.sf.openrocket.util.LineStyle;
 import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.SafetyMutex;
 import net.sf.openrocket.util.UniqueID;
 
-import javax.swing.event.ChangeListener;
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EmptyStackException;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Stack;
-
 
-public abstract class RocketComponent implements ChangeSource, Cloneable,
-               Iterable<RocketComponent> , Visitable<ComponentVisitor, RocketComponent> {
+public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent>,
+               Visitable<ComponentVisitor, RocketComponent> {
        private static final LogHelper log = Application.getLogger();
        
        /*
@@ -54,6 +55,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
                }
        }
        
+       /**
+        * A safety mutex that can be used to prevent concurrent access to this component.
+        */
+       protected SafetyMutex mutex = SafetyMutex.newInstance();
+       
        ////////  Parent/child trees
        /**
         * Parent component of the current component, or null if none exists.
@@ -63,7 +69,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
        /**
         * List of child components of this component.
         */
-       private List<RocketComponent> children = new ArrayList<RocketComponent>();
+       private ArrayList<RocketComponent> children = new ArrayList<RocketComponent>();
        
 
        ////////  Parameters common to all components:
@@ -112,10 +118,10 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
        private String id = null;
        
        /**
-        * When invalidated is non-null this component cannot be used anymore.
-        * This is a safety mechanism to prevent accidental use after calling {@link #copyFrom(RocketComponent)}.
+        * Used to invalidate the component after calling {@link #copyFrom(RocketComponent)}.
         */
-       private TraceException invalidated = null;
+       private Invalidator invalidator = new Invalidator(this);
+       
        
        ////  NOTE !!!  All fields must be copied in the method copyFrom()!  ////
        
@@ -131,9 +137,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
                this.relativePosition = relativePosition;
                newID();
        }
-
-    ////////////  Methods that must be implemented  ////////////
-
+       
+       ////////////  Methods that must be implemented  ////////////
+       
 
        /**
         * Static component name.  The name may not vary of the parameters, it must be static.
@@ -152,14 +158,14 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
        
 
        /**
-        * Return the longitudal (around the y- or z-axis) unitary moment of inertia.  
+        * Return the longitudinal (around the y- or z-axis) unitary moment of inertia.  
         * The unitary moment of inertia is the moment of inertia with the assumption that
         * the mass of the component is one kilogram.  The inertia is measured in
         * respect to the non-overridden CG.
         * 
-        * @return   the longitudal unitary moment of inertia of this component.
+        * @return   the longitudinal unitary moment of inertia of this component.
         */
-       public abstract double getLongitudalUnitInertia();
+       public abstract double getLongitudinalUnitInertia();
        
        
        /**
@@ -204,6 +210,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @see #isCompatible(Class)
         */
        public final boolean isCompatible(RocketComponent c) {
+               mutex.verify();
                return isCompatible(c.getClass());
        }
        
@@ -269,39 +276,44 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
 
 
        /**
-        * Return a descriptive name of the component.
-        * 
-        * The description may include extra information about the type of component,
-        * e.g. "Conical nose cone".
+        * Return the user-provided name of the component, or the component base
+        * name if the user-provided name is empty.  This can be used in the UI.
         * 
         * @return A string describing the component.
         */
        @Override
        public final String toString() {
-               if (name.equals(""))
+               mutex.verify();
+               if (name.length() == 0)
                        return getComponentName();
                else
                        return name;
        }
        
        
-       public final void printStructure() {
-               System.out.println("Rocket structure from '" + this.toString() + "':");
-               printStructure(0);
+       /**
+        * Create a string describing the basic component structure from this component downwards.
+        * @return      a string containing the rocket structure
+        */
+       public final String toDebugString() {
+               mutex.lock("toDebugString");
+               try {
+                       StringBuilder sb = new StringBuilder();
+                       toDebugString(sb);
+                       return sb.toString();
+               } finally {
+                       mutex.unlock("toDebugString");
+               }
        }
        
-       private void printStructure(int level) {
-               String s = "";
-               
-               for (int i = 0; i < level; i++) {
-                       s += "  ";
-               }
-               s += this.toString() + " (" + this.getComponentName() + ")";
-               System.out.println(s);
-               
-               for (RocketComponent c : children) {
-                       c.printStructure(level + 1);
+       private void toDebugString(StringBuilder sb) {
+               sb.append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this));
+               sb.append("[\"").append(this.getName()).append('"');
+               for (RocketComponent c : this.children) {
+                       sb.append("; ");
+                       c.toDebugString(sb);
                }
+               sb.append(']');
        }
        
        
@@ -315,7 +327,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
        public final RocketComponent copy() {
                RocketComponent clone = copyWithOriginalID();
                
-               Iterator<RocketComponent> iterator = clone.deepIterator(true);
+               Iterator<RocketComponent> iterator = clone.iterator(true);
                while (iterator.hasNext()) {
                        iterator.next().newID();
                }
@@ -341,41 +353,51 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @return A deep copy of the structure.
         */
        protected RocketComponent copyWithOriginalID() {
-               checkState();
-               RocketComponent clone;
+               mutex.lock("copyWithOriginalID");
                try {
-                       clone = (RocketComponent) this.clone();
-               } catch (CloneNotSupportedException e) {
-                       throw new BugException("CloneNotSupportedException encountered, " +
-                                       "report a bug!", e);
-               }
-               
-               // Reset all parent/child information
-               clone.parent = null;
-               clone.children = new ArrayList<RocketComponent>();
-               
-               // Add copied children to the structure without firing events.
-               for (RocketComponent child : this.children) {
-                       RocketComponent childCopy = child.copyWithOriginalID();
-                       // Don't use add method since it fires events
-                       clone.children.add(childCopy);
-                       childCopy.parent = clone;
+                       checkState();
+                       RocketComponent clone;
+                       try {
+                               clone = (RocketComponent) this.clone();
+                       } catch (CloneNotSupportedException e) {
+                               throw new BugException("CloneNotSupportedException encountered, report a bug!", e);
+                       }
+                       
+                       // Reset the mutex
+                       clone.mutex = SafetyMutex.newInstance();
+                       
+                       // Reset all parent/child information
+                       clone.parent = null;
+                       clone.children = new ArrayList<RocketComponent>();
+                       
+                       // Add copied children to the structure without firing events.
+                       for (RocketComponent child : this.children) {
+                               RocketComponent childCopy = child.copyWithOriginalID();
+                               // Don't use add method since it fires events
+                               clone.children.add(childCopy);
+                               childCopy.parent = clone;
+                       }
+                       
+                       this.checkComponentStructure();
+                       clone.checkComponentStructure();
+                       
+                       return clone;
+               } finally {
+                       mutex.unlock("copyWithOriginalID");
                }
-               
-               return clone;
        }
        
        
-    /**
-     * Accept a visitor to this RocketComponent in the component hierarchy.
-     * 
-     * @param theVisitor  the visitor that will be called back with a reference to this RocketComponent
-     */
-    @Override 
-    public void accept (final ComponentVisitor theVisitor) {
-        theVisitor.visit(this);
-    }
-
+       /**
+        * Accept a visitor to this RocketComponent in the component hierarchy.
+        
+        * @param theVisitor  the visitor that will be called back with a reference to this RocketComponent
+        */
+       @Override
+       public void accept(final ComponentVisitor theVisitor) {
+               theVisitor.visit(this);
+       }
+       
        //////////////  Methods that may not be overridden  ////////////
        
 
@@ -387,6 +409,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * to use the default color.
         */
        public final Color getColor() {
+               mutex.verify();
                return color;
        }
        
@@ -405,6 +428,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
        
        
        public final LineStyle getLineStyle() {
+               mutex.verify();
                return lineStyle;
        }
        
@@ -426,6 +450,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @return  the override mass
         */
        public final double getOverrideMass() {
+               mutex.verify();
                return overrideMass;
        }
        
@@ -451,6 +476,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @return  whether the mass is overridden
         */
        public final boolean isMassOverridden() {
+               mutex.verify();
                return massOverriden;
        }
        
@@ -478,6 +504,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @return  the override CG
         */
        public final Coordinate getOverrideCG() {
+               mutex.verify();
                return getComponentCG().setX(overrideCGX);
        }
        
@@ -487,6 +514,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @return      the x-coordinate of the override CG.
         */
        public final double getOverrideCGX() {
+               mutex.verify();
                return overrideCGX;
        }
        
@@ -512,6 +540,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @return  whether the CG is overridden
         */
        public final boolean isCGOverridden() {
+               mutex.verify();
                return cgOverriden;
        }
        
@@ -542,6 +571,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @return      whether the current mass and/or CG override overrides subcomponents as well.
         */
        public boolean getOverrideSubcomponents() {
+               mutex.verify();
                return overrideSubcomponents;
        }
        
@@ -572,6 +602,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @return      whether the option to override subcomponents is currently enabled.
         */
        public boolean isOverrideSubcomponentsEnabled() {
+               mutex.verify();
                return isCGOverridden() || isMassOverridden();
        }
        
@@ -582,6 +613,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * Get the user-defined name of the component.
         */
        public final String getName() {
+               mutex.verify();
                return name;
        }
        
@@ -609,6 +641,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @return  the comment of the component.
         */
        public final String getComment() {
+               mutex.verify();
                return comment;
        }
        
@@ -643,6 +676,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * Generate a new ID for this component.
         */
        private final void newID() {
+               mutex.verify();
                this.id = UniqueID.uuid();
        }
        
@@ -658,6 +692,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * itself.
         */
        public final double getLength() {
+               mutex.verify();
                return length;
        }
        
@@ -667,6 +702,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * but can be provided by a subclass.
         */
        public final Position getRelativePosition() {
+               mutex.verify();
                return relativePosition;
        }
        
@@ -727,6 +763,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @return  the positional value.
         */
        public final double getPositionValue() {
+               mutex.verify();
                return position;
        }
        
@@ -780,84 +817,89 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         */
        public final Coordinate[] toRelative(Coordinate c, RocketComponent dest) {
                checkState();
-               double absoluteX = Double.NaN;
-               RocketComponent search = dest;
-               Coordinate[] array = new Coordinate[1];
-               array[0] = c;
-               
-               RocketComponent component = this;
-               while ((component != search) && (component.parent != null)) {
-                       
-                       array = component.shiftCoordinates(array);
-                       
-                       switch (component.relativePosition) {
-                       case TOP:
-                               for (int i = 0; i < array.length; i++) {
-                                       array[i] = array[i].add(component.position, 0, 0);
-                               }
-                               break;
-                       
-                       case MIDDLE:
-                               for (int i = 0; i < array.length; i++) {
-                                       array[i] = array[i].add(component.position +
-                                                       (component.parent.length - component.length) / 2, 0, 0);
-                               }
-                               break;
-                       
-                       case BOTTOM:
-                               for (int i = 0; i < array.length; i++) {
-                                       array[i] = array[i].add(component.position +
-                                                       (component.parent.length - component.length), 0, 0);
-                               }
-                               break;
+               mutex.lock("toRelative");
+               try {
+                       double absoluteX = Double.NaN;
+                       RocketComponent search = dest;
+                       Coordinate[] array = new Coordinate[1];
+                       array[0] = c;
                        
-                       case AFTER:
-                               // Add length of all previous brother-components with POSITION_RELATIVE_AFTER
-                               int index = component.parent.children.indexOf(component);
-                               assert (index >= 0);
-                               for (index--; index >= 0; index--) {
-                                       RocketComponent comp = component.parent.children.get(index);
-                                       double length = comp.getTotalLength();
+                       RocketComponent component = this;
+                       while ((component != search) && (component.parent != null)) {
+                               
+                               array = component.shiftCoordinates(array);
+                               
+                               switch (component.relativePosition) {
+                               case TOP:
+                                       for (int i = 0; i < array.length; i++) {
+                                               array[i] = array[i].add(component.position, 0, 0);
+                                       }
+                                       break;
+                               
+                               case MIDDLE:
+                                       for (int i = 0; i < array.length; i++) {
+                                               array[i] = array[i].add(component.position +
+                                                               (component.parent.length - component.length) / 2, 0, 0);
+                                       }
+                                       break;
+                               
+                               case BOTTOM:
+                                       for (int i = 0; i < array.length; i++) {
+                                               array[i] = array[i].add(component.position +
+                                                               (component.parent.length - component.length), 0, 0);
+                                       }
+                                       break;
+                               
+                               case AFTER:
+                                       // Add length of all previous brother-components with POSITION_RELATIVE_AFTER
+                                       int index = component.parent.children.indexOf(component);
+                                       assert (index >= 0);
+                                       for (index--; index >= 0; index--) {
+                                               RocketComponent comp = component.parent.children.get(index);
+                                               double componentLength = comp.getTotalLength();
+                                               for (int i = 0; i < array.length; i++) {
+                                                       array[i] = array[i].add(componentLength, 0, 0);
+                                               }
+                                       }
                                        for (int i = 0; i < array.length; i++) {
-                                               array[i] = array[i].add(length, 0, 0);
+                                               array[i] = array[i].add(component.position + component.parent.length, 0, 0);
                                        }
+                                       break;
+                               
+                               case ABSOLUTE:
+                                       search = null; // Requires back-search if dest!=null
+                                       if (Double.isNaN(absoluteX)) {
+                                               absoluteX = component.position;
+                                       }
+                                       break;
+                               
+                               default:
+                                       throw new BugException("Unknown relative positioning type of component" +
+                                                       component + ": " + component.relativePosition);
                                }
+                               
+                               component = component.parent; // parent != null
+                       }
+                       
+                       if (!Double.isNaN(absoluteX)) {
                                for (int i = 0; i < array.length; i++) {
-                                       array[i] = array[i].add(component.position + component.parent.length, 0, 0);
+                                       array[i] = array[i].setX(absoluteX + c.x);
                                }
-                               break;
+                       }
                        
-                       case ABSOLUTE:
-                               search = null; // Requires back-search if dest!=null
-                               if (Double.isNaN(absoluteX)) {
-                                       absoluteX = component.position;
+                       // Check whether destination has been found or whether to backtrack
+                       // TODO: LOW: Backtracking into clustered components uses only one component 
+                       if ((dest != null) && (component != dest)) {
+                               Coordinate[] origin = dest.toAbsolute(Coordinate.NUL);
+                               for (int i = 0; i < array.length; i++) {
+                                       array[i] = array[i].sub(origin[0]);
                                }
-                               break;
-                       
-                       default:
-                               throw new BugException("Unknown relative positioning type of component" +
-                                               component + ": " + component.relativePosition);
                        }
                        
-                       component = component.parent; // parent != null
+                       return array;
+               } finally {
+                       mutex.unlock("toRelative");
                }
-               
-               if (!Double.isNaN(absoluteX)) {
-                       for (int i = 0; i < array.length; i++) {
-                               array[i] = array[i].setX(absoluteX + c.x);
-                       }
-               }
-               
-               // Check whether destination has been found or whether to backtrack
-               // TODO: LOW: Backtracking into clustered components uses only one component 
-               if ((dest != null) && (component != dest)) {
-                       Coordinate[] origin = dest.toAbsolute(Coordinate.NUL);
-                       for (int i = 0; i < array.length; i++) {
-                               array[i] = array[i].sub(origin[0]);
-                       }
-               }
-               
-               return array;
        }
        
        
@@ -869,12 +911,18 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         */
        private final double getTotalLength() {
                checkState();
-               double l = 0;
-               if (relativePosition == Position.AFTER)
-                       l = length;
-               for (int i = 0; i < children.size(); i++)
-                       l += children.get(i).getTotalLength();
-               return l;
+               this.checkComponentStructure();
+               mutex.lock("getTotalLength");
+               try {
+                       double l = 0;
+                       if (relativePosition == Position.AFTER)
+                               l = length;
+                       for (int i = 0; i < children.size(); i++)
+                               l += children.get(i).getTotalLength();
+                       return l;
+               } finally {
+                       mutex.unlock("getTotalLength");
+               }
        }
        
        
@@ -887,6 +935,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @return The mass of the component or the given override mass.
         */
        public final double getMass() {
+               mutex.verify();
                if (massOverriden)
                        return overrideMass;
                return getComponentMass();
@@ -913,15 +962,15 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
        
        
        /**
-        * Return the longitudal (around the y- or z-axis) moment of inertia of this component.
+        * Return the longitudinal (around the y- or z-axis) moment of inertia of this component.
         * The moment of inertia is scaled in reference to the (possibly overridden) mass
         * and is relative to the non-overridden CG.
         * 
-        * @return    the longitudal moment of inertia of this component.
+        * @return    the longitudinal moment of inertia of this component.
         */
-       public final double getLongitudalInertia() {
+       public final double getLongitudinalInertia() {
                checkState();
-               return getLongitudalUnitInertia() * getMass();
+               return getLongitudinalUnitInertia() * getMass();
        }
        
        /**
@@ -964,12 +1013,12 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * This method may be overridden to enforce more strict component addition rules.  
         * The tests should be performed first and then this method called.
         * 
-        * @param component  The component to add.
-        * @param position   Position to add component to.
+        * @param component     The component to add.
+        * @param index         Position to add component to.
         * @throws IllegalArgumentException  If the component is already part of 
         *                                                                       some component tree.
         */
-       public void addChild(RocketComponent component, int position) {
+       public void addChild(RocketComponent component, int index) {
                checkState();
                if (component.parent != null) {
                        throw new IllegalArgumentException("component " + component.getComponentName() +
@@ -980,9 +1029,12 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
                                        " not currently compatible with component " + getComponentName());
                }
                
-               children.add(position, component);
+               children.add(index, component);
                component.parent = this;
                
+               this.checkComponentStructure();
+               component.checkComponentStructure();
+               
                fireAddRemoveEvent(component);
        }
        
@@ -997,6 +1049,10 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
                checkState();
                RocketComponent component = children.remove(n);
                component.parent = null;
+               
+               this.checkComponentStructure();
+               component.checkComponentStructure();
+               
                fireAddRemoveEvent(component);
        }
        
@@ -1009,8 +1065,15 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         */
        public final boolean removeChild(RocketComponent component) {
                checkState();
+               
+               component.checkComponentStructure();
+               
                if (children.remove(component)) {
                        component.parent = null;
+                       
+                       this.checkComponentStructure();
+                       component.checkComponentStructure();
+                       
                        fireAddRemoveEvent(component);
                        return true;
                }
@@ -1024,13 +1087,17 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * Move a child to another position.
         * 
         * @param component     the component to move
-        * @param position      the component's new position
+        * @param index the component's new position
         * @throws IllegalArgumentException If an illegal placement was attempted.
         */
-       public final void moveChild(RocketComponent component, int position) {
+       public final void moveChild(RocketComponent component, int index) {
                checkState();
                if (children.remove(component)) {
-                       children.add(position, component);
+                       children.add(index, component);
+                       
+                       this.checkComponentStructure();
+                       component.checkComponentStructure();
+                       
                        fireAddRemoveEvent(component);
                }
        }
@@ -1041,7 +1108,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * type of component removed.
         */
        private void fireAddRemoveEvent(RocketComponent component) {
-               Iterator<RocketComponent> iter = component.deepIterator(true);
+               Iterator<RocketComponent> iter = component.iterator(true);
                int type = ComponentChangeEvent.TREE_CHANGE;
                while (iter.hasNext()) {
                        RocketComponent c = iter.next();
@@ -1057,17 +1124,20 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
        
        public final int getChildCount() {
                checkState();
+               this.checkComponentStructure();
                return children.size();
        }
        
        public final RocketComponent getChild(int n) {
                checkState();
+               this.checkComponentStructure();
                return children.get(n);
        }
        
-       public final RocketComponent[] getChildren() {
+       public final List<RocketComponent> getChildren() {
                checkState();
-               return children.toArray(new RocketComponent[0]);
+               this.checkComponentStructure();
+               return children.clone();
        }
        
        
@@ -1080,6 +1150,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         */
        public final int getChildPosition(RocketComponent child) {
                checkState();
+               this.checkComponentStructure();
                return children.indexOf(child);
        }
        
@@ -1176,7 +1247,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         */
        public final RocketComponent findComponent(String idToFind) {
                checkState();
-               Iterator<RocketComponent> iter = this.deepIterator(true);
+               Iterator<RocketComponent> iter = this.iterator(true);
                while (iter.hasNext()) {
                        RocketComponent c = iter.next();
                        if (c.getID().equals(idToFind))
@@ -1186,8 +1257,10 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
        }
        
        
+       // TODO: Move these methods elsewhere (used only in SymmetricComponent)
        public final RocketComponent getPreviousComponent() {
                checkState();
+               this.checkComponentStructure();
                if (parent == null)
                        return null;
                int pos = parent.getChildPosition(this);
@@ -1215,21 +1288,22 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
                return c;
        }
        
+       // TODO: Move these methods elsewhere (used only in SymmetricComponent)
        public final RocketComponent getNextComponent() {
                checkState();
                if (getChildCount() > 0)
                        return getChild(0);
                
                RocketComponent current = this;
-               RocketComponent parent = this.parent;
+               RocketComponent nextParent = this.parent;
                
-               while (parent != null) {
-                       int pos = parent.getChildPosition(current);
-                       if (pos < parent.getChildCount() - 1)
-                               return parent.getChild(pos + 1);
+               while (nextParent != null) {
+                       int pos = nextParent.getChildPosition(current);
+                       if (pos < nextParent.getChildCount() - 1)
+                               return nextParent.getChild(pos + 1);
                        
-                       current = parent;
-                       parent = current.parent;
+                       current = nextParent;
+                       nextParent = current.parent;
                }
                return null;
        }
@@ -1277,6 +1351,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * 
         * @throws IllegalStateException - if the root component is not a <code>Rocket</code>
         */
+       @Override
        public void addChangeListener(ChangeListener l) {
                checkState();
                getRocket().addChangeListener(l);
@@ -1290,6 +1365,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * 
         * @param l  Listener to remove
         */
+       @Override
        public void removeChangeListener(ChangeListener l) {
                if (this.parent != null) {
                        getRoot().removeChangeListener(l);
@@ -1340,154 +1416,123 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * @throws      BugException    if this component has been invalidated by {@link #copyFrom(RocketComponent)}.
         */
        protected void checkState() {
-               if (invalidated != null) {
-                       throw new BugException("This component has been invalidated.  Cause is the point of invalidation.",
-                                       invalidated);
-               }
+               invalidator.check(true);
+               mutex.verify();
        }
        
        
-       ///////////  Iterator implementation  //////////
-       
        /**
-        * Private inner class to implement the Iterator.
-        * 
-        * This iterator is fail-fast if the root of the structure is a Rocket.
+        * Check that the local component structure is correct.  This can be called after changing
+        * the component structure in order to verify the integrity.
+        * <p>
+        * TODO: Remove this after the "inconsistent internal state" bug has been corrected
         */
-       private class RocketComponentIterator implements Iterator<RocketComponent> {
-               // Stack holds iterators which still have some components left.
-               private final Stack<Iterator<RocketComponent>> iteratorstack =
-                                       new Stack<Iterator<RocketComponent>>();
-               
-               private final Rocket root;
-               private final int treeModID;
-               
-               private final RocketComponent original;
-               private boolean returnSelf = false;
-               
-               // Construct iterator with component's child's iterator, if it has elements
-               public RocketComponentIterator(RocketComponent c, boolean returnSelf) {
-                       
-                       RocketComponent gp = c.getRoot();
-                       if (gp instanceof Rocket) {
-                               root = (Rocket) gp;
-                               treeModID = root.getTreeModID();
-                       } else {
-                               root = null;
-                               treeModID = -1;
+       public void checkComponentStructure() {
+               if (this.parent != null) {
+                       // Test that this component is found in parent's children with == operator
+                       if (!containsExact(this.parent.children, this)) {
+                               throw new BugException("Inconsistent component structure detected, parent does not contain this " +
+                                               "component as a child, parent=" + parent.toDebugString() + " this=" + this.toDebugString());
                        }
-                       
-                       Iterator<RocketComponent> i = c.children.iterator();
-                       if (i.hasNext())
-                               iteratorstack.push(i);
-                       
-                       this.original = c;
-                       this.returnSelf = returnSelf;
-               }
-               
-               public boolean hasNext() {
-                       checkState();
-                       checkID();
-                       if (returnSelf)
-                               return true;
-                       return !iteratorstack.empty(); // Elements remain if stack is not empty
                }
-               
-               public RocketComponent next() {
-                       Iterator<RocketComponent> i;
-                       
-                       checkState();
-                       checkID();
-                       
-                       // Return original component first
-                       if (returnSelf) {
-                               returnSelf = false;
-                               return original;
-                       }
-                       
-                       // Peek first iterator from stack, throw exception if empty
-                       try {
-                               i = iteratorstack.peek();
-                       } catch (EmptyStackException e) {
-                               throw new NoSuchElementException("No further elements in " +
-                                               "RocketComponent iterator");
+               for (RocketComponent child : this.children) {
+                       if (child.parent != this) {
+                               throw new BugException("Inconsistent component structure detected, child does not have this component " +
+                                               "as the parent, this=" + this.toDebugString() + " child=" + child.toDebugString() +
+                                               " child.parent=" + (child.parent == null ? "null" : child.parent.toDebugString()));
                        }
-                       
-                       // Retrieve next component of the iterator, remove iterator from stack if empty
-                       RocketComponent c = i.next();
-                       if (!i.hasNext())
-                               iteratorstack.pop();
-                       
-                       // Add iterator of component children to stack if it has children
-                       i = c.children.iterator();
-                       if (i.hasNext())
-                               iteratorstack.push(i);
-                       
-                       return c;
                }
-               
-               private void checkID() {
-                       if (root != null) {
-                               if (root.getTreeModID() != treeModID) {
-                                       throw new IllegalStateException("Rocket modified while being iterated");
-                               }
+       }
+       
+       // Check whether the list contains exactly the searched-for component (with == operator)
+       private boolean containsExact(List<RocketComponent> haystack, RocketComponent needle) {
+               for (RocketComponent c : haystack) {
+                       if (needle == c) {
+                               return true;
                        }
                }
-               
-               public void remove() {
-                       throw new UnsupportedOperationException("remove() not supported by " +
-                                       "RocketComponent iterator");
-               }
+               return false;
        }
        
+       
+       ///////////  Iterators  //////////
+       
        /**
         * Returns an iterator that iterates over all children and sub-children.
-        * 
+        * <p>
         * The iterator iterates through all children below this object, including itself if
-        * returnSelf is true.  The order of the iteration is not specified
+        * <code>returnSelf</code> is true.  The order of the iteration is not specified
         * (it may be specified in the future).
-        * 
+        * <p>
         * If an iterator iterating over only the direct children of the component is required,
-        * use  component.getChildren().iterator()
+        * use <code>component.getChildren().iterator()</code>.
+        * 
+        * TODO: HIGH: Remove this after merges have been done
         * 
         * @param returnSelf boolean value specifying whether the component itself should be 
         *                                       returned
         * @return An iterator for the children and sub-children.
+        * @deprecated Use {@link #iterator(boolean)} instead
         */
+       @Deprecated
        public final Iterator<RocketComponent> deepIterator(boolean returnSelf) {
-               checkState();
-               return new RocketComponentIterator(this, returnSelf);
+               return iterator(returnSelf);
        }
        
+       
        /**
-        * Returns an iterator that iterates over all children and sub-children.
+        * Returns an iterator that iterates over all children and sub-children, including itself.
+        * <p>
+        * This method is equivalent to <code>deepIterator(true)</code>.
         * 
-        * The iterator does NOT return the component itself.  It is thus equivalent to
-        * deepIterator(false).
+        * TODO: HIGH: Remove this after merges have been done
         * 
-        * @see #iterator()
-        * @return An iterator for the children and sub-children.
+        * @return An iterator for this component, its children and sub-children.
+        * @deprecated Use {@link #iterator()} instead
         */
+       @Deprecated
        public final Iterator<RocketComponent> deepIterator() {
+               return iterator();
+       }
+       
+       
+
+       /**
+        * Returns an iterator that iterates over all children and sub-children.
+        * <p>
+        * The iterator iterates through all children below this object, including itself if
+        * <code>returnSelf</code> is true.  The order of the iteration is not specified
+        * (it may be specified in the future).
+        * <p>
+        * If an iterator iterating over only the direct children of the component is required,
+        * use <code>component.getChildren().iterator()</code>.
+        * 
+        * @param returnSelf boolean value specifying whether the component itself should be 
+        *                                       returned
+        * @return An iterator for the children and sub-children.
+        */
+       public final Iterator<RocketComponent> iterator(boolean returnSelf) {
                checkState();
-               return new RocketComponentIterator(this, false);
+               return new RocketComponentIterator(this, returnSelf);
        }
        
        
        /**
-        * Return an iterator that iterates of the children of the component.  The iterator
-        * does NOT recurse to sub-children nor return itself.
+        * Returns an iterator that iterates over this components, its children and sub-children.
+        * <p>
+        * This method is equivalent to <code>iterator(true)</code>.
         * 
-        * @return An iterator for thchildren.
+        * @return An iterator for this component, its children and sub-children.
         */
+       @Override
        public final Iterator<RocketComponent> iterator() {
-               checkState();
-               return Collections.unmodifiableList(children).iterator();
+               return iterator(true);
        }
        
        
 
 
+
        /**
         * Compare component equality based on the ID of this component.  Only the
         * ID and class type is used for a basis of comparison.
@@ -1543,7 +1588,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
                                        length * density;
        }
        
-       protected static final double ringLongitudalUnitInertia(double outerRadius,
+       protected static final double ringLongitudinalUnitInertia(double outerRadius,
                        double innerRadius, double length) {
                // 1/12 * (3 * (r1^2 + r2^2) + h^2)
                return (3 * (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) + MathUtil.pow2(length)) / 12;
@@ -1566,27 +1611,47 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
         * mechanism and when converting a finset into a freeform fin set.
         * This component must not have a parent, otherwise this method will fail.
         * <p>
-        * The fields are copied by reference, and the supplied component must not be used
-        * after the call, as it is in an undefined state.  This is enforced by invalidating
-        * the source component.
+        * The child components in the source tree are copied into the current tree, however,
+        * the original components should not be used since they represent old copies of the
+        * components.  It is recommended to invalidate them by calling {@link #invalidate()}.
+        * <p>
+        * This method returns a list of components that should be invalidated after references
+        * to them have been removed (for example by firing appropriate events).  The list contains
+        * all children and sub-children of the current component and the entire component
+        * tree of <code>src</code>.
         * 
-        * TODO: MEDIUM: Make general to copy all private/protected fields...
+        * @return      a list of components that should not be used after this call.
         */
-       protected void copyFrom(RocketComponent src) {
+       protected List<RocketComponent> copyFrom(RocketComponent src) {
                checkState();
+               List<RocketComponent> toInvalidate = new ArrayList<RocketComponent>();
                
                if (this.parent != null) {
-                       throw new UnsupportedOperationException("copyFrom called for non-root component "
-                                       + this);
+                       throw new UnsupportedOperationException("copyFrom called for non-root component, parent=" +
+                                       this.parent.toDebugString() + ", this=" + this.toDebugString());
                }
                
-               // Set parents and children
-               this.children = src.children;
-               src.children = new ArrayList<RocketComponent>();
+               // Add current structure to be invalidated
+               Iterator<RocketComponent> iterator = this.iterator(false);
+               while (iterator.hasNext()) {
+                       toInvalidate.add(iterator.next());
+               }
                
-               for (RocketComponent c : this.children) {
-                       c.parent = this;
+               // Remove previous components
+               for (RocketComponent child : this.children) {
+                       child.parent = null;
                }
+               this.children.clear();
+               
+               // Copy new children to this component
+               for (RocketComponent c : src.children) {
+                       RocketComponent copy = c.copyWithOriginalID();
+                       this.children.add(copy);
+                       copy.parent = this;
+               }
+               
+               this.checkComponentStructure();
+               src.checkComponentStructure();
                
                // Set all parameters
                this.length = src.length;
@@ -1603,7 +1668,108 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
                this.comment = src.comment;
                this.id = src.id;
                
-               src.invalidated = new TraceException();
+               // Add source components to invalidation tree
+               for (RocketComponent c : src) {
+                       toInvalidate.add(c);
+               }
+               
+               return toInvalidate;
+       }
+       
+       protected void invalidate() {
+               invalidator.invalidate();
+       }
+       
+       
+       //////////  Iterator implementation  ///////////
+       
+       /**
+        * Private inner class to implement the Iterator.
+        * 
+        * This iterator is fail-fast if the root of the structure is a Rocket.
+        */
+       private static class RocketComponentIterator implements Iterator<RocketComponent> {
+               // Stack holds iterators which still have some components left.
+               private final Deque<Iterator<RocketComponent>> iteratorStack = new ArrayDeque<Iterator<RocketComponent>>();
+               
+               private final Rocket root;
+               private final int treeModID;
+               
+               private final RocketComponent original;
+               private boolean returnSelf = false;
+               
+               // Construct iterator with component's child's iterator, if it has elements
+               public RocketComponentIterator(RocketComponent c, boolean returnSelf) {
+                       
+                       RocketComponent gp = c.getRoot();
+                       if (gp instanceof Rocket) {
+                               root = (Rocket) gp;
+                               treeModID = root.getTreeModID();
+                       } else {
+                               root = null;
+                               treeModID = -1;
+                       }
+                       
+                       Iterator<RocketComponent> i = c.children.iterator();
+                       if (i.hasNext())
+                               iteratorStack.push(i);
+                       
+                       this.original = c;
+                       this.returnSelf = returnSelf;
+               }
+               
+               @Override
+               public boolean hasNext() {
+                       checkID();
+                       if (returnSelf)
+                               return true;
+                       return !iteratorStack.isEmpty(); // Elements remain if stack is not empty
+               }
+               
+               @Override
+               public RocketComponent next() {
+                       Iterator<RocketComponent> i;
+                       
+                       checkID();
+                       
+                       // Return original component first
+                       if (returnSelf) {
+                               returnSelf = false;
+                               return original;
+                       }
+                       
+                       // Peek first iterator from stack, throw exception if empty
+                       i = iteratorStack.peek();
+                       if (i == null) {
+                               throw new NoSuchElementException("No further elements in RocketComponent iterator");
+                       }
+                       
+                       // Retrieve next component of the iterator, remove iterator from stack if empty
+                       RocketComponent c = i.next();
+                       if (!i.hasNext())
+                               iteratorStack.pop();
+                       
+                       // Add iterator of component children to stack if it has children
+                       i = c.children.iterator();
+                       if (i.hasNext())
+                               iteratorStack.push(i);
+                       
+                       return c;
+               }
+               
+               private void checkID() {
+                       if (root != null) {
+                               if (root.getTreeModID() != treeModID) {
+                                       throw new IllegalStateException("Rocket modified while being iterated");
+                               }
+                       }
+               }
+               
+               @Override
+               public void remove() {
+                       throw new UnsupportedOperationException("remove() not supported by " +
+                                       "RocketComponent iterator");
+               }
        }
        
 }
index e25ba691d939e143ecaeb39d9c66e15f91a35f67..ac8318def028abff4ad1a01b443f83c47e9fb76e 100644 (file)
@@ -33,7 +33,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
        private double planCenter = -1;
        private double volume = -1;
        private double fullVolume = -1;
-       private double longitudalInertia = -1;
+       private double longitudinalInertia = -1;
        private double rotationalInertia = -1;
        private Coordinate cg = null;
        
@@ -229,14 +229,14 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
        
        
        @Override
-       public double getLongitudalUnitInertia() {
-               if (longitudalInertia < 0) {
+       public double getLongitudinalUnitInertia() {
+               if (longitudinalInertia < 0) {
                        if (getComponentVolume() > 0.0000001)  // == 0.1cm^3
                                integrateInertiaVolume();
                        else
                                integrateInertiaSurface();
                }
-               return longitudalInertia;
+               return longitudinalInertia;
        }
        
        
@@ -347,7 +347,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
        
        
        /**
-        * Integrate the longitudal and rotational inertia based on component volume.
+        * Integrate the longitudinal and rotational inertia based on component volume.
         * This method may be used only if the total volume is zero.
         */
        private void integrateInertiaVolume() {
@@ -359,7 +359,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
 
                r1 = getRadius(0);
                x = 0;
-               longitudalInertia = 0;
+               longitudinalInertia = 0;
                rotationalInertia = 0;
                
                double volume = 0;
@@ -390,7 +390,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                        }
                        
                        rotationalInertia += dV * (pow2(outer) + pow2(inner))/2;
-                       longitudalInertia += dV * ((3 * (pow2(outer) + pow2(inner)) + pow2(l))/12 
+                       longitudinalInertia += dV * ((3 * (pow2(outer) + pow2(inner)) + pow2(l))/12 
                                        + pow2(x+l/2));
                        
                        volume += dV;
@@ -406,16 +406,16 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                }
                
                rotationalInertia /= volume;
-               longitudalInertia /= volume;
+               longitudinalInertia /= volume;
 
-               // Shift longitudal inertia to CG
-               longitudalInertia = Math.max(longitudalInertia - pow2(getComponentCG().x), 0);
+               // Shift longitudinal inertia to CG
+               longitudinalInertia = Math.max(longitudinalInertia - pow2(getComponentCG().x), 0);
        }
        
        
 
        /**
-        * Integrate the longitudal and rotational inertia based on component surface area.
+        * Integrate the longitudinal and rotational inertia based on component surface area.
         * This method may be used only if the total volume is zero.
         */
        private void integrateInertiaSurface() {
@@ -425,7 +425,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
 
                r1 = getRadius(0);
                x = 0;
-               longitudalInertia = 0;
+               longitudinalInertia = 0;
                rotationalInertia = 0;
                
                double surface = 0;
@@ -444,7 +444,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                        final double dS = hyp * (r1+r2) * Math.PI;
 
                        rotationalInertia += dS * pow2(outer);
-                       longitudalInertia += dS * ((6 * pow2(outer) + pow2(l))/12 + pow2(x+l/2));
+                       longitudinalInertia += dS * ((6 * pow2(outer) + pow2(l))/12 + pow2(x+l/2));
                        
                        surface += dS;
                        
@@ -454,16 +454,16 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                }
 
                if (MathUtil.equals(surface, 0)) {
-                       longitudalInertia = 0;
+                       longitudinalInertia = 0;
                        rotationalInertia = 0;
                        return;
                }
                
-               longitudalInertia /= surface;
+               longitudinalInertia /= surface;
                rotationalInertia /= surface;
                
-               // Shift longitudal inertia to CG
-               longitudalInertia = Math.max(longitudalInertia - pow2(getComponentCG().x), 0);
+               // Shift longitudinal inertia to CG
+               longitudinalInertia = Math.max(longitudinalInertia - pow2(getComponentCG().x), 0);
        }
        
        
@@ -481,7 +481,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                        planCenter = -1;
                        volume = -1;
                        fullVolume = -1;
-                       longitudalInertia = -1;
+                       longitudinalInertia = -1;
                        rotationalInertia = -1;
                        cg = null;
                }
index cb516a5ee22182aa4fa3c629e11a9d39aa22cd17..5f46b7ff97c5f3d029378d98853f529def51d5e2 100644 (file)
@@ -8,11 +8,11 @@ package net.sf.openrocket.rocketcomponent;
  * The elements of the concrete object hierarchy are only visitable by an associated hierarchy of visitors, 
  * while these visitors are only able to visit the elements of that hierarchy. 
  * 
- * The key concept regarding the Visitor pattern is to realize that Java will only Ã’discriminateÓ the type of an 
+ * The key concept regarding the Visitor pattern is to realize that Java will only "discriminate" the type of an 
  * object being called, not the type of an object being passed.
  *
  * In order for the type of two objects to be determinable to the JVM, each object must be the receiver of an
- * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes Ã’knownÓ but the 
+ * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes "known" but the 
  * concrete type of the argument is still unknown.  <code>visit</code> is then called on the parameter object, passing 
  * the Visitable back, which has type and identity. Flow of control has now been 'double-dispatched' such that the 
  * type (and identity) of both objects are known. 
@@ -28,14 +28,14 @@ package net.sf.openrocket.rocketcomponent;
  * <T> The visitable (the concrete class that implements this interface)
  */
 public interface Visitable<V extends Visitor<V, T>, T extends Visitable<V, T>> {
-
-    /**
-     * Any class in the hierarchy that allows itself to be visited will implement this method.  The normal
-     * behavior is that the visitor will invoke this method of a Visitable, passing itself.  The Visitable 
-     * turns around calls the Visitor back. This idiom is also known as 'double-dispatching'.
-     * 
-     * @param visitor  the visitor that will be called back
-     */
-    public void accept(V visitor);
-
+       
+       /**
+        * Any class in the hierarchy that allows itself to be visited will implement this method.  The normal
+        * behavior is that the visitor will invoke this method of a Visitable, passing itself.  The Visitable 
+        * turns around calls the Visitor back. This idiom is also known as 'double-dispatching'.
+        
+        * @param visitor  the visitor that will be called back
+        */
+       public void accept(V visitor);
+       
 }
index 2e689ea6b9af8986516c232f58ca6e46ba3253df..edb39f2b1e8e1c7dbe2f5c60325ddc201bea3e6d 100644 (file)
@@ -8,11 +8,11 @@ package net.sf.openrocket.rocketcomponent;
  * The elements of the concrete object hierarchy are only visitable by an associated hierarchy of visitors, 
  * while these visitors are only able to visit the elements of that hierarchy. 
  * 
- * The key concept regarding the Visitor pattern is to realize that Java will only Ã’discriminateÓ the type of an 
+ * The key concept regarding the Visitor pattern is to realize that Java will only "discriminate" the type of an 
  * object being called, not the type of an object being passed.
  *
  * In order for the type of two objects to be determinable to the JVM, each object must be the receiver of an
- * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes Ã’knownÓ but the 
+ * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes "known" but the 
  * concrete type of the argument is still unknown.  <code>visit</code> is then called on the parameter object, passing 
  * the Visitable back, which has type and identity. Flow of control has now been 'double-dispatched' such that the 
  * type (and identity) of both objects are known. 
@@ -28,12 +28,12 @@ package net.sf.openrocket.rocketcomponent;
  * <T> The visitable 
  */
 public interface Visitor<V extends Visitor<V, T>, T extends Visitable<V, T>> {
-
-    /**
-     * The callback method.  This method is the 2nd leg of the double-dispatch, having been invoked from a 
-     * corresponding <code>accept</code>.
-     *           
-     * @param visitable  the instance of the Visitable (the target of what is being visiting)
-     */
-    void visit(T visitable);
+       
+       /**
+        * The callback method.  This method is the 2nd leg of the double-dispatch, having been invoked from a 
+        * corresponding <code>accept</code>.
+        *           
+        * @param visitable  the instance of the Visitable (the target of what is being visiting)
+        */
+       void visit(T visitable);
 }
\ No newline at end of file
index 348feaa58e7f28845cee704b68c8fd7d4f367fe8..7b33ae04874937e02d3ac70757b66f3c837a31b4 100644 (file)
@@ -114,7 +114,7 @@ public abstract class AbstractSimulationStepper implements SimulationStepper {
        protected MassData calculateMassData(SimulationStatus status) throws SimulationException {
                MassData mass;
                Coordinate cg;
-               double longitudalInertia, rotationalInertia;
+               double longitudinalInertia, rotationalInertia;
                
                // Call pre-listener
                mass = SimulationListenerHelper.firePreMassCalculation(status);
@@ -124,15 +124,15 @@ public abstract class AbstractSimulationStepper implements SimulationStepper {
                
                MassCalculator calc = status.getSimulationConditions().getMassCalculator();
                cg = calc.getCG(status.getConfiguration(), status.getMotorConfiguration());
-               longitudalInertia = calc.getLongitudalInertia(status.getConfiguration(), status.getMotorConfiguration());
+               longitudinalInertia = calc.getLongitudinalInertia(status.getConfiguration(), status.getMotorConfiguration());
                rotationalInertia = calc.getRotationalInertia(status.getConfiguration(), status.getMotorConfiguration());
-               mass = new MassData(cg, longitudalInertia, rotationalInertia);
+               mass = new MassData(cg, longitudinalInertia, rotationalInertia);
                
                // Call post-listener
                mass = SimulationListenerHelper.firePostMassCalculation(status, mass);
                
                checkNaN(mass.getCG());
-               checkNaN(mass.getLongitudalInertia());
+               checkNaN(mass.getLongitudinalInertia());
                checkNaN(mass.getRotationalInertia());
                
                return mass;
index f8772bb3cf6ccfe1fde0417f6af6ee924d539bfa..f3eb3d9c751d1a7b0d6ef5faf6f3dff0e9ea22c3 100644 (file)
@@ -1,12 +1,12 @@
 package net.sf.openrocket.simulation;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
+import net.sf.openrocket.util.ArrayList;
 import net.sf.openrocket.util.Monitorable;
 import net.sf.openrocket.util.Mutable;
 
@@ -157,12 +157,11 @@ public class FlightDataBranch implements Monitorable {
         * @return              a list of the variable values, or <code>null</code> if
         *                              the variable type hasn't been added to this branch.
         */
-       @SuppressWarnings("unchecked")
        public List<Double> get(FlightDataType type) {
                ArrayList<Double> list = values.get(type);
                if (list == null)
                        return null;
-               return (List<Double>) list.clone();
+               return list.clone();
        }
        
        /**
@@ -226,9 +225,8 @@ public class FlightDataBranch implements Monitorable {
         * 
         * @return      the list of events during the flight.
         */
-       @SuppressWarnings("unchecked")
        public List<FlightEvent> getEvents() {
-               return (List<FlightEvent>) events.clone();
+               return events.clone();
        }
        
        
index 8c4f45d4d20e0972e0e9c4eed02e264ef4ff3b8e..a089d4c1ddae1cb4bf483ad8f95ed6718da158fc 100644 (file)
@@ -1,7 +1,7 @@
 package net.sf.openrocket.simulation;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
 
 import net.sf.openrocket.unit.UnitGroup;
 
@@ -11,7 +11,10 @@ import net.sf.openrocket.unit.UnitGroup;
  * a name, you should use {@link #getType(String, UnitGroup)} to return the default unit type,
  * or a new type if the name does not currently exist.
  * <p>
- * Each type has a type name (description) and a unit group.  The type is identified purely by its name.
+ * Each type has a type name (description), a unit group and a priority.  The type is identified
+ * purely by its name case-insensitively.  The unit group provides the units for the type.
+ * The priority is used to order the types.  The pre-existing types are defined specific priority
+ * numbers, and other types have a default priority number that is after all other types.
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
@@ -21,46 +24,34 @@ public class FlightDataType implements Comparable<FlightDataType> {
        private static final int DEFAULT_PRIORITY = 999;
        
        /** List of existing types.  MUST BE DEFINED BEFORE ANY TYPES!! */
-       private static final List<FlightDataType> EXISTING_TYPES = new ArrayList<FlightDataType>();
+       private static final Map<String, FlightDataType> EXISTING_TYPES = new HashMap<String, FlightDataType>();
        
 
 
        //// Time
-       public static final FlightDataType TYPE_TIME =
-                       newType("Time", UnitGroup.UNITS_FLIGHT_TIME, 1);
+       public static final FlightDataType TYPE_TIME = newType("Time", UnitGroup.UNITS_FLIGHT_TIME, 1);
        
 
        //// Vertical position and motion
-       public static final FlightDataType TYPE_ALTITUDE =
-                       newType("Altitude", UnitGroup.UNITS_DISTANCE, 10);
-       public static final FlightDataType TYPE_VELOCITY_Z =
-                       newType("Vertical velocity", UnitGroup.UNITS_VELOCITY, 11);
-       public static final FlightDataType TYPE_ACCELERATION_Z =
-                       newType("Vertical acceleration", UnitGroup.UNITS_ACCELERATION, 12);
+       public static final FlightDataType TYPE_ALTITUDE = newType("Altitude", UnitGroup.UNITS_DISTANCE, 10);
+       public static final FlightDataType TYPE_VELOCITY_Z = newType("Vertical velocity", UnitGroup.UNITS_VELOCITY, 11);
+       public static final FlightDataType TYPE_ACCELERATION_Z = newType("Vertical acceleration", UnitGroup.UNITS_ACCELERATION, 12);
        
 
        //// Total motion
-       public static final FlightDataType TYPE_VELOCITY_TOTAL =
-                       newType("Total velocity", UnitGroup.UNITS_VELOCITY, 20);
-       public static final FlightDataType TYPE_ACCELERATION_TOTAL =
-                       newType("Total acceleration", UnitGroup.UNITS_ACCELERATION, 21);
+       public static final FlightDataType TYPE_VELOCITY_TOTAL = newType("Total velocity", UnitGroup.UNITS_VELOCITY, 20);
+       public static final FlightDataType TYPE_ACCELERATION_TOTAL = newType("Total acceleration", UnitGroup.UNITS_ACCELERATION, 21);
        
 
        //// Lateral position and motion
        
-       public static final FlightDataType TYPE_POSITION_X =
-                       newType("Position upwind", UnitGroup.UNITS_DISTANCE, 30);
-       public static final FlightDataType TYPE_POSITION_Y =
-                       newType("Position parallel to wind", UnitGroup.UNITS_DISTANCE, 31);
-       public static final FlightDataType TYPE_POSITION_XY =
-                       newType("Lateral distance", UnitGroup.UNITS_DISTANCE, 32);
-       public static final FlightDataType TYPE_POSITION_DIRECTION =
-                       newType("Lateral direction", UnitGroup.UNITS_ANGLE, 33);
+       public static final FlightDataType TYPE_POSITION_X = newType("Position upwind", UnitGroup.UNITS_DISTANCE, 30);
+       public static final FlightDataType TYPE_POSITION_Y = newType("Position parallel to wind", UnitGroup.UNITS_DISTANCE, 31);
+       public static final FlightDataType TYPE_POSITION_XY = newType("Lateral distance", UnitGroup.UNITS_DISTANCE, 32);
+       public static final FlightDataType TYPE_POSITION_DIRECTION = newType("Lateral direction", UnitGroup.UNITS_ANGLE, 33);
        
-       public static final FlightDataType TYPE_VELOCITY_XY =
-                       newType("Lateral velocity", UnitGroup.UNITS_VELOCITY, 34);
-       public static final FlightDataType TYPE_ACCELERATION_XY =
-                       newType("Lateral acceleration", UnitGroup.UNITS_ACCELERATION, 35);
+       public static final FlightDataType TYPE_VELOCITY_XY = newType("Lateral velocity", UnitGroup.UNITS_VELOCITY, 34);
+       public static final FlightDataType TYPE_ACCELERATION_XY = newType("Lateral acceleration", UnitGroup.UNITS_ACCELERATION, 35);
        
 
        //// Angular motion
@@ -71,97 +62,65 @@ public class FlightDataType implements Comparable<FlightDataType> {
        
 
        //// Stability information
-       public static final FlightDataType TYPE_MASS =
-                       newType("Mass", UnitGroup.UNITS_MASS, 50);
-       public static final FlightDataType TYPE_CP_LOCATION =
-                       newType("CP location", UnitGroup.UNITS_LENGTH, 51);
-       public static final FlightDataType TYPE_CG_LOCATION =
-                       newType("CG location", UnitGroup.UNITS_LENGTH, 52);
-       public static final FlightDataType TYPE_STABILITY =
-                       newType("Stability margin calibers", UnitGroup.UNITS_COEFFICIENT, 53);
-       // TODO: HIGH: Add moment of inertia
+       public static final FlightDataType TYPE_MASS = newType("Mass", UnitGroup.UNITS_MASS, 50);
+       public static final FlightDataType TYPE_LONGITUDINAL_INERTIA = newType("Longitudinal moment of inertia", UnitGroup.UNITS_INERTIA, 51);
+       public static final FlightDataType TYPE_ROTATIONAL_INERTIA = newType("Rotational moment of inertia", UnitGroup.UNITS_INERTIA, 52);
+       public static final FlightDataType TYPE_CP_LOCATION = newType("CP location", UnitGroup.UNITS_LENGTH, 53);
+       public static final FlightDataType TYPE_CG_LOCATION = newType("CG location", UnitGroup.UNITS_LENGTH, 54);
+       public static final FlightDataType TYPE_STABILITY = newType("Stability margin calibers", UnitGroup.UNITS_COEFFICIENT, 55);
        
 
        //// Characteristic numbers
-       public static final FlightDataType TYPE_MACH_NUMBER =
-                       newType("Mach number", UnitGroup.UNITS_COEFFICIENT, 60);
-       public static final FlightDataType TYPE_REYNOLDS_NUMBER =
-                       newType("Reynolds number", UnitGroup.UNITS_COEFFICIENT, 61);
+       public static final FlightDataType TYPE_MACH_NUMBER = newType("Mach number", UnitGroup.UNITS_COEFFICIENT, 60);
+       public static final FlightDataType TYPE_REYNOLDS_NUMBER = newType("Reynolds number", UnitGroup.UNITS_COEFFICIENT, 61);
        
 
        //// Thrust and drag
-       public static final FlightDataType TYPE_THRUST_FORCE =
-                       newType("Thrust", UnitGroup.UNITS_FORCE, 70);
-       public static final FlightDataType TYPE_DRAG_FORCE =
-                       newType("Drag force", UnitGroup.UNITS_FORCE, 71);
-       
-       public static final FlightDataType TYPE_DRAG_COEFF =
-                       newType("Drag coefficient", UnitGroup.UNITS_COEFFICIENT, 72);
-       public static final FlightDataType TYPE_AXIAL_DRAG_COEFF =
-                       newType("Axial drag coefficient", UnitGroup.UNITS_COEFFICIENT, 73);
+       public static final FlightDataType TYPE_THRUST_FORCE = newType("Thrust", UnitGroup.UNITS_FORCE, 70);
+       public static final FlightDataType TYPE_DRAG_FORCE = newType("Drag force", UnitGroup.UNITS_FORCE, 71);
+       public static final FlightDataType TYPE_DRAG_COEFF = newType("Drag coefficient", UnitGroup.UNITS_COEFFICIENT, 72);
+       public static final FlightDataType TYPE_AXIAL_DRAG_COEFF = newType("Axial drag coefficient", UnitGroup.UNITS_COEFFICIENT, 73);
        
 
        ////  Component drag coefficients
-       public static final FlightDataType TYPE_FRICTION_DRAG_COEFF =
-                       newType("Friction drag coefficient", UnitGroup.UNITS_COEFFICIENT, 80);
-       public static final FlightDataType TYPE_PRESSURE_DRAG_COEFF =
-                       newType("Pressure drag coefficient", UnitGroup.UNITS_COEFFICIENT, 81);
-       public static final FlightDataType TYPE_BASE_DRAG_COEFF =
-                       newType("Base drag coefficient", UnitGroup.UNITS_COEFFICIENT, 82);
+       public static final FlightDataType TYPE_FRICTION_DRAG_COEFF = newType("Friction drag coefficient", UnitGroup.UNITS_COEFFICIENT, 80);
+       public static final FlightDataType TYPE_PRESSURE_DRAG_COEFF = newType("Pressure drag coefficient", UnitGroup.UNITS_COEFFICIENT, 81);
+       public static final FlightDataType TYPE_BASE_DRAG_COEFF = newType("Base drag coefficient", UnitGroup.UNITS_COEFFICIENT, 82);
        
 
        ////  Other coefficients
-       public static final FlightDataType TYPE_NORMAL_FORCE_COEFF =
-                       newType("Normal force coefficient", UnitGroup.UNITS_COEFFICIENT, 90);
-       public static final FlightDataType TYPE_PITCH_MOMENT_COEFF =
-                       newType("Pitch moment coefficient", UnitGroup.UNITS_COEFFICIENT, 91);
-       public static final FlightDataType TYPE_YAW_MOMENT_COEFF =
-                       newType("Yaw moment coefficient", UnitGroup.UNITS_COEFFICIENT, 92);
-       public static final FlightDataType TYPE_SIDE_FORCE_COEFF =
-                       newType("Side force coefficient", UnitGroup.UNITS_COEFFICIENT, 93);
-       public static final FlightDataType TYPE_ROLL_MOMENT_COEFF =
-                       newType("Roll moment coefficient", UnitGroup.UNITS_COEFFICIENT, 94);
-       public static final FlightDataType TYPE_ROLL_FORCING_COEFF =
-                       newType("Roll forcing coefficient", UnitGroup.UNITS_COEFFICIENT, 95);
-       public static final FlightDataType TYPE_ROLL_DAMPING_COEFF =
-                       newType("Roll damping coefficient", UnitGroup.UNITS_COEFFICIENT, 96);
-       
-       public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF =
-                       newType("Pitch damping coefficient", UnitGroup.UNITS_COEFFICIENT, 97);
-       public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF =
-                       newType("Yaw damping coefficient", UnitGroup.UNITS_COEFFICIENT, 98);
+       public static final FlightDataType TYPE_NORMAL_FORCE_COEFF = newType("Normal force coefficient", UnitGroup.UNITS_COEFFICIENT, 90);
+       public static final FlightDataType TYPE_PITCH_MOMENT_COEFF = newType("Pitch moment coefficient", UnitGroup.UNITS_COEFFICIENT, 91);
+       public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType("Yaw moment coefficient", UnitGroup.UNITS_COEFFICIENT, 92);
+       public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType("Side force coefficient", UnitGroup.UNITS_COEFFICIENT, 93);
+       public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType("Roll moment coefficient", UnitGroup.UNITS_COEFFICIENT, 94);
+       public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType("Roll forcing coefficient", UnitGroup.UNITS_COEFFICIENT, 95);
+       public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType("Roll damping coefficient", UnitGroup.UNITS_COEFFICIENT, 96);
+       
+       public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType("Pitch damping coefficient", UnitGroup.UNITS_COEFFICIENT, 97);
+       public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType("Yaw damping coefficient", UnitGroup.UNITS_COEFFICIENT, 98);
        
 
        ////  Reference length + area
-       public static final FlightDataType TYPE_REFERENCE_LENGTH =
-                       newType("Reference length", UnitGroup.UNITS_LENGTH, 100);
-       public static final FlightDataType TYPE_REFERENCE_AREA =
-                       newType("Reference area", UnitGroup.UNITS_AREA, 101);
+       public static final FlightDataType TYPE_REFERENCE_LENGTH = newType("Reference length", UnitGroup.UNITS_LENGTH, 100);
+       public static final FlightDataType TYPE_REFERENCE_AREA = newType("Reference area", UnitGroup.UNITS_AREA, 101);
        
 
        ////  Orientation
-       public static final FlightDataType TYPE_ORIENTATION_THETA =
-                       newType("Vertical orientation (zenith)", UnitGroup.UNITS_ANGLE, 106);
-       public static final FlightDataType TYPE_ORIENTATION_PHI =
-                       newType("Lateral orientation (azimuth)", UnitGroup.UNITS_ANGLE, 107);
+       public static final FlightDataType TYPE_ORIENTATION_THETA = newType("Vertical orientation (zenith)", UnitGroup.UNITS_ANGLE, 106);
+       public static final FlightDataType TYPE_ORIENTATION_PHI = newType("Lateral orientation (azimuth)", UnitGroup.UNITS_ANGLE, 107);
        
 
        ////  Atmospheric conditions
-       public static final FlightDataType TYPE_WIND_VELOCITY = newType("Wind velocity",
-                       UnitGroup.UNITS_VELOCITY, 110);
-       public static final FlightDataType TYPE_AIR_TEMPERATURE = newType("Air temperature",
-                       UnitGroup.UNITS_TEMPERATURE, 111);
-       public static final FlightDataType TYPE_AIR_PRESSURE = newType("Air pressure",
-                       UnitGroup.UNITS_PRESSURE, 112);
-       public static final FlightDataType TYPE_SPEED_OF_SOUND = newType("Speed of sound",
-                       UnitGroup.UNITS_VELOCITY, 113);
+       public static final FlightDataType TYPE_WIND_VELOCITY = newType("Wind velocity", UnitGroup.UNITS_VELOCITY, 110);
+       public static final FlightDataType TYPE_AIR_TEMPERATURE = newType("Air temperature", UnitGroup.UNITS_TEMPERATURE, 111);
+       public static final FlightDataType TYPE_AIR_PRESSURE = newType("Air pressure", UnitGroup.UNITS_PRESSURE, 112);
+       public static final FlightDataType TYPE_SPEED_OF_SOUND = newType("Speed of sound", UnitGroup.UNITS_VELOCITY, 113);
        
 
        ////  Simulation information
-       public static final FlightDataType TYPE_TIME_STEP = newType("Simulation time step",
-                       UnitGroup.UNITS_TIME_STEP, 200);
-       public static final FlightDataType TYPE_COMPUTATION_TIME = newType("Computation time",
-                       UnitGroup.UNITS_SHORT_TIME, 201);
+       public static final FlightDataType TYPE_TIME_STEP = newType("Simulation time step", UnitGroup.UNITS_TIME_STEP, 200);
+       public static final FlightDataType TYPE_COMPUTATION_TIME = newType("Computation time", UnitGroup.UNITS_SHORT_TIME, 201);
        
        
 
@@ -174,12 +133,11 @@ public class FlightDataType implements Comparable<FlightDataType> {
         * @return              a data type.
         */
        public static synchronized FlightDataType getType(String s, UnitGroup u) {
-               for (FlightDataType t : EXISTING_TYPES) {
-                       if (t.getName().equalsIgnoreCase(s))
-                               return t;
+               FlightDataType type = EXISTING_TYPES.get(s.toLowerCase());
+               if (type != null) {
+                       return type;
                }
-               FlightDataType type = new FlightDataType(s, u, DEFAULT_PRIORITY);
-               EXISTING_TYPES.add(type);
+               type = newType(s, u, DEFAULT_PRIORITY);
                return type;
        }
        
@@ -188,7 +146,7 @@ public class FlightDataType implements Comparable<FlightDataType> {
         */
        private static synchronized FlightDataType newType(String s, UnitGroup u, int priority) {
                FlightDataType type = new FlightDataType(s, u, priority);
-               EXISTING_TYPES.add(type);
+               EXISTING_TYPES.put(s.toLowerCase(), type);
                return type;
        }
        
@@ -242,6 +200,6 @@ public class FlightDataType implements Comparable<FlightDataType> {
        public int compareTo(FlightDataType o) {
                if (this.priority != o.priority)
                        return this.priority - o.priority;
-               return this.name.compareTo(o.name);
+               return this.name.compareToIgnoreCase(o.name);
        }
 }
\ No newline at end of file
index 37ea4fe1d21dbc577c8cee1d423e2a73dc0dc287..1d910df0a751f0a0e1edfe84f8647002c4697115 100644 (file)
@@ -11,16 +11,16 @@ import net.sf.openrocket.util.MathUtil;
 public class MassData {
 
        private final Coordinate cg;
-       private final double longitudalInertia;
+       private final double longitudinalInertia;
        private final double rotationalInertia;
        
        
-       public MassData(Coordinate cg, double longitudalInertia, double rotationalInertia) {
+       public MassData(Coordinate cg, double longitudinalInertia, double rotationalInertia) {
                if (cg == null) {
                        throw new IllegalArgumentException("cg is null");
                }
                this.cg = cg;
-               this.longitudalInertia = longitudalInertia;
+               this.longitudinalInertia = longitudinalInertia;
                this.rotationalInertia = rotationalInertia;
        }
 
@@ -31,8 +31,8 @@ public class MassData {
                return cg;
        }
        
-       public double getLongitudalInertia() {
-               return longitudalInertia;
+       public double getLongitudinalInertia() {
+               return longitudinalInertia;
        }
        
        public double getRotationalInertia() {
@@ -49,20 +49,20 @@ public class MassData {
                        return false;
                
                MassData other = (MassData) obj;
-               return (this.cg.equals(other.cg) && MathUtil.equals(this.longitudalInertia, other.longitudalInertia) &&
+               return (this.cg.equals(other.cg) && MathUtil.equals(this.longitudinalInertia, other.longitudinalInertia) &&
                                MathUtil.equals(this.rotationalInertia, other.rotationalInertia));
        }
 
        
        @Override
        public int hashCode() {
-               return (int) (cg.hashCode() ^ Double.doubleToLongBits(longitudalInertia) ^ Double.doubleToLongBits(rotationalInertia));
+               return (int) (cg.hashCode() ^ Double.doubleToLongBits(longitudinalInertia) ^ Double.doubleToLongBits(rotationalInertia));
        }
 
 
        @Override
        public String toString() {
-               return "MassData [cg=" + cg + ", longitudalInertia=" + longitudalInertia
+               return "MassData [cg=" + cg + ", longitudinalInertia=" + longitudinalInertia
                                + ", rotationalInertia=" + rotationalInertia + "]";
        }
        
index 97d934cf5b9272fcf0e9608d966b1af47b091f19..6f2c2387b3fc7c1bc5eac4b4c2577cedb7493130 100644 (file)
@@ -169,7 +169,7 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
                 * diminished by it affecting only 1/6th of the total, so it's an acceptable error.
                 */
                double thrustEstimate = store.thrustForce;
-               store.thrustForce = calculateThrust(status, store.timestep, store.longitudalAcceleration,
+               store.thrustForce = calculateThrust(status, store.timestep, store.longitudinalAcceleration,
                                store.atmosphericConditions, true);
                double thrustDiff = Math.abs(store.thrustForce - thrustEstimate);
                // Log if difference over 1%, recompute if over 10%
@@ -357,8 +357,8 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
                        double momZ = store.forces.getCroll() * dynP * refArea * refLength;
                        
                        // Compute acceleration in rocket coordinates
-                       store.angularAcceleration = new Coordinate(momX / store.massData.getLongitudalInertia(),
-                                               momY / store.massData.getLongitudalInertia(), momZ / store.massData.getRotationalInertia());
+                       store.angularAcceleration = new Coordinate(momX / store.massData.getLongitudinalInertia(),
+                                               momY / store.massData.getLongitudinalInertia(), momZ / store.massData.getRotationalInertia());
                        
                        store.rollAcceleration = store.angularAcceleration.z;
                        // TODO: LOW: This should be hypot, but does it matter?
@@ -576,6 +576,8 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
                }
                if (store.massData != null) {
                        data.setValue(FlightDataType.TYPE_MASS, store.massData.getCG().weight);
+                       data.setValue(FlightDataType.TYPE_LONGITUDINAL_INERTIA, store.massData.getLongitudinalInertia());
+                       data.setValue(FlightDataType.TYPE_ROTATIONAL_INERTIA, store.massData.getRotationalInertia());
                }
                
                data.setValue(FlightDataType.TYPE_THRUST_FORCE, store.thrustForce);
@@ -667,7 +669,7 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
                
                public FlightConditions flightConditions;
                
-               public double longitudalAcceleration = Double.NaN;
+               public double longitudinalAcceleration = Double.NaN;
                
                public MassData massData;
                
index dab946eb51410b6e5f39a631b454b94675054fdb..05a55fa29cac07f6ad26ee4111aa133a2ec11896 100644 (file)
@@ -185,7 +185,7 @@ public class CSVSaveListener extends AbstractSimulationListener {
                        @Override
                        public double getValue(SimulationStatus status) {
                                Iterator<RocketComponent> iterator =
-                                               status.getConfiguration().getRocket().deepIterator();
+                                               status.getConfiguration().getRocket().iterator();
                                FinSet fin = null;
                                
                                while (iterator.hasNext()) {
index b8ef783490524740264849e1c99446d62a8c06d5..020d714e57abb13c7f48c5554d320520e72b637f 100644 (file)
@@ -1,6 +1,8 @@
 package net.sf.openrocket.startup;
 
 import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
+import net.sf.openrocket.l10n.DebugTranslator;
+import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.logging.LogLevel;
 import net.sf.openrocket.logging.LogLevelBufferLogger;
@@ -16,6 +18,8 @@ public final class Application {
        private static LogHelper logger;
        private static LogLevelBufferLogger logBuffer;
        
+       private static Translator translator = new DebugTranslator();
+       
        private static ThrustCurveMotorSetDatabase motorSetDatabase;
        
 
@@ -78,6 +82,24 @@ public final class Application {
        }
        
        
+       /**
+        * Return the translator to use for obtaining translated strings.
+        * @return      a translator.
+        */
+       public static Translator getTranslator() {
+               return translator;
+       }
+       
+       /**
+        * Set the translator used in obtaining translated strings.
+        * @param translator    the translator to set.
+        */
+       public static void setTranslator(Translator translator) {
+               Application.translator = translator;
+       }
+       
+       
+
        /**
         * Return the database of all thrust curves loaded into the system.
         */
index 658f70c0b05a42d6ecd47765302eac39d13bc556..101e068ae30aa2159bac4740ba0586f13894b239 100644 (file)
@@ -99,6 +99,7 @@ public class Startup {
                        System.setProperty("openrocket.log.stdout", "VBOSE");
                        System.setProperty("openrocket.log.tracelevel", "VBOSE");
                        System.setProperty("openrocket.debug.menu", "true");
+                       System.setProperty("openrocket.debug.mutexlocation", "true");
                        System.setProperty("openrocket.debug.motordigest", "true");
                }
        }
@@ -329,7 +330,7 @@ public class Startup {
                // Check whether to log to stdout/stderr
                PrintStreamLogger printer = new PrintStreamLogger();
                boolean logout = setLogOutput(printer, System.out, System.getProperty(LOG_STDOUT_PROPERTY), null);
-               boolean logerr = setLogOutput(printer, System.err, System.getProperty(LOG_STDERR_PROPERTY), LogLevel.WARN);
+               boolean logerr = setLogOutput(printer, System.err, System.getProperty(LOG_STDERR_PROPERTY), LogLevel.ERROR);
                if (logout || logerr) {
                        delegator.addLogger(printer);
                }
index 4d9784c82b32ce119151365388155470e675e2c5..c33c4b6ff4f570573a11ecd3f33c04e251d4306d 100644 (file)
@@ -15,7 +15,7 @@ import net.sf.openrocket.util.MathUtil;
 
 
 public class CaliberUnit extends GeneralUnit {
-
+       
        public static final double DEFAULT_CALIBER = 0.01;
        
        private final Configuration configuration;
@@ -23,7 +23,7 @@ public class CaliberUnit extends GeneralUnit {
        
        private double caliber = -1;
        
-       
+
        /* Listener for rocket and configuration, resets the caliber to -1. */
        private final ChangeListener listener = new ChangeListener() {
                @Override
@@ -33,7 +33,7 @@ public class CaliberUnit extends GeneralUnit {
        };
        
        
-       
+
        public CaliberUnit(Configuration configuration) {
                super(1.0, "cal");
                this.configuration = configuration;
@@ -63,7 +63,7 @@ public class CaliberUnit extends GeneralUnit {
                
                return value * caliber;
        }
-
+       
        @Override
        public double toUnit(double value) {
                if (caliber < 0)
@@ -71,7 +71,7 @@ public class CaliberUnit extends GeneralUnit {
                
                return value / caliber;
        }
-
+       
        
        // TODO: HIGH:  Check caliber calculation method...
        private void calculateCaliber() {
@@ -81,7 +81,7 @@ public class CaliberUnit extends GeneralUnit {
                if (configuration != null) {
                        iterator = configuration.iterator();
                } else if (rocket != null) {
-                       iterator = rocket.deepIterator();
+                       iterator = rocket.iterator(false);
                } else {
                        Collection<RocketComponent> set = Collections.emptyList();
                        iterator = set.iterator();
@@ -90,8 +90,8 @@ public class CaliberUnit extends GeneralUnit {
                while (iterator.hasNext()) {
                        RocketComponent c = iterator.next();
                        if (c instanceof SymmetricComponent) {
-                               double r1 = ((SymmetricComponent)c).getForeRadius() * 2;
-                               double r2 = ((SymmetricComponent)c).getAftRadius() * 2;
+                               double r1 = ((SymmetricComponent) c).getForeRadius() * 2;
+                               double r2 = ((SymmetricComponent) c).getAftRadius() * 2;
                                caliber = MathUtil.max(caliber, r1, r2);
                        }
                }
index 1d8848b27376afe7f1a6ffcf2dfdf8dfab9eb059..0f75d827574a985c5dd6d42ea884e5167d43baf7 100644 (file)
@@ -22,7 +22,7 @@ import net.sf.openrocket.rocketcomponent.Rocket;
  */
 
 public class UnitGroup {
-
+       
        public static final UnitGroup UNITS_NONE;
        
        public static final UnitGroup UNITS_MOTOR_DIMENSIONS;
@@ -34,6 +34,7 @@ public class UnitGroup {
        public static final UnitGroup UNITS_VELOCITY;
        public static final UnitGroup UNITS_ACCELERATION;
        public static final UnitGroup UNITS_MASS;
+       public static final UnitGroup UNITS_INERTIA;
        public static final UnitGroup UNITS_ANGLE;
        public static final UnitGroup UNITS_DENSITY_BULK;
        public static final UnitGroup UNITS_DENSITY_SURFACE;
@@ -57,59 +58,62 @@ public class UnitGroup {
        
        public static final UnitGroup UNITS_COEFFICIENT;
        
-//     public static final UnitGroup UNITS_FREQUENCY;
-       
+       //      public static final UnitGroup UNITS_FREQUENCY;
        
+
        public static final Map<String, UnitGroup> UNITS;
        
-       
+
        /*
         * Note:  Units may not use HTML tags.
+        * 
+        * The scaling value "X" is obtained by "one of this unit is X of SI units"
+        * Type into Google for example:  "1 in^2 in m^2"
         */
        static {
                UNITS_NONE = new UnitGroup();
                UNITS_NONE.addUnit(Unit.NOUNIT2);
                
                UNITS_LENGTH = new UnitGroup();
-               UNITS_LENGTH.addUnit(new GeneralUnit(0.001,"mm"));
-               UNITS_LENGTH.addUnit(new GeneralUnit(0.01,"cm"));
-               UNITS_LENGTH.addUnit(new GeneralUnit(1,"m"));
-               UNITS_LENGTH.addUnit(new GeneralUnit(0.0254,"in"));
-               UNITS_LENGTH.addUnit(new GeneralUnit(0.3048,"ft"));
+               UNITS_LENGTH.addUnit(new GeneralUnit(0.001, "mm"));
+               UNITS_LENGTH.addUnit(new GeneralUnit(0.01, "cm"));
+               UNITS_LENGTH.addUnit(new GeneralUnit(1, "m"));
+               UNITS_LENGTH.addUnit(new GeneralUnit(0.0254, "in"));
+               UNITS_LENGTH.addUnit(new GeneralUnit(0.3048, "ft"));
                UNITS_LENGTH.setDefaultUnit(1);
                
                UNITS_MOTOR_DIMENSIONS = new UnitGroup();
-               UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.001,"mm"));
-               UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.01,"cm"));
-               UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.0254,"in"));
+               UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.001, "mm"));
+               UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.01, "cm"));
+               UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.0254, "in"));
                UNITS_MOTOR_DIMENSIONS.setDefaultUnit(0);
                
                UNITS_DISTANCE = new UnitGroup();
-               UNITS_DISTANCE.addUnit(new GeneralUnit(1,"m"));
-               UNITS_DISTANCE.addUnit(new GeneralUnit(1000,"km"));
-               UNITS_DISTANCE.addUnit(new GeneralUnit(0.3048,"ft"));
-               UNITS_DISTANCE.addUnit(new GeneralUnit(0.9144,"yd"));
-               UNITS_DISTANCE.addUnit(new GeneralUnit(1609.344,"mi"));
+               UNITS_DISTANCE.addUnit(new GeneralUnit(1, "m"));
+               UNITS_DISTANCE.addUnit(new GeneralUnit(1000, "km"));
+               UNITS_DISTANCE.addUnit(new GeneralUnit(0.3048, "ft"));
+               UNITS_DISTANCE.addUnit(new GeneralUnit(0.9144, "yd"));
+               UNITS_DISTANCE.addUnit(new GeneralUnit(1609.344, "mi"));
                
                UNITS_AREA = new UnitGroup();
-               UNITS_AREA.addUnit(new GeneralUnit(pow2(0.001),"mm" + SQUARED));
-               UNITS_AREA.addUnit(new GeneralUnit(pow2(0.01),"cm" + SQUARED));
-               UNITS_AREA.addUnit(new GeneralUnit(1,"m" + SQUARED));
-               UNITS_AREA.addUnit(new GeneralUnit(pow2(0.0254),"in" + SQUARED));
-               UNITS_AREA.addUnit(new GeneralUnit(pow2(0.3048),"ft" + SQUARED));
+               UNITS_AREA.addUnit(new GeneralUnit(pow2(0.001), "mm" + SQUARED));
+               UNITS_AREA.addUnit(new GeneralUnit(pow2(0.01), "cm" + SQUARED));
+               UNITS_AREA.addUnit(new GeneralUnit(1, "m" + SQUARED));
+               UNITS_AREA.addUnit(new GeneralUnit(pow2(0.0254), "in" + SQUARED));
+               UNITS_AREA.addUnit(new GeneralUnit(pow2(0.3048), "ft" + SQUARED));
                UNITS_AREA.setDefaultUnit(1);
                
-               
+
                UNITS_STABILITY = new UnitGroup();
-               UNITS_STABILITY.addUnit(new GeneralUnit(0.001,"mm"));
-               UNITS_STABILITY.addUnit(new GeneralUnit(0.01,"cm"));
-               UNITS_STABILITY.addUnit(new GeneralUnit(0.0254,"in"));
-               UNITS_STABILITY.addUnit(new CaliberUnit((Rocket)null));
+               UNITS_STABILITY.addUnit(new GeneralUnit(0.001, "mm"));
+               UNITS_STABILITY.addUnit(new GeneralUnit(0.01, "cm"));
+               UNITS_STABILITY.addUnit(new GeneralUnit(0.0254, "in"));
+               UNITS_STABILITY.addUnit(new CaliberUnit((Rocket) null));
                UNITS_STABILITY.setDefaultUnit(3);
                
                UNITS_VELOCITY = new UnitGroup();
                UNITS_VELOCITY.addUnit(new GeneralUnit(1, "m/s"));
-               UNITS_VELOCITY.addUnit(new GeneralUnit(1/3.6, "km/h"));
+               UNITS_VELOCITY.addUnit(new GeneralUnit(1 / 3.6, "km/h"));
                UNITS_VELOCITY.addUnit(new GeneralUnit(0.3048, "ft/s"));
                UNITS_VELOCITY.addUnit(new GeneralUnit(0.44704, "mph"));
                
@@ -117,106 +121,114 @@ public class UnitGroup {
                UNITS_ACCELERATION.addUnit(new GeneralUnit(1, "m/s" + SQUARED));
                UNITS_ACCELERATION.addUnit(new GeneralUnit(0.3048, "ft/s" + SQUARED));
                
-
                UNITS_MASS = new UnitGroup();
-               UNITS_MASS.addUnit(new GeneralUnit(0.001,"g"));
-               UNITS_MASS.addUnit(new GeneralUnit(1,"kg"));
-               UNITS_MASS.addUnit(new GeneralUnit(0.0283495231,"oz"));
-               UNITS_MASS.addUnit(new GeneralUnit(0.45359237,"lb"));
+               UNITS_MASS.addUnit(new GeneralUnit(0.001, "g"));
+               UNITS_MASS.addUnit(new GeneralUnit(1, "kg"));
+               UNITS_MASS.addUnit(new GeneralUnit(0.0283495231, "oz"));
+               UNITS_MASS.addUnit(new GeneralUnit(0.45359237, "lb"));
+               
+               UNITS_INERTIA = new UnitGroup();
+               UNITS_INERTIA.addUnit(new GeneralUnit(0.0001, "kg" + DOT + "cm" + SQUARED));
+               UNITS_INERTIA.addUnit(new GeneralUnit(1, "kg" + DOT + "m" + SQUARED));
+               UNITS_INERTIA.addUnit(new GeneralUnit(1.82899783e-5, "oz" + DOT + "in" + SQUARED));
+               UNITS_INERTIA.addUnit(new GeneralUnit(0.000292639653, "lb" + DOT + "in" + SQUARED));
+               UNITS_INERTIA.addUnit(new GeneralUnit(0.0421401101, "lb" + DOT + "ft" + SQUARED));
+               UNITS_INERTIA.addUnit(new GeneralUnit(1.35581795, "lbf" + DOT + "ft" + DOT + "s" + SQUARED));
+               UNITS_INERTIA.setDefaultUnit(1);
                
                UNITS_ANGLE = new UnitGroup();
                UNITS_ANGLE.addUnit(new DegreeUnit());
-               UNITS_ANGLE.addUnit(new FixedPrecisionUnit("rad",0.01));
+               UNITS_ANGLE.addUnit(new FixedPrecisionUnit("rad", 0.01));
                
                UNITS_DENSITY_BULK = new UnitGroup();
-               UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1000,"g/cm" + CUBED));
-               UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1,"kg/m" + CUBED));
-               UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1729.99404,"oz/in" + CUBED));
-               UNITS_DENSITY_BULK.addUnit(new GeneralUnit(16.0184634,"lb/ft" + CUBED));
-
+               UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1000, "g/cm" + CUBED));
+               UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1, "kg/m" + CUBED));
+               UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1729.99404, "oz/in" + CUBED));
+               UNITS_DENSITY_BULK.addUnit(new GeneralUnit(16.0184634, "lb/ft" + CUBED));
+               
                UNITS_DENSITY_SURFACE = new UnitGroup();
-               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(10,"g/cm" + SQUARED));
-               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.001,"g/m" + SQUARED));
-               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(1,"kg/m" + SQUARED));
-               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(43.9418487,"oz/in" + SQUARED));
-               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.305151727,"oz/ft" + SQUARED));
-               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(4.88242764,"lb/ft" + SQUARED));
+               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(10, "g/cm" + SQUARED));
+               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.001, "g/m" + SQUARED));
+               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(1, "kg/m" + SQUARED));
+               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(43.9418487, "oz/in" + SQUARED));
+               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.305151727, "oz/ft" + SQUARED));
+               UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(4.88242764, "lb/ft" + SQUARED));
                UNITS_DENSITY_SURFACE.setDefaultUnit(1);
-
+               
                UNITS_DENSITY_LINE = new UnitGroup();
-               UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.001,"g/m"));
-               UNITS_DENSITY_LINE.addUnit(new GeneralUnit(1,"kg/m"));
-               UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.0930102465,"oz/ft"));
-
+               UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.001, "g/m"));
+               UNITS_DENSITY_LINE.addUnit(new GeneralUnit(1, "kg/m"));
+               UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.0930102465, "oz/ft"));
+               
                UNITS_FORCE = new UnitGroup();
-               UNITS_FORCE.addUnit(new GeneralUnit(1,"N"));
-               UNITS_FORCE.addUnit(new GeneralUnit(4.44822162,"lbf"));
-               UNITS_FORCE.addUnit(new GeneralUnit(9.80665,"kgf"));
-
+               UNITS_FORCE.addUnit(new GeneralUnit(1, "N"));
+               UNITS_FORCE.addUnit(new GeneralUnit(4.44822162, "lbf"));
+               UNITS_FORCE.addUnit(new GeneralUnit(9.80665, "kgf"));
+               
                UNITS_IMPULSE = new UnitGroup();
-               UNITS_IMPULSE.addUnit(new GeneralUnit(1,"Ns"));
-               UNITS_IMPULSE.addUnit(new GeneralUnit(4.44822162, "lbf"+DOT+"s"));
-
+               UNITS_IMPULSE.addUnit(new GeneralUnit(1, "Ns"));
+               UNITS_IMPULSE.addUnit(new GeneralUnit(4.44822162, "lbf" + DOT + "s"));
+               
                UNITS_TIME_STEP = new UnitGroup();
                UNITS_TIME_STEP.addUnit(new FixedPrecisionUnit("ms", 1, 0.001));
                UNITS_TIME_STEP.addUnit(new FixedPrecisionUnit("s", 0.01));
                UNITS_TIME_STEP.setDefaultUnit(1);
-
+               
                UNITS_SHORT_TIME = new UnitGroup();
-               UNITS_SHORT_TIME.addUnit(new GeneralUnit(1,"s"));
-
+               UNITS_SHORT_TIME.addUnit(new GeneralUnit(1, "s"));
+               
                UNITS_FLIGHT_TIME = new UnitGroup();
-               UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(1,"s"));
-               UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(60,"min"));
+               UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(1, "s"));
+               UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(60, "min"));
                
                UNITS_ROLL = new UnitGroup();
                UNITS_ROLL.addUnit(new GeneralUnit(1, "rad/s"));
-               UNITS_ROLL.addUnit(new GeneralUnit(2*Math.PI, "r/s"));
-               UNITS_ROLL.addUnit(new GeneralUnit(2*Math.PI/60, "rpm"));
+               UNITS_ROLL.addUnit(new GeneralUnit(2 * Math.PI, "r/s"));
+               UNITS_ROLL.addUnit(new GeneralUnit(2 * Math.PI / 60, "rpm"));
                UNITS_ROLL.setDefaultUnit(1);
-
+               
                UNITS_TEMPERATURE = new UnitGroup();
                UNITS_TEMPERATURE.addUnit(new FixedPrecisionUnit("K", 1));
-               UNITS_TEMPERATURE.addUnit(new TemperatureUnit(1, 273.15, DEGREE+"C"));
-               UNITS_TEMPERATURE.addUnit(new TemperatureUnit(5.0/9.0, 459.67, DEGREE+"F"));
+               UNITS_TEMPERATURE.addUnit(new TemperatureUnit(1, 273.15, DEGREE + "C"));
+               UNITS_TEMPERATURE.addUnit(new TemperatureUnit(5.0 / 9.0, 459.67, DEGREE + "F"));
                UNITS_TEMPERATURE.setDefaultUnit(1);
                
                UNITS_PRESSURE = new UnitGroup();
                UNITS_PRESSURE.addUnit(new FixedPrecisionUnit("mbar", 1, 1.0e2));
                UNITS_PRESSURE.addUnit(new FixedPrecisionUnit("bar", 0.001, 1.0e5));
                UNITS_PRESSURE.addUnit(new FixedPrecisionUnit("atm", 0.001, 1.01325e5));
-               UNITS_PRESSURE.addUnit(new GeneralUnit(101325.0/760.0, "mmHg"));
+               UNITS_PRESSURE.addUnit(new GeneralUnit(101325.0 / 760.0, "mmHg"));
                UNITS_PRESSURE.addUnit(new GeneralUnit(3386.389, "inHg"));
                UNITS_PRESSURE.addUnit(new GeneralUnit(6894.75729, "psi"));
                UNITS_PRESSURE.addUnit(new GeneralUnit(1, "Pa"));
-
+               
                UNITS_RELATIVE = new UnitGroup();
-               UNITS_RELATIVE.addUnit(new FixedPrecisionUnit(""+ZWSP, 0.01, 1.0));
+               UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + ZWSP, 0.01, 1.0));
                UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("%", 1, 0.01));
-               UNITS_RELATIVE.addUnit(new FixedPrecisionUnit(""+PERMILLE, 1, 0.001));
+               UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + PERMILLE, 1, 0.001));
                UNITS_RELATIVE.setDefaultUnit(1);
-
                
+
                UNITS_ROUGHNESS = new UnitGroup();
-               UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.000001, MICRO+"m"));
+               UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.000001, MICRO + "m"));
                UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.0000254, "mil"));
                
-               
+
                UNITS_COEFFICIENT = new UnitGroup();
-               UNITS_COEFFICIENT.addUnit(new FixedPrecisionUnit(""+ZWSP, 0.01));  // zero-width space
-               
+               UNITS_COEFFICIENT.addUnit(new FixedPrecisionUnit("" + ZWSP, 0.01)); // zero-width space
                
+
                // This is not used by OpenRocket, and not extensively tested:
-//             UNITS_FREQUENCY = new UnitGroup();
-//             UNITS_FREQUENCY.addUnit(new GeneralUnit(1, "s"));
-//             UNITS_FREQUENCY.addUnit(new GeneralUnit(0.001, "ms"));
-//             UNITS_FREQUENCY.addUnit(new GeneralUnit(0.000001, MICRO + "s"));
-//             UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz"));
-//             UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz"));
-//             UNITS_FREQUENCY.setDefaultUnit(3);
+               //              UNITS_FREQUENCY = new UnitGroup();
+               //              UNITS_FREQUENCY.addUnit(new GeneralUnit(1, "s"));
+               //              UNITS_FREQUENCY.addUnit(new GeneralUnit(0.001, "ms"));
+               //              UNITS_FREQUENCY.addUnit(new GeneralUnit(0.000001, MICRO + "s"));
+               //              UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz"));
+               //              UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz"));
+               //              UNITS_FREQUENCY.setDefaultUnit(3);
                
 
-               HashMap<String,UnitGroup> map = new HashMap<String,UnitGroup>();
+               HashMap<String, UnitGroup> map = new HashMap<String, UnitGroup>();
                map.put("NONE", UNITS_NONE);
                map.put("LENGTH", UNITS_LENGTH);
                map.put("MOTOR_DIMENSIONS", UNITS_MOTOR_DIMENSIONS);
@@ -226,6 +238,7 @@ public class UnitGroup {
                map.put("AREA", UNITS_AREA);
                map.put("STABILITY", UNITS_STABILITY);
                map.put("MASS", UNITS_MASS);
+               map.put("INERTIA", UNITS_INERTIA);
                map.put("ANGLE", UNITS_ANGLE);
                map.put("DENSITY_BULK", UNITS_DENSITY_BULK);
                map.put("DENSITY_SURFACE", UNITS_DENSITY_SURFACE);
@@ -241,7 +254,7 @@ public class UnitGroup {
                map.put("RELATIVE", UNITS_RELATIVE);
                map.put("ROUGHNESS", UNITS_ROUGHNESS);
                map.put("COEFFICIENT", UNITS_COEFFICIENT);
-
+               
                UNITS = Collections.unmodifiableMap(map);
        }
        
@@ -249,52 +262,54 @@ public class UnitGroup {
                UNITS_LENGTH.setDefaultUnit("cm");
                UNITS_MOTOR_DIMENSIONS.setDefaultUnit("mm");
                UNITS_DISTANCE.setDefaultUnit("m");
-               UNITS_AREA.setDefaultUnit("cm"+SQUARED);
+               UNITS_AREA.setDefaultUnit("cm" + SQUARED);
                UNITS_STABILITY.setDefaultUnit("cal");
                UNITS_VELOCITY.setDefaultUnit("m/s");
-               UNITS_ACCELERATION.setDefaultUnit("m/s"+SQUARED);
+               UNITS_ACCELERATION.setDefaultUnit("m/s" + SQUARED);
                UNITS_MASS.setDefaultUnit("g");
-               UNITS_ANGLE.setDefaultUnit(""+DEGREE);
-               UNITS_DENSITY_BULK.setDefaultUnit("g/cm"+CUBED);
-               UNITS_DENSITY_SURFACE.setDefaultUnit("g/m"+SQUARED);
+               UNITS_INERTIA.setDefaultUnit("kg" + DOT + "m" + SQUARED);
+               UNITS_ANGLE.setDefaultUnit("" + DEGREE);
+               UNITS_DENSITY_BULK.setDefaultUnit("g/cm" + CUBED);
+               UNITS_DENSITY_SURFACE.setDefaultUnit("g/m" + SQUARED);
                UNITS_DENSITY_LINE.setDefaultUnit("g/m");
                UNITS_FORCE.setDefaultUnit("N");
                UNITS_IMPULSE.setDefaultUnit("Ns");
                UNITS_TIME_STEP.setDefaultUnit("s");
                UNITS_FLIGHT_TIME.setDefaultUnit("s");
                UNITS_ROLL.setDefaultUnit("r/s");
-               UNITS_TEMPERATURE.setDefaultUnit(DEGREE+"C");
+               UNITS_TEMPERATURE.setDefaultUnit(DEGREE + "C");
                UNITS_PRESSURE.setDefaultUnit("mbar");
                UNITS_RELATIVE.setDefaultUnit("%");
-               UNITS_ROUGHNESS.setDefaultUnit(MICRO+"m");
+               UNITS_ROUGHNESS.setDefaultUnit(MICRO + "m");
        }
        
        public static void setDefaultImperialUnits() {
                UNITS_LENGTH.setDefaultUnit("in");
                UNITS_MOTOR_DIMENSIONS.setDefaultUnit("in");
                UNITS_DISTANCE.setDefaultUnit("ft");
-               UNITS_AREA.setDefaultUnit("in"+SQUARED);
+               UNITS_AREA.setDefaultUnit("in" + SQUARED);
                UNITS_STABILITY.setDefaultUnit("cal");
                UNITS_VELOCITY.setDefaultUnit("ft/s");
-               UNITS_ACCELERATION.setDefaultUnit("ft/s"+SQUARED);
+               UNITS_ACCELERATION.setDefaultUnit("ft/s" + SQUARED);
                UNITS_MASS.setDefaultUnit("oz");
-               UNITS_ANGLE.setDefaultUnit(""+DEGREE);
-               UNITS_DENSITY_BULK.setDefaultUnit("oz/in"+CUBED);
-               UNITS_DENSITY_SURFACE.setDefaultUnit("oz/ft"+SQUARED);
+               UNITS_INERTIA.setDefaultUnit("lb" + DOT + "ft" + SQUARED);
+               UNITS_ANGLE.setDefaultUnit("" + DEGREE);
+               UNITS_DENSITY_BULK.setDefaultUnit("oz/in" + CUBED);
+               UNITS_DENSITY_SURFACE.setDefaultUnit("oz/ft" + SQUARED);
                UNITS_DENSITY_LINE.setDefaultUnit("oz/ft");
                UNITS_FORCE.setDefaultUnit("N");
                UNITS_IMPULSE.setDefaultUnit("Ns");
                UNITS_TIME_STEP.setDefaultUnit("s");
                UNITS_FLIGHT_TIME.setDefaultUnit("s");
                UNITS_ROLL.setDefaultUnit("r/s");
-               UNITS_TEMPERATURE.setDefaultUnit(DEGREE+"F");
+               UNITS_TEMPERATURE.setDefaultUnit(DEGREE + "F");
                UNITS_PRESSURE.setDefaultUnit("mbar");
                UNITS_RELATIVE.setDefaultUnit("%");
                UNITS_ROUGHNESS.setDefaultUnit("mil");
        }
        
        
-       
+
        public static UnitGroup stabilityUnits(Rocket rocket) {
                return new StabilityUnitGroup(rocket);
        }
@@ -304,17 +319,17 @@ public class UnitGroup {
                return new StabilityUnitGroup(config);
        }
        
-
+       
        //////////////////////////////////////////////////////
-
        
+
        private ArrayList<Unit> units = new ArrayList<Unit>();
        private int defaultUnit = 0;
        
        public int getUnitCount() {
                return units.size();
        }
-
+       
        public Unit getDefaultUnit() {
                return units.get(defaultUnit);
        }
@@ -324,14 +339,14 @@ public class UnitGroup {
        }
        
        public void setDefaultUnit(int n) {
-               if (n<0 || n>=units.size()) {
-                       throw new IllegalArgumentException("index out of range: "+n);
+               if (n < 0 || n >= units.size()) {
+                       throw new IllegalArgumentException("index out of range: " + n);
                }
                defaultUnit = n;
        }
        
        
-       
+
        /**
         * Find a unit by approximate unit name.  Only letters and (ordinary) numbers are
         * considered in the matching.  This method is mainly means for testing, allowing
@@ -342,7 +357,7 @@ public class UnitGroup {
         */
        public Unit findApproximate(String str) {
                str = str.replaceAll("\\W", "").trim();
-               for (Unit u: units) {
+               for (Unit u : units) {
                        String name = u.getUnit().replaceAll("\\W", "").trim();
                        if (str.equalsIgnoreCase(name))
                                return u;
@@ -358,15 +373,15 @@ public class UnitGroup {
         * @throws  IllegalArgumentException    if the corresponding unit is not found in the group.
         */
        public void setDefaultUnit(String name) throws IllegalArgumentException {
-               for (int i=0; i < units.size(); i++) {
+               for (int i = 0; i < units.size(); i++) {
                        if (units.get(i).getUnit().equals(name)) {
                                setDefaultUnit(i);
                                return;
                        }
                }
-               throw new IllegalArgumentException("name="+name);
+               throw new IllegalArgumentException("name=" + name);
        }
-
+       
        
        public Unit getUnit(int n) {
                return units.get(n);
@@ -381,7 +396,7 @@ public class UnitGroup {
        }
        
        public void addUnit(int n, Unit u) {
-               units.add(n,u);
+               units.add(n, u);
        }
        
        public void removeUnit(int n) {
@@ -424,8 +439,8 @@ public class UnitGroup {
        
        
 
-       
-       
+
+
        /**
         * Creates a new Value object with the specified value and the default unit of this group.
         * 
@@ -437,9 +452,10 @@ public class UnitGroup {
        }
        
        
-       
-       
+
+
        private static final Pattern STRING_PATTERN = Pattern.compile("^\\s*([0-9.,-]+)(.*?)$");
+       
        /**
         * Converts a string into an SI value.  If the string has one of the units in this
         * group appended to it, that unit will be used in conversion.  Otherwise the default
@@ -467,7 +483,7 @@ public class UnitGroup {
                        value = this.getDefaultUnit().fromUnit(value);
                } else {
                        int i;
-                       for (i=0; i < units.size(); i++) {
+                       for (i = 0; i < units.size(); i++) {
                                Unit u = units.get(i);
                                if (unit.equalsIgnoreCase(u.getUnit())) {
                                        value = u.fromUnit(value);
@@ -475,7 +491,7 @@ public class UnitGroup {
                                }
                        }
                        if (i >= units.size()) {
-                               throw new NumberFormatException("unknown unit "+unit);
+                               throw new NumberFormatException("unknown unit " + unit);
                        }
                }
                
@@ -485,7 +501,7 @@ public class UnitGroup {
        
        ///////////////////////////
        
-       
+
        /**
         * A private class that switches the CaliberUnit to a rocket-specific CaliberUnit.
         * All other methods are passed through to UNITS_STABILITY.
@@ -503,14 +519,14 @@ public class UnitGroup {
                        caliberUnit = new CaliberUnit(config);
                }
                
-
+               
                ////  Modify CaliberUnit to use local variable
                
                @Override
                public Unit getDefaultUnit() {
                        return getUnit(UNITS_STABILITY.getDefaultUnitIndex());
                }
-
+               
                @Override
                public Unit getUnit(int n) {
                        Unit u = UNITS_STABILITY.getUnit(n);
@@ -519,18 +535,18 @@ public class UnitGroup {
                        }
                        return u;
                }
-
+               
                @Override
                public int getUnitIndex(Unit u) {
                        if (u instanceof CaliberUnit) {
-                               for (int i=0; i < UNITS_STABILITY.getUnitCount(); i++) {
+                               for (int i = 0; i < UNITS_STABILITY.getUnitCount(); i++) {
                                        if (UNITS_STABILITY.getUnit(i) instanceof CaliberUnit)
                                                return i;
                                }
                        }
                        return UNITS_STABILITY.getUnitIndex(u);
                }
-
+               
                
 
                ////  Pass on to UNITS_STABILITY
@@ -539,30 +555,30 @@ public class UnitGroup {
                public int getDefaultUnitIndex() {
                        return UNITS_STABILITY.getDefaultUnitIndex();
                }
-
+               
                @Override
                public void setDefaultUnit(int n) {
                        UNITS_STABILITY.setDefaultUnit(n);
                }
-
+               
                @Override
                public int getUnitCount() {
                        return UNITS_STABILITY.getUnitCount();
                }
-
-
+               
+               
                ////  Unsupported methods
                
                @Override
                public void addUnit(int n, Unit u) {
                        throw new UnsupportedOperationException("StabilityUnitGroup must not be modified");
                }
-
+               
                @Override
                public void addUnit(Unit u) {
                        throw new UnsupportedOperationException("StabilityUnitGroup must not be modified");
                }
-
+               
                @Override
                public void removeUnit(int n) {
                        throw new UnsupportedOperationException("StabilityUnitGroup must not be modified");
diff --git a/src/net/sf/openrocket/util/ArrayList.java b/src/net/sf/openrocket/util/ArrayList.java
new file mode 100644 (file)
index 0000000..a91deaa
--- /dev/null
@@ -0,0 +1,30 @@
+package net.sf.openrocket.util;
+
+import java.util.Collection;
+
+/**
+ * An implementation of an ArrayList with a type-safe {@link #clone()} method.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ArrayList<E> extends java.util.ArrayList<E> {
+       
+       public ArrayList() {
+               super();
+       }
+       
+       public ArrayList(Collection<? extends E> c) {
+               super(c);
+       }
+       
+       public ArrayList(int initialCapacity) {
+               super(initialCapacity);
+       }
+       
+       @SuppressWarnings("unchecked")
+       @Override
+       public ArrayList<E> clone() {
+               return (ArrayList<E>) super.clone();
+       }
+       
+}
index ff97d9a8e1853df824d396f81654e74ebb05459b..10d632e2737b5761a93baccebcdfe2ee4fbc2581 100644 (file)
@@ -22,16 +22,16 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.Vector;
 
 import javax.imageio.ImageIO;
 import javax.swing.AbstractAction;
 import javax.swing.AbstractButton;
 import javax.swing.Action;
+import javax.swing.BoundedRangeModel;
+import javax.swing.ComboBoxModel;
 import javax.swing.DefaultBoundedRangeModel;
 import javax.swing.DefaultComboBoxModel;
 import javax.swing.DefaultListSelectionModel;
@@ -45,8 +45,10 @@ import javax.swing.JSpinner;
 import javax.swing.JTable;
 import javax.swing.JTree;
 import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
 import javax.swing.LookAndFeel;
 import javax.swing.RootPaneContainer;
+import javax.swing.SpinnerModel;
 import javax.swing.SpinnerNumberModel;
 import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
@@ -54,10 +56,13 @@ import javax.swing.event.ChangeListener;
 import javax.swing.table.AbstractTableModel;
 import javax.swing.table.DefaultTableColumnModel;
 import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableColumnModel;
 import javax.swing.table.TableModel;
+import javax.swing.tree.DefaultMutableTreeNode;
 import javax.swing.tree.DefaultTreeModel;
 import javax.swing.tree.DefaultTreeSelectionModel;
-import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreeSelectionModel;
 
 import net.sf.openrocket.gui.Resettable;
 import net.sf.openrocket.logging.LogHelper;
@@ -194,6 +199,7 @@ public class GUIUtil {
                window.addWindowListener(new WindowAdapter() {
                        @Override
                        public void windowClosed(WindowEvent e) {
+                               log.debug("Clearing all models of window " + window);
                                setNullModels(window);
                                MemoryManagement.collectable(window);
                        }
@@ -302,7 +308,11 @@ public class GUIUtil {
                        for (ChangeListener l : spinner.getChangeListeners()) {
                                spinner.removeChangeListener(l);
                        }
+                       SpinnerModel model = spinner.getModel();
                        spinner.setModel(new SpinnerNumberModel());
+                       if (model instanceof Invalidatable) {
+                               ((Invalidatable) model).invalidate();
+                       }
                        
                } else if (c instanceof JSlider) {
                        
@@ -310,7 +320,11 @@ public class GUIUtil {
                        for (ChangeListener l : slider.getChangeListeners()) {
                                slider.removeChangeListener(l);
                        }
+                       BoundedRangeModel model = slider.getModel();
                        slider.setModel(new DefaultBoundedRangeModel());
+                       if (model instanceof Invalidatable) {
+                               ((Invalidatable) model).invalidate();
+                       }
                        
                } else if (c instanceof JComboBox) {
                        
@@ -318,7 +332,11 @@ public class GUIUtil {
                        for (ActionListener l : combo.getActionListeners()) {
                                combo.removeActionListener(l);
                        }
+                       ComboBoxModel model = combo.getModel();
                        combo.setModel(new DefaultComboBoxModel());
+                       if (model instanceof Invalidatable) {
+                               ((Invalidatable) model).invalidate();
+                       }
                        
                } else if (c instanceof AbstractButton) {
                        
@@ -326,60 +344,51 @@ public class GUIUtil {
                        for (ActionListener l : button.getActionListeners()) {
                                button.removeActionListener(l);
                        }
+                       Action model = button.getAction();
                        button.setAction(new AbstractAction() {
                                @Override
                                public void actionPerformed(ActionEvent e) {
                                }
                        });
+                       if (model instanceof Invalidatable) {
+                               ((Invalidatable) model).invalidate();
+                       }
                        
                } else if (c instanceof JTable) {
                        
                        JTable table = (JTable) c;
+                       TableModel model1 = table.getModel();
                        table.setModel(new DefaultTableModel());
+                       if (model1 instanceof Invalidatable) {
+                               ((Invalidatable) model1).invalidate();
+                       }
+                       
+                       TableColumnModel model2 = table.getColumnModel();
                        table.setColumnModel(new DefaultTableColumnModel());
+                       if (model2 instanceof Invalidatable) {
+                               ((Invalidatable) model2).invalidate();
+                       }
+                       
+                       ListSelectionModel model3 = table.getSelectionModel();
                        table.setSelectionModel(new DefaultListSelectionModel());
+                       if (model3 instanceof Invalidatable) {
+                               ((Invalidatable) model3).invalidate();
+                       }
                        
                } else if (c instanceof JTree) {
                        
                        JTree tree = (JTree) c;
-                       tree.setModel(new DefaultTreeModel(new TreeNode() {
-                               @SuppressWarnings("rawtypes")
-                               @Override
-                               public Enumeration children() {
-                                       return new Vector().elements();
-                               }
-                               
-                               @Override
-                               public boolean getAllowsChildren() {
-                                       return false;
-                               }
-                               
-                               @Override
-                               public TreeNode getChildAt(int childIndex) {
-                                       return null;
-                               }
-                               
-                               @Override
-                               public int getChildCount() {
-                                       return 0;
-                               }
-                               
-                               @Override
-                               public int getIndex(TreeNode node) {
-                                       return 0;
-                               }
-                               
-                               @Override
-                               public TreeNode getParent() {
-                                       return null;
-                               }
-                               
-                               @Override
-                               public boolean isLeaf() {
-                                       return true;
-                               }
-                       }));
+                       TreeModel model1 = tree.getModel();
+                       tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode()));
+                       if (model1 instanceof Invalidatable) {
+                               ((Invalidatable) model1).invalidate();
+                       }
+                       
+                       TreeSelectionModel model2 = tree.getSelectionModel();
                        tree.setSelectionModel(new DefaultTreeSelectionModel());
+                       if (model2 instanceof Invalidatable) {
+                               ((Invalidatable) model2).invalidate();
+                       }
                        
                } else if (c instanceof Resettable) {
                        
@@ -398,7 +407,6 @@ public class GUIUtil {
        
        
 
-
        /**
         * A mouse listener that toggles the state of a boolean value in a table model
         * when clicked on another column of the table.
index 6878163babf02446fbd524213393f5f5c5b38f28..446044df35c16d686f15cceead7f781e233ff282 100644 (file)
@@ -48,6 +48,7 @@ public class Icons {
        public static final Icon FILE_OPEN_EXAMPLE = loadImageIcon("pix/icons/document-open-example.png", "Open example document");
        public static final Icon FILE_SAVE = loadImageIcon("pix/icons/document-save.png", "Save document");
        public static final Icon FILE_SAVE_AS = loadImageIcon("pix/icons/document-save-as.png", "Save document as");
+       public static final Icon FILE_PRINT = loadImageIcon("pix/icons/document-print.png", "Print document");
        public static final Icon FILE_CLOSE = loadImageIcon("pix/icons/document-close.png", "Close document");
        public static final Icon FILE_QUIT = loadImageIcon("pix/icons/application-exit.png", "Quit OpenRocket");
        
index 33331665a830c63bf6ef7d33fb6a247392149d8a..f7d65c412d9231550b0db2afc4718e23e2d09613 100644 (file)
@@ -17,13 +17,13 @@ public final class Inertia {
        }
        
        /**
-        * Return the longitudal unit moment of inertia of a solid cylinder,
+        * Return the longitudinal unit moment of inertia of a solid cylinder,
         * relative to the midpoint lengthwise.
         * 
         * @param radius        the radius of the cylinder.
         * @param length        the total length of the cylinder (reference at midpoint)
         */
-       public static double filledCylinderLongitudal(double radius, double length) {
+       public static double filledCylinderLongitudinal(double radius, double length) {
                return (3*pow2(radius) + pow2(length))/12;
        }
        
diff --git a/src/net/sf/openrocket/util/Invalidatable.java b/src/net/sf/openrocket/util/Invalidatable.java
new file mode 100644 (file)
index 0000000..9449989
--- /dev/null
@@ -0,0 +1,17 @@
+package net.sf.openrocket.util;
+
+/**
+ * An object that can be invalidated (in some sense of the word).  After calling the
+ * invalidate method the object should not be used any more and it may enforce
+ * disusage for certain methods.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface Invalidatable {
+       
+       /**
+        * Invalidate this object.
+        */
+       public void invalidate();
+       
+}
diff --git a/src/net/sf/openrocket/util/Invalidator.java b/src/net/sf/openrocket/util/Invalidator.java
new file mode 100644 (file)
index 0000000..7b67811
--- /dev/null
@@ -0,0 +1,73 @@
+package net.sf.openrocket.util;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.logging.TraceException;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A class that performs object invalidation functions.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class Invalidator implements Invalidatable {
+       private static final boolean USE_CHECKS = Prefs.useSafetyChecks();
+       
+       private static final LogHelper log = Application.getLogger();
+       
+       private final Object monitorable;
+       private TraceException invalidated = null;
+       
+       
+       /**
+        * Sole constructor.  The parameter is used when writing error messages, and
+        * is not referenced otherwise.
+        * 
+        * @param monitorable   the object this invalidator is monitoring (may be null or a descriptive string)
+        */
+       public Invalidator(Object monitorable) {
+               this.monitorable = monitorable;
+       }
+       
+       
+       /**
+        * Check whether the object has been invalidated.  Depending on the parameter either
+        * a BugException is thrown or a warning about the object access is logged.
+        * 
+        * @param throwException        whether to throw an exception or log a warning.
+        * @return      <code>true</code> when the object has not been invalidated, <code>false</code> if it has
+        * @throws      BugException    if the object has been invalidated and <code>throwException</code> is true.
+        */
+       public boolean check(boolean throwException) {
+               if (invalidated != null) {
+                       if (throwException) {
+                               throw new BugException(monitorable + ": This object has been invalidated", invalidated);
+                       } else {
+                               log.warn(1, monitorable + ": This object has been invalidated",
+                                               new TraceException("Usage was attempted here", invalidated));
+                       }
+                       return false;
+               }
+               return true;
+       }
+       
+       
+       /**
+        * Check whether the object has been invalidated.
+        * @return      <code>true</code> if the object has been invalidated, <code>false</code> otherwise.
+        */
+       public boolean isInvalidated() {
+               return invalidated != null;
+       }
+       
+       
+       @Override
+       public void invalidate() {
+               if (USE_CHECKS) {
+                       if (invalidated != null) {
+                               log.warn(1, monitorable + ": This object has already been invalidated, ignoring", invalidated);
+                       }
+                       invalidated = new TraceException("Invalidation occurred here");
+               }
+       }
+       
+}
diff --git a/src/net/sf/openrocket/util/ListenerList.java b/src/net/sf/openrocket/util/ListenerList.java
new file mode 100644 (file)
index 0000000..b4f4c02
--- /dev/null
@@ -0,0 +1,264 @@
+package net.sf.openrocket.util;
+
+import java.util.Iterator;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.logging.TraceException;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A list of listeners of a specific type.  This class contains various utility,
+ * safety and debugging methods for handling listeners.
+ * <p>
+ * Note that unlike normal listener implementations, this list does NOT allow the 
+ * exact same listener (equality using ==) twice.  While adding a listener twice to
+ * a event source would in principle be valid, in practice it's most likely a bug.
+ * For example the Swing implementation Sun JRE contains such bugs.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ * @param <T>  the type of the listeners.
+ */
+public class ListenerList<T> implements Invalidatable, Iterable<T> {
+       private static final LogHelper log = Application.getLogger();
+       
+       private final ArrayList<ListenerData<T>> listeners = new ArrayList<ListenerData<T>>();
+       private final TraceException instantiationLocation;
+       
+       private TraceException invalidated = null;
+       
+       
+       /**
+        * Sole contructor.
+        */
+       public ListenerList() {
+               this.instantiationLocation = new TraceException(1, 1);
+       }
+       
+       
+       /**
+        * Adds the specified listener to this list.  The listener is not added if it
+        * already is in the list (checked by the equality operator ==).  This method throws
+        * a BugException if {@link #invalidate()} has been called.
+        * 
+        * @param listener      the listener to add.
+        * @return                      whether the listeners was actually added to the list.
+        * @throws BugException         if this listener list has been invalidated.
+        */
+       public boolean addListener(T listener) {
+               checkState(true);
+               
+               ListenerData<T> data = new ListenerData<T>(listener);
+               if (listeners.contains(data)) {
+                       log.warn(1, "Attempting to add duplicate listener " + listener);
+                       return false;
+               }
+               listeners.add(data);
+               return true;
+       }
+       
+       
+       /**
+        * Remove the specified listener from the list.  The listener is removed based on the
+        * quality operator ==, not by the equals() method.
+        * 
+        * @param listener      the listener to remove.
+        * @return                      whether the listener was actually removed.
+        */
+       public boolean removeListener(T listener) {
+               checkState(false);
+               
+               Iterator<ListenerData<T>> iterator = listeners.iterator();
+               while (iterator.hasNext()) {
+                       if (iterator.next().listener == listener) {
+                               iterator.remove();
+                               log.verbose(1, "Removing listener " + listener);
+                               return true;
+                       }
+               }
+               log.info(1, "Attempting to remove non-existant listener " + listener);
+               return false;
+       }
+       
+       
+       /**
+        * Return the number of listeners in this list.
+        */
+       public int getListenerCount() {
+               return listeners.size();
+       }
+       
+       
+       /**
+        * Return an iterator that iterates of the listeners.  This iterator is backed by
+        * a copy of the iterator list, so {@link #addListener(Object)} and {@link #removeListener(Object)}
+        * may be called while iterating the list without effect on the iteration.  The returned
+        * iterator does not support the {@link Iterator#remove()} method.
+        */
+       @Override
+       public Iterator<T> iterator() {
+               checkState(false);
+               return new ListenerDataIterator();
+       }
+       
+       /**
+        * Return the instantiation location of this listener list.
+        * @return      the location where this listener list was instantiated.
+        */
+       public TraceException getInstantiationLocation() {
+               return instantiationLocation;
+       }
+       
+       
+       /**
+        * Invalidate this listener list.  Invalidation removes all listeners from the list.
+        * After invalidation {@link #addListener(Object)} will throw an exception, the other
+        * methods produce a warning log message.
+        */
+       @Override
+       public void invalidate() {
+               this.invalidated = new TraceException("Invalidation occurred at this point");
+               if (!listeners.isEmpty()) {
+                       log.info("Invalidating " + this + " while still having listeners " + listeners);
+               }
+               listeners.clear();
+       }
+       
+       
+       public boolean isInvalidated() {
+               return this.invalidated != null;
+       }
+       
+       
+       private void checkState(boolean error) {
+               if (this.invalidated != null) {
+                       if (error) {
+                               throw new BugException(this + ": this ListenerList has been invalidated", invalidated);
+                       } else {
+                               log.warn(1, this + ": this ListenerList has been invalidated",
+                                               new TraceException("ListenerList was attempted to be used here", invalidated));
+                       }
+               }
+       }
+       
+       
+       @Override
+       public String toString() {
+               StringBuilder sb = new StringBuilder();
+               sb.append("ListenerList[");
+               
+               if (this.invalidated != null) {
+                       sb.append("INVALIDATED]");
+                       return sb.toString();
+               }
+               
+               if (listeners.isEmpty()) {
+                       sb.append("empty");
+               } else {
+                       boolean first = true;
+                       for (ListenerData<T> l : listeners) {
+                               if (!first) {
+                                       sb.append("; ");
+                               }
+                               first = false;
+                               sb.append(l);
+                       }
+               }
+               sb.append("]");
+               return sb.toString();
+       }
+       
+       
+       /**
+        * A class containing data about a listener.
+        * 
+        * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+        * @param <T>   the listener type
+        */
+       public static class ListenerData<T> {
+               private final T listener;
+               private final long addTimestamp;
+               private final TraceException addLocation;
+               private long accessTimestamp;
+               
+               /**
+                * Sole constructor.
+                */
+               private ListenerData(T listener) {
+                       if (listener == null) {
+                               throw new NullPointerException("listener is null");
+                       }
+                       this.listener = listener;
+                       this.addTimestamp = System.currentTimeMillis();
+                       this.accessTimestamp = this.addTimestamp;
+                       this.addLocation = new TraceException("Listener " + listener + " add position");
+               }
+               
+               @Override
+               public boolean equals(Object obj) {
+                       if (this == obj)
+                               return true;
+                       if (!(obj instanceof ListenerData))
+                               return false;
+                       ListenerData<?> other = (ListenerData<?>) obj;
+                       return this.listener == other.listener;
+               }
+               
+               @Override
+               public int hashCode() {
+                       return listener.hashCode();
+               }
+               
+               /**
+                * Return the listener.
+                */
+               public T getListener() {
+                       return listener;
+               }
+               
+               /**
+                * Return the millisecond timestamp when this listener was added to the
+                * listener list.
+                */
+               public long getAddTimestamp() {
+                       return addTimestamp;
+               }
+               
+               /**
+                * Return the location where this listener was added to the listener list.
+                */
+               public TraceException getAddLocation() {
+                       return addLocation;
+               }
+               
+               /**
+                * Return the millisecond timestamp when this listener was last accessed through
+                * the listener list iterator.
+                */
+               public long getAccessTimestamp() {
+                       return accessTimestamp;
+               }
+       }
+       
+       
+       private class ListenerDataIterator implements Iterator<T> {
+               private final Iterator<ListenerData<T>> iterator = listeners.clone().iterator();
+               
+               @Override
+               public boolean hasNext() {
+                       return iterator.hasNext();
+               }
+               
+               @Override
+               public T next() {
+                       ListenerData<T> data = iterator.next();
+                       data.accessTimestamp = System.currentTimeMillis();
+                       return data.listener;
+               }
+               
+               @Override
+               public void remove() {
+                       throw new UnsupportedOperationException("Remove not supported");
+               }
+       }
+       
+}
index 34589b588f3bd52fb3383a43f02f324360263f9f..7b55200be2304c89a1e660b64f6420645a43b478 100644 (file)
@@ -23,7 +23,7 @@ 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;
+       private static final int PURGE_CALL_COUNT = 1000;
        
 
        /**
@@ -31,7 +31,11 @@ public final class MemoryManagement {
         * to 
         */
        private static List<MemoryData> objects = new LinkedList<MemoryData>();
-       private static int callCount = 0;
+       private static int collectableCallCount = 0;
+       
+
+       private static List<WeakReference<ListenerList<?>>> listenerLists = new LinkedList<WeakReference<ListenerList<?>>>();
+       private static int listenerCallCount = 0;
        
        
        private MemoryManagement() {
@@ -51,31 +55,62 @@ public final class MemoryManagement {
                }
                log.debug("Adding object into collectable list: " + o);
                objects.add(new MemoryData(o));
-               callCount++;
-               if (callCount % PURGE_CALL_COUNT == 0) {
-                       purge();
+               collectableCallCount++;
+               if (collectableCallCount % PURGE_CALL_COUNT == 0) {
+                       purgeCollectables();
                }
        }
        
        
        /**
-        * Return the number of times {@link #collectable(Object)} has been called.
-        * @return      the number of times {@link #collectable(Object)} has been called.
+        * 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 int getCallCount() {
-               return callCount;
+       public static synchronized List<MemoryData> getRemainingCollectableObjects() {
+               for (int i = 0; i < 5; i++) {
+                       System.runFinalization();
+                       System.gc();
+                       try {
+                               Thread.sleep(1);
+                       } catch (InterruptedException e) {
+                       }
+               }
+               purgeCollectables();
+               return new ArrayList<MemoryData>(objects);
        }
        
        
+
+
        /**
-        * 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.
+        * Register a new ListenerList object.  This can be used to monitor freeing of listeners
+        * and find memory leaks.  The objects are held by a weak reference, allowing them to be
+        * garbage-collected.
+        * 
+        * @param list  the listener list to register
+        */
+       public static synchronized void registerListenerList(ListenerList<?> list) {
+               listenerLists.add(new WeakReference<ListenerList<?>>(list));
+               listenerCallCount++;
+               if (listenerCallCount % PURGE_CALL_COUNT == 0) {
+                       purgeListeners();
+               }
+               
+       }
+       
+       /**
+        * Return a list of listener list objects corresponding to the objects that have been
+        * registered by {@link #registerListenerList(ListenerList)} and have not been garbage-collected yet.
         * 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.
+        * @return      a list of listener list objects that have not yet been garbage-collected.
         */
-       public static synchronized ArrayList<MemoryData> getRemainingObjects() {
+       public static synchronized List<ListenerList<?>> getRemainingListenerLists() {
                for (int i = 0; i < 5; i++) {
                        System.runFinalization();
                        System.gc();
@@ -84,8 +119,15 @@ public final class MemoryManagement {
                        } catch (InterruptedException e) {
                        }
                }
-               purge();
-               return new ArrayList<MemoryData>(objects);
+               purgeListeners();
+               List<ListenerList<?>> list = new ArrayList<ListenerList<?>>();
+               for (WeakReference<ListenerList<?>> ref : listenerLists) {
+                       ListenerList<?> l = ref.get();
+                       if (l != null) {
+                               list.add(l);
+                       }
+               }
+               return list;
        }
        
        
@@ -93,7 +135,7 @@ public final class MemoryManagement {
        /**
         * Purge all cleared references from the object list.
         */
-       private static void purge() {
+       private static void purgeCollectables() {
                int origCount = objects.size();
                Iterator<MemoryData> iterator = objects.iterator();
                while (iterator.hasNext()) {
@@ -106,6 +148,21 @@ public final class MemoryManagement {
        }
        
        
+       /**
+        * Purge all cleared references from the object list.
+        */
+       private static void purgeListeners() {
+               int origCount = listenerLists.size();
+               Iterator<WeakReference<ListenerList<?>>> iterator = listenerLists.iterator();
+               while (iterator.hasNext()) {
+                       WeakReference<ListenerList<?>> ref = iterator.next();
+                       if (ref.get() == null) {
+                               iterator.remove();
+                       }
+               }
+               log.debug(listenerLists.size() + " of " + origCount + " listener lists remaining after purge.");
+       }
+       
        /**
         * A value object class containing data of a discarded object reference.
         */
index eb0b6c472a00568d0b648b30a858050c530fc15b..bff7480e80990c537966902e8643992e263b71c5 100644 (file)
@@ -1,7 +1,15 @@
 package net.sf.openrocket.util;
 
+/**
+ * Interface describing objects whose state changes can be monitored based on
+ * a modification ID number.  If a specific object has the same modification ID
+ * at two different points in time, the object state is guaranteed to be the same.
+ * This does not necessarily hold between two different instances of an object type.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
 public interface Monitorable {
-
+       
        /**
         * Return a modification ID unique to the current state of this object and contained objects.
         * The general contract is that if a specific object has the same modification ID at two moments
@@ -17,7 +25,7 @@ public interface Monitorable {
         * increasing a modification counter or retrieving a new unique ID every time a value is set.
         * <p>
         * Objects that contain other objects with a mutable state may for example return the sum of the
-        * object's own modification ID, a modification ID counter (initially zero) and the modification ID
+        * object's own modification ID, a modification ID counter (initially zero) and the modification IDs
         * of the contained objects.  When a mutable object is set, the modification counter is increased by
         * the modification ID of the current object in order to preserve monotonicity.
         * <p>
index ed7cb2c85b77281aa5a3ca6fd61d286faf90fe8c..ac3144e2ccc4a307da86f999d93dde4f477a0b6f 100644 (file)
@@ -508,7 +508,7 @@ public class Prefs {
                if (dpi < 10)
                        dpi = 960;
                
-               return ((double) dpi) / 10.0;
+               return (dpi) / 10.0;
        }
        
        
@@ -558,7 +558,19 @@ public class Prefs {
        }
        
        
-
+       /**
+        * Return whether to use additional safety code checks.
+        */
+       public static boolean useSafetyChecks() {
+               // Currently default to false unless openrocket.debug.safetycheck is defined
+               String s = System.getProperty("openrocket.debug.safetycheck");
+               if (s != null && !(s.equalsIgnoreCase("false") || s.equalsIgnoreCase("off"))) {
+                       return true;
+               }
+               return false;
+       }
+       
+       
        public static Point getWindowPosition(Class<?> c) {
                int x, y;
                String pref = PREFNODE.node("windows").get("position." + c.getCanonicalName(), null);
index b91f70473124568efc9f7205e54a9e96ef48f095..e62270ab0b138124d1c91201b0670abd1d3f60d7 100644 (file)
@@ -18,9 +18,14 @@ public class Reflection {
         */
        public static class Method {
                private final java.lang.reflect.Method method;
+               
                public Method(java.lang.reflect.Method m) {
+                       if (m == null) {
+                               throw new IllegalArgumentException("method is null");
+                       }
                        method = m;
                }
+               
                /**
                 * Same as Method.invoke(), but the possible exceptions are wrapped into 
                 * RuntimeExceptions.
@@ -29,21 +34,23 @@ public class Reflection {
                        try {
                                return method.invoke(obj, args);
                        } catch (IllegalArgumentException e) {
-                               throw new BugException("Error while invoking method '"+method+"'. "+
-                                               "Please report this as a bug.",e);
+                               throw new BugException("Error while invoking method '" + method + "'. " +
+                                               "Please report this as a bug.", e);
                        } catch (IllegalAccessException e) {
-                               throw new BugException("Error while invoking method '"+method+"'. "+
-                                               "Please report this as a bug.",e);
+                               throw new BugException("Error while invoking method '" + method + "'. " +
+                                               "Please report this as a bug.", e);
                        } catch (InvocationTargetException e) {
                                throw Reflection.handleWrappedException(e);
                        }
                }
+               
                /**
                 * Invoke static method.  Equivalent to invoke(null, args...).
                 */
                public Object invokeStatic(Object... args) {
-                       return invoke(null,args);
+                       return invoke(null, args);
                }
+               
                /**
                 * Same as Method.toString().
                 */
@@ -72,47 +79,47 @@ public class Reflection {
                        throw new BugException("wrapped exception without cause", e);
                }
                if (cause instanceof RuntimeException) {
-                       throw (RuntimeException)cause;
+                       throw (RuntimeException) cause;
                }
                if (cause instanceof Error) {
-                       throw (Error)cause;
+                       throw (Error) cause;
                }
                throw new BugException("wrapped exception occurred", cause);
        }
        
        
-       
+
        /**
         * Throws an exception if method not found.
         */
        public static Reflection.Method findMethodStatic(
                        Class<? extends RocketComponent> componentClass,
                        String method, Class<?>... params) {
-               Reflection.Method m = findMethod(ROCKETCOMPONENT_PACKAGE, componentClass, 
+               Reflection.Method m = findMethod(ROCKETCOMPONENT_PACKAGE, componentClass,
                                "", method, params);
                if (m == null) {
                        throw new BugException("Could not find method for componentClass="
-                                       +componentClass+" method="+method);
+                                       + componentClass + " method=" + method);
                }
                return m;
        }
        
        
-       
-       public static Reflection.Method findMethod(String pack, RocketComponent component, 
-                       String method, Class<?>...params) {
-               return findMethod(pack,component.getClass(),"",method,params);
+
+       public static Reflection.Method findMethod(String pack, RocketComponent component,
+                       String method, Class<?>... params) {
+               return findMethod(pack, component.getClass(), "", method, params);
        }
        
        
-       public static Reflection.Method findMethod(String pack, RocketComponent component, 
+       public static Reflection.Method findMethod(String pack, RocketComponent component,
                        String suffix, String method, Class<?>... params) {
                return findMethod(pack, component.getClass(), suffix, method, params);
        }
-
        
-       public static Reflection.Method findMethod(String pack, 
-                       Class<? extends RocketComponent> componentClass, 
+       
+       public static Reflection.Method findMethod(String pack,
+                       Class<? extends RocketComponent> componentClass,
                        String suffix, String method, Class<?>... params) {
                Class<?> currentclass;
                String name;
@@ -120,18 +127,18 @@ public class Reflection {
                currentclass = componentClass;
                while ((currentclass != null) && (currentclass != Object.class)) {
                        name = currentclass.getCanonicalName();
-                       if (name.lastIndexOf('.')>=0)
-                               name = name.substring(name.lastIndexOf(".")+1);
+                       if (name.lastIndexOf('.') >= 0)
+                               name = name.substring(name.lastIndexOf(".") + 1);
                        name = pack + "." + name + suffix;
                        
                        try {
                                Class<?> c = Class.forName(name);
-                               java.lang.reflect.Method m = c.getMethod(method,params);
+                               java.lang.reflect.Method m = c.getMethod(method, params);
                                return new Reflection.Method(m);
                        } catch (ClassNotFoundException ignore) {
                        } catch (NoSuchMethodException ignore) {
                        }
-
+                       
                        currentclass = currentclass.getSuperclass();
                }
                return null;
@@ -147,24 +154,24 @@ public class Reflection {
                currentclass = component.getClass();
                while ((currentclass != null) && (currentclass != Object.class)) {
                        name = currentclass.getCanonicalName();
-                       if (name.lastIndexOf('.')>=0)
-                               name = name.substring(name.lastIndexOf(".")+1);
+                       if (name.lastIndexOf('.') >= 0)
+                               name = name.substring(name.lastIndexOf(".") + 1);
                        name = pack + "." + name + suffix;
                        
                        try {
                                Class<?> c = Class.forName(name);
                                Class<?>[] paramClasses = new Class<?>[params.length];
-                               for (int i=0; i < params.length; i++) {
+                               for (int i = 0; i < params.length; i++) {
                                        paramClasses[i] = params[i].getClass();
                                }
                                
                                // Constructors must be searched manually.  Why?!
-                               main: for (Constructor<?> constructor: c.getConstructors()) {
+                               main: for (Constructor<?> constructor : c.getConstructors()) {
                                        Class<?>[] parameterTypes = constructor.getParameterTypes();
                                        if (params.length != parameterTypes.length)
                                                continue;
-                                       for (int i=0; i < params.length; i++) {
-                                               if (!parameterTypes[i].isInstance(params[i])) 
+                                       for (int i = 0; i < params.length; i++) {
+                                               if (!parameterTypes[i].isInstance(params[i]))
                                                        continue main;
                                        }
                                        // Matching constructor found
@@ -172,18 +179,18 @@ public class Reflection {
                                }
                        } catch (ClassNotFoundException ignore) {
                        } catch (IllegalArgumentException e) {
-                               throw new BugException("Construction of "+name+" failed",e);
+                               throw new BugException("Construction of " + name + " failed", e);
                        } catch (InstantiationException e) {
-                               throw new BugException("Construction of "+name+" failed",e);
+                               throw new BugException("Construction of " + name + " failed", e);
                        } catch (IllegalAccessException e) {
-                               throw new BugException("Construction of "+name+" failed",e);
+                               throw new BugException("Construction of " + name + " failed", e);
                        } catch (InvocationTargetException e) {
                                throw Reflection.handleWrappedException(e);
                        }
-
+                       
                        currentclass = currentclass.getSuperclass();
                }
-               throw new BugException("Suitable constructor for component "+component+ 
+               throw new BugException("Suitable constructor for component " + component +
                                " not found");
        }
 }
index fb202a916b71514a41f9a5484c316a82ffc5da82..2e89c0e47e5a41059cafc3b4f4b62e33bcc08a9b 100644 (file)
@@ -4,6 +4,7 @@ import java.util.LinkedList;
 
 import net.sf.openrocket.gui.main.ExceptionHandler;
 import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.logging.TraceException;
 import net.sf.openrocket.startup.Application;
 
 /**
@@ -16,18 +17,26 @@ import net.sf.openrocket.startup.Application;
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
-public class SafetyMutex {
+public abstract 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>();
+       /**
+        * Return a new instance of a safety mutex.  This returns an actual implementation
+        * or a bogus implementation depending on whether safety checks are enabled or disabled.
+        * 
+        * @return      a new instance of a safety mutex
+        */
+       public static SafetyMutex newInstance() {
+               if (Prefs.useSafetyChecks()) {
+                       return new ConcreteSafetyMutex();
+               } else {
+                       return new BogusSafetyMutex();
+               }
+       }
        
        
+
        /**
         * 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
@@ -35,12 +44,7 @@ public class SafetyMutex {
         * 
         * @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);
-               }
-       }
+       public abstract void verify();
        
        
        /**
@@ -52,23 +56,9 @@ public class SafetyMutex {
         * 
         * @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);
-       }
+       public abstract void lock(String 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,
@@ -79,93 +69,170 @@ public class SafetyMutex {
         * @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);
-                       
+       public abstract boolean unlock(String location);
+       
+       
 
-                       // Check that the mutex is locked
-                       if (lockingThread == null) {
-                               error("Mutex was not locked", false);
-                               return false;
+       /**
+        * Bogus implementation of a safety mutex (used when safety checking is not performed).
+        */
+       static class BogusSafetyMutex extends SafetyMutex {
+               
+               @Override
+               public void verify() {
+               }
+               
+               @Override
+               public void lock(String location) {
+               }
+               
+               @Override
+               public boolean unlock(String location) {
+                       return true;
+               }
+               
+       }
+       
+       /**
+        * A concrete, working implementation of a safety mutex.
+        */
+       static class ConcreteSafetyMutex extends SafetyMutex {
+               private static final boolean STORE_LOCKING_LOCATION = (System.getProperty("openrocket.debug.mutexlocation") != null);
+               
+               // Package-private for unit testing
+               static volatile boolean errorReported = false;
+               
+               // lockingThread is set when this mutex is locked.
+               Thread lockingThread = null;
+               // longingLocation is set when lockingThread is, if STORE_LOCKING_LOCATION is true
+               TraceException lockingLocation = null;
+               // Stack of places that have locked this mutex
+               final LinkedList<String> locations = new LinkedList<String>();
+               
+               
+
+               @Override
+               public synchronized void verify() {
+                       checkState(true);
+                       if (lockingThread != null && lockingThread != Thread.currentThread()) {
+                               error("Mutex is already locked", true);
                        }
-                       
-                       // 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;
+               }
+               
+               
+
+               @Override
+               public synchronized void lock(String location) {
+                       if (location == null) {
+                               throw new IllegalArgumentException("location is null");
                        }
+                       checkState(true);
                        
-                       // 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;
+                       Thread currentThread = Thread.currentThread();
+                       if (lockingThread != null && lockingThread != currentThread) {
+                               error("Mutex is already locked", true);
                        }
                        
-                       // Unlock the mutex if the last one
-                       if (locations.isEmpty()) {
-                               lockingThread = null;
+                       lockingThread = currentThread;
+                       if (STORE_LOCKING_LOCATION) {
+                               lockingLocation = new TraceException("Location where mutex was locked '" + location + "'");
                        }
-                       return true;
-               } catch (Exception e) {
-                       ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " +
-                                       "locking thread=" + lockingThread + " locations=" + locations, e);
-                       return false;
+                       locations.push(location);
                }
-       }
-       
-       
+               
+               
 
-       /**
-        * 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)
+
+               @Override
+               public synchronized boolean unlock(String location) {
                        try {
-                               error("Mutex data inconsistency occurred - unlocking mutex", throwException);
-                       } finally {
-                               lockingThread = null;
-                               locations.clear();
+                               
+                               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;
+                                       lockingLocation = null;
+                               }
+                               return true;
+                       } catch (Exception e) {
+                               ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " +
+                                               "locking thread=" + lockingThread + " locations=" + locations, e);
+                               return false;
                        }
                }
-       }
-       
-       
-       /**
-        * 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);
+
+               /**
+                * 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;
+                                       lockingLocation = null;
+                                       locations.clear();
+                               }
+                       }
                }
                
-               if (throwException) {
-                       throw ex;
+               
+               /**
+                * 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, lockingLocation);
+                       
+                       if (!errorReported) {
+                               errorReported = true;
+                               ExceptionHandler.handleErrorCondition(ex);
+                       } else {
+                               log.error(message, ex);
+                       }
+                       
+                       if (throwException) {
+                               throw ex;
+                       }
                }
        }
-       
 }
index 394d385fb34964411ea873cdcbd638bc6e2aa012..a23ee73a200f1a816f344f71219e861ac5803a8d 100644 (file)
  */
 package net.sf.openrocket.file.rocksim;
 
-import junit.framework.TestCase;
-
 import java.lang.reflect.Field;
+import java.util.List;
 
+import junit.framework.TestCase;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 
 /**
  * A base class for the Rocksim tests.  Includes code from the junitx.addons project.
  */
 public abstract class RocksimTestBase extends TestCase {
-
-    /**
-     * Test constructor.
-     *
-     * @param name the name of the test to run.
-     */
-    public RocksimTestBase(String name) {
-        super(name);
-    }
-    
-    public void assertContains (RocketComponent child, RocketComponent[] components) {
-        for (int i = 0; i < components.length; i++) {
-            if (components[i].equals(child)) {
-                return;
-            }
-        }
-        fail("Component array did not contain child.");
-    }
-    
-    /**
-     * Returns the value of the field on the specified object.  The name
-     * parameter is a <code>String</code> specifying the simple name of the
-     * desired field.<p>
-     *
-     * The object is first searched for any matching field.  If no matching
-     * field is found, the superclasses are recursively searched.
-     *
-     * @exception NoSuchFieldException if a field with the specified name is
-     * not found.
-     */
-    public static Object getField(Object object,
-                                  String name)
-            throws NoSuchFieldException {
-        if (object == null) {
-            throw new IllegalArgumentException("Invalid null object argument");
-        }
-        for (Class cls = object.getClass();
-             cls != null;
-             cls = cls.getSuperclass()) {
-            try {
-                Field field = cls.getDeclaredField(name);
-                field.setAccessible(true);
-                return field.get(object);
-            } catch (Exception ex) {
-                /* in case of an exception, we will throw a new
-                 * NoSuchFieldException object */
-                ;
-            }
-        }
-        throw new NoSuchFieldException("Could get value for field " +
-                object.getClass().getName() + "." + name);
-    }
-
-    /**
-     * Returns the value of the field on the specified class.  The name
-     * parameter is a <code>String</code> specifying the simple name of the
-     * desired field.<p>
-     *
-     * The class is first searched for any matching field.  If no matching
-     * field is found, the superclasses are recursively searched.
-     *
-     * @exception NoSuchFieldException if a field with the specified name is
-     * not found.
-     */
-    public static Object getField(Class cls,
-                                  String name)
-            throws NoSuchFieldException {
-        if (cls == null) {
-            throw new IllegalArgumentException("Invalid null cls argument");
-        }
-        Class base = cls;
-        while (base != null) {
-            try {
-                Field field = base.getDeclaredField(name);
-                field.setAccessible(true);
-                return field.get(base);
-            } catch (Exception ex) {
-                /* in case of an exception, we will throw a new
-                 * NoSuchFieldException object */
-                ;
-            }
-            base = base.getSuperclass();
-        }
-        throw new NoSuchFieldException("Could get value for static field " +
-                cls.getName() + "." + name);
-    }
-
+       
+       /**
+        * Test constructor.
+        *
+        * @param name the name of the test to run.
+        */
+       public RocksimTestBase(String name) {
+               super(name);
+       }
+       
+       public void assertContains(RocketComponent child, List<RocketComponent> components) {
+               assertTrue("Components did not contain child", components.contains(child));
+       }
+       
+       /**
+        * Returns the value of the field on the specified object.  The name
+        * parameter is a <code>String</code> specifying the simple name of the
+        * desired field.<p>
+        *
+        * The object is first searched for any matching field.  If no matching
+        * field is found, the superclasses are recursively searched.
+        *
+        * @exception NoSuchFieldException if a field with the specified name is
+        * not found.
+        */
+       public static Object getField(Object object,
+                                                                       String name)
+                       throws NoSuchFieldException {
+               if (object == null) {
+                       throw new IllegalArgumentException("Invalid null object argument");
+               }
+               for (Class cls = object.getClass(); cls != null; cls = cls.getSuperclass()) {
+                       try {
+                               Field field = cls.getDeclaredField(name);
+                               field.setAccessible(true);
+                               return field.get(object);
+                       } catch (Exception ex) {
+                               /* in case of an exception, we will throw a new
+                                * NoSuchFieldException object */
+                               ;
+                       }
+               }
+               throw new NoSuchFieldException("Could get value for field " +
+                               object.getClass().getName() + "." + name);
+       }
+       
+       /**
+        * Returns the value of the field on the specified class.  The name
+        * parameter is a <code>String</code> specifying the simple name of the
+        * desired field.<p>
+        *
+        * The class is first searched for any matching field.  If no matching
+        * field is found, the superclasses are recursively searched.
+        *
+        * @exception NoSuchFieldException if a field with the specified name is
+        * not found.
+        */
+       public static Object getField(Class cls,
+                                                                       String name)
+                       throws NoSuchFieldException {
+               if (cls == null) {
+                       throw new IllegalArgumentException("Invalid null cls argument");
+               }
+               Class base = cls;
+               while (base != null) {
+                       try {
+                               Field field = base.getDeclaredField(name);
+                               field.setAccessible(true);
+                               return field.get(base);
+                       } catch (Exception ex) {
+                               /* in case of an exception, we will throw a new
+                                * NoSuchFieldException object */
+                               ;
+                       }
+                       base = base.getSuperclass();
+               }
+               throw new NoSuchFieldException("Could get value for static field " +
+                               cls.getName() + "." + name);
+       }
+       
 
 }
diff --git a/test/net/sf/openrocket/gui/TestGUI.java b/test/net/sf/openrocket/gui/TestGUI.java
new file mode 100644 (file)
index 0000000..6e05aab
--- /dev/null
@@ -0,0 +1,12 @@
+package net.sf.openrocket.gui;
+
+import org.junit.Test;
+
+public class TestGUI {
+       
+       @Test
+       public void test() {
+               // No-op
+       }
+       
+}
index b2e6de5bfb72c49a1f16ad51877e8e1a3b4d2d94..4bd9b8d421421333674f2c86a2cc62a1b28037b8 100644 (file)
@@ -7,7 +7,7 @@ import java.util.List;
 import org.junit.Test;
 
 public class LogLevelBufferLoggerTest {
-
+       
        @Test
        public void testLogger() {
                LogLevelBufferLogger logger = new LogLevelBufferLogger(4);
@@ -39,10 +39,10 @@ public class LogLevelBufferLoggerTest {
                assertEquals("user 1", list.get(0).getMessage());
                assertEquals("warn 1", list.get(1).getMessage());
                assertEquals("user 2", list.get(2).getMessage());
-               assertEquals("--- 2 INFO lines removed ---", list.get(3).getMessage());
+               assertEquals("===== 2 INFO lines removed =====", list.get(3).getMessage());
                assertEquals("info 3", list.get(4).getMessage());
                assertEquals("error 1", list.get(5).getMessage());
-               assertEquals("--- 4 DEBUG lines removed ---", list.get(6).getMessage());
+               assertEquals("===== 4 DEBUG lines removed =====", list.get(6).getMessage());
                assertEquals("debug 5", list.get(7).getMessage());
                assertEquals("warn 2", list.get(8).getMessage());
                assertEquals("debug 6", list.get(9).getMessage());
index d9cffa98254ade268a75e432d4ac0a52b2a9e23a..7ca43d7166ec35e9efbb80da8c82c598bef846dd 100644 (file)
@@ -12,7 +12,7 @@ public class ThrustCurveMotorTest {
 
        private final double radius = 0.025;
        private final double length = 0.10;
-       private final double longitudal = Inertia.filledCylinderLongitudal(radius, length);
+       private final double longitudinal = Inertia.filledCylinderLongitudinal(radius, length);
        private final double rotational = Inertia.filledCylinderRotational(radius);
        
        private final ThrustCurveMotor motor = 
@@ -66,7 +66,7 @@ public class ThrustCurveMotorTest {
                assertEquals("Testing thrust", thrust, instance.getThrust(), EPS);
                assertEquals("Testing mass", mass, instance.getCG().weight, EPS);
                assertEquals("Testing cg x", cgx, instance.getCG().x, EPS);
-               assertEquals("Testing longitudal inertia", mass*longitudal, instance.getLongitudalInertia(), EPS);
+               assertEquals("Testing longitudinal inertia", mass*longitudinal, instance.getLongitudinalInertia(), EPS);
                assertEquals("Testing rotational inertia", mass*rotational, instance.getRotationalInertia(), EPS);
        }
                        
diff --git a/test/net/sf/openrocket/optimization/rocketoptimization/modifiers/TestGenericModifier.java b/test/net/sf/openrocket/optimization/rocketoptimization/modifiers/TestGenericModifier.java
new file mode 100644 (file)
index 0000000..68517d9
--- /dev/null
@@ -0,0 +1,100 @@
+package net.sf.openrocket.optimization.rocketoptimization.modifiers;
+
+import static net.sf.openrocket.util.MathUtil.EPSILON;
+import static org.junit.Assert.assertEquals;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.unit.UnitGroup;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestGenericModifier {
+       
+       private TestValue value;
+       private GenericModifier<TestValue> gm;
+       private Simulation sim;
+       
+       @Before
+       public void setup() {
+               value = new TestValue();
+               sim = new Simulation(new Rocket());
+               
+               gm = new GenericModifier<TestGenericModifier.TestValue>("Test modifier", null,
+                               UnitGroup.UNITS_NONE, 2.0, TestValue.class, "value") {
+                       @Override
+                       protected TestValue getModifiedObject(Simulation simulation) {
+                               Assert.assertTrue(simulation == sim);
+                               return value;
+                       }
+               };
+               
+               gm.setMinValue(0.5);
+               gm.setMaxValue(5.5);
+       }
+       
+       @Test
+       public void testGetCurrentValue() throws OptimizationException {
+               value.d = 1.0;
+               assertEquals(2.0, gm.getCurrentValue(sim), EPSILON);
+               value.d = 2.0;
+               assertEquals(4.0, gm.getCurrentValue(sim), EPSILON);
+       }
+       
+       @Test
+       public void testGetCurrentScaledValue() throws OptimizationException {
+               value.d = 0.0;
+               assertEquals(-0.1, gm.getCurrentScaledValue(sim), EPSILON);
+               value.d = 1.0;
+               assertEquals(0.3, gm.getCurrentScaledValue(sim), EPSILON);
+               value.d = 2.0;
+               assertEquals(0.7, gm.getCurrentScaledValue(sim), EPSILON);
+               value.d = 3.0;
+               assertEquals(1.1, gm.getCurrentScaledValue(sim), EPSILON);
+       }
+       
+       @Test
+       public void testModify() throws OptimizationException {
+               value.d = 0.0;
+               gm.modify(sim, -0.5);
+               assertEquals(-1.0, value.d, EPSILON);
+               
+               gm.modify(sim, 0.0);
+               assertEquals(0.25, value.d, EPSILON);
+               
+               gm.modify(sim, 0.5);
+               assertEquals(1.5, value.d, EPSILON);
+               
+               gm.modify(sim, 1.0);
+               assertEquals(2.75, value.d, EPSILON);
+               
+               gm.modify(sim, 1.5);
+               assertEquals(4.0, value.d, EPSILON);
+       }
+       
+       public void testSingularRange() throws OptimizationException {
+               gm.setMinValue(1.0);
+               gm.setMaxValue(1.0);
+               value.d = 0.5;
+               assertEquals(0.0, gm.getCurrentScaledValue(sim), EPSILON);
+               value.d = 1.0;
+               assertEquals(0.5, gm.getCurrentScaledValue(sim), EPSILON);
+               value.d = 1.00001;
+               assertEquals(1.0, gm.getCurrentScaledValue(sim), EPSILON);
+       }
+       
+       public class TestValue {
+               private double d;
+               
+               public double getValue() {
+                       return d;
+               }
+               
+               public void setValue(double value) {
+                       this.d = value;
+               }
+       }
+       
+}
index d0a2374b18210af91ccf766b510337e3c3a623f0..fb231dc0497e07d2fdbb634d7363f620fe00a59c 100644 (file)
@@ -42,8 +42,8 @@ public class ComponentCompare {
        public static void assertDeepEquality(RocketComponent c1, RocketComponent c2) {
                assertEquality(c1, c2);
                
-               Iterator<RocketComponent> i1 = c1.iterator();
-               Iterator<RocketComponent> i2 = c2.iterator();
+               Iterator<RocketComponent> i1 = c1.getChildren().iterator();
+               Iterator<RocketComponent> i2 = c2.getChildren().iterator();
                while (i1.hasNext()) {
                        assertTrue("iterator continues", i2.hasNext());
                        RocketComponent comp1 = i1.next();
@@ -59,8 +59,8 @@ public class ComponentCompare {
                        boolean allowNameDifference) {
                assertSimilarity(c1, c2, allowNameDifference);
                
-               Iterator<RocketComponent> i1 = c1.iterator();
-               Iterator<RocketComponent> i2 = c2.iterator();
+               Iterator<RocketComponent> i1 = c1.getChildren().iterator();
+               Iterator<RocketComponent> i2 = c2.getChildren().iterator();
                while (i1.hasNext()) {
                        assertTrue("iterator continues", i2.hasNext());
                        RocketComponent comp1 = i1.next();
index de97c9778fa261c3cb9ca0eed986c1d778166367..11acf71aedeab5bf0f14378ea94d7efd2387e479 100644 (file)
@@ -19,8 +19,8 @@ public class ComponentCompareTest {
                Rocket r1 = net.sf.openrocket.util.TestRockets.makeBigBlue();
                Rocket r2 = net.sf.openrocket.util.TestRockets.makeBigBlue();
 
-               Iterator<RocketComponent> i1 = r1.deepIterator(true);
-               Iterator<RocketComponent> i2 = r2.deepIterator(true);
+               Iterator<RocketComponent> i1 = r1.iterator(true);
+               Iterator<RocketComponent> i2 = r2.iterator(true);
                while (i1.hasNext()) {
                        assertTrue(i2.hasNext());
                        
@@ -46,8 +46,8 @@ public class ComponentCompareTest {
                }
                
                
-               i1 = r1.deepIterator(true);
-               i2 = r2.deepIterator(true);
+               i1 = r1.iterator(true);
+               i2 = r2.iterator(true);
                boolean finsetfound = false;
                while (i1.hasNext()) {
                        RocketComponent c1 = i1.next();
index dd01848dd8d33968dcac39afbfe3bb794c132968..94d5a9f58cf884d55c588b8b373de13102718125 100644 (file)
@@ -2,13 +2,19 @@ package net.sf.openrocket.util;
 
 import static org.junit.Assert.*;
 
+import org.junit.Before;
 import org.junit.Test;
 
 public class TestMutex {
        
+       @Before
+       public void setup() {
+               System.setProperty("openrocket.debug.safetycheck", "true");
+       }
+       
        @Test
        public void testSingleLocking() {
-               SafetyMutex m = new SafetyMutex();
+               SafetyMutex.ConcreteSafetyMutex m = new SafetyMutex.ConcreteSafetyMutex();
                
                // Test single locking
                assertNull(m.lockingThread);
@@ -21,7 +27,7 @@ public class TestMutex {
        
        @Test
        public void testDoubleLocking() {
-               SafetyMutex m = new SafetyMutex();
+               SafetyMutex.ConcreteSafetyMutex m = new SafetyMutex.ConcreteSafetyMutex();
                
                // Test double locking
                m.verify();
@@ -37,9 +43,9 @@ public class TestMutex {
        
        @Test
        public void testDoubleUnlocking() {
-               SafetyMutex m = new SafetyMutex();
+               SafetyMutex.ConcreteSafetyMutex m = new SafetyMutex.ConcreteSafetyMutex();
                // Mark error reported to not init exception handler
-               SafetyMutex.errorReported = true;
+               SafetyMutex.ConcreteSafetyMutex.errorReported = true;
                
                m.lock("here");
                assertTrue(m.unlock("here"));
@@ -53,7 +59,7 @@ public class TestMutex {
        
        @Test(timeout = 1000)
        public void testThreadingErrors() {
-               final SafetyMutex m = new SafetyMutex();
+               final SafetyMutex.ConcreteSafetyMutex m = new SafetyMutex.ConcreteSafetyMutex();
                
                // Initialize and start the thread
                Thread thread = new Thread() {
@@ -153,4 +159,17 @@ public class TestMutex {
                }
        }
        
+       
+       public void testBogusMutex() {
+               SafetyMutex m = new SafetyMutex.BogusSafetyMutex();
+               m.lock("foo");
+               m.lock("bar");
+               m.lock("baz");
+               m.verify();
+               m.unlock("a");
+               m.unlock(null);
+               m.unlock("");
+               m.unlock("c");
+       }
+       
 }