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