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