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