package net.sf.openrocket.rocketcomponent;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.logging.TraceException;
+import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.LineStyle;
import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.UniqueID;
import javax.swing.event.ChangeListener;
import java.awt.Color;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Stack;
-import java.util.UUID;
-public abstract class RocketComponent implements ChangeSource, Cloneable,
+public abstract class RocketComponent implements ChangeSource, Cloneable,
Iterable<RocketComponent> , Visitable<ComponentVisitor, RocketComponent> {
-
+ private static final LogHelper log = Application.getLogger();
+
/*
* Text is suitable to the form
* Position relative to: <title>
ABSOLUTE("Tip of the nose cone");
private String title;
+
Position(String title) {
this.title = title;
}
* List of child components of this component.
*/
private List<RocketComponent> children = new ArrayList<RocketComponent>();
-
+
//////// Parameters common to all components:
/**
* By default it is zero, i.e. no translation.
*/
protected double length = 0;
-
+
/**
* Positioning of this component relative to the parent component.
*/
*/
protected double position = 0;
-
+
// Color of the component, null means to use the default color
private Color color = null;
private LineStyle lineStyle = null;
-
+
// Override mass/CG
private double overrideMass = 0;
private boolean massOverriden = false;
// User-specified comment
private String comment = "";
-
+
// Unique ID of the component
private String id = null;
- //// NOTE !!! All fields must be copied in the method copyFrom()! ////
-
+ /**
+ * 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)}.
+ */
+ private TraceException invalidated = null;
+ //// NOTE !!! All fields must be copied in the method copyFrom()! ////
+
+
/**
* Default constructor. Sets the name of the component to the component's static name
* and the relative position of the component.
// These must not fire any events, due to Rocket undo system initialization
this.name = getComponentName();
this.relativePosition = relativePosition;
- this.id = UUID.randomUUID().toString();
+ newID();
}
//////////// Methods that must be implemented ////////////
/**
* Static component name. The name may not vary of the parameters, it must be static.
*/
- public abstract String getComponentName(); // Static component type name
-
+ public abstract String getComponentName(); // Static component type name
+
/**
* Return the component mass (regardless of mass overriding).
*/
- public abstract double getComponentMass(); // Mass of non-overridden component
-
+ public abstract double getComponentMass(); // Mass of non-overridden component
+
/**
* Return the component CG and mass (regardless of CG or mass overriding).
*/
- public abstract Coordinate getComponentCG(); // CG of non-overridden component
-
+ public abstract Coordinate getComponentCG(); // CG of non-overridden component
+
/**
* Return the longitudal (around the y- or z-axis) unitary moment of inertia.
* The unitary moment of inertia is the moment of inertia with the assumption that
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();
/**
* Test whether the given component type can be added to this component. This type safety
}
-
+
/**
* 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();
-
+
/**
* Return true if the component may have an aerodynamic effect on the rocket.
*/
public abstract boolean isAerodynamic();
-
+
/**
* Return true if the component may have an effect on the rocket's mass.
*/
public abstract boolean isMassive();
-
-
- //////////// Methods that may be overridden ////////////
+
+ //////////// Methods that may be overridden ////////////
+
/**
* Shift the coordinates in the array corresponding to radial movement. A component
* that has a radial position must shift the coordinates in this array suitably.
* of the passed array and return the array itself.
*/
public Coordinate[] shiftCoordinates(Coordinate[] c) {
+ checkState();
return c;
}
*/
protected void componentChanged(ComponentChangeEvent e) {
// No-op
+ checkState();
}
-
-
+
+
/**
* Return a descriptive name of the component.
*
else
return name;
}
-
+
public final void printStructure() {
- System.out.println("Rocket structure from '"+this.toString()+"':");
+ System.out.println("Rocket structure from '" + this.toString() + "':");
printStructure(0);
}
private void printStructure(int level) {
String s = "";
- for (int i=0; i < level; i++) {
+ for (int i = 0; i < level; i++) {
s += " ";
}
- s += this.toString() + " (" + this.getComponentName()+")";
+ s += this.toString() + " (" + this.getComponentName() + ")";
System.out.println(s);
- for (RocketComponent c: children) {
- c.printStructure(level+1);
+ for (RocketComponent c : children) {
+ c.printStructure(level + 1);
+ }
+ }
+
+
+ /**
+ * 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);
+ while (iterator.hasNext()) {
+ iterator.next().newID();
}
+ return clone;
}
+
/**
* Make a deep copy of the rocket component tree structure from this component
- * downwards. This method does not fire any events.
+ * downwards while maintaining the component ID's. The purpose of this method is
+ * to allow copies to be created with the original ID's for the purpose of the
+ * 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,
* or if some fields should not be copied. This should be performed by
- * <code>RocketComponent c = super.copy();</code> and then cloning/modifying the
- * appropriate fields.
+ * <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.
*/
- public RocketComponent copy() {
+ protected RocketComponent copyWithOriginalID() {
+ checkState();
RocketComponent clone;
try {
- clone = (RocketComponent)this.clone();
+ clone = (RocketComponent) this.clone();
} catch (CloneNotSupportedException e) {
throw new BugException("CloneNotSupportedException encountered, " +
- "report a bug!",e);
+ "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.copy();
+ 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;
}
-
+
return clone;
}
-
-
+
+
/**
* Accept a visitor to this RocketComponent in the component hierarchy.
*
////////////// Methods that may not be overridden ////////////
-
+
////////// Common parameter setting/getting //////////
(color != null && color.equals(c)))
return;
+ checkState();
this.color = c;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
public final void setLineStyle(LineStyle style) {
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.
* @param m the override mass
*/
public final void setOverrideMass(double m) {
- overrideMass = Math.max(m,0);
+ if (MathUtil.equals(m, overrideMass))
+ return;
+ checkState();
+ overrideMass = Math.max(m, 0);
if (massOverriden)
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
* @param o whether the mass is overridden
*/
public final void setMassOverridden(boolean o) {
- 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.
*
public final Coordinate getOverrideCG() {
return getComponentCG().setX(overrideCGX);
}
-
+
/**
* Return the x-coordinate of the current override CG.
*
public final void setOverrideCGX(double x) {
if (MathUtil.equals(overrideCGX, x))
return;
+ checkState();
this.overrideCGX = x;
if (isCGOverridden())
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
* @param o whether the CG is overridden
*/
public final void setCGOverridden(boolean o) {
- if (cgOverriden != o) {
- cgOverriden = o;
- fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ if (cgOverriden == o) {
+ return;
}
+ checkState();
+ cgOverriden = o;
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
/**
* Return whether the mass and/or CG override overrides all subcomponent values
* as well. The default implementation is a normal getter/setter implementation,
* @param override whether the mass and/or CG override overrides all subcomponent.
*/
public void setOverrideSubcomponents(boolean override) {
- if (overrideSubcomponents != override) {
- overrideSubcomponents = override;
- fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ if (overrideSubcomponents == override) {
+ return;
}
+ checkState();
+ overrideSubcomponents = override;
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
/**
}
-
-
+
+
/**
* Get the user-defined name of the component.
*/
* the default name, currently the component name.
*/
public final void setName(String name) {
-// System.out.println("Set name called:"+name+" orig:"+this.name);
- if (name==null || name.matches("^\\s*$"))
+ if (this.name.equals(name)) {
+ return;
+ }
+ checkState();
+ if (name == null || name.matches("^\\s*$"))
this.name = getComponentName();
else
this.name = name;
public final void setComment(String comment) {
if (this.comment.equals(comment))
return;
+ checkState();
if (comment == null)
this.comment = "";
else
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
+
/**
* Returns the unique ID of the component.
*
return id;
}
-
/**
- * Set the unique ID of the component. If <code>id</code> in <code>null</code> then
- * this method generates a new unique ID for the component.
- * <p>
- * This method should be used only in special cases, such as when creating database
- * entries with empty IDs.
- *
- * @param id the ID to set.
+ * Generate a new ID for this component.
*/
- public final void setID(String id) {
- if (id == null) {
- this.id = UUID.randomUUID().toString();
- } else {
- this.id = id;
- }
- fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
+ private final void newID() {
+ 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
public final double getLength() {
return length;
}
-
+
/**
* Get the positioning of the component relative to its parent component.
* This is one of the enums of {@link Position}. A setter method is not provided,
protected void setRelativePosition(RocketComponent.Position position) {
if (this.relativePosition == position)
return;
+ checkState();
// Update position so as not to move the component
if (this.parent != null) {
- double thisPos = this.toRelative(Coordinate.NUL,this.parent)[0].x;
-
+ double thisPos = this.toRelative(Coordinate.NUL, this.parent)[0].x;
+
switch (position) {
case ABSOLUTE:
this.position = this.toAbsolute(Coordinate.NUL)[0].x;
break;
-
+
case TOP:
this.position = thisPos;
break;
-
+
case MIDDLE:
- this.position = thisPos - (this.parent.length - this.length)/2;
+ this.position = thisPos - (this.parent.length - this.length) / 2;
break;
-
+
case BOTTOM:
this.position = thisPos - (this.parent.length - this.length);
break;
-
+
default:
- assert(false): "Should not occur";
+ throw new BugException("Unknown position type: " + position);
}
}
this.relativePosition = position;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
/**
* Get the position value of the component. The exact meaning of the value is
* dependent on the current relative positioning.
return position;
}
-
+
/**
* Set the position value of the component. The exact meaning of the value
* depends on the current relative positioning.
public void setPositionValue(double value) {
if (MathUtil.equals(this.position, value))
return;
+ checkState();
this.position = value;
}
-
- /////////// Coordinate changes ///////////
+ /////////// Coordinate changes ///////////
+
/**
* Returns coordinate c in absolute coordinates. Equivalent to toComponent(c,null).
*/
public Coordinate[] toAbsolute(Coordinate c) {
- return toRelative(c,null);
+ checkState();
+ return toRelative(c, null);
}
-
+
/**
* Return coordinate <code>c</code> described in the coordinate system of
* <code>dest</code>. If <code>dest</code> is <code>null</code> returns
* relative to <code>dest</code>.
*/
public final Coordinate[] toRelative(Coordinate c, RocketComponent dest) {
+ checkState();
double absoluteX = Double.NaN;
RocketComponent search = dest;
Coordinate[] array = new Coordinate[1];
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);
+ 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);
+ 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);
+ 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);
+ assert (index >= 0);
for (index--; index >= 0; index--) {
RocketComponent comp = component.parent.children.get(index);
double length = comp.getTotalLength();
- for (int i=0; i < array.length; i++) {
- array[i] = array[i].add(length,0,0);
+ for (int i = 0; i < array.length; i++) {
+ array[i] = array[i].add(length, 0, 0);
}
}
- for (int i=0; i < array.length; i++) {
- array[i] = array[i].add(component.position + component.parent.length,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
+ 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);
+ throw new BugException("Unknown relative positioning type of component" +
+ component + ": " + component.relativePosition);
}
-
- component = component.parent; // parent != null
+
+ component = component.parent; // parent != null
}
-
+
if (!Double.isNaN(absoluteX)) {
- for (int i=0; i < array.length; i++) {
+ 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++) {
+ for (int i = 0; i < array.length; i++) {
array[i] = array[i].sub(origin[0]);
}
}
* @return Sum of the lengths.
*/
private final double getTotalLength() {
- double l=0;
+ checkState();
+ double l = 0;
if (relativePosition == Position.AFTER)
l = length;
- for (int i=0; i<children.size(); i++)
+ for (int i = 0; i < children.size(); i++)
l += children.get(i).getTotalLength();
return l;
}
-
- /////////// Total mass and CG calculation ////////////
+ /////////// Total mass and CG calculation ////////////
+
/**
* Return the (possibly overridden) mass of component.
*
* @return The CG of the component or the given override CG.
*/
public final Coordinate getCG() {
+ checkState();
if (cgOverriden)
return getOverrideCG().setWeight(getMass());
-
+
if (massOverriden)
return getComponentCG().setWeight(getMass());
return getComponentCG();
}
-
+
/**
* Return the longitudal (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
* @return the longitudal moment of inertia of this component.
*/
public final double getLongitudalInertia() {
+ checkState();
return getLongitudalUnitInertia() * getMass();
}
* @return the rotational moment of inertia of this component.
*/
public final double getRotationalInertia() {
+ checkState();
return getRotationalUnitInertia() * getMass();
}
-
+
/////////// Children handling ///////////
* @see #addChild(RocketComponent,int)
*/
public final void addChild(RocketComponent component) {
- addChild(component,children.size());
+ checkState();
+ addChild(component, children.size());
}
-
+
/**
* Adds a child to the rocket component tree. The component is added to
* some component tree.
*/
public void addChild(RocketComponent component, int position) {
+ checkState();
if (component.parent != null) {
- throw new IllegalArgumentException("component "+component.getComponentName()+
+ throw new IllegalArgumentException("component " + component.getComponentName() +
" is already in a tree");
}
if (!isCompatible(component)) {
- throw new IllegalStateException("Component "+component.getComponentName()+
- " not currently compatible with component "+getComponentName());
+ throw new IllegalStateException("Component " + component.getComponentName() +
+ " not currently compatible with component " + getComponentName());
}
- children.add(position,component);
+ children.add(position, component);
component.parent = this;
fireAddRemoveEvent(component);
* @throws IndexOutOfBoundsException if n is out of bounds
*/
public final void removeChild(int n) {
+ checkState();
RocketComponent component = children.remove(n);
component.parent = null;
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
+ * @param component the component to remove
+ * @return whether the component was a child
*/
- public final void removeChild(RocketComponent component) {
+ public final boolean removeChild(RocketComponent component) {
+ checkState();
if (children.remove(component)) {
component.parent = null;
-
fireAddRemoveEvent(component);
+ return true;
}
+ return false;
}
-
-
+
+
/**
* Move a child to another position.
*
* @throws IllegalArgumentException If an illegal placement was attempted.
*/
public final void moveChild(RocketComponent component, int position) {
+ checkState();
if (children.remove(component)) {
children.add(position, component);
fireAddRemoveEvent(component);
public final int getChildCount() {
+ checkState();
return children.size();
}
public final RocketComponent getChild(int n) {
+ checkState();
return children.get(n);
}
public final RocketComponent[] getChildren() {
+ checkState();
return children.toArray(new RocketComponent[0]);
}
* @return Position in the list or -1 if not found.
*/
public final int getChildPosition(RocketComponent child) {
+ checkState();
return children.indexOf(child);
}
* @return The parent of this component or <code>null</code>.
*/
public final RocketComponent getParent() {
+ checkState();
return parent;
}
* @return The root component of the component tree.
*/
public final RocketComponent getRoot() {
+ checkState();
RocketComponent gp = this;
while (gp.parent != null)
gp = gp.parent;
* @throws IllegalStateException If the root component is not a Rocket.
*/
public final Rocket getRocket() {
+ checkState();
RocketComponent r = getRoot();
if (r instanceof Rocket)
- return (Rocket)r;
+ return (Rocket) r;
throw new IllegalStateException("getRocket() called with root component "
- +r.getComponentName());
+ + r.getComponentName());
}
* @throws IllegalStateException if a Stage component is not in the parentage.
*/
public final Stage getStage() {
+ checkState();
RocketComponent c = this;
while (c != null) {
if (c instanceof Stage)
- return (Stage)c;
+ return (Stage) c;
c = c.getParent();
}
throw new IllegalStateException("getStage() called without Stage as a parent.");
* @return the stage number this component belongs to.
*/
public final int getStageNumber() {
+ checkState();
if (parent == null) {
throw new IllegalArgumentException("getStageNumber() called for root component");
}
* down (including this component) for the ID and the corresponding component is returned,
* or null if not found.
*
- * @param id ID to search for.
+ * @param idToFind ID to search for.
* @return The component with the ID, or null if not found.
*/
- public final RocketComponent findComponent(String id) {
+ public final RocketComponent findComponent(String idToFind) {
+ checkState();
Iterator<RocketComponent> iter = this.deepIterator(true);
while (iter.hasNext()) {
RocketComponent c = iter.next();
- if (c.id.equals(id))
+ if (c.getID().equals(idToFind))
return c;
}
return null;
}
-
+
public final RocketComponent getPreviousComponent() {
+ checkState();
if (parent == null)
return null;
int pos = parent.getChildPosition(this);
StringBuffer sb = new StringBuffer();
sb.append("Inconsistent internal state: ");
sb.append("this=").append(this).append('[')
- .append(System.identityHashCode(this)).append(']');
+ .append(System.identityHashCode(this)).append(']');
sb.append(" parent.children=[");
- for (int i=0; i < parent.children.size(); i++) {
+ for (int i = 0; i < parent.children.size(); i++) {
RocketComponent c = parent.children.get(i);
sb.append(c).append('[').append(System.identityHashCode(c)).append(']');
- if (i < parent.children.size()-1)
+ if (i < parent.children.size() - 1)
sb.append(", ");
}
sb.append(']');
throw new IllegalStateException(sb.toString());
}
- assert(pos >= 0);
+ assert (pos >= 0);
if (pos == 0)
return parent;
- RocketComponent c = parent.getChild(pos-1);
+ RocketComponent c = parent.getChild(pos - 1);
while (c.getChildCount() > 0)
- c = c.getChild(c.getChildCount()-1);
+ c = c.getChild(c.getChildCount() - 1);
return c;
}
public final RocketComponent getNextComponent() {
+ checkState();
if (getChildCount() > 0)
return getChild(0);
while (parent != null) {
int pos = parent.getChildPosition(current);
- if (pos < parent.getChildCount()-1)
- return parent.getChild(pos+1);
-
+ if (pos < parent.getChildCount() - 1)
+ return parent.getChild(pos + 1);
+
current = parent;
parent = current.parent;
}
* @throws IllegalStateException - if the root component is not a Rocket
*/
public void addComponentChangeListener(ComponentChangeListener l) {
+ checkState();
getRocket().addComponentChangeListener(l);
}
}
}
-
+
/**
* Adds a <code>ChangeListener</code> to the rocket tree. This is identical to
* <code>addComponentChangeListener()</code> except that it uses a
* @throws IllegalStateException - if the root component is not a <code>Rocket</code>
*/
public void addChangeListener(ChangeListener l) {
+ checkState();
getRocket().addChangeListener(l);
}
* @param e Event to send
*/
protected void fireComponentChangeEvent(ComponentChangeEvent e) {
- if (parent==null) {
+ 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
* @see #fireComponentChangeEvent(ComponentChangeEvent)
*/
protected void fireComponentChangeEvent(int type) {
- fireComponentChangeEvent(new ComponentChangeEvent(this,type));
+ fireComponentChangeEvent(new ComponentChangeEvent(this, type));
+ }
+
+
+ /**
+ * 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 (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);
+ }
}
-
/////////// Iterator implementation //////////
private final Rocket root;
private final int treeModID;
-
+
private final RocketComponent original;
- private boolean returnSelf=false;
+ 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;
+ root = (Rocket) gp;
treeModID = root.getTreeModID();
} else {
root = null;
}
public boolean hasNext() {
+ checkState();
checkID();
if (returnSelf)
return true;
- return !iteratorstack.empty(); // Elements remain if stack is not empty
+ 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;
+ returnSelf = false;
return original;
}
return c;
}
-
+
private void checkID() {
if (root != null) {
if (root.getTreeModID() != treeModID) {
"RocketComponent iterator");
}
}
-
+
/**
* Returns an iterator that iterates over all children and sub-children.
*
* @return An iterator for the children and sub-children.
*/
public final Iterator<RocketComponent> deepIterator(boolean returnSelf) {
- return new RocketComponentIterator(this,returnSelf);
+ checkState();
+ return new RocketComponentIterator(this, returnSelf);
}
/**
* @return An iterator for the children and sub-children.
*/
public final Iterator<RocketComponent> deepIterator() {
- return new RocketComponentIterator(this,false);
+ checkState();
+ return new RocketComponentIterator(this, false);
}
* @return An iterator for the children.
*/
public final Iterator<RocketComponent> iterator() {
+ checkState();
return Collections.unmodifiableList(children).iterator();
}
+
+
+
+ /**
+ * Compare component equality based on the ID of this component. Only the
+ * ID and class type is used for a basis of comparison.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (this.getClass() != obj.getClass())
+ return false;
+ RocketComponent other = (RocketComponent) obj;
+ return this.id.equals(other.id);
+ }
+
+
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+
+
//////////// Helper methods for subclasses
+
+
+
/**
* 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>.
*/
protected static final void addBound(Collection<Coordinate> bounds, double x, double r) {
- bounds.add(new Coordinate(x,-r,-r));
- bounds.add(new Coordinate(x, r,-r));
+ bounds.add(new Coordinate(x, -r, -r));
+ bounds.add(new Coordinate(x, r, -r));
bounds.add(new Coordinate(x, r, r));
- bounds.add(new Coordinate(x,-r, r));
+ bounds.add(new Coordinate(x, -r, r));
}
- protected static final Coordinate ringCG(double outerRadius, double innerRadius,
+ protected static final Coordinate ringCG(double outerRadius, double innerRadius,
double x1, double x2, double density) {
- return new Coordinate((x1+x2)/2, 0, 0,
- ringMass(outerRadius, innerRadius, x2-x1, density));
+ return new Coordinate((x1 + x2) / 2, 0, 0,
+ ringMass(outerRadius, innerRadius, x2 - x1, density));
}
protected static final double ringMass(double outerRadius, double innerRadius,
double length, double density) {
- return Math.PI*(MathUtil.pow2(outerRadius) - MathUtil.pow2(innerRadius)) *
+ return Math.PI * (MathUtil.pow2(outerRadius) - MathUtil.pow2(innerRadius)) *
length * density;
}
-
- protected static final double ringLongitudalUnitInertia(double outerRadius,
+
+ protected static final double ringLongitudalUnitInertia(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;
+ return (3 * (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) + MathUtil.pow2(length)) / 12;
}
-
- protected static final double ringRotationalUnitInertia(double outerRadius,
+
+ protected static final double ringRotationalUnitInertia(double outerRadius,
double innerRadius) {
// 1/2 * (r1^2 + r2^2)
- return (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius))/2;
+ return (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) / 2;
}
-
-
- //////////// OTHER
+
+ //////////// OTHER
+
/**
* Loads the RocketComponent fields from the given component. This method is meant
* for in-place replacement of a component. It is used with the undo/redo
* 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.
+ * 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) {
+ checkState();
if (this.parent != null) {
- throw new UnsupportedOperationException("copyFrom called for non-root component "
+ throw new UnsupportedOperationException("copyFrom called for non-root component "
+ this);
}
this.children = src.children;
src.children = new ArrayList<RocketComponent>();
- for (RocketComponent c: this.children) {
+ for (RocketComponent c : this.children) {
c.parent = this;
}
-
+
// Set all parameters
this.length = src.length;
this.relativePosition = src.relativePosition;
this.name = src.name;
this.comment = src.comment;
this.id = src.id;
+
+ src.invalidated = new TraceException();
}
}