package net.sf.openrocket.rocketcomponent;
import java.awt.Color;
-import java.util.ArrayList;
+import java.util.ArrayDeque;
import java.util.Collection;
-import java.util.Collections;
-import java.util.EmptyStackException;
+import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
-import java.util.Stack;
import javax.swing.event.ChangeListener;
-import net.sf.openrocket.logging.TraceException;
+import net.sf.openrocket.l10n.Translator;
+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.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;
-public abstract class RocketComponent implements ChangeSource, Cloneable,
- Iterable<RocketComponent> {
+public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent> {
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
/*
* Text is suitable to the form
*/
public enum Position {
/** Position relative to the top of the parent component. */
- TOP("Top of the parent component"),
+ //// Top of the parent component
+ TOP(trans.get("RocketComponent.Position.TOP")),
/** Position relative to the middle of the parent component. */
- MIDDLE("Middle of the parent component"),
+ //// Middle of the parent component
+ MIDDLE(trans.get("RocketComponent.Position.MIDDLE")),
/** Position relative to the bottom of the parent component. */
- BOTTOM("Bottom of the parent component"),
+ //// Bottom of the parent component
+ BOTTOM(trans.get("RocketComponent.Position.BOTTOM")),
/** Position after the parent component (for body components). */
- AFTER("After the parent component"),
+ //// After the parent component
+ AFTER(trans.get("RocketComponent.Position.AFTER")),
/** Specify an absolute X-coordinate position. */
- ABSOLUTE("Tip of the nose cone");
+ //// Tip of the nose cone
+ ABSOLUTE(trans.get("RocketComponent.Position.ABSOLUTE"));
private String title;
}
}
+ /**
+ * 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()! ////
newID();
}
-
-
-
-
//////////// Methods that must be implemented ////////////
/**
- * 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();
/**
- * Return the rotational (around the x-axis) unitary moment of inertia.
+ * Return the rotational (around the x-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 rotational unitary moment of inertia of this component.
*/
public abstract double getRotationalUnitInertia();
* Test whether this component allows any children components. This method must
* return true if and only if {@link #isCompatible(Class)} returns true for any
* rocket component class.
- *
+ *
* @return <code>true</code> if children can be attached to this component, <code>false</code> otherwise.
*/
public abstract boolean allowsChildren();
* is enforced by the <code>addChild()</code> methods. The return value of this method
* may change to reflect the current state of this component (e.g. two components of some
* type cannot be placed as children).
- *
+ *
* @param type The RocketComponent class type to add.
* @return Whether such a component can be added.
*/
/**
* Test whether the given component can be added to this component. This is equivalent
* to calling <code>isCompatible(c.getClass())</code>.
- *
+ *
* @param c Component to test.
* @return Whether the component can be added.
* @see #isCompatible(Class)
*/
public final boolean isCompatible(RocketComponent c) {
- checkState();
+ mutex.verify();
return isCompatible(c.getClass());
}
/**
* Return a collection of bounding coordinates. The coordinates must be such that
* the component is fully enclosed in their convex hull.
- *
+ *
* @return a collection of coordinates that bound the component.
*/
public abstract Collection<Coordinate> getComponentBounds();
* coordinate for each cluster.
* <p>
* The default implementation simply returns the array, and thus produces no shift.
- *
+ *
* @param c an array of coordinates to shift.
* @return an array of shifted coordinates. The method may modify the contents
* of the passed array and return the array itself.
/**
- * Called when any component in the tree fires a ComponentChangeEvent. This is by
- * default a no-op, but subclasses may override this method to e.g. invalidate
- * cached data. The overriding method *must* call
+ * Called when any component in the tree fires a ComponentChangeEvent. This is by
+ * default a no-op, but subclasses may override this method to e.g. invalidate
+ * cached data. The overriding method *must* call
* <code>super.componentChanged(e)</code> at some point.
- *
+ *
* @param e The event fired
*/
protected void componentChanged(ComponentChangeEvent e) {
/**
- * 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(']');
}
* Make a deep copy of the rocket component tree structure from this component
* downwards for copying purposes. Each component in the copy will be assigned
* a new component ID, making it a safe copy. This method does not fire any events.
- *
+ *
* @return A deep copy of the structure.
*/
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();
}
* undo/redo mechanism. This method should not be used for other purposes,
* such as copy/paste. This method does not fire any events.
* <p>
- * This method must be overridden by any component that refers to mutable objects,
+ * This method must be overridden by any component that refers to mutable objects,
* or if some fields should not be copied. This should be performed by
* <code>RocketComponent c = super.copyWithOriginalID();</code> and then cloning/modifying
* the appropriate fields.
* <p>
* This is not performed as serializing/deserializing for performance reasons.
- *
+ *
* @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;
}
* to use the default color.
*/
public final Color getColor() {
- checkState();
+ mutex.verify();
return color;
}
/**
- * Set the color of the object to use in 2D figures.
+ * Set the color of the object to use in 2D figures.
*/
public final void setColor(Color c) {
- checkState();
if ((color == null && c == null) ||
(color != null && color.equals(c)))
return;
+ checkState();
this.color = c;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
public final LineStyle getLineStyle() {
- checkState();
+ mutex.verify();
return lineStyle;
}
public final void setLineStyle(LineStyle style) {
- checkState();
if (this.lineStyle == style)
return;
+ checkState();
this.lineStyle = style;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
/**
* Get the current override mass. The mass is not necessarily in use
* at the moment.
- *
+ *
* @return the override mass
*/
public final double getOverrideMass() {
- checkState();
+ mutex.verify();
return overrideMass;
}
/**
* Set the current override mass. The mass is not set to use by this
* method.
- *
+ *
* @param m the override mass
*/
public final void setOverrideMass(double m) {
+ if (MathUtil.equals(m, overrideMass))
+ return;
checkState();
overrideMass = Math.max(m, 0);
if (massOverriden)
/**
* Return whether mass override is active for this component. This does NOT
* take into account whether a parent component is overriding the mass.
- *
+ *
* @return whether the mass is overridden
*/
public final boolean isMassOverridden() {
- checkState();
+ mutex.verify();
return massOverriden;
}
/**
* Set whether the mass is currently overridden.
- *
+ *
* @param o whether the mass is overridden
*/
public final void setMassOverridden(boolean o) {
- checkState();
- if (massOverriden != o) {
- massOverriden = o;
- fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ if (massOverriden == o) {
+ return;
}
+ checkState();
+ massOverriden = o;
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
/**
* Return the current override CG. The CG is not necessarily overridden.
- *
+ *
* @return the override CG
*/
public final Coordinate getOverrideCG() {
- checkState();
+ mutex.verify();
return getComponentCG().setX(overrideCGX);
}
/**
* Return the x-coordinate of the current override CG.
- *
+ *
* @return the x-coordinate of the override CG.
*/
public final double getOverrideCGX() {
- checkState();
+ mutex.verify();
return overrideCGX;
}
/**
* Set the current override CG to (x,0,0).
- *
+ *
* @param x the x-coordinate of the override CG to set.
*/
public final void setOverrideCGX(double x) {
- checkState();
if (MathUtil.equals(overrideCGX, x))
return;
+ checkState();
this.overrideCGX = x;
if (isCGOverridden())
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
/**
* Return whether the CG is currently overridden.
- *
+ *
* @return whether the CG is overridden
*/
public final boolean isCGOverridden() {
- checkState();
+ mutex.verify();
return cgOverriden;
}
/**
* Set whether the CG is currently overridden.
- *
+ *
* @param o whether the CG is overridden
*/
public final void setCGOverridden(boolean o) {
- checkState();
- if (cgOverriden != o) {
- cgOverriden = o;
- fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ if (cgOverriden == o) {
+ return;
}
+ checkState();
+ cgOverriden = o;
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
* always or never overrides subcomponents. In this case the subclass should
* also override {@link #isOverrideSubcomponentsEnabled()} to return
* <code>false</code>.
- *
+ *
* @return whether the current mass and/or CG override overrides subcomponents as well.
*/
public boolean getOverrideSubcomponents() {
- checkState();
+ mutex.verify();
return overrideSubcomponents;
}
/**
* Set whether the mass and/or CG override overrides all subcomponent values
* as well. See {@link #getOverrideSubcomponents()} for details.
- *
+ *
* @param override whether the mass and/or CG override overrides all subcomponent.
*/
public void setOverrideSubcomponents(boolean override) {
- checkState();
- if (overrideSubcomponents != override) {
- overrideSubcomponents = override;
- fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ if (overrideSubcomponents == override) {
+ return;
}
+ checkState();
+ overrideSubcomponents = override;
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
/**
* <p>
* This method may be overridden if the setting of overriding subcomponents
* cannot be set.
- *
+ *
* @return whether the option to override subcomponents is currently enabled.
*/
public boolean isOverrideSubcomponentsEnabled() {
- checkState();
+ mutex.verify();
return isCGOverridden() || isMassOverridden();
}
* Get the user-defined name of the component.
*/
public final String getName() {
+ mutex.verify();
return name;
}
* the default name, currently the component name.
*/
public final void setName(String name) {
+ if (this.name.equals(name)) {
+ return;
+ }
checkState();
if (name == null || name.matches("^\\s*$"))
this.name = getComponentName();
/**
* Return the comment of the component. The component may contain multiple lines
* using \n as a newline separator.
- *
+ *
* @return the comment of the component.
*/
public final String getComment() {
- checkState();
+ mutex.verify();
return comment;
}
/**
* Set the comment of the component.
- *
+ *
* @param comment the comment of the component.
*/
public final void setComment(String comment) {
- checkState();
if (this.comment.equals(comment))
return;
+ checkState();
if (comment == null)
this.comment = "";
else
/**
* Returns the unique ID of the component.
- *
+ *
* @return the ID of the component.
*/
public final String getID() {
* Generate a new ID for this component.
*/
private final void newID() {
+ mutex.verify();
this.id = UniqueID.uuid();
}
* Get the characteristic length of the component, for example the length of a body tube
* of the length of the root chord of a fin. This is used in positioning the component
* relative to its parent.
- *
+ *
* If the length of a component is settable, the class must define the setter method
* itself.
*/
public final double getLength() {
- checkState();
+ mutex.verify();
return length;
}
* but can be provided by a subclass.
*/
public final Position getRelativePosition() {
- checkState();
+ mutex.verify();
return relativePosition;
}
* do not support setting the relative position. A component that does support
* it should override this with a public method that simply calls this
* supermethod AND fire a suitable ComponentChangeEvent.
- *
+ *
* @param position the relative positioning.
*/
protected void setRelativePosition(RocketComponent.Position position) {
- checkState();
if (this.relativePosition == position)
return;
+ checkState();
// Update position so as not to move the component
if (this.parent != null) {
/**
* Get the position value of the component. The exact meaning of the value is
* dependent on the current relative positioning.
- *
+ *
* @return the positional value.
*/
public final double getPositionValue() {
- checkState();
+ mutex.verify();
return position;
}
* do not support setting the relative position. A component that does support
* it should override this with a public method that simply calls this
* supermethod AND fire a suitable ComponentChangeEvent.
- *
+ *
* @param value the position value of the component.
*/
public void setPositionValue(double value) {
- checkState();
if (MathUtil.equals(this.position, value))
return;
+ checkState();
this.position = value;
}
/**
- * Return coordinate <code>c</code> described in the coordinate system of
+ * Return coordinate <code>c</code> described in the coordinate system of
* <code>dest</code>. If <code>dest</code> is <code>null</code> returns
* absolute coordinates.
* <p>
* This method returns an array of coordinates, each of which represents a
* position of the coordinate in clustered cases. The array is guaranteed
- * to contain at least one element.
+ * to contain at least one element.
* <p>
* The current implementation does not support rotating components.
- *
+ *
* @param c Coordinate in the component's coordinate system.
* @param dest Destination component coordinate system.
* @return an array of coordinates describing <code>c</code> in coordinates
*/
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(length, 0, 0);
+ 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(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
- }
-
- 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;
+ } finally {
+ mutex.unlock("toRelative");
}
-
- return array;
}
/**
- * Recursively sum the lengths of all subcomponents that have position
+ * Recursively sum the lengths of all subcomponents that have position
* Position.AFTER.
- *
+ *
* @return Sum of the lengths.
*/
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 (possibly overridden) mass of component.
- *
+ *
* @return The mass of the component or the given override mass.
*/
public final double getMass() {
- checkState();
+ mutex.verify();
if (massOverriden)
return overrideMass;
return getComponentMass();
/**
* Return the (possibly overridden) center of gravity and mass.
- *
+ *
* Returns the CG with the weight of the coordinate set to the weight of the component.
* Both CG and mass may be separately overridden.
- *
+ *
* @return The CG of the component or the given override CG.
*/
public final Coordinate getCG() {
/**
- * 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();
}
/**
* Return the rotational (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 rotational moment of inertia of this component.
*/
public final double getRotationalInertia() {
/**
* Adds a child to the rocket component tree. The component is added to the end
- * of the component's child list. This is a helper method that calls
+ * of the component's child list. This is a helper method that calls
* {@link #addChild(RocketComponent,int)}.
- *
+ *
* @param component The component to add.
- * @throws IllegalArgumentException if the component is already part of some
+ * @throws IllegalArgumentException if the component is already part of some
* component tree.
* @see #addChild(RocketComponent,int)
*/
/**
- * Adds a child to the rocket component tree. The component is added to
+ * Adds a child to the rocket component tree. The component is added to
* the given position of the component's child list.
* <p>
- * This method may be overridden to enforce more strict component addition rules.
+ * 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.
- * @throws IllegalArgumentException If the component is already part of
+ *
+ * @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);
}
/**
* Removes a child from the rocket component tree.
- *
+ *
* @param n remove the n'th child.
* @throws IndexOutOfBoundsException if n is out of bounds
*/
checkState();
RocketComponent component = children.remove(n);
component.parent = null;
+
+ this.checkComponentStructure();
+ component.checkComponentStructure();
+
fireAddRemoveEvent(component);
}
/**
* Removes a child from the rocket component tree. Does nothing if the component
* is not present as a child.
- *
+ *
* @param component the component to remove
* @return whether the component was a child
*/
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();
}
/**
* Returns the position of the child in this components child list, or -1 if the
* component is not a child of this component.
- *
+ *
* @param child The child to search for.
* @return Position in the list or -1 if not found.
*/
public final int getChildPosition(RocketComponent child) {
checkState();
+ this.checkComponentStructure();
return children.indexOf(child);
}
/**
* Get the parent component of this component. Returns <code>null</code> if the component
* has no parent.
- *
+ *
* @return The parent of this component or <code>null</code>.
*/
public final RocketComponent getParent() {
/**
* Get the root component of the component tree.
- *
+ *
* @return The root component of the component tree.
*/
public final RocketComponent getRoot() {
}
/**
- * Returns the root Rocket component of this component tree. Throws an
+ * Returns the root Rocket component of this component tree. Throws an
* IllegalStateException if the root component is not a Rocket.
- *
+ *
* @return The root Rocket component of the component tree.
* @throws IllegalStateException If the root component is not a Rocket.
*/
/**
* Return the Stage component that this component belongs to. Throws an
* IllegalStateException if a Stage is not in the parentage of this component.
- *
+ *
* @return The Stage component this component belongs to.
* @throws IllegalStateException if a Stage component is not in the parentage.
*/
/**
* Return the stage number of the stage this component belongs to. The stages
* are numbered from zero upwards.
- *
+ *
* @return the stage number this component belongs to.
*/
public final int getStageNumber() {
* Find a component with the given ID. The component tree is searched from this component
* down (including this component) for the ID and the corresponding component is returned,
* or null if not found.
- *
+ *
* @param idToFind ID to search for.
* @return The component with the ID, or null if not found.
*/
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;
}
* Adds a ComponentChangeListener to the rocket tree. The listener is added to the root
* component, which must be of type Rocket (which overrides this method). Events of all
* subcomponents are sent to all listeners.
- *
+ *
* @throws IllegalStateException - if the root component is not a Rocket
*/
public void addComponentChangeListener(ComponentChangeListener l) {
* the root component, which must be of type Rocket (which overrides this method).
* Does nothing if the root component is not a Rocket. (The asymmetry is so
* that listeners can always be removed just in case.)
- *
+ *
* @param l Listener to remove
*/
public void removeComponentChangeListener(ComponentChangeListener l) {
/**
- * Adds a <code>ChangeListener</code> to the rocket tree. This is identical to
- * <code>addComponentChangeListener()</code> except that it uses a
+ * Adds a <code>ChangeListener</code> to the rocket tree. This is identical to
+ * <code>addComponentChangeListener()</code> except that it uses a
* <code>ChangeListener</code>. The same events are dispatched to the
- * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass
+ * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass
* of <code>ChangeEvent</code>.
- *
+ *
* @throws IllegalStateException - if the root component is not a <code>Rocket</code>
*/
+ @Override
public void addChangeListener(ChangeListener l) {
checkState();
getRocket().addChangeListener(l);
* removeComponentChangeListener() except it uses a ChangeListener.
* Does nothing if the root component is not a Rocket. (The asymmetry is so
* that listeners can always be removed just in case.)
- *
+ *
* @param l Listener to remove
*/
+ @Override
public void removeChangeListener(ChangeListener l) {
if (this.parent != null) {
getRoot().removeChangeListener(l);
/**
- * Fires a ComponentChangeEvent on the rocket structure. The call is passed to the
+ * Fires a ComponentChangeEvent on the rocket structure. The call is passed to the
* root component, which must be of type Rocket (which overrides this method).
* Events of all subcomponents are sent to all listeners.
- *
- * If the component tree root is not a Rocket, the event is ignored. This is the
- * case when constructing components not in any Rocket tree. In this case it
+ *
+ * If the component tree root is not a Rocket, the event is ignored. This is the
+ * case when constructing components not in any Rocket tree. In this case it
* would be impossible for the component to have listeners in any case.
- *
+ *
* @param e Event to send
*/
protected void fireComponentChangeEvent(ComponentChangeEvent e) {
checkState();
if (parent == null) {
/* Ignore if root invalid. */
+ log.debug("Attempted firing event " + e + " with root " + this.getComponentName() + ", ignoring event");
return;
}
getRoot().fireComponentChangeEvent(e);
/**
* Fires a ComponentChangeEvent of the given type. The source of the event is set to
* this component.
- *
+ *
* @param type Type of event
* @see #fireComponentChangeEvent(ComponentChangeEvent)
*/
/**
* Checks whether this component has been invalidated and should no longer be used.
* This is a safety check that in-place replaced components are no longer used.
- * All non-trivial methods should call this method as the first thing, unless the
- * method may be used in debugging cases.
- *
+ * All non-trivial methods (with the exception of methods simply getting a property)
+ * should call this method before changing or computing anything.
+ *
* @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()
- *
- * @param returnSelf boolean value specifying whether the component itself should be
+ * 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, including itself.
+ * <p>
+ * This method is equivalent to <code>deepIterator(true)</code>.
+ *
+ * TODO: HIGH: Remove this after merges have been done
+ *
+ * @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.
- *
- * The iterator does NOT return the component itself. It is thus equivalent to
- * deepIterator(false).
- *
- * @see #iterator()
+ * <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> deepIterator() {
+ 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.
- *
- * @return An iterator for the children.
+ * Returns an iterator that iterates over this component, its children and sub-children.
+ * <p>
+ * This method is equivalent to <code>iterator(true)</code>.
+ *
+ * @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.
/**
* Helper method to add rotationally symmetric bounds at the specified coordinates.
* The X-axis value is <code>x</code> and the radius at the specified position is
- * <code>r</code>.
+ * <code>r</code>.
*/
protected static final void addBound(Collection<Coordinate> bounds, double x, double r) {
bounds.add(new Coordinate(x, -r, -r));
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.
- *
- * TODO: MEDIUM: Make general to copy all private/protected fields...
- */
- protected void copyFrom(RocketComponent src) {
+ * 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>.
+ *
+ * @return a list of components that should not be used after this call.
+ */
+ 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.relativePosition = src.relativePosition;
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");
+ }
}
}