</classpathentry>
<classpathentry kind="lib" path="lib/miglayout15-swing.jar"/>
<classpathentry kind="lib" path="lib/iText-5.0.2.jar"/>
+ <classpathentry kind="lib" path="lib-test/hamcrest-core-1.3.0RC1.jar"/>
+ <classpathentry kind="lib" path="lib-test/hamcrest-library-1.3.0RC1.jar"/>
+ <classpathentry kind="lib" path="lib-test/jmock-2.6.0-RC2.jar"/>
+ <classpathentry kind="lib" path="lib-test/jmock-junit4-2.6.0-RC2.jar"/>
+ <classpathentry kind="lib" path="lib-test/junit-dep-4.8.2.jar"/>
+ <classpathentry kind="lib" path="lib-test/uispec4j-2.3-jdk16.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+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
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
- 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:
<pathelement location="${build-test.dir}"/>
<pathelement location="${classes.dir}"/>
<pathelement location="${src-test.dir}"/>
- <pathelement location="lib-test/junit-dep-4.8.2.jar"/>
- <pathelement location="lib-test/hamcrest-core-1.3.0RC1.jar"/>
- <pathelement location="lib-test/hamcrest-library-1.3.0RC1.jar"/>
- <pathelement location="lib-test/jmock-2.6.0-RC2.jar"/>
- <pathelement location="lib-test/jmock-junit4-2.6.0-RC2.jar"/>
+ <fileset dir="lib-test/" includes="*.jar"/>
</path>
<zipfileset src="lib/miglayout15-swing.jar" />
<zipfileset src="lib/jcommon-1.0.16.jar" />
<zipfileset src="lib/jfreechart-1.0.13.jar" />
+ <zipfileset src="lib/iText-5.0.2.jar" />
</jar>
</target>
openrocket.log.tracelevel=VBOSE
openrocket.debug.menu=true
openrocket.debug.motordigest=true
+ openrocket.debug.mutexlocation=true
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.
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.
+
+
--- /dev/null
+
+#
+# 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
+
+
+
document-new.png
document-open.png
document-open-example.png (modified)
+document-print.png
document-save-as.png
document-save.png
edit-copy.png
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;
*/
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;
/** The aerodynamic modification ID of the latest rocket */
private int rocketAeroModID = -1;
- private int stageCount = -1;
+ private int rocketTreeModID = -1;
//////////////// Aerodynamic calculators ////////////////
+ @Override
public abstract Coordinate getCP(Configuration configuration, FlightConditions conditions,
WarningSet warnings);
+ @Override
public abstract Map<RocketComponent, AerodynamicForces> getForceAnalysis(Configuration configuration, FlightConditions conditions,
WarningSet warnings);
+ @Override
public abstract AerodynamicForces getAerodynamicForces(Configuration configuration,
FlightConditions conditions, WarningSet warnings);
/*
* The worst theta angle is stored in conditions.
*/
+ @Override
public Coordinate getWorstCP(Configuration configuration, FlightConditions conditions,
WarningSet warnings) {
FlightConditions cond = conditions.clone();
*/
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();
}
}
calcMap = new HashMap<RocketComponent, RocketComponentCalc>();
- iterator = configuration.getRocket().deepIterator();
+ iterator = configuration.getRocket().iterator();
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
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;
}
- @SuppressWarnings("unchecked")
@Override
public WarningSet clone() {
try {
WarningSet newSet = (WarningSet) super.clone();
- newSet.warnings = (ArrayList<Warning>) this.warnings.clone();
+ newSet.warnings = this.warnings.clone();
newSet.mutable = this.mutable.clone();
return newSet;
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];
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);
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;
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
* 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);
-
+
}
* @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;
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])
}
// 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;
}
}
-
+
/* Calculate fin properties:
*
* macLength // MAC length
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);
}
}
macLength /= area;
macSpan /= area;
macLead /= area;
- cosGamma /= (DIVISIONS-1);
- cosGammaLead /= (DIVISIONS-1);
+ cosGamma /= (DIVISIONS - 1);
+ cosGammaLead /= (DIVISIONS - 1);
}
private static final double CNA_SUBSONIC = 0.9;
private static final double CNA_SUPERSONIC = 1.5;
- private static final double CNA_SUPERSONIC_B = pow(pow2(CNA_SUPERSONIC)-1, 1.5);
+ private static final double CNA_SUPERSONIC_B = pow(pow2(CNA_SUPERSONIC) - 1, 1.5);
private static final double GAMMA = 1.4;
private static final LinearInterpolator K1, K2, K3;
private static final PolyInterpolator cnaInterpolator = new PolyInterpolator(
new double[] { CNA_SUBSONIC, CNA_SUPERSONIC },
new double[] { CNA_SUBSONIC, CNA_SUPERSONIC },
new double[] { CNA_SUBSONIC }
- );
+ );
/* Pre-calculate the values for K1, K2 and K3 */
static {
// Up to Mach 5
- int n = (int)((5.0-CNA_SUPERSONIC)*10);
+ int n = (int) ((5.0 - CNA_SUPERSONIC) * 10);
double[] x = new double[n];
double[] k1 = new double[n];
double[] k2 = new double[n];
double[] k3 = new double[n];
- for (int i=0; i<n; i++) {
- double M = CNA_SUPERSONIC + i*0.1;
- double beta = sqrt(M*M - 1);
+ for (int i = 0; i < n; i++) {
+ double M = CNA_SUPERSONIC + i * 0.1;
+ double beta = sqrt(M * M - 1);
x[i] = M;
- k1[i] = 2.0/beta;
- k2[i] = ((GAMMA+1)*pow(M, 4) - 4*pow2(beta)) / (4*pow(beta,4));
- k3[i] = ((GAMMA+1)*pow(M, 8) + (2*pow2(GAMMA) - 7*GAMMA - 5) * pow(M,6) +
- 10*(GAMMA+1)*pow(M,4) + 8) / (6*pow(beta,7));
+ k1[i] = 2.0 / beta;
+ k2[i] = ((GAMMA + 1) * pow(M, 4) - 4 * pow2(beta)) / (4 * pow(beta, 4));
+ k3[i] = ((GAMMA + 1) * pow(M, 8) + (2 * pow2(GAMMA) - 7 * GAMMA - 5) * pow(M, 6) +
+ 10 * (GAMMA + 1) * pow(M, 4) + 8) / (6 * pow(beta, 7));
}
- K1 = new LinearInterpolator(x,k1);
- K2 = new LinearInterpolator(x,k2);
- K3 = new LinearInterpolator(x,k3);
+ K1 = new LinearInterpolator(x, k1);
+ K2 = new LinearInterpolator(x, k2);
+ K3 = new LinearInterpolator(x, k3);
-// System.out.println("K1[m="+CNA_SUPERSONIC+"] = "+k1[0]);
-// System.out.println("K2[m="+CNA_SUPERSONIC+"] = "+k2[0]);
-// System.out.println("K3[m="+CNA_SUPERSONIC+"] = "+k3[0]);
+ // System.out.println("K1[m="+CNA_SUPERSONIC+"] = "+k1[0]);
+ // System.out.println("K2[m="+CNA_SUPERSONIC+"] = "+k2[0]);
+ // System.out.println("K3[m="+CNA_SUPERSONIC+"] = "+k3[0]);
}
-
+
protected double calculateFinCNa1(FlightConditions conditions) {
double mach = conditions.getMach();
double ref = conditions.getRefArea();
- double alpha = MathUtil.min(conditions.getAOA(),
+ double alpha = MathUtil.min(conditions.getAOA(),
Math.PI - conditions.getAOA(), STALL_ANGLE);
// Subsonic case
if (mach <= CNA_SUBSONIC) {
- return 2*Math.PI*pow2(span)/(1 + sqrt(1 + (1-pow2(mach))*
- pow2(pow2(span)/(finArea*cosGamma)))) / ref;
+ return 2 * Math.PI * pow2(span) / (1 + sqrt(1 + (1 - pow2(mach)) *
+ pow2(pow2(span) / (finArea * cosGamma)))) / ref;
}
// Supersonic case
if (mach >= CNA_SUPERSONIC) {
- return finArea * (K1.getValue(mach) + K2.getValue(mach)*alpha +
- K3.getValue(mach)*pow2(alpha)) / ref;
+ return finArea * (K1.getValue(mach) + K2.getValue(mach) * alpha +
+ K3.getValue(mach) * pow2(alpha)) / ref;
}
// Transonic case, interpolate
double subV, superV;
double subD, superD;
- double sq = sqrt(1 + (1-pow2(CNA_SUBSONIC)) * pow2(span*span/(finArea*cosGamma)));
- subV = 2*Math.PI*pow2(span)/ref / (1+sq);
- subD = 2*mach*Math.PI*pow(span,6) / (pow2(finArea*cosGamma) * ref *
- sq * pow2(1+sq));
+ double sq = sqrt(1 + (1 - pow2(CNA_SUBSONIC)) * pow2(span * span / (finArea * cosGamma)));
+ subV = 2 * Math.PI * pow2(span) / ref / (1 + sq);
+ subD = 2 * mach * Math.PI * pow(span, 6) / (pow2(finArea * cosGamma) * ref *
+ sq * pow2(1 + sq));
- superV = finArea * (K1.getValue(CNA_SUPERSONIC) + K2.getValue(CNA_SUPERSONIC)*alpha +
- K3.getValue(CNA_SUPERSONIC)*pow2(alpha)) / ref;
- superD = -finArea/ref * 2*CNA_SUPERSONIC / CNA_SUPERSONIC_B;
+ superV = finArea * (K1.getValue(CNA_SUPERSONIC) + K2.getValue(CNA_SUPERSONIC) * alpha +
+ K3.getValue(CNA_SUPERSONIC) * pow2(alpha)) / ref;
+ superD = -finArea / ref * 2 * CNA_SUPERSONIC / CNA_SUPERSONIC_B;
-// System.out.println("subV="+subV+" superV="+superV+" subD="+subD+" superD="+superD);
+ // System.out.println("subV="+subV+" superV="+superV+" subD="+subD+" superD="+superD);
return cnaInterpolator.interpolate(mach, subV, superV, subD, superD, 0);
}
-
+
private double calculateDampingMoment(FlightConditions conditions) {
double rollRate = conditions.getRollRate();
-
+
if (Math.abs(rollRate) < 0.1)
return 0;
double mach = conditions.getMach();
- double radius = component.getBodyRadius();
double absRate = Math.abs(rollRate);
-
+
/*
* At low speeds and relatively large roll rates (i.e. near apogee) the
* fin tips rotate well above stall angle. In this case sum the chords
* separately.
*/
- if (absRate * (radius + span) / conditions.getVelocity() > 15*Math.PI/180) {
+ if (absRate * (bodyRadius + span) / conditions.getVelocity() > 15 * Math.PI / 180) {
double sum = 0;
- for (int i=0; i < DIVISIONS; i++) {
- double dist = radius + span*i/DIVISIONS;
- double aoa = Math.min(absRate*dist/conditions.getVelocity(), 15*Math.PI/180);
+ for (int i = 0; i < DIVISIONS; i++) {
+ double dist = bodyRadius + span * i / DIVISIONS;
+ double aoa = Math.min(absRate * dist / conditions.getVelocity(), 15 * Math.PI / 180);
sum += chordLength[i] * dist * aoa;
}
- sum = sum * (span/DIVISIONS) * 2*Math.PI/conditions.getBeta() /
+ sum = sum * (span / DIVISIONS) * 2 * Math.PI / conditions.getBeta() /
(conditions.getRefArea() * conditions.getRefLength());
-// System.out.println("SPECIAL: " +
-// (MathUtil.sign(rollRate) *component.getFinCount() * sum));
- return MathUtil.sign(rollRate) * component.getFinCount() * sum;
+ // System.out.println("SPECIAL: " +
+ // (MathUtil.sign(rollRate) *component.getFinCount() * sum));
+ return MathUtil.sign(rollRate) * finCount * sum;
}
-
-
+
+
if (mach <= CNA_SUBSONIC) {
-// System.out.println("BASIC: "+
-// (component.getFinCount() * 2*Math.PI * rollRate * rollSum /
-// (conditions.getRefArea() * conditions.getRefLength() *
-// conditions.getVelocity() * conditions.getBeta())));
+ // System.out.println("BASIC: "+
+ // (component.getFinCount() * 2*Math.PI * rollRate * rollSum /
+ // (conditions.getRefArea() * conditions.getRefLength() *
+ // conditions.getVelocity() * conditions.getBeta())));
- return component.getFinCount() * 2*Math.PI * rollRate * rollSum /
- (conditions.getRefArea() * conditions.getRefLength() *
- conditions.getVelocity() * conditions.getBeta());
+ return finCount * 2 * Math.PI * rollRate * rollSum /
+ (conditions.getRefArea() * conditions.getRefLength() *
+ conditions.getVelocity() * conditions.getBeta());
}
if (mach >= CNA_SUPERSONIC) {
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
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
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;
}
* 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
*
* 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;
}
-// @SuppressWarnings("null")
-// public static void main(String arg[]) {
-// Rocket rocket = TestRocket.makeRocket();
-// FinSet finset = null;
-//
-// Iterator<RocketComponent> iter = rocket.deepIterator();
-// while (iter.hasNext()) {
-// RocketComponent c = iter.next();
-// if (c instanceof FinSet) {
-// finset = (FinSet)c;
-// break;
-// }
-// }
-//
-// ((TrapezoidFinSet)finset).setHeight(0.10);
-// ((TrapezoidFinSet)finset).setRootChord(0.10);
-// ((TrapezoidFinSet)finset).setTipChord(0.10);
-// ((TrapezoidFinSet)finset).setSweep(0.0);
-//
-//
-// FinSetCalc calc = new FinSetCalc(finset);
-//
-// calc.calculateFinGeometry();
-// FlightConditions cond = new FlightConditions(new Configuration(rocket));
-// for (double m=0; m < 3; m+=0.05) {
-// cond.setMach(m);
-// cond.setAOA(0.0*Math.PI/180);
-// double cna = calc.calculateFinCNa1(cond);
-// System.out.printf("%5.2f "+cna+"\n", m);
-// }
-//
-// }
-
+ // @SuppressWarnings("null")
+ // public static void main(String arg[]) {
+ // Rocket rocket = TestRocket.makeRocket();
+ // FinSet finset = null;
+ //
+ // Iterator<RocketComponent> iter = rocket.deepIterator();
+ // while (iter.hasNext()) {
+ // RocketComponent c = iter.next();
+ // if (c instanceof FinSet) {
+ // finset = (FinSet)c;
+ // break;
+ // }
+ // }
+ //
+ // ((TrapezoidFinSet)finset).setHeight(0.10);
+ // ((TrapezoidFinSet)finset).setRootChord(0.10);
+ // ((TrapezoidFinSet)finset).setTipChord(0.10);
+ // ((TrapezoidFinSet)finset).setSweep(0.0);
+ //
+ //
+ // FinSetCalc calc = new FinSetCalc(finset);
+ //
+ // calc.calculateFinGeometry();
+ // FlightConditions cond = new FlightConditions(new Configuration(rocket));
+ // for (double m=0; m < 3; m+=0.05) {
+ // cond.setMach(m);
+ // cond.setAOA(0.0*Math.PI/180);
+ // double cna = calc.calculateFinCNa1(cond);
+ // System.out.printf("%5.2f "+cna+"\n", m);
+ // }
+ //
+ // }
+
@Override
public double calculatePressureDragForce(FlightConditions conditions,
double stagnationCD, double baseCD, WarningSet warnings) {
-
- // Compute and cache the fin geometry
- if (Double.isNaN(cosGammaLead)) {
- calculateFinGeometry();
- calculatePoly();
- }
-
- FinSet.CrossSection profile = component.getCrossSection();
double mach = conditions.getMach();
double drag = 0;
-
+
// Pressure fore-drag
- if (profile == FinSet.CrossSection.AIRFOIL ||
- profile == FinSet.CrossSection.ROUNDED) {
+ if (crossSection == FinSet.CrossSection.AIRFOIL ||
+ crossSection == FinSet.CrossSection.ROUNDED) {
// Round leading edge
if (mach < 0.9) {
drag = Math.pow(1 - pow2(mach), -0.417) - 1;
} else if (mach < 1) {
- drag = 1 - 1.785 * (mach-0.9);
+ drag = 1 - 1.785 * (mach - 0.9);
} else {
- drag = 1.214 - 0.502/pow2(mach) + 0.1095/pow2(pow2(mach));
+ drag = 1.214 - 0.502 / pow2(mach) + 0.1095 / pow2(pow2(mach));
}
- } else if (profile == FinSet.CrossSection.SQUARE) {
+ } else if (crossSection == FinSet.CrossSection.SQUARE) {
drag = stagnationCD;
} else {
- throw new UnsupportedOperationException("Unsupported fin profile: "+profile);
+ throw new UnsupportedOperationException("Unsupported fin profile: " + crossSection);
}
// Slanted leading edge
drag *= pow2(cosGammaLead);
// Trailing edge drag
- if (profile == FinSet.CrossSection.SQUARE) {
+ if (crossSection == FinSet.CrossSection.SQUARE) {
drag += baseCD;
- } else if (profile == FinSet.CrossSection.ROUNDED) {
- drag += baseCD/2;
+ } else if (crossSection == FinSet.CrossSection.ROUNDED) {
+ drag += baseCD / 2;
}
// Airfoil assumed to have zero base drag
-
+
// Scale to correct reference area
- drag *= component.getFinCount() * component.getSpan() * component.getThickness() /
- conditions.getRefArea();
+ drag *= finCount * span * thickness / conditions.getRefArea();
return drag;
}
- private int getInterferenceFinCount() {
- if (interferenceFinCount < 1) {
-
- RocketComponent parent = component.getParent();
- if (parent == null) {
- throw new IllegalStateException("fin set without parent component");
- }
-
- double lead = component.toRelative(Coordinate.NUL, parent)[0].x;
- double trail = component.toRelative(new Coordinate(component.getLength()),
+ private void calculateInterferenceFinCount(FinSet component) {
+ RocketComponent parent = component.getParent();
+ if (parent == null) {
+ throw new IllegalStateException("fin set without parent component");
+ }
+
+ double lead = component.toRelative(Coordinate.NUL, parent)[0].x;
+ double trail = component.toRelative(new Coordinate(component.getLength()),
parent)[0].x;
-
- /*
- * The counting fails if the fin root chord is very small, in that case assume
- * no other fin interference than this fin set.
- */
- if (trail-lead < 0.007) {
- interferenceFinCount = component.getFinCount();
- } else {
- interferenceFinCount = 0;
- for (RocketComponent c: parent.getChildren()) {
- if (c instanceof FinSet) {
- double finLead = c.toRelative(Coordinate.NUL, parent)[0].x;
- double finTrail = c.toRelative(new Coordinate(c.getLength()), parent)[0].x;
-
- // Compute overlap of the fins
-
- if ((finLead < trail - 0.005) && (finTrail > lead + 0.005)) {
- interferenceFinCount += ((FinSet)c).getFinCount();
- }
+
+ /*
+ * The counting fails if the fin root chord is very small, in that case assume
+ * no other fin interference than this fin set.
+ */
+ if (trail - lead < 0.007) {
+ interferenceFinCount = finCount;
+ } else {
+ interferenceFinCount = 0;
+ for (RocketComponent c : parent.getChildren()) {
+ if (c instanceof FinSet) {
+ double finLead = c.toRelative(Coordinate.NUL, parent)[0].x;
+ double finTrail = c.toRelative(new Coordinate(c.getLength()), parent)[0].x;
+
+ // Compute overlap of the fins
+
+ if ((finLead < trail - 0.005) && (finTrail > lead + 0.005)) {
+ interferenceFinCount += ((FinSet) c).getFinCount();
}
}
}
- if (interferenceFinCount < component.getFinCount()) {
- throw new BugException("Counted " + interferenceFinCount + " parallel fins, " +
- "when component itself has " + component.getFinCount() +
+ }
+ if (interferenceFinCount < component.getFinCount()) {
+ throw new BugException("Counted " + interferenceFinCount + " parallel fins, " +
+ "when component itself has " + component.getFinCount() +
", fin points=" + Arrays.toString(component.getFinPoints()));
- }
}
- return interferenceFinCount;
}
}
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());
// 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;
cnaCache = 2 * (A1 - A0);
System.out.println("cnaCache = " + cnaCache);
- cpCache = (component.getLength() * A1 - component.getFullVolume()) / (A1 - A0);
+ cpCache = (length * A1 - fullVolume) / (A1 - A0);
}
}
* 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
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
}
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;
calculateNoseInterpolator();
}
- return interpolator.getValue(conditions.getMach()) * area / conditions.getRefArea();
+ return interpolator.getValue(conditions.getMach()) * frontalArea / conditions.getRefArea();
}
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
package net.sf.openrocket.communication;
-import java.util.ArrayList;
import java.util.List;
+import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.ComparablePair;
import net.sf.openrocket.util.Prefs;
public class UpdateInfo {
-
+
private final String latestVersion;
private final ArrayList<ComparablePair<Integer, String>> updates;
this.latestVersion = version;
this.updates = new ArrayList<ComparablePair<Integer, String>>(updates);
}
-
-
+
+
/**
* Get the latest OpenRocket version. If it is the current version, then the value
public String getLatestVersion() {
return latestVersion;
}
-
-
+
+
/**
* Return a list of the new features/updates that are available. The list has a
* priority for each update and a message text. The returned list may be modified.
*
* @return a modifiable list of the updates.
*/
- @SuppressWarnings("unchecked")
public List<ComparablePair<Integer, String>> getUpdates() {
- return (List<ComparablePair<Integer, String>>) updates.clone();
+ return updates.clone();
}
@Override
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;
import net.sf.openrocket.motor.DesignationComparator;
import net.sf.openrocket.motor.Manufacturer;
import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.Motor.Type;
import net.sf.openrocket.motor.MotorDigest;
import net.sf.openrocket.motor.ThrustCurveMotor;
-import net.sf.openrocket.motor.Motor.Type;
+import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.MathUtil;
public class ThrustCurveMotorSet implements Comparable<ThrustCurveMotorSet> {
}
- @SuppressWarnings("unchecked")
public List<ThrustCurveMotor> getMotors() {
- return (List<ThrustCurveMotor>) motors.clone();
+ return motors.clone();
}
import java.awt.event.ActionEvent;
import java.io.File;
-import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
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;
- @SuppressWarnings("unchecked")
public List<Simulation> getSimulations() {
- return (ArrayList<Simulation>) simulations.clone();
+ return simulations.clone();
}
public int getSimulationCount() {
undoDescription.add(null);
}
+ rocket.checkComponentStructure();
+ undoHistory.get(undoPosition).checkComponentStructure();
+ undoHistory.get(undoPosition).copyWithOriginalID().checkComponentStructure();
rocket.loadFrom(undoHistory.get(undoPosition).copyWithOriginalID());
+ rocket.checkComponentStructure();
}
// Actual action to make
+ @Override
public void actionPerformed(ActionEvent e) {
switch (type) {
case UNDO:
package net.sf.openrocket.document;
-import java.util.ArrayList;
import java.util.List;
import javax.swing.event.ChangeEvent;
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;
NOT_SIMULATED
}
- private final SafetyMutex mutex = new SafetyMutex();
+ private SafetyMutex mutex = SafetyMutex.newInstance();
private final Rocket rocket;
*
* @return a copy of this simulation and its conditions.
*/
- @SuppressWarnings("unchecked")
public Simulation copy() {
mutex.lock("copy");
try {
Simulation copy = (Simulation) super.clone();
+ copy.mutex = SafetyMutex.newInstance();
copy.status = Status.NOT_SIMULATED;
copy.conditions = this.conditions.clone();
- copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
+ copy.simulationListeners = this.simulationListeners.clone();
copy.listeners = new ArrayList<ChangeListener>();
copy.simulatedConditions = null;
copy.simulatedMotors = null;
* @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 {
copy.name = this.name;
copy.conditions.copyFrom(this.conditions);
- copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
+ copy.simulationListeners = this.simulationListeners.clone();
copy.simulationStepperClass = this.simulationStepperClass;
copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
// Size per component
int componentCount = 0;
Rocket rocket = doc.getRocket();
- Iterator<RocketComponent> iterator = rocket.deepIterator(true);
+ Iterator<RocketComponent> iterator = rocket.iterator(true);
while (iterator.hasNext()) {
iterator.next();
componentCount++;
*/
// Check for motor definitions (version 1.2)
- Iterator<RocketComponent> iterator = document.getRocket().deepIterator();
+ Iterator<RocketComponent> iterator = document.getRocket().iterator();
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
if (!(c instanceof MotorMount))
}
// Check for fin tabs (version 1.1)
- iterator = document.getRocket().deepIterator();
+ iterator = document.getRocket().iterator();
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
writeln("<subcomponents>");
indent++;
boolean emptyline = false;
- for (RocketComponent subcomponent : component) {
+ for (RocketComponent subcomponent : component.getChildren()) {
if (emptyline)
writeln("");
emptyline = true;
}
-
private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
GUISimulationConditions cond = simulation.getConditions();
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;
*/
class DelegatorHandler extends DefaultHandler {
private final WarningSet warnings;
-
- private final Stack<ElementHandler> handlerStack = new Stack<ElementHandler>();
- private final Stack<StringBuilder> elementData = new Stack<StringBuilder>();
- private final Stack<HashMap<String, String>> elementAttributes =
- new Stack<HashMap<String, String>>();
-
+
+ private final Deque<ElementHandler> handlerStack = new ArrayDeque<ElementHandler>();
+ private final Deque<StringBuilder> elementData = new ArrayDeque<StringBuilder>();
+ private final Deque<HashMap<String, String>> elementAttributes = new ArrayDeque<HashMap<String, String>>();
+
// Ignore all elements as long as ignore > 0
private int ignore = 0;
-
-
+
+
public DelegatorHandler(ElementHandler initialHandler, WarningSet warnings) {
this.warnings = warnings;
handlerStack.add(initialHandler);
elementData.add(new StringBuilder()); // Just in case
}
-
-
+
+
///////// SAX handlers
-
+
@Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
-
+
// Check for ignore
if (ignore > 0) {
ignore++;
return;
}
-
+
// Check for unknown namespace
if (!uri.equals("")) {
warnings.add(Warning.fromString("Unknown namespace element '" + uri
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);
ignore++;
}
}
-
-
+
+
/**
* Stores encountered characters in the elementData stack.
*/
// Check for ignore
if (ignore > 0)
return;
-
+
StringBuilder sb = elementData.peek();
sb.append(chars, start, length);
}
-
-
+
+
/**
* Removes the last layer from the stack.
*/
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
-
+
// Check for ignore
if (ignore > 0) {
ignore--;
return;
}
-
+
// Remove data from stack
String data = elementData.pop().toString(); // throws on error
HashMap<String, String> attr = elementAttributes.pop();
-
+
// Remove last handler and call the next one
ElementHandler h;
h = handlerStack.peek();
h.closeElement(localName, attr, data, warnings);
}
-
-
+
+
private static HashMap<String, String> copyAttributes(Attributes atts) {
HashMap<String, String> ret = new HashMap<String, String>();
for (int i = 0; i < atts.getLength(); i++) {
return ret;
}
}
-
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;
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;
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
-public class BooleanModel extends AbstractAction implements ChangeListener {
+public class BooleanModel extends AbstractAction implements ChangeListener, Invalidatable {
private static final LogHelper log = Application.getLogger();
private final ChangeSource source;
private boolean oldValue;
private boolean oldEnabled;
+ private Invalidator invalidator = new Invalidator(this);
+
+
public BooleanModel(ChangeSource source, String valueName) {
this.source = source;
this.valueName = valueName;
}
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) {
* @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();
* @see #addEnableComponent(Component, boolean)
*/
public void addEnableComponent(Component component) {
+ checkState(true);
addEnableComponent(component, true);
}
@Override
public void stateChanged(ChangeEvent event) {
+ checkState(true);
+
if (firing > 0) {
log.debug("Ignoring stateChanged of " + this + ", currently firing events");
return;
}
}
+
+ @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) {
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;
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
-public class DoubleModel implements ChangeListener, ChangeSource {
+public class DoubleModel implements ChangeListener, ChangeSource, Invalidatable {
private static final LogHelper log = Application.getLogger();
* 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() {
public void removeChangeListener(ChangeListener l) {
DoubleModel.this.removeChangeListener(l);
}
+
+ @Override
+ public void invalidate() {
+ DoubleModel.this.invalidate();
+ }
}
/**
//////////// JSlider model ////////////
- private class ValueSliderModel implements BoundedRangeModel, ChangeListener {
+ private class ValueSliderModel implements BoundedRangeModel, ChangeListener, Invalidatable {
private static final int MAX = 1000;
/*
return x * x;
}
+ @Override
public int getValue() {
double value = DoubleModel.this.getValue();
if (value <= min.getValue())
}
+ @Override
public void setValue(int newValue) {
if (firing > 0) {
// Ignore loops
// 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
//////////// Action model ////////////
- private class AutomaticActionModel extends AbstractAction implements ChangeListener {
+ private class AutomaticActionModel extends AbstractAction implements ChangeListener, Invalidatable {
private boolean oldValue = false;
public AutomaticActionModel() {
}
// If the value has changed, generate an event to the listeners
+ @Override
public void stateChanged(ChangeEvent e) {
boolean newValue = isAutomatic();
if (oldValue == newValue)
}
}
+ @Override
public void actionPerformed(ActionEvent e) {
// Setting performed in putValue
}
+ @Override
+ public void invalidate() {
+ DoubleModel.this.invalidate();
+ }
}
/**
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);
* @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) {
* 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
* @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 + "'");
* 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);
* 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);
}
+ /**
+ * 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();
* Fire a ChangeEvent to all listeners.
*/
protected void fireStateChanged() {
+ checkState(true);
+
Object[] l = listeners.toArray();
ChangeEvent event = new ChangeEvent(this);
firing++;
* 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)
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) {
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.
* 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();
+ }
}
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();
- }
- }
-
}
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;
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.");
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;
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);
JPanel subPanel = new JPanel(new MigLayout());
// Cluster type selection
- subPanel.add(new JLabel("Select cluster configuration:"),"spanx, wrap");
- subPanel.add(new ClusterSelectionPanel((InnerTube)component),"spanx, wrap");
-// JPanel clusterSelection = new ClusterSelectionPanel((InnerTube)component);
-// clusterSelection.setBackground(Color.blue);
-// subPanel.add(clusterSelection);
+ subPanel.add(new JLabel("Select cluster configuration:"), "spanx, wrap");
+ subPanel.add(new ClusterSelectionPanel((InnerTube) component), "spanx, wrap");
+ // JPanel clusterSelection = new ClusterSelectionPanel((InnerTube)component);
+ // clusterSelection.setBackground(Color.blue);
+ // subPanel.add(clusterSelection);
panel.add(subPanel);
-
- subPanel = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]"));
+ subPanel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]"));
+
// Tube separation scale
JLabel l = new JLabel("Tube separation:");
l.setToolTipText("The separation of the tubes, 1.0 = touching each other");
subPanel.add(l);
- DoubleModel dm = new DoubleModel(component,"ClusterScale",1,UnitGroup.UNITS_NONE,0);
-
+ DoubleModel dm = new DoubleModel(component, "ClusterScale", 1, UnitGroup.UNITS_NONE, 0);
+
JSpinner spin = new JSpinner(dm.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
spin.setToolTipText("The separation of the tubes, 1.0 = touching each other");
- subPanel.add(spin,"growx");
+ subPanel.add(spin, "growx");
BasicSlider bs = new BasicSlider(dm.getSliderModel(0, 1, 4));
bs.setToolTipText("The separation of the tubes, 1.0 = touching each other");
- subPanel.add(bs,"skip,w 100lp, wrap");
-
+ subPanel.add(bs, "skip,w 100lp, wrap");
+
// Rotation
l = new JLabel("Rotation:");
l.setToolTipText("Rotation angle of the cluster configuration");
subPanel.add(l);
- dm = new DoubleModel(component,"ClusterRotation",1,UnitGroup.UNITS_ANGLE,
- -Math.PI,Math.PI);
-
+ dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE,
+ -Math.PI, Math.PI);
+
spin = new JSpinner(dm.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
spin.setToolTipText("Rotation angle of the cluster configuration");
- subPanel.add(spin,"growx");
+ subPanel.add(spin, "growx");
- subPanel.add(new UnitSelector(dm),"growx");
+ subPanel.add(new UnitSelector(dm), "growx");
bs = new BasicSlider(dm.getSliderModel(-Math.PI, 0, Math.PI));
bs.setToolTipText("Rotation angle of the cluster configuration");
- subPanel.add(bs,"w 100lp, wrap para");
-
-
+ subPanel.add(bs, "w 100lp, wrap para");
+
+
// Split button
JButton split = new JButton("Split cluster");
split.setToolTipText("<html>Split the cluster into separate components.<br>" +
RocketComponent parent = component.getParent();
int index = parent.getChildPosition(component);
if (index < 0) {
- throw new IllegalStateException("component="+component+
- " parent="+parent+" parent.children=" +
- Arrays.toString(parent.getChildren()));
+ throw new BugException("Inconsistent state: component=" + component +
+ " parent=" + parent + " parent.children=" + parent.getChildren());
}
-
- InnerTube tube = (InnerTube)component;
+
+ InnerTube tube = (InnerTube) component;
if (tube.getClusterCount() <= 1)
return;
-
+
ComponentConfigDialog.addUndoPosition("Split cluster");
-
+
Coordinate[] coords = { Coordinate.NUL };
coords = component.shiftCoordinates(coords);
parent.removeChild(index);
- for (int i=0; i<coords.length; i++) {
- InnerTube copy = (InnerTube)component.copy();
+ for (int i = 0; i < coords.length; i++) {
+ InnerTube copy = (InnerTube) component.copy();
copy.setClusterConfiguration(ClusterConfiguration.SINGLE);
copy.setClusterRotation(0.0);
copy.setClusterScale(1.0);
copy.setRadialShift(coords[i].y, coords[i].z);
- copy.setName(copy.getName() + " #" + (i+1));
+ copy.setName(copy.getName() + " #" + (i + 1));
- parent.addChild(copy, index+i);
+ parent.addChild(copy, index + i);
}
}
});
});
subPanel.add(split, "spanx, split 2, gapright para, sizegroup buttons, right");
-
+
// Reset button
JButton reset = new JButton("Reset settings");
reset.setToolTipText("Reset the separation and rotation to the default values");
reset.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
- ((InnerTube)component).setClusterScale(1.0);
- ((InnerTube)component).setClusterRotation(0.0);
+ ((InnerTube) component).setClusterScale(1.0);
+ ((InnerTube) component).setClusterRotation(0.0);
}
});
- subPanel.add(reset,"sizegroup buttons, right");
-
- panel.add(subPanel,"grow");
+ subPanel.add(reset, "sizegroup buttons, right");
+ panel.add(subPanel, "grow");
+
return panel;
}
}
public ClusterSelectionPanel(Clusterable component) {
super(new MigLayout("gap 0 0",
- "["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]",
- "["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]["+BUTTON_SIZE+"!]"));
+ "[" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!]",
+ "[" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!][" + BUTTON_SIZE + "!]"));
- for (int i=0; i<ClusterConfiguration.CONFIGURATIONS.length; i++) {
+ for (int i = 0; i < ClusterConfiguration.CONFIGURATIONS.length; i++) {
ClusterConfiguration config = ClusterConfiguration.CONFIGURATIONS[i];
- JComponent button = new ClusterButton(component,config);
- if (i%4 == 3)
- add(button,"wrap");
+ JComponent button = new ClusterButton(component, config);
+ if (i % 4 == 3)
+ add(button, "wrap");
else
add(button);
}
-
+
}
private class ClusterButton extends JPanel implements ChangeListener, MouseListener,
- Resettable {
+ Resettable {
private Clusterable component;
private ClusterConfiguration config;
public ClusterButton(Clusterable c, ClusterConfiguration config) {
component = c;
this.config = config;
- setMinimumSize(new Dimension(BUTTON_SIZE,BUTTON_SIZE));
- setPreferredSize(new Dimension(BUTTON_SIZE,BUTTON_SIZE));
- setMaximumSize(new Dimension(BUTTON_SIZE,BUTTON_SIZE));
+ setMinimumSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
+ setPreferredSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
+ setMaximumSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
-// setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
+ // setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
component.addChangeListener(this);
addMouseListener(this);
}
-
+
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
- Graphics2D g2 = (Graphics2D)g;
+ Graphics2D g2 = (Graphics2D) g;
Rectangle area = g2.getClipBounds();
if (component.getClusterConfiguration() == config)
g2.fillRect(area.x, area.y, area.width, area.height);
- g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_NORMALIZE);
- g2.setRenderingHint(RenderingHints.KEY_RENDERING,
+ g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
- g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
List<Double> points = config.getPoints();
Ellipse2D.Float circle = new Ellipse2D.Float();
- for (int i=0; i < points.size()/2; i++) {
- double x = points.get(i*2);
- double y = points.get(i*2+1);
+ for (int i = 0; i < points.size() / 2; i++) {
+ double x = points.get(i * 2);
+ double y = points.get(i * 2 + 1);
- double px = BUTTON_SIZE/2 + x*MOTOR_DIAMETER;
- double py = BUTTON_SIZE/2 - y*MOTOR_DIAMETER;
- circle.setFrameFromCenter(px,py,px+MOTOR_DIAMETER/2,py+MOTOR_DIAMETER/2);
+ double px = BUTTON_SIZE / 2 + x * MOTOR_DIAMETER;
+ double py = BUTTON_SIZE / 2 - y * MOTOR_DIAMETER;
+ circle.setFrameFromCenter(px, py, px + MOTOR_DIAMETER / 2, py + MOTOR_DIAMETER / 2);
g2.setColor(MOTOR_FILL_COLOR);
g2.fill(circle);
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);
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;
import net.sf.openrocket.gui.adaptors.MaterialModel;
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.StyledLabel;
-import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.gui.components.StyledLabel.Style;
+import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.rocketcomponent.ComponentAssembly;
import net.sf.openrocket.rocketcomponent.ExternalComponent;
+import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
import net.sf.openrocket.rocketcomponent.NoseCone;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Invalidatable;
import net.sf.openrocket.util.LineStyle;
import net.sf.openrocket.util.Prefs;
public class RocketComponentConfig extends JPanel {
-
+
protected final RocketComponent component;
protected final JTabbedPane tabbedPane;
+ private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
+
protected final JTextField componentNameField;
protected JTextArea commentTextArea;
private final TextFieldListener textFieldListener;
private JButton colorButton;
private JCheckBox colorDefault;
private JPanel buttonPanel;
-
+
private JLabel massLabel;
public RocketComponentConfig(RocketComponent component) {
- setLayout(new MigLayout("fill","[grow, fill]"));
+ setLayout(new MigLayout("fill", "[grow, fill]"));
this.component = component;
JLabel label = new JLabel("Component name:");
label.setToolTipText("The component name.");
- this.add(label,"split, gapright 10");
+ this.add(label, "split, gapright 10");
componentNameField = new JTextField(15);
textFieldListener = new TextFieldListener();
componentNameField.addActionListener(textFieldListener);
componentNameField.addFocusListener(textFieldListener);
componentNameField.setToolTipText("The component name.");
- this.add(componentNameField,"growx, growy 0, wrap");
-
+ this.add(componentNameField, "growx, growy 0, wrap");
+
tabbedPane = new JTabbedPane();
- this.add(tabbedPane,"growx, growy 1, wrap");
+ this.add(tabbedPane, "growx, growy 1, wrap");
tabbedPane.addTab("Override", null, overrideTab(), "Mass and CG override options");
if (component.isMassive())
massLabel = new StyledLabel("Mass: ", -1);
buttonPanel.add(massLabel, "growx");
- for (JButton b: buttons) {
+ for (JButton b : buttons) {
buttonPanel.add(b, "right, gap para");
}
// 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
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()) {
String materialString, String finishString) {
JLabel label = new JLabel(materialString);
label.setToolTipText("The component material affects the weight of the component.");
- panel.add(label,"spanx 4, wrap rel");
+ panel.add(label, "spanx 4, wrap rel");
JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
combo.setToolTipText("The component material affects the weight of the component.");
- panel.add(combo,"spanx 4, growx, wrap paragraph");
-
+ panel.add(combo, "spanx 4, growx, wrap paragraph");
+
if (component instanceof ExternalComponent) {
label = new JLabel(finishString);
- String tip = "<html>The component finish affects the aerodynamic drag of the "
- +"component.<br>"
- + "The value indicated is the average roughness height of the surface.";
+ String tip = "<html>The component finish affects the aerodynamic drag of the "
+ + "component.<br>"
+ + "The value indicated is the average roughness height of the surface.";
label.setToolTipText(tip);
- panel.add(label,"spanx 4, wmin 220lp, wrap rel");
+ panel.add(label, "spanx 4, wmin 220lp, wrap rel");
- combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component,"Finish"));
+ combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
combo.setToolTipText(tip);
- panel.add(combo,"spanx 4, growx, split");
+ panel.add(combo, "spanx 4, growx, split");
JButton button = new JButton("Set for all");
button.setToolTipText("Set this finish for all components of the rocket.");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- Finish f = ((ExternalComponent)component).getFinish();
+ Finish f = ((ExternalComponent) component).getFinish();
Rocket rocket = component.getRocket();
try {
rocket.freeze();
String desc = ComponentConfigDialog.getUndoDescription();
ComponentConfigDialog.addUndoPosition("Set rocket finish");
// Do changes
- Iterator<RocketComponent> iter = rocket.deepIterator();
+ Iterator<RocketComponent> iter = rocket.iterator();
while (iter.hasNext()) {
RocketComponent c = iter.next();
if (c instanceof ExternalComponent) {
- ((ExternalComponent)c).setFinish(f);
+ ((ExternalComponent) c).setFinish(f);
}
}
// Restore undo description
private JPanel overrideTab() {
JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
- "[][65lp::][30lp::][]",""));
+ "[][65lp::][30lp::][]", ""));
panel.add(new StyledLabel("Override the mass or center of gravity of the " +
- component.getComponentName() + ":", Style.BOLD),"spanx, wrap 20lp");
-
+ component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
+
JCheckBox check;
BooleanModel bm;
UnitSelector us;
BasicSlider bs;
-
+
//// Mass
bm = new BooleanModel(component, "MassOverridden");
check = new JCheckBox(bm);
check.setText("Override mass:");
panel.add(check, "growx 1, gapright 20lp");
- DoubleModel m = new DoubleModel(component,"OverrideMass",UnitGroup.UNITS_MASS,0);
+ DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
JSpinner spin = new JSpinner(m.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
bm.addEnableComponent(spin, true);
- panel.add(spin,"growx 1");
+ panel.add(spin, "growx 1");
us = new UnitSelector(m);
bm.addEnableComponent(us, true);
- panel.add(us,"growx 1");
+ panel.add(us, "growx 1");
bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
bm.addEnableComponent(bs);
- panel.add(bs,"growx 5, w 100lp, wrap");
-
+ panel.add(bs, "growx 5, w 100lp, wrap");
+
//// CG override
bm = new BooleanModel(component, "CGOverridden");
check = new JCheckBox(bm);
check.setText("Override center of gravity:");
panel.add(check, "growx 1, gapright 20lp");
- m = new DoubleModel(component,"OverrideCGX",UnitGroup.UNITS_LENGTH,0);
+ m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
// Calculate suitable length for slider
DoubleModel length;
if (component instanceof ComponentAssembly) {
- double l=0;
+ double l = 0;
- Iterator<RocketComponent> iterator = component.deepIterator();
+ Iterator<RocketComponent> iterator = component.iterator(false);
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
if (c.getRelativePosition() == RocketComponent.Position.AFTER)
}
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);
panel.add(new StyledLabel("<html>The overridden mass does not include motors.<br>" +
"The center of gravity is measured from the front end of the " +
- component.getComponentName().toLowerCase()+".", -1),
+ component.getComponentName().toLowerCase() + ".", -1),
"spanx, wrap, gap para, height 0::30lp");
return panel;
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
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) {
}
c = JColorChooser.showDialog(tabbedPane, "Choose color", c);
- if (c!=null) {
+ if (c != null) {
component.setColor(c);
}
}
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
});
panel.add(colorDefault, "wrap para");
-
- panel.add(new JLabel("Component line style:"), "gapleft para, gapright 10lp");
- LineStyle[] list = new LineStyle[LineStyle.values().length+1];
+ panel.add(new JLabel("Component line style:"), "gapleft para, gapright 10lp");
+
+ LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
-
+
JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
list, "Default style"));
panel.add(combo, "spanx 2, growx, wrap 50lp");
-
+
JButton button = new JButton("Save as default style");
button.addActionListener(new ActionListener() {
@Override
return panel;
}
-
+
protected JPanel shoulderTab() {
JPanel panel = new JPanel(new MigLayout("fill"));
JPanel sub;
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());
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);
}
+ protected void register(Invalidatable model) {
+ this.invalidatables.add(model);
+ }
+
+ public void invalidateModels() {
+ for (Invalidatable i : invalidatables) {
+ i.invalidate();
+ }
+ }
+
}
};
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());
this.rocket = rocket;
ArrayList<MotorMount> mountList = new ArrayList<MotorMount>();
- Iterator<RocketComponent> iterator = rocket.deepIterator();
+ Iterator<RocketComponent> iterator = rocket.iterator();
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
if (c instanceof MotorMount) {
- 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
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;
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();
// 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);
rocket.addComponentChangeListener(new ComponentChangeListener() {
+ @Override
public void componentChanged(ComponentChangeEvent e) {
setTitle();
}
// 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();
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();
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();
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);
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();
"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();
});
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();
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();
item.getAccessibleContext().setAccessibleDescription("Setup the application " +
"preferences");
item.addActionListener(new ActionListener() {
+ @Override
public void actionPerformed(ActionEvent e) {
log.user("Preferences selected");
PreferencesDialog.showPreferences();
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);
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);
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);
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);
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);
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,
log.user("Memory statistics selected");
// Get discarded but remaining objects (this also runs System.gc multiple times)
- List<MemoryData> objects = MemoryManagement.getRemainingObjects();
+ List<MemoryData> objects = MemoryManagement.getRemainingCollectableObjects();
StringBuilder sb = new StringBuilder();
sb.append("Objects that should have been garbage-collected but have not been:\n");
int count = 0;
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() {
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();
}
- /**
- *
- */
- public void printAction() {
- new PrintDialog(document);
- }
-
+ /**
+ *
+ */
+ public void printAction() {
+ new PrintDialog(document);
+ }
+
/**
* Open a new design window with a basic rocket+stage.
*/
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);
}
+ @Override
public Object getChild(Object parent, int index) {
RocketComponent component = (RocketComponent) parent;
}
+ @Override
public int getChildCount(Object parent) {
RocketComponent c = (RocketComponent) parent;
}
+ @Override
public int getIndexOfChild(Object parent, Object child) {
if (parent == null || child == null)
return -1;
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);
}
}
}
+ @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
}
public void expandAll() {
- Iterator<RocketComponent> iterator = root.deepIterator();
+ Iterator<RocketComponent> iterator = root.iterator(false);
while (iterator.hasNext()) {
tree.makeVisible(makeTreePath(iterator.next()));
}
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;
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);
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);
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);
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);
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)
/** Bonus given for only using a single axis. */
private static final double BONUS_ONLY_ONE_AXIS = 50.0;
-
- private static final double INCLUDE_ZERO_DISTANCE = 0.3; // 30% of total range
-
+
+ private static final double INCLUDE_ZERO_DISTANCE = 0.3; // 30% of total range
+
/** The data types to be plotted. */
private ArrayList<FlightDataType> plotDataTypes = new ArrayList<FlightDataType>();
/** The corresponding Axis on which they will be plotted, or null to auto-select. */
private ArrayList<Integer> plotDataAxes = new ArrayList<Integer>();
-
+
private EnumSet<FlightEvent.Type> events = EnumSet.noneOf(FlightEvent.Type.class);
/** The domain (x) axis. */
private FlightDataType domainAxisType = null;
private Unit domainAxisUnit = null;
-
+
/** All available axes. */
private final int axesCount;
private ArrayList<Axis> allAxes = new ArrayList<Axis>();
-
-
+
+
private String name = null;
-
+
public PlotConfiguration() {
this(null, FlightDataType.TYPE_TIME);
}
}
-
-
-
+
+
+
public FlightDataType getDomainAxisType() {
return domainAxisType;
}
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;
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");
plotDataUnits.add(type.getUnitGroup().getDefaultUnit());
plotDataAxes.add(axis);
}
-
-
+
+
public void setPlotDataType(int index, FlightDataType type) {
FlightDataType origType = plotDataTypes.get(index);
plotDataTypes.set(index, type);
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);
}
-
- 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);
}
/// Events
public Set<FlightEvent.Type> getActiveEvents() {
- return (Set<FlightEvent.Type>) events.clone();
+ return events.clone();
}
public void setEvent(FlightEvent.Type type, boolean active) {
}
-
-
-
+
+
+
public List<Axis> getAllAxes() {
List<Axis> list = new ArrayList<Axis>();
list.addAll(allAxes);
}
-
+
/**
* Find the best combination of the auto-selectable axes.
*
}
-
-
+
+
/**
* Recursively search for the best combination of the auto-selectable axes.
* This is a brute-force search method.
// Create copy to fill in
PlotConfiguration copy = this.clone();
- int autoindex;
- for (autoindex=0; autoindex < plotDataAxes.size(); autoindex++) {
+ int autoindex;
+ for (autoindex = 0; autoindex < plotDataAxes.size(); autoindex++) {
if (plotDataAxes.get(autoindex) < 0)
break;
}
-
+
if (autoindex >= plotDataAxes.size()) {
// All axes have been assigned, just return since we are already the best
return new Pair<PlotConfiguration, Double>(copy, copy.getGoodnessValue(data));
}
-
+
// Set the auto-selected index one at a time and choose the best one
PlotConfiguration best = null;
double bestValue = Double.NEGATIVE_INFINITY;
- for (int i=0; i < axesCount; i++) {
+ for (int i = 0; i < axesCount; i++) {
copy.plotDataAxes.set(autoindex, i);
Pair<PlotConfiguration, Double> result = copy.recursiveFillAutoAxes(data);
if (result.getV() > bestValue) {
}
-
-
-
+
+
+
/**
* Fit the axes to hold the provided data. All of the plotDataAxis elements must
* be non-negative.
protected void fitAxes(FlightDataBranch data) {
// Reset axes
- for (Axis a: allAxes) {
+ for (Axis a : allAxes) {
a.reset();
}
// Add full range to the axes
int length = plotDataTypes.size();
- for (int i=0; i<length; i++) {
+ for (int i = 0; i < length; i++) {
FlightDataType type = plotDataTypes.get(i);
Unit unit = plotDataUnits.get(i);
int index = plotDataAxes.get(i);
double min = unit.toUnit(data.getMinimum(type));
double max = unit.toUnit(data.getMaximum(type));
-
+
axis.addBound(min);
axis.addBound(max);
}
// Ensure non-zero (or NaN) range, add a few percent range, include zero if it is close
- for (Axis a: allAxes) {
+ for (Axis a : allAxes) {
if (MathUtil.equals(a.getMinValue(), a.getMaxValue())) {
- a.addBound(a.getMinValue()-1);
- a.addBound(a.getMaxValue()+1);
+ a.addBound(a.getMinValue() - 1);
+ a.addBound(a.getMaxValue() + 1);
}
double addition = a.getRangeLength() * 0.03;
a.addBound(0);
}
}
-
+
// Check whether to use a common zero
Axis left = allAxes.get(0);
Axis right = allAxes.get(1);
Double.isNaN(left.getMinValue()) || Double.isNaN(right.getMinValue()))
return;
-
-
+
+
//// Compute common zero
// TODO: MEDIUM: This algorithm may require tweaking
-
+
double min1 = left.getMinValue();
double max1 = left.getMaxValue();
double min2 = right.getMinValue();
double scale = Math.max(left.getRangeLength(), right.getRangeLength()) /
Math.min(left.getRangeLength(), right.getRangeLength());
- System.out.println("Scale: "+scale);
+ System.out.println("Scale: " + scale);
scale = roundScale(scale);
if (right.getRangeLength() > left.getRangeLength()) {
- scale = 1/scale;
+ scale = 1 / scale;
}
System.out.println("Rounded scale: " + scale);
-
+
// Scale right axis, enlarge axes if necessary and scale back
min2 *= scale;
max2 *= scale;
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);
} else {
scale = 1;
}
- return scale*mul;
+ return scale * mul;
}
-
+
private double roundScaleUp(double scale) {
double mul = 1;
while (scale >= 10) {
} else {
scale = 1;
}
- return scale*mul;
+ return scale * mul;
}
-
+
private double roundScaleDown(double scale) {
double mul = 1;
while (scale >= 10) {
} 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.
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.
*/
// 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;
}
-
+
/**
* Reset the units of this configuration to the default units. Returns this
* PlotConfiguration.
* @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 {
PlotConfiguration copy = (PlotConfiguration) super.clone();
// Shallow-clone all immutable lists
- copy.plotDataTypes = (ArrayList<FlightDataType>) this.plotDataTypes.clone();
- copy.plotDataAxes = (ArrayList<Integer>) this.plotDataAxes.clone();
- copy.plotDataUnits = (ArrayList<Unit>) this.plotDataUnits.clone();
+ copy.plotDataTypes = this.plotDataTypes.clone();
+ copy.plotDataAxes = this.plotDataAxes.clone();
+ copy.plotDataUnits = this.plotDataUnits.clone();
copy.events = this.events.clone();
// Deep-clone all Axis since they are mutable
copy.allAxes = new ArrayList<Axis>();
- for (Axis a: this.allAxes) {
+ for (Axis a : this.allAxes) {
copy.allAxes.add(a.clone());
}
return copy;
-
+
} catch (CloneNotSupportedException e) {
throw new BugException("BUG! Could not clone().");
}
private SimulationWorker backgroundSimulationWorker = null;
+ private boolean dirty = false;
-
private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
--- /dev/null
+package net.sf.openrocket.l10n;
+
+/**
+ * A translator implementation that returns the logical key in brackets instead
+ * of an actual translation.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class DebugTranslator implements Translator {
+
+ @Override
+ public String get(String key) {
+ return "[" + key + "]";
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.l10n;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A translator that obtains translated strings from a resource bundle.
+ * <p>
+ * If a message is not found in any resource bundle, an error is logged and the key itself
+ * is returned.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ResourceBundleTranslator implements Translator {
+ private static final LogHelper log = Application.getLogger();
+
+ private final ResourceBundle bundle;
+ private final String baseName;
+ private final Locale locale;
+
+ /**
+ * Create a ResourceBundleTranslator using the default Locale.
+ *
+ * @param baseName the base name of the resource bundle
+ */
+ public ResourceBundleTranslator(String baseName) {
+ this(baseName, Locale.getDefault());
+ }
+
+ /**
+ * Create a ResourceBundleTranslator using the specified Locale.
+ *
+ * @param baseName the base name of the resource bundle
+ * @param locale the locale to use
+ */
+ public ResourceBundleTranslator(String baseName, Locale locale) {
+ this.bundle = ResourceBundle.getBundle(baseName, locale);
+ this.baseName = baseName;
+ this.locale = locale;
+ }
+
+
+ /*
+ * NOTE: This method must be thread-safe!
+ */
+ @Override
+ public synchronized String get(String key) {
+ try {
+ return bundle.getString(key);
+ } catch (MissingResourceException e) {
+ log.error("String not found for key '" + key + "' in bundle '" + baseName + "' with locale " + locale, e);
+ }
+ return key;
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.l10n;
+
+/**
+ * An interface for obtaining translations from logical keys.
+ * <p>
+ * Translator implementations must be thread-safe.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface Translator {
+
+ /**
+ * Retrieve a translated string based on a logical key. This always returns
+ * some string, potentially falling back to the key itself.
+ *
+ * @param key the logical string key.
+ * @return the translated string.
+ * @throws NullPointerException if key is null.
+ */
+ public String get(String key);
+
+}
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
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class DelegatorLogger extends LogHelper {
-
+
/**
* List of loggers. This list must not be modified, instead it should be
* replaced every time the list is changed.
public void log(LogLine line) {
// Must create local reference for thread safety
List<LogHelper> list = loggers;
- for (LogHelper l: list) {
+ for (LogHelper l : list) {
l.log(line);
}
}
* Add a logger from the delegation list.
* @param logger the logger to add.
*/
- @SuppressWarnings("unchecked")
public synchronized void addLogger(LogHelper logger) {
- ArrayList<LogHelper> newList = (ArrayList<LogHelper>) loggers.clone();
+ ArrayList<LogHelper> newList = loggers.clone();
newList.add(logger);
this.loggers = newList;
}
* Remove a logger from the delegation list.
* @param logger the logger to be removed.
*/
- @SuppressWarnings("unchecked")
public synchronized void removeLogger(LogHelper logger) {
- ArrayList<LogHelper> newList = (ArrayList<LogHelper>) loggers.clone();
+ ArrayList<LogHelper> newList = loggers.clone();
newList.remove(logger);
this.loggers = newList;
}
-
+
}
}
+ /**
+ * 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.
*/
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.
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class AbstractMassCalculator implements MassCalculator {
+ private static final LogHelper log = Application.getLogger();
private int rocketMassModID = -1;
- private int stageCount = -1;
+ private int rocketTreeModID = -1;
/**
*/
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();
}
}
* are relative to their respective CG.
*/
private Coordinate[] cgCache = null;
- private double longitudalInertiaCache[] = null;
+ private double longitudinalInertiaCache[] = null;
private double rotationalInertiaCache[] = null;
/**
- * 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);
for (int stage : configuration.getActiveStages()) {
Coordinate stageCG = cgCache[stage];
- totalInertia += (longitudalInertiaCache[stage] +
+ totalInertia += (longitudinalInertiaCache[stage] +
stageCG.weight * MathUtil.pow2(stageCG.x - totalCG.x));
}
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);
}
}
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;
}
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;
// 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;
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);
}
}
private static class MassData {
public Coordinate cg = Coordinate.NUL;
- public double longitudalInertia = 0;
+ public double longitudinalInertia = 0;
public double rotationalInetria = 0;
}
protected void voidMassCache() {
super.voidMassCache();
this.cgCache = null;
- this.longitudalInertiaCache = null;
+ this.longitudinalInertiaCache = null;
this.rotationalInertiaCache = null;
}
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.
* @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);
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.
private Coordinate instCG;
private final double unitRotationalInertia;
- private final double unitLongitudalInertia;
+ private final double unitLongitudinalInertia;
private int modID = 0;
instCG = cg[0];
stepCG = cg[0];
unitRotationalInertia = Inertia.filledCylinderRotational(getDiameter() / 2);
- unitLongitudalInertia = Inertia.filledCylinderLongitudal(getDiameter() / 2, getLength());
+ unitLongitudinalInertia = Inertia.filledCylinderLongitudinal(getDiameter() / 2, getLength());
}
@Override
}
@Override
- public double getLongitudalInertia() {
- return unitLongitudalInertia * stepCG.weight;
+ public double getLongitudinalInertia() {
+ return unitLongitudinalInertia * stepCG.weight;
}
@Override
package net.sf.openrocket.optimization.rocketoptimization;
import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.ChangeSource;
/**
* An interface what modifies a single parameter in a rocket simulation
* based on a double value in the range [0...1].
+ * <p>
+ * The implementation must fire change events when the minimum and maximum ranges
+ * are modified, NOT when the actual modified value changes.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
/**
* 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;
/**
/**
- * 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();
/**
* 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;
*
* @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;
}
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization.modifiers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.MathUtil;
+
+/**
+ * An abstract implementation of the SimulationModifier interface. An implementation
+ * needs only to implement the {@link #getCurrentValue(Simulation)} and
+ * {@link #modify(net.sf.openrocket.document.Simulation, double)} methods.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class AbstractSimulationModifier implements SimulationModifier {
+
+ private final String name;
+ private final Object relatedObject;
+ private final UnitGroup unitGroup;
+
+ private double minValue = 0.0;
+ private double maxValue = 1.0;
+
+ private final List<ChangeListener> listeners = new ArrayList<ChangeListener>();
+
+
+ /**
+ * Sole constructor.
+ *
+ * @param modifierName the name of this modifier (returned by {@link #getName()})
+ * @param relatedObject the related object (returned by {@link #getRelatedObject()})
+ * @param unitGroup the unit group (returned by {@link #getUnitGroup()})
+ */
+ public AbstractSimulationModifier(String modifierName, Object relatedObject, UnitGroup unitGroup) {
+ this.name = modifierName;
+ this.relatedObject = relatedObject;
+ this.unitGroup = unitGroup;
+ }
+
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Object getRelatedObject() {
+ return relatedObject;
+ }
+
+ @Override
+ public double getCurrentScaledValue(Simulation simulation) throws OptimizationException {
+ double value = getCurrentValue(simulation);
+ return toScaledValue(value);
+ }
+
+
+
+ /**
+ * Returns the scaled value (normally within [0...1]). If the min...max range is singular,
+ * this method returns 0.0, 1.0 or 0.5 depending on whether the value is less than,
+ * greater than or equal to the limit.
+ *
+ * @param value the value in SI units
+ * @return the value in scaled range (normally within [0...1])
+ */
+ protected double toScaledValue(double value) {
+ if (MathUtil.equals(minValue, maxValue)) {
+ if (value > maxValue)
+ return 1.0;
+ if (value < minValue)
+ return 0.0;
+ return 0.5;
+ }
+
+ return MathUtil.map(value, minValue, maxValue, 0.0, 1.0);
+ }
+
+
+ /**
+ * Returns the base value (in SI units).
+ *
+ * @param value the value in scaled range (normally within [0...1])
+ * @return the value in SI units
+ */
+ protected double toBaseValue(double value) {
+ return MathUtil.map(value, 0.0, 1.0, minValue, maxValue);
+ }
+
+
+
+ @Override
+ public double getMinValue() {
+ return minValue;
+ }
+
+ @Override
+ public void setMinValue(double value) {
+ if (MathUtil.equals(minValue, value))
+ return;
+ this.minValue = value;
+ if (maxValue < minValue)
+ maxValue = minValue;
+ fireChangeEvent();
+ }
+
+ @Override
+ public double getMaxValue() {
+ return maxValue;
+ }
+
+ @Override
+ public void setMaxValue(double value) {
+ if (MathUtil.equals(maxValue, value))
+ return;
+ this.maxValue = value;
+ if (minValue > maxValue)
+ minValue = maxValue;
+ fireChangeEvent();
+ }
+
+ @Override
+ public UnitGroup getUnitGroup() {
+ return unitGroup;
+ }
+
+
+ @Override
+ public void addChangeListener(ChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeChangeListener(ChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+
+ /**
+ * Fire a change event to the listeners.
+ */
+ protected void fireChangeEvent() {
+ ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
+ ChangeEvent event = new ChangeEvent(this);
+ for (ChangeListener l : array) {
+ l.stateChanged(event);
+ }
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization.modifiers;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.unit.UnitGroup;
+
+/**
+ * A generic simulation modifier that modifies a value of a certain RocketComponent
+ * based on the component's ID and the value name.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class GenericComponentModifier extends GenericModifier<RocketComponent> {
+
+ private final Class<RocketComponent> componentClass;
+ private final String componentId;
+
+ /**
+ * Sole constructor.
+ *
+ * @param modifierName the name of this modifier (returned by {@link #getName()})
+ * @param relatedObject the related object (returned by {@link #getRelatedObject()})
+ * @param unitGroup the unit group (returned by {@link #getUnitGroup()})
+ * @param multiplier the multiplier by which the value returned by the getter is multiplied
+ * to obtain the desired value
+ * @param componentClass the RocketComponent class type that is being modified
+ * @param componentId the ID of the component to modify
+ * @param methodName the base name of the getter/setter methods (without "get"/"set")
+ */
+ public GenericComponentModifier(String modifierName, Object relatedObject, UnitGroup unitGroup,
+ double multiplier, Class<RocketComponent> componentClass, String componentId, String methodName) {
+ super(modifierName, relatedObject, unitGroup, multiplier, componentClass, methodName);
+
+ this.componentClass = componentClass;
+ this.componentId = componentId;
+ }
+
+ @Override
+ protected RocketComponent getModifiedObject(Simulation simulation) throws OptimizationException {
+ RocketComponent c = simulation.getRocket().findComponent(componentId);
+ if (c == null) {
+ throw new OptimizationException("Could not find component of type " + componentClass.getSimpleName()
+ + " with correct ID");
+ }
+ return c;
+ }
+
+}
package net.sf.openrocket.optimization.rocketoptimization.modifiers;
-import javax.swing.event.ChangeListener;
-
import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.optimization.general.OptimizationException;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Reflection.Method;
-public class GenericModifier implements SimulationModifier {
+/**
+ * A generic SimulationModifier that uses reflection to get and set a double value.
+ * Implementations need to implement the {@link #getModifiedObject(Simulation)} method
+ * to return which object is modified.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class GenericModifier<T> extends AbstractSimulationModifier {
- private final String name;
- private final Object relatedObject;
- private final UnitGroup unitGroup;
private final double multiplier;
- private final Object modifiable;
private final Method getter;
private final Method setter;
- private double minValue;
- private double maxValue;
-
-
-
-
+ /**
+ * Sole constructor.
+ *
+ * @param modifierName the name of this modifier (returned by {@link #getName()})
+ * @param relatedObject the related object (returned by {@link #getRelatedObject()})
+ * @param unitGroup the unit group (returned by {@link #getUnitGroup()})
+ * @param multiplier the multiplier by which the value returned by the getter is multiplied
+ * to obtain the desired value
+ * @param modifiedClass the class type that {@link #getModifiedObject(Simulation)} returns
+ * @param methodName the base name of the getter/setter methods (without "get"/"set")
+ */
public GenericModifier(String modifierName, Object relatedObject, UnitGroup unitGroup, double multiplier,
- Object modifiable, String methodName) {
- this.name = modifierName;
- this.relatedObject = relatedObject;
- this.unitGroup = unitGroup;
+ Class<T> modifiedClass, String methodName) {
+ super(modifierName, relatedObject, unitGroup);
this.multiplier = multiplier;
- this.modifiable = modifiable;
if (MathUtil.equals(multiplier, 0)) {
throw new IllegalArgumentException("multiplier is zero");
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;
}
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization.services;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationModifierService;
+
+public class DefaultSimulationModifierService implements SimulationModifierService {
+
+ @Override
+ public Collection<SimulationModifier> getModifiers(OpenRocketDocument document) {
+ // TODO: Should this really be OpenRocketDocument instead of Simulation?
+ List<SimulationModifier> list = new ArrayList<SimulationModifier>();
+
+ // TODO: implement
+
+
+ return null;
+ }
+
+}
@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;
* Null method (ComponentAssembly has no mass of itself).
*/
@Override
- public double getLongitudalUnitInertia() {
+ public double getLongitudinalUnitInertia() {
return 0;
}
package net.sf.openrocket.rocketcomponent;
-import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
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;
*
* @return a <code>Collection</code> containing coordinates bouding the rocket.
*/
- @SuppressWarnings("unchecked")
public Collection<Coordinate> getBounds() {
if (rocket.getModID() != boundsModID) {
boundsModID = rocket.getModID();
cachedLength = maxX - minX;
}
}
- return (ArrayList<Coordinate>) cachedBounds.clone();
+ return cachedBounds.clone();
}
for (RocketComponent stage : rocket.getChildren()) {
if (isStageActive(stage)) {
- list.add(stage.deepIterator());
+ list.add(stage.iterator(false));
}
}
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;
}
}
-
+
/**
* 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.
*/
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.
*/
public double getComponentMass() {
return material.getDensity() * getComponentVolume();
}
-
+
/**
* ExternalComponent has aerodynamic effect, so return true.
*/
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;
this.finish = finish;
fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
}
-
+
@Override
- protected void copyFrom(RocketComponent c) {
- super.copyFrom(c);
-
- ExternalComponent src = (ExternalComponent)c;
+ protected List<RocketComponent> copyFrom(RocketComponent c) {
+ ExternalComponent src = (ExternalComponent) c;
this.finish = src.finish;
this.material = src.material;
+ return super.copyFrom(c);
}
/**
/*
- * 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
* 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;
@Override
- protected void copyFrom(RocketComponent c) {
- super.copyFrom(c);
-
+ protected List<RocketComponent> copyFrom(RocketComponent c) {
FinSet src = (FinSet) c;
this.fins = src.fins;
this.finRotation = src.finRotation;
this.tabLength = src.tabLength;
this.tabRelativePosition = src.tabRelativePosition;
this.tabShift = src.tabShift;
+
+ return super.copyFrom(c);
}
}
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();
log.info("Converting " + finset.getComponentName() + " into freeform fin set");
final RocketComponent root = finset.getRoot();
FreeformFinSet freeform;
+ List<RocketComponent> toInvalidate = Collections.emptyList();
try {
if (root instanceof Rocket) {
}
// Copy component attributes
- freeform.copyFrom(finset);
+ toInvalidate = freeform.copyFrom(finset);
// Set name
final String componentTypeName = finset.getComponentName();
if (root instanceof Rocket) {
((Rocket) root).thaw();
}
+ // Invalidate components after events have been fired
+ for (RocketComponent c : toInvalidate) {
+ c.invalidate();
+ }
}
return freeform;
}
* @param index the fin point index to remove
* @throws IllegalFinPointException if removing would result in invalid fin planform
*/
- @SuppressWarnings("unchecked")
public void removePoint(int index) throws IllegalFinPointException {
if (index == 0 || index == points.size() - 1) {
throw new IllegalFinPointException("cannot remove first or last point");
}
- ArrayList<Coordinate> copy = (ArrayList<Coordinate>) this.points.clone();
+ ArrayList<Coordinate> copy = this.points.clone();
copy.remove(index);
validate(copy);
this.points = copy;
}
- @SuppressWarnings("unchecked")
@Override
protected RocketComponent copyWithOriginalID() {
RocketComponent c = super.copyWithOriginalID();
- ((FreeformFinSet) c).points = (ArrayList<Coordinate>) this.points.clone();
+ ((FreeformFinSet) c).points = this.points.clone();
return c;
}
- /**
- * Accept a visitor to this FreeformFinSet in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this FreeformFinSet
- */
- @Override
- public void accept(ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
+ /**
+ * Accept a visitor to this FreeformFinSet in the component hierarchy.
+ *
+ * @param theVisitor the visitor that will be called back with a reference to this FreeformFinSet
+ */
+ @Override
+ public void accept(ComponentVisitor theVisitor) {
+ theVisitor.visit(this);
+ }
private void validate(ArrayList<Coordinate> points) throws IllegalFinPointException {
final int n = points.size();
}
@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;
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;
/**
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class MassObject extends InternalComponent {
-
- private double radius;
-
- private double radialPosition;
- private double radialDirection;
-
- private double shiftY = 0;
- private double shiftZ = 0;
-
-
- public MassObject () {
- this(0.03, 0.015);
- }
-
- public MassObject (double length, double radius) {
- super();
-
- this.length = length;
- this.radius = radius;
-
- this.setRelativePosition(Position.TOP);
- this.setPositionValue(0.0);
- }
-
-
- public void setLength (double length) {
- length = Math.max(length, 0);
- if (MathUtil.equals(this.length, length)) {
- return;
- }
- this.length = length;
- fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
- }
-
-
- public final double getRadius () {
- return radius;
- }
-
-
- public final void setRadius (double radius) {
- radius = Math.max(radius, 0);
- if (MathUtil.equals(this.radius, radius)) {
- return;
- }
- this.radius = radius;
- fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
- }
-
-
- public final double getRadialPosition () {
- return radialPosition;
- }
-
- public final void setRadialPosition (double radialPosition) {
- radialPosition = Math.max(radialPosition, 0);
- if (MathUtil.equals(this.radialPosition, radialPosition)) {
- return;
- }
- this.radialPosition = radialPosition;
- shiftY = radialPosition * Math.cos(radialDirection);
- shiftZ = radialPosition * Math.sin(radialDirection);
- fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
- }
-
- public final double getRadialDirection () {
- return radialDirection;
- }
-
- public final void setRadialDirection (double radialDirection) {
- radialDirection = MathUtil.reduce180(radialDirection);
- if (MathUtil.equals(this.radialDirection, radialDirection)) {
- return;
- }
- this.radialDirection = radialDirection;
- shiftY = radialPosition * Math.cos(radialDirection);
- shiftZ = radialPosition * Math.sin(radialDirection);
- fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
- }
-
-
- /**
- * Shift the coordinates according to the radial position and direction.
- */
- @Override
- public final Coordinate[] shiftCoordinates (Coordinate[] array) {
- for (int i = 0; i < array.length; i++) {
- array[i] = array[i].add(0, shiftY, shiftZ);
- }
- return array;
- }
-
- @Override
- public final Coordinate getComponentCG () {
- return new Coordinate(length / 2, shiftY, shiftZ, getComponentMass());
- }
-
- @Override
- public final double getLongitudalUnitInertia () {
- return (3 * pow2(radius) + pow2(length)) / 12;
- }
-
- @Override
- public final double getRotationalUnitInertia () {
- return pow2(radius) / 2;
- }
-
- @Override
- public final Collection<Coordinate> getComponentBounds () {
- Collection<Coordinate> c = new ArrayList<Coordinate>();
- addBound(c, 0, radius);
- addBound(c, length, radius);
- return c;
- }
-
- /**
- * Accept a visitor to this MassObject in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this MassObject
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
+ private double radius;
+
+ private double radialPosition;
+ private double radialDirection;
+
+ private double shiftY = 0;
+ private double shiftZ = 0;
+
+
+ public MassObject() {
+ this(0.03, 0.015);
+ }
+
+ public MassObject(double length, double radius) {
+ super();
+
+ this.length = length;
+ this.radius = radius;
+
+ this.setRelativePosition(Position.TOP);
+ this.setPositionValue(0.0);
+ }
+
+
+ public void setLength(double length) {
+ length = Math.max(length, 0);
+ if (MathUtil.equals(this.length, length)) {
+ return;
+ }
+ this.length = length;
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ }
+
+
+ public final double getRadius() {
+ return radius;
+ }
+
+
+ public final void setRadius(double radius) {
+ radius = Math.max(radius, 0);
+ if (MathUtil.equals(this.radius, radius)) {
+ return;
+ }
+ this.radius = radius;
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ }
+
+
+
+ public final double getRadialPosition() {
+ return radialPosition;
+ }
+
+ public final void setRadialPosition(double radialPosition) {
+ radialPosition = Math.max(radialPosition, 0);
+ if (MathUtil.equals(this.radialPosition, radialPosition)) {
+ return;
+ }
+ this.radialPosition = radialPosition;
+ shiftY = radialPosition * Math.cos(radialDirection);
+ shiftZ = radialPosition * Math.sin(radialDirection);
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ }
+
+ public final double getRadialDirection() {
+ return radialDirection;
+ }
+
+ public final void setRadialDirection(double radialDirection) {
+ radialDirection = MathUtil.reduce180(radialDirection);
+ if (MathUtil.equals(this.radialDirection, radialDirection)) {
+ return;
+ }
+ this.radialDirection = radialDirection;
+ shiftY = radialPosition * Math.cos(radialDirection);
+ shiftZ = radialPosition * Math.sin(radialDirection);
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ }
+
+
+ /**
+ * Shift the coordinates according to the radial position and direction.
+ */
+ @Override
+ public final Coordinate[] shiftCoordinates(Coordinate[] array) {
+ for (int i = 0; i < array.length; i++) {
+ array[i] = array[i].add(0, shiftY, shiftZ);
+ }
+ return array;
+ }
+
+ @Override
+ public final Coordinate getComponentCG() {
+ return new Coordinate(length / 2, shiftY, shiftZ, getComponentMass());
+ }
+
+ @Override
+ public final double getLongitudinalUnitInertia() {
+ return (3 * pow2(radius) + pow2(length)) / 12;
+ }
+
+ @Override
+ public final double getRotationalUnitInertia() {
+ return pow2(radius) / 2;
+ }
+
+ @Override
+ public final Collection<Coordinate> getComponentBounds() {
+ Collection<Coordinate> c = new ArrayList<Coordinate>();
+ addBound(c, 0, radius);
+ addBound(c, length, radius);
+ return c;
+ }
+
+ /**
+ * Accept a visitor to this MassObject in the component hierarchy.
+ *
+ * @param theVisitor the visitor that will be called back with a reference to this MassObject
+ */
+ @Override
+ public void accept(final ComponentVisitor theVisitor) {
+ theVisitor.visit(this);
+ }
+
}
@Override
- public double getLongitudalUnitInertia() {
- return ringLongitudalUnitInertia(getOuterRadius(), getInnerRadius(), getLength());
+ public double getLongitudinalUnitInertia() {
+ return ringLongitudinalUnitInertia(getOuterRadius(), getInnerRadius(), getLength());
}
@Override
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.
* Make a deep copy of the Rocket structure. This method is exposed as public to allow
* for undo/redo system functionality.
*/
- @Override
@SuppressWarnings("unchecked")
+ @Override
public Rocket copyWithOriginalID() {
Rocket copy = (Rocket) super.copyWithOriginalID();
- copy.motorConfigurationIDs = (ArrayList<String>) this.motorConfigurationIDs.clone();
+ copy.motorConfigurationIDs = this.motorConfigurationIDs.clone();
copy.motorConfigurationNames =
(HashMap<String, String>) this.motorConfigurationNames.clone();
copy.resetListeners();
*/
@SuppressWarnings("unchecked")
public void loadFrom(Rocket r) {
- super.copyFrom(r);
+
+ // Store list of components to invalidate after event has been fired
+ List<RocketComponent> toInvalidate = this.copyFrom(r);
int type = ComponentChangeEvent.UNDO_CHANGE | ComponentChangeEvent.NONFUNCTIONAL_CHANGE;
if (this.massModID != r.massModID)
type |= ComponentChangeEvent.MASS_CHANGE;
if (this.aeroModID != r.aeroModID)
type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
- if (this.treeModID != r.treeModID)
- type |= ComponentChangeEvent.TREE_CHANGE;
+ // Loading a rocket is always a tree change since the component objects change
+ type |= ComponentChangeEvent.TREE_CHANGE;
this.modID = r.modID;
this.massModID = r.massModID;
this.refType = r.refType;
this.customReferenceLength = r.customReferenceLength;
- this.motorConfigurationIDs = (ArrayList<String>) r.motorConfigurationIDs.clone();
+ this.motorConfigurationIDs = r.motorConfigurationIDs.clone();
this.motorConfigurationNames =
(HashMap<String, String>) r.motorConfigurationNames.clone();
this.perfectFinish = r.perfectFinish;
if (!this.motorConfigurationIDs.contains(id))
defaultConfiguration.setMotorConfigurationID(null);
+ this.checkComponentStructure();
+
fireComponentChangeEvent(type);
+
+ // Invalidate obsolete components after event
+ for (RocketComponent c : toInvalidate) {
+ c.invalidate();
+ }
}
@Override
protected void fireComponentChangeEvent(ComponentChangeEvent e) {
- checkState();
-
- // Update modification ID's only for normal (not undo/redo) events
- if (!e.isUndoChange()) {
- modID = UniqueID.next();
- if (e.isMassChange())
- massModID = modID;
- if (e.isAerodynamicChange())
- aeroModID = modID;
- if (e.isTreeChange())
- treeModID = modID;
- if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
- functionalModID = modID;
- }
-
- // Check whether frozen
- if (freezeList != null) {
- log.debug("Rocket is in frozen state, adding event " + e + " info freeze list");
- freezeList.add(e);
- return;
- }
-
- log.debug("Firing rocket change event " + e);
-
- // Notify all components first
- Iterator<RocketComponent> iterator = this.deepIterator(true);
- while (iterator.hasNext()) {
- iterator.next().componentChanged(e);
- }
-
- // Notify all listeners
- Object[] listeners = listenerList.getListenerList();
- for (int i = listeners.length - 2; i >= 0; i -= 2) {
- if (listeners[i] == ComponentChangeListener.class) {
- ((ComponentChangeListener) listeners[i + 1]).componentChanged(e);
- } else if (listeners[i] == ChangeListener.class) {
- ((ChangeListener) listeners[i + 1]).stateChanged(e);
+ mutex.lock("fireComponentChangeEvent");
+ try {
+ checkState();
+
+ // Update modification ID's only for normal (not undo/redo) events
+ if (!e.isUndoChange()) {
+ modID = UniqueID.next();
+ if (e.isMassChange())
+ massModID = modID;
+ if (e.isAerodynamicChange())
+ aeroModID = modID;
+ if (e.isTreeChange())
+ treeModID = modID;
+ if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
+ functionalModID = modID;
+ }
+
+ // Check whether frozen
+ if (freezeList != null) {
+ log.debug("Rocket is in frozen state, adding event " + e + " info freeze list");
+ freezeList.add(e);
+ return;
}
+
+ log.debug("Firing rocket change event " + e);
+
+ // Notify all components first
+ Iterator<RocketComponent> iterator = this.iterator(true);
+ while (iterator.hasNext()) {
+ iterator.next().componentChanged(e);
+ }
+
+ // Notify all listeners
+ Object[] listeners = listenerList.getListenerList();
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ComponentChangeListener.class) {
+ ((ComponentChangeListener) listeners[i + 1]).componentChanged(e);
+ } else if (listeners[i] == ChangeListener.class) {
+ ((ChangeListener) listeners[i + 1]).stateChanged(e);
+ }
+ }
+ } finally {
+ mutex.unlock("fireComponentChangeEvent");
}
}
if (id == null)
return false;
- Iterator<RocketComponent> iterator = this.deepIterator();
+ Iterator<RocketComponent> iterator = this.iterator();
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
List<List<String>> list = new ArrayList<List<String>>();
List<String> currentList = null;
- Iterator<RocketComponent> iterator = this.deepIterator();
+ Iterator<RocketComponent> iterator = this.iterator();
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
}
@Override
- public double getLongitudalUnitInertia() {
+ public double getLongitudinalUnitInertia() {
return 0;
}
public boolean isCompatible(Class<? extends RocketComponent> type) {
return (Stage.class.isAssignableFrom(type));
}
-
- /**
- * Accept a visitor to this Rocket in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this Rocket
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
+
+ /**
+ * Accept a visitor to this Rocket in the component hierarchy.
+ *
+ * @param theVisitor the visitor that will be called back with a reference to this Rocket
+ */
+ @Override
+ public void accept(final ComponentVisitor theVisitor) {
+ theVisitor.visit(this);
+ }
}
package net.sf.openrocket.rocketcomponent;
+import java.awt.Color;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.swing.event.ChangeListener;
+
import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.logging.TraceException;
import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.Invalidator;
import net.sf.openrocket.util.LineStyle;
import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.SafetyMutex;
import net.sf.openrocket.util.UniqueID;
-import javax.swing.event.ChangeListener;
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EmptyStackException;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Stack;
-
-public abstract class RocketComponent implements ChangeSource, Cloneable,
- Iterable<RocketComponent> , Visitable<ComponentVisitor, RocketComponent> {
+public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent>,
+ Visitable<ComponentVisitor, RocketComponent> {
private static final LogHelper log = Application.getLogger();
/*
}
}
+ /**
+ * 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.
/**
* List of child components of this component.
*/
- private List<RocketComponent> children = new ArrayList<RocketComponent>();
+ private ArrayList<RocketComponent> children = new ArrayList<RocketComponent>();
//////// Parameters common to all components:
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()! ////
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.
/**
- * 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();
/**
* @see #isCompatible(Class)
*/
public final boolean isCompatible(RocketComponent c) {
+ mutex.verify();
return isCompatible(c.getClass());
}
/**
- * 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(']');
}
public final RocketComponent copy() {
RocketComponent clone = copyWithOriginalID();
- Iterator<RocketComponent> iterator = clone.deepIterator(true);
+ Iterator<RocketComponent> iterator = clone.iterator(true);
while (iterator.hasNext()) {
iterator.next().newID();
}
* @return A deep copy of the structure.
*/
protected RocketComponent copyWithOriginalID() {
- checkState();
- RocketComponent clone;
+ mutex.lock("copyWithOriginalID");
try {
- clone = (RocketComponent) this.clone();
- } catch (CloneNotSupportedException e) {
- throw new BugException("CloneNotSupportedException encountered, " +
- "report a bug!", e);
- }
-
- // Reset all parent/child information
- clone.parent = null;
- clone.children = new ArrayList<RocketComponent>();
-
- // Add copied children to the structure without firing events.
- for (RocketComponent child : this.children) {
- RocketComponent childCopy = child.copyWithOriginalID();
- // Don't use add method since it fires events
- clone.children.add(childCopy);
- childCopy.parent = clone;
+ checkState();
+ RocketComponent clone;
+ try {
+ clone = (RocketComponent) this.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new BugException("CloneNotSupportedException encountered, report a bug!", e);
+ }
+
+ // Reset the mutex
+ clone.mutex = SafetyMutex.newInstance();
+
+ // Reset all parent/child information
+ clone.parent = null;
+ clone.children = new ArrayList<RocketComponent>();
+
+ // Add copied children to the structure without firing events.
+ for (RocketComponent child : this.children) {
+ RocketComponent childCopy = child.copyWithOriginalID();
+ // Don't use add method since it fires events
+ clone.children.add(childCopy);
+ childCopy.parent = clone;
+ }
+
+ this.checkComponentStructure();
+ clone.checkComponentStructure();
+
+ return clone;
+ } finally {
+ mutex.unlock("copyWithOriginalID");
}
-
- return clone;
}
- /**
- * Accept a visitor to this RocketComponent in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this RocketComponent
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+ /**
+ * Accept a visitor to this RocketComponent in the component hierarchy.
+ *
+ * @param theVisitor the visitor that will be called back with a reference to this RocketComponent
+ */
+ @Override
+ public void accept(final ComponentVisitor theVisitor) {
+ theVisitor.visit(this);
+ }
+
////////////// Methods that may not be overridden ////////////
* to use the default color.
*/
public final Color getColor() {
+ mutex.verify();
return color;
}
public final LineStyle getLineStyle() {
+ mutex.verify();
return lineStyle;
}
* @return the override mass
*/
public final double getOverrideMass() {
+ mutex.verify();
return overrideMass;
}
* @return whether the mass is overridden
*/
public final boolean isMassOverridden() {
+ mutex.verify();
return massOverriden;
}
* @return the override CG
*/
public final Coordinate getOverrideCG() {
+ mutex.verify();
return getComponentCG().setX(overrideCGX);
}
* @return the x-coordinate of the override CG.
*/
public final double getOverrideCGX() {
+ mutex.verify();
return overrideCGX;
}
* @return whether the CG is overridden
*/
public final boolean isCGOverridden() {
+ mutex.verify();
return cgOverriden;
}
* @return whether the current mass and/or CG override overrides subcomponents as well.
*/
public boolean getOverrideSubcomponents() {
+ mutex.verify();
return overrideSubcomponents;
}
* @return whether the option to override subcomponents is currently enabled.
*/
public boolean isOverrideSubcomponentsEnabled() {
+ mutex.verify();
return isCGOverridden() || isMassOverridden();
}
* Get the user-defined name of the component.
*/
public final String getName() {
+ mutex.verify();
return name;
}
* @return the comment of the component.
*/
public final String getComment() {
+ mutex.verify();
return comment;
}
* Generate a new ID for this component.
*/
private final void newID() {
+ mutex.verify();
this.id = UniqueID.uuid();
}
* itself.
*/
public final double getLength() {
+ mutex.verify();
return length;
}
* but can be provided by a subclass.
*/
public final Position getRelativePosition() {
+ mutex.verify();
return relativePosition;
}
* @return the positional value.
*/
public final double getPositionValue() {
+ mutex.verify();
return position;
}
*/
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;
}
*/
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");
+ }
}
* @return The mass of the component or the given override mass.
*/
public final double getMass() {
+ mutex.verify();
if (massOverriden)
return overrideMass;
return getComponentMass();
/**
- * 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();
}
/**
* 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() +
" not currently compatible with component " + getComponentName());
}
- children.add(position, component);
+ children.add(index, component);
component.parent = this;
+ this.checkComponentStructure();
+ component.checkComponentStructure();
+
fireAddRemoveEvent(component);
}
checkState();
RocketComponent component = children.remove(n);
component.parent = null;
+
+ this.checkComponentStructure();
+ component.checkComponentStructure();
+
fireAddRemoveEvent(component);
}
*/
public final boolean removeChild(RocketComponent component) {
checkState();
+
+ component.checkComponentStructure();
+
if (children.remove(component)) {
component.parent = null;
+
+ this.checkComponentStructure();
+ component.checkComponentStructure();
+
fireAddRemoveEvent(component);
return true;
}
* 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);
}
}
* type of component removed.
*/
private void fireAddRemoveEvent(RocketComponent component) {
- Iterator<RocketComponent> iter = component.deepIterator(true);
+ Iterator<RocketComponent> iter = component.iterator(true);
int type = ComponentChangeEvent.TREE_CHANGE;
while (iter.hasNext()) {
RocketComponent c = iter.next();
public final int getChildCount() {
checkState();
+ this.checkComponentStructure();
return children.size();
}
public final RocketComponent getChild(int n) {
checkState();
+ this.checkComponentStructure();
return children.get(n);
}
- public final RocketComponent[] getChildren() {
+ public final List<RocketComponent> getChildren() {
checkState();
- return children.toArray(new RocketComponent[0]);
+ this.checkComponentStructure();
+ return children.clone();
}
*/
public final int getChildPosition(RocketComponent child) {
checkState();
+ this.checkComponentStructure();
return children.indexOf(child);
}
*/
public final RocketComponent findComponent(String idToFind) {
checkState();
- Iterator<RocketComponent> iter = this.deepIterator(true);
+ Iterator<RocketComponent> iter = this.iterator(true);
while (iter.hasNext()) {
RocketComponent c = iter.next();
if (c.getID().equals(idToFind))
}
+ // 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);
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;
}
*
* @throws IllegalStateException - if the root component is not a <code>Rocket</code>
*/
+ @Override
public void addChangeListener(ChangeListener l) {
checkState();
getRocket().addChangeListener(l);
*
* @param l Listener to remove
*/
+ @Override
public void removeChangeListener(ChangeListener l) {
if (this.parent != null) {
getRoot().removeChangeListener(l);
* @throws BugException if this component has been invalidated by {@link #copyFrom(RocketComponent)}.
*/
protected void checkState() {
- if (invalidated != null) {
- throw new BugException("This component has been invalidated. Cause is the point of invalidation.",
- invalidated);
- }
+ invalidator.check(true);
+ mutex.verify();
}
- /////////// Iterator implementation //////////
-
/**
- * Private inner class to implement the Iterator.
- *
- * This iterator is fail-fast if the root of the structure is a Rocket.
+ * Check that the local component structure is correct. This can be called after changing
+ * the component structure in order to verify the integrity.
+ * <p>
+ * TODO: Remove this after the "inconsistent internal state" bug has been corrected
*/
- private class RocketComponentIterator implements Iterator<RocketComponent> {
- // Stack holds iterators which still have some components left.
- private final Stack<Iterator<RocketComponent>> iteratorstack =
- new Stack<Iterator<RocketComponent>>();
-
- private final Rocket root;
- private final int treeModID;
-
- private final RocketComponent original;
- private boolean returnSelf = false;
-
- // Construct iterator with component's child's iterator, if it has elements
- public RocketComponentIterator(RocketComponent c, boolean returnSelf) {
-
- RocketComponent gp = c.getRoot();
- if (gp instanceof Rocket) {
- root = (Rocket) gp;
- treeModID = root.getTreeModID();
- } else {
- root = null;
- treeModID = -1;
+ public void checkComponentStructure() {
+ if (this.parent != null) {
+ // Test that this component is found in parent's children with == operator
+ if (!containsExact(this.parent.children, this)) {
+ throw new BugException("Inconsistent component structure detected, parent does not contain this " +
+ "component as a child, parent=" + parent.toDebugString() + " this=" + this.toDebugString());
}
-
- Iterator<RocketComponent> i = c.children.iterator();
- if (i.hasNext())
- iteratorstack.push(i);
-
- this.original = c;
- this.returnSelf = returnSelf;
- }
-
- public boolean hasNext() {
- checkState();
- checkID();
- if (returnSelf)
- return true;
- return !iteratorstack.empty(); // Elements remain if stack is not empty
}
-
- public RocketComponent next() {
- Iterator<RocketComponent> i;
-
- checkState();
- checkID();
-
- // Return original component first
- if (returnSelf) {
- returnSelf = false;
- return original;
- }
-
- // Peek first iterator from stack, throw exception if empty
- try {
- i = iteratorstack.peek();
- } catch (EmptyStackException e) {
- throw new NoSuchElementException("No further elements in " +
- "RocketComponent iterator");
+ for (RocketComponent child : this.children) {
+ if (child.parent != this) {
+ throw new BugException("Inconsistent component structure detected, child does not have this component " +
+ "as the parent, this=" + this.toDebugString() + " child=" + child.toDebugString() +
+ " child.parent=" + (child.parent == null ? "null" : child.parent.toDebugString()));
}
-
- // Retrieve next component of the iterator, remove iterator from stack if empty
- RocketComponent c = i.next();
- if (!i.hasNext())
- iteratorstack.pop();
-
- // Add iterator of component children to stack if it has children
- i = c.children.iterator();
- if (i.hasNext())
- iteratorstack.push(i);
-
- return c;
}
-
- private void checkID() {
- if (root != null) {
- if (root.getTreeModID() != treeModID) {
- throw new IllegalStateException("Rocket modified while being iterated");
- }
+ }
+
+ // Check whether the list contains exactly the searched-for component (with == operator)
+ private boolean containsExact(List<RocketComponent> haystack, RocketComponent needle) {
+ for (RocketComponent c : haystack) {
+ if (needle == c) {
+ return true;
}
}
-
- public void remove() {
- throw new UnsupportedOperationException("remove() not supported by " +
- "RocketComponent iterator");
- }
+ return false;
}
+
+ /////////// Iterators //////////
+
/**
* Returns an iterator that iterates over all children and sub-children.
- *
+ * <p>
* The iterator iterates through all children below this object, including itself if
- * returnSelf is true. The order of the iteration is not specified
+ * <code>returnSelf</code> is true. The order of the iteration is not specified
* (it may be specified in the future).
- *
+ * <p>
* If an iterator iterating over only the direct children of the component is required,
- * use component.getChildren().iterator()
+ * use <code>component.getChildren().iterator()</code>.
+ *
+ * TODO: HIGH: Remove this after merges have been done
*
* @param returnSelf boolean value specifying whether the component itself should be
* returned
* @return An iterator for the children and sub-children.
+ * @deprecated Use {@link #iterator(boolean)} instead
*/
+ @Deprecated
public final Iterator<RocketComponent> deepIterator(boolean returnSelf) {
- checkState();
- return new RocketComponentIterator(this, returnSelf);
+ return iterator(returnSelf);
}
+
/**
- * Returns an iterator that iterates over all children and sub-children.
+ * Returns an iterator that iterates over all children and sub-children, including itself.
+ * <p>
+ * This method is equivalent to <code>deepIterator(true)</code>.
*
- * The iterator does NOT return the component itself. It is thus equivalent to
- * deepIterator(false).
+ * TODO: HIGH: Remove this after merges have been done
*
- * @see #iterator()
- * @return An iterator for the children and sub-children.
+ * @return An iterator for this component, its children and sub-children.
+ * @deprecated Use {@link #iterator()} instead
*/
+ @Deprecated
public final Iterator<RocketComponent> deepIterator() {
+ return iterator();
+ }
+
+
+
+ /**
+ * Returns an iterator that iterates over all children and sub-children.
+ * <p>
+ * The iterator iterates through all children below this object, including itself if
+ * <code>returnSelf</code> is true. The order of the iteration is not specified
+ * (it may be specified in the future).
+ * <p>
+ * If an iterator iterating over only the direct children of the component is required,
+ * use <code>component.getChildren().iterator()</code>.
+ *
+ * @param returnSelf boolean value specifying whether the component itself should be
+ * returned
+ * @return An iterator for the children and sub-children.
+ */
+ public final Iterator<RocketComponent> iterator(boolean returnSelf) {
checkState();
- return new RocketComponentIterator(this, false);
+ return new RocketComponentIterator(this, returnSelf);
}
/**
- * Return an iterator that iterates of the children of the component. The iterator
- * does NOT recurse to sub-children nor return itself.
+ * Returns an iterator that iterates over this components, its children and sub-children.
+ * <p>
+ * This method is equivalent to <code>iterator(true)</code>.
*
- * @return An iterator for the children.
+ * @return An iterator for this component, its children and sub-children.
*/
+ @Override
public final Iterator<RocketComponent> iterator() {
- checkState();
- return Collections.unmodifiableList(children).iterator();
+ return iterator(true);
}
+
/**
* Compare component equality based on the ID of this component. Only the
* ID and class type is used for a basis of comparison.
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;
* mechanism and when converting a finset into a freeform fin set.
* This component must not have a parent, otherwise this method will fail.
* <p>
- * The fields are copied by reference, and the supplied component must not be used
- * after the call, as it is in an undefined state. This is enforced by invalidating
- * the source component.
+ * The child components in the source tree are copied into the current tree, however,
+ * the original components should not be used since they represent old copies of the
+ * components. It is recommended to invalidate them by calling {@link #invalidate()}.
+ * <p>
+ * This method returns a list of components that should be invalidated after references
+ * to them have been removed (for example by firing appropriate events). The list contains
+ * all children and sub-children of the current component and the entire component
+ * tree of <code>src</code>.
*
- * TODO: MEDIUM: Make general to copy all private/protected fields...
+ * @return a list of components that should not be used after this call.
*/
- protected void copyFrom(RocketComponent src) {
+ protected List<RocketComponent> copyFrom(RocketComponent src) {
checkState();
+ List<RocketComponent> toInvalidate = new ArrayList<RocketComponent>();
if (this.parent != null) {
- throw new UnsupportedOperationException("copyFrom called for non-root component "
- + this);
+ throw new UnsupportedOperationException("copyFrom called for non-root component, parent=" +
+ this.parent.toDebugString() + ", this=" + this.toDebugString());
}
- // Set parents and children
- this.children = src.children;
- src.children = new ArrayList<RocketComponent>();
+ // Add current structure to be invalidated
+ Iterator<RocketComponent> iterator = this.iterator(false);
+ while (iterator.hasNext()) {
+ toInvalidate.add(iterator.next());
+ }
- for (RocketComponent c : this.children) {
- c.parent = this;
+ // Remove previous components
+ for (RocketComponent child : this.children) {
+ child.parent = null;
}
+ this.children.clear();
+
+ // Copy new children to this component
+ for (RocketComponent c : src.children) {
+ RocketComponent copy = c.copyWithOriginalID();
+ this.children.add(copy);
+ copy.parent = this;
+ }
+
+ this.checkComponentStructure();
+ src.checkComponentStructure();
// Set all parameters
this.length = src.length;
this.comment = src.comment;
this.id = src.id;
- src.invalidated = new TraceException();
+ // Add source components to invalidation tree
+ for (RocketComponent c : src) {
+ toInvalidate.add(c);
+ }
+
+ return toInvalidate;
+ }
+
+ protected void invalidate() {
+ invalidator.invalidate();
+ }
+
+
+ ////////// Iterator implementation ///////////
+
+ /**
+ * Private inner class to implement the Iterator.
+ *
+ * This iterator is fail-fast if the root of the structure is a Rocket.
+ */
+ private static class RocketComponentIterator implements Iterator<RocketComponent> {
+ // Stack holds iterators which still have some components left.
+ private final Deque<Iterator<RocketComponent>> iteratorStack = new ArrayDeque<Iterator<RocketComponent>>();
+
+ private final Rocket root;
+ private final int treeModID;
+
+ private final RocketComponent original;
+ private boolean returnSelf = false;
+
+ // Construct iterator with component's child's iterator, if it has elements
+ public RocketComponentIterator(RocketComponent c, boolean returnSelf) {
+
+ RocketComponent gp = c.getRoot();
+ if (gp instanceof Rocket) {
+ root = (Rocket) gp;
+ treeModID = root.getTreeModID();
+ } else {
+ root = null;
+ treeModID = -1;
+ }
+
+ Iterator<RocketComponent> i = c.children.iterator();
+ if (i.hasNext())
+ iteratorStack.push(i);
+
+ this.original = c;
+ this.returnSelf = returnSelf;
+ }
+
+ @Override
+ public boolean hasNext() {
+ checkID();
+ if (returnSelf)
+ return true;
+ return !iteratorStack.isEmpty(); // Elements remain if stack is not empty
+ }
+
+ @Override
+ public RocketComponent next() {
+ Iterator<RocketComponent> i;
+
+ checkID();
+
+ // Return original component first
+ if (returnSelf) {
+ returnSelf = false;
+ return original;
+ }
+
+ // Peek first iterator from stack, throw exception if empty
+ i = iteratorStack.peek();
+ if (i == null) {
+ throw new NoSuchElementException("No further elements in RocketComponent iterator");
+ }
+
+ // Retrieve next component of the iterator, remove iterator from stack if empty
+ RocketComponent c = i.next();
+ if (!i.hasNext())
+ iteratorStack.pop();
+
+ // Add iterator of component children to stack if it has children
+ i = c.children.iterator();
+ if (i.hasNext())
+ iteratorStack.push(i);
+
+ return c;
+ }
+
+ private void checkID() {
+ if (root != null) {
+ if (root.getTreeModID() != treeModID) {
+ throw new IllegalStateException("Rocket modified while being iterated");
+ }
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove() not supported by " +
+ "RocketComponent iterator");
+ }
}
}
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;
@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;
}
/**
- * 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() {
r1 = getRadius(0);
x = 0;
- longitudalInertia = 0;
+ longitudinalInertia = 0;
rotationalInertia = 0;
double volume = 0;
}
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;
}
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() {
r1 = getRadius(0);
x = 0;
- longitudalInertia = 0;
+ longitudinalInertia = 0;
rotationalInertia = 0;
double surface = 0;
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;
}
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);
}
planCenter = -1;
volume = -1;
fullVolume = -1;
- longitudalInertia = -1;
+ longitudinalInertia = -1;
rotationalInertia = -1;
cg = null;
}
* The elements of the concrete object hierarchy are only visitable by an associated hierarchy of visitors,
* while these visitors are only able to visit the elements of that hierarchy.
*
- * The key concept regarding the Visitor pattern is to realize that Java will only ÒdiscriminateÓ the type of an
+ * The key concept regarding the Visitor pattern is to realize that Java will only "discriminate" the type of an
* object being called, not the type of an object being passed.
*
* In order for the type of two objects to be determinable to the JVM, each object must be the receiver of an
- * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes ÒknownÓ but the
+ * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes "known" but the
* concrete type of the argument is still unknown. <code>visit</code> is then called on the parameter object, passing
* the Visitable back, which has type and identity. Flow of control has now been 'double-dispatched' such that the
* type (and identity) of both objects are known.
* <T> The visitable (the concrete class that implements this interface)
*/
public interface Visitable<V extends Visitor<V, T>, T extends Visitable<V, T>> {
-
- /**
- * Any class in the hierarchy that allows itself to be visited will implement this method. The normal
- * behavior is that the visitor will invoke this method of a Visitable, passing itself. The Visitable
- * turns around calls the Visitor back. This idiom is also known as 'double-dispatching'.
- *
- * @param visitor the visitor that will be called back
- */
- public void accept(V visitor);
-
+
+ /**
+ * Any class in the hierarchy that allows itself to be visited will implement this method. The normal
+ * behavior is that the visitor will invoke this method of a Visitable, passing itself. The Visitable
+ * turns around calls the Visitor back. This idiom is also known as 'double-dispatching'.
+ *
+ * @param visitor the visitor that will be called back
+ */
+ public void accept(V visitor);
+
}
* The elements of the concrete object hierarchy are only visitable by an associated hierarchy of visitors,
* while these visitors are only able to visit the elements of that hierarchy.
*
- * The key concept regarding the Visitor pattern is to realize that Java will only ÒdiscriminateÓ the type of an
+ * The key concept regarding the Visitor pattern is to realize that Java will only "discriminate" the type of an
* object being called, not the type of an object being passed.
*
* In order for the type of two objects to be determinable to the JVM, each object must be the receiver of an
- * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes ÒknownÓ but the
+ * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes "known" but the
* concrete type of the argument is still unknown. <code>visit</code> is then called on the parameter object, passing
* the Visitable back, which has type and identity. Flow of control has now been 'double-dispatched' such that the
* type (and identity) of both objects are known.
* <T> The visitable
*/
public interface Visitor<V extends Visitor<V, T>, T extends Visitable<V, T>> {
-
- /**
- * The callback method. This method is the 2nd leg of the double-dispatch, having been invoked from a
- * corresponding <code>accept</code>.
- *
- * @param visitable the instance of the Visitable (the target of what is being visiting)
- */
- void visit(T visitable);
+
+ /**
+ * The callback method. This method is the 2nd leg of the double-dispatch, having been invoked from a
+ * corresponding <code>accept</code>.
+ *
+ * @param visitable the instance of the Visitable (the target of what is being visiting)
+ */
+ void visit(T visitable);
}
\ No newline at end of file
protected MassData calculateMassData(SimulationStatus status) throws SimulationException {
MassData mass;
Coordinate cg;
- double longitudalInertia, rotationalInertia;
+ double longitudinalInertia, rotationalInertia;
// Call pre-listener
mass = SimulationListenerHelper.firePreMassCalculation(status);
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;
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;
* @return a list of the variable values, or <code>null</code> if
* the variable type hasn't been added to this branch.
*/
- @SuppressWarnings("unchecked")
public List<Double> get(FlightDataType type) {
ArrayList<Double> list = values.get(type);
if (list == null)
return null;
- return (List<Double>) list.clone();
+ return list.clone();
}
/**
*
* @return the list of events during the flight.
*/
- @SuppressWarnings("unchecked")
public List<FlightEvent> getEvents() {
- return (List<FlightEvent>) events.clone();
+ return events.clone();
}
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;
* a name, you should use {@link #getType(String, UnitGroup)} to return the default unit type,
* or a new type if the name does not currently exist.
* <p>
- * Each type has a type name (description) and a unit group. The type is identified purely by its name.
+ * Each type has a type name (description), a unit group and a priority. The type is identified
+ * purely by its name case-insensitively. The unit group provides the units for the type.
+ * The priority is used to order the types. The pre-existing types are defined specific priority
+ * numbers, and other types have a default priority number that is after all other types.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
private static final int DEFAULT_PRIORITY = 999;
/** List of existing types. MUST BE DEFINED BEFORE ANY TYPES!! */
- private static final List<FlightDataType> EXISTING_TYPES = new ArrayList<FlightDataType>();
+ private static final Map<String, FlightDataType> EXISTING_TYPES = new HashMap<String, FlightDataType>();
//// Time
- public static final FlightDataType TYPE_TIME =
- newType("Time", UnitGroup.UNITS_FLIGHT_TIME, 1);
+ public static final FlightDataType TYPE_TIME = newType("Time", UnitGroup.UNITS_FLIGHT_TIME, 1);
//// Vertical position and motion
- public static final FlightDataType TYPE_ALTITUDE =
- newType("Altitude", UnitGroup.UNITS_DISTANCE, 10);
- public static final FlightDataType TYPE_VELOCITY_Z =
- newType("Vertical velocity", UnitGroup.UNITS_VELOCITY, 11);
- public static final FlightDataType TYPE_ACCELERATION_Z =
- newType("Vertical acceleration", UnitGroup.UNITS_ACCELERATION, 12);
+ public static final FlightDataType TYPE_ALTITUDE = newType("Altitude", UnitGroup.UNITS_DISTANCE, 10);
+ public static final FlightDataType TYPE_VELOCITY_Z = newType("Vertical velocity", UnitGroup.UNITS_VELOCITY, 11);
+ public static final FlightDataType TYPE_ACCELERATION_Z = newType("Vertical acceleration", UnitGroup.UNITS_ACCELERATION, 12);
//// Total motion
- public static final FlightDataType TYPE_VELOCITY_TOTAL =
- newType("Total velocity", UnitGroup.UNITS_VELOCITY, 20);
- public static final FlightDataType TYPE_ACCELERATION_TOTAL =
- newType("Total acceleration", UnitGroup.UNITS_ACCELERATION, 21);
+ public static final FlightDataType TYPE_VELOCITY_TOTAL = newType("Total velocity", UnitGroup.UNITS_VELOCITY, 20);
+ public static final FlightDataType TYPE_ACCELERATION_TOTAL = newType("Total acceleration", UnitGroup.UNITS_ACCELERATION, 21);
//// Lateral position and motion
- public static final FlightDataType TYPE_POSITION_X =
- newType("Position upwind", UnitGroup.UNITS_DISTANCE, 30);
- public static final FlightDataType TYPE_POSITION_Y =
- newType("Position parallel to wind", UnitGroup.UNITS_DISTANCE, 31);
- public static final FlightDataType TYPE_POSITION_XY =
- newType("Lateral distance", UnitGroup.UNITS_DISTANCE, 32);
- public static final FlightDataType TYPE_POSITION_DIRECTION =
- newType("Lateral direction", UnitGroup.UNITS_ANGLE, 33);
+ public static final FlightDataType TYPE_POSITION_X = newType("Position upwind", UnitGroup.UNITS_DISTANCE, 30);
+ public static final FlightDataType TYPE_POSITION_Y = newType("Position parallel to wind", UnitGroup.UNITS_DISTANCE, 31);
+ public static final FlightDataType TYPE_POSITION_XY = newType("Lateral distance", UnitGroup.UNITS_DISTANCE, 32);
+ public static final FlightDataType TYPE_POSITION_DIRECTION = newType("Lateral direction", UnitGroup.UNITS_ANGLE, 33);
- public static final FlightDataType TYPE_VELOCITY_XY =
- newType("Lateral velocity", UnitGroup.UNITS_VELOCITY, 34);
- public static final FlightDataType TYPE_ACCELERATION_XY =
- newType("Lateral acceleration", UnitGroup.UNITS_ACCELERATION, 35);
+ public static final FlightDataType TYPE_VELOCITY_XY = newType("Lateral velocity", UnitGroup.UNITS_VELOCITY, 34);
+ public static final FlightDataType TYPE_ACCELERATION_XY = newType("Lateral acceleration", UnitGroup.UNITS_ACCELERATION, 35);
//// Angular motion
//// 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);
* @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;
}
*/
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;
}
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
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;
}
return cg;
}
- public double getLongitudalInertia() {
- return longitudalInertia;
+ public double getLongitudinalInertia() {
+ return longitudinalInertia;
}
public double getRotationalInertia() {
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 + "]";
}
* 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%
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?
}
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);
public FlightConditions flightConditions;
- public double longitudalAcceleration = Double.NaN;
+ public double longitudinalAcceleration = Double.NaN;
public MassData massData;
@Override
public double getValue(SimulationStatus status) {
Iterator<RocketComponent> iterator =
- status.getConfiguration().getRocket().deepIterator();
+ status.getConfiguration().getRocket().iterator();
FinSet fin = null;
while (iterator.hasNext()) {
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;
private static LogHelper logger;
private static LogLevelBufferLogger logBuffer;
+ private static Translator translator = new DebugTranslator();
+
private static ThrustCurveMotorSetDatabase motorSetDatabase;
}
+ /**
+ * 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.
*/
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");
}
}
// 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);
}
public class CaliberUnit extends GeneralUnit {
-
+
public static final double DEFAULT_CALIBER = 0.01;
private final Configuration configuration;
private double caliber = -1;
-
+
/* Listener for rocket and configuration, resets the caliber to -1. */
private final ChangeListener listener = new ChangeListener() {
@Override
};
-
+
public CaliberUnit(Configuration configuration) {
super(1.0, "cal");
this.configuration = configuration;
return value * caliber;
}
-
+
@Override
public double toUnit(double value) {
if (caliber < 0)
return value / caliber;
}
-
+
// TODO: HIGH: Check caliber calculation method...
private void calculateCaliber() {
if (configuration != null) {
iterator = configuration.iterator();
} else if (rocket != null) {
- iterator = rocket.deepIterator();
+ iterator = rocket.iterator(false);
} else {
Collection<RocketComponent> set = Collections.emptyList();
iterator = set.iterator();
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);
}
}
*/
public class UnitGroup {
-
+
public static final UnitGroup UNITS_NONE;
public static final UnitGroup UNITS_MOTOR_DIMENSIONS;
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;
public static final UnitGroup UNITS_COEFFICIENT;
-// public static final UnitGroup UNITS_FREQUENCY;
-
+ // public static final UnitGroup UNITS_FREQUENCY;
+
public static final Map<String, UnitGroup> UNITS;
-
+
/*
* Note: Units may not use HTML tags.
+ *
+ * The scaling value "X" is obtained by "one of this unit is X of SI units"
+ * Type into Google for example: "1 in^2 in m^2"
*/
static {
UNITS_NONE = new UnitGroup();
UNITS_NONE.addUnit(Unit.NOUNIT2);
UNITS_LENGTH = new UnitGroup();
- UNITS_LENGTH.addUnit(new GeneralUnit(0.001,"mm"));
- UNITS_LENGTH.addUnit(new GeneralUnit(0.01,"cm"));
- UNITS_LENGTH.addUnit(new GeneralUnit(1,"m"));
- UNITS_LENGTH.addUnit(new GeneralUnit(0.0254,"in"));
- UNITS_LENGTH.addUnit(new GeneralUnit(0.3048,"ft"));
+ UNITS_LENGTH.addUnit(new GeneralUnit(0.001, "mm"));
+ UNITS_LENGTH.addUnit(new GeneralUnit(0.01, "cm"));
+ UNITS_LENGTH.addUnit(new GeneralUnit(1, "m"));
+ UNITS_LENGTH.addUnit(new GeneralUnit(0.0254, "in"));
+ UNITS_LENGTH.addUnit(new GeneralUnit(0.3048, "ft"));
UNITS_LENGTH.setDefaultUnit(1);
UNITS_MOTOR_DIMENSIONS = new UnitGroup();
- UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.001,"mm"));
- UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.01,"cm"));
- UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.0254,"in"));
+ UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.001, "mm"));
+ UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.01, "cm"));
+ UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.0254, "in"));
UNITS_MOTOR_DIMENSIONS.setDefaultUnit(0);
UNITS_DISTANCE = new UnitGroup();
- UNITS_DISTANCE.addUnit(new GeneralUnit(1,"m"));
- UNITS_DISTANCE.addUnit(new GeneralUnit(1000,"km"));
- UNITS_DISTANCE.addUnit(new GeneralUnit(0.3048,"ft"));
- UNITS_DISTANCE.addUnit(new GeneralUnit(0.9144,"yd"));
- UNITS_DISTANCE.addUnit(new GeneralUnit(1609.344,"mi"));
+ UNITS_DISTANCE.addUnit(new GeneralUnit(1, "m"));
+ UNITS_DISTANCE.addUnit(new GeneralUnit(1000, "km"));
+ UNITS_DISTANCE.addUnit(new GeneralUnit(0.3048, "ft"));
+ UNITS_DISTANCE.addUnit(new GeneralUnit(0.9144, "yd"));
+ UNITS_DISTANCE.addUnit(new GeneralUnit(1609.344, "mi"));
UNITS_AREA = new UnitGroup();
- UNITS_AREA.addUnit(new GeneralUnit(pow2(0.001),"mm" + SQUARED));
- UNITS_AREA.addUnit(new GeneralUnit(pow2(0.01),"cm" + SQUARED));
- UNITS_AREA.addUnit(new GeneralUnit(1,"m" + SQUARED));
- UNITS_AREA.addUnit(new GeneralUnit(pow2(0.0254),"in" + SQUARED));
- UNITS_AREA.addUnit(new GeneralUnit(pow2(0.3048),"ft" + SQUARED));
+ UNITS_AREA.addUnit(new GeneralUnit(pow2(0.001), "mm" + SQUARED));
+ UNITS_AREA.addUnit(new GeneralUnit(pow2(0.01), "cm" + SQUARED));
+ UNITS_AREA.addUnit(new GeneralUnit(1, "m" + SQUARED));
+ UNITS_AREA.addUnit(new GeneralUnit(pow2(0.0254), "in" + SQUARED));
+ UNITS_AREA.addUnit(new GeneralUnit(pow2(0.3048), "ft" + SQUARED));
UNITS_AREA.setDefaultUnit(1);
-
+
UNITS_STABILITY = new UnitGroup();
- UNITS_STABILITY.addUnit(new GeneralUnit(0.001,"mm"));
- UNITS_STABILITY.addUnit(new GeneralUnit(0.01,"cm"));
- UNITS_STABILITY.addUnit(new GeneralUnit(0.0254,"in"));
- UNITS_STABILITY.addUnit(new CaliberUnit((Rocket)null));
+ UNITS_STABILITY.addUnit(new GeneralUnit(0.001, "mm"));
+ UNITS_STABILITY.addUnit(new GeneralUnit(0.01, "cm"));
+ UNITS_STABILITY.addUnit(new GeneralUnit(0.0254, "in"));
+ UNITS_STABILITY.addUnit(new CaliberUnit((Rocket) null));
UNITS_STABILITY.setDefaultUnit(3);
UNITS_VELOCITY = new UnitGroup();
UNITS_VELOCITY.addUnit(new GeneralUnit(1, "m/s"));
- UNITS_VELOCITY.addUnit(new GeneralUnit(1/3.6, "km/h"));
+ UNITS_VELOCITY.addUnit(new GeneralUnit(1 / 3.6, "km/h"));
UNITS_VELOCITY.addUnit(new GeneralUnit(0.3048, "ft/s"));
UNITS_VELOCITY.addUnit(new GeneralUnit(0.44704, "mph"));
UNITS_ACCELERATION.addUnit(new GeneralUnit(1, "m/s" + SQUARED));
UNITS_ACCELERATION.addUnit(new GeneralUnit(0.3048, "ft/s" + SQUARED));
-
UNITS_MASS = new UnitGroup();
- UNITS_MASS.addUnit(new GeneralUnit(0.001,"g"));
- UNITS_MASS.addUnit(new GeneralUnit(1,"kg"));
- UNITS_MASS.addUnit(new GeneralUnit(0.0283495231,"oz"));
- UNITS_MASS.addUnit(new GeneralUnit(0.45359237,"lb"));
+ UNITS_MASS.addUnit(new GeneralUnit(0.001, "g"));
+ UNITS_MASS.addUnit(new GeneralUnit(1, "kg"));
+ UNITS_MASS.addUnit(new GeneralUnit(0.0283495231, "oz"));
+ UNITS_MASS.addUnit(new GeneralUnit(0.45359237, "lb"));
+
+ UNITS_INERTIA = new UnitGroup();
+ UNITS_INERTIA.addUnit(new GeneralUnit(0.0001, "kg" + DOT + "cm" + SQUARED));
+ UNITS_INERTIA.addUnit(new GeneralUnit(1, "kg" + DOT + "m" + SQUARED));
+ UNITS_INERTIA.addUnit(new GeneralUnit(1.82899783e-5, "oz" + DOT + "in" + SQUARED));
+ UNITS_INERTIA.addUnit(new GeneralUnit(0.000292639653, "lb" + DOT + "in" + SQUARED));
+ UNITS_INERTIA.addUnit(new GeneralUnit(0.0421401101, "lb" + DOT + "ft" + SQUARED));
+ UNITS_INERTIA.addUnit(new GeneralUnit(1.35581795, "lbf" + DOT + "ft" + DOT + "s" + SQUARED));
+ UNITS_INERTIA.setDefaultUnit(1);
UNITS_ANGLE = new UnitGroup();
UNITS_ANGLE.addUnit(new DegreeUnit());
- UNITS_ANGLE.addUnit(new FixedPrecisionUnit("rad",0.01));
+ UNITS_ANGLE.addUnit(new FixedPrecisionUnit("rad", 0.01));
UNITS_DENSITY_BULK = new UnitGroup();
- UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1000,"g/cm" + CUBED));
- UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1,"kg/m" + CUBED));
- UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1729.99404,"oz/in" + CUBED));
- UNITS_DENSITY_BULK.addUnit(new GeneralUnit(16.0184634,"lb/ft" + CUBED));
-
+ UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1000, "g/cm" + CUBED));
+ UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1, "kg/m" + CUBED));
+ UNITS_DENSITY_BULK.addUnit(new GeneralUnit(1729.99404, "oz/in" + CUBED));
+ UNITS_DENSITY_BULK.addUnit(new GeneralUnit(16.0184634, "lb/ft" + CUBED));
+
UNITS_DENSITY_SURFACE = new UnitGroup();
- UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(10,"g/cm" + SQUARED));
- UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.001,"g/m" + SQUARED));
- UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(1,"kg/m" + SQUARED));
- UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(43.9418487,"oz/in" + SQUARED));
- UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.305151727,"oz/ft" + SQUARED));
- UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(4.88242764,"lb/ft" + SQUARED));
+ UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(10, "g/cm" + SQUARED));
+ UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.001, "g/m" + SQUARED));
+ UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(1, "kg/m" + SQUARED));
+ UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(43.9418487, "oz/in" + SQUARED));
+ UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(0.305151727, "oz/ft" + SQUARED));
+ UNITS_DENSITY_SURFACE.addUnit(new GeneralUnit(4.88242764, "lb/ft" + SQUARED));
UNITS_DENSITY_SURFACE.setDefaultUnit(1);
-
+
UNITS_DENSITY_LINE = new UnitGroup();
- UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.001,"g/m"));
- UNITS_DENSITY_LINE.addUnit(new GeneralUnit(1,"kg/m"));
- UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.0930102465,"oz/ft"));
-
+ UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.001, "g/m"));
+ UNITS_DENSITY_LINE.addUnit(new GeneralUnit(1, "kg/m"));
+ UNITS_DENSITY_LINE.addUnit(new GeneralUnit(0.0930102465, "oz/ft"));
+
UNITS_FORCE = new UnitGroup();
- UNITS_FORCE.addUnit(new GeneralUnit(1,"N"));
- UNITS_FORCE.addUnit(new GeneralUnit(4.44822162,"lbf"));
- UNITS_FORCE.addUnit(new GeneralUnit(9.80665,"kgf"));
-
+ UNITS_FORCE.addUnit(new GeneralUnit(1, "N"));
+ UNITS_FORCE.addUnit(new GeneralUnit(4.44822162, "lbf"));
+ UNITS_FORCE.addUnit(new GeneralUnit(9.80665, "kgf"));
+
UNITS_IMPULSE = new UnitGroup();
- UNITS_IMPULSE.addUnit(new GeneralUnit(1,"Ns"));
- UNITS_IMPULSE.addUnit(new GeneralUnit(4.44822162, "lbf"+DOT+"s"));
-
+ UNITS_IMPULSE.addUnit(new GeneralUnit(1, "Ns"));
+ UNITS_IMPULSE.addUnit(new GeneralUnit(4.44822162, "lbf" + DOT + "s"));
+
UNITS_TIME_STEP = new UnitGroup();
UNITS_TIME_STEP.addUnit(new FixedPrecisionUnit("ms", 1, 0.001));
UNITS_TIME_STEP.addUnit(new FixedPrecisionUnit("s", 0.01));
UNITS_TIME_STEP.setDefaultUnit(1);
-
+
UNITS_SHORT_TIME = new UnitGroup();
- UNITS_SHORT_TIME.addUnit(new GeneralUnit(1,"s"));
-
+ UNITS_SHORT_TIME.addUnit(new GeneralUnit(1, "s"));
+
UNITS_FLIGHT_TIME = new UnitGroup();
- UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(1,"s"));
- UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(60,"min"));
+ UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(1, "s"));
+ UNITS_FLIGHT_TIME.addUnit(new GeneralUnit(60, "min"));
UNITS_ROLL = new UnitGroup();
UNITS_ROLL.addUnit(new GeneralUnit(1, "rad/s"));
- UNITS_ROLL.addUnit(new GeneralUnit(2*Math.PI, "r/s"));
- UNITS_ROLL.addUnit(new GeneralUnit(2*Math.PI/60, "rpm"));
+ UNITS_ROLL.addUnit(new GeneralUnit(2 * Math.PI, "r/s"));
+ UNITS_ROLL.addUnit(new GeneralUnit(2 * Math.PI / 60, "rpm"));
UNITS_ROLL.setDefaultUnit(1);
-
+
UNITS_TEMPERATURE = new UnitGroup();
UNITS_TEMPERATURE.addUnit(new FixedPrecisionUnit("K", 1));
- UNITS_TEMPERATURE.addUnit(new TemperatureUnit(1, 273.15, DEGREE+"C"));
- UNITS_TEMPERATURE.addUnit(new TemperatureUnit(5.0/9.0, 459.67, DEGREE+"F"));
+ UNITS_TEMPERATURE.addUnit(new TemperatureUnit(1, 273.15, DEGREE + "C"));
+ UNITS_TEMPERATURE.addUnit(new TemperatureUnit(5.0 / 9.0, 459.67, DEGREE + "F"));
UNITS_TEMPERATURE.setDefaultUnit(1);
UNITS_PRESSURE = new UnitGroup();
UNITS_PRESSURE.addUnit(new FixedPrecisionUnit("mbar", 1, 1.0e2));
UNITS_PRESSURE.addUnit(new FixedPrecisionUnit("bar", 0.001, 1.0e5));
UNITS_PRESSURE.addUnit(new FixedPrecisionUnit("atm", 0.001, 1.01325e5));
- UNITS_PRESSURE.addUnit(new GeneralUnit(101325.0/760.0, "mmHg"));
+ UNITS_PRESSURE.addUnit(new GeneralUnit(101325.0 / 760.0, "mmHg"));
UNITS_PRESSURE.addUnit(new GeneralUnit(3386.389, "inHg"));
UNITS_PRESSURE.addUnit(new GeneralUnit(6894.75729, "psi"));
UNITS_PRESSURE.addUnit(new GeneralUnit(1, "Pa"));
-
+
UNITS_RELATIVE = new UnitGroup();
- UNITS_RELATIVE.addUnit(new FixedPrecisionUnit(""+ZWSP, 0.01, 1.0));
+ UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + ZWSP, 0.01, 1.0));
UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("%", 1, 0.01));
- UNITS_RELATIVE.addUnit(new FixedPrecisionUnit(""+PERMILLE, 1, 0.001));
+ UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + PERMILLE, 1, 0.001));
UNITS_RELATIVE.setDefaultUnit(1);
-
+
UNITS_ROUGHNESS = new UnitGroup();
- UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.000001, MICRO+"m"));
+ UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.000001, MICRO + "m"));
UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.0000254, "mil"));
-
+
UNITS_COEFFICIENT = new UnitGroup();
- UNITS_COEFFICIENT.addUnit(new FixedPrecisionUnit(""+ZWSP, 0.01)); // zero-width space
-
+ UNITS_COEFFICIENT.addUnit(new FixedPrecisionUnit("" + ZWSP, 0.01)); // zero-width space
+
// This is not used by OpenRocket, and not extensively tested:
-// UNITS_FREQUENCY = new UnitGroup();
-// UNITS_FREQUENCY.addUnit(new GeneralUnit(1, "s"));
-// UNITS_FREQUENCY.addUnit(new GeneralUnit(0.001, "ms"));
-// UNITS_FREQUENCY.addUnit(new GeneralUnit(0.000001, MICRO + "s"));
-// UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz"));
-// UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz"));
-// UNITS_FREQUENCY.setDefaultUnit(3);
+ // UNITS_FREQUENCY = new UnitGroup();
+ // UNITS_FREQUENCY.addUnit(new GeneralUnit(1, "s"));
+ // UNITS_FREQUENCY.addUnit(new GeneralUnit(0.001, "ms"));
+ // UNITS_FREQUENCY.addUnit(new GeneralUnit(0.000001, MICRO + "s"));
+ // UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz"));
+ // UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz"));
+ // UNITS_FREQUENCY.setDefaultUnit(3);
- HashMap<String,UnitGroup> map = new HashMap<String,UnitGroup>();
+ HashMap<String, UnitGroup> map = new HashMap<String, UnitGroup>();
map.put("NONE", UNITS_NONE);
map.put("LENGTH", UNITS_LENGTH);
map.put("MOTOR_DIMENSIONS", UNITS_MOTOR_DIMENSIONS);
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);
map.put("RELATIVE", UNITS_RELATIVE);
map.put("ROUGHNESS", UNITS_ROUGHNESS);
map.put("COEFFICIENT", UNITS_COEFFICIENT);
-
+
UNITS = Collections.unmodifiableMap(map);
}
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);
}
return new StabilityUnitGroup(config);
}
-
+
//////////////////////////////////////////////////////
-
+
private ArrayList<Unit> units = new ArrayList<Unit>();
private int defaultUnit = 0;
public int getUnitCount() {
return units.size();
}
-
+
public Unit getDefaultUnit() {
return units.get(defaultUnit);
}
}
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
*/
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;
* @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);
}
public void addUnit(int n, Unit u) {
- units.add(n,u);
+ units.add(n, u);
}
public void removeUnit(int n) {
-
-
+
+
/**
* Creates a new Value object with the specified value and the default unit of this group.
*
}
-
-
+
+
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
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);
}
}
if (i >= units.size()) {
- throw new NumberFormatException("unknown unit "+unit);
+ throw new NumberFormatException("unknown unit " + unit);
}
}
///////////////////////////
-
+
/**
* A private class that switches the CaliberUnit to a rocket-specific CaliberUnit.
* All other methods are passed through to UNITS_STABILITY.
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);
}
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
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");
--- /dev/null
+package net.sf.openrocket.util;
+
+import java.util.Collection;
+
+/**
+ * An implementation of an ArrayList with a type-safe {@link #clone()} method.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ArrayList<E> extends java.util.ArrayList<E> {
+
+ public ArrayList() {
+ super();
+ }
+
+ public ArrayList(Collection<? extends E> c) {
+ super(c);
+ }
+
+ public ArrayList(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public ArrayList<E> clone() {
+ return (ArrayList<E>) super.clone();
+ }
+
+}
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;
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;
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;
window.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
+ log.debug("Clearing all models of window " + window);
setNullModels(window);
MemoryManagement.collectable(window);
}
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) {
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) {
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) {
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) {
-
/**
* A mouse listener that toggles the state of a boolean value in a table model
* when clicked on another column of the table.
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");
}
/**
- * 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;
}
--- /dev/null
+package net.sf.openrocket.util;
+
+/**
+ * An object that can be invalidated (in some sense of the word). After calling the
+ * invalidate method the object should not be used any more and it may enforce
+ * disusage for certain methods.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface Invalidatable {
+
+ /**
+ * Invalidate this object.
+ */
+ public void invalidate();
+
+}
--- /dev/null
+package net.sf.openrocket.util;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.logging.TraceException;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A class that performs object invalidation functions.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class Invalidator implements Invalidatable {
+ private static final boolean USE_CHECKS = Prefs.useSafetyChecks();
+
+ private static final LogHelper log = Application.getLogger();
+
+ private final Object monitorable;
+ private TraceException invalidated = null;
+
+
+ /**
+ * Sole constructor. The parameter is used when writing error messages, and
+ * is not referenced otherwise.
+ *
+ * @param monitorable the object this invalidator is monitoring (may be null or a descriptive string)
+ */
+ public Invalidator(Object monitorable) {
+ this.monitorable = monitorable;
+ }
+
+
+ /**
+ * Check whether the object has been invalidated. Depending on the parameter either
+ * a BugException is thrown or a warning about the object access is logged.
+ *
+ * @param throwException whether to throw an exception or log a warning.
+ * @return <code>true</code> when the object has not been invalidated, <code>false</code> if it has
+ * @throws BugException if the object has been invalidated and <code>throwException</code> is true.
+ */
+ public boolean check(boolean throwException) {
+ if (invalidated != null) {
+ if (throwException) {
+ throw new BugException(monitorable + ": This object has been invalidated", invalidated);
+ } else {
+ log.warn(1, monitorable + ": This object has been invalidated",
+ new TraceException("Usage was attempted here", invalidated));
+ }
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Check whether the object has been invalidated.
+ * @return <code>true</code> if the object has been invalidated, <code>false</code> otherwise.
+ */
+ public boolean isInvalidated() {
+ return invalidated != null;
+ }
+
+
+ @Override
+ public void invalidate() {
+ if (USE_CHECKS) {
+ if (invalidated != null) {
+ log.warn(1, monitorable + ": This object has already been invalidated, ignoring", invalidated);
+ }
+ invalidated = new TraceException("Invalidation occurred here");
+ }
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.util;
+
+import java.util.Iterator;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.logging.TraceException;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A list of listeners of a specific type. This class contains various utility,
+ * safety and debugging methods for handling listeners.
+ * <p>
+ * Note that unlike normal listener implementations, this list does NOT allow the
+ * exact same listener (equality using ==) twice. While adding a listener twice to
+ * a event source would in principle be valid, in practice it's most likely a bug.
+ * For example the Swing implementation Sun JRE contains such bugs.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ * @param <T> the type of the listeners.
+ */
+public class ListenerList<T> implements Invalidatable, Iterable<T> {
+ private static final LogHelper log = Application.getLogger();
+
+ private final ArrayList<ListenerData<T>> listeners = new ArrayList<ListenerData<T>>();
+ private final TraceException instantiationLocation;
+
+ private TraceException invalidated = null;
+
+
+ /**
+ * Sole contructor.
+ */
+ public ListenerList() {
+ this.instantiationLocation = new TraceException(1, 1);
+ }
+
+
+ /**
+ * Adds the specified listener to this list. The listener is not added if it
+ * already is in the list (checked by the equality operator ==). This method throws
+ * a BugException if {@link #invalidate()} has been called.
+ *
+ * @param listener the listener to add.
+ * @return whether the listeners was actually added to the list.
+ * @throws BugException if this listener list has been invalidated.
+ */
+ public boolean addListener(T listener) {
+ checkState(true);
+
+ ListenerData<T> data = new ListenerData<T>(listener);
+ if (listeners.contains(data)) {
+ log.warn(1, "Attempting to add duplicate listener " + listener);
+ return false;
+ }
+ listeners.add(data);
+ return true;
+ }
+
+
+ /**
+ * Remove the specified listener from the list. The listener is removed based on the
+ * quality operator ==, not by the equals() method.
+ *
+ * @param listener the listener to remove.
+ * @return whether the listener was actually removed.
+ */
+ public boolean removeListener(T listener) {
+ checkState(false);
+
+ Iterator<ListenerData<T>> iterator = listeners.iterator();
+ while (iterator.hasNext()) {
+ if (iterator.next().listener == listener) {
+ iterator.remove();
+ log.verbose(1, "Removing listener " + listener);
+ return true;
+ }
+ }
+ log.info(1, "Attempting to remove non-existant listener " + listener);
+ return false;
+ }
+
+
+ /**
+ * Return the number of listeners in this list.
+ */
+ public int getListenerCount() {
+ return listeners.size();
+ }
+
+
+ /**
+ * Return an iterator that iterates of the listeners. This iterator is backed by
+ * a copy of the iterator list, so {@link #addListener(Object)} and {@link #removeListener(Object)}
+ * may be called while iterating the list without effect on the iteration. The returned
+ * iterator does not support the {@link Iterator#remove()} method.
+ */
+ @Override
+ public Iterator<T> iterator() {
+ checkState(false);
+ return new ListenerDataIterator();
+ }
+
+ /**
+ * Return the instantiation location of this listener list.
+ * @return the location where this listener list was instantiated.
+ */
+ public TraceException getInstantiationLocation() {
+ return instantiationLocation;
+ }
+
+
+ /**
+ * Invalidate this listener list. Invalidation removes all listeners from the list.
+ * After invalidation {@link #addListener(Object)} will throw an exception, the other
+ * methods produce a warning log message.
+ */
+ @Override
+ public void invalidate() {
+ this.invalidated = new TraceException("Invalidation occurred at this point");
+ if (!listeners.isEmpty()) {
+ log.info("Invalidating " + this + " while still having listeners " + listeners);
+ }
+ listeners.clear();
+ }
+
+
+ public boolean isInvalidated() {
+ return this.invalidated != null;
+ }
+
+
+ private void checkState(boolean error) {
+ if (this.invalidated != null) {
+ if (error) {
+ throw new BugException(this + ": this ListenerList has been invalidated", invalidated);
+ } else {
+ log.warn(1, this + ": this ListenerList has been invalidated",
+ new TraceException("ListenerList was attempted to be used here", invalidated));
+ }
+ }
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("ListenerList[");
+
+ if (this.invalidated != null) {
+ sb.append("INVALIDATED]");
+ return sb.toString();
+ }
+
+ if (listeners.isEmpty()) {
+ sb.append("empty");
+ } else {
+ boolean first = true;
+ for (ListenerData<T> l : listeners) {
+ if (!first) {
+ sb.append("; ");
+ }
+ first = false;
+ sb.append(l);
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+
+ /**
+ * A class containing data about a listener.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ * @param <T> the listener type
+ */
+ public static class ListenerData<T> {
+ private final T listener;
+ private final long addTimestamp;
+ private final TraceException addLocation;
+ private long accessTimestamp;
+
+ /**
+ * Sole constructor.
+ */
+ private ListenerData(T listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener is null");
+ }
+ this.listener = listener;
+ this.addTimestamp = System.currentTimeMillis();
+ this.accessTimestamp = this.addTimestamp;
+ this.addLocation = new TraceException("Listener " + listener + " add position");
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof ListenerData))
+ return false;
+ ListenerData<?> other = (ListenerData<?>) obj;
+ return this.listener == other.listener;
+ }
+
+ @Override
+ public int hashCode() {
+ return listener.hashCode();
+ }
+
+ /**
+ * Return the listener.
+ */
+ public T getListener() {
+ return listener;
+ }
+
+ /**
+ * Return the millisecond timestamp when this listener was added to the
+ * listener list.
+ */
+ public long getAddTimestamp() {
+ return addTimestamp;
+ }
+
+ /**
+ * Return the location where this listener was added to the listener list.
+ */
+ public TraceException getAddLocation() {
+ return addLocation;
+ }
+
+ /**
+ * Return the millisecond timestamp when this listener was last accessed through
+ * the listener list iterator.
+ */
+ public long getAccessTimestamp() {
+ return accessTimestamp;
+ }
+ }
+
+
+ private class ListenerDataIterator implements Iterator<T> {
+ private final Iterator<ListenerData<T>> iterator = listeners.clone().iterator();
+
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public T next() {
+ ListenerData<T> data = iterator.next();
+ data.accessTimestamp = System.currentTimeMillis();
+ return data.listener;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Remove not supported");
+ }
+ }
+
+}
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;
/**
* to
*/
private static List<MemoryData> objects = new LinkedList<MemoryData>();
- private static int callCount = 0;
+ private static int collectableCallCount = 0;
+
+
+ private static List<WeakReference<ListenerList<?>>> listenerLists = new LinkedList<WeakReference<ListenerList<?>>>();
+ private static int listenerCallCount = 0;
private MemoryManagement() {
}
log.debug("Adding object into collectable list: " + o);
objects.add(new MemoryData(o));
- callCount++;
- if (callCount % PURGE_CALL_COUNT == 0) {
- purge();
+ collectableCallCount++;
+ if (collectableCallCount % PURGE_CALL_COUNT == 0) {
+ purgeCollectables();
}
}
/**
- * Return the number of times {@link #collectable(Object)} has been called.
- * @return the number of times {@link #collectable(Object)} has been called.
+ * Return a list of MemoryData objects corresponding to the objects that have been
+ * registered by {@link #collectable(Object)} and have not been garbage-collected properly.
+ * This method first calls <code>System.gc()</code> multiple times to attempt to
+ * force any remaining garbage collection.
+ *
+ * @return a list of MemoryData objects for objects that have not yet been garbage-collected.
*/
- public static synchronized int getCallCount() {
- return callCount;
+ public static synchronized List<MemoryData> getRemainingCollectableObjects() {
+ for (int i = 0; i < 5; i++) {
+ System.runFinalization();
+ System.gc();
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ }
+ }
+ purgeCollectables();
+ return new ArrayList<MemoryData>(objects);
}
+
+
/**
- * Return a list of MemoryData objects corresponding to the objects that have been
- * registered by {@link #collectable(Object)} and have not been garbage-collected properly.
+ * Register a new ListenerList object. This can be used to monitor freeing of listeners
+ * and find memory leaks. The objects are held by a weak reference, allowing them to be
+ * garbage-collected.
+ *
+ * @param list the listener list to register
+ */
+ public static synchronized void registerListenerList(ListenerList<?> list) {
+ listenerLists.add(new WeakReference<ListenerList<?>>(list));
+ listenerCallCount++;
+ if (listenerCallCount % PURGE_CALL_COUNT == 0) {
+ purgeListeners();
+ }
+
+ }
+
+ /**
+ * Return a list of listener list objects corresponding to the objects that have been
+ * registered by {@link #registerListenerList(ListenerList)} and have not been garbage-collected yet.
* This method first calls <code>System.gc()</code> multiple times to attempt to
* force any remaining garbage collection.
*
- * @return a list of MemoryData objects for objects that have not yet been garbage-collected.
+ * @return a list of listener list objects that have not yet been garbage-collected.
*/
- public static synchronized ArrayList<MemoryData> getRemainingObjects() {
+ public static synchronized List<ListenerList<?>> getRemainingListenerLists() {
for (int i = 0; i < 5; i++) {
System.runFinalization();
System.gc();
} catch (InterruptedException e) {
}
}
- purge();
- return new ArrayList<MemoryData>(objects);
+ purgeListeners();
+ List<ListenerList<?>> list = new ArrayList<ListenerList<?>>();
+ for (WeakReference<ListenerList<?>> ref : listenerLists) {
+ ListenerList<?> l = ref.get();
+ if (l != null) {
+ list.add(l);
+ }
+ }
+ return list;
}
/**
* Purge all cleared references from the object list.
*/
- private static void purge() {
+ private static void purgeCollectables() {
int origCount = objects.size();
Iterator<MemoryData> iterator = objects.iterator();
while (iterator.hasNext()) {
}
+ /**
+ * Purge all cleared references from the object list.
+ */
+ private static void purgeListeners() {
+ int origCount = listenerLists.size();
+ Iterator<WeakReference<ListenerList<?>>> iterator = listenerLists.iterator();
+ while (iterator.hasNext()) {
+ WeakReference<ListenerList<?>> ref = iterator.next();
+ if (ref.get() == null) {
+ iterator.remove();
+ }
+ }
+ log.debug(listenerLists.size() + " of " + origCount + " listener lists remaining after purge.");
+ }
+
/**
* A value object class containing data of a discarded object reference.
*/
package net.sf.openrocket.util;
+/**
+ * Interface describing objects whose state changes can be monitored based on
+ * a modification ID number. If a specific object has the same modification ID
+ * at two different points in time, the object state is guaranteed to be the same.
+ * This does not necessarily hold between two different instances of an object type.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
public interface Monitorable {
-
+
/**
* Return a modification ID unique to the current state of this object and contained objects.
* The general contract is that if a specific object has the same modification ID at two moments
* increasing a modification counter or retrieving a new unique ID every time a value is set.
* <p>
* Objects that contain other objects with a mutable state may for example return the sum of the
- * object's own modification ID, a modification ID counter (initially zero) and the modification ID
+ * object's own modification ID, a modification ID counter (initially zero) and the modification IDs
* of the contained objects. When a mutable object is set, the modification counter is increased by
* the modification ID of the current object in order to preserve monotonicity.
* <p>
if (dpi < 10)
dpi = 960;
- return ((double) dpi) / 10.0;
+ return (dpi) / 10.0;
}
}
-
+ /**
+ * 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);
*/
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.
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().
*/
throw new BugException("wrapped exception without cause", e);
}
if (cause instanceof RuntimeException) {
- throw (RuntimeException)cause;
+ throw (RuntimeException) cause;
}
if (cause instanceof Error) {
- throw (Error)cause;
+ throw (Error) cause;
}
throw new BugException("wrapped exception occurred", cause);
}
-
+
/**
* Throws an exception if method not found.
*/
public static Reflection.Method findMethodStatic(
Class<? extends RocketComponent> componentClass,
String method, Class<?>... params) {
- Reflection.Method m = findMethod(ROCKETCOMPONENT_PACKAGE, componentClass,
+ Reflection.Method m = findMethod(ROCKETCOMPONENT_PACKAGE, componentClass,
"", method, params);
if (m == null) {
throw new BugException("Could not find method for componentClass="
- +componentClass+" method="+method);
+ + componentClass + " method=" + method);
}
return m;
}
-
- public static Reflection.Method findMethod(String pack, RocketComponent component,
- String method, Class<?>...params) {
- return findMethod(pack,component.getClass(),"",method,params);
+
+ public static Reflection.Method findMethod(String pack, RocketComponent component,
+ String method, Class<?>... params) {
+ return findMethod(pack, component.getClass(), "", method, params);
}
- public static Reflection.Method findMethod(String pack, RocketComponent component,
+ public static Reflection.Method findMethod(String pack, RocketComponent component,
String suffix, String method, Class<?>... params) {
return findMethod(pack, component.getClass(), suffix, method, params);
}
-
- public static Reflection.Method findMethod(String pack,
- Class<? extends RocketComponent> componentClass,
+
+ public static Reflection.Method findMethod(String pack,
+ Class<? extends RocketComponent> componentClass,
String suffix, String method, Class<?>... params) {
Class<?> currentclass;
String name;
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;
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
}
} 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");
}
}
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;
/**
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
-public class SafetyMutex {
+public abstract class SafetyMutex {
private static final LogHelper log = Application.getLogger();
- // Package-private for unit testing
- static volatile boolean errorReported = false;
- // lockingThread is set when this mutex is locked.
- Thread lockingThread = null;
- // Stack of places that have locked this mutex
- final LinkedList<String> locations = new LinkedList<String>();
+ /**
+ * Return a new instance of a safety mutex. This returns an actual implementation
+ * or a bogus implementation depending on whether safety checks are enabled or disabled.
+ *
+ * @return a new instance of a safety mutex
+ */
+ public static SafetyMutex newInstance() {
+ if (Prefs.useSafetyChecks()) {
+ return new ConcreteSafetyMutex();
+ } else {
+ return new BogusSafetyMutex();
+ }
+ }
+
/**
* Verify that this mutex is unlocked, but don't lock it. This has the same effect
* as <code>mutex.lock(); mutex.unlock();</code> and is useful for methods that return
*
* @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();
/**
*
* @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,
* @param location a location string matching that which locked the mutex
* @return whether the unlocking was successful (this normally doesn't need to be checked)
*/
- public synchronized boolean unlock(String location) {
- try {
-
- if (location == null) {
- ExceptionHandler.handleErrorCondition("location is null");
- location = "";
- }
- checkState(false);
-
+ public abstract boolean unlock(String location);
+
+
- // Check that the mutex is locked
- if (lockingThread == null) {
- error("Mutex was not locked", false);
- return false;
+ /**
+ * Bogus implementation of a safety mutex (used when safety checking is not performed).
+ */
+ static class BogusSafetyMutex extends SafetyMutex {
+
+ @Override
+ public void verify() {
+ }
+
+ @Override
+ public void lock(String location) {
+ }
+
+ @Override
+ public boolean unlock(String location) {
+ return true;
+ }
+
+ }
+
+ /**
+ * A concrete, working implementation of a safety mutex.
+ */
+ static class ConcreteSafetyMutex extends SafetyMutex {
+ private static final boolean STORE_LOCKING_LOCATION = (System.getProperty("openrocket.debug.mutexlocation") != null);
+
+ // Package-private for unit testing
+ static volatile boolean errorReported = false;
+
+ // lockingThread is set when this mutex is locked.
+ Thread lockingThread = null;
+ // longingLocation is set when lockingThread is, if STORE_LOCKING_LOCATION is true
+ TraceException lockingLocation = null;
+ // Stack of places that have locked this mutex
+ final LinkedList<String> locations = new LinkedList<String>();
+
+
+
+ @Override
+ public synchronized void verify() {
+ checkState(true);
+ if (lockingThread != null && lockingThread != Thread.currentThread()) {
+ error("Mutex is already locked", true);
}
-
- // Check that the mutex is locked by the current thread
- if (lockingThread != Thread.currentThread()) {
- error("Mutex is being unlocked from differerent thread than where it was locked", false);
- return false;
+ }
+
+
+
+ @Override
+ public synchronized void lock(String location) {
+ if (location == null) {
+ throw new IllegalArgumentException("location is null");
}
+ checkState(true);
- // Check that the unlock location is correct
- String lastLocation = locations.pop();
- if (!location.equals(lastLocation)) {
- locations.push(lastLocation);
- error("Mutex unlocking location does not match locking location, location=" + location, false);
- return false;
+ Thread currentThread = Thread.currentThread();
+ if (lockingThread != null && lockingThread != currentThread) {
+ error("Mutex is already locked", true);
}
- // Unlock the mutex if the last one
- if (locations.isEmpty()) {
- lockingThread = null;
+ lockingThread = currentThread;
+ if (STORE_LOCKING_LOCATION) {
+ lockingLocation = new TraceException("Location where mutex was locked '" + location + "'");
}
- return true;
- } catch (Exception e) {
- ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " +
- "locking thread=" + lockingThread + " locations=" + locations, e);
- return false;
+ locations.push(location);
}
- }
-
-
+
+
- /**
- * Check that the internal state of the mutex (lockingThread vs. locations) is correct.
- */
- private void checkState(boolean throwException) {
- /*
- * Disallowed states:
- * lockingThread == null && !locations.isEmpty()
- * lockingThread != null && locations.isEmpty()
- */
- if ((lockingThread == null) ^ (locations.isEmpty())) {
- // Clear the mutex only after error() has executed (and possibly thrown an exception)
+
+ @Override
+ public synchronized boolean unlock(String location) {
try {
- error("Mutex data inconsistency occurred - unlocking mutex", throwException);
- } finally {
- lockingThread = null;
- locations.clear();
+
+ if (location == null) {
+ ExceptionHandler.handleErrorCondition("location is null");
+ location = "";
+ }
+ checkState(false);
+
+
+ // Check that the mutex is locked
+ if (lockingThread == null) {
+ error("Mutex was not locked", false);
+ return false;
+ }
+
+ // Check that the mutex is locked by the current thread
+ if (lockingThread != Thread.currentThread()) {
+ error("Mutex is being unlocked from differerent thread than where it was locked", false);
+ return false;
+ }
+
+ // Check that the unlock location is correct
+ String lastLocation = locations.pop();
+ if (!location.equals(lastLocation)) {
+ locations.push(lastLocation);
+ error("Mutex unlocking location does not match locking location, location=" + location, false);
+ return false;
+ }
+
+ // Unlock the mutex if the last one
+ if (locations.isEmpty()) {
+ lockingThread = null;
+ lockingLocation = null;
+ }
+ return true;
+ } catch (Exception e) {
+ ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " +
+ "locking thread=" + lockingThread + " locations=" + locations, e);
+ return false;
}
}
- }
-
-
- /**
- * Raise an error. The first occurrence is passed directly to the exception handler,
- * later errors are simply logged.
- */
- private void error(String message, boolean throwException) {
- message = message +
- ", current thread = " + Thread.currentThread() +
- ", locking thread=" + lockingThread +
- ", locking locations=" + locations;
- ConcurrencyException ex = new ConcurrencyException(message);
- if (!errorReported) {
- errorReported = true;
- ExceptionHandler.handleErrorCondition(ex);
- } else {
- log.error(message, ex);
+
+ /**
+ * Check that the internal state of the mutex (lockingThread vs. locations) is correct.
+ */
+ private void checkState(boolean throwException) {
+ /*
+ * Disallowed states:
+ * lockingThread == null && !locations.isEmpty()
+ * lockingThread != null && locations.isEmpty()
+ */
+ if ((lockingThread == null) ^ (locations.isEmpty())) {
+ // Clear the mutex only after error() has executed (and possibly thrown an exception)
+ try {
+ error("Mutex data inconsistency occurred - unlocking mutex", throwException);
+ } finally {
+ lockingThread = null;
+ lockingLocation = null;
+ locations.clear();
+ }
+ }
}
- if (throwException) {
- throw ex;
+
+ /**
+ * Raise an error. The first occurrence is passed directly to the exception handler,
+ * later errors are simply logged.
+ */
+ private void error(String message, boolean throwException) {
+ message = message +
+ ", current thread = " + Thread.currentThread() +
+ ", locking thread=" + lockingThread +
+ ", locking locations=" + locations;
+
+ ConcurrencyException ex = new ConcurrencyException(message, lockingLocation);
+
+ if (!errorReported) {
+ errorReported = true;
+ ExceptionHandler.handleErrorCondition(ex);
+ } else {
+ log.error(message, ex);
+ }
+
+ if (throwException) {
+ throw ex;
+ }
}
}
-
}
*/
package net.sf.openrocket.file.rocksim;
-import junit.framework.TestCase;
-
import java.lang.reflect.Field;
+import java.util.List;
+import junit.framework.TestCase;
import net.sf.openrocket.rocketcomponent.RocketComponent;
/**
* A base class for the Rocksim tests. Includes code from the junitx.addons project.
*/
public abstract class RocksimTestBase extends TestCase {
-
- /**
- * Test constructor.
- *
- * @param name the name of the test to run.
- */
- public RocksimTestBase(String name) {
- super(name);
- }
-
- public void assertContains (RocketComponent child, RocketComponent[] components) {
- for (int i = 0; i < components.length; i++) {
- if (components[i].equals(child)) {
- return;
- }
- }
- fail("Component array did not contain child.");
- }
-
- /**
- * Returns the value of the field on the specified object. The name
- * parameter is a <code>String</code> specifying the simple name of the
- * desired field.<p>
- *
- * The object is first searched for any matching field. If no matching
- * field is found, the superclasses are recursively searched.
- *
- * @exception NoSuchFieldException if a field with the specified name is
- * not found.
- */
- public static Object getField(Object object,
- String name)
- throws NoSuchFieldException {
- if (object == null) {
- throw new IllegalArgumentException("Invalid null object argument");
- }
- for (Class cls = object.getClass();
- cls != null;
- cls = cls.getSuperclass()) {
- try {
- Field field = cls.getDeclaredField(name);
- field.setAccessible(true);
- return field.get(object);
- } catch (Exception ex) {
- /* in case of an exception, we will throw a new
- * NoSuchFieldException object */
- ;
- }
- }
- throw new NoSuchFieldException("Could get value for field " +
- object.getClass().getName() + "." + name);
- }
-
- /**
- * Returns the value of the field on the specified class. The name
- * parameter is a <code>String</code> specifying the simple name of the
- * desired field.<p>
- *
- * The class is first searched for any matching field. If no matching
- * field is found, the superclasses are recursively searched.
- *
- * @exception NoSuchFieldException if a field with the specified name is
- * not found.
- */
- public static Object getField(Class cls,
- String name)
- throws NoSuchFieldException {
- if (cls == null) {
- throw new IllegalArgumentException("Invalid null cls argument");
- }
- Class base = cls;
- while (base != null) {
- try {
- Field field = base.getDeclaredField(name);
- field.setAccessible(true);
- return field.get(base);
- } catch (Exception ex) {
- /* in case of an exception, we will throw a new
- * NoSuchFieldException object */
- ;
- }
- base = base.getSuperclass();
- }
- throw new NoSuchFieldException("Could get value for static field " +
- cls.getName() + "." + name);
- }
-
+
+ /**
+ * Test constructor.
+ *
+ * @param name the name of the test to run.
+ */
+ public RocksimTestBase(String name) {
+ super(name);
+ }
+
+ public void assertContains(RocketComponent child, List<RocketComponent> components) {
+ assertTrue("Components did not contain child", components.contains(child));
+ }
+
+ /**
+ * Returns the value of the field on the specified object. The name
+ * parameter is a <code>String</code> specifying the simple name of the
+ * desired field.<p>
+ *
+ * The object is first searched for any matching field. If no matching
+ * field is found, the superclasses are recursively searched.
+ *
+ * @exception NoSuchFieldException if a field with the specified name is
+ * not found.
+ */
+ public static Object getField(Object object,
+ String name)
+ throws NoSuchFieldException {
+ if (object == null) {
+ throw new IllegalArgumentException("Invalid null object argument");
+ }
+ for (Class cls = object.getClass(); cls != null; cls = cls.getSuperclass()) {
+ try {
+ Field field = cls.getDeclaredField(name);
+ field.setAccessible(true);
+ return field.get(object);
+ } catch (Exception ex) {
+ /* in case of an exception, we will throw a new
+ * NoSuchFieldException object */
+ ;
+ }
+ }
+ throw new NoSuchFieldException("Could get value for field " +
+ object.getClass().getName() + "." + name);
+ }
+
+ /**
+ * Returns the value of the field on the specified class. The name
+ * parameter is a <code>String</code> specifying the simple name of the
+ * desired field.<p>
+ *
+ * The class is first searched for any matching field. If no matching
+ * field is found, the superclasses are recursively searched.
+ *
+ * @exception NoSuchFieldException if a field with the specified name is
+ * not found.
+ */
+ public static Object getField(Class cls,
+ String name)
+ throws NoSuchFieldException {
+ if (cls == null) {
+ throw new IllegalArgumentException("Invalid null cls argument");
+ }
+ Class base = cls;
+ while (base != null) {
+ try {
+ Field field = base.getDeclaredField(name);
+ field.setAccessible(true);
+ return field.get(base);
+ } catch (Exception ex) {
+ /* in case of an exception, we will throw a new
+ * NoSuchFieldException object */
+ ;
+ }
+ base = base.getSuperclass();
+ }
+ throw new NoSuchFieldException("Could get value for static field " +
+ cls.getName() + "." + name);
+ }
+
}
--- /dev/null
+package net.sf.openrocket.gui;
+
+import org.junit.Test;
+
+public class TestGUI {
+
+ @Test
+ public void test() {
+ // No-op
+ }
+
+}
import org.junit.Test;
public class LogLevelBufferLoggerTest {
-
+
@Test
public void testLogger() {
LogLevelBufferLogger logger = new LogLevelBufferLogger(4);
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());
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 =
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);
}
--- /dev/null
+package net.sf.openrocket.optimization.rocketoptimization.modifiers;
+
+import static net.sf.openrocket.util.MathUtil.EPSILON;
+import static org.junit.Assert.assertEquals;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.unit.UnitGroup;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestGenericModifier {
+
+ private TestValue value;
+ private GenericModifier<TestValue> gm;
+ private Simulation sim;
+
+ @Before
+ public void setup() {
+ value = new TestValue();
+ sim = new Simulation(new Rocket());
+
+ gm = new GenericModifier<TestGenericModifier.TestValue>("Test modifier", null,
+ UnitGroup.UNITS_NONE, 2.0, TestValue.class, "value") {
+ @Override
+ protected TestValue getModifiedObject(Simulation simulation) {
+ Assert.assertTrue(simulation == sim);
+ return value;
+ }
+ };
+
+ gm.setMinValue(0.5);
+ gm.setMaxValue(5.5);
+ }
+
+ @Test
+ public void testGetCurrentValue() throws OptimizationException {
+ value.d = 1.0;
+ assertEquals(2.0, gm.getCurrentValue(sim), EPSILON);
+ value.d = 2.0;
+ assertEquals(4.0, gm.getCurrentValue(sim), EPSILON);
+ }
+
+ @Test
+ public void testGetCurrentScaledValue() throws OptimizationException {
+ value.d = 0.0;
+ assertEquals(-0.1, gm.getCurrentScaledValue(sim), EPSILON);
+ value.d = 1.0;
+ assertEquals(0.3, gm.getCurrentScaledValue(sim), EPSILON);
+ value.d = 2.0;
+ assertEquals(0.7, gm.getCurrentScaledValue(sim), EPSILON);
+ value.d = 3.0;
+ assertEquals(1.1, gm.getCurrentScaledValue(sim), EPSILON);
+ }
+
+ @Test
+ public void testModify() throws OptimizationException {
+ value.d = 0.0;
+ gm.modify(sim, -0.5);
+ assertEquals(-1.0, value.d, EPSILON);
+
+ gm.modify(sim, 0.0);
+ assertEquals(0.25, value.d, EPSILON);
+
+ gm.modify(sim, 0.5);
+ assertEquals(1.5, value.d, EPSILON);
+
+ gm.modify(sim, 1.0);
+ assertEquals(2.75, value.d, EPSILON);
+
+ gm.modify(sim, 1.5);
+ assertEquals(4.0, value.d, EPSILON);
+ }
+
+ public void testSingularRange() throws OptimizationException {
+ gm.setMinValue(1.0);
+ gm.setMaxValue(1.0);
+ value.d = 0.5;
+ assertEquals(0.0, gm.getCurrentScaledValue(sim), EPSILON);
+ value.d = 1.0;
+ assertEquals(0.5, gm.getCurrentScaledValue(sim), EPSILON);
+ value.d = 1.00001;
+ assertEquals(1.0, gm.getCurrentScaledValue(sim), EPSILON);
+ }
+
+ public class TestValue {
+ private double d;
+
+ public double getValue() {
+ return d;
+ }
+
+ public void setValue(double value) {
+ this.d = value;
+ }
+ }
+
+}
public static void assertDeepEquality(RocketComponent c1, RocketComponent c2) {
assertEquality(c1, c2);
- Iterator<RocketComponent> i1 = c1.iterator();
- Iterator<RocketComponent> i2 = c2.iterator();
+ Iterator<RocketComponent> i1 = c1.getChildren().iterator();
+ Iterator<RocketComponent> i2 = c2.getChildren().iterator();
while (i1.hasNext()) {
assertTrue("iterator continues", i2.hasNext());
RocketComponent comp1 = i1.next();
boolean allowNameDifference) {
assertSimilarity(c1, c2, allowNameDifference);
- Iterator<RocketComponent> i1 = c1.iterator();
- Iterator<RocketComponent> i2 = c2.iterator();
+ Iterator<RocketComponent> i1 = c1.getChildren().iterator();
+ Iterator<RocketComponent> i2 = c2.getChildren().iterator();
while (i1.hasNext()) {
assertTrue("iterator continues", i2.hasNext());
RocketComponent comp1 = i1.next();
Rocket r1 = net.sf.openrocket.util.TestRockets.makeBigBlue();
Rocket r2 = net.sf.openrocket.util.TestRockets.makeBigBlue();
- Iterator<RocketComponent> i1 = r1.deepIterator(true);
- Iterator<RocketComponent> i2 = r2.deepIterator(true);
+ Iterator<RocketComponent> i1 = r1.iterator(true);
+ Iterator<RocketComponent> i2 = r2.iterator(true);
while (i1.hasNext()) {
assertTrue(i2.hasNext());
}
- 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();
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);
@Test
public void testDoubleLocking() {
- SafetyMutex m = new SafetyMutex();
+ SafetyMutex.ConcreteSafetyMutex m = new SafetyMutex.ConcreteSafetyMutex();
// Test double locking
m.verify();
@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"));
@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() {
}
}
+
+ 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");
+ }
+
}