I18 changes
[debian/openrocket] / src / net / sf / openrocket / rocketcomponent / Rocket.java
1 package net.sf.openrocket.rocketcomponent;
2
3 import java.util.Collection;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.Iterator;
7 import java.util.LinkedList;
8 import java.util.List;
9 import java.util.UUID;
10
11 import javax.swing.event.ChangeListener;
12 import javax.swing.event.EventListenerList;
13
14 import net.sf.openrocket.gui.main.ExceptionHandler;
15 import net.sf.openrocket.l10n.Translator;
16 import net.sf.openrocket.logging.LogHelper;
17 import net.sf.openrocket.motor.Motor;
18 import net.sf.openrocket.startup.Application;
19 import net.sf.openrocket.util.ArrayList;
20 import net.sf.openrocket.util.Chars;
21 import net.sf.openrocket.util.Coordinate;
22 import net.sf.openrocket.util.MathUtil;
23 import net.sf.openrocket.util.UniqueID;
24
25
26 /**
27  * Base for all rocket components.  This is the "starting point" for all rocket trees.
28  * It provides the actual implementations of several methods defined in RocketComponent
29  * (eg. the rocket listener lists) and the methods defined in RocketComponent call these.
30  * It also defines some other methods that concern the whole rocket, and helper methods
31  * that keep information about the program state.
32  * 
33  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
34  */
35
36 public class Rocket extends RocketComponent {
37         private static final LogHelper log = Application.getLogger();
38         private static final Translator trans = Application.getTranslator();
39         
40         public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
41         
42
43         /**
44          * List of component change listeners.
45          */
46         private EventListenerList listenerList = new EventListenerList();
47         
48         /**
49          * When freezeList != null, events are not dispatched but stored in the list.
50          * When the structure is thawed, a single combined event will be fired.
51          */
52         private List<ComponentChangeEvent> freezeList = null;
53         
54
55         private int modID;
56         private int massModID;
57         private int aeroModID;
58         private int treeModID;
59         private int functionalModID;
60         
61
62         private ReferenceType refType = ReferenceType.MAXIMUM; // Set in constructor
63         private double customReferenceLength = DEFAULT_REFERENCE_LENGTH;
64         
65
66         // The default configuration used in dialogs
67         private final Configuration defaultConfiguration;
68         
69
70         private String designer = "";
71         private String revision = "";
72         
73
74         // Motor configuration list
75         private ArrayList<String> motorConfigurationIDs = new ArrayList<String>();
76         private HashMap<String, String> motorConfigurationNames = new HashMap<String, String>();
77         {
78                 motorConfigurationIDs.add(null);
79         }
80         
81
82         // Does the rocket have a perfect finish (a notable amount of laminar flow)
83         private boolean perfectFinish = false;
84         
85         
86
87         /////////////  Constructor  /////////////
88         
89         public Rocket() {
90                 super(RocketComponent.Position.AFTER);
91                 modID = UniqueID.next();
92                 massModID = modID;
93                 aeroModID = modID;
94                 treeModID = modID;
95                 functionalModID = modID;
96                 defaultConfiguration = new Configuration(this);
97         }
98         
99         
100
101         public String getDesigner() {
102                 checkState();
103                 return designer;
104         }
105         
106         public void setDesigner(String s) {
107                 if (s == null)
108                         s = "";
109                 designer = s;
110                 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
111         }
112         
113         
114         public String getRevision() {
115                 checkState();
116                 return revision;
117         }
118         
119         public void setRevision(String s) {
120                 if (s == null)
121                         s = "";
122                 revision = s;
123                 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
124         }
125         
126         
127
128
129         /**
130          * Return the number of stages in this rocket.
131          * 
132          * @return   the number of stages in this rocket.
133          */
134         public int getStageCount() {
135                 checkState();
136                 return this.getChildCount();
137         }
138         
139         
140         /**
141          * Return the non-negative modification ID of this rocket.  The ID is changed
142          * every time any change occurs in the rocket.  This can be used to check 
143          * whether it is necessary to void cached data in cases where listeners can not
144          * or should not be used.
145          * <p>
146          * Three other modification IDs are also available, {@link #getMassModID()},
147          * {@link #getAerodynamicModID()} {@link #getTreeModID()}, which change every time 
148          * a mass change, aerodynamic change, or tree change occur.  Even though the values 
149          * of the different modification ID's may be equal, they should be treated totally 
150          * separate.
151          * <p>
152          * Note that undo events restore the modification IDs that were in use at the
153          * corresponding undo level.  Subsequent modifications, however, produce modIDs
154          * distinct from those already used.
155          * 
156          * @return   a unique ID number for this modification state.
157          */
158         public int getModID() {
159                 return modID;
160         }
161         
162         /**
163          * Return the non-negative mass modification ID of this rocket.  See
164          * {@link #getModID()} for details.
165          * 
166          * @return   a unique ID number for this mass-modification state.
167          */
168         public int getMassModID() {
169                 return massModID;
170         }
171         
172         /**
173          * Return the non-negative aerodynamic modification ID of this rocket.  See
174          * {@link #getModID()} for details.
175          * 
176          * @return   a unique ID number for this aerodynamic-modification state.
177          */
178         public int getAerodynamicModID() {
179                 return aeroModID;
180         }
181         
182         /**
183          * Return the non-negative tree modification ID of this rocket.  See
184          * {@link #getModID()} for details.
185          * 
186          * @return   a unique ID number for this tree-modification state.
187          */
188         public int getTreeModID() {
189                 return treeModID;
190         }
191         
192         /**
193          * Return the non-negative functional modificationID of this rocket.
194          * This changes every time a functional change occurs.
195          * 
196          * @return      a unique ID number for this functional modification state.
197          */
198         public int getFunctionalModID() {
199                 return functionalModID;
200         }
201         
202         
203
204
205         public ReferenceType getReferenceType() {
206                 checkState();
207                 return refType;
208         }
209         
210         public void setReferenceType(ReferenceType type) {
211                 if (refType == type)
212                         return;
213                 refType = type;
214                 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
215         }
216         
217         
218         public double getCustomReferenceLength() {
219                 checkState();
220                 return customReferenceLength;
221         }
222         
223         public void setCustomReferenceLength(double length) {
224                 if (MathUtil.equals(customReferenceLength, length))
225                         return;
226                 
227                 this.customReferenceLength = Math.max(length, 0.001);
228                 
229                 if (refType == ReferenceType.CUSTOM) {
230                         fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
231                 }
232         }
233         
234         
235
236
237
238         /**
239          * Set whether the rocket has a perfect finish.  This will affect whether the
240          * boundary layer is assumed to be fully turbulent or not.
241          * 
242          * @param perfectFinish         whether the finish is perfect.
243          */
244         public void setPerfectFinish(boolean perfectFinish) {
245                 if (this.perfectFinish == perfectFinish)
246                         return;
247                 this.perfectFinish = perfectFinish;
248                 fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
249         }
250         
251         
252
253         /**
254          * Get whether the rocket has a perfect finish.
255          * 
256          * @return the perfectFinish
257          */
258         public boolean isPerfectFinish() {
259                 return perfectFinish;
260         }
261         
262         
263
264
265
266         /**
267          * Make a deep copy of the Rocket structure.  This method is exposed as public to allow
268          * for undo/redo system functionality.
269          */
270         @SuppressWarnings("unchecked")
271         @Override
272         public Rocket copyWithOriginalID() {
273                 Rocket copy = (Rocket) super.copyWithOriginalID();
274                 copy.motorConfigurationIDs = this.motorConfigurationIDs.clone();
275                 copy.motorConfigurationNames =
276                                 (HashMap<String, String>) this.motorConfigurationNames.clone();
277                 copy.resetListeners();
278                 
279                 return copy;
280         }
281         
282         /**
283          * Load the rocket structure from the source.  The method loads the fields of this
284          * Rocket object and copies the references to siblings from the <code>source</code>.
285          * The object <code>source</code> should not be used after this call, as it is in
286          * an illegal state!
287          * <p>
288          * This method is meant to be used in conjunction with undo/redo functionality,
289          * and therefore fires an UNDO_EVENT, masked with all applicable mass/aerodynamic/tree
290          * changes.
291          */
292         @SuppressWarnings("unchecked")
293         public void loadFrom(Rocket r) {
294                 
295                 // Store list of components to invalidate after event has been fired
296                 List<RocketComponent> toInvalidate = this.copyFrom(r);
297                 
298                 int type = ComponentChangeEvent.UNDO_CHANGE | ComponentChangeEvent.NONFUNCTIONAL_CHANGE;
299                 if (this.massModID != r.massModID)
300                         type |= ComponentChangeEvent.MASS_CHANGE;
301                 if (this.aeroModID != r.aeroModID)
302                         type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
303                 // Loading a rocket is always a tree change since the component objects change
304                 type |= ComponentChangeEvent.TREE_CHANGE;
305                 
306                 this.modID = r.modID;
307                 this.massModID = r.massModID;
308                 this.aeroModID = r.aeroModID;
309                 this.treeModID = r.treeModID;
310                 this.functionalModID = r.functionalModID;
311                 this.refType = r.refType;
312                 this.customReferenceLength = r.customReferenceLength;
313                 
314                 this.motorConfigurationIDs = r.motorConfigurationIDs.clone();
315                 this.motorConfigurationNames =
316                                 (HashMap<String, String>) r.motorConfigurationNames.clone();
317                 this.perfectFinish = r.perfectFinish;
318                 
319                 String id = defaultConfiguration.getMotorConfigurationID();
320                 if (!this.motorConfigurationIDs.contains(id))
321                         defaultConfiguration.setMotorConfigurationID(null);
322                 
323                 this.checkComponentStructure();
324                 
325                 fireComponentChangeEvent(type);
326                 
327                 // Invalidate obsolete components after event
328                 for (RocketComponent c : toInvalidate) {
329                         c.invalidate();
330                 }
331         }
332         
333         
334
335
336         ///////  Implement the ComponentChangeListener lists
337         
338         /**
339          * Creates a new EventListenerList for this component.  This is necessary when cloning
340          * the structure.
341          */
342         public void resetListeners() {
343                 //              System.out.println("RESETTING LISTENER LIST of Rocket "+this);
344                 listenerList = new EventListenerList();
345         }
346         
347         
348         public void printListeners() {
349                 System.out.println("" + this + " has " + listenerList.getListenerCount() + " listeners:");
350                 Object[] list = listenerList.getListenerList();
351                 for (int i = 1; i < list.length; i += 2)
352                         System.out.println("  " + ((i + 1) / 2) + ": " + list[i]);
353         }
354         
355         @Override
356         public void addComponentChangeListener(ComponentChangeListener l) {
357                 checkState();
358                 listenerList.add(ComponentChangeListener.class, l);
359                 log.verbose("Added ComponentChangeListener " + l + ", current number of listeners is " +
360                                 listenerList.getListenerCount());
361         }
362         
363         @Override
364         public void removeComponentChangeListener(ComponentChangeListener l) {
365                 listenerList.remove(ComponentChangeListener.class, l);
366                 log.verbose("Removed ComponentChangeListener " + l + ", current number of listeners is " +
367                                 listenerList.getListenerCount());
368         }
369         
370         
371         @Override
372         public void addChangeListener(ChangeListener l) {
373                 checkState();
374                 listenerList.add(ChangeListener.class, l);
375                 log.verbose("Added ChangeListener " + l + ", current number of listeners is " +
376                                 listenerList.getListenerCount());
377         }
378         
379         @Override
380         public void removeChangeListener(ChangeListener l) {
381                 listenerList.remove(ChangeListener.class, l);
382                 log.verbose("Removed ChangeListener " + l + ", current number of listeners is " +
383                                 listenerList.getListenerCount());
384         }
385         
386         
387         @Override
388         protected void fireComponentChangeEvent(ComponentChangeEvent e) {
389                 mutex.lock("fireComponentChangeEvent");
390                 try {
391                         checkState();
392                         
393                         // Update modification ID's only for normal (not undo/redo) events
394                         if (!e.isUndoChange()) {
395                                 modID = UniqueID.next();
396                                 if (e.isMassChange())
397                                         massModID = modID;
398                                 if (e.isAerodynamicChange())
399                                         aeroModID = modID;
400                                 if (e.isTreeChange())
401                                         treeModID = modID;
402                                 if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
403                                         functionalModID = modID;
404                         }
405                         
406                         // Check whether frozen
407                         if (freezeList != null) {
408                                 log.debug("Rocket is in frozen state, adding event " + e + " info freeze list");
409                                 freezeList.add(e);
410                                 return;
411                         }
412                         
413                         log.debug("Firing rocket change event " + e);
414                         
415                         // Notify all components first
416                         Iterator<RocketComponent> iterator = this.iterator(true);
417                         while (iterator.hasNext()) {
418                                 iterator.next().componentChanged(e);
419                         }
420                         
421                         // Notify all listeners
422                         Object[] listeners = listenerList.getListenerList();
423                         for (int i = listeners.length - 2; i >= 0; i -= 2) {
424                                 if (listeners[i] == ComponentChangeListener.class) {
425                                         ((ComponentChangeListener) listeners[i + 1]).componentChanged(e);
426                                 } else if (listeners[i] == ChangeListener.class) {
427                                         ((ChangeListener) listeners[i + 1]).stateChanged(e);
428                                 }
429                         }
430                 } finally {
431                         mutex.unlock("fireComponentChangeEvent");
432                 }
433         }
434         
435         
436         /**
437          * Freezes the rocket structure from firing any events.  This may be performed to
438          * combine several actions on the structure into a single large action.
439          * <code>thaw()</code> must always be called afterwards.
440          * 
441          * NOTE:  Always use a try/finally to ensure <code>thaw()</code> is called:
442          * <pre>
443          *     Rocket r = c.getRocket();
444          *     try {
445          *         r.freeze();
446          *         // do stuff
447          *     } finally {
448          *         r.thaw();
449          *     }
450          * </pre>
451          * 
452          * @see #thaw()
453          */
454         public void freeze() {
455                 checkState();
456                 if (freezeList == null) {
457                         freezeList = new LinkedList<ComponentChangeEvent>();
458                         log.debug("Freezing Rocket");
459                 } else {
460                         ExceptionHandler.handleErrorCondition("Attempting to freeze Rocket when it is already frozen, " +
461                                         "freezeList=" + freezeList);
462                 }
463         }
464         
465         /**
466          * Thaws a frozen rocket structure and fires a combination of the events fired during
467          * the freeze.  The event type is a combination of those fired and the source is the
468          * last component to have been an event source.
469          *
470          * @see #freeze()
471          */
472         public void thaw() {
473                 checkState();
474                 if (freezeList == null) {
475                         ExceptionHandler.handleErrorCondition("Attempting to thaw Rocket when it is not frozen");
476                         return;
477                 }
478                 if (freezeList.size() == 0) {
479                         log.warn("Thawing rocket with no changes made");
480                         freezeList = null;
481                         return;
482                 }
483                 
484                 log.debug("Thawing rocket, freezeList=" + freezeList);
485                 
486                 int type = 0;
487                 Object c = null;
488                 for (ComponentChangeEvent e : freezeList) {
489                         type = type | e.getType();
490                         c = e.getSource();
491                 }
492                 freezeList = null;
493                 
494                 fireComponentChangeEvent(new ComponentChangeEvent((RocketComponent) c, type));
495         }
496         
497         
498
499
500         ////////  Motor configurations  ////////
501         
502
503         /**
504          * Return the default configuration.  This should be used in the user interface
505          * to ensure a consistent rocket configuration between dialogs.  It should NOT
506          * be used in simulations not relating to the UI.
507          * 
508          * @return   the default {@link Configuration}.
509          */
510         public Configuration getDefaultConfiguration() {
511                 checkState();
512                 return defaultConfiguration;
513         }
514         
515         
516         /**
517          * Return an array of the motor configuration IDs.  This array is guaranteed
518          * to contain the <code>null</code> ID as the first element.
519          * 
520          * @return  an array of the motor configuration IDs.
521          */
522         public String[] getMotorConfigurationIDs() {
523                 checkState();
524                 return motorConfigurationIDs.toArray(new String[0]);
525         }
526         
527         /**
528          * Add a new motor configuration ID to the motor configurations.  The new ID
529          * is returned.
530          * 
531          * @return  the new motor configuration ID.
532          */
533         public String newMotorConfigurationID() {
534                 checkState();
535                 String id = UUID.randomUUID().toString();
536                 motorConfigurationIDs.add(id);
537                 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
538                 return id;
539         }
540         
541         /**
542          * Add a specified motor configuration ID to the motor configurations.
543          * 
544          * @param id    the motor configuration ID.
545          * @return              true if successful, false if the ID was already used.
546          */
547         public boolean addMotorConfigurationID(String id) {
548                 checkState();
549                 if (id == null || motorConfigurationIDs.contains(id))
550                         return false;
551                 motorConfigurationIDs.add(id);
552                 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
553                 return true;
554         }
555         
556         /**
557          * Remove a motor configuration ID from the configuration IDs.  The <code>null</code>
558          * ID cannot be removed, and an attempt to remove it will be silently ignored.
559          * 
560          * @param id   the motor configuration ID to remove
561          */
562         public void removeMotorConfigurationID(String id) {
563                 checkState();
564                 if (id == null)
565                         return;
566                 motorConfigurationIDs.remove(id);
567                 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
568         }
569         
570         
571         /**
572          * Check whether <code>id</code> is a valid motor configuration ID.
573          * 
574          * @param id    the configuration ID.
575          * @return              whether a motor configuration with that ID exists.
576          */
577         public boolean isMotorConfigurationID(String id) {
578                 checkState();
579                 return motorConfigurationIDs.contains(id);
580         }
581         
582         
583
584         /**
585          * Check whether the given motor configuration ID has motors defined for it.
586          * 
587          * @param id    the motor configuration ID (may be invalid).
588          * @return              whether any motors are defined for it.
589          */
590         public boolean hasMotors(String id) {
591                 checkState();
592                 if (id == null)
593                         return false;
594                 
595                 Iterator<RocketComponent> iterator = this.iterator();
596                 while (iterator.hasNext()) {
597                         RocketComponent c = iterator.next();
598                         
599                         if (c instanceof MotorMount) {
600                                 MotorMount mount = (MotorMount) c;
601                                 if (!mount.isMotorMount())
602                                         continue;
603                                 if (mount.getMotor(id) != null) {
604                                         return true;
605                                 }
606                         }
607                 }
608                 return false;
609         }
610         
611         
612         /**
613          * Return the user-set name of the motor configuration.  If no name has been set,
614          * returns an empty string (not null).
615          *  
616          * @param id   the motor configuration id
617          * @return         the configuration name
618          */
619         public String getMotorConfigurationName(String id) {
620                 checkState();
621                 if (!isMotorConfigurationID(id))
622                         return "";
623                 String s = motorConfigurationNames.get(id);
624                 if (s == null)
625                         return "";
626                 return s;
627         }
628         
629         
630         /**
631          * Set the name of the motor configuration.  A name can be unset by passing
632          * <code>null</code> or an empty string.
633          * 
634          * @param id    the motor configuration id
635          * @param name  the name for the motor configuration
636          */
637         public void setMotorConfigurationName(String id, String name) {
638                 checkState();
639                 motorConfigurationNames.put(id, name);
640                 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
641         }
642         
643         
644         /**
645          * Return either the motor configuration name (if set) or its description. 
646          * 
647          * @param id  the motor configuration ID.
648          * @return    a textual representation of the configuration
649          */
650         public String getMotorConfigurationNameOrDescription(String id) {
651                 checkState();
652                 String name;
653                 
654                 name = getMotorConfigurationName(id);
655                 if (name != null && !name.equals(""))
656                         return name;
657                 
658                 return getMotorConfigurationDescription(id);
659         }
660         
661         /**
662          * Return a description for the motor configuration, generated from the motor 
663          * designations of the components.
664          * 
665          * @param id  the motor configuration ID.
666          * @return    a textual representation of the configuration
667          */
668         @SuppressWarnings("null")
669         public String getMotorConfigurationDescription(String id) {
670                 checkState();
671                 String name;
672                 int motorCount = 0;
673                 
674                 // Generate the description
675                 
676                 // First iterate over each stage and store the designations of each motor
677                 List<List<String>> list = new ArrayList<List<String>>();
678                 List<String> currentList = null;
679                 
680                 Iterator<RocketComponent> iterator = this.iterator();
681                 while (iterator.hasNext()) {
682                         RocketComponent c = iterator.next();
683                         
684                         if (c instanceof Stage) {
685                                 
686                                 currentList = new ArrayList<String>();
687                                 list.add(currentList);
688                                 
689                         } else if (c instanceof MotorMount) {
690                                 
691                                 MotorMount mount = (MotorMount) c;
692                                 Motor motor = mount.getMotor(id);
693                                 
694                                 if (mount.isMotorMount() && motor != null) {
695                                         String designation = motor.getDesignation(mount.getMotorDelay(id));
696                                         
697                                         for (int i = 0; i < mount.getMotorCount(); i++) {
698                                                 currentList.add(designation);
699                                                 motorCount++;
700                                         }
701                                 }
702                                 
703                         }
704                 }
705                 
706                 if (motorCount == 0) {
707                         //// [No motors]
708                         return trans.get("Rocket.motorCount.Nomotor");
709                 }
710                 
711                 // Change multiple occurrences of a motor to n x motor 
712                 List<String> stages = new ArrayList<String>();
713                 
714                 for (List<String> stage : list) {
715                         String stageName = "";
716                         String previous = null;
717                         int count = 0;
718                         
719                         Collections.sort(stage);
720                         for (String current : stage) {
721                                 if (current.equals(previous)) {
722                                         
723                                         count++;
724                                         
725                                 } else {
726                                         
727                                         if (previous != null) {
728                                                 String s = "";
729                                                 if (count > 1) {
730                                                         s = "" + count + Chars.TIMES + previous;
731                                                 } else {
732                                                         s = previous;
733                                                 }
734                                                 
735                                                 if (stageName.equals(""))
736                                                         stageName = s;
737                                                 else
738                                                         stageName = stageName + "," + s;
739                                         }
740                                         
741                                         previous = current;
742                                         count = 1;
743                                         
744                                 }
745                         }
746                         if (previous != null) {
747                                 String s = "";
748                                 if (count > 1) {
749                                         s = "" + count + Chars.TIMES + previous;
750                                 } else {
751                                         s = previous;
752                                 }
753                                 
754                                 if (stageName.equals(""))
755                                         stageName = s;
756                                 else
757                                         stageName = stageName + "," + s;
758                         }
759                         
760                         stages.add(stageName);
761                 }
762                 
763                 name = "[";
764                 for (int i = 0; i < stages.size(); i++) {
765                         String s = stages.get(i);
766                         if (s.equals(""))
767                                 s = "None";
768                         if (i == 0)
769                                 name = name + s;
770                         else
771                                 name = name + "; " + s;
772                 }
773                 name += "]";
774                 return name;
775         }
776         
777         
778
779         ////////  Obligatory component information
780         
781
782         @Override
783         public String getComponentName() {
784                 //// Rocket
785                 return trans.get("Rocket.compname.Rocket");
786         }
787         
788         @Override
789         public Coordinate getComponentCG() {
790                 return new Coordinate(0, 0, 0, 0);
791         }
792         
793         @Override
794         public double getComponentMass() {
795                 return 0;
796         }
797         
798         @Override
799         public double getLongitudinalUnitInertia() {
800                 return 0;
801         }
802         
803         @Override
804         public double getRotationalUnitInertia() {
805                 return 0;
806         }
807         
808         @Override
809         public Collection<Coordinate> getComponentBounds() {
810                 return Collections.emptyList();
811         }
812         
813         @Override
814         public boolean isAerodynamic() {
815                 return false;
816         }
817         
818         @Override
819         public boolean isMassive() {
820                 return false;
821         }
822         
823         @Override
824         public boolean allowsChildren() {
825                 return true;
826         }
827         
828         /**
829          * Allows only <code>Stage</code> components to be added to the type Rocket.
830          */
831         @Override
832         public boolean isCompatible(Class<? extends RocketComponent> type) {
833                 return (Stage.class.isAssignableFrom(type));
834         }
835         
836         /**
837          * Accept a visitor to this Rocket in the component hierarchy.
838          * 
839          * @param theVisitor  the visitor that will be called back with a reference to this Rocket
840          */
841         @Override
842         public void accept(final ComponentVisitor theVisitor) {
843                 theVisitor.visit(this);
844         }
845 }