1 package net.sf.openrocket.rocketcomponent;
3 import java.util.Collection;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.Iterator;
7 import java.util.LinkedList;
11 import javax.swing.event.ChangeListener;
12 import javax.swing.event.EventListenerList;
14 import net.sf.openrocket.gui.main.ExceptionHandler;
15 import net.sf.openrocket.logging.LogHelper;
16 import net.sf.openrocket.motor.Motor;
17 import net.sf.openrocket.startup.Application;
18 import net.sf.openrocket.util.ArrayList;
19 import net.sf.openrocket.util.Chars;
20 import net.sf.openrocket.util.Coordinate;
21 import net.sf.openrocket.util.MathUtil;
22 import net.sf.openrocket.util.UniqueID;
26 * Base for all rocket components. This is the "starting point" for all rocket trees.
27 * It provides the actual implementations of several methods defined in RocketComponent
28 * (eg. the rocket listener lists) and the methods defined in RocketComponent call these.
29 * It also defines some other methods that concern the whole rocket, and helper methods
30 * that keep information about the program state.
32 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
35 public class Rocket extends RocketComponent {
36 private static final LogHelper log = Application.getLogger();
38 public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
42 * List of component change listeners.
44 private EventListenerList listenerList = new EventListenerList();
47 * When freezeList != null, events are not dispatched but stored in the list.
48 * When the structure is thawed, a single combined event will be fired.
50 private List<ComponentChangeEvent> freezeList = null;
54 private int massModID;
55 private int aeroModID;
56 private int treeModID;
57 private int functionalModID;
60 private ReferenceType refType = ReferenceType.MAXIMUM; // Set in constructor
61 private double customReferenceLength = DEFAULT_REFERENCE_LENGTH;
64 // The default configuration used in dialogs
65 private final Configuration defaultConfiguration;
68 private String designer = "";
69 private String revision = "";
72 // Motor configuration list
73 private ArrayList<String> motorConfigurationIDs = new ArrayList<String>();
74 private HashMap<String, String> motorConfigurationNames = new HashMap<String, String>();
76 motorConfigurationIDs.add(null);
80 // Does the rocket have a perfect finish (a notable amount of laminar flow)
81 private boolean perfectFinish = false;
85 ///////////// Constructor /////////////
88 super(RocketComponent.Position.AFTER);
89 modID = UniqueID.next();
93 functionalModID = modID;
94 defaultConfiguration = new Configuration(this);
99 public String getDesigner() {
104 public void setDesigner(String s) {
108 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
112 public String getRevision() {
117 public void setRevision(String s) {
121 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
128 * Return the number of stages in this rocket.
130 * @return the number of stages in this rocket.
132 public int getStageCount() {
134 return this.getChildCount();
139 * Return the non-negative modification ID of this rocket. The ID is changed
140 * every time any change occurs in the rocket. This can be used to check
141 * whether it is necessary to void cached data in cases where listeners can not
142 * or should not be used.
144 * Three other modification IDs are also available, {@link #getMassModID()},
145 * {@link #getAerodynamicModID()} {@link #getTreeModID()}, which change every time
146 * a mass change, aerodynamic change, or tree change occur. Even though the values
147 * of the different modification ID's may be equal, they should be treated totally
150 * Note that undo events restore the modification IDs that were in use at the
151 * corresponding undo level. Subsequent modifications, however, produce modIDs
152 * distinct from those already used.
154 * @return a unique ID number for this modification state.
156 public int getModID() {
161 * Return the non-negative mass modification ID of this rocket. See
162 * {@link #getModID()} for details.
164 * @return a unique ID number for this mass-modification state.
166 public int getMassModID() {
171 * Return the non-negative aerodynamic modification ID of this rocket. See
172 * {@link #getModID()} for details.
174 * @return a unique ID number for this aerodynamic-modification state.
176 public int getAerodynamicModID() {
181 * Return the non-negative tree modification ID of this rocket. See
182 * {@link #getModID()} for details.
184 * @return a unique ID number for this tree-modification state.
186 public int getTreeModID() {
191 * Return the non-negative functional modificationID of this rocket.
192 * This changes every time a functional change occurs.
194 * @return a unique ID number for this functional modification state.
196 public int getFunctionalModID() {
197 return functionalModID;
203 public ReferenceType getReferenceType() {
208 public void setReferenceType(ReferenceType type) {
212 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
216 public double getCustomReferenceLength() {
218 return customReferenceLength;
221 public void setCustomReferenceLength(double length) {
222 if (MathUtil.equals(customReferenceLength, length))
225 this.customReferenceLength = Math.max(length, 0.001);
227 if (refType == ReferenceType.CUSTOM) {
228 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
237 * Set whether the rocket has a perfect finish. This will affect whether the
238 * boundary layer is assumed to be fully turbulent or not.
240 * @param perfectFinish whether the finish is perfect.
242 public void setPerfectFinish(boolean perfectFinish) {
243 if (this.perfectFinish == perfectFinish)
245 this.perfectFinish = perfectFinish;
246 fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
252 * Get whether the rocket has a perfect finish.
254 * @return the perfectFinish
256 public boolean isPerfectFinish() {
257 return perfectFinish;
265 * Make a deep copy of the Rocket structure. This method is exposed as public to allow
266 * for undo/redo system functionality.
268 @SuppressWarnings("unchecked")
270 public Rocket copyWithOriginalID() {
271 Rocket copy = (Rocket) super.copyWithOriginalID();
272 copy.motorConfigurationIDs = this.motorConfigurationIDs.clone();
273 copy.motorConfigurationNames =
274 (HashMap<String, String>) this.motorConfigurationNames.clone();
275 copy.resetListeners();
281 * Load the rocket structure from the source. The method loads the fields of this
282 * Rocket object and copies the references to siblings from the <code>source</code>.
283 * The object <code>source</code> should not be used after this call, as it is in
286 * This method is meant to be used in conjunction with undo/redo functionality,
287 * and therefore fires an UNDO_EVENT, masked with all applicable mass/aerodynamic/tree
290 @SuppressWarnings("unchecked")
291 public void loadFrom(Rocket r) {
293 // Store list of components to invalidate after event has been fired
294 List<RocketComponent> toInvalidate = this.copyFrom(r);
296 int type = ComponentChangeEvent.UNDO_CHANGE | ComponentChangeEvent.NONFUNCTIONAL_CHANGE;
297 if (this.massModID != r.massModID)
298 type |= ComponentChangeEvent.MASS_CHANGE;
299 if (this.aeroModID != r.aeroModID)
300 type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
301 // Loading a rocket is always a tree change since the component objects change
302 type |= ComponentChangeEvent.TREE_CHANGE;
304 this.modID = r.modID;
305 this.massModID = r.massModID;
306 this.aeroModID = r.aeroModID;
307 this.treeModID = r.treeModID;
308 this.functionalModID = r.functionalModID;
309 this.refType = r.refType;
310 this.customReferenceLength = r.customReferenceLength;
312 this.motorConfigurationIDs = r.motorConfigurationIDs.clone();
313 this.motorConfigurationNames =
314 (HashMap<String, String>) r.motorConfigurationNames.clone();
315 this.perfectFinish = r.perfectFinish;
317 String id = defaultConfiguration.getMotorConfigurationID();
318 if (!this.motorConfigurationIDs.contains(id))
319 defaultConfiguration.setMotorConfigurationID(null);
321 this.checkComponentStructure();
323 fireComponentChangeEvent(type);
325 // Invalidate obsolete components after event
326 for (RocketComponent c : toInvalidate) {
334 /////// Implement the ComponentChangeListener lists
337 * Creates a new EventListenerList for this component. This is necessary when cloning
340 public void resetListeners() {
341 // System.out.println("RESETTING LISTENER LIST of Rocket "+this);
342 listenerList = new EventListenerList();
346 public void printListeners() {
347 System.out.println("" + this + " has " + listenerList.getListenerCount() + " listeners:");
348 Object[] list = listenerList.getListenerList();
349 for (int i = 1; i < list.length; i += 2)
350 System.out.println(" " + ((i + 1) / 2) + ": " + list[i]);
354 public void addComponentChangeListener(ComponentChangeListener l) {
356 listenerList.add(ComponentChangeListener.class, l);
357 log.verbose("Added ComponentChangeListener " + l + ", current number of listeners is " +
358 listenerList.getListenerCount());
362 public void removeComponentChangeListener(ComponentChangeListener l) {
363 listenerList.remove(ComponentChangeListener.class, l);
364 log.verbose("Removed ComponentChangeListener " + l + ", current number of listeners is " +
365 listenerList.getListenerCount());
370 public void addChangeListener(ChangeListener l) {
372 listenerList.add(ChangeListener.class, l);
373 log.verbose("Added ChangeListener " + l + ", current number of listeners is " +
374 listenerList.getListenerCount());
378 public void removeChangeListener(ChangeListener l) {
379 listenerList.remove(ChangeListener.class, l);
380 log.verbose("Removed ChangeListener " + l + ", current number of listeners is " +
381 listenerList.getListenerCount());
386 protected void fireComponentChangeEvent(ComponentChangeEvent e) {
387 mutex.lock("fireComponentChangeEvent");
391 // Update modification ID's only for normal (not undo/redo) events
392 if (!e.isUndoChange()) {
393 modID = UniqueID.next();
394 if (e.isMassChange())
396 if (e.isAerodynamicChange())
398 if (e.isTreeChange())
400 if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
401 functionalModID = modID;
404 // Check whether frozen
405 if (freezeList != null) {
406 log.debug("Rocket is in frozen state, adding event " + e + " info freeze list");
411 log.debug("Firing rocket change event " + e);
413 // Notify all components first
414 Iterator<RocketComponent> iterator = this.iterator(true);
415 while (iterator.hasNext()) {
416 iterator.next().componentChanged(e);
419 // Notify all listeners
420 Object[] listeners = listenerList.getListenerList();
421 for (int i = listeners.length - 2; i >= 0; i -= 2) {
422 if (listeners[i] == ComponentChangeListener.class) {
423 ((ComponentChangeListener) listeners[i + 1]).componentChanged(e);
424 } else if (listeners[i] == ChangeListener.class) {
425 ((ChangeListener) listeners[i + 1]).stateChanged(e);
429 mutex.unlock("fireComponentChangeEvent");
435 * Freezes the rocket structure from firing any events. This may be performed to
436 * combine several actions on the structure into a single large action.
437 * <code>thaw()</code> must always be called afterwards.
439 * NOTE: Always use a try/finally to ensure <code>thaw()</code> is called:
441 * Rocket r = c.getRocket();
452 public void freeze() {
454 if (freezeList == null) {
455 freezeList = new LinkedList<ComponentChangeEvent>();
456 log.debug("Freezing Rocket");
458 ExceptionHandler.handleErrorCondition("Attempting to freeze Rocket when it is already frozen, " +
459 "freezeList=" + freezeList);
464 * Thaws a frozen rocket structure and fires a combination of the events fired during
465 * the freeze. The event type is a combination of those fired and the source is the
466 * last component to have been an event source.
472 if (freezeList == null) {
473 ExceptionHandler.handleErrorCondition("Attempting to thaw Rocket when it is not frozen");
476 if (freezeList.size() == 0) {
477 log.warn("Thawing rocket with no changes made");
482 log.debug("Thawing rocket, freezeList=" + freezeList);
486 for (ComponentChangeEvent e : freezeList) {
487 type = type | e.getType();
492 fireComponentChangeEvent(new ComponentChangeEvent((RocketComponent) c, type));
498 //////// Motor configurations ////////
502 * Return the default configuration. This should be used in the user interface
503 * to ensure a consistent rocket configuration between dialogs. It should NOT
504 * be used in simulations not relating to the UI.
506 * @return the default {@link Configuration}.
508 public Configuration getDefaultConfiguration() {
510 return defaultConfiguration;
515 * Return an array of the motor configuration IDs. This array is guaranteed
516 * to contain the <code>null</code> ID as the first element.
518 * @return an array of the motor configuration IDs.
520 public String[] getMotorConfigurationIDs() {
522 return motorConfigurationIDs.toArray(new String[0]);
526 * Add a new motor configuration ID to the motor configurations. The new ID
529 * @return the new motor configuration ID.
531 public String newMotorConfigurationID() {
533 String id = UUID.randomUUID().toString();
534 motorConfigurationIDs.add(id);
535 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
540 * Add a specified motor configuration ID to the motor configurations.
542 * @param id the motor configuration ID.
543 * @return true if successful, false if the ID was already used.
545 public boolean addMotorConfigurationID(String id) {
547 if (id == null || motorConfigurationIDs.contains(id))
549 motorConfigurationIDs.add(id);
550 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
555 * Remove a motor configuration ID from the configuration IDs. The <code>null</code>
556 * ID cannot be removed, and an attempt to remove it will be silently ignored.
558 * @param id the motor configuration ID to remove
560 public void removeMotorConfigurationID(String id) {
564 motorConfigurationIDs.remove(id);
565 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
570 * Check whether <code>id</code> is a valid motor configuration ID.
572 * @param id the configuration ID.
573 * @return whether a motor configuration with that ID exists.
575 public boolean isMotorConfigurationID(String id) {
577 return motorConfigurationIDs.contains(id);
583 * Check whether the given motor configuration ID has motors defined for it.
585 * @param id the motor configuration ID (may be invalid).
586 * @return whether any motors are defined for it.
588 public boolean hasMotors(String id) {
593 Iterator<RocketComponent> iterator = this.iterator();
594 while (iterator.hasNext()) {
595 RocketComponent c = iterator.next();
597 if (c instanceof MotorMount) {
598 MotorMount mount = (MotorMount) c;
599 if (!mount.isMotorMount())
601 if (mount.getMotor(id) != null) {
611 * Return the user-set name of the motor configuration. If no name has been set,
612 * returns an empty string (not null).
614 * @param id the motor configuration id
615 * @return the configuration name
617 public String getMotorConfigurationName(String id) {
619 if (!isMotorConfigurationID(id))
621 String s = motorConfigurationNames.get(id);
629 * Set the name of the motor configuration. A name can be unset by passing
630 * <code>null</code> or an empty string.
632 * @param id the motor configuration id
633 * @param name the name for the motor configuration
635 public void setMotorConfigurationName(String id, String name) {
637 motorConfigurationNames.put(id, name);
638 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
643 * Return either the motor configuration name (if set) or its description.
645 * @param id the motor configuration ID.
646 * @return a textual representation of the configuration
648 public String getMotorConfigurationNameOrDescription(String id) {
652 name = getMotorConfigurationName(id);
653 if (name != null && !name.equals(""))
656 return getMotorConfigurationDescription(id);
660 * Return a description for the motor configuration, generated from the motor
661 * designations of the components.
663 * @param id the motor configuration ID.
664 * @return a textual representation of the configuration
666 @SuppressWarnings("null")
667 public String getMotorConfigurationDescription(String id) {
672 // Generate the description
674 // First iterate over each stage and store the designations of each motor
675 List<List<String>> list = new ArrayList<List<String>>();
676 List<String> currentList = null;
678 Iterator<RocketComponent> iterator = this.iterator();
679 while (iterator.hasNext()) {
680 RocketComponent c = iterator.next();
682 if (c instanceof Stage) {
684 currentList = new ArrayList<String>();
685 list.add(currentList);
687 } else if (c instanceof MotorMount) {
689 MotorMount mount = (MotorMount) c;
690 Motor motor = mount.getMotor(id);
692 if (mount.isMotorMount() && motor != null) {
693 String designation = motor.getDesignation(mount.getMotorDelay(id));
695 for (int i = 0; i < mount.getMotorCount(); i++) {
696 currentList.add(designation);
704 if (motorCount == 0) {
705 return "[No motors]";
708 // Change multiple occurrences of a motor to n x motor
709 List<String> stages = new ArrayList<String>();
711 for (List<String> stage : list) {
712 String stageName = "";
713 String previous = null;
716 Collections.sort(stage);
717 for (String current : stage) {
718 if (current.equals(previous)) {
724 if (previous != null) {
727 s = "" + count + Chars.TIMES + previous;
732 if (stageName.equals(""))
735 stageName = stageName + "," + s;
743 if (previous != null) {
746 s = "" + count + Chars.TIMES + previous;
751 if (stageName.equals(""))
754 stageName = stageName + "," + s;
757 stages.add(stageName);
761 for (int i = 0; i < stages.size(); i++) {
762 String s = stages.get(i);
768 name = name + "; " + s;
776 //////// Obligatory component information
780 public String getComponentName() {
785 public Coordinate getComponentCG() {
786 return new Coordinate(0, 0, 0, 0);
790 public double getComponentMass() {
795 public double getLongitudinalUnitInertia() {
800 public double getRotationalUnitInertia() {
805 public Collection<Coordinate> getComponentBounds() {
806 return Collections.emptyList();
810 public boolean isAerodynamic() {
815 public boolean isMassive() {
820 public boolean allowsChildren() {
825 * Allows only <code>Stage</code> components to be added to the type Rocket.
828 public boolean isCompatible(Class<? extends RocketComponent> type) {
829 return (Stage.class.isAssignableFrom(type));
833 * Accept a visitor to this Rocket in the component hierarchy.
835 * @param theVisitor the visitor that will be called back with a reference to this Rocket
838 public void accept(final ComponentVisitor theVisitor) {
839 theVisitor.visit(this);