1 package net.sf.openrocket.rocketcomponent;
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;
10 import java.util.UUID;
12 import javax.swing.event.ChangeListener;
13 import javax.swing.event.EventListenerList;
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;
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.
28 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
31 public class Rocket extends RocketComponent {
32 public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
34 private static final boolean DEBUG_LISTENERS = false;
38 * The next modification ID to use. This variable may only be accessed via
39 * the synchronized {@link #getNextModID()} method!
41 private static int nextModID = 1;
45 * List of component change listeners.
47 private EventListenerList listenerList = new EventListenerList();
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.
53 private List<ComponentChangeEvent> freezeList = null;
57 private int massModID;
58 private int aeroModID;
59 private int treeModID;
60 private int functionalModID;
63 private ReferenceType refType = ReferenceType.MAXIMUM; // Set in constructor
64 private double customReferenceLength = DEFAULT_REFERENCE_LENGTH;
67 // The default configuration used in dialogs
68 private final Configuration defaultConfiguration;
71 private String designer = "";
72 private String revision = "";
75 // Motor configuration list
76 private ArrayList<String> motorConfigurationIDs = new ArrayList<String>();
77 private HashMap<String, String> motorConfigurationNames = new HashMap<String, String>();
79 motorConfigurationIDs.add(null);
83 // Does the rocket have a perfect finish (a notable amount of laminar flow)
84 private boolean perfectFinish = false;
88 ///////////// Constructor /////////////
91 super(RocketComponent.Position.AFTER);
92 modID = getNextModID();
96 functionalModID = modID;
97 defaultConfiguration = new Configuration(this);
102 public String getDesigner() {
106 public void setDesigner(String s) {
110 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
114 public String getRevision() {
118 public void setRevision(String s) {
122 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
129 * Return the number of stages in this rocket.
131 * @return the number of stages in this rocket.
133 public int getStageCount() {
134 return this.getChildCount();
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.
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
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.
155 * @return a unique ID number for this modification state.
157 public int getModID() {
162 * Return the non-negative mass modification ID of this rocket. See
163 * {@link #getModID()} for details.
165 * @return a unique ID number for this mass-modification state.
167 public int getMassModID() {
172 * Return the non-negative aerodynamic modification ID of this rocket. See
173 * {@link #getModID()} for details.
175 * @return a unique ID number for this aerodynamic-modification state.
177 public int getAerodynamicModID() {
182 * Return the non-negative tree modification ID of this rocket. See
183 * {@link #getModID()} for details.
185 * @return a unique ID number for this tree-modification state.
187 public int getTreeModID() {
192 * Return the non-negative functional modificationID of this rocket.
193 * This changes every time a functional change occurs.
195 * @return a unique ID number for this functional modification state.
197 public int getFunctionalModID() {
198 return functionalModID;
204 public ReferenceType getReferenceType() {
208 public void setReferenceType(ReferenceType type) {
212 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
216 public double getCustomReferenceLength() {
217 return customReferenceLength;
220 public void setCustomReferenceLength(double length) {
221 if (MathUtil.equals(customReferenceLength, length))
224 this.customReferenceLength = Math.max(length,0.001);
226 if (refType == ReferenceType.CUSTOM) {
227 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
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.
239 * @param perfectFinish whether the finish is perfect.
241 public void setPerfectFinish(boolean perfectFinish) {
242 if (this.perfectFinish == perfectFinish)
244 this.perfectFinish = perfectFinish;
245 fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
251 * Get whether the rocket has a perfect finish.
253 * @return the perfectFinish
255 public boolean isPerfectFinish() {
256 return perfectFinish;
262 * Return a new unique modification ID. This method is thread-safe.
264 * @return a new modification ID unique to this session.
266 private synchronized int getNextModID() {
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.
278 @SuppressWarnings("unchecked")
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();
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
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
300 @SuppressWarnings("unchecked")
301 public void loadFrom(Rocket r) {
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;
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;
320 this.motorConfigurationIDs = (ArrayList<String>) r.motorConfigurationIDs.clone();
321 this.motorConfigurationNames =
322 (HashMap<String, String>) r.motorConfigurationNames.clone();
323 this.perfectFinish = r.perfectFinish;
325 String id = defaultConfiguration.getMotorConfigurationID();
326 if (!this.motorConfigurationIDs.contains(id))
327 defaultConfiguration.setMotorConfigurationID(null);
329 fireComponentChangeEvent(type);
335 /////// Implement the ComponentChangeListener lists
338 * Creates a new EventListenerList for this component. This is necessary when cloning
341 public void resetListeners() {
342 // System.out.println("RESETTING LISTENER LIST of Rocket "+this);
343 listenerList = new EventListenerList();
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]);
355 public void addComponentChangeListener(ComponentChangeListener l) {
356 listenerList.add(ComponentChangeListener.class,l);
358 System.out.println(this+": Added listner (now "+listenerList.getListenerCount()+
362 public void removeComponentChangeListener(ComponentChangeListener l) {
363 listenerList.remove(ComponentChangeListener.class, l);
365 System.out.println(this+": Removed listner (now "+listenerList.getListenerCount()+
371 public void addChangeListener(ChangeListener l) {
372 listenerList.add(ChangeListener.class,l);
374 System.out.println(this+": Added listner (now "+listenerList.getListenerCount()+
378 public void removeChangeListener(ChangeListener l) {
379 listenerList.remove(ChangeListener.class, l);
381 System.out.println(this+": Removed listner (now "+listenerList.getListenerCount()+
387 protected void fireComponentChangeEvent(ComponentChangeEvent e) {
389 // Update modification ID's only for normal (not undo/redo) events
390 if (!e.isUndoChange()) {
391 modID = getNextModID();
392 if (e.isMassChange())
394 if (e.isAerodynamicChange())
396 if (e.isTreeChange())
398 if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
399 functionalModID = modID;
403 System.out.println("FIRING "+e);
405 // Check whether frozen
406 if (freezeList != null) {
411 // Notify all components first
412 Iterator<RocketComponent> iterator = this.deepIterator(true);
413 while (iterator.hasNext()) {
414 iterator.next().componentChanged(e);
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);
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.
434 * NOTE: Always use a try/finally to ensure <code>thaw()</code> is called:
436 * Rocket r = c.getRocket();
447 public void freeze() {
448 if (freezeList == null)
449 freezeList = new LinkedList<ComponentChangeEvent>();
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.
460 if (freezeList == null)
462 if (freezeList.size()==0) {
469 for (ComponentChangeEvent e: freezeList) {
470 type = type | e.getType();
475 fireComponentChangeEvent(new ComponentChangeEvent((RocketComponent)c,type));
481 //////// Motor configurations ////////
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.
489 * @return the default {@link Configuration}.
491 public Configuration getDefaultConfiguration() {
492 return defaultConfiguration;
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.
500 * @return an array of the motor configuration IDs.
502 public String[] getMotorConfigurationIDs() {
503 return motorConfigurationIDs.toArray(new String[0]);
507 * Add a new motor configuration ID to the motor configurations. The new ID
510 * @return the new motor configuration ID.
512 public String newMotorConfigurationID() {
513 String id = UUID.randomUUID().toString();
514 motorConfigurationIDs.add(id);
515 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
520 * Add a specified motor configuration ID to the motor configurations.
522 * @param id the motor configuration ID.
523 * @return true if successful, false if the ID was already used.
525 public boolean addMotorConfigurationID(String id) {
526 if (id == null || motorConfigurationIDs.contains(id))
528 motorConfigurationIDs.add(id);
529 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
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.
537 * @param id the motor configuration ID to remove
539 public void removeMotorConfigurationID(String id) {
542 motorConfigurationIDs.remove(id);
543 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
548 * Check whether <code>id</code> is a valid motor configuration ID.
550 * @param id the configuration ID.
551 * @return whether a motor configuration with that ID exists.
553 public boolean isMotorConfigurationID(String id) {
554 return motorConfigurationIDs.contains(id);
560 * Check whether the given motor configuration ID has motors defined for it.
562 * @param id the motor configuration ID (may be invalid).
563 * @return whether any motors are defined for it.
565 public boolean hasMotors(String id) {
569 Iterator<RocketComponent> iterator = this.deepIterator();
570 while (iterator.hasNext()) {
571 RocketComponent c = iterator.next();
573 if (c instanceof MotorMount) {
574 MotorMount mount = (MotorMount) c;
575 if (!mount.isMotorMount())
577 if (mount.getMotor(id) != null) {
587 * Return the user-set name of the motor configuration. If no name has been set,
588 * returns an empty string (not null).
590 * @param id the motor configuration id
591 * @return the configuration name
593 public String getMotorConfigurationName(String id) {
594 if (!isMotorConfigurationID(id))
596 String s = motorConfigurationNames.get(id);
604 * Set the name of the motor configuration. A name can be unset by passing
605 * <code>null</code> or an empty string.
607 * @param id the motor configuration id
608 * @param name the name for the motor configuration
610 public void setMotorConfigurationName(String id, String name) {
611 motorConfigurationNames.put(id,name);
612 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
617 * Return either the motor configuration name (if set) or its description.
619 * @param id the motor configuration ID.
620 * @return a textual representation of the configuration
622 public String getMotorConfigurationNameOrDescription(String id) {
625 name = getMotorConfigurationName(id);
626 if (name != null && !name.equals(""))
629 return getMotorConfigurationDescription(id);
633 * Return a description for the motor configuration, generated from the motor
634 * designations of the components.
636 * @param id the motor configuration ID.
637 * @return a textual representation of the configuration
639 @SuppressWarnings("null")
640 public String getMotorConfigurationDescription(String id) {
644 // Generate the description
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;
650 Iterator<RocketComponent> iterator = this.deepIterator();
651 while (iterator.hasNext()) {
652 RocketComponent c = iterator.next();
654 if (c instanceof Stage) {
656 currentList = new ArrayList<String>();
657 list.add(currentList);
659 } else if (c instanceof MotorMount) {
661 MotorMount mount = (MotorMount) c;
662 Motor motor = mount.getMotor(id);
664 if (mount.isMotorMount() && motor != null) {
665 String designation = motor.getDesignation(mount.getMotorDelay(id));
667 for (int i=0; i < mount.getMotorCount(); i++) {
668 currentList.add(designation);
676 if (motorCount == 0) {
677 return "[No motors]";
680 // Change multiple occurrences of a motor to n x motor
681 List<String> stages = new ArrayList<String>();
683 for (List<String> stage: list) {
684 String stageName = "";
685 String previous = null;
688 Collections.sort(stage);
689 for (String current: stage) {
690 if (current.equals(previous)) {
696 if (previous != null) {
699 s = "" + count + Chars.TIMES + previous;
704 if (stageName.equals(""))
707 stageName = stageName + "," + s;
715 if (previous != null) {
718 s = "" + count + Chars.TIMES + previous;
723 if (stageName.equals(""))
726 stageName = stageName + "," + s;
729 stages.add(stageName);
733 for (int i=0; i < stages.size(); i++) {
734 String s = stages.get(i);
740 name = name + "; " + s;
748 //////// Obligatory component information
752 public String getComponentName() {
757 public Coordinate getComponentCG() {
758 return new Coordinate(0,0,0,0);
762 public double getComponentMass() {
767 public double getLongitudalUnitInertia() {
772 public double getRotationalUnitInertia() {
777 public Collection<Coordinate> getComponentBounds() {
778 return Collections.emptyList();
782 public boolean isAerodynamic() {
787 public boolean isMassive() {
792 * Allows only <code>Stage</code> components to be added to the type Rocket.
795 public boolean isCompatible(Class<? extends RocketComponent> type) {
796 return (Stage.class.isAssignableFrom(type));