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