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