]> git.gag.com Git - debian/openrocket/blob - core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java
Implement persistence of ComponentPreset favorites to preferences. Implement favorit...
[debian/openrocket] / core / src / net / sf / openrocket / rocketcomponent / RocketComponent.java
1 package net.sf.openrocket.rocketcomponent;
2
3 import java.util.ArrayDeque;
4 import java.util.Collection;
5 import java.util.Deque;
6 import java.util.EventListener;
7 import java.util.Iterator;
8 import java.util.List;
9 import java.util.NoSuchElementException;
10
11 import net.sf.openrocket.l10n.Translator;
12 import net.sf.openrocket.logging.LogHelper;
13 import net.sf.openrocket.preset.ComponentPreset;
14 import net.sf.openrocket.startup.Application;
15 import net.sf.openrocket.util.ArrayList;
16 import net.sf.openrocket.util.BugException;
17 import net.sf.openrocket.util.ChangeSource;
18 import net.sf.openrocket.util.Color;
19 import net.sf.openrocket.util.Coordinate;
20 import net.sf.openrocket.util.Invalidator;
21 import net.sf.openrocket.util.LineStyle;
22 import net.sf.openrocket.util.MathUtil;
23 import net.sf.openrocket.util.SafetyMutex;
24 import net.sf.openrocket.util.UniqueID;
25
26
27 public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent> {
28         private static final LogHelper log = Application.getLogger();
29         private static final Translator trans = Application.getTranslator();
30         
31         /*
32          * Text is suitable to the form
33          *    Position relative to:  <title>
34          */
35         public enum Position {
36                 /** Position relative to the top of the parent component. */
37                 //// Top of the parent component
38                 TOP(trans.get("RocketComponent.Position.TOP")),
39                 /** Position relative to the middle of the parent component. */
40                 //// Middle of the parent component
41                 MIDDLE(trans.get("RocketComponent.Position.MIDDLE")),
42                 /** Position relative to the bottom of the parent component. */
43                 //// Bottom of the parent component
44                 BOTTOM(trans.get("RocketComponent.Position.BOTTOM")),
45                 /** Position after the parent component (for body components). */
46                 //// After the parent component
47                 AFTER(trans.get("RocketComponent.Position.AFTER")),
48                 /** Specify an absolute X-coordinate position. */
49                 //// Tip of the nose cone
50                 ABSOLUTE(trans.get("RocketComponent.Position.ABSOLUTE"));
51                 
52                 private String title;
53                 
54                 Position(String title) {
55                         this.title = title;
56                 }
57                 
58                 @Override
59                 public String toString() {
60                         return title;
61                 }
62         }
63         
64         /**
65          * A safety mutex that can be used to prevent concurrent access to this component.
66          */
67         protected SafetyMutex mutex = SafetyMutex.newInstance();
68         
69         ////////  Parent/child trees
70         /**
71          * Parent component of the current component, or null if none exists.
72          */
73         private RocketComponent parent = null;
74         
75         /**
76          * List of child components of this component.
77          */
78         private ArrayList<RocketComponent> children = new ArrayList<RocketComponent>();
79         
80         
81         ////////  Parameters common to all components:
82         
83         /**
84          * Characteristic length of the component.  This is used in calculating the coordinate
85          * transformations and positions of other components in reference to this component.
86          * This may and should be used as the "true" length of the component, where applicable.
87          * By default it is zero, i.e. no translation.
88          */
89         protected double length = 0;
90         
91         /**
92          * Positioning of this component relative to the parent component.
93          */
94         protected Position relativePosition;
95         
96         /**
97          * Offset of the position of this component relative to the normal position given by
98          * relativePosition.  By default zero, i.e. no position change.
99          */
100         protected double position = 0;
101         
102         
103         // Color of the component, null means to use the default color
104         private Color color = null;
105         private LineStyle lineStyle = null;
106         
107         
108         // Override mass/CG
109         private double overrideMass = 0;
110         private boolean massOverriden = false;
111         private double overrideCGX = 0;
112         private boolean cgOverriden = false;
113         
114         private boolean overrideSubcomponents = false;
115         
116         
117         // User-given name of the component
118         private String name = null;
119         
120         // User-specified comment
121         private String comment = "";
122         
123         // Unique ID of the component
124         private String id = null;
125         
126         // Preset component this component is based upon
127         private ComponentPreset presetComponent = null;
128         
129         
130         /**
131          * Used to invalidate the component after calling {@link #copyFrom(RocketComponent)}.
132          */
133         private Invalidator invalidator = new Invalidator(this);
134         
135         
136         ////  NOTE !!!  All fields must be copied in the method copyFrom()!  ////
137         
138         
139         
140         /**
141          * Default constructor.  Sets the name of the component to the component's static name
142          * and the relative position of the component.
143          */
144         public RocketComponent(Position relativePosition) {
145                 // These must not fire any events, due to Rocket undo system initialization
146                 this.name = getComponentName();
147                 this.relativePosition = relativePosition;
148                 newID();
149         }
150         
151         ////////////  Methods that must be implemented  ////////////
152         
153         
154         /**
155          * Static component name.  The name may not vary of the parameters, it must be static.
156          */
157         public abstract String getComponentName(); // Static component type name
158         
159         /**
160          * Return the component mass (regardless of mass overriding).
161          */
162         public abstract double getComponentMass(); // Mass of non-overridden component
163         
164         /**
165          * Return the component CG and mass (regardless of CG or mass overriding).
166          */
167         public abstract Coordinate getComponentCG(); // CG of non-overridden component
168         
169         
170         /**
171          * Return the longitudinal (around the y- or z-axis) unitary moment of inertia.
172          * The unitary moment of inertia is the moment of inertia with the assumption that
173          * the mass of the component is one kilogram.  The inertia is measured in
174          * respect to the non-overridden CG.
175          *
176          * @return   the longitudinal unitary moment of inertia of this component.
177          */
178         public abstract double getLongitudinalUnitInertia();
179         
180         
181         /**
182          * Return the rotational (around the x-axis) unitary moment of inertia.
183          * The unitary moment of inertia is the moment of inertia with the assumption that
184          * the mass of the component is one kilogram.  The inertia is measured in
185          * respect to the non-overridden CG.
186          *
187          * @return   the rotational unitary moment of inertia of this component.
188          */
189         public abstract double getRotationalUnitInertia();
190         
191         
192         /**
193          * Test whether this component allows any children components.  This method must
194          * return true if and only if {@link #isCompatible(Class)} returns true for any
195          * rocket component class.
196          *
197          * @return      <code>true</code> if children can be attached to this component, <code>false</code> otherwise.
198          */
199         public abstract boolean allowsChildren();
200         
201         /**
202          * Test whether the given component type can be added to this component.  This type safety
203          * is enforced by the <code>addChild()</code> methods.  The return value of this method
204          * may change to reflect the current state of this component (e.g. two components of some
205          * type cannot be placed as children).
206          *
207          * @param type  The RocketComponent class type to add.
208          * @return      Whether such a component can be added.
209          */
210         public abstract boolean isCompatible(Class<? extends RocketComponent> type);
211         
212         
213         /* Non-abstract helper method */
214         /**
215          * Test whether the given component can be added to this component.  This is equivalent
216          * to calling <code>isCompatible(c.getClass())</code>.
217          *
218          * @param c  Component to test.
219          * @return   Whether the component can be added.
220          * @see #isCompatible(Class)
221          */
222         public final boolean isCompatible(RocketComponent c) {
223                 mutex.verify();
224                 return isCompatible(c.getClass());
225         }
226         
227         
228         
229         /**
230          * Return a collection of bounding coordinates.  The coordinates must be such that
231          * the component is fully enclosed in their convex hull.
232          *
233          * @return      a collection of coordinates that bound the component.
234          */
235         public abstract Collection<Coordinate> getComponentBounds();
236         
237         /**
238          * Return true if the component may have an aerodynamic effect on the rocket.
239          */
240         public abstract boolean isAerodynamic();
241         
242         /**
243          * Return true if the component may have an effect on the rocket's mass.
244          */
245         public abstract boolean isMassive();
246         
247         
248         
249         
250         
251         ////////////  Methods that may be overridden  ////////////
252         
253         
254         /**
255          * Shift the coordinates in the array corresponding to radial movement.  A component
256          * that has a radial position must shift the coordinates in this array suitably.
257          * If the component is clustered, then a new array must be returned with a
258          * coordinate for each cluster.
259          * <p>
260          * The default implementation simply returns the array, and thus produces no shift.
261          *
262          * @param c   an array of coordinates to shift.
263          * @return    an array of shifted coordinates.  The method may modify the contents
264          *                        of the passed array and return the array itself.
265          */
266         public Coordinate[] shiftCoordinates(Coordinate[] c) {
267                 checkState();
268                 return c;
269         }
270         
271         
272         /**
273          * Called when any component in the tree fires a ComponentChangeEvent.  This is by
274          * default a no-op, but subclasses may override this method to e.g. invalidate
275          * cached data.  The overriding method *must* call
276          * <code>super.componentChanged(e)</code> at some point.
277          *
278          * @param e  The event fired
279          */
280         protected void componentChanged(ComponentChangeEvent e) {
281                 // No-op
282                 checkState();
283         }
284         
285         
286         
287         
288         /**
289          * Return the user-provided name of the component, or the component base
290          * name if the user-provided name is empty.  This can be used in the UI.
291          *
292          * @return A string describing the component.
293          */
294         @Override
295         public final String toString() {
296                 mutex.verify();
297                 if (name.length() == 0)
298                         return getComponentName();
299                 else
300                         return name;
301         }
302         
303         
304         /**
305          * Create a string describing the basic component structure from this component downwards.
306          * @return      a string containing the rocket structure
307          */
308         public final String toDebugString() {
309                 mutex.lock("toDebugString");
310                 try {
311                         StringBuilder sb = new StringBuilder();
312                         toDebugString(sb);
313                         return sb.toString();
314                 } finally {
315                         mutex.unlock("toDebugString");
316                 }
317         }
318         
319         private void toDebugString(StringBuilder sb) {
320                 sb.append(this.getClass().getSimpleName()).append('@').append(System.identityHashCode(this));
321                 sb.append("[\"").append(this.getName()).append('"');
322                 for (RocketComponent c : this.children) {
323                         sb.append("; ");
324                         c.toDebugString(sb);
325                 }
326                 sb.append(']');
327         }
328         
329         
330         /**
331          * Make a deep copy of the rocket component tree structure from this component
332          * downwards for copying purposes.  Each component in the copy will be assigned
333          * a new component ID, making it a safe copy.  This method does not fire any events.
334          *
335          * @return A deep copy of the structure.
336          */
337         public final RocketComponent copy() {
338                 RocketComponent clone = copyWithOriginalID();
339                 
340                 Iterator<RocketComponent> iterator = clone.iterator(true);
341                 while (iterator.hasNext()) {
342                         iterator.next().newID();
343                 }
344                 return clone;
345         }
346         
347         
348         
349         /**
350          * Make a deep copy of the rocket component tree structure from this component
351          * downwards while maintaining the component ID's.  The purpose of this method is
352          * to allow copies to be created with the original ID's for the purpose of the
353          * undo/redo mechanism.  This method should not be used for other purposes,
354          * such as copy/paste.  This method does not fire any events.
355          * <p>
356          * This method must be overridden by any component that refers to mutable objects,
357          * or if some fields should not be copied.  This should be performed by
358          * <code>RocketComponent c = super.copyWithOriginalID();</code> and then cloning/modifying
359          * the appropriate fields.
360          * <p>
361          * This is not performed as serializing/deserializing for performance reasons.
362          *
363          * @return A deep copy of the structure.
364          */
365         protected RocketComponent copyWithOriginalID() {
366                 mutex.lock("copyWithOriginalID");
367                 try {
368                         checkState();
369                         RocketComponent clone;
370                         try {
371                                 clone = (RocketComponent) this.clone();
372                         } catch (CloneNotSupportedException e) {
373                                 throw new BugException("CloneNotSupportedException encountered, report a bug!", e);
374                         }
375                         
376                         // Reset the mutex
377                         clone.mutex = SafetyMutex.newInstance();
378                         
379                         // Reset all parent/child information
380                         clone.parent = null;
381                         clone.children = new ArrayList<RocketComponent>();
382                         
383                         // Add copied children to the structure without firing events.
384                         for (RocketComponent child : this.children) {
385                                 RocketComponent childCopy = child.copyWithOriginalID();
386                                 // Don't use add method since it fires events
387                                 clone.children.add(childCopy);
388                                 childCopy.parent = clone;
389                         }
390                         
391                         this.checkComponentStructure();
392                         clone.checkComponentStructure();
393                         
394                         return clone;
395                 } finally {
396                         mutex.unlock("copyWithOriginalID");
397                 }
398         }
399         
400         
401         //////////////  Methods that may not be overridden  ////////////
402         
403         
404         
405         ////////// Common parameter setting/getting //////////
406         
407         /**
408          * Return the color of the object to use in 2D figures, or <code>null</code>
409          * to use the default color.
410          */
411         public final Color getColor() {
412                 mutex.verify();
413                 return color;
414         }
415         
416         /**
417          * Set the color of the object to use in 2D figures.
418          */
419         public final void setColor(Color c) {
420                 if ((color == null && c == null) ||
421                                 (color != null && color.equals(c)))
422                         return;
423                 
424                 checkState();
425                 this.color = c;
426                 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
427         }
428         
429         
430         public final LineStyle getLineStyle() {
431                 mutex.verify();
432                 return lineStyle;
433         }
434         
435         public final void setLineStyle(LineStyle style) {
436                 if (this.lineStyle == style)
437                         return;
438                 checkState();
439                 this.lineStyle = style;
440                 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
441         }
442         
443         
444         
445         
446         /**
447          * Get the current override mass.  The mass is not necessarily in use
448          * at the moment.
449          *
450          * @return  the override mass
451          */
452         public final double getOverrideMass() {
453                 mutex.verify();
454                 return overrideMass;
455         }
456         
457         /**
458          * Set the current override mass.  The mass is not set to use by this
459          * method.
460          *
461          * @param m  the override mass
462          */
463         public final void setOverrideMass(double m) {
464                 if (MathUtil.equals(m, overrideMass))
465                         return;
466                 checkState();
467                 overrideMass = Math.max(m, 0);
468                 if (massOverriden)
469                         fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
470         }
471         
472         /**
473          * Return whether mass override is active for this component.  This does NOT
474          * take into account whether a parent component is overriding the mass.
475          *
476          * @return  whether the mass is overridden
477          */
478         public final boolean isMassOverridden() {
479                 mutex.verify();
480                 return massOverriden;
481         }
482         
483         /**
484          * Set whether the mass is currently overridden.
485          *
486          * @param o  whether the mass is overridden
487          */
488         public final void setMassOverridden(boolean o) {
489                 if (massOverriden == o) {
490                         return;
491                 }
492                 checkState();
493                 massOverriden = o;
494                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
495         }
496         
497         
498         
499         
500         
501         /**
502          * Return the current override CG.  The CG is not necessarily overridden.
503          *
504          * @return  the override CG
505          */
506         public final Coordinate getOverrideCG() {
507                 mutex.verify();
508                 return getComponentCG().setX(overrideCGX);
509         }
510         
511         /**
512          * Return the x-coordinate of the current override CG.
513          *
514          * @return      the x-coordinate of the override CG.
515          */
516         public final double getOverrideCGX() {
517                 mutex.verify();
518                 return overrideCGX;
519         }
520         
521         /**
522          * Set the current override CG to (x,0,0).
523          *
524          * @param x  the x-coordinate of the override CG to set.
525          */
526         public final void setOverrideCGX(double x) {
527                 if (MathUtil.equals(overrideCGX, x))
528                         return;
529                 checkState();
530                 this.overrideCGX = x;
531                 if (isCGOverridden())
532                         fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
533                 else
534                         fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
535         }
536         
537         /**
538          * Return whether the CG is currently overridden.
539          *
540          * @return  whether the CG is overridden
541          */
542         public final boolean isCGOverridden() {
543                 mutex.verify();
544                 return cgOverriden;
545         }
546         
547         /**
548          * Set whether the CG is currently overridden.
549          *
550          * @param o  whether the CG is overridden
551          */
552         public final void setCGOverridden(boolean o) {
553                 if (cgOverriden == o) {
554                         return;
555                 }
556                 checkState();
557                 cgOverriden = o;
558                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
559         }
560         
561         
562         
563         /**
564          * Return whether the mass and/or CG override overrides all subcomponent values
565          * as well.  The default implementation is a normal getter/setter implementation,
566          * however, subclasses are allowed to override this behavior if some subclass
567          * always or never overrides subcomponents.  In this case the subclass should
568          * also override {@link #isOverrideSubcomponentsEnabled()} to return
569          * <code>false</code>.
570          *
571          * @return      whether the current mass and/or CG override overrides subcomponents as well.
572          */
573         public boolean getOverrideSubcomponents() {
574                 mutex.verify();
575                 return overrideSubcomponents;
576         }
577         
578         
579         /**
580          * Set whether the mass and/or CG override overrides all subcomponent values
581          * as well.  See {@link #getOverrideSubcomponents()} for details.
582          *
583          * @param override      whether the mass and/or CG override overrides all subcomponent.
584          */
585         public void setOverrideSubcomponents(boolean override) {
586                 if (overrideSubcomponents == override) {
587                         return;
588                 }
589                 checkState();
590                 overrideSubcomponents = override;
591                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
592         }
593         
594         /**
595          * Return whether the option to override all subcomponents is enabled or not.
596          * The default implementation returns <code>false</code> if neither mass nor
597          * CG is overridden, <code>true</code> otherwise.
598          * <p>
599          * This method may be overridden if the setting of overriding subcomponents
600          * cannot be set.
601          *
602          * @return      whether the option to override subcomponents is currently enabled.
603          */
604         public boolean isOverrideSubcomponentsEnabled() {
605                 mutex.verify();
606                 return isCGOverridden() || isMassOverridden();
607         }
608         
609         
610         
611         
612         /**
613          * Get the user-defined name of the component.
614          */
615         public final String getName() {
616                 mutex.verify();
617                 return name;
618         }
619         
620         /**
621          * Set the user-defined name of the component.  If name==null, sets the name to
622          * the default name, currently the component name.
623          */
624         public final void setName(String name) {
625                 if (this.name.equals(name)) {
626                         return;
627                 }
628                 checkState();
629                 if (name == null || name.matches("^\\s*$"))
630                         this.name = getComponentName();
631                 else
632                         this.name = name;
633                 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
634         }
635         
636         
637         /**
638          * Return the comment of the component.  The component may contain multiple lines
639          * using \n as a newline separator.
640          *
641          * @return  the comment of the component.
642          */
643         public final String getComment() {
644                 mutex.verify();
645                 return comment;
646         }
647         
648         /**
649          * Set the comment of the component.
650          *
651          * @param comment  the comment of the component.
652          */
653         public final void setComment(String comment) {
654                 if (this.comment.equals(comment))
655                         return;
656                 checkState();
657                 if (comment == null)
658                         this.comment = "";
659                 else
660                         this.comment = comment;
661                 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
662         }
663         
664         
665         
666         /**
667          * Return the preset component that this component is based upon.
668          * 
669          * @return      the preset component, or <code>null</code> if this is not based on a preset.
670          */
671         public final ComponentPreset getPresetComponent() {
672                 return presetComponent;
673         }
674         
675         /**
676          * Return the most compatible preset type for this component.
677          * This method should be overridden by components which have presets
678          * 
679          * @return the most compatible ComponentPreset.Type or <code>null</code> if no presets are compatible.
680          */
681         public ComponentPreset.Type getPresetType() {
682                 return null;
683         }
684         
685         /**
686          * Set the preset component this component is based upon and load all of the 
687          * preset values.
688          * 
689          * @param preset        the preset component to load, or <code>null</code> to clear the preset.
690          */
691         public final void loadPreset(ComponentPreset preset) {
692                 if (presetComponent == preset) {
693                         return;
694                 }
695                 
696                 if (preset == null) {
697                         clearPreset();
698                         return;
699                 }
700                 
701                 // TODO - do we need to this compatibility check?
702                 /*
703                 if (preset.getComponentClass() != this.getClass()) {
704                         throw new IllegalArgumentException("Attempting to load preset of type " + preset.getComponentClass()
705                                         + " into component of type " + this.getClass());
706                 }
707                 */
708                 
709                 RocketComponent root = getRoot();
710                 final Rocket rocket;
711                 if (root instanceof Rocket) {
712                         rocket = (Rocket) root;
713                 } else {
714                         rocket = null;
715                 }
716                 
717                 try {
718                         if (rocket != null) {
719                                 rocket.freeze();
720                         }
721                         
722                         loadFromPreset(preset);
723                         
724                         this.presetComponent = preset;
725                         fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
726                         
727                 } finally {
728                         if (rocket != null) {
729                                 rocket.thaw();
730                         }
731                 }
732         }
733         
734         
735         /**
736          * Load component properties from the specified preset.  The preset is guaranteed
737          * to be of the correct type.
738          * <p>
739          * This method should fire the appropriate events related to the changes.  The rocket
740          * is frozen by the caller, so the events will be automatically combined.
741          * <p>
742          * This method must FIRST perform the preset loading and THEN call super.loadFromPreset().
743          * This is because mass setting requires the dimensions to be set beforehand.
744          * 
745          * @param preset        the preset to load from
746          */
747         protected void loadFromPreset(ComponentPreset preset) {
748                 // No-op
749         }
750         
751         
752         /**
753          * Clear the current component preset.  This does not affect the component properties
754          * otherwise.
755          */
756         public final void clearPreset() {
757                 if (presetComponent == null)
758                         return;
759                 presetComponent = null;
760                 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
761         }
762         
763         
764         
765         /**
766          * Returns the unique ID of the component.
767          *
768          * @return      the ID of the component.
769          */
770         public final String getID() {
771                 return id;
772         }
773         
774         /**
775          * Generate a new ID for this component.
776          */
777         private final void newID() {
778                 mutex.verify();
779                 this.id = UniqueID.uuid();
780         }
781         
782         
783         
784         
785         /**
786          * Get the characteristic length of the component, for example the length of a body tube
787          * of the length of the root chord of a fin.  This is used in positioning the component
788          * relative to its parent.
789          *
790          * If the length of a component is settable, the class must define the setter method
791          * itself.
792          */
793         public final double getLength() {
794                 mutex.verify();
795                 return length;
796         }
797         
798         /**
799          * Get the positioning of the component relative to its parent component.
800          * This is one of the enums of {@link Position}.  A setter method is not provided,
801          * but can be provided by a subclass.
802          */
803         public final Position getRelativePosition() {
804                 mutex.verify();
805                 return relativePosition;
806         }
807         
808         
809         /**
810          * Set the positioning of the component relative to its parent component.
811          * The actual position of the component is maintained to the best ability.
812          * <p>
813          * The default implementation is of protected visibility, since many components
814          * do not support setting the relative position.  A component that does support
815          * it should override this with a public method that simply calls this
816          * supermethod AND fire a suitable ComponentChangeEvent.
817          *
818          * @param position      the relative positioning.
819          */
820         protected void setRelativePosition(RocketComponent.Position position) {
821                 if (this.relativePosition == position)
822                         return;
823                 checkState();
824                 
825                 // Update position so as not to move the component
826                 if (this.parent != null) {
827                         double thisPos = this.toRelative(Coordinate.NUL, this.parent)[0].x;
828                         
829                         switch (position) {
830                         case ABSOLUTE:
831                                 this.position = this.toAbsolute(Coordinate.NUL)[0].x;
832                                 break;
833                         
834                         case TOP:
835                                 this.position = thisPos;
836                                 break;
837                         
838                         case MIDDLE:
839                                 this.position = thisPos - (this.parent.length - this.length) / 2;
840                                 break;
841                         
842                         case BOTTOM:
843                                 this.position = thisPos - (this.parent.length - this.length);
844                                 break;
845                         
846                         default:
847                                 throw new BugException("Unknown position type: " + position);
848                         }
849                 }
850                 
851                 this.relativePosition = position;
852                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
853         }
854         
855         
856         /**
857          * Determine position relative to given position argument.  Note: This is a side-effect free method.  No state
858          * is modified.
859          *
860          * @param thePosition the relative position to be used as the basis for the computation
861          * @param relativeTo  the position is computed relative the the given component
862          *
863          * @return double position of the component relative to the parent, with respect to <code>position</code>
864          */
865         public double asPositionValue(Position thePosition, RocketComponent relativeTo) {
866                 double result = this.position;
867                 if (relativeTo != null) {
868                         double thisPos = this.toRelative(Coordinate.NUL, relativeTo)[0].x;
869                         
870                         switch (thePosition) {
871                         case ABSOLUTE:
872                                 result = this.toAbsolute(Coordinate.NUL)[0].x;
873                                 break;
874                         case TOP:
875                                 result = thisPos;
876                                 break;
877                         case MIDDLE:
878                                 result = thisPos - (relativeTo.length - this.length) / 2;
879                                 break;
880                         case BOTTOM:
881                                 result = thisPos - (relativeTo.length - this.length);
882                                 break;
883                         default:
884                                 throw new BugException("Unknown position type: " + thePosition);
885                         }
886                 }
887                 return result;
888         }
889         
890         /**
891          * Get the position value of the component.  The exact meaning of the value is
892          * dependent on the current relative positioning.
893          *
894          * @return  the positional value.
895          */
896         public final double getPositionValue() {
897                 mutex.verify();
898                 return position;
899         }
900         
901         
902         /**
903          * Set the position value of the component.  The exact meaning of the value
904          * depends on the current relative positioning.
905          * <p>
906          * The default implementation is of protected visibility, since many components
907          * do not support setting the relative position.  A component that does support
908          * it should override this with a public method that simply calls this
909          * supermethod AND fire a suitable ComponentChangeEvent.
910          *
911          * @param value         the position value of the component.
912          */
913         public void setPositionValue(double value) {
914                 if (MathUtil.equals(this.position, value))
915                         return;
916                 checkState();
917                 this.position = value;
918         }
919         
920         
921         
922         ///////////  Coordinate changes  ///////////
923         
924         /**
925          * Returns coordinate c in absolute coordinates.  Equivalent to toComponent(c,null).
926          */
927         public Coordinate[] toAbsolute(Coordinate c) {
928                 checkState();
929                 return toRelative(c, null);
930         }
931         
932         
933         /**
934          * Return coordinate <code>c</code> described in the coordinate system of
935          * <code>dest</code>.  If <code>dest</code> is <code>null</code> returns
936          * absolute coordinates.
937          * <p>
938          * This method returns an array of coordinates, each of which represents a
939          * position of the coordinate in clustered cases.  The array is guaranteed
940          * to contain at least one element.
941          * <p>
942          * The current implementation does not support rotating components.
943          *
944          * @param c    Coordinate in the component's coordinate system.
945          * @param dest Destination component coordinate system.
946          * @return     an array of coordinates describing <code>c</code> in coordinates
947          *                         relative to <code>dest</code>.
948          */
949         public final Coordinate[] toRelative(Coordinate c, RocketComponent dest) {
950                 checkState();
951                 mutex.lock("toRelative");
952                 try {
953                         double absoluteX = Double.NaN;
954                         RocketComponent search = dest;
955                         Coordinate[] array = new Coordinate[1];
956                         array[0] = c;
957                         
958                         RocketComponent component = this;
959                         while ((component != search) && (component.parent != null)) {
960                                 
961                                 array = component.shiftCoordinates(array);
962                                 
963                                 switch (component.relativePosition) {
964                                 case TOP:
965                                         for (int i = 0; i < array.length; i++) {
966                                                 array[i] = array[i].add(component.position, 0, 0);
967                                         }
968                                         break;
969                                 
970                                 case MIDDLE:
971                                         for (int i = 0; i < array.length; i++) {
972                                                 array[i] = array[i].add(component.position +
973                                                                 (component.parent.length - component.length) / 2, 0, 0);
974                                         }
975                                         break;
976                                 
977                                 case BOTTOM:
978                                         for (int i = 0; i < array.length; i++) {
979                                                 array[i] = array[i].add(component.position +
980                                                                 (component.parent.length - component.length), 0, 0);
981                                         }
982                                         break;
983                                 
984                                 case AFTER:
985                                         // Add length of all previous brother-components with POSITION_RELATIVE_AFTER
986                                         int index = component.parent.children.indexOf(component);
987                                         assert (index >= 0);
988                                         for (index--; index >= 0; index--) {
989                                                 RocketComponent comp = component.parent.children.get(index);
990                                                 double componentLength = comp.getTotalLength();
991                                                 for (int i = 0; i < array.length; i++) {
992                                                         array[i] = array[i].add(componentLength, 0, 0);
993                                                 }
994                                         }
995                                         for (int i = 0; i < array.length; i++) {
996                                                 array[i] = array[i].add(component.position + component.parent.length, 0, 0);
997                                         }
998                                         break;
999                                 
1000                                 case ABSOLUTE:
1001                                         search = null; // Requires back-search if dest!=null
1002                                         if (Double.isNaN(absoluteX)) {
1003                                                 absoluteX = component.position;
1004                                         }
1005                                         break;
1006                                 
1007                                 default:
1008                                         throw new BugException("Unknown relative positioning type of component" +
1009                                                         component + ": " + component.relativePosition);
1010                                 }
1011                                 
1012                                 component = component.parent; // parent != null
1013                         }
1014                         
1015                         if (!Double.isNaN(absoluteX)) {
1016                                 for (int i = 0; i < array.length; i++) {
1017                                         array[i] = array[i].setX(absoluteX + c.x);
1018                                 }
1019                         }
1020                         
1021                         // Check whether destination has been found or whether to backtrack
1022                         // TODO: LOW: Backtracking into clustered components uses only one component
1023                         if ((dest != null) && (component != dest)) {
1024                                 Coordinate[] origin = dest.toAbsolute(Coordinate.NUL);
1025                                 for (int i = 0; i < array.length; i++) {
1026                                         array[i] = array[i].sub(origin[0]);
1027                                 }
1028                         }
1029                         
1030                         return array;
1031                 } finally {
1032                         mutex.unlock("toRelative");
1033                 }
1034         }
1035         
1036         
1037         /**
1038          * Recursively sum the lengths of all subcomponents that have position
1039          * Position.AFTER.
1040          *
1041          * @return  Sum of the lengths.
1042          */
1043         private final double getTotalLength() {
1044                 checkState();
1045                 this.checkComponentStructure();
1046                 mutex.lock("getTotalLength");
1047                 try {
1048                         double l = 0;
1049                         if (relativePosition == Position.AFTER)
1050                                 l = length;
1051                         for (int i = 0; i < children.size(); i++)
1052                                 l += children.get(i).getTotalLength();
1053                         return l;
1054                 } finally {
1055                         mutex.unlock("getTotalLength");
1056                 }
1057         }
1058         
1059         
1060         
1061         /////////// Total mass and CG calculation ////////////
1062         
1063         /**
1064          * Return the (possibly overridden) mass of component.
1065          *
1066          * @return The mass of the component or the given override mass.
1067          */
1068         public final double getMass() {
1069                 mutex.verify();
1070                 if (massOverriden)
1071                         return overrideMass;
1072                 return getComponentMass();
1073         }
1074         
1075         /**
1076          * Return the (possibly overridden) center of gravity and mass.
1077          *
1078          * Returns the CG with the weight of the coordinate set to the weight of the component.
1079          * Both CG and mass may be separately overridden.
1080          *
1081          * @return The CG of the component or the given override CG.
1082          */
1083         public final Coordinate getCG() {
1084                 checkState();
1085                 if (cgOverriden)
1086                         return getOverrideCG().setWeight(getMass());
1087                 
1088                 if (massOverriden)
1089                         return getComponentCG().setWeight(getMass());
1090                 
1091                 return getComponentCG();
1092         }
1093         
1094         
1095         /**
1096          * Return the longitudinal (around the y- or z-axis) moment of inertia of this component.
1097          * The moment of inertia is scaled in reference to the (possibly overridden) mass
1098          * and is relative to the non-overridden CG.
1099          *
1100          * @return    the longitudinal moment of inertia of this component.
1101          */
1102         public final double getLongitudinalInertia() {
1103                 checkState();
1104                 return getLongitudinalUnitInertia() * getMass();
1105         }
1106         
1107         /**
1108          * Return the rotational (around the y- or z-axis) moment of inertia of this component.
1109          * The moment of inertia is scaled in reference to the (possibly overridden) mass
1110          * and is relative to the non-overridden CG.
1111          *
1112          * @return    the rotational moment of inertia of this component.
1113          */
1114         public final double getRotationalInertia() {
1115                 checkState();
1116                 return getRotationalUnitInertia() * getMass();
1117         }
1118         
1119         
1120         
1121         ///////////  Children handling  ///////////
1122         
1123         
1124         /**
1125          * Adds a child to the rocket component tree.  The component is added to the end
1126          * of the component's child list.  This is a helper method that calls
1127          * {@link #addChild(RocketComponent,int)}.
1128          *
1129          * @param component  The component to add.
1130          * @throws IllegalArgumentException  if the component is already part of some
1131          *                                                                       component tree.
1132          * @see #addChild(RocketComponent,int)
1133          */
1134         public final void addChild(RocketComponent component) {
1135                 checkState();
1136                 addChild(component, children.size());
1137         }
1138         
1139         
1140         /**
1141          * Adds a child to the rocket component tree.  The component is added to
1142          * the given position of the component's child list.
1143          * <p>
1144          * This method may be overridden to enforce more strict component addition rules.
1145          * The tests should be performed first and then this method called.
1146          *
1147          * @param component     The component to add.
1148          * @param index         Position to add component to.
1149          * @throws IllegalArgumentException  If the component is already part of
1150          *                                                                       some component tree.
1151          */
1152         public void addChild(RocketComponent component, int index) {
1153                 checkState();
1154                 
1155                 if (component.parent != null) {
1156                         throw new IllegalArgumentException("component " + component.getComponentName() +
1157                                         " is already in a tree");
1158                 }
1159                 
1160                 // Ensure that the no loops are created in component tree [A -> X -> Y -> B, B.addChild(A)]
1161                 if (this.getRoot().equals(component)) {
1162                         throw new IllegalStateException("Component " + component.getComponentName() +
1163                                         " is a parent of " + this.getComponentName() + ", attempting to create cycle in tree.");
1164                 }
1165                 
1166                 if (!isCompatible(component)) {
1167                         throw new IllegalStateException("Component " + component.getComponentName() +
1168                                         " not currently compatible with component " + getComponentName());
1169                 }
1170                 
1171                 children.add(index, component);
1172                 component.parent = this;
1173                 
1174                 this.checkComponentStructure();
1175                 component.checkComponentStructure();
1176                 
1177                 fireAddRemoveEvent(component);
1178         }
1179         
1180         /**
1181          * Removes a child from the rocket component tree.
1182          *
1183          * @param n  remove the n'th child.
1184          * @throws IndexOutOfBoundsException  if n is out of bounds
1185          */
1186         public final void removeChild(int n) {
1187                 checkState();
1188                 RocketComponent component = children.remove(n);
1189                 component.parent = null;
1190                 
1191                 this.checkComponentStructure();
1192                 component.checkComponentStructure();
1193                 
1194                 fireAddRemoveEvent(component);
1195         }
1196         
1197         /**
1198          * Removes a child from the rocket component tree.  Does nothing if the component
1199          * is not present as a child.
1200          *
1201          * @param component             the component to remove
1202          * @return                              whether the component was a child
1203          */
1204         public final boolean removeChild(RocketComponent component) {
1205                 checkState();
1206                 
1207                 component.checkComponentStructure();
1208                 
1209                 if (children.remove(component)) {
1210                         component.parent = null;
1211                         
1212                         this.checkComponentStructure();
1213                         component.checkComponentStructure();
1214                         
1215                         fireAddRemoveEvent(component);
1216                         return true;
1217                 }
1218                 return false;
1219         }
1220         
1221         
1222         
1223         
1224         /**
1225          * Move a child to another position.
1226          *
1227          * @param component     the component to move
1228          * @param index the component's new position
1229          * @throws IllegalArgumentException If an illegal placement was attempted.
1230          */
1231         public final void moveChild(RocketComponent component, int index) {
1232                 checkState();
1233                 if (children.remove(component)) {
1234                         children.add(index, component);
1235                         
1236                         this.checkComponentStructure();
1237                         component.checkComponentStructure();
1238                         
1239                         fireAddRemoveEvent(component);
1240                 }
1241         }
1242         
1243         
1244         /**
1245          * Fires an AERODYNAMIC_CHANGE, MASS_CHANGE or OTHER_CHANGE event depending on the
1246          * type of component removed.
1247          */
1248         private void fireAddRemoveEvent(RocketComponent component) {
1249                 Iterator<RocketComponent> iter = component.iterator(true);
1250                 int type = ComponentChangeEvent.TREE_CHANGE;
1251                 while (iter.hasNext()) {
1252                         RocketComponent c = iter.next();
1253                         if (c.isAerodynamic())
1254                                 type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
1255                         if (c.isMassive())
1256                                 type |= ComponentChangeEvent.MASS_CHANGE;
1257                 }
1258                 
1259                 fireComponentChangeEvent(type);
1260         }
1261         
1262         
1263         public final int getChildCount() {
1264                 checkState();
1265                 this.checkComponentStructure();
1266                 return children.size();
1267         }
1268         
1269         public final RocketComponent getChild(int n) {
1270                 checkState();
1271                 this.checkComponentStructure();
1272                 return children.get(n);
1273         }
1274         
1275         public final List<RocketComponent> getChildren() {
1276                 checkState();
1277                 this.checkComponentStructure();
1278                 return children.clone();
1279         }
1280         
1281         
1282         /**
1283          * Returns the position of the child in this components child list, or -1 if the
1284          * component is not a child of this component.
1285          *
1286          * @param child  The child to search for.
1287          * @return  Position in the list or -1 if not found.
1288          */
1289         public final int getChildPosition(RocketComponent child) {
1290                 checkState();
1291                 this.checkComponentStructure();
1292                 return children.indexOf(child);
1293         }
1294         
1295         /**
1296          * Get the parent component of this component.  Returns <code>null</code> if the component
1297          * has no parent.
1298          *
1299          * @return  The parent of this component or <code>null</code>.
1300          */
1301         public final RocketComponent getParent() {
1302                 checkState();
1303                 return parent;
1304         }
1305         
1306         /**
1307          * Get the root component of the component tree.
1308          *
1309          * @return  The root component of the component tree.
1310          */
1311         public final RocketComponent getRoot() {
1312                 checkState();
1313                 RocketComponent gp = this;
1314                 while (gp.parent != null)
1315                         gp = gp.parent;
1316                 return gp;
1317         }
1318         
1319         /**
1320          * Returns the root Rocket component of this component tree.  Throws an
1321          * IllegalStateException if the root component is not a Rocket.
1322          *
1323          * @return  The root Rocket component of the component tree.
1324          * @throws  IllegalStateException  If the root component is not a Rocket.
1325          */
1326         public final Rocket getRocket() {
1327                 checkState();
1328                 RocketComponent r = getRoot();
1329                 if (r instanceof Rocket)
1330                         return (Rocket) r;
1331                 throw new IllegalStateException("getRocket() called with root component "
1332                                 + r.getComponentName());
1333         }
1334         
1335         
1336         /**
1337          * Return the Stage component that this component belongs to.  Throws an
1338          * IllegalStateException if a Stage is not in the parentage of this component.
1339          *
1340          * @return      The Stage component this component belongs to.
1341          * @throws      IllegalStateException   if a Stage component is not in the parentage.
1342          */
1343         public final Stage getStage() {
1344                 checkState();
1345                 RocketComponent c = this;
1346                 while (c != null) {
1347                         if (c instanceof Stage)
1348                                 return (Stage) c;
1349                         c = c.getParent();
1350                 }
1351                 throw new IllegalStateException("getStage() called without Stage as a parent.");
1352         }
1353         
1354         /**
1355          * Return the stage number of the stage this component belongs to.  The stages
1356          * are numbered from zero upwards.
1357          *
1358          * @return   the stage number this component belongs to.
1359          */
1360         public final int getStageNumber() {
1361                 checkState();
1362                 if (parent == null) {
1363                         throw new IllegalArgumentException("getStageNumber() called for root component");
1364                 }
1365                 
1366                 RocketComponent stage = this;
1367                 while (!(stage instanceof Stage)) {
1368                         stage = stage.parent;
1369                         if (stage == null || stage.parent == null) {
1370                                 throw new IllegalStateException("getStageNumber() could not find parent " +
1371                                                 "stage.");
1372                         }
1373                 }
1374                 return stage.parent.getChildPosition(stage);
1375         }
1376         
1377         
1378         /**
1379          * Find a component with the given ID.  The component tree is searched from this component
1380          * down (including this component) for the ID and the corresponding component is returned,
1381          * or null if not found.
1382          *
1383          * @param idToFind  ID to search for.
1384          * @return    The component with the ID, or null if not found.
1385          */
1386         public final RocketComponent findComponent(String idToFind) {
1387                 checkState();
1388                 Iterator<RocketComponent> iter = this.iterator(true);
1389                 while (iter.hasNext()) {
1390                         RocketComponent c = iter.next();
1391                         if (c.getID().equals(idToFind))
1392                                 return c;
1393                 }
1394                 return null;
1395         }
1396         
1397         
1398         // TODO: Move these methods elsewhere (used only in SymmetricComponent)
1399         public final RocketComponent getPreviousComponent() {
1400                 checkState();
1401                 this.checkComponentStructure();
1402                 if (parent == null)
1403                         return null;
1404                 int pos = parent.getChildPosition(this);
1405                 if (pos < 0) {
1406                         StringBuffer sb = new StringBuffer();
1407                         sb.append("Inconsistent internal state: ");
1408                         sb.append("this=").append(this).append('[')
1409                                         .append(System.identityHashCode(this)).append(']');
1410                         sb.append(" parent.children=[");
1411                         for (int i = 0; i < parent.children.size(); i++) {
1412                                 RocketComponent c = parent.children.get(i);
1413                                 sb.append(c).append('[').append(System.identityHashCode(c)).append(']');
1414                                 if (i < parent.children.size() - 1)
1415                                         sb.append(", ");
1416                         }
1417                         sb.append(']');
1418                         throw new IllegalStateException(sb.toString());
1419                 }
1420                 assert (pos >= 0);
1421                 if (pos == 0)
1422                         return parent;
1423                 RocketComponent c = parent.getChild(pos - 1);
1424                 while (c.getChildCount() > 0)
1425                         c = c.getChild(c.getChildCount() - 1);
1426                 return c;
1427         }
1428         
1429         // TODO: Move these methods elsewhere (used only in SymmetricComponent)
1430         public final RocketComponent getNextComponent() {
1431                 checkState();
1432                 if (getChildCount() > 0)
1433                         return getChild(0);
1434                 
1435                 RocketComponent current = this;
1436                 RocketComponent nextParent = this.parent;
1437                 
1438                 while (nextParent != null) {
1439                         int pos = nextParent.getChildPosition(current);
1440                         if (pos < nextParent.getChildCount() - 1)
1441                                 return nextParent.getChild(pos + 1);
1442                         
1443                         current = nextParent;
1444                         nextParent = current.parent;
1445                 }
1446                 return null;
1447         }
1448         
1449         
1450         ///////////  Event handling  //////////
1451         //
1452         // Listener lists are provided by the root Rocket component,
1453         // a single listener list for the whole rocket.
1454         //
1455         
1456         /**
1457          * Adds a ComponentChangeListener to the rocket tree.  The listener is added to the root
1458          * component, which must be of type Rocket (which overrides this method).  Events of all
1459          * subcomponents are sent to all listeners.
1460          *
1461          * @throws IllegalStateException - if the root component is not a Rocket
1462          */
1463         public void addComponentChangeListener(ComponentChangeListener l) {
1464                 checkState();
1465                 getRocket().addComponentChangeListener(l);
1466         }
1467         
1468         /**
1469          * Removes a ComponentChangeListener from the rocket tree.  The listener is removed from
1470          * the root component, which must be of type Rocket (which overrides this method).
1471          * Does nothing if the root component is not a Rocket.  (The asymmetry is so
1472          * that listeners can always be removed just in case.)
1473          *
1474          * @param l  Listener to remove
1475          */
1476         public void removeComponentChangeListener(ComponentChangeListener l) {
1477                 if (parent != null) {
1478                         getRoot().removeComponentChangeListener(l);
1479                 }
1480         }
1481         
1482         
1483         /**
1484          * Adds a <code>ChangeListener</code> to the rocket tree.  This is identical to
1485          * <code>addComponentChangeListener()</code> except that it uses a
1486          * <code>ChangeListener</code>.  The same events are dispatched to the
1487          * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass
1488          * of <code>ChangeEvent</code>.
1489          *
1490          * @throws IllegalStateException - if the root component is not a <code>Rocket</code>
1491          */
1492         @Override
1493         public void addChangeListener(EventListener l) {
1494                 checkState();
1495                 getRocket().addChangeListener(l);
1496         }
1497         
1498         /**
1499          * Removes a ChangeListener from the rocket tree.  This is identical to
1500          * removeComponentChangeListener() except it uses a ChangeListener.
1501          * Does nothing if the root component is not a Rocket.  (The asymmetry is so
1502          * that listeners can always be removed just in case.)
1503          *
1504          * @param l  Listener to remove
1505          */
1506         @Override
1507         public void removeChangeListener(EventListener l) {
1508                 if (this.parent != null) {
1509                         getRoot().removeChangeListener(l);
1510                 }
1511         }
1512         
1513         
1514         /**
1515          * Fires a ComponentChangeEvent on the rocket structure.  The call is passed to the
1516          * root component, which must be of type Rocket (which overrides this method).
1517          * Events of all subcomponents are sent to all listeners.
1518          *
1519          * If the component tree root is not a Rocket, the event is ignored.  This is the
1520          * case when constructing components not in any Rocket tree.  In this case it
1521          * would be impossible for the component to have listeners in any case.
1522          *
1523          * @param e  Event to send
1524          */
1525         protected void fireComponentChangeEvent(ComponentChangeEvent e) {
1526                 checkState();
1527                 if (parent == null) {
1528                         /* Ignore if root invalid. */
1529                         log.debug("Attempted firing event " + e + " with root " + this.getComponentName() + ", ignoring event");
1530                         return;
1531                 }
1532                 getRoot().fireComponentChangeEvent(e);
1533         }
1534         
1535         
1536         /**
1537          * Fires a ComponentChangeEvent of the given type.  The source of the event is set to
1538          * this component.
1539          *
1540          * @param type  Type of event
1541          * @see #fireComponentChangeEvent(ComponentChangeEvent)
1542          */
1543         protected void fireComponentChangeEvent(int type) {
1544                 fireComponentChangeEvent(new ComponentChangeEvent(this, type));
1545         }
1546         
1547         
1548         /**
1549          * Checks whether this component has been invalidated and should no longer be used.
1550          * This is a safety check that in-place replaced components are no longer used.
1551          * All non-trivial methods (with the exception of methods simply getting a property)
1552          * should call this method before changing or computing anything.
1553          *
1554          * @throws      BugException    if this component has been invalidated by {@link #copyFrom(RocketComponent)}.
1555          */
1556         protected void checkState() {
1557                 invalidator.check(true);
1558                 mutex.verify();
1559         }
1560         
1561         
1562         /**
1563          * Check that the local component structure is correct.  This can be called after changing
1564          * the component structure in order to verify the integrity.
1565          * <p>
1566          * TODO: Remove this after the "inconsistent internal state" bug has been corrected
1567          */
1568         public void checkComponentStructure() {
1569                 if (this.parent != null) {
1570                         // Test that this component is found in parent's children with == operator
1571                         if (!containsExact(this.parent.children, this)) {
1572                                 throw new BugException("Inconsistent component structure detected, parent does not contain this " +
1573                                                 "component as a child, parent=" + parent.toDebugString() + " this=" + this.toDebugString());
1574                         }
1575                 }
1576                 for (RocketComponent child : this.children) {
1577                         if (child.parent != this) {
1578                                 throw new BugException("Inconsistent component structure detected, child does not have this component " +
1579                                                 "as the parent, this=" + this.toDebugString() + " child=" + child.toDebugString() +
1580                                                 " child.parent=" + (child.parent == null ? "null" : child.parent.toDebugString()));
1581                         }
1582                 }
1583         }
1584         
1585         // Check whether the list contains exactly the searched-for component (with == operator)
1586         private boolean containsExact(List<RocketComponent> haystack, RocketComponent needle) {
1587                 for (RocketComponent c : haystack) {
1588                         if (needle == c) {
1589                                 return true;
1590                         }
1591                 }
1592                 return false;
1593         }
1594         
1595         
1596         ///////////  Iterators  //////////
1597         
1598         /**
1599          * Returns an iterator that iterates over all children and sub-children.
1600          * <p>
1601          * The iterator iterates through all children below this object, including itself if
1602          * <code>returnSelf</code> is true.  The order of the iteration is not specified
1603          * (it may be specified in the future).
1604          * <p>
1605          * If an iterator iterating over only the direct children of the component is required,
1606          * use <code>component.getChildren().iterator()</code>.
1607          *
1608          * TODO: HIGH: Remove this after merges have been done
1609          *
1610          * @param returnSelf boolean value specifying whether the component itself should be
1611          *                                       returned
1612          * @return An iterator for the children and sub-children.
1613          * @deprecated Use {@link #iterator(boolean)} instead
1614          */
1615         @Deprecated
1616         public final Iterator<RocketComponent> deepIterator(boolean returnSelf) {
1617                 return iterator(returnSelf);
1618         }
1619         
1620         
1621         /**
1622          * Returns an iterator that iterates over all children and sub-children, including itself.
1623          * <p>
1624          * This method is equivalent to <code>deepIterator(true)</code>.
1625          *
1626          * TODO: HIGH: Remove this after merges have been done
1627          *
1628          * @return An iterator for this component, its children and sub-children.
1629          * @deprecated Use {@link #iterator()} instead
1630          */
1631         @Deprecated
1632         public final Iterator<RocketComponent> deepIterator() {
1633                 return iterator();
1634         }
1635         
1636         
1637         
1638         /**
1639          * Returns an iterator that iterates over all children and sub-children.
1640          * <p>
1641          * The iterator iterates through all children below this object, including itself if
1642          * <code>returnSelf</code> is true.  The order of the iteration is not specified
1643          * (it may be specified in the future).
1644          * <p>
1645          * If an iterator iterating over only the direct children of the component is required,
1646          * use <code>component.getChildren().iterator()</code>.
1647          *
1648          * @param returnSelf boolean value specifying whether the component itself should be
1649          *                                       returned
1650          * @return An iterator for the children and sub-children.
1651          */
1652         public final Iterator<RocketComponent> iterator(boolean returnSelf) {
1653                 checkState();
1654                 return new RocketComponentIterator(this, returnSelf);
1655         }
1656         
1657         
1658         /**
1659          * Returns an iterator that iterates over this component, its children and sub-children.
1660          * <p>
1661          * This method is equivalent to <code>iterator(true)</code>.
1662          *
1663          * @return An iterator for this component, its children and sub-children.
1664          */
1665         @Override
1666         public final Iterator<RocketComponent> iterator() {
1667                 return iterator(true);
1668         }
1669         
1670         
1671         
1672         
1673         
1674         /**
1675          * Compare component equality based on the ID of this component.  Only the
1676          * ID and class type is used for a basis of comparison.
1677          */
1678         @Override
1679         public boolean equals(Object obj) {
1680                 if (this == obj)
1681                         return true;
1682                 if (obj == null)
1683                         return false;
1684                 if (this.getClass() != obj.getClass())
1685                         return false;
1686                 RocketComponent other = (RocketComponent) obj;
1687                 return this.id.equals(other.id);
1688         }
1689         
1690         
1691         
1692         @Override
1693         public int hashCode() {
1694                 return id.hashCode();
1695         }
1696         
1697         
1698         
1699         ////////////  Helper methods for subclasses
1700         
1701         
1702         
1703         
1704         /**
1705          * Helper method to add rotationally symmetric bounds at the specified coordinates.
1706          * The X-axis value is <code>x</code> and the radius at the specified position is
1707          * <code>r</code>.
1708          */
1709         protected static final void addBound(Collection<Coordinate> bounds, double x, double r) {
1710                 bounds.add(new Coordinate(x, -r, -r));
1711                 bounds.add(new Coordinate(x, r, -r));
1712                 bounds.add(new Coordinate(x, r, r));
1713                 bounds.add(new Coordinate(x, -r, r));
1714         }
1715         
1716         
1717         protected static final Coordinate ringCG(double outerRadius, double innerRadius,
1718                         double x1, double x2, double density) {
1719                 return new Coordinate((x1 + x2) / 2, 0, 0,
1720                                 ringMass(outerRadius, innerRadius, x2 - x1, density));
1721         }
1722         
1723         protected static final double ringMass(double outerRadius, double innerRadius,
1724                         double length, double density) {
1725                 return Math.PI * (MathUtil.pow2(outerRadius) - MathUtil.pow2(innerRadius)) *
1726                                 length * density;
1727         }
1728         
1729         protected static final double ringLongitudinalUnitInertia(double outerRadius,
1730                         double innerRadius, double length) {
1731                 // 1/12 * (3 * (r1^2 + r2^2) + h^2)
1732                 return (3 * (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) + MathUtil.pow2(length)) / 12;
1733         }
1734         
1735         protected static final double ringRotationalUnitInertia(double outerRadius,
1736                         double innerRadius) {
1737                 // 1/2 * (r1^2 + r2^2)
1738                 return (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) / 2;
1739         }
1740         
1741         
1742         
1743         ////////////  OTHER
1744         
1745         
1746         /**
1747          * Loads the RocketComponent fields from the given component.  This method is meant
1748          * for in-place replacement of a component.  It is used with the undo/redo
1749          * mechanism and when converting a finset into a freeform fin set.
1750          * This component must not have a parent, otherwise this method will fail.
1751          * <p>
1752          * The child components in the source tree are copied into the current tree, however,
1753          * the original components should not be used since they represent old copies of the
1754          * components.  It is recommended to invalidate them by calling {@link #invalidate()}.
1755          * <p>
1756          * This method returns a list of components that should be invalidated after references
1757          * to them have been removed (for example by firing appropriate events).  The list contains
1758          * all children and sub-children of the current component and the entire component
1759          * tree of <code>src</code>.
1760          *
1761          * @return      a list of components that should not be used after this call.
1762          */
1763         protected List<RocketComponent> copyFrom(RocketComponent src) {
1764                 checkState();
1765                 List<RocketComponent> toInvalidate = new ArrayList<RocketComponent>();
1766                 
1767                 if (this.parent != null) {
1768                         throw new UnsupportedOperationException("copyFrom called for non-root component, parent=" +
1769                                         this.parent.toDebugString() + ", this=" + this.toDebugString());
1770                 }
1771                 
1772                 // Add current structure to be invalidated
1773                 Iterator<RocketComponent> iterator = this.iterator(false);
1774                 while (iterator.hasNext()) {
1775                         toInvalidate.add(iterator.next());
1776                 }
1777                 
1778                 // Remove previous components
1779                 for (RocketComponent child : this.children) {
1780                         child.parent = null;
1781                 }
1782                 this.children.clear();
1783                 
1784                 // Copy new children to this component
1785                 for (RocketComponent c : src.children) {
1786                         RocketComponent copy = c.copyWithOriginalID();
1787                         this.children.add(copy);
1788                         copy.parent = this;
1789                 }
1790                 
1791                 this.checkComponentStructure();
1792                 src.checkComponentStructure();
1793                 
1794                 // Set all parameters
1795                 this.length = src.length;
1796                 this.relativePosition = src.relativePosition;
1797                 this.position = src.position;
1798                 this.color = src.color;
1799                 this.lineStyle = src.lineStyle;
1800                 this.overrideMass = src.overrideMass;
1801                 this.massOverriden = src.massOverriden;
1802                 this.overrideCGX = src.overrideCGX;
1803                 this.cgOverriden = src.cgOverriden;
1804                 this.overrideSubcomponents = src.overrideSubcomponents;
1805                 this.name = src.name;
1806                 this.comment = src.comment;
1807                 this.id = src.id;
1808                 
1809                 // Add source components to invalidation tree
1810                 for (RocketComponent c : src) {
1811                         toInvalidate.add(c);
1812                 }
1813                 
1814                 return toInvalidate;
1815         }
1816         
1817         protected void invalidate() {
1818                 invalidator.invalidate();
1819         }
1820         
1821         
1822         //////////  Iterator implementation  ///////////
1823         
1824         /**
1825          * Private inner class to implement the Iterator.
1826          *
1827          * This iterator is fail-fast if the root of the structure is a Rocket.
1828          */
1829         private static class RocketComponentIterator implements Iterator<RocketComponent> {
1830                 // Stack holds iterators which still have some components left.
1831                 private final Deque<Iterator<RocketComponent>> iteratorStack = new ArrayDeque<Iterator<RocketComponent>>();
1832                 
1833                 private final Rocket root;
1834                 private final int treeModID;
1835                 
1836                 private final RocketComponent original;
1837                 private boolean returnSelf = false;
1838                 
1839                 // Construct iterator with component's child's iterator, if it has elements
1840                 public RocketComponentIterator(RocketComponent c, boolean returnSelf) {
1841                         
1842                         RocketComponent gp = c.getRoot();
1843                         if (gp instanceof Rocket) {
1844                                 root = (Rocket) gp;
1845                                 treeModID = root.getTreeModID();
1846                         } else {
1847                                 root = null;
1848                                 treeModID = -1;
1849                         }
1850                         
1851                         Iterator<RocketComponent> i = c.children.iterator();
1852                         if (i.hasNext())
1853                                 iteratorStack.push(i);
1854                         
1855                         this.original = c;
1856                         this.returnSelf = returnSelf;
1857                 }
1858                 
1859                 @Override
1860                 public boolean hasNext() {
1861                         checkID();
1862                         if (returnSelf)
1863                                 return true;
1864                         return !iteratorStack.isEmpty(); // Elements remain if stack is not empty
1865                 }
1866                 
1867                 @Override
1868                 public RocketComponent next() {
1869                         Iterator<RocketComponent> i;
1870                         
1871                         checkID();
1872                         
1873                         // Return original component first
1874                         if (returnSelf) {
1875                                 returnSelf = false;
1876                                 return original;
1877                         }
1878                         
1879                         // Peek first iterator from stack, throw exception if empty
1880                         i = iteratorStack.peek();
1881                         if (i == null) {
1882                                 throw new NoSuchElementException("No further elements in RocketComponent iterator");
1883                         }
1884                         
1885                         // Retrieve next component of the iterator, remove iterator from stack if empty
1886                         RocketComponent c = i.next();
1887                         if (!i.hasNext())
1888                                 iteratorStack.pop();
1889                         
1890                         // Add iterator of component children to stack if it has children
1891                         i = c.children.iterator();
1892                         if (i.hasNext())
1893                                 iteratorStack.push(i);
1894                         
1895                         return c;
1896                 }
1897                 
1898                 private void checkID() {
1899                         if (root != null) {
1900                                 if (root.getTreeModID() != treeModID) {
1901                                         throw new IllegalStateException("Rocket modified while being iterated");
1902                                 }
1903                         }
1904                 }
1905                 
1906                 @Override
1907                 public void remove() {
1908                         throw new UnsupportedOperationException("remove() not supported by " +
1909                                         "RocketComponent iterator");
1910                 }
1911         }
1912         
1913 }