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