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