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