1 package net.sf.openrocket.rocketcomponent;
3 import java.util.Collection;
4 import java.util.EventListener;
5 import java.util.Iterator;
7 import java.util.NoSuchElementException;
9 import net.sf.openrocket.l10n.Translator;
10 import net.sf.openrocket.logging.LogHelper;
11 import net.sf.openrocket.preset.ComponentPreset;
12 import net.sf.openrocket.startup.Application;
13 import net.sf.openrocket.util.ArrayList;
14 import net.sf.openrocket.util.BugException;
15 import net.sf.openrocket.util.ChangeSource;
16 import net.sf.openrocket.util.Color;
17 import net.sf.openrocket.util.Coordinate;
18 import net.sf.openrocket.util.Invalidator;
19 import net.sf.openrocket.util.LineStyle;
20 import net.sf.openrocket.util.MathUtil;
21 import net.sf.openrocket.util.SafetyMutex;
22 import net.sf.openrocket.util.SimpleStack;
23 import net.sf.openrocket.util.UniqueID;
26 public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent> {
27 private static final LogHelper log = Application.getLogger();
28 private static final Translator trans = Application.getTranslator();
31 * Text is suitable to the form
32 * Position relative to: <title>
34 public enum Position {
35 /** Position relative to the top of the parent component. */
36 //// Top of the parent component
37 TOP(trans.get("RocketComponent.Position.TOP")),
38 /** Position relative to the middle of the parent component. */
39 //// Middle of the parent component
40 MIDDLE(trans.get("RocketComponent.Position.MIDDLE")),
41 /** Position relative to the bottom of the parent component. */
42 //// Bottom of the parent component
43 BOTTOM(trans.get("RocketComponent.Position.BOTTOM")),
44 /** Position after the parent component (for body components). */
45 //// After the parent component
46 AFTER(trans.get("RocketComponent.Position.AFTER")),
47 /** Specify an absolute X-coordinate position. */
48 //// Tip of the nose cone
49 ABSOLUTE(trans.get("RocketComponent.Position.ABSOLUTE"));
53 Position(String title) {
58 public String toString() {
64 * A safety mutex that can be used to prevent concurrent access to this component.
66 protected SafetyMutex mutex = SafetyMutex.newInstance();
68 //////// Parent/child trees
70 * Parent component of the current component, or null if none exists.
72 private RocketComponent parent = null;
75 * List of child components of this component.
77 private ArrayList<RocketComponent> children = new ArrayList<RocketComponent>();
80 //////// Parameters common to all components:
83 * Characteristic length of the component. This is used in calculating the coordinate
84 * transformations and positions of other components in reference to this component.
85 * This may and should be used as the "true" length of the component, where applicable.
86 * By default it is zero, i.e. no translation.
88 protected double length = 0;
91 * Positioning of this component relative to the parent component.
93 protected Position relativePosition;
96 * Offset of the position of this component relative to the normal position given by
97 * relativePosition. By default zero, i.e. no position change.
99 protected double position = 0;
102 // Color of the component, null means to use the default color
103 private Color color = null;
104 private LineStyle lineStyle = null;
108 private double overrideMass = 0;
109 private boolean massOverriden = false;
110 private double overrideCGX = 0;
111 private boolean cgOverriden = false;
113 private boolean overrideSubcomponents = false;
116 // User-given name of the component
117 private String name = null;
119 // User-specified comment
120 private String comment = "";
122 // Unique ID of the component
123 private String id = null;
125 // Preset component this component is based upon
126 private ComponentPreset presetComponent = null;
130 * Used to invalidate the component after calling {@link #copyFrom(RocketComponent)}.
132 private Invalidator invalidator = new Invalidator(this);
135 //// NOTE !!! All fields must be copied in the method copyFrom()! ////
140 * Default constructor. Sets the name of the component to the component's static name
141 * and the relative position of the component.
143 public RocketComponent(Position relativePosition) {
144 // These must not fire any events, due to Rocket undo system initialization
145 this.name = getComponentName();
146 this.relativePosition = relativePosition;
150 //////////// Methods that must be implemented ////////////
154 * Static component name. The name may not vary of the parameters, it must be static.
156 public abstract String getComponentName(); // Static component type name
159 * Return the component mass (regardless of mass overriding).
161 public abstract double getComponentMass(); // Mass of non-overridden component
164 * Return the component CG and mass (regardless of CG or mass overriding).
166 public abstract Coordinate getComponentCG(); // CG of non-overridden component
170 * Return the longitudinal (around the y- or z-axis) unitary moment of inertia.
171 * The unitary moment of inertia is the moment of inertia with the assumption that
172 * the mass of the component is one kilogram. The inertia is measured in
173 * respect to the non-overridden CG.
175 * @return the longitudinal unitary moment of inertia of this component.
177 public abstract double getLongitudinalUnitInertia();
181 * Return the rotational (around the x-axis) unitary moment of inertia.
182 * The unitary moment of inertia is the moment of inertia with the assumption that
183 * the mass of the component is one kilogram. The inertia is measured in
184 * respect to the non-overridden CG.
186 * @return the rotational unitary moment of inertia of this component.
188 public abstract double getRotationalUnitInertia();
192 * Test whether this component allows any children components. This method must
193 * return true if and only if {@link #isCompatible(Class)} returns true for any
194 * rocket component class.
196 * @return <code>true</code> if children can be attached to this component, <code>false</code> otherwise.
198 public abstract boolean allowsChildren();
201 * Test whether the given component type can be added to this component. This type safety
202 * is enforced by the <code>addChild()</code> methods. The return value of this method
203 * may change to reflect the current state of this component (e.g. two components of some
204 * type cannot be placed as children).
206 * @param type The RocketComponent class type to add.
207 * @return Whether such a component can be added.
209 public abstract boolean isCompatible(Class<? extends RocketComponent> type);
212 /* Non-abstract helper method */
214 * Test whether the given component can be added to this component. This is equivalent
215 * to calling <code>isCompatible(c.getClass())</code>.
217 * @param c Component to test.
218 * @return Whether the component can be added.
219 * @see #isCompatible(Class)
221 public final boolean isCompatible(RocketComponent c) {
223 return isCompatible(c.getClass());
229 * Return a collection of bounding coordinates. The coordinates must be such that
230 * the component is fully enclosed in their convex hull.
232 * @return a collection of coordinates that bound the component.
234 public abstract Collection<Coordinate> getComponentBounds();
237 * Return true if the component may have an aerodynamic effect on the rocket.
239 public abstract boolean isAerodynamic();
242 * Return true if the component may have an effect on the rocket's mass.
244 public abstract boolean isMassive();
250 //////////// Methods that may be overridden ////////////
254 * Shift the coordinates in the array corresponding to radial movement. A component
255 * that has a radial position must shift the coordinates in this array suitably.
256 * If the component is clustered, then a new array must be returned with a
257 * coordinate for each cluster.
259 * The default implementation simply returns the array, and thus produces no shift.
261 * @param c an array of coordinates to shift.
262 * @return an array of shifted coordinates. The method may modify the contents
263 * of the passed array and return the array itself.
265 public Coordinate[] shiftCoordinates(Coordinate[] c) {
272 * Called when any component in the tree fires a ComponentChangeEvent. This is by
273 * default a no-op, but subclasses may override this method to e.g. invalidate
274 * cached data. The overriding method *must* call
275 * <code>super.componentChanged(e)</code> at some point.
277 * @param e The event fired
279 protected void componentChanged(ComponentChangeEvent e) {
288 * Return the user-provided name of the component, or the component base
289 * name if the user-provided name is empty. This can be used in the UI.
291 * @return A string describing the component.
294 public final String toString() {
296 if (name.length() == 0)
297 return getComponentName();
304 * Create a string describing the basic component structure from this component downwards.
305 * @return a string containing the rocket structure
307 public final String toDebugString() {
308 mutex.lock("toDebugString");
310 StringBuilder sb = new StringBuilder();
312 return sb.toString();
314 mutex.unlock("toDebugString");
318 private void toDebugString(StringBuilder sb) {
319 sb.append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this));
320 sb.append("[\"").append(this.getName()).append('"');
321 for (RocketComponent c : this.children) {
330 * Make a deep copy of the rocket component tree structure from this component
331 * downwards for copying purposes. Each component in the copy will be assigned
332 * a new component ID, making it a safe copy. This method does not fire any events.
334 * @return A deep copy of the structure.
336 public final RocketComponent copy() {
337 RocketComponent clone = copyWithOriginalID();
339 Iterator<RocketComponent> iterator = clone.iterator(true);
340 while (iterator.hasNext()) {
341 iterator.next().newID();
349 * Make a deep copy of the rocket component tree structure from this component
350 * downwards while maintaining the component ID's. The purpose of this method is
351 * to allow copies to be created with the original ID's for the purpose of the
352 * undo/redo mechanism. This method should not be used for other purposes,
353 * such as copy/paste. This method does not fire any events.
355 * This method must be overridden by any component that refers to mutable objects,
356 * or if some fields should not be copied. This should be performed by
357 * <code>RocketComponent c = super.copyWithOriginalID();</code> and then cloning/modifying
358 * the appropriate fields.
360 * This is not performed as serializing/deserializing for performance reasons.
362 * @return A deep copy of the structure.
364 protected RocketComponent copyWithOriginalID() {
365 mutex.lock("copyWithOriginalID");
368 RocketComponent clone;
370 clone = (RocketComponent) this.clone();
371 } catch (CloneNotSupportedException e) {
372 throw new BugException("CloneNotSupportedException encountered, report a bug!", e);
376 clone.mutex = SafetyMutex.newInstance();
378 // Reset all parent/child information
380 clone.children = new ArrayList<RocketComponent>();
382 // Add copied children to the structure without firing events.
383 for (RocketComponent child : this.children) {
384 RocketComponent childCopy = child.copyWithOriginalID();
385 // Don't use add method since it fires events
386 clone.children.add(childCopy);
387 childCopy.parent = clone;
390 this.checkComponentStructure();
391 clone.checkComponentStructure();
395 mutex.unlock("copyWithOriginalID");
400 ////////////// Methods that may not be overridden ////////////
404 ////////// Common parameter setting/getting //////////
407 * Return the color of the object to use in 2D figures, or <code>null</code>
408 * to use the default color.
410 public final Color getColor() {
416 * Set the color of the object to use in 2D figures.
418 public final void setColor(Color c) {
419 if ((color == null && c == null) ||
420 (color != null && color.equals(c)))
425 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
429 public final LineStyle getLineStyle() {
434 public final void setLineStyle(LineStyle style) {
435 if (this.lineStyle == style)
438 this.lineStyle = style;
439 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
446 * Get the current override mass. The mass is not necessarily in use
449 * @return the override mass
451 public final double getOverrideMass() {
457 * Set the current override mass. The mass is not set to use by this
460 * @param m the override mass
462 public final void setOverrideMass(double m) {
463 if (MathUtil.equals(m, overrideMass))
466 overrideMass = Math.max(m, 0);
468 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
472 * Return whether mass override is active for this component. This does NOT
473 * take into account whether a parent component is overriding the mass.
475 * @return whether the mass is overridden
477 public final boolean isMassOverridden() {
479 return massOverriden;
483 * Set whether the mass is currently overridden.
485 * @param o whether the mass is overridden
487 public final void setMassOverridden(boolean o) {
488 if (massOverriden == o) {
493 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
501 * Return the current override CG. The CG is not necessarily overridden.
503 * @return the override CG
505 public final Coordinate getOverrideCG() {
507 return getComponentCG().setX(overrideCGX);
511 * Return the x-coordinate of the current override CG.
513 * @return the x-coordinate of the override CG.
515 public final double getOverrideCGX() {
521 * Set the current override CG to (x,0,0).
523 * @param x the x-coordinate of the override CG to set.
525 public final void setOverrideCGX(double x) {
526 if (MathUtil.equals(overrideCGX, x))
529 this.overrideCGX = x;
530 if (isCGOverridden())
531 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
533 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
537 * Return whether the CG is currently overridden.
539 * @return whether the CG is overridden
541 public final boolean isCGOverridden() {
547 * Set whether the CG is currently overridden.
549 * @param o whether the CG is overridden
551 public final void setCGOverridden(boolean o) {
552 if (cgOverriden == o) {
557 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
563 * Return whether the mass and/or CG override overrides all subcomponent values
564 * as well. The default implementation is a normal getter/setter implementation,
565 * however, subclasses are allowed to override this behavior if some subclass
566 * always or never overrides subcomponents. In this case the subclass should
567 * also override {@link #isOverrideSubcomponentsEnabled()} to return
568 * <code>false</code>.
570 * @return whether the current mass and/or CG override overrides subcomponents as well.
572 public boolean getOverrideSubcomponents() {
574 return overrideSubcomponents;
579 * Set whether the mass and/or CG override overrides all subcomponent values
580 * as well. See {@link #getOverrideSubcomponents()} for details.
582 * @param override whether the mass and/or CG override overrides all subcomponent.
584 public void setOverrideSubcomponents(boolean override) {
585 if (overrideSubcomponents == override) {
589 overrideSubcomponents = override;
590 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
594 * Return whether the option to override all subcomponents is enabled or not.
595 * The default implementation returns <code>false</code> if neither mass nor
596 * CG is overridden, <code>true</code> otherwise.
598 * This method may be overridden if the setting of overriding subcomponents
601 * @return whether the option to override subcomponents is currently enabled.
603 public boolean isOverrideSubcomponentsEnabled() {
605 return isCGOverridden() || isMassOverridden();
612 * Get the user-defined name of the component.
614 public final String getName() {
620 * Set the user-defined name of the component. If name==null, sets the name to
621 * the default name, currently the component name.
623 public final void setName(String name) {
624 if (this.name.equals(name)) {
628 if (name == null || name.matches("^\\s*$"))
629 this.name = getComponentName();
632 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
637 * Return the comment of the component. The component may contain multiple lines
638 * using \n as a newline separator.
640 * @return the comment of the component.
642 public final String getComment() {
648 * Set the comment of the component.
650 * @param comment the comment of the component.
652 public final void setComment(String comment) {
653 if (this.comment.equals(comment))
659 this.comment = comment;
660 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
666 * Return the preset component that this component is based upon.
668 * @return the preset component, or <code>null</code> if this is not based on a preset.
670 public final ComponentPreset getPresetComponent() {
671 return presetComponent;
675 * Return the most compatible preset type for this component.
676 * This method should be overridden by components which have presets
678 * @return the most compatible ComponentPreset.Type or <code>null</code> if no presets are compatible.
680 public ComponentPreset.Type getPresetType() {
685 * Set the preset component this component is based upon and load all of the
688 * @param preset the preset component to load, or <code>null</code> to clear the preset.
690 public final void loadPreset(ComponentPreset preset) {
691 if (presetComponent == preset) {
695 if (preset == null) {
700 // TODO - do we need to this compatibility check?
702 if (preset.getComponentClass() != this.getClass()) {
703 throw new IllegalArgumentException("Attempting to load preset of type " + preset.getComponentClass()
704 + " into component of type " + this.getClass());
708 RocketComponent root = getRoot();
710 if (root instanceof Rocket) {
711 rocket = (Rocket) root;
717 if (rocket != null) {
721 loadFromPreset(preset);
723 this.presetComponent = preset;
724 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
727 if (rocket != null) {
735 * Load component properties from the specified preset. The preset is guaranteed
736 * to be of the correct type.
738 * This method should fire the appropriate events related to the changes. The rocket
739 * is frozen by the caller, so the events will be automatically combined.
741 * This method must FIRST perform the preset loading and THEN call super.loadFromPreset().
742 * This is because mass setting requires the dimensions to be set beforehand.
744 * @param preset the preset to load from
746 protected void loadFromPreset(ComponentPreset preset) {
747 if ( preset.has(ComponentPreset.LENGTH) ) {
748 this.length = preset.get(ComponentPreset.LENGTH);
754 * Clear the current component preset. This does not affect the component properties
757 public final void clearPreset() {
758 if (presetComponent == null)
760 presetComponent = null;
761 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
767 * Returns the unique ID of the component.
769 * @return the ID of the component.
771 public final String getID() {
776 * Generate a new ID for this component.
778 private final void newID() {
780 this.id = UniqueID.uuid();
787 * Get the characteristic length of the component, for example the length of a body tube
788 * of the length of the root chord of a fin. This is used in positioning the component
789 * relative to its parent.
791 * If the length of a component is settable, the class must define the setter method
794 public final double getLength() {
800 * Get the positioning of the component relative to its parent component.
801 * This is one of the enums of {@link Position}. A setter method is not provided,
802 * but can be provided by a subclass.
804 public final Position getRelativePosition() {
806 return relativePosition;
811 * Set the positioning of the component relative to its parent component.
812 * The actual position of the component is maintained to the best ability.
814 * The default implementation is of protected visibility, since many components
815 * do not support setting the relative position. A component that does support
816 * it should override this with a public method that simply calls this
817 * supermethod AND fire a suitable ComponentChangeEvent.
819 * @param position the relative positioning.
821 protected void setRelativePosition(RocketComponent.Position position) {
822 if (this.relativePosition == position)
826 // Update position so as not to move the component
827 if (this.parent != null) {
828 double thisPos = this.toRelative(Coordinate.NUL, this.parent)[0].x;
832 this.position = this.toAbsolute(Coordinate.NUL)[0].x;
836 this.position = thisPos;
840 this.position = thisPos - (this.parent.length - this.length) / 2;
844 this.position = thisPos - (this.parent.length - this.length);
848 throw new BugException("Unknown position type: " + position);
852 this.relativePosition = position;
853 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
858 * Determine position relative to given position argument. Note: This is a side-effect free method. No state
861 * @param thePosition the relative position to be used as the basis for the computation
862 * @param relativeTo the position is computed relative the the given component
864 * @return double position of the component relative to the parent, with respect to <code>position</code>
866 public double asPositionValue(Position thePosition, RocketComponent relativeTo) {
867 double result = this.position;
868 if (relativeTo != null) {
869 double thisPos = this.toRelative(Coordinate.NUL, relativeTo)[0].x;
871 switch (thePosition) {
873 result = this.toAbsolute(Coordinate.NUL)[0].x;
879 result = thisPos - (relativeTo.length - this.length) / 2;
882 result = thisPos - (relativeTo.length - this.length);
885 throw new BugException("Unknown position type: " + thePosition);
892 * Get the position value of the component. The exact meaning of the value is
893 * dependent on the current relative positioning.
895 * @return the positional value.
897 public final double getPositionValue() {
904 * Set the position value of the component. The exact meaning of the value
905 * depends on the current relative positioning.
907 * The default implementation is of protected visibility, since many components
908 * do not support setting the relative position. A component that does support
909 * it should override this with a public method that simply calls this
910 * supermethod AND fire a suitable ComponentChangeEvent.
912 * @param value the position value of the component.
914 public void setPositionValue(double value) {
915 if (MathUtil.equals(this.position, value))
918 this.position = value;
923 /////////// Coordinate changes ///////////
926 * Returns coordinate c in absolute coordinates. Equivalent to toComponent(c,null).
928 public Coordinate[] toAbsolute(Coordinate c) {
930 return toRelative(c, null);
935 * Return coordinate <code>c</code> described in the coordinate system of
936 * <code>dest</code>. If <code>dest</code> is <code>null</code> returns
937 * absolute coordinates.
939 * This method returns an array of coordinates, each of which represents a
940 * position of the coordinate in clustered cases. The array is guaranteed
941 * to contain at least one element.
943 * The current implementation does not support rotating components.
945 * @param c Coordinate in the component's coordinate system.
946 * @param dest Destination component coordinate system.
947 * @return an array of coordinates describing <code>c</code> in coordinates
948 * relative to <code>dest</code>.
950 public final Coordinate[] toRelative(Coordinate c, RocketComponent dest) {
952 mutex.lock("toRelative");
954 double absoluteX = Double.NaN;
955 RocketComponent search = dest;
956 Coordinate[] array = new Coordinate[1];
959 RocketComponent component = this;
960 while ((component != search) && (component.parent != null)) {
962 array = component.shiftCoordinates(array);
964 switch (component.relativePosition) {
966 for (int i = 0; i < array.length; i++) {
967 array[i] = array[i].add(component.position, 0, 0);
972 for (int i = 0; i < array.length; i++) {
973 array[i] = array[i].add(component.position +
974 (component.parent.length - component.length) / 2, 0, 0);
979 for (int i = 0; i < array.length; i++) {
980 array[i] = array[i].add(component.position +
981 (component.parent.length - component.length), 0, 0);
986 // Add length of all previous brother-components with POSITION_RELATIVE_AFTER
987 int index = component.parent.children.indexOf(component);
989 for (index--; index >= 0; index--) {
990 RocketComponent comp = component.parent.children.get(index);
991 double componentLength = comp.getTotalLength();
992 for (int i = 0; i < array.length; i++) {
993 array[i] = array[i].add(componentLength, 0, 0);
996 for (int i = 0; i < array.length; i++) {
997 array[i] = array[i].add(component.position + component.parent.length, 0, 0);
1002 search = null; // Requires back-search if dest!=null
1003 if (Double.isNaN(absoluteX)) {
1004 absoluteX = component.position;
1009 throw new BugException("Unknown relative positioning type of component" +
1010 component + ": " + component.relativePosition);
1013 component = component.parent; // parent != null
1016 if (!Double.isNaN(absoluteX)) {
1017 for (int i = 0; i < array.length; i++) {
1018 array[i] = array[i].setX(absoluteX + c.x);
1022 // Check whether destination has been found or whether to backtrack
1023 // TODO: LOW: Backtracking into clustered components uses only one component
1024 if ((dest != null) && (component != dest)) {
1025 Coordinate[] origin = dest.toAbsolute(Coordinate.NUL);
1026 for (int i = 0; i < array.length; i++) {
1027 array[i] = array[i].sub(origin[0]);
1033 mutex.unlock("toRelative");
1039 * Recursively sum the lengths of all subcomponents that have position
1042 * @return Sum of the lengths.
1044 private final double getTotalLength() {
1046 this.checkComponentStructure();
1047 mutex.lock("getTotalLength");
1050 if (relativePosition == Position.AFTER)
1052 for (int i = 0; i < children.size(); i++)
1053 l += children.get(i).getTotalLength();
1056 mutex.unlock("getTotalLength");
1062 /////////// Total mass and CG calculation ////////////
1065 * Return the (possibly overridden) mass of component.
1067 * @return The mass of the component or the given override mass.
1069 public final double getMass() {
1072 return overrideMass;
1073 return getComponentMass();
1077 * Return the (possibly overridden) center of gravity and mass.
1079 * Returns the CG with the weight of the coordinate set to the weight of the component.
1080 * Both CG and mass may be separately overridden.
1082 * @return The CG of the component or the given override CG.
1084 public final Coordinate getCG() {
1087 return getOverrideCG().setWeight(getMass());
1090 return getComponentCG().setWeight(getMass());
1092 return getComponentCG();
1097 * Return the longitudinal (around the y- or z-axis) moment of inertia of this component.
1098 * The moment of inertia is scaled in reference to the (possibly overridden) mass
1099 * and is relative to the non-overridden CG.
1101 * @return the longitudinal moment of inertia of this component.
1103 public final double getLongitudinalInertia() {
1105 return getLongitudinalUnitInertia() * getMass();
1109 * Return the rotational (around the y- or z-axis) moment of inertia of this component.
1110 * The moment of inertia is scaled in reference to the (possibly overridden) mass
1111 * and is relative to the non-overridden CG.
1113 * @return the rotational moment of inertia of this component.
1115 public final double getRotationalInertia() {
1117 return getRotationalUnitInertia() * getMass();
1122 /////////// Children handling ///////////
1126 * Adds a child to the rocket component tree. The component is added to the end
1127 * of the component's child list. This is a helper method that calls
1128 * {@link #addChild(RocketComponent,int)}.
1130 * @param component The component to add.
1131 * @throws IllegalArgumentException if the component is already part of some
1133 * @see #addChild(RocketComponent,int)
1135 public final void addChild(RocketComponent component) {
1137 addChild(component, children.size());
1142 * Adds a child to the rocket component tree. The component is added to
1143 * the given position of the component's child list.
1145 * This method may be overridden to enforce more strict component addition rules.
1146 * The tests should be performed first and then this method called.
1148 * @param component The component to add.
1149 * @param index Position to add component to.
1150 * @throws IllegalArgumentException If the component is already part of
1151 * some component tree.
1153 public void addChild(RocketComponent component, int index) {
1156 if (component.parent != null) {
1157 throw new IllegalArgumentException("component " + component.getComponentName() +
1158 " is already in a tree");
1161 // Ensure that the no loops are created in component tree [A -> X -> Y -> B, B.addChild(A)]
1162 if (this.getRoot().equals(component)) {
1163 throw new IllegalStateException("Component " + component.getComponentName() +
1164 " is a parent of " + this.getComponentName() + ", attempting to create cycle in tree.");
1167 if (!isCompatible(component)) {
1168 throw new IllegalStateException("Component " + component.getComponentName() +
1169 " not currently compatible with component " + getComponentName());
1172 children.add(index, component);
1173 component.parent = this;
1175 this.checkComponentStructure();
1176 component.checkComponentStructure();
1178 fireAddRemoveEvent(component);
1182 * Removes a child from the rocket component tree.
1184 * @param n remove the n'th child.
1185 * @throws IndexOutOfBoundsException if n is out of bounds
1187 public final void removeChild(int n) {
1189 RocketComponent component = children.remove(n);
1190 component.parent = null;
1192 this.checkComponentStructure();
1193 component.checkComponentStructure();
1195 fireAddRemoveEvent(component);
1199 * Removes a child from the rocket component tree. Does nothing if the component
1200 * is not present as a child.
1202 * @param component the component to remove
1203 * @return whether the component was a child
1205 public final boolean removeChild(RocketComponent component) {
1208 component.checkComponentStructure();
1210 if (children.remove(component)) {
1211 component.parent = null;
1213 this.checkComponentStructure();
1214 component.checkComponentStructure();
1216 fireAddRemoveEvent(component);
1226 * Move a child to another position.
1228 * @param component the component to move
1229 * @param index the component's new position
1230 * @throws IllegalArgumentException If an illegal placement was attempted.
1232 public final void moveChild(RocketComponent component, int index) {
1234 if (children.remove(component)) {
1235 children.add(index, component);
1237 this.checkComponentStructure();
1238 component.checkComponentStructure();
1240 fireAddRemoveEvent(component);
1246 * Fires an AERODYNAMIC_CHANGE, MASS_CHANGE or OTHER_CHANGE event depending on the
1247 * type of component removed.
1249 private void fireAddRemoveEvent(RocketComponent component) {
1250 Iterator<RocketComponent> iter = component.iterator(true);
1251 int type = ComponentChangeEvent.TREE_CHANGE;
1252 while (iter.hasNext()) {
1253 RocketComponent c = iter.next();
1254 if (c.isAerodynamic())
1255 type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
1257 type |= ComponentChangeEvent.MASS_CHANGE;
1260 fireComponentChangeEvent(type);
1264 public final int getChildCount() {
1266 this.checkComponentStructure();
1267 return children.size();
1270 public final RocketComponent getChild(int n) {
1272 this.checkComponentStructure();
1273 return children.get(n);
1276 public final List<RocketComponent> getChildren() {
1278 this.checkComponentStructure();
1279 return children.clone();
1284 * Returns the position of the child in this components child list, or -1 if the
1285 * component is not a child of this component.
1287 * @param child The child to search for.
1288 * @return Position in the list or -1 if not found.
1290 public final int getChildPosition(RocketComponent child) {
1292 this.checkComponentStructure();
1293 return children.indexOf(child);
1297 * Get the parent component of this component. Returns <code>null</code> if the component
1300 * @return The parent of this component or <code>null</code>.
1302 public final RocketComponent getParent() {
1308 * Get the root component of the component tree.
1310 * @return The root component of the component tree.
1312 public final RocketComponent getRoot() {
1314 RocketComponent gp = this;
1315 while (gp.parent != null)
1321 * Returns the root Rocket component of this component tree. Throws an
1322 * IllegalStateException if the root component is not a Rocket.
1324 * @return The root Rocket component of the component tree.
1325 * @throws IllegalStateException If the root component is not a Rocket.
1327 public final Rocket getRocket() {
1329 RocketComponent r = getRoot();
1330 if (r instanceof Rocket)
1332 throw new IllegalStateException("getRocket() called with root component "
1333 + r.getComponentName());
1338 * Return the Stage component that this component belongs to. Throws an
1339 * IllegalStateException if a Stage is not in the parentage of this component.
1341 * @return The Stage component this component belongs to.
1342 * @throws IllegalStateException if a Stage component is not in the parentage.
1344 public final Stage getStage() {
1346 RocketComponent c = this;
1348 if (c instanceof Stage)
1352 throw new IllegalStateException("getStage() called without Stage as a parent.");
1356 * Return the stage number of the stage this component belongs to. The stages
1357 * are numbered from zero upwards.
1359 * @return the stage number this component belongs to.
1361 public final int getStageNumber() {
1363 if (parent == null) {
1364 throw new IllegalArgumentException("getStageNumber() called for root component");
1367 RocketComponent stage = this;
1368 while (!(stage instanceof Stage)) {
1369 stage = stage.parent;
1370 if (stage == null || stage.parent == null) {
1371 throw new IllegalStateException("getStageNumber() could not find parent " +
1375 return stage.parent.getChildPosition(stage);
1380 * Find a component with the given ID. The component tree is searched from this component
1381 * down (including this component) for the ID and the corresponding component is returned,
1382 * or null if not found.
1384 * @param idToFind ID to search for.
1385 * @return The component with the ID, or null if not found.
1387 public final RocketComponent findComponent(String idToFind) {
1389 Iterator<RocketComponent> iter = this.iterator(true);
1390 while (iter.hasNext()) {
1391 RocketComponent c = iter.next();
1392 if (c.getID().equals(idToFind))
1399 // TODO: Move these methods elsewhere (used only in SymmetricComponent)
1400 public final RocketComponent getPreviousComponent() {
1402 this.checkComponentStructure();
1405 int pos = parent.getChildPosition(this);
1407 StringBuffer sb = new StringBuffer();
1408 sb.append("Inconsistent internal state: ");
1409 sb.append("this=").append(this).append('[')
1410 .append(System.identityHashCode(this)).append(']');
1411 sb.append(" parent.children=[");
1412 for (int i = 0; i < parent.children.size(); i++) {
1413 RocketComponent c = parent.children.get(i);
1414 sb.append(c).append('[').append(System.identityHashCode(c)).append(']');
1415 if (i < parent.children.size() - 1)
1419 throw new IllegalStateException(sb.toString());
1424 RocketComponent c = parent.getChild(pos - 1);
1425 while (c.getChildCount() > 0)
1426 c = c.getChild(c.getChildCount() - 1);
1430 // TODO: Move these methods elsewhere (used only in SymmetricComponent)
1431 public final RocketComponent getNextComponent() {
1433 if (getChildCount() > 0)
1436 RocketComponent current = this;
1437 RocketComponent nextParent = this.parent;
1439 while (nextParent != null) {
1440 int pos = nextParent.getChildPosition(current);
1441 if (pos < nextParent.getChildCount() - 1)
1442 return nextParent.getChild(pos + 1);
1444 current = nextParent;
1445 nextParent = current.parent;
1451 /////////// Event handling //////////
1453 // Listener lists are provided by the root Rocket component,
1454 // a single listener list for the whole rocket.
1458 * Adds a ComponentChangeListener to the rocket tree. The listener is added to the root
1459 * component, which must be of type Rocket (which overrides this method). Events of all
1460 * subcomponents are sent to all listeners.
1462 * @throws IllegalStateException - if the root component is not a Rocket
1464 public void addComponentChangeListener(ComponentChangeListener l) {
1466 getRocket().addComponentChangeListener(l);
1470 * Removes a ComponentChangeListener from the rocket tree. The listener is removed from
1471 * the root component, which must be of type Rocket (which overrides this method).
1472 * Does nothing if the root component is not a Rocket. (The asymmetry is so
1473 * that listeners can always be removed just in case.)
1475 * @param l Listener to remove
1477 public void removeComponentChangeListener(ComponentChangeListener l) {
1478 if (parent != null) {
1479 getRoot().removeComponentChangeListener(l);
1485 * Adds a <code>ChangeListener</code> to the rocket tree. This is identical to
1486 * <code>addComponentChangeListener()</code> except that it uses a
1487 * <code>ChangeListener</code>. The same events are dispatched to the
1488 * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass
1489 * of <code>ChangeEvent</code>.
1491 * @throws IllegalStateException - if the root component is not a <code>Rocket</code>
1494 public void addChangeListener(EventListener l) {
1496 getRocket().addChangeListener(l);
1500 * Removes a ChangeListener from the rocket tree. This is identical to
1501 * removeComponentChangeListener() except it uses a ChangeListener.
1502 * Does nothing if the root component is not a Rocket. (The asymmetry is so
1503 * that listeners can always be removed just in case.)
1505 * @param l Listener to remove
1508 public void removeChangeListener(EventListener l) {
1509 if (this.parent != null) {
1510 getRoot().removeChangeListener(l);
1516 * Fires a ComponentChangeEvent on the rocket structure. The call is passed to the
1517 * root component, which must be of type Rocket (which overrides this method).
1518 * Events of all subcomponents are sent to all listeners.
1520 * If the component tree root is not a Rocket, the event is ignored. This is the
1521 * case when constructing components not in any Rocket tree. In this case it
1522 * would be impossible for the component to have listeners in any case.
1524 * @param e Event to send
1526 protected void fireComponentChangeEvent(ComponentChangeEvent e) {
1528 if (parent == null) {
1529 /* Ignore if root invalid. */
1530 log.debug("Attempted firing event " + e + " with root " + this.getComponentName() + ", ignoring event");
1533 getRoot().fireComponentChangeEvent(e);
1538 * Fires a ComponentChangeEvent of the given type. The source of the event is set to
1541 * @param type Type of event
1542 * @see #fireComponentChangeEvent(ComponentChangeEvent)
1544 protected void fireComponentChangeEvent(int type) {
1545 fireComponentChangeEvent(new ComponentChangeEvent(this, type));
1550 * Checks whether this component has been invalidated and should no longer be used.
1551 * This is a safety check that in-place replaced components are no longer used.
1552 * All non-trivial methods (with the exception of methods simply getting a property)
1553 * should call this method before changing or computing anything.
1555 * @throws BugException if this component has been invalidated by {@link #copyFrom(RocketComponent)}.
1557 protected void checkState() {
1558 invalidator.check(true);
1564 * Check that the local component structure is correct. This can be called after changing
1565 * the component structure in order to verify the integrity.
1567 * TODO: Remove this after the "inconsistent internal state" bug has been corrected
1569 public void checkComponentStructure() {
1570 if (this.parent != null) {
1571 // Test that this component is found in parent's children with == operator
1572 if (!containsExact(this.parent.children, this)) {
1573 throw new BugException("Inconsistent component structure detected, parent does not contain this " +
1574 "component as a child, parent=" + parent.toDebugString() + " this=" + this.toDebugString());
1577 for (RocketComponent child : this.children) {
1578 if (child.parent != this) {
1579 throw new BugException("Inconsistent component structure detected, child does not have this component " +
1580 "as the parent, this=" + this.toDebugString() + " child=" + child.toDebugString() +
1581 " child.parent=" + (child.parent == null ? "null" : child.parent.toDebugString()));
1586 // Check whether the list contains exactly the searched-for component (with == operator)
1587 private boolean containsExact(List<RocketComponent> haystack, RocketComponent needle) {
1588 for (RocketComponent c : haystack) {
1597 /////////// Iterators //////////
1600 * Returns an iterator that iterates over all children and sub-children.
1602 * The iterator iterates through all children below this object, including itself if
1603 * <code>returnSelf</code> is true. The order of the iteration is not specified
1604 * (it may be specified in the future).
1606 * If an iterator iterating over only the direct children of the component is required,
1607 * use <code>component.getChildren().iterator()</code>.
1609 * TODO: HIGH: Remove this after merges have been done
1611 * @param returnSelf boolean value specifying whether the component itself should be
1613 * @return An iterator for the children and sub-children.
1614 * @deprecated Use {@link #iterator(boolean)} instead
1617 public final Iterator<RocketComponent> deepIterator(boolean returnSelf) {
1618 return iterator(returnSelf);
1623 * Returns an iterator that iterates over all children and sub-children, including itself.
1625 * This method is equivalent to <code>deepIterator(true)</code>.
1627 * TODO: HIGH: Remove this after merges have been done
1629 * @return An iterator for this component, its children and sub-children.
1630 * @deprecated Use {@link #iterator()} instead
1633 public final Iterator<RocketComponent> deepIterator() {
1640 * Returns an iterator that iterates over all children and sub-children.
1642 * The iterator iterates through all children below this object, including itself if
1643 * <code>returnSelf</code> is true. The order of the iteration is not specified
1644 * (it may be specified in the future).
1646 * If an iterator iterating over only the direct children of the component is required,
1647 * use <code>component.getChildren().iterator()</code>.
1649 * @param returnSelf boolean value specifying whether the component itself should be
1651 * @return An iterator for the children and sub-children.
1653 public final Iterator<RocketComponent> iterator(boolean returnSelf) {
1655 return new RocketComponentIterator(this, returnSelf);
1660 * Returns an iterator that iterates over this component, its children and sub-children.
1662 * This method is equivalent to <code>iterator(true)</code>.
1664 * @return An iterator for this component, its children and sub-children.
1667 public final Iterator<RocketComponent> iterator() {
1668 return iterator(true);
1672 * Retrieve the List of MotorMounts in the Rocket.
1674 * Each element returned will a RocketComponent which implements MotorMount. Further isMotorMount()
1677 * @return List<MotorMount>
1679 public final List<MotorMount> getMotorMounts() {
1680 Iterator<RocketComponent> it = iterator();
1681 List<MotorMount> mmts = new ArrayList<MotorMount>();
1683 while (it.hasNext()) {
1684 RocketComponent c = it.next();
1685 if (c instanceof MotorMount) {
1686 if ( ((MotorMount)c).isMotorMount() ) {
1687 mmts.add((MotorMount) c);
1695 * Compare component equality based on the ID of this component. Only the
1696 * ID and class type is used for a basis of comparison.
1699 public boolean equals(Object obj) {
1704 if (this.getClass() != obj.getClass())
1706 RocketComponent other = (RocketComponent) obj;
1707 return this.id.equals(other.id);
1713 public int hashCode() {
1714 return id.hashCode();
1719 //////////// Helper methods for subclasses
1725 * Helper method to add rotationally symmetric bounds at the specified coordinates.
1726 * The X-axis value is <code>x</code> and the radius at the specified position is
1729 protected static final void addBound(Collection<Coordinate> bounds, double x, double r) {
1730 bounds.add(new Coordinate(x, -r, -r));
1731 bounds.add(new Coordinate(x, r, -r));
1732 bounds.add(new Coordinate(x, r, r));
1733 bounds.add(new Coordinate(x, -r, r));
1737 protected static final Coordinate ringCG(double outerRadius, double innerRadius,
1738 double x1, double x2, double density) {
1739 return new Coordinate((x1 + x2) / 2, 0, 0,
1740 ringMass(outerRadius, innerRadius, x2 - x1, density));
1743 protected static final double ringVolume( double outerRadius, double innerRadius, double length ) {
1744 return ringMass( outerRadius, innerRadius, length, 1.0 );
1747 protected static final double ringMass(double outerRadius, double innerRadius,
1748 double length, double density) {
1749 return Math.PI * (MathUtil.pow2(outerRadius) - MathUtil.pow2(innerRadius)) *
1753 protected static final double ringLongitudinalUnitInertia(double outerRadius,
1754 double innerRadius, double length) {
1755 // 1/12 * (3 * (r1^2 + r2^2) + h^2)
1756 return (3 * (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) + MathUtil.pow2(length)) / 12;
1759 protected static final double ringRotationalUnitInertia(double outerRadius,
1760 double innerRadius) {
1761 // 1/2 * (r1^2 + r2^2)
1762 return (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) / 2;
1771 * Loads the RocketComponent fields from the given component. This method is meant
1772 * for in-place replacement of a component. It is used with the undo/redo
1773 * mechanism and when converting a finset into a freeform fin set.
1774 * This component must not have a parent, otherwise this method will fail.
1776 * The child components in the source tree are copied into the current tree, however,
1777 * the original components should not be used since they represent old copies of the
1778 * components. It is recommended to invalidate them by calling {@link #invalidate()}.
1780 * This method returns a list of components that should be invalidated after references
1781 * to them have been removed (for example by firing appropriate events). The list contains
1782 * all children and sub-children of the current component and the entire component
1783 * tree of <code>src</code>.
1785 * @return a list of components that should not be used after this call.
1787 protected List<RocketComponent> copyFrom(RocketComponent src) {
1789 List<RocketComponent> toInvalidate = new ArrayList<RocketComponent>();
1791 if (this.parent != null) {
1792 throw new UnsupportedOperationException("copyFrom called for non-root component, parent=" +
1793 this.parent.toDebugString() + ", this=" + this.toDebugString());
1796 // Add current structure to be invalidated
1797 Iterator<RocketComponent> iterator = this.iterator(false);
1798 while (iterator.hasNext()) {
1799 toInvalidate.add(iterator.next());
1802 // Remove previous components
1803 for (RocketComponent child : this.children) {
1804 child.parent = null;
1806 this.children.clear();
1808 // Copy new children to this component
1809 for (RocketComponent c : src.children) {
1810 RocketComponent copy = c.copyWithOriginalID();
1811 this.children.add(copy);
1815 this.checkComponentStructure();
1816 src.checkComponentStructure();
1818 // Set all parameters
1819 this.length = src.length;
1820 this.relativePosition = src.relativePosition;
1821 this.position = src.position;
1822 this.color = src.color;
1823 this.lineStyle = src.lineStyle;
1824 this.overrideMass = src.overrideMass;
1825 this.massOverriden = src.massOverriden;
1826 this.overrideCGX = src.overrideCGX;
1827 this.cgOverriden = src.cgOverriden;
1828 this.overrideSubcomponents = src.overrideSubcomponents;
1829 this.name = src.name;
1830 this.comment = src.comment;
1833 // Add source components to invalidation tree
1834 for (RocketComponent c : src) {
1835 toInvalidate.add(c);
1838 return toInvalidate;
1841 protected void invalidate() {
1842 invalidator.invalidate();
1846 ////////// Iterator implementation ///////////
1849 * Private inner class to implement the Iterator.
1851 * This iterator is fail-fast if the root of the structure is a Rocket.
1853 private static class RocketComponentIterator implements Iterator<RocketComponent> {
1854 // Stack holds iterators which still have some components left.
1855 private final SimpleStack<Iterator<RocketComponent>> iteratorStack = new SimpleStack<Iterator<RocketComponent>>();
1857 private final Rocket root;
1858 private final int treeModID;
1860 private final RocketComponent original;
1861 private boolean returnSelf = false;
1863 // Construct iterator with component's child's iterator, if it has elements
1864 public RocketComponentIterator(RocketComponent c, boolean returnSelf) {
1866 RocketComponent gp = c.getRoot();
1867 if (gp instanceof Rocket) {
1869 treeModID = root.getTreeModID();
1875 Iterator<RocketComponent> i = c.children.iterator();
1877 iteratorStack.push(i);
1880 this.returnSelf = returnSelf;
1884 public boolean hasNext() {
1888 return !iteratorStack.isEmpty(); // Elements remain if stack is not empty
1892 public RocketComponent next() {
1893 Iterator<RocketComponent> i;
1897 // Return original component first
1903 // Peek first iterator from stack, throw exception if empty
1904 i = iteratorStack.peek();
1906 throw new NoSuchElementException("No further elements in RocketComponent iterator");
1909 // Retrieve next component of the iterator, remove iterator from stack if empty
1910 RocketComponent c = i.next();
1912 iteratorStack.pop();
1914 // Add iterator of component children to stack if it has children
1915 i = c.children.iterator();
1917 iteratorStack.push(i);
1922 private void checkID() {
1924 if (root.getTreeModID() != treeModID) {
1925 throw new IllegalStateException("Rocket modified while being iterated");
1931 public void remove() {
1932 throw new UnsupportedOperationException("remove() not supported by " +
1933 "RocketComponent iterator");