From: plaa Date: Sun, 9 Jan 2011 09:01:02 +0000 (+0000) Subject: SafetyMutex and rocket optimization updates X-Git-Tag: upstream/1.1.4^2~9 X-Git-Url: https://git.gag.com/?a=commitdiff_plain;h=f8f9a3cc430a2d4893de6157e483889439f33cc3;p=debian%2Fopenrocket SafetyMutex and rocket optimization updates git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@96 180e2498-e6e9-4542-8430-84ac67f01cd8 --- diff --git a/.classpath b/.classpath index 4a68d2c2..dafa0a18 100644 --- a/.classpath +++ b/.classpath @@ -18,5 +18,11 @@ + + + + + + diff --git a/ChangeLog b/ChangeLog index efbb9793..dfe86bb8 100644 --- 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 f5ceebe8..f3f97c5b 100644 --- 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: diff --git a/build.xml b/build.xml index 0540b3dc..ac382d24 100644 --- a/build.xml +++ b/build.xml @@ -36,11 +36,7 @@ - - - - - + @@ -77,6 +73,7 @@ + diff --git a/doc/properties.txt b/doc/properties.txt index cbddc55f..77c0501e 100644 --- a/doc/properties.txt +++ b/doc/properties.txt @@ -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 index 00000000..0071165b --- /dev/null +++ b/l10n/messages.properties @@ -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 index 00000000..eb396763 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 index 00000000..ed956531 Binary files /dev/null and b/lib/iText-5.0.2.jar differ diff --git a/pix/icons/copyright.txt b/pix/icons/copyright.txt index 8b8e2670..c70dca5d 100644 --- a/pix/icons/copyright.txt +++ b/pix/icons/copyright.txt @@ -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 index 00000000..3a875432 Binary files /dev/null and b/pix/icons/document-print.png differ diff --git a/src/net/sf/openrocket/aerodynamics/AbstractAerodynamicCalculator.java b/src/net/sf/openrocket/aerodynamics/AbstractAerodynamicCalculator.java index cf730b6a..a92d0ab4 100644 --- a/src/net/sf/openrocket/aerodynamics/AbstractAerodynamicCalculator.java +++ b/src/net/sf/openrocket/aerodynamics/AbstractAerodynamicCalculator.java @@ -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 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(); } } diff --git a/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index 4618fc87..4b243389 100644 --- a/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -725,7 +725,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { calcMap = new HashMap(); - iterator = configuration.getRocket().deepIterator(); + iterator = configuration.getRocket().iterator(); while (iterator.hasNext()) { RocketComponent c = iterator.next(); diff --git a/src/net/sf/openrocket/aerodynamics/WarningSet.java b/src/net/sf/openrocket/aerodynamics/WarningSet.java index 40eab61b..c4e81c62 100644 --- a/src/net/sf/openrocket/aerodynamics/WarningSet.java +++ b/src/net/sf/openrocket/aerodynamics/WarningSet.java @@ -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 implements Cloneable, Monit } - @SuppressWarnings("unchecked") @Override public WarningSet clone() { try { WarningSet newSet = (WarningSet) super.clone(); - newSet.warnings = (ArrayList) this.warnings.clone(); + newSet.warnings = this.warnings.clone(); newSet.mutable = this.mutable.clone(); return newSet; diff --git a/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java b/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java index 46bc327a..2b3d7a6a 100644 --- a/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java +++ b/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java @@ -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= 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 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 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; } } diff --git a/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java b/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java index 4f74bf1e..f1f73f60 100644 --- a/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java +++ b/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java @@ -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 diff --git a/src/net/sf/openrocket/communication/UpdateInfo.java b/src/net/sf/openrocket/communication/UpdateInfo.java index a9b0d044..99cf30b6 100644 --- a/src/net/sf/openrocket/communication/UpdateInfo.java +++ b/src/net/sf/openrocket/communication/UpdateInfo.java @@ -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> updates; @@ -22,8 +22,8 @@ public class UpdateInfo { this.latestVersion = version; this.updates = new ArrayList>(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> getUpdates() { - return (List>) updates.clone(); + return updates.clone(); } @Override diff --git a/src/net/sf/openrocket/database/ThrustCurveMotorSet.java b/src/net/sf/openrocket/database/ThrustCurveMotorSet.java index dfd7cb55..99c422e3 100644 --- a/src/net/sf/openrocket/database/ThrustCurveMotorSet.java +++ b/src/net/sf/openrocket/database/ThrustCurveMotorSet.java @@ -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 { @@ -153,9 +153,8 @@ public class ThrustCurveMotorSet implements Comparable { } - @SuppressWarnings("unchecked") public List getMotors() { - return (List) motors.clone(); + return motors.clone(); } diff --git a/src/net/sf/openrocket/document/OpenRocketDocument.java b/src/net/sf/openrocket/document/OpenRocketDocument.java index 02a91e82..25b149e4 100644 --- a/src/net/sf/openrocket/document/OpenRocketDocument.java +++ b/src/net/sf/openrocket/document/OpenRocketDocument.java @@ -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 getSimulations() { - return (ArrayList) 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: diff --git a/src/net/sf/openrocket/document/Simulation.java b/src/net/sf/openrocket/document/Simulation.java index 9d5d0cd3..7b4e0f25 100644 --- a/src/net/sf/openrocket/document/Simulation.java +++ b/src/net/sf/openrocket/document/Simulation.java @@ -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) this.simulationListeners.clone(); + copy.simulationListeners = this.simulationListeners.clone(); copy.listeners = new ArrayList(); 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) this.simulationListeners.clone(); + copy.simulationListeners = this.simulationListeners.clone(); copy.simulationStepperClass = this.simulationStepperClass; copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass; diff --git a/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index 7cadcf4b..85085259 100644 --- a/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -130,7 +130,7 @@ public class OpenRocketSaver extends RocketSaver { // Size per component int componentCount = 0; Rocket rocket = doc.getRocket(); - Iterator iterator = rocket.deepIterator(true); + Iterator 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 iterator = document.getRocket().deepIterator(); + Iterator 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(""); 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(); diff --git a/src/net/sf/openrocket/file/simplesax/DelegatorHandler.java b/src/net/sf/openrocket/file/simplesax/DelegatorHandler.java index 08b9ef0a..3ec6bdc1 100644 --- a/src/net/sf/openrocket/file/simplesax/DelegatorHandler.java +++ b/src/net/sf/openrocket/file/simplesax/DelegatorHandler.java @@ -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 handlerStack = new Stack(); - private final Stack elementData = new Stack(); - private final Stack> elementAttributes = - new Stack>(); - + + private final Deque handlerStack = new ArrayDeque(); + private final Deque elementData = new ArrayDeque(); + private final Deque> elementAttributes = new ArrayDeque>(); + // 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 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 copyAttributes(Attributes atts) { HashMap ret = new HashMap(); for (int i = 0; i < atts.getLength(); i++) { @@ -119,4 +119,3 @@ class DelegatorHandler extends DefaultHandler { return ret; } } - diff --git a/src/net/sf/openrocket/gui/adaptors/BooleanModel.java b/src/net/sf/openrocket/gui/adaptors/BooleanModel.java index 2aa651c6..a88f4b89 100644 --- a/src/net/sf/openrocket/gui/adaptors/BooleanModel.java +++ b/src/net/sf/openrocket/gui/adaptors/BooleanModel.java @@ -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 */ -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) { diff --git a/src/net/sf/openrocket/gui/adaptors/DoubleModel.java b/src/net/sf/openrocket/gui/adaptors/DoubleModel.java index a81381dc..7b3783a0 100644 --- a/src/net/sf/openrocket/gui/adaptors/DoubleModel.java +++ b/src/net/sf/openrocket/gui/adaptors/DoubleModel.java @@ -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 */ -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) diff --git a/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java b/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java index e37d80ee..4b4db2d8 100644 --- a/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java +++ b/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java @@ -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(); - } - } - } diff --git a/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java b/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java index ccc1174f..c4e85985 100644 --- a/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/FinSetConfig.java @@ -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."); diff --git a/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java b/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java index 691fcf7f..751aa86b 100644 --- a/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java @@ -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("Split the cluster into separate components.
" + @@ -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 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); diff --git a/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java b/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java index 4bca3bd6..d6d60746 100644 --- a/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java @@ -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 invalidatables = new ArrayList(); + 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 = "The component finish affects the aerodynamic drag of the " - +"component.
" - + "The value indicated is the average roughness height of the surface."; + String tip = "The component finish affects the aerodynamic drag of the " + + "component.
" + + "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(component,"Finish")); + combo = new JComboBox(new EnumModel(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 iter = rocket.deepIterator(); + Iterator 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 iterator = component.deepIterator(); + Iterator 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("The overridden mass does not include motors.
" + "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(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(); + } + } + } diff --git a/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java b/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java index 927cc548..e3e9d575 100644 --- a/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java @@ -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()); diff --git a/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java b/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java index a4390cf4..782afb76 100644 --- a/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java @@ -67,7 +67,7 @@ public class EditMotorConfigurationDialog extends JDialog { this.rocket = rocket; ArrayList mountList = new ArrayList(); - Iterator iterator = rocket.deepIterator(); + Iterator iterator = rocket.iterator(); while (iterator.hasNext()) { RocketComponent c = iterator.next(); if (c instanceof MotorMount) { diff --git a/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java b/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java index 58f5664b..f0861208 100644 --- a/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java @@ -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 diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java index 3c8f4e2c..9d79c13b 100644 --- a/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -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 objects = MemoryManagement.getRemainingObjects(); + List 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. */ diff --git a/src/net/sf/openrocket/gui/main/ExceptionHandler.java b/src/net/sf/openrocket/gui/main/ExceptionHandler.java index 9fbc9a84..cd4af70b 100644 --- a/src/net/sf/openrocket/gui/main/ExceptionHandler.java +++ b/src/net/sf/openrocket/gui/main/ExceptionHandler.java @@ -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); diff --git a/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java b/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java index f35222c1..8cf58282 100644 --- a/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java +++ b/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java @@ -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 iterator = root.deepIterator(); + Iterator iterator = root.iterator(false); while (iterator.hasNext()) { tree.makeVisible(makeTreePath(iterator.next())); } diff --git a/src/net/sf/openrocket/gui/plot/PlotConfiguration.java b/src/net/sf/openrocket/gui/plot/PlotConfiguration.java index ebe5e96c..1cf5e974 100644 --- a/src/net/sf/openrocket/gui/plot/PlotConfiguration.java +++ b/src/net/sf/openrocket/gui/plot/PlotConfiguration.java @@ -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 plotDataTypes = new ArrayList(); @@ -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 plotDataAxes = new ArrayList(); - + private EnumSet 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 allAxes = new ArrayList(); - - + + 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 getActiveEvents() { - return (Set) events.clone(); + return events.clone(); } public void setEvent(FlightEvent.Type type, boolean active) { @@ -308,9 +310,9 @@ public class PlotConfiguration implements Cloneable { } - - - + + + public List getAllAxes() { List list = new ArrayList(); 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(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 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 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) this.plotDataTypes.clone(); - copy.plotDataAxes = (ArrayList) this.plotDataAxes.clone(); - copy.plotDataUnits = (ArrayList) 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(); - 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()."); } diff --git a/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index ed1293e0..774ad152 100644 --- a/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -105,8 +105,8 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change private SimulationWorker backgroundSimulationWorker = null; + private boolean dirty = false; - private List listeners = new ArrayList(); diff --git a/src/net/sf/openrocket/l10n/DebugTranslator.java b/src/net/sf/openrocket/l10n/DebugTranslator.java new file mode 100644 index 00000000..19e6c84a --- /dev/null +++ b/src/net/sf/openrocket/l10n/DebugTranslator.java @@ -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 + */ +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 index 00000000..5bfdbd28 --- /dev/null +++ b/src/net/sf/openrocket/l10n/ResourceBundleTranslator.java @@ -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. + *

+ * If a message is not found in any resource bundle, an error is logged and the key itself + * is returned. + * + * @author Sampo Niskanen + */ +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 index 00000000..c95d7bec --- /dev/null +++ b/src/net/sf/openrocket/l10n/Translator.java @@ -0,0 +1,22 @@ +package net.sf.openrocket.l10n; + +/** + * An interface for obtaining translations from logical keys. + *

+ * Translator implementations must be thread-safe. + * + * @author Sampo Niskanen + */ +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); + +} diff --git a/src/net/sf/openrocket/logging/DelegatorLogger.java b/src/net/sf/openrocket/logging/DelegatorLogger.java index 45ff9e55..8c3a22df 100644 --- a/src/net/sf/openrocket/logging/DelegatorLogger.java +++ b/src/net/sf/openrocket/logging/DelegatorLogger.java @@ -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 */ 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 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 newList = (ArrayList) loggers.clone(); + ArrayList 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 newList = (ArrayList) loggers.clone(); + ArrayList newList = loggers.clone(); newList.remove(logger); this.loggers = newList; } - + } diff --git a/src/net/sf/openrocket/logging/TraceException.java b/src/net/sf/openrocket/logging/TraceException.java index b08109a3..01029d7d 100644 --- a/src/net/sf/openrocket/logging/TraceException.java +++ b/src/net/sf/openrocket/logging/TraceException.java @@ -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. */ diff --git a/src/net/sf/openrocket/masscalc/AbstractMassCalculator.java b/src/net/sf/openrocket/masscalc/AbstractMassCalculator.java index cfcf496b..30b53de6 100644 --- a/src/net/sf/openrocket/masscalc/AbstractMassCalculator.java +++ b/src/net/sf/openrocket/masscalc/AbstractMassCalculator.java @@ -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 */ 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(); } } diff --git a/src/net/sf/openrocket/masscalc/BasicMassCalculator.java b/src/net/sf/openrocket/masscalc/BasicMassCalculator.java index 38d7b218..4b5d8276 100644 --- a/src/net/sf/openrocket/masscalc/BasicMassCalculator.java +++ b/src/net/sf/openrocket/masscalc/BasicMassCalculator.java @@ -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; } diff --git a/src/net/sf/openrocket/masscalc/MassCalculator.java b/src/net/sf/openrocket/masscalc/MassCalculator.java index 732d2ec7..003650e1 100644 --- a/src/net/sf/openrocket/masscalc/MassCalculator.java +++ b/src/net/sf/openrocket/masscalc/MassCalculator.java @@ -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. diff --git a/src/net/sf/openrocket/models/gravity/BasicGravityModel.java b/src/net/sf/openrocket/models/gravity/BasicGravityModel.java index 7811227f..a216528b 100644 --- a/src/net/sf/openrocket/models/gravity/BasicGravityModel.java +++ b/src/net/sf/openrocket/models/gravity/BasicGravityModel.java @@ -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); diff --git a/src/net/sf/openrocket/motor/MotorInstance.java b/src/net/sf/openrocket/motor/MotorInstance.java index 0122700f..6b48e3a4 100644 --- a/src/net/sf/openrocket/motor/MotorInstance.java +++ b/src/net/sf/openrocket/motor/MotorInstance.java @@ -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. diff --git a/src/net/sf/openrocket/motor/ThrustCurveMotor.java b/src/net/sf/openrocket/motor/ThrustCurveMotor.java index 2bbddd14..a51283ea 100644 --- a/src/net/sf/openrocket/motor/ThrustCurveMotor.java +++ b/src/net/sf/openrocket/motor/ThrustCurveMotor.java @@ -393,7 +393,7 @@ public class ThrustCurveMotor implements Motor, Comparable { 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 { 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 { } @Override - public double getLongitudalInertia() { - return unitLongitudalInertia * stepCG.weight; + public double getLongitudinalInertia() { + return unitLongitudinalInertia * stepCG.weight; } @Override diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/SimulationModifier.java b/src/net/sf/openrocket/optimization/rocketoptimization/SimulationModifier.java index 147ac0d4..abaac19b 100644 --- a/src/net/sf/openrocket/optimization/rocketoptimization/SimulationModifier.java +++ b/src/net/sf/openrocket/optimization/rocketoptimization/SimulationModifier.java @@ -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]. + *

+ * The implementation must fire change events when the minimum and maximum ranges + * are modified, NOT when the actual modified value changes. * * @author Sampo Niskanen */ @@ -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 index 00000000..04ba7bce --- /dev/null +++ b/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/AbstractSimulationModifier.java @@ -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 + */ +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 listeners = new ArrayList(); + + + /** + * 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 index 00000000..c05454cd --- /dev/null +++ b/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericComponentModifier.java @@ -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 + */ +public class GenericComponentModifier extends GenericModifier { + + private final Class 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 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; + } + +} diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericModifier.java b/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericModifier.java index ac62dc62..6df31241 100644 --- a/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericModifier.java +++ b/src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericModifier.java @@ -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 + */ +public abstract class GenericModifier 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 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 index 00000000..8bfd55c1 --- /dev/null +++ b/src/net/sf/openrocket/optimization/rocketoptimization/services/DefaultSimulationModifierService.java @@ -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 getModifiers(OpenRocketDocument document) { + // TODO: Should this really be OpenRocketDocument instead of Simulation? + List list = new ArrayList(); + + // TODO: implement + + + return null; + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/src/net/sf/openrocket/rocketcomponent/BodyTube.java index 5656c72b..f45f3526 100644 --- a/src/net/sf/openrocket/rocketcomponent/BodyTube.java +++ b/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -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; diff --git a/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java b/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java index 1f60917e..de5b78d2 100644 --- a/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java +++ b/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java @@ -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; } diff --git a/src/net/sf/openrocket/rocketcomponent/Configuration.java b/src/net/sf/openrocket/rocketcomponent/Configuration.java index ca3e58fb..9ab9c0b3 100644 --- a/src/net/sf/openrocket/rocketcomponent/Configuration.java +++ b/src/net/sf/openrocket/rocketcomponent/Configuration.java @@ -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 Collection containing coordinates bouding the rocket. */ - @SuppressWarnings("unchecked") public Collection getBounds() { if (rocket.getModID() != boundsModID) { boundsModID = rocket.getModID(); @@ -285,7 +284,7 @@ public class Configuration implements Cloneable, ChangeSource, ComponentChangeLi cachedLength = maxX - minX; } } - return (ArrayList) 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)); } } diff --git a/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java b/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java index 8a120904..6fa4a4c2 100644 --- a/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java @@ -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 copyFrom(RocketComponent c) { + ExternalComponent src = (ExternalComponent) c; this.finish = src.finish; this.material = src.material; + return super.copyFrom(c); } /** diff --git a/src/net/sf/openrocket/rocketcomponent/FinSet.java b/src/net/sf/openrocket/rocketcomponent/FinSet.java index ea60d417..8d6a77f4 100644 --- a/src/net/sf/openrocket/rocketcomponent/FinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -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 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); } } diff --git a/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java index ce640215..24ca9811 100644 --- a/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java @@ -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 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 copy = (ArrayList) this.points.clone(); + ArrayList 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) 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 points) throws IllegalFinPointException { final int n = points.size(); diff --git a/src/net/sf/openrocket/rocketcomponent/LaunchLug.java b/src/net/sf/openrocket/rocketcomponent/LaunchLug.java index 500e9063..a51629a3 100644 --- a/src/net/sf/openrocket/rocketcomponent/LaunchLug.java +++ b/src/net/sf/openrocket/rocketcomponent/LaunchLug.java @@ -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; diff --git a/src/net/sf/openrocket/rocketcomponent/MassObject.java b/src/net/sf/openrocket/rocketcomponent/MassObject.java index 0e490306..bbd9789d 100644 --- a/src/net/sf/openrocket/rocketcomponent/MassObject.java +++ b/src/net/sf/openrocket/rocketcomponent/MassObject.java @@ -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 */ 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 getComponentBounds () { - Collection c = new ArrayList(); - 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 getComponentBounds() { + Collection c = new ArrayList(); + 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); + } + } diff --git a/src/net/sf/openrocket/rocketcomponent/RingComponent.java b/src/net/sf/openrocket/rocketcomponent/RingComponent.java index 0450b415..b6c6cec6 100644 --- a/src/net/sf/openrocket/rocketcomponent/RingComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/RingComponent.java @@ -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 diff --git a/src/net/sf/openrocket/rocketcomponent/Rocket.java b/src/net/sf/openrocket/rocketcomponent/Rocket.java index 509c9ff5..3a284534 100644 --- a/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -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) this.motorConfigurationIDs.clone(); + copy.motorConfigurationIDs = this.motorConfigurationIDs.clone(); copy.motorConfigurationNames = (HashMap) 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 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) r.motorConfigurationIDs.clone(); + this.motorConfigurationIDs = r.motorConfigurationIDs.clone(); this.motorConfigurationNames = (HashMap) 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 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 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 iterator = this.deepIterator(); + Iterator iterator = this.iterator(); while (iterator.hasNext()) { RocketComponent c = iterator.next(); @@ -660,7 +675,7 @@ public class Rocket extends RocketComponent { List> list = new ArrayList>(); List currentList = null; - Iterator iterator = this.deepIterator(); + Iterator 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 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); + } } diff --git a/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 02d4c1d0..8573caa6 100644 --- a/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -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 , Visitable { +public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable, + Visitable { 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 children = new ArrayList(); + private ArrayList children = new ArrayList(); //////// 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 iterator = clone.deepIterator(true); + Iterator 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(); - - // 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(); + + // 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 iter = component.deepIterator(true); + Iterator 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 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 iter = this.deepIterator(true); + Iterator 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 Rocket */ + @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. + *

+ * TODO: Remove this after the "inconsistent internal state" bug has been corrected */ - private class RocketComponentIterator implements Iterator { - // Stack holds iterators which still have some components left. - private final Stack> iteratorstack = - new Stack>(); - - 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 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 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 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. - * + *

* The iterator iterates through all children below this object, including itself if - * returnSelf is true. The order of the iteration is not specified + * returnSelf is true. The order of the iteration is not specified * (it may be specified in the future). - * + *

* If an iterator iterating over only the direct children of the component is required, - * use component.getChildren().iterator() + * use component.getChildren().iterator(). + * + * 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 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. + *

+ * This method is equivalent to deepIterator(true). * - * 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 deepIterator() { + return iterator(); + } + + + + /** + * Returns an iterator that iterates over all children and sub-children. + *

+ * The iterator iterates through all children below this object, including itself if + * returnSelf is true. The order of the iteration is not specified + * (it may be specified in the future). + *

+ * If an iterator iterating over only the direct children of the component is required, + * use component.getChildren().iterator(). + * + * @param returnSelf boolean value specifying whether the component itself should be + * returned + * @return An iterator for the children and sub-children. + */ + public final Iterator 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. + *

+ * This method is equivalent to iterator(true). * - * @return An iterator for the children. + * @return An iterator for this component, its children and sub-children. */ + @Override public final Iterator 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. *

- * 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()}. + *

+ * 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 src. * - * 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 copyFrom(RocketComponent src) { checkState(); + List toInvalidate = new ArrayList(); 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(); + // Add current structure to be invalidated + Iterator 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 { + // Stack holds iterators which still have some components left. + private final Deque> iteratorStack = new ArrayDeque>(); + + 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 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 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"); + } } } diff --git a/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java b/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java index e25ba691..ac8318de 100644 --- a/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java @@ -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; } diff --git a/src/net/sf/openrocket/rocketcomponent/Visitable.java b/src/net/sf/openrocket/rocketcomponent/Visitable.java index cb516a5e..5f46b7ff 100644 --- a/src/net/sf/openrocket/rocketcomponent/Visitable.java +++ b/src/net/sf/openrocket/rocketcomponent/Visitable.java @@ -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. visit 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; * The visitable (the concrete class that implements this interface) */ public interface Visitable, T extends Visitable> { - - /** - * 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); + } diff --git a/src/net/sf/openrocket/rocketcomponent/Visitor.java b/src/net/sf/openrocket/rocketcomponent/Visitor.java index 2e689ea6..edb39f2b 100644 --- a/src/net/sf/openrocket/rocketcomponent/Visitor.java +++ b/src/net/sf/openrocket/rocketcomponent/Visitor.java @@ -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. visit 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; * The visitable */ public interface Visitor, T extends Visitable> { - - /** - * The callback method. This method is the 2nd leg of the double-dispatch, having been invoked from a - * corresponding accept. - * - * @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 accept. + * + * @param visitable the instance of the Visitable (the target of what is being visiting) + */ + void visit(T visitable); } \ No newline at end of file diff --git a/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java b/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java index 348feaa5..7b33ae04 100644 --- a/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java +++ b/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java @@ -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; diff --git a/src/net/sf/openrocket/simulation/FlightDataBranch.java b/src/net/sf/openrocket/simulation/FlightDataBranch.java index f8772bb3..f3eb3d9c 100644 --- a/src/net/sf/openrocket/simulation/FlightDataBranch.java +++ b/src/net/sf/openrocket/simulation/FlightDataBranch.java @@ -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 null if * the variable type hasn't been added to this branch. */ - @SuppressWarnings("unchecked") public List get(FlightDataType type) { ArrayList list = values.get(type); if (list == null) return null; - return (List) 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 getEvents() { - return (List) events.clone(); + return events.clone(); } diff --git a/src/net/sf/openrocket/simulation/FlightDataType.java b/src/net/sf/openrocket/simulation/FlightDataType.java index 8c4f45d4..a089d4c1 100644 --- a/src/net/sf/openrocket/simulation/FlightDataType.java +++ b/src/net/sf/openrocket/simulation/FlightDataType.java @@ -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. *

- * 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 */ @@ -21,46 +24,34 @@ public class FlightDataType implements Comparable { private static final int DEFAULT_PRIORITY = 999; /** List of existing types. MUST BE DEFINED BEFORE ANY TYPES!! */ - private static final List EXISTING_TYPES = new ArrayList(); + private static final Map EXISTING_TYPES = new HashMap(); //// 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 { //// 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 { * @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 { */ 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 { 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 diff --git a/src/net/sf/openrocket/simulation/MassData.java b/src/net/sf/openrocket/simulation/MassData.java index 37ea4fe1..1d910df0 100644 --- a/src/net/sf/openrocket/simulation/MassData.java +++ b/src/net/sf/openrocket/simulation/MassData.java @@ -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 + "]"; } diff --git a/src/net/sf/openrocket/simulation/RK4SimulationStepper.java b/src/net/sf/openrocket/simulation/RK4SimulationStepper.java index 97d934cf..6f2c2387 100644 --- a/src/net/sf/openrocket/simulation/RK4SimulationStepper.java +++ b/src/net/sf/openrocket/simulation/RK4SimulationStepper.java @@ -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; diff --git a/src/net/sf/openrocket/simulation/listeners/example/CSVSaveListener.java b/src/net/sf/openrocket/simulation/listeners/example/CSVSaveListener.java index dab946eb..05a55fa2 100644 --- a/src/net/sf/openrocket/simulation/listeners/example/CSVSaveListener.java +++ b/src/net/sf/openrocket/simulation/listeners/example/CSVSaveListener.java @@ -185,7 +185,7 @@ public class CSVSaveListener extends AbstractSimulationListener { @Override public double getValue(SimulationStatus status) { Iterator iterator = - status.getConfiguration().getRocket().deepIterator(); + status.getConfiguration().getRocket().iterator(); FinSet fin = null; while (iterator.hasNext()) { diff --git a/src/net/sf/openrocket/startup/Application.java b/src/net/sf/openrocket/startup/Application.java index b8ef7834..020d714e 100644 --- a/src/net/sf/openrocket/startup/Application.java +++ b/src/net/sf/openrocket/startup/Application.java @@ -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. */ diff --git a/src/net/sf/openrocket/startup/Startup.java b/src/net/sf/openrocket/startup/Startup.java index 658f70c0..101e068a 100644 --- a/src/net/sf/openrocket/startup/Startup.java +++ b/src/net/sf/openrocket/startup/Startup.java @@ -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); } diff --git a/src/net/sf/openrocket/unit/CaliberUnit.java b/src/net/sf/openrocket/unit/CaliberUnit.java index 4d9784c8..c33c4b6f 100644 --- a/src/net/sf/openrocket/unit/CaliberUnit.java +++ b/src/net/sf/openrocket/unit/CaliberUnit.java @@ -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 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); } } diff --git a/src/net/sf/openrocket/unit/UnitGroup.java b/src/net/sf/openrocket/unit/UnitGroup.java index 1d8848b2..0f75d827 100644 --- a/src/net/sf/openrocket/unit/UnitGroup.java +++ b/src/net/sf/openrocket/unit/UnitGroup.java @@ -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 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 map = new HashMap(); + HashMap map = new HashMap(); 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 units = new ArrayList(); 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 index 00000000..a91deaa4 --- /dev/null +++ b/src/net/sf/openrocket/util/ArrayList.java @@ -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 + */ +public class ArrayList extends java.util.ArrayList { + + public ArrayList() { + super(); + } + + public ArrayList(Collection c) { + super(c); + } + + public ArrayList(int initialCapacity) { + super(initialCapacity); + } + + @SuppressWarnings("unchecked") + @Override + public ArrayList clone() { + return (ArrayList) super.clone(); + } + +} diff --git a/src/net/sf/openrocket/util/GUIUtil.java b/src/net/sf/openrocket/util/GUIUtil.java index ff97d9a8..10d632e2 100644 --- a/src/net/sf/openrocket/util/GUIUtil.java +++ b/src/net/sf/openrocket/util/GUIUtil.java @@ -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. diff --git a/src/net/sf/openrocket/util/Icons.java b/src/net/sf/openrocket/util/Icons.java index 6878163b..446044df 100644 --- a/src/net/sf/openrocket/util/Icons.java +++ b/src/net/sf/openrocket/util/Icons.java @@ -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"); diff --git a/src/net/sf/openrocket/util/Inertia.java b/src/net/sf/openrocket/util/Inertia.java index 33331665..f7d65c41 100644 --- a/src/net/sf/openrocket/util/Inertia.java +++ b/src/net/sf/openrocket/util/Inertia.java @@ -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 index 00000000..94499894 --- /dev/null +++ b/src/net/sf/openrocket/util/Invalidatable.java @@ -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 + */ +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 index 00000000..7b678114 --- /dev/null +++ b/src/net/sf/openrocket/util/Invalidator.java @@ -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 + */ +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 true when the object has not been invalidated, false if it has + * @throws BugException if the object has been invalidated and throwException 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 true if the object has been invalidated, false 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 index 00000000..b4f4c02b --- /dev/null +++ b/src/net/sf/openrocket/util/ListenerList.java @@ -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. + *

+ * 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 + * @param the type of the listeners. + */ +public class ListenerList implements Invalidatable, Iterable { + private static final LogHelper log = Application.getLogger(); + + private final ArrayList> listeners = new ArrayList>(); + 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 data = new ListenerData(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> 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 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 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 + * @param the listener type + */ + public static class ListenerData { + 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 { + private final Iterator> iterator = listeners.clone().iterator(); + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public T next() { + ListenerData data = iterator.next(); + data.accessTimestamp = System.currentTimeMillis(); + return data.listener; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove not supported"); + } + } + +} diff --git a/src/net/sf/openrocket/util/MemoryManagement.java b/src/net/sf/openrocket/util/MemoryManagement.java index 34589b58..7b55200b 100644 --- a/src/net/sf/openrocket/util/MemoryManagement.java +++ b/src/net/sf/openrocket/util/MemoryManagement.java @@ -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 objects = new LinkedList(); - private static int callCount = 0; + private static int collectableCallCount = 0; + + + private static List>> listenerLists = new LinkedList>>(); + 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 System.gc() multiple times to attempt to + * force any remaining garbage collection. + * + * @return a list of MemoryData objects for objects that have not yet been garbage-collected. */ - public static synchronized int getCallCount() { - return callCount; + public static synchronized List getRemainingCollectableObjects() { + for (int i = 0; i < 5; i++) { + System.runFinalization(); + System.gc(); + try { + Thread.sleep(1); + } catch (InterruptedException e) { + } + } + purgeCollectables(); + return new ArrayList(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>(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 System.gc() multiple times to attempt to * force any remaining garbage collection. * - * @return a list of MemoryData objects for objects that have not yet been garbage-collected. + * @return a list of listener list objects that have not yet been garbage-collected. */ - public static synchronized ArrayList getRemainingObjects() { + public static synchronized List> 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(objects); + purgeListeners(); + List> list = new ArrayList>(); + for (WeakReference> 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 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>> iterator = listenerLists.iterator(); + while (iterator.hasNext()) { + WeakReference> 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. */ diff --git a/src/net/sf/openrocket/util/Monitorable.java b/src/net/sf/openrocket/util/Monitorable.java index eb0b6c47..bff7480e 100644 --- a/src/net/sf/openrocket/util/Monitorable.java +++ b/src/net/sf/openrocket/util/Monitorable.java @@ -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 + */ 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. *

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

diff --git a/src/net/sf/openrocket/util/Prefs.java b/src/net/sf/openrocket/util/Prefs.java index ed7cb2c8..ac3144e2 100644 --- a/src/net/sf/openrocket/util/Prefs.java +++ b/src/net/sf/openrocket/util/Prefs.java @@ -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); diff --git a/src/net/sf/openrocket/util/Reflection.java b/src/net/sf/openrocket/util/Reflection.java index b91f7047..e62270ab 100644 --- a/src/net/sf/openrocket/util/Reflection.java +++ b/src/net/sf/openrocket/util/Reflection.java @@ -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 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 componentClass, + + public static Reflection.Method findMethod(String pack, + Class 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"); } } diff --git a/src/net/sf/openrocket/util/SafetyMutex.java b/src/net/sf/openrocket/util/SafetyMutex.java index fb202a91..2e89c0e4 100644 --- a/src/net/sf/openrocket/util/SafetyMutex.java +++ b/src/net/sf/openrocket/util/SafetyMutex.java @@ -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 */ -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 locations = new LinkedList(); + /** + * 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 mutex.lock(); mutex.unlock(); 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 locations = new LinkedList(); + + + + @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; + } } } - } diff --git a/test/net/sf/openrocket/file/rocksim/RocksimTestBase.java b/test/net/sf/openrocket/file/rocksim/RocksimTestBase.java index 394d385f..a23ee73a 100644 --- a/test/net/sf/openrocket/file/rocksim/RocksimTestBase.java +++ b/test/net/sf/openrocket/file/rocksim/RocksimTestBase.java @@ -3,102 +3,95 @@ */ 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 String specifying the simple name of the - * desired field.

- * - * 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 String specifying the simple name of the - * desired field.

- * - * 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 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 String specifying the simple name of the + * desired field.

+ * + * 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 String specifying the simple name of the + * desired field.

+ * + * 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 index 00000000..6e05aabb --- /dev/null +++ b/test/net/sf/openrocket/gui/TestGUI.java @@ -0,0 +1,12 @@ +package net.sf.openrocket.gui; + +import org.junit.Test; + +public class TestGUI { + + @Test + public void test() { + // No-op + } + +} diff --git a/test/net/sf/openrocket/logging/LogLevelBufferLoggerTest.java b/test/net/sf/openrocket/logging/LogLevelBufferLoggerTest.java index b2e6de5b..4bd9b8d4 100644 --- a/test/net/sf/openrocket/logging/LogLevelBufferLoggerTest.java +++ b/test/net/sf/openrocket/logging/LogLevelBufferLoggerTest.java @@ -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()); diff --git a/test/net/sf/openrocket/motor/ThrustCurveMotorTest.java b/test/net/sf/openrocket/motor/ThrustCurveMotorTest.java index d9cffa98..7ca43d71 100644 --- a/test/net/sf/openrocket/motor/ThrustCurveMotorTest.java +++ b/test/net/sf/openrocket/motor/ThrustCurveMotorTest.java @@ -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 index 00000000..68517d9d --- /dev/null +++ b/test/net/sf/openrocket/optimization/rocketoptimization/modifiers/TestGenericModifier.java @@ -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 gm; + private Simulation sim; + + @Before + public void setup() { + value = new TestValue(); + sim = new Simulation(new Rocket()); + + gm = new GenericModifier("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; + } + } + +} diff --git a/test/net/sf/openrocket/rocketcomponent/ComponentCompare.java b/test/net/sf/openrocket/rocketcomponent/ComponentCompare.java index d0a2374b..fb231dc0 100644 --- a/test/net/sf/openrocket/rocketcomponent/ComponentCompare.java +++ b/test/net/sf/openrocket/rocketcomponent/ComponentCompare.java @@ -42,8 +42,8 @@ public class ComponentCompare { public static void assertDeepEquality(RocketComponent c1, RocketComponent c2) { assertEquality(c1, c2); - Iterator i1 = c1.iterator(); - Iterator i2 = c2.iterator(); + Iterator i1 = c1.getChildren().iterator(); + Iterator 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 i1 = c1.iterator(); - Iterator i2 = c2.iterator(); + Iterator i1 = c1.getChildren().iterator(); + Iterator i2 = c2.getChildren().iterator(); while (i1.hasNext()) { assertTrue("iterator continues", i2.hasNext()); RocketComponent comp1 = i1.next(); diff --git a/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java b/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java index de97c977..11acf71a 100644 --- a/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java +++ b/test/net/sf/openrocket/rocketcomponent/ComponentCompareTest.java @@ -19,8 +19,8 @@ public class ComponentCompareTest { Rocket r1 = net.sf.openrocket.util.TestRockets.makeBigBlue(); Rocket r2 = net.sf.openrocket.util.TestRockets.makeBigBlue(); - Iterator i1 = r1.deepIterator(true); - Iterator i2 = r2.deepIterator(true); + Iterator i1 = r1.iterator(true); + Iterator 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(); diff --git a/test/net/sf/openrocket/util/TestMutex.java b/test/net/sf/openrocket/util/TestMutex.java index dd01848d..94d5a9f5 100644 --- a/test/net/sf/openrocket/util/TestMutex.java +++ b/test/net/sf/openrocket/util/TestMutex.java @@ -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"); + } + }