1 package net.sf.openrocket.rocketcomponent;
3 import net.sf.openrocket.logging.LogHelper;
4 import net.sf.openrocket.startup.Application;
5 import net.sf.openrocket.util.ArrayList;
6 import net.sf.openrocket.util.*;
8 import javax.swing.event.ChangeListener;
11 import java.util.List;
14 public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent> {
15 private static final LogHelper log = Application.getLogger();
18 * Text is suitable to the form
19 * Position relative to: <title>
21 public enum Position {
22 /** Position relative to the top of the parent component. */
23 TOP("Top of the parent component"),
24 /** Position relative to the middle of the parent component. */
25 MIDDLE("Middle of the parent component"),
26 /** Position relative to the bottom of the parent component. */
27 BOTTOM("Bottom of the parent component"),
28 /** Position after the parent component (for body components). */
29 AFTER("After the parent component"),
30 /** Specify an absolute X-coordinate position. */
31 ABSOLUTE("Tip of the nose cone");
35 Position(String title) {
40 public String toString() {
46 * A safety mutex that can be used to prevent concurrent access to this component.
48 protected SafetyMutex mutex = SafetyMutex.newInstance();
50 //////// Parent/child trees
52 * Parent component of the current component, or null if none exists.
54 private RocketComponent parent = null;
57 * List of child components of this component.
59 private ArrayList<RocketComponent> children = new ArrayList<RocketComponent>();
62 //////// Parameters common to all components:
65 * Characteristic length of the component. This is used in calculating the coordinate
66 * transformations and positions of other components in reference to this component.
67 * This may and should be used as the "true" length of the component, where applicable.
68 * By default it is zero, i.e. no translation.
70 protected double length = 0;
73 * Positioning of this component relative to the parent component.
75 protected Position relativePosition;
78 * Offset of the position of this component relative to the normal position given by
79 * relativePosition. By default zero, i.e. no position change.
81 protected double position = 0;
84 // Color of the component, null means to use the default color
85 private Color color = null;
86 private LineStyle lineStyle = null;
90 private double overrideMass = 0;
91 private boolean massOverriden = false;
92 private double overrideCGX = 0;
93 private boolean cgOverriden = false;
95 private boolean overrideSubcomponents = false;
98 // User-given name of the component
99 private String name = null;
101 // User-specified comment
102 private String comment = "";
104 // Unique ID of the component
105 private String id = null;
108 * Used to invalidate the component after calling {@link #copyFrom(RocketComponent)}.
110 private Invalidator invalidator = new Invalidator(this);
113 //// NOTE !!! All fields must be copied in the method copyFrom()! ////
118 * Default constructor. Sets the name of the component to the component's static name
119 * and the relative position of the component.
121 public RocketComponent(Position relativePosition) {
122 // These must not fire any events, due to Rocket undo system initialization
123 this.name = getComponentName();
124 this.relativePosition = relativePosition;
128 //////////// Methods that must be implemented ////////////
132 * Static component name. The name may not vary of the parameters, it must be static.
134 public abstract String getComponentName(); // Static component type name
137 * Return the component mass (regardless of mass overriding).
139 public abstract double getComponentMass(); // Mass of non-overridden component
142 * Return the component CG and mass (regardless of CG or mass overriding).
144 public abstract Coordinate getComponentCG(); // CG of non-overridden component
148 * Return the longitudinal (around the y- or z-axis) unitary moment of inertia.
149 * The unitary moment of inertia is the moment of inertia with the assumption that
150 * the mass of the component is one kilogram. The inertia is measured in
151 * respect to the non-overridden CG.
153 * @return the longitudinal unitary moment of inertia of this component.
155 public abstract double getLongitudinalUnitInertia();
159 * Return the rotational (around the x-axis) unitary moment of inertia.
160 * The unitary moment of inertia is the moment of inertia with the assumption that
161 * the mass of the component is one kilogram. The inertia is measured in
162 * respect to the non-overridden CG.
164 * @return the rotational unitary moment of inertia of this component.
166 public abstract double getRotationalUnitInertia();
170 * Test whether this component allows any children components. This method must
171 * return true if and only if {@link #isCompatible(Class)} returns true for any
172 * rocket component class.
174 * @return <code>true</code> if children can be attached to this component, <code>false</code> otherwise.
176 public abstract boolean allowsChildren();
179 * Test whether the given component type can be added to this component. This type safety
180 * is enforced by the <code>addChild()</code> methods. The return value of this method
181 * may change to reflect the current state of this component (e.g. two components of some
182 * type cannot be placed as children).
184 * @param type The RocketComponent class type to add.
185 * @return Whether such a component can be added.
187 public abstract boolean isCompatible(Class<? extends RocketComponent> type);
190 /* Non-abstract helper method */
192 * Test whether the given component can be added to this component. This is equivalent
193 * to calling <code>isCompatible(c.getClass())</code>.
195 * @param c Component to test.
196 * @return Whether the component can be added.
197 * @see #isCompatible(Class)
199 public final boolean isCompatible(RocketComponent c) {
201 return isCompatible(c.getClass());
207 * Return a collection of bounding coordinates. The coordinates must be such that
208 * the component is fully enclosed in their convex hull.
210 * @return a collection of coordinates that bound the component.
212 public abstract Collection<Coordinate> getComponentBounds();
215 * Return true if the component may have an aerodynamic effect on the rocket.
217 public abstract boolean isAerodynamic();
220 * Return true if the component may have an effect on the rocket's mass.
222 public abstract boolean isMassive();
228 //////////// Methods that may be overridden ////////////
232 * Shift the coordinates in the array corresponding to radial movement. A component
233 * that has a radial position must shift the coordinates in this array suitably.
234 * If the component is clustered, then a new array must be returned with a
235 * coordinate for each cluster.
237 * The default implementation simply returns the array, and thus produces no shift.
239 * @param c an array of coordinates to shift.
240 * @return an array of shifted coordinates. The method may modify the contents
241 * of the passed array and return the array itself.
243 public Coordinate[] shiftCoordinates(Coordinate[] c) {
250 * Called when any component in the tree fires a ComponentChangeEvent. This is by
251 * default a no-op, but subclasses may override this method to e.g. invalidate
252 * cached data. The overriding method *must* call
253 * <code>super.componentChanged(e)</code> at some point.
255 * @param e The event fired
257 protected void componentChanged(ComponentChangeEvent e) {
266 * Return the user-provided name of the component, or the component base
267 * name if the user-provided name is empty. This can be used in the UI.
269 * @return A string describing the component.
272 public final String toString() {
274 if (name.length() == 0)
275 return getComponentName();
282 * Create a string describing the basic component structure from this component downwards.
283 * @return a string containing the rocket structure
285 public final String toDebugString() {
286 mutex.lock("toDebugString");
288 StringBuilder sb = new StringBuilder();
290 return sb.toString();
292 mutex.unlock("toDebugString");
296 private void toDebugString(StringBuilder sb) {
297 sb.append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this));
298 sb.append("[\"").append(this.getName()).append('"');
299 for (RocketComponent c : this.children) {
308 * Make a deep copy of the rocket component tree structure from this component
309 * downwards for copying purposes. Each component in the copy will be assigned
310 * a new component ID, making it a safe copy. This method does not fire any events.
312 * @return A deep copy of the structure.
314 public final RocketComponent copy() {
315 RocketComponent clone = copyWithOriginalID();
317 Iterator<RocketComponent> iterator = clone.iterator(true);
318 while (iterator.hasNext()) {
319 iterator.next().newID();
327 * Make a deep copy of the rocket component tree structure from this component
328 * downwards while maintaining the component ID's. The purpose of this method is
329 * to allow copies to be created with the original ID's for the purpose of the
330 * undo/redo mechanism. This method should not be used for other purposes,
331 * such as copy/paste. This method does not fire any events.
333 * This method must be overridden by any component that refers to mutable objects,
334 * or if some fields should not be copied. This should be performed by
335 * <code>RocketComponent c = super.copyWithOriginalID();</code> and then cloning/modifying
336 * the appropriate fields.
338 * This is not performed as serializing/deserializing for performance reasons.
340 * @return A deep copy of the structure.
342 protected RocketComponent copyWithOriginalID() {
343 mutex.lock("copyWithOriginalID");
346 RocketComponent clone;
348 clone = (RocketComponent) this.clone();
349 } catch (CloneNotSupportedException e) {
350 throw new BugException("CloneNotSupportedException encountered, report a bug!", e);
354 clone.mutex = SafetyMutex.newInstance();
356 // Reset all parent/child information
358 clone.children = new ArrayList<RocketComponent>();
360 // Add copied children to the structure without firing events.
361 for (RocketComponent child : this.children) {
362 RocketComponent childCopy = child.copyWithOriginalID();
363 // Don't use add method since it fires events
364 clone.children.add(childCopy);
365 childCopy.parent = clone;
368 this.checkComponentStructure();
369 clone.checkComponentStructure();
373 mutex.unlock("copyWithOriginalID");
378 ////////////// Methods that may not be overridden ////////////
382 ////////// Common parameter setting/getting //////////
385 * Return the color of the object to use in 2D figures, or <code>null</code>
386 * to use the default color.
388 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() {
412 public final void setLineStyle(LineStyle style) {
413 if (this.lineStyle == style)
416 this.lineStyle = style;
417 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
424 * Get the current override mass. The mass is not necessarily in use
427 * @return the override mass
429 public final double getOverrideMass() {
435 * Set the current override mass. The mass is not set to use by this
438 * @param m the override mass
440 public final void setOverrideMass(double m) {
441 if (MathUtil.equals(m, overrideMass))
444 overrideMass = Math.max(m, 0);
446 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
450 * Return whether mass override is active for this component. This does NOT
451 * take into account whether a parent component is overriding the mass.
453 * @return whether the mass is overridden
455 public final boolean isMassOverridden() {
457 return massOverriden;
461 * Set whether the mass is currently overridden.
463 * @param o whether the mass is overridden
465 public final void setMassOverridden(boolean o) {
466 if (massOverriden == o) {
471 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
479 * Return the current override CG. The CG is not necessarily overridden.
481 * @return the override CG
483 public final Coordinate getOverrideCG() {
485 return getComponentCG().setX(overrideCGX);
489 * Return the x-coordinate of the current override CG.
491 * @return the x-coordinate of the override CG.
493 public final double getOverrideCGX() {
499 * Set the current override CG to (x,0,0).
501 * @param x the x-coordinate of the override CG to set.
503 public final void setOverrideCGX(double x) {
504 if (MathUtil.equals(overrideCGX, x))
507 this.overrideCGX = x;
508 if (isCGOverridden())
509 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
511 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
515 * Return whether the CG is currently overridden.
517 * @return whether the CG is overridden
519 public final boolean isCGOverridden() {
525 * Set whether the CG is currently overridden.
527 * @param o whether the CG is overridden
529 public final void setCGOverridden(boolean o) {
530 if (cgOverriden == o) {
535 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
541 * Return whether the mass and/or CG override overrides all subcomponent values
542 * as well. The default implementation is a normal getter/setter implementation,
543 * however, subclasses are allowed to override this behavior if some subclass
544 * always or never overrides subcomponents. In this case the subclass should
545 * also override {@link #isOverrideSubcomponentsEnabled()} to return
546 * <code>false</code>.
548 * @return whether the current mass and/or CG override overrides subcomponents as well.
550 public boolean getOverrideSubcomponents() {
552 return overrideSubcomponents;
557 * Set whether the mass and/or CG override overrides all subcomponent values
558 * as well. See {@link #getOverrideSubcomponents()} for details.
560 * @param override whether the mass and/or CG override overrides all subcomponent.
562 public void setOverrideSubcomponents(boolean override) {
563 if (overrideSubcomponents == override) {
567 overrideSubcomponents = override;
568 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
572 * Return whether the option to override all subcomponents is enabled or not.
573 * The default implementation returns <code>false</code> if neither mass nor
574 * CG is overridden, <code>true</code> otherwise.
576 * This method may be overridden if the setting of overriding subcomponents
579 * @return whether the option to override subcomponents is currently enabled.
581 public boolean isOverrideSubcomponentsEnabled() {
583 return isCGOverridden() || isMassOverridden();
590 * Get the user-defined name of the component.
592 public final String getName() {
598 * Set the user-defined name of the component. If name==null, sets the name to
599 * the default name, currently the component name.
601 public final void setName(String name) {
602 if (this.name.equals(name)) {
606 if (name == null || name.matches("^\\s*$"))
607 this.name = getComponentName();
610 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
615 * Return the comment of the component. The component may contain multiple lines
616 * using \n as a newline separator.
618 * @return the comment of the component.
620 public final String getComment() {
626 * Set the comment of the component.
628 * @param comment the comment of the component.
630 public final void setComment(String comment) {
631 if (this.comment.equals(comment))
637 this.comment = comment;
638 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
644 * Returns the unique ID of the component.
646 * @return the ID of the component.
648 public final String getID() {
653 * Generate a new ID for this component.
655 private final void newID() {
657 this.id = UniqueID.uuid();
664 * Get the characteristic length of the component, for example the length of a body tube
665 * of the length of the root chord of a fin. This is used in positioning the component
666 * relative to its parent.
668 * If the length of a component is settable, the class must define the setter method
671 public final double getLength() {
677 * Get the positioning of the component relative to its parent component.
678 * This is one of the enums of {@link Position}. A setter method is not provided,
679 * but can be provided by a subclass.
681 public final Position getRelativePosition() {
683 return relativePosition;
688 * Set the positioning of the component relative to its parent component.
689 * The actual position of the component is maintained to the best ability.
691 * The default implementation is of protected visibility, since many components
692 * do not support setting the relative position. A component that does support
693 * it should override this with a public method that simply calls this
694 * supermethod AND fire a suitable ComponentChangeEvent.
696 * @param position the relative positioning.
698 protected void setRelativePosition(RocketComponent.Position position) {
699 if (this.relativePosition == position)
703 // Update position so as not to move the component
704 if (this.parent != null) {
705 double thisPos = this.toRelative(Coordinate.NUL, this.parent)[0].x;
709 this.position = this.toAbsolute(Coordinate.NUL)[0].x;
713 this.position = thisPos;
717 this.position = thisPos - (this.parent.length - this.length) / 2;
721 this.position = thisPos - (this.parent.length - this.length);
725 throw new BugException("Unknown position type: " + position);
729 this.relativePosition = position;
730 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
737 * Get the position value of the component. The exact meaning of the value is
738 * dependent on the current relative positioning.
740 * @return the positional value.
742 public final double getPositionValue() {
749 * Set the position value of the component. The exact meaning of the value
750 * depends on the current relative positioning.
752 * The default implementation is of protected visibility, since many components
753 * do not support setting the relative position. A component that does support
754 * it should override this with a public method that simply calls this
755 * supermethod AND fire a suitable ComponentChangeEvent.
757 * @param value the position value of the component.
759 public void setPositionValue(double value) {
760 if (MathUtil.equals(this.position, value))
763 this.position = value;
768 /////////// Coordinate changes ///////////
771 * Returns coordinate c in absolute coordinates. Equivalent to toComponent(c,null).
773 public Coordinate[] toAbsolute(Coordinate c) {
775 return toRelative(c, null);
780 * Return coordinate <code>c</code> described in the coordinate system of
781 * <code>dest</code>. If <code>dest</code> is <code>null</code> returns
782 * absolute coordinates.
784 * This method returns an array of coordinates, each of which represents a
785 * position of the coordinate in clustered cases. The array is guaranteed
786 * to contain at least one element.
788 * The current implementation does not support rotating components.
790 * @param c Coordinate in the component's coordinate system.
791 * @param dest Destination component coordinate system.
792 * @return an array of coordinates describing <code>c</code> in coordinates
793 * relative to <code>dest</code>.
795 public final Coordinate[] toRelative(Coordinate c, RocketComponent dest) {
797 mutex.lock("toRelative");
799 double absoluteX = Double.NaN;
800 RocketComponent search = dest;
801 Coordinate[] array = new Coordinate[1];
804 RocketComponent component = this;
805 while ((component != search) && (component.parent != null)) {
807 array = component.shiftCoordinates(array);
809 switch (component.relativePosition) {
811 for (int i = 0; i < array.length; i++) {
812 array[i] = array[i].add(component.position, 0, 0);
817 for (int i = 0; i < array.length; i++) {
818 array[i] = array[i].add(component.position +
819 (component.parent.length - component.length) / 2, 0, 0);
824 for (int i = 0; i < array.length; i++) {
825 array[i] = array[i].add(component.position +
826 (component.parent.length - component.length), 0, 0);
831 // Add length of all previous brother-components with POSITION_RELATIVE_AFTER
832 int index = component.parent.children.indexOf(component);
834 for (index--; index >= 0; index--) {
835 RocketComponent comp = component.parent.children.get(index);
836 double componentLength = comp.getTotalLength();
837 for (int i = 0; i < array.length; i++) {
838 array[i] = array[i].add(componentLength, 0, 0);
841 for (int i = 0; i < array.length; i++) {
842 array[i] = array[i].add(component.position + component.parent.length, 0, 0);
847 search = null; // Requires back-search if dest!=null
848 if (Double.isNaN(absoluteX)) {
849 absoluteX = component.position;
854 throw new BugException("Unknown relative positioning type of component" +
855 component + ": " + component.relativePosition);
858 component = component.parent; // parent != null
861 if (!Double.isNaN(absoluteX)) {
862 for (int i = 0; i < array.length; i++) {
863 array[i] = array[i].setX(absoluteX + c.x);
867 // Check whether destination has been found or whether to backtrack
868 // TODO: LOW: Backtracking into clustered components uses only one component
869 if ((dest != null) && (component != dest)) {
870 Coordinate[] origin = dest.toAbsolute(Coordinate.NUL);
871 for (int i = 0; i < array.length; i++) {
872 array[i] = array[i].sub(origin[0]);
878 mutex.unlock("toRelative");
884 * Recursively sum the lengths of all subcomponents that have position
887 * @return Sum of the lengths.
889 private final double getTotalLength() {
891 this.checkComponentStructure();
892 mutex.lock("getTotalLength");
895 if (relativePosition == Position.AFTER)
897 for (int i = 0; i < children.size(); i++)
898 l += children.get(i).getTotalLength();
901 mutex.unlock("getTotalLength");
907 /////////// Total mass and CG calculation ////////////
910 * Return the (possibly overridden) mass of component.
912 * @return The mass of the component or the given override mass.
914 public final double getMass() {
918 return getComponentMass();
922 * Return the (possibly overridden) center of gravity and mass.
924 * Returns the CG with the weight of the coordinate set to the weight of the component.
925 * Both CG and mass may be separately overridden.
927 * @return The CG of the component or the given override CG.
929 public final Coordinate getCG() {
932 return getOverrideCG().setWeight(getMass());
935 return getComponentCG().setWeight(getMass());
937 return getComponentCG();
942 * Return the longitudinal (around the y- or z-axis) moment of inertia of this component.
943 * The moment of inertia is scaled in reference to the (possibly overridden) mass
944 * and is relative to the non-overridden CG.
946 * @return the longitudinal moment of inertia of this component.
948 public final double getLongitudinalInertia() {
950 return getLongitudinalUnitInertia() * getMass();
954 * Return the rotational (around the y- or z-axis) moment of inertia of this component.
955 * The moment of inertia is scaled in reference to the (possibly overridden) mass
956 * and is relative to the non-overridden CG.
958 * @return the rotational moment of inertia of this component.
960 public final double getRotationalInertia() {
962 return getRotationalUnitInertia() * getMass();
967 /////////// Children handling ///////////
971 * Adds a child to the rocket component tree. The component is added to the end
972 * of the component's child list. This is a helper method that calls
973 * {@link #addChild(RocketComponent,int)}.
975 * @param component The component to add.
976 * @throws IllegalArgumentException if the component is already part of some
978 * @see #addChild(RocketComponent,int)
980 public final void addChild(RocketComponent component) {
982 addChild(component, children.size());
987 * Adds a child to the rocket component tree. The component is added to
988 * the given position of the component's child list.
990 * This method may be overridden to enforce more strict component addition rules.
991 * The tests should be performed first and then this method called.
993 * @param component The component to add.
994 * @param index Position to add component to.
995 * @throws IllegalArgumentException If the component is already part of
996 * some component tree.
998 public void addChild(RocketComponent component, int index) {
1000 if (component.parent != null) {
1001 throw new IllegalArgumentException("component " + component.getComponentName() +
1002 " is already in a tree");
1004 if (!isCompatible(component)) {
1005 throw new IllegalStateException("Component " + component.getComponentName() +
1006 " not currently compatible with component " + getComponentName());
1009 children.add(index, component);
1010 component.parent = this;
1012 this.checkComponentStructure();
1013 component.checkComponentStructure();
1015 fireAddRemoveEvent(component);
1020 * Removes a child from the rocket component tree.
1022 * @param n remove the n'th child.
1023 * @throws IndexOutOfBoundsException if n is out of bounds
1025 public final void removeChild(int n) {
1027 RocketComponent component = children.remove(n);
1028 component.parent = null;
1030 this.checkComponentStructure();
1031 component.checkComponentStructure();
1033 fireAddRemoveEvent(component);
1037 * Removes a child from the rocket component tree. Does nothing if the component
1038 * is not present as a child.
1040 * @param component the component to remove
1041 * @return whether the component was a child
1043 public final boolean removeChild(RocketComponent component) {
1046 component.checkComponentStructure();
1048 if (children.remove(component)) {
1049 component.parent = null;
1051 this.checkComponentStructure();
1052 component.checkComponentStructure();
1054 fireAddRemoveEvent(component);
1064 * Move a child to another position.
1066 * @param component the component to move
1067 * @param index the component's new position
1068 * @throws IllegalArgumentException If an illegal placement was attempted.
1070 public final void moveChild(RocketComponent component, int index) {
1072 if (children.remove(component)) {
1073 children.add(index, component);
1075 this.checkComponentStructure();
1076 component.checkComponentStructure();
1078 fireAddRemoveEvent(component);
1084 * Fires an AERODYNAMIC_CHANGE, MASS_CHANGE or OTHER_CHANGE event depending on the
1085 * type of component removed.
1087 private void fireAddRemoveEvent(RocketComponent component) {
1088 Iterator<RocketComponent> iter = component.iterator(true);
1089 int type = ComponentChangeEvent.TREE_CHANGE;
1090 while (iter.hasNext()) {
1091 RocketComponent c = iter.next();
1092 if (c.isAerodynamic())
1093 type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
1095 type |= ComponentChangeEvent.MASS_CHANGE;
1098 fireComponentChangeEvent(type);
1102 public final int getChildCount() {
1104 this.checkComponentStructure();
1105 return children.size();
1108 public final RocketComponent getChild(int n) {
1110 this.checkComponentStructure();
1111 return children.get(n);
1114 public final List<RocketComponent> getChildren() {
1116 this.checkComponentStructure();
1117 return children.clone();
1122 * Returns the position of the child in this components child list, or -1 if the
1123 * component is not a child of this component.
1125 * @param child The child to search for.
1126 * @return Position in the list or -1 if not found.
1128 public final int getChildPosition(RocketComponent child) {
1130 this.checkComponentStructure();
1131 return children.indexOf(child);
1135 * Get the parent component of this component. Returns <code>null</code> if the component
1138 * @return The parent of this component or <code>null</code>.
1140 public final RocketComponent getParent() {
1146 * Get the root component of the component tree.
1148 * @return The root component of the component tree.
1150 public final RocketComponent getRoot() {
1152 RocketComponent gp = this;
1153 while (gp.parent != null)
1159 * Returns the root Rocket component of this component tree. Throws an
1160 * IllegalStateException if the root component is not a Rocket.
1162 * @return The root Rocket component of the component tree.
1163 * @throws IllegalStateException If the root component is not a Rocket.
1165 public final Rocket getRocket() {
1167 RocketComponent r = getRoot();
1168 if (r instanceof Rocket)
1170 throw new IllegalStateException("getRocket() called with root component "
1171 + r.getComponentName());
1176 * Return the Stage component that this component belongs to. Throws an
1177 * IllegalStateException if a Stage is not in the parentage of this component.
1179 * @return The Stage component this component belongs to.
1180 * @throws IllegalStateException if a Stage component is not in the parentage.
1182 public final Stage getStage() {
1184 RocketComponent c = this;
1186 if (c instanceof Stage)
1190 throw new IllegalStateException("getStage() called without Stage as a parent.");
1194 * Return the stage number of the stage this component belongs to. The stages
1195 * are numbered from zero upwards.
1197 * @return the stage number this component belongs to.
1199 public final int getStageNumber() {
1201 if (parent == null) {
1202 throw new IllegalArgumentException("getStageNumber() called for root component");
1205 RocketComponent stage = this;
1206 while (!(stage instanceof Stage)) {
1207 stage = stage.parent;
1208 if (stage == null || stage.parent == null) {
1209 throw new IllegalStateException("getStageNumber() could not find parent " +
1213 return stage.parent.getChildPosition(stage);
1218 * Find a component with the given ID. The component tree is searched from this component
1219 * down (including this component) for the ID and the corresponding component is returned,
1220 * or null if not found.
1222 * @param idToFind ID to search for.
1223 * @return The component with the ID, or null if not found.
1225 public final RocketComponent findComponent(String idToFind) {
1227 Iterator<RocketComponent> iter = this.iterator(true);
1228 while (iter.hasNext()) {
1229 RocketComponent c = iter.next();
1230 if (c.getID().equals(idToFind))
1237 // TODO: Move these methods elsewhere (used only in SymmetricComponent)
1238 public final RocketComponent getPreviousComponent() {
1240 this.checkComponentStructure();
1243 int pos = parent.getChildPosition(this);
1245 StringBuffer sb = new StringBuffer();
1246 sb.append("Inconsistent internal state: ");
1247 sb.append("this=").append(this).append('[')
1248 .append(System.identityHashCode(this)).append(']');
1249 sb.append(" parent.children=[");
1250 for (int i = 0; i < parent.children.size(); i++) {
1251 RocketComponent c = parent.children.get(i);
1252 sb.append(c).append('[').append(System.identityHashCode(c)).append(']');
1253 if (i < parent.children.size() - 1)
1257 throw new IllegalStateException(sb.toString());
1262 RocketComponent c = parent.getChild(pos - 1);
1263 while (c.getChildCount() > 0)
1264 c = c.getChild(c.getChildCount() - 1);
1268 // TODO: Move these methods elsewhere (used only in SymmetricComponent)
1269 public final RocketComponent getNextComponent() {
1271 if (getChildCount() > 0)
1274 RocketComponent current = this;
1275 RocketComponent nextParent = this.parent;
1277 while (nextParent != null) {
1278 int pos = nextParent.getChildPosition(current);
1279 if (pos < nextParent.getChildCount() - 1)
1280 return nextParent.getChild(pos + 1);
1282 current = nextParent;
1283 nextParent = current.parent;
1289 /////////// Event handling //////////
1291 // Listener lists are provided by the root Rocket component,
1292 // a single listener list for the whole rocket.
1296 * Adds a ComponentChangeListener to the rocket tree. The listener is added to the root
1297 * component, which must be of type Rocket (which overrides this method). Events of all
1298 * subcomponents are sent to all listeners.
1300 * @throws IllegalStateException - if the root component is not a Rocket
1302 public void addComponentChangeListener(ComponentChangeListener l) {
1304 getRocket().addComponentChangeListener(l);
1308 * Removes a ComponentChangeListener from the rocket tree. The listener is removed from
1309 * the root component, which must be of type Rocket (which overrides this method).
1310 * Does nothing if the root component is not a Rocket. (The asymmetry is so
1311 * that listeners can always be removed just in case.)
1313 * @param l Listener to remove
1315 public void removeComponentChangeListener(ComponentChangeListener l) {
1316 if (parent != null) {
1317 getRoot().removeComponentChangeListener(l);
1323 * Adds a <code>ChangeListener</code> to the rocket tree. This is identical to
1324 * <code>addComponentChangeListener()</code> except that it uses a
1325 * <code>ChangeListener</code>. The same events are dispatched to the
1326 * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass
1327 * of <code>ChangeEvent</code>.
1329 * @throws IllegalStateException - if the root component is not a <code>Rocket</code>
1332 public void addChangeListener(ChangeListener l) {
1334 getRocket().addChangeListener(l);
1338 * Removes a ChangeListener from the rocket tree. This is identical to
1339 * removeComponentChangeListener() except it uses a ChangeListener.
1340 * Does nothing if the root component is not a Rocket. (The asymmetry is so
1341 * that listeners can always be removed just in case.)
1343 * @param l Listener to remove
1346 public void removeChangeListener(ChangeListener l) {
1347 if (this.parent != null) {
1348 getRoot().removeChangeListener(l);
1354 * Fires a ComponentChangeEvent on the rocket structure. The call is passed to the
1355 * root component, which must be of type Rocket (which overrides this method).
1356 * Events of all subcomponents are sent to all listeners.
1358 * If the component tree root is not a Rocket, the event is ignored. This is the
1359 * case when constructing components not in any Rocket tree. In this case it
1360 * would be impossible for the component to have listeners in any case.
1362 * @param e Event to send
1364 protected void fireComponentChangeEvent(ComponentChangeEvent e) {
1366 if (parent == null) {
1367 /* Ignore if root invalid. */
1368 log.debug("Attempted firing event " + e + " with root " + this.getComponentName() + ", ignoring event");
1371 getRoot().fireComponentChangeEvent(e);
1376 * Fires a ComponentChangeEvent of the given type. The source of the event is set to
1379 * @param type Type of event
1380 * @see #fireComponentChangeEvent(ComponentChangeEvent)
1382 protected void fireComponentChangeEvent(int type) {
1383 fireComponentChangeEvent(new ComponentChangeEvent(this, type));
1388 * Checks whether this component has been invalidated and should no longer be used.
1389 * This is a safety check that in-place replaced components are no longer used.
1390 * All non-trivial methods (with the exception of methods simply getting a property)
1391 * should call this method before changing or computing anything.
1393 * @throws BugException if this component has been invalidated by {@link #copyFrom(RocketComponent)}.
1395 protected void checkState() {
1396 invalidator.check(true);
1402 * Check that the local component structure is correct. This can be called after changing
1403 * the component structure in order to verify the integrity.
1405 * TODO: Remove this after the "inconsistent internal state" bug has been corrected
1407 public void checkComponentStructure() {
1408 if (this.parent != null) {
1409 // Test that this component is found in parent's children with == operator
1410 if (!containsExact(this.parent.children, this)) {
1411 throw new BugException("Inconsistent component structure detected, parent does not contain this " +
1412 "component as a child, parent=" + parent.toDebugString() + " this=" + this.toDebugString());
1415 for (RocketComponent child : this.children) {
1416 if (child.parent != this) {
1417 throw new BugException("Inconsistent component structure detected, child does not have this component " +
1418 "as the parent, this=" + this.toDebugString() + " child=" + child.toDebugString() +
1419 " child.parent=" + (child.parent == null ? "null" : child.parent.toDebugString()));
1424 // Check whether the list contains exactly the searched-for component (with == operator)
1425 private boolean containsExact(List<RocketComponent> haystack, RocketComponent needle) {
1426 for (RocketComponent c : haystack) {
1435 /////////// Iterators //////////
1438 * Returns an iterator that iterates over all children and sub-children.
1440 * The iterator iterates through all children below this object, including itself if
1441 * <code>returnSelf</code> is true. The order of the iteration is not specified
1442 * (it may be specified in the future).
1444 * If an iterator iterating over only the direct children of the component is required,
1445 * use <code>component.getChildren().iterator()</code>.
1447 * TODO: HIGH: Remove this after merges have been done
1449 * @param returnSelf boolean value specifying whether the component itself should be
1451 * @return An iterator for the children and sub-children.
1452 * @deprecated Use {@link #iterator(boolean)} instead
1455 public final Iterator<RocketComponent> deepIterator(boolean returnSelf) {
1456 return iterator(returnSelf);
1461 * Returns an iterator that iterates over all children and sub-children, including itself.
1463 * This method is equivalent to <code>deepIterator(true)</code>.
1465 * TODO: HIGH: Remove this after merges have been done
1467 * @return An iterator for this component, its children and sub-children.
1468 * @deprecated Use {@link #iterator()} instead
1471 public final Iterator<RocketComponent> deepIterator() {
1478 * Returns an iterator that iterates over all children and sub-children.
1480 * The iterator iterates through all children below this object, including itself if
1481 * <code>returnSelf</code> is true. The order of the iteration is not specified
1482 * (it may be specified in the future).
1484 * If an iterator iterating over only the direct children of the component is required,
1485 * use <code>component.getChildren().iterator()</code>.
1487 * @param returnSelf boolean value specifying whether the component itself should be
1489 * @return An iterator for the children and sub-children.
1491 public final Iterator<RocketComponent> iterator(boolean returnSelf) {
1493 return new RocketComponentIterator(this, returnSelf);
1498 * Returns an iterator that iterates over this components, its children and sub-children.
1500 * This method is equivalent to <code>iterator(true)</code>.
1502 * @return An iterator for this component, its children and sub-children.
1505 public final Iterator<RocketComponent> iterator() {
1506 return iterator(true);
1514 * Compare component equality based on the ID of this component. Only the
1515 * ID and class type is used for a basis of comparison.
1518 public boolean equals(Object obj) {
1523 if (this.getClass() != obj.getClass())
1525 RocketComponent other = (RocketComponent) obj;
1526 return this.id.equals(other.id);
1532 public int hashCode() {
1533 return id.hashCode();
1538 //////////// Helper methods for subclasses
1544 * Helper method to add rotationally symmetric bounds at the specified coordinates.
1545 * The X-axis value is <code>x</code> and the radius at the specified position is
1548 protected static final void addBound(Collection<Coordinate> bounds, double x, double r) {
1549 bounds.add(new Coordinate(x, -r, -r));
1550 bounds.add(new Coordinate(x, r, -r));
1551 bounds.add(new Coordinate(x, r, r));
1552 bounds.add(new Coordinate(x, -r, r));
1556 protected static final Coordinate ringCG(double outerRadius, double innerRadius,
1557 double x1, double x2, double density) {
1558 return new Coordinate((x1 + x2) / 2, 0, 0,
1559 ringMass(outerRadius, innerRadius, x2 - x1, density));
1562 protected static final double ringMass(double outerRadius, double innerRadius,
1563 double length, double density) {
1564 return Math.PI * (MathUtil.pow2(outerRadius) - MathUtil.pow2(innerRadius)) *
1568 protected static final double ringLongitudinalUnitInertia(double outerRadius,
1569 double innerRadius, double length) {
1570 // 1/12 * (3 * (r1^2 + r2^2) + h^2)
1571 return (3 * (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) + MathUtil.pow2(length)) / 12;
1574 protected static final double ringRotationalUnitInertia(double outerRadius,
1575 double innerRadius) {
1576 // 1/2 * (r1^2 + r2^2)
1577 return (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) / 2;
1586 * Loads the RocketComponent fields from the given component. This method is meant
1587 * for in-place replacement of a component. It is used with the undo/redo
1588 * mechanism and when converting a finset into a freeform fin set.
1589 * This component must not have a parent, otherwise this method will fail.
1591 * The child components in the source tree are copied into the current tree, however,
1592 * the original components should not be used since they represent old copies of the
1593 * components. It is recommended to invalidate them by calling {@link #invalidate()}.
1595 * This method returns a list of components that should be invalidated after references
1596 * to them have been removed (for example by firing appropriate events). The list contains
1597 * all children and sub-children of the current component and the entire component
1598 * tree of <code>src</code>.
1600 * @return a list of components that should not be used after this call.
1602 protected List<RocketComponent> copyFrom(RocketComponent src) {
1604 List<RocketComponent> toInvalidate = new ArrayList<RocketComponent>();
1606 if (this.parent != null) {
1607 throw new UnsupportedOperationException("copyFrom called for non-root component, parent=" +
1608 this.parent.toDebugString() + ", this=" + this.toDebugString());
1611 // Add current structure to be invalidated
1612 Iterator<RocketComponent> iterator = this.iterator(false);
1613 while (iterator.hasNext()) {
1614 toInvalidate.add(iterator.next());
1617 // Remove previous components
1618 for (RocketComponent child : this.children) {
1619 child.parent = null;
1621 this.children.clear();
1623 // Copy new children to this component
1624 for (RocketComponent c : src.children) {
1625 RocketComponent copy = c.copyWithOriginalID();
1626 this.children.add(copy);
1630 this.checkComponentStructure();
1631 src.checkComponentStructure();
1633 // Set all parameters
1634 this.length = src.length;
1635 this.relativePosition = src.relativePosition;
1636 this.position = src.position;
1637 this.color = src.color;
1638 this.lineStyle = src.lineStyle;
1639 this.overrideMass = src.overrideMass;
1640 this.massOverriden = src.massOverriden;
1641 this.overrideCGX = src.overrideCGX;
1642 this.cgOverriden = src.cgOverriden;
1643 this.overrideSubcomponents = src.overrideSubcomponents;
1644 this.name = src.name;
1645 this.comment = src.comment;
1648 // Add source components to invalidation tree
1649 for (RocketComponent c : src) {
1650 toInvalidate.add(c);
1653 return toInvalidate;
1656 protected void invalidate() {
1657 invalidator.invalidate();
1661 ////////// Iterator implementation ///////////
1664 * Private inner class to implement the Iterator.
1666 * This iterator is fail-fast if the root of the structure is a Rocket.
1668 private static class RocketComponentIterator implements Iterator<RocketComponent> {
1669 // Stack holds iterators which still have some components left.
1670 private final Deque<Iterator<RocketComponent>> iteratorStack = new ArrayDeque<Iterator<RocketComponent>>();
1672 private final Rocket root;
1673 private final int treeModID;
1675 private final RocketComponent original;
1676 private boolean returnSelf = false;
1678 // Construct iterator with component's child's iterator, if it has elements
1679 public RocketComponentIterator(RocketComponent c, boolean returnSelf) {
1681 RocketComponent gp = c.getRoot();
1682 if (gp instanceof Rocket) {
1684 treeModID = root.getTreeModID();
1690 Iterator<RocketComponent> i = c.children.iterator();
1692 iteratorStack.push(i);
1695 this.returnSelf = returnSelf;
1699 public boolean hasNext() {
1703 return !iteratorStack.isEmpty(); // Elements remain if stack is not empty
1707 public RocketComponent next() {
1708 Iterator<RocketComponent> i;
1712 // Return original component first
1718 // Peek first iterator from stack, throw exception if empty
1719 i = iteratorStack.peek();
1721 throw new NoSuchElementException("No further elements in RocketComponent iterator");
1724 // Retrieve next component of the iterator, remove iterator from stack if empty
1725 RocketComponent c = i.next();
1727 iteratorStack.pop();
1729 // Add iterator of component children to stack if it has children
1730 i = c.children.iterator();
1732 iteratorStack.push(i);
1737 private void checkID() {
1739 if (root.getTreeModID() != treeModID) {
1740 throw new IllegalStateException("Rocket modified while being iterated");
1746 public void remove() {
1747 throw new UnsupportedOperationException("remove() not supported by " +
1748 "RocketComponent iterator");