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