1 package net.sf.openrocket.rocketcomponent;
3 import net.sf.openrocket.logging.LogHelper;
4 import net.sf.openrocket.logging.TraceException;
5 import net.sf.openrocket.startup.Application;
6 import net.sf.openrocket.util.BugException;
7 import net.sf.openrocket.util.ChangeSource;
8 import net.sf.openrocket.util.Coordinate;
9 import net.sf.openrocket.util.LineStyle;
10 import net.sf.openrocket.util.MathUtil;
11 import net.sf.openrocket.util.UniqueID;
13 import javax.swing.event.ChangeListener;
14 import java.awt.Color;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.EmptyStackException;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.NoSuchElementException;
22 import java.util.Stack;
25 public abstract class RocketComponent implements ChangeSource, Cloneable,
26 Iterable<RocketComponent> , Visitable<ComponentVisitor, RocketComponent> {
27 private static final LogHelper log = Application.getLogger();
30 * Text is suitable to the form
31 * Position relative to: <title>
33 public enum Position {
34 /** Position relative to the top of the parent component. */
35 TOP("Top of the parent component"),
36 /** Position relative to the middle of the parent component. */
37 MIDDLE("Middle of the parent component"),
38 /** Position relative to the bottom of the parent component. */
39 BOTTOM("Bottom of the parent component"),
40 /** Position after the parent component (for body components). */
41 AFTER("After the parent component"),
42 /** Specify an absolute X-coordinate position. */
43 ABSOLUTE("Tip of the nose cone");
47 Position(String title) {
52 public String toString() {
57 //////// Parent/child trees
59 * Parent component of the current component, or null if none exists.
61 private RocketComponent parent = null;
64 * List of child components of this component.
66 private List<RocketComponent> children = new ArrayList<RocketComponent>();
69 //////// Parameters common to all components:
72 * Characteristic length of the component. This is used in calculating the coordinate
73 * transformations and positions of other components in reference to this component.
74 * This may and should be used as the "true" length of the component, where applicable.
75 * By default it is zero, i.e. no translation.
77 protected double length = 0;
80 * Positioning of this component relative to the parent component.
82 protected Position relativePosition;
85 * Offset of the position of this component relative to the normal position given by
86 * relativePosition. By default zero, i.e. no position change.
88 protected double position = 0;
91 // Color of the component, null means to use the default color
92 private Color color = null;
93 private LineStyle lineStyle = null;
97 private double overrideMass = 0;
98 private boolean massOverriden = false;
99 private double overrideCGX = 0;
100 private boolean cgOverriden = false;
102 private boolean overrideSubcomponents = false;
105 // User-given name of the component
106 private String name = null;
108 // User-specified comment
109 private String comment = "";
111 // Unique ID of the component
112 private String id = null;
115 * When invalidated is non-null this component cannot be used anymore.
116 * This is a safety mechanism to prevent accidental use after calling {@link #copyFrom(RocketComponent)}.
118 private TraceException invalidated = null;
120 //// NOTE !!! All fields must be copied in the method copyFrom()! ////
125 * Default constructor. Sets the name of the component to the component's static name
126 * and the relative position of the component.
128 public RocketComponent(Position relativePosition) {
129 // These must not fire any events, due to Rocket undo system initialization
130 this.name = getComponentName();
131 this.relativePosition = relativePosition;
135 //////////// Methods that must be implemented ////////////
139 * Static component name. The name may not vary of the parameters, it must be static.
141 public abstract String getComponentName(); // Static component type name
144 * Return the component mass (regardless of mass overriding).
146 public abstract double getComponentMass(); // Mass of non-overridden component
149 * Return the component CG and mass (regardless of CG or mass overriding).
151 public abstract Coordinate getComponentCG(); // CG of non-overridden component
155 * Return the longitudal (around the y- or z-axis) unitary moment of inertia.
156 * The unitary moment of inertia is the moment of inertia with the assumption that
157 * the mass of the component is one kilogram. The inertia is measured in
158 * respect to the non-overridden CG.
160 * @return the longitudal unitary moment of inertia of this component.
162 public abstract double getLongitudalUnitInertia();
166 * Return the rotational (around the x-axis) unitary moment of inertia.
167 * The unitary moment of inertia is the moment of inertia with the assumption that
168 * the mass of the component is one kilogram. The inertia is measured in
169 * respect to the non-overridden CG.
171 * @return the rotational unitary moment of inertia of this component.
173 public abstract double getRotationalUnitInertia();
177 * Test whether this component allows any children components. This method must
178 * return true if and only if {@link #isCompatible(Class)} returns true for any
179 * rocket component class.
181 * @return <code>true</code> if children can be attached to this component, <code>false</code> otherwise.
183 public abstract boolean allowsChildren();
186 * Test whether the given component type can be added to this component. This type safety
187 * is enforced by the <code>addChild()</code> methods. The return value of this method
188 * may change to reflect the current state of this component (e.g. two components of some
189 * type cannot be placed as children).
191 * @param type The RocketComponent class type to add.
192 * @return Whether such a component can be added.
194 public abstract boolean isCompatible(Class<? extends RocketComponent> type);
197 /* Non-abstract helper method */
199 * Test whether the given component can be added to this component. This is equivalent
200 * to calling <code>isCompatible(c.getClass())</code>.
202 * @param c Component to test.
203 * @return Whether the component can be added.
204 * @see #isCompatible(Class)
206 public final boolean isCompatible(RocketComponent c) {
207 return isCompatible(c.getClass());
213 * Return a collection of bounding coordinates. The coordinates must be such that
214 * the component is fully enclosed in their convex hull.
216 * @return a collection of coordinates that bound the component.
218 public abstract Collection<Coordinate> getComponentBounds();
221 * Return true if the component may have an aerodynamic effect on the rocket.
223 public abstract boolean isAerodynamic();
226 * Return true if the component may have an effect on the rocket's mass.
228 public abstract boolean isMassive();
234 //////////// Methods that may be overridden ////////////
238 * Shift the coordinates in the array corresponding to radial movement. A component
239 * that has a radial position must shift the coordinates in this array suitably.
240 * If the component is clustered, then a new array must be returned with a
241 * coordinate for each cluster.
243 * The default implementation simply returns the array, and thus produces no shift.
245 * @param c an array of coordinates to shift.
246 * @return an array of shifted coordinates. The method may modify the contents
247 * of the passed array and return the array itself.
249 public Coordinate[] shiftCoordinates(Coordinate[] c) {
256 * Called when any component in the tree fires a ComponentChangeEvent. This is by
257 * default a no-op, but subclasses may override this method to e.g. invalidate
258 * cached data. The overriding method *must* call
259 * <code>super.componentChanged(e)</code> at some point.
261 * @param e The event fired
263 protected void componentChanged(ComponentChangeEvent e) {
272 * Return a descriptive name of the component.
274 * The description may include extra information about the type of component,
275 * e.g. "Conical nose cone".
277 * @return A string describing the component.
280 public final String toString() {
282 return getComponentName();
288 public final void printStructure() {
289 System.out.println("Rocket structure from '" + this.toString() + "':");
293 private void printStructure(int level) {
296 for (int i = 0; i < level; i++) {
299 s += this.toString() + " (" + this.getComponentName() + ")";
300 System.out.println(s);
302 for (RocketComponent c : children) {
303 c.printStructure(level + 1);
309 * Make a deep copy of the rocket component tree structure from this component
310 * downwards for copying purposes. Each component in the copy will be assigned
311 * a new component ID, making it a safe copy. This method does not fire any events.
313 * @return A deep copy of the structure.
315 public final RocketComponent copy() {
316 RocketComponent clone = copyWithOriginalID();
318 Iterator<RocketComponent> iterator = clone.deepIterator(true);
319 while (iterator.hasNext()) {
320 iterator.next().newID();
328 * Make a deep copy of the rocket component tree structure from this component
329 * downwards while maintaining the component ID's. The purpose of this method is
330 * to allow copies to be created with the original ID's for the purpose of the
331 * undo/redo mechanism. This method should not be used for other purposes,
332 * such as copy/paste. This method does not fire any events.
334 * This method must be overridden by any component that refers to mutable objects,
335 * or if some fields should not be copied. This should be performed by
336 * <code>RocketComponent c = super.copyWithOriginalID();</code> and then cloning/modifying
337 * the appropriate fields.
339 * This is not performed as serializing/deserializing for performance reasons.
341 * @return A deep copy of the structure.
343 protected RocketComponent copyWithOriginalID() {
345 RocketComponent clone;
347 clone = (RocketComponent) this.clone();
348 } catch (CloneNotSupportedException e) {
349 throw new BugException("CloneNotSupportedException encountered, " +
353 // Reset all parent/child information
355 clone.children = new ArrayList<RocketComponent>();
357 // Add copied children to the structure without firing events.
358 for (RocketComponent child : this.children) {
359 RocketComponent childCopy = child.copyWithOriginalID();
360 // Don't use add method since it fires events
361 clone.children.add(childCopy);
362 childCopy.parent = clone;
370 * Accept a visitor to this RocketComponent in the component hierarchy.
372 * @param theVisitor the visitor that will be called back with a reference to this RocketComponent
375 public void accept (final ComponentVisitor theVisitor) {
376 theVisitor.visit(this);
379 ////////////// Methods that may not be overridden ////////////
383 ////////// Common parameter setting/getting //////////
386 * Return the color of the object to use in 2D figures, or <code>null</code>
387 * to use the default color.
389 public final Color getColor() {
394 * Set the color of the object to use in 2D figures.
396 public final void setColor(Color c) {
397 if ((color == null && c == null) ||
398 (color != null && color.equals(c)))
403 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
407 public final LineStyle getLineStyle() {
411 public final void setLineStyle(LineStyle style) {
412 if (this.lineStyle == style)
415 this.lineStyle = style;
416 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
423 * Get the current override mass. The mass is not necessarily in use
426 * @return the override mass
428 public final double getOverrideMass() {
433 * Set the current override mass. The mass is not set to use by this
436 * @param m the override mass
438 public final void setOverrideMass(double m) {
439 if (MathUtil.equals(m, overrideMass))
442 overrideMass = Math.max(m, 0);
444 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
448 * Return whether mass override is active for this component. This does NOT
449 * take into account whether a parent component is overriding the mass.
451 * @return whether the mass is overridden
453 public final boolean isMassOverridden() {
454 return massOverriden;
458 * Set whether the mass is currently overridden.
460 * @param o whether the mass is overridden
462 public final void setMassOverridden(boolean o) {
463 if (massOverriden == o) {
468 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
476 * Return the current override CG. The CG is not necessarily overridden.
478 * @return the override CG
480 public final Coordinate getOverrideCG() {
481 return getComponentCG().setX(overrideCGX);
485 * Return the x-coordinate of the current override CG.
487 * @return the x-coordinate of the override CG.
489 public final double getOverrideCGX() {
494 * Set the current override CG to (x,0,0).
496 * @param x the x-coordinate of the override CG to set.
498 public final void setOverrideCGX(double x) {
499 if (MathUtil.equals(overrideCGX, x))
502 this.overrideCGX = x;
503 if (isCGOverridden())
504 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
506 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
510 * Return whether the CG is currently overridden.
512 * @return whether the CG is overridden
514 public final boolean isCGOverridden() {
519 * Set whether the CG is currently overridden.
521 * @param o whether the CG is overridden
523 public final void setCGOverridden(boolean o) {
524 if (cgOverriden == o) {
529 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
535 * Return whether the mass and/or CG override overrides all subcomponent values
536 * as well. The default implementation is a normal getter/setter implementation,
537 * however, subclasses are allowed to override this behavior if some subclass
538 * always or never overrides subcomponents. In this case the subclass should
539 * also override {@link #isOverrideSubcomponentsEnabled()} to return
540 * <code>false</code>.
542 * @return whether the current mass and/or CG override overrides subcomponents as well.
544 public boolean getOverrideSubcomponents() {
545 return overrideSubcomponents;
550 * Set whether the mass and/or CG override overrides all subcomponent values
551 * as well. See {@link #getOverrideSubcomponents()} for details.
553 * @param override whether the mass and/or CG override overrides all subcomponent.
555 public void setOverrideSubcomponents(boolean override) {
556 if (overrideSubcomponents == override) {
560 overrideSubcomponents = override;
561 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
565 * Return whether the option to override all subcomponents is enabled or not.
566 * The default implementation returns <code>false</code> if neither mass nor
567 * CG is overridden, <code>true</code> otherwise.
569 * This method may be overridden if the setting of overriding subcomponents
572 * @return whether the option to override subcomponents is currently enabled.
574 public boolean isOverrideSubcomponentsEnabled() {
575 return isCGOverridden() || isMassOverridden();
582 * Get the user-defined name of the component.
584 public final String getName() {
589 * Set the user-defined name of the component. If name==null, sets the name to
590 * the default name, currently the component name.
592 public final void setName(String name) {
593 if (this.name.equals(name)) {
597 if (name == null || name.matches("^\\s*$"))
598 this.name = getComponentName();
601 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
606 * Return the comment of the component. The component may contain multiple lines
607 * using \n as a newline separator.
609 * @return the comment of the component.
611 public final String getComment() {
616 * Set the comment of the component.
618 * @param comment the comment of the component.
620 public final void setComment(String comment) {
621 if (this.comment.equals(comment))
627 this.comment = comment;
628 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
634 * Returns the unique ID of the component.
636 * @return the ID of the component.
638 public final String getID() {
643 * Generate a new ID for this component.
645 private final void newID() {
646 this.id = UniqueID.uuid();
653 * Get the characteristic length of the component, for example the length of a body tube
654 * of the length of the root chord of a fin. This is used in positioning the component
655 * relative to its parent.
657 * If the length of a component is settable, the class must define the setter method
660 public final double getLength() {
665 * Get the positioning of the component relative to its parent component.
666 * This is one of the enums of {@link Position}. A setter method is not provided,
667 * but can be provided by a subclass.
669 public final Position getRelativePosition() {
670 return relativePosition;
675 * Set the positioning of the component relative to its parent component.
676 * The actual position of the component is maintained to the best ability.
678 * The default implementation is of protected visibility, since many components
679 * do not support setting the relative position. A component that does support
680 * it should override this with a public method that simply calls this
681 * supermethod AND fire a suitable ComponentChangeEvent.
683 * @param position the relative positioning.
685 protected void setRelativePosition(RocketComponent.Position position) {
686 if (this.relativePosition == position)
690 // Update position so as not to move the component
691 if (this.parent != null) {
692 double thisPos = this.toRelative(Coordinate.NUL, this.parent)[0].x;
696 this.position = this.toAbsolute(Coordinate.NUL)[0].x;
700 this.position = thisPos;
704 this.position = thisPos - (this.parent.length - this.length) / 2;
708 this.position = thisPos - (this.parent.length - this.length);
712 throw new BugException("Unknown position type: " + position);
716 this.relativePosition = position;
717 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
724 * Get the position value of the component. The exact meaning of the value is
725 * dependent on the current relative positioning.
727 * @return the positional value.
729 public final double getPositionValue() {
735 * Set the position value of the component. The exact meaning of the value
736 * depends on the current relative positioning.
738 * The default implementation is of protected visibility, since many components
739 * do not support setting the relative position. A component that does support
740 * it should override this with a public method that simply calls this
741 * supermethod AND fire a suitable ComponentChangeEvent.
743 * @param value the position value of the component.
745 public void setPositionValue(double value) {
746 if (MathUtil.equals(this.position, value))
749 this.position = value;
754 /////////// Coordinate changes ///////////
757 * Returns coordinate c in absolute coordinates. Equivalent to toComponent(c,null).
759 public Coordinate[] toAbsolute(Coordinate c) {
761 return toRelative(c, null);
766 * Return coordinate <code>c</code> described in the coordinate system of
767 * <code>dest</code>. If <code>dest</code> is <code>null</code> returns
768 * absolute coordinates.
770 * This method returns an array of coordinates, each of which represents a
771 * position of the coordinate in clustered cases. The array is guaranteed
772 * to contain at least one element.
774 * The current implementation does not support rotating components.
776 * @param c Coordinate in the component's coordinate system.
777 * @param dest Destination component coordinate system.
778 * @return an array of coordinates describing <code>c</code> in coordinates
779 * relative to <code>dest</code>.
781 public final Coordinate[] toRelative(Coordinate c, RocketComponent dest) {
783 double absoluteX = Double.NaN;
784 RocketComponent search = dest;
785 Coordinate[] array = new Coordinate[1];
788 RocketComponent component = this;
789 while ((component != search) && (component.parent != null)) {
791 array = component.shiftCoordinates(array);
793 switch (component.relativePosition) {
795 for (int i = 0; i < array.length; i++) {
796 array[i] = array[i].add(component.position, 0, 0);
801 for (int i = 0; i < array.length; i++) {
802 array[i] = array[i].add(component.position +
803 (component.parent.length - component.length) / 2, 0, 0);
808 for (int i = 0; i < array.length; i++) {
809 array[i] = array[i].add(component.position +
810 (component.parent.length - component.length), 0, 0);
815 // Add length of all previous brother-components with POSITION_RELATIVE_AFTER
816 int index = component.parent.children.indexOf(component);
818 for (index--; index >= 0; index--) {
819 RocketComponent comp = component.parent.children.get(index);
820 double length = comp.getTotalLength();
821 for (int i = 0; i < array.length; i++) {
822 array[i] = array[i].add(length, 0, 0);
825 for (int i = 0; i < array.length; i++) {
826 array[i] = array[i].add(component.position + component.parent.length, 0, 0);
831 search = null; // Requires back-search if dest!=null
832 if (Double.isNaN(absoluteX)) {
833 absoluteX = component.position;
838 throw new BugException("Unknown relative positioning type of component" +
839 component + ": " + component.relativePosition);
842 component = component.parent; // parent != null
845 if (!Double.isNaN(absoluteX)) {
846 for (int i = 0; i < array.length; i++) {
847 array[i] = array[i].setX(absoluteX + c.x);
851 // Check whether destination has been found or whether to backtrack
852 // TODO: LOW: Backtracking into clustered components uses only one component
853 if ((dest != null) && (component != dest)) {
854 Coordinate[] origin = dest.toAbsolute(Coordinate.NUL);
855 for (int i = 0; i < array.length; i++) {
856 array[i] = array[i].sub(origin[0]);
865 * Recursively sum the lengths of all subcomponents that have position
868 * @return Sum of the lengths.
870 private final double getTotalLength() {
873 if (relativePosition == Position.AFTER)
875 for (int i = 0; i < children.size(); i++)
876 l += children.get(i).getTotalLength();
882 /////////// Total mass and CG calculation ////////////
885 * Return the (possibly overridden) mass of component.
887 * @return The mass of the component or the given override mass.
889 public final double getMass() {
892 return getComponentMass();
896 * Return the (possibly overridden) center of gravity and mass.
898 * Returns the CG with the weight of the coordinate set to the weight of the component.
899 * Both CG and mass may be separately overridden.
901 * @return The CG of the component or the given override CG.
903 public final Coordinate getCG() {
906 return getOverrideCG().setWeight(getMass());
909 return getComponentCG().setWeight(getMass());
911 return getComponentCG();
916 * Return the longitudal (around the y- or z-axis) moment of inertia of this component.
917 * The moment of inertia is scaled in reference to the (possibly overridden) mass
918 * and is relative to the non-overridden CG.
920 * @return the longitudal moment of inertia of this component.
922 public final double getLongitudalInertia() {
924 return getLongitudalUnitInertia() * getMass();
928 * Return the rotational (around the y- or z-axis) moment of inertia of this component.
929 * The moment of inertia is scaled in reference to the (possibly overridden) mass
930 * and is relative to the non-overridden CG.
932 * @return the rotational moment of inertia of this component.
934 public final double getRotationalInertia() {
936 return getRotationalUnitInertia() * getMass();
941 /////////// Children handling ///////////
945 * Adds a child to the rocket component tree. The component is added to the end
946 * of the component's child list. This is a helper method that calls
947 * {@link #addChild(RocketComponent,int)}.
949 * @param component The component to add.
950 * @throws IllegalArgumentException if the component is already part of some
952 * @see #addChild(RocketComponent,int)
954 public final void addChild(RocketComponent component) {
956 addChild(component, children.size());
961 * Adds a child to the rocket component tree. The component is added to
962 * the given position of the component's child list.
964 * This method may be overridden to enforce more strict component addition rules.
965 * The tests should be performed first and then this method called.
967 * @param component The component to add.
968 * @param position Position to add component to.
969 * @throws IllegalArgumentException If the component is already part of
970 * some component tree.
972 public void addChild(RocketComponent component, int position) {
974 if (component.parent != null) {
975 throw new IllegalArgumentException("component " + component.getComponentName() +
976 " is already in a tree");
978 if (!isCompatible(component)) {
979 throw new IllegalStateException("Component " + component.getComponentName() +
980 " not currently compatible with component " + getComponentName());
983 children.add(position, component);
984 component.parent = this;
986 fireAddRemoveEvent(component);
991 * Removes a child from the rocket component tree.
993 * @param n remove the n'th child.
994 * @throws IndexOutOfBoundsException if n is out of bounds
996 public final void removeChild(int n) {
998 RocketComponent component = children.remove(n);
999 component.parent = null;
1000 fireAddRemoveEvent(component);
1004 * Removes a child from the rocket component tree. Does nothing if the component
1005 * is not present as a child.
1007 * @param component the component to remove
1008 * @return whether the component was a child
1010 public final boolean removeChild(RocketComponent component) {
1012 if (children.remove(component)) {
1013 component.parent = null;
1014 fireAddRemoveEvent(component);
1024 * Move a child to another position.
1026 * @param component the component to move
1027 * @param position the component's new position
1028 * @throws IllegalArgumentException If an illegal placement was attempted.
1030 public final void moveChild(RocketComponent component, int position) {
1032 if (children.remove(component)) {
1033 children.add(position, component);
1034 fireAddRemoveEvent(component);
1040 * Fires an AERODYNAMIC_CHANGE, MASS_CHANGE or OTHER_CHANGE event depending on the
1041 * type of component removed.
1043 private void fireAddRemoveEvent(RocketComponent component) {
1044 Iterator<RocketComponent> iter = component.deepIterator(true);
1045 int type = ComponentChangeEvent.TREE_CHANGE;
1046 while (iter.hasNext()) {
1047 RocketComponent c = iter.next();
1048 if (c.isAerodynamic())
1049 type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
1051 type |= ComponentChangeEvent.MASS_CHANGE;
1054 fireComponentChangeEvent(type);
1058 public final int getChildCount() {
1060 return children.size();
1063 public final RocketComponent getChild(int n) {
1065 return children.get(n);
1068 public final RocketComponent[] getChildren() {
1070 return children.toArray(new RocketComponent[0]);
1075 * Returns the position of the child in this components child list, or -1 if the
1076 * component is not a child of this component.
1078 * @param child The child to search for.
1079 * @return Position in the list or -1 if not found.
1081 public final int getChildPosition(RocketComponent child) {
1083 return children.indexOf(child);
1087 * Get the parent component of this component. Returns <code>null</code> if the component
1090 * @return The parent of this component or <code>null</code>.
1092 public final RocketComponent getParent() {
1098 * Get the root component of the component tree.
1100 * @return The root component of the component tree.
1102 public final RocketComponent getRoot() {
1104 RocketComponent gp = this;
1105 while (gp.parent != null)
1111 * Returns the root Rocket component of this component tree. Throws an
1112 * IllegalStateException if the root component is not a Rocket.
1114 * @return The root Rocket component of the component tree.
1115 * @throws IllegalStateException If the root component is not a Rocket.
1117 public final Rocket getRocket() {
1119 RocketComponent r = getRoot();
1120 if (r instanceof Rocket)
1122 throw new IllegalStateException("getRocket() called with root component "
1123 + r.getComponentName());
1128 * Return the Stage component that this component belongs to. Throws an
1129 * IllegalStateException if a Stage is not in the parentage of this component.
1131 * @return The Stage component this component belongs to.
1132 * @throws IllegalStateException if a Stage component is not in the parentage.
1134 public final Stage getStage() {
1136 RocketComponent c = this;
1138 if (c instanceof Stage)
1142 throw new IllegalStateException("getStage() called without Stage as a parent.");
1146 * Return the stage number of the stage this component belongs to. The stages
1147 * are numbered from zero upwards.
1149 * @return the stage number this component belongs to.
1151 public final int getStageNumber() {
1153 if (parent == null) {
1154 throw new IllegalArgumentException("getStageNumber() called for root component");
1157 RocketComponent stage = this;
1158 while (!(stage instanceof Stage)) {
1159 stage = stage.parent;
1160 if (stage == null || stage.parent == null) {
1161 throw new IllegalStateException("getStageNumber() could not find parent " +
1165 return stage.parent.getChildPosition(stage);
1170 * Find a component with the given ID. The component tree is searched from this component
1171 * down (including this component) for the ID and the corresponding component is returned,
1172 * or null if not found.
1174 * @param idToFind ID to search for.
1175 * @return The component with the ID, or null if not found.
1177 public final RocketComponent findComponent(String idToFind) {
1179 Iterator<RocketComponent> iter = this.deepIterator(true);
1180 while (iter.hasNext()) {
1181 RocketComponent c = iter.next();
1182 if (c.getID().equals(idToFind))
1189 public final RocketComponent getPreviousComponent() {
1193 int pos = parent.getChildPosition(this);
1195 StringBuffer sb = new StringBuffer();
1196 sb.append("Inconsistent internal state: ");
1197 sb.append("this=").append(this).append('[')
1198 .append(System.identityHashCode(this)).append(']');
1199 sb.append(" parent.children=[");
1200 for (int i = 0; i < parent.children.size(); i++) {
1201 RocketComponent c = parent.children.get(i);
1202 sb.append(c).append('[').append(System.identityHashCode(c)).append(']');
1203 if (i < parent.children.size() - 1)
1207 throw new IllegalStateException(sb.toString());
1212 RocketComponent c = parent.getChild(pos - 1);
1213 while (c.getChildCount() > 0)
1214 c = c.getChild(c.getChildCount() - 1);
1218 public final RocketComponent getNextComponent() {
1220 if (getChildCount() > 0)
1223 RocketComponent current = this;
1224 RocketComponent parent = this.parent;
1226 while (parent != null) {
1227 int pos = parent.getChildPosition(current);
1228 if (pos < parent.getChildCount() - 1)
1229 return parent.getChild(pos + 1);
1232 parent = current.parent;
1238 /////////// Event handling //////////
1240 // Listener lists are provided by the root Rocket component,
1241 // a single listener list for the whole rocket.
1245 * Adds a ComponentChangeListener to the rocket tree. The listener is added to the root
1246 * component, which must be of type Rocket (which overrides this method). Events of all
1247 * subcomponents are sent to all listeners.
1249 * @throws IllegalStateException - if the root component is not a Rocket
1251 public void addComponentChangeListener(ComponentChangeListener l) {
1253 getRocket().addComponentChangeListener(l);
1257 * Removes a ComponentChangeListener from the rocket tree. The listener is removed from
1258 * the root component, which must be of type Rocket (which overrides this method).
1259 * Does nothing if the root component is not a Rocket. (The asymmetry is so
1260 * that listeners can always be removed just in case.)
1262 * @param l Listener to remove
1264 public void removeComponentChangeListener(ComponentChangeListener l) {
1265 if (parent != null) {
1266 getRoot().removeComponentChangeListener(l);
1272 * Adds a <code>ChangeListener</code> to the rocket tree. This is identical to
1273 * <code>addComponentChangeListener()</code> except that it uses a
1274 * <code>ChangeListener</code>. The same events are dispatched to the
1275 * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass
1276 * of <code>ChangeEvent</code>.
1278 * @throws IllegalStateException - if the root component is not a <code>Rocket</code>
1280 public void addChangeListener(ChangeListener l) {
1282 getRocket().addChangeListener(l);
1286 * Removes a ChangeListener from the rocket tree. This is identical to
1287 * removeComponentChangeListener() except it uses a ChangeListener.
1288 * Does nothing if the root component is not a Rocket. (The asymmetry is so
1289 * that listeners can always be removed just in case.)
1291 * @param l Listener to remove
1293 public void removeChangeListener(ChangeListener l) {
1294 if (this.parent != null) {
1295 getRoot().removeChangeListener(l);
1301 * Fires a ComponentChangeEvent on the rocket structure. The call is passed to the
1302 * root component, which must be of type Rocket (which overrides this method).
1303 * Events of all subcomponents are sent to all listeners.
1305 * If the component tree root is not a Rocket, the event is ignored. This is the
1306 * case when constructing components not in any Rocket tree. In this case it
1307 * would be impossible for the component to have listeners in any case.
1309 * @param e Event to send
1311 protected void fireComponentChangeEvent(ComponentChangeEvent e) {
1313 if (parent == null) {
1314 /* Ignore if root invalid. */
1315 log.debug("Attempted firing event " + e + " with root " + this.getComponentName() + ", ignoring event");
1318 getRoot().fireComponentChangeEvent(e);
1323 * Fires a ComponentChangeEvent of the given type. The source of the event is set to
1326 * @param type Type of event
1327 * @see #fireComponentChangeEvent(ComponentChangeEvent)
1329 protected void fireComponentChangeEvent(int type) {
1330 fireComponentChangeEvent(new ComponentChangeEvent(this, type));
1335 * Checks whether this component has been invalidated and should no longer be used.
1336 * This is a safety check that in-place replaced components are no longer used.
1337 * All non-trivial methods (with the exception of methods simply getting a property)
1338 * should call this method before changing or computing anything.
1340 * @throws BugException if this component has been invalidated by {@link #copyFrom(RocketComponent)}.
1342 protected void checkState() {
1343 if (invalidated != null) {
1344 throw new BugException("This component has been invalidated. Cause is the point of invalidation.",
1350 /////////// Iterator implementation //////////
1353 * Private inner class to implement the Iterator.
1355 * This iterator is fail-fast if the root of the structure is a Rocket.
1357 private class RocketComponentIterator implements Iterator<RocketComponent> {
1358 // Stack holds iterators which still have some components left.
1359 private final Stack<Iterator<RocketComponent>> iteratorstack =
1360 new Stack<Iterator<RocketComponent>>();
1362 private final Rocket root;
1363 private final int treeModID;
1365 private final RocketComponent original;
1366 private boolean returnSelf = false;
1368 // Construct iterator with component's child's iterator, if it has elements
1369 public RocketComponentIterator(RocketComponent c, boolean returnSelf) {
1371 RocketComponent gp = c.getRoot();
1372 if (gp instanceof Rocket) {
1374 treeModID = root.getTreeModID();
1380 Iterator<RocketComponent> i = c.children.iterator();
1382 iteratorstack.push(i);
1385 this.returnSelf = returnSelf;
1388 public boolean hasNext() {
1393 return !iteratorstack.empty(); // Elements remain if stack is not empty
1396 public RocketComponent next() {
1397 Iterator<RocketComponent> i;
1402 // Return original component first
1408 // Peek first iterator from stack, throw exception if empty
1410 i = iteratorstack.peek();
1411 } catch (EmptyStackException e) {
1412 throw new NoSuchElementException("No further elements in " +
1413 "RocketComponent iterator");
1416 // Retrieve next component of the iterator, remove iterator from stack if empty
1417 RocketComponent c = i.next();
1419 iteratorstack.pop();
1421 // Add iterator of component children to stack if it has children
1422 i = c.children.iterator();
1424 iteratorstack.push(i);
1429 private void checkID() {
1431 if (root.getTreeModID() != treeModID) {
1432 throw new IllegalStateException("Rocket modified while being iterated");
1437 public void remove() {
1438 throw new UnsupportedOperationException("remove() not supported by " +
1439 "RocketComponent iterator");
1444 * Returns an iterator that iterates over all children and sub-children.
1446 * The iterator iterates through all children below this object, including itself if
1447 * returnSelf is true. The order of the iteration is not specified
1448 * (it may be specified in the future).
1450 * If an iterator iterating over only the direct children of the component is required,
1451 * use component.getChildren().iterator()
1453 * @param returnSelf boolean value specifying whether the component itself should be
1455 * @return An iterator for the children and sub-children.
1457 public final Iterator<RocketComponent> deepIterator(boolean returnSelf) {
1459 return new RocketComponentIterator(this, returnSelf);
1463 * Returns an iterator that iterates over all children and sub-children.
1465 * The iterator does NOT return the component itself. It is thus equivalent to
1466 * deepIterator(false).
1469 * @return An iterator for the children and sub-children.
1471 public final Iterator<RocketComponent> deepIterator() {
1473 return new RocketComponentIterator(this, false);
1478 * Return an iterator that iterates of the children of the component. The iterator
1479 * does NOT recurse to sub-children nor return itself.
1481 * @return An iterator for the children.
1483 public final Iterator<RocketComponent> iterator() {
1485 return Collections.unmodifiableList(children).iterator();
1492 * Compare component equality based on the ID of this component. Only the
1493 * ID and class type is used for a basis of comparison.
1496 public boolean equals(Object obj) {
1501 if (this.getClass() != obj.getClass())
1503 RocketComponent other = (RocketComponent) obj;
1504 return this.id.equals(other.id);
1510 public int hashCode() {
1511 return id.hashCode();
1516 //////////// Helper methods for subclasses
1522 * Helper method to add rotationally symmetric bounds at the specified coordinates.
1523 * The X-axis value is <code>x</code> and the radius at the specified position is
1526 protected static final void addBound(Collection<Coordinate> bounds, double x, double r) {
1527 bounds.add(new Coordinate(x, -r, -r));
1528 bounds.add(new Coordinate(x, r, -r));
1529 bounds.add(new Coordinate(x, r, r));
1530 bounds.add(new Coordinate(x, -r, r));
1534 protected static final Coordinate ringCG(double outerRadius, double innerRadius,
1535 double x1, double x2, double density) {
1536 return new Coordinate((x1 + x2) / 2, 0, 0,
1537 ringMass(outerRadius, innerRadius, x2 - x1, density));
1540 protected static final double ringMass(double outerRadius, double innerRadius,
1541 double length, double density) {
1542 return Math.PI * (MathUtil.pow2(outerRadius) - MathUtil.pow2(innerRadius)) *
1546 protected static final double ringLongitudalUnitInertia(double outerRadius,
1547 double innerRadius, double length) {
1548 // 1/12 * (3 * (r1^2 + r2^2) + h^2)
1549 return (3 * (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) + MathUtil.pow2(length)) / 12;
1552 protected static final double ringRotationalUnitInertia(double outerRadius,
1553 double innerRadius) {
1554 // 1/2 * (r1^2 + r2^2)
1555 return (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) / 2;
1564 * Loads the RocketComponent fields from the given component. This method is meant
1565 * for in-place replacement of a component. It is used with the undo/redo
1566 * mechanism and when converting a finset into a freeform fin set.
1567 * This component must not have a parent, otherwise this method will fail.
1569 * The fields are copied by reference, and the supplied component must not be used
1570 * after the call, as it is in an undefined state. This is enforced by invalidating
1571 * the source component.
1573 * TODO: MEDIUM: Make general to copy all private/protected fields...
1575 protected void copyFrom(RocketComponent src) {
1578 if (this.parent != null) {
1579 throw new UnsupportedOperationException("copyFrom called for non-root component "
1583 // Set parents and children
1584 this.children = src.children;
1585 src.children = new ArrayList<RocketComponent>();
1587 for (RocketComponent c : this.children) {
1591 // Set all parameters
1592 this.length = src.length;
1593 this.relativePosition = src.relativePosition;
1594 this.position = src.position;
1595 this.color = src.color;
1596 this.lineStyle = src.lineStyle;
1597 this.overrideMass = src.overrideMass;
1598 this.massOverriden = src.massOverriden;
1599 this.overrideCGX = src.overrideCGX;
1600 this.cgOverriden = src.cgOverriden;
1601 this.overrideSubcomponents = src.overrideSubcomponents;
1602 this.name = src.name;
1603 this.comment = src.comment;
1606 src.invalidated = new TraceException();