1 package net.sf.openrocket.rocketcomponent;
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.Chars;
8 import net.sf.openrocket.util.Coordinate;
9 import net.sf.openrocket.util.MathUtil;
10 import net.sf.openrocket.util.UniqueID;
12 import javax.swing.event.ChangeListener;
13 import javax.swing.event.EventListenerList;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.Iterator;
19 import java.util.LinkedList;
20 import java.util.List;
21 import java.util.UUID;
25 * Base for all rocket components. This is the "starting point" for all rocket trees.
26 * It provides the actual implementations of several methods defined in RocketComponent
27 * (eg. the rocket listener lists) and the methods defined in RocketComponent call these.
28 * It also defines some other methods that concern the whole rocket, and helper methods
29 * that keep information about the program state.
31 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
34 public class Rocket extends RocketComponent {
35 private static final LogHelper log = Application.getLogger();
37 public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
41 * List of component change listeners.
43 private EventListenerList listenerList = new EventListenerList();
46 * When freezeList != null, events are not dispatched but stored in the list.
47 * When the structure is thawed, a single combined event will be fired.
49 private List<ComponentChangeEvent> freezeList = null;
53 private int massModID;
54 private int aeroModID;
55 private int treeModID;
56 private int functionalModID;
59 private ReferenceType refType = ReferenceType.MAXIMUM; // Set in constructor
60 private double customReferenceLength = DEFAULT_REFERENCE_LENGTH;
63 // The default configuration used in dialogs
64 private final Configuration defaultConfiguration;
67 private String designer = "";
68 private String revision = "";
71 // Motor configuration list
72 private ArrayList<String> motorConfigurationIDs = new ArrayList<String>();
73 private HashMap<String, String> motorConfigurationNames = new HashMap<String, String>();
75 motorConfigurationIDs.add(null);
79 // Does the rocket have a perfect finish (a notable amount of laminar flow)
80 private boolean perfectFinish = false;
84 ///////////// Constructor /////////////
87 super(RocketComponent.Position.AFTER);
88 modID = UniqueID.next();
92 functionalModID = modID;
93 defaultConfiguration = new Configuration(this);
98 public String getDesigner() {
103 public void setDesigner(String s) {
107 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
111 public String getRevision() {
116 public void setRevision(String s) {
120 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
127 * Return the number of stages in this rocket.
129 * @return the number of stages in this rocket.
131 public int getStageCount() {
133 return this.getChildCount();
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.
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
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.
153 * @return a unique ID number for this modification state.
155 public int getModID() {
160 * Return the non-negative mass modification ID of this rocket. See
161 * {@link #getModID()} for details.
163 * @return a unique ID number for this mass-modification state.
165 public int getMassModID() {
170 * Return the non-negative aerodynamic modification ID of this rocket. See
171 * {@link #getModID()} for details.
173 * @return a unique ID number for this aerodynamic-modification state.
175 public int getAerodynamicModID() {
180 * Return the non-negative tree modification ID of this rocket. See
181 * {@link #getModID()} for details.
183 * @return a unique ID number for this tree-modification state.
185 public int getTreeModID() {
190 * Return the non-negative functional modificationID of this rocket.
191 * This changes every time a functional change occurs.
193 * @return a unique ID number for this functional modification state.
195 public int getFunctionalModID() {
196 return functionalModID;
202 public ReferenceType getReferenceType() {
207 public void setReferenceType(ReferenceType type) {
211 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
215 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;
264 * Make a deep copy of the Rocket structure. This method is exposed as public to allow
265 * for undo/redo system functionality.
268 @SuppressWarnings("unchecked")
269 public Rocket copyWithOriginalID() {
270 Rocket copy = (Rocket) super.copyWithOriginalID();
271 copy.motorConfigurationIDs = (ArrayList<String>) this.motorConfigurationIDs.clone();
272 copy.motorConfigurationNames =
273 (HashMap<String, String>) this.motorConfigurationNames.clone();
274 copy.resetListeners();
280 * Load the rocket structure from the source. The method loads the fields of this
281 * Rocket object and copies the references to siblings from the <code>source</code>.
282 * The object <code>source</code> should not be used after this call, as it is in
285 * This method is meant to be used in conjunction with undo/redo functionality,
286 * and therefore fires an UNDO_EVENT, masked with all applicable mass/aerodynamic/tree
289 @SuppressWarnings("unchecked")
290 public void loadFrom(Rocket r) {
293 int type = ComponentChangeEvent.UNDO_CHANGE | ComponentChangeEvent.NONFUNCTIONAL_CHANGE;
294 if (this.massModID != r.massModID)
295 type |= ComponentChangeEvent.MASS_CHANGE;
296 if (this.aeroModID != r.aeroModID)
297 type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
298 if (this.treeModID != r.treeModID)
299 type |= ComponentChangeEvent.TREE_CHANGE;
301 this.modID = r.modID;
302 this.massModID = r.massModID;
303 this.aeroModID = r.aeroModID;
304 this.treeModID = r.treeModID;
305 this.functionalModID = r.functionalModID;
306 this.refType = r.refType;
307 this.customReferenceLength = r.customReferenceLength;
309 this.motorConfigurationIDs = (ArrayList<String>) r.motorConfigurationIDs.clone();
310 this.motorConfigurationNames =
311 (HashMap<String, String>) r.motorConfigurationNames.clone();
312 this.perfectFinish = r.perfectFinish;
314 String id = defaultConfiguration.getMotorConfigurationID();
315 if (!this.motorConfigurationIDs.contains(id))
316 defaultConfiguration.setMotorConfigurationID(null);
318 fireComponentChangeEvent(type);
324 /////// Implement the ComponentChangeListener lists
327 * Creates a new EventListenerList for this component. This is necessary when cloning
330 public void resetListeners() {
331 // System.out.println("RESETTING LISTENER LIST of Rocket "+this);
332 listenerList = new EventListenerList();
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]);
344 public void addComponentChangeListener(ComponentChangeListener l) {
346 listenerList.add(ComponentChangeListener.class, l);
347 log.verbose("Added ComponentChangeListener " + l + ", current number of listeners is " +
348 listenerList.getListenerCount());
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());
360 public void addChangeListener(ChangeListener l) {
362 listenerList.add(ChangeListener.class, l);
363 log.verbose("Added ChangeListener " + l + ", current number of listeners is " +
364 listenerList.getListenerCount());
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());
376 protected void fireComponentChangeEvent(ComponentChangeEvent e) {
379 // Update modification ID's only for normal (not undo/redo) events
380 if (!e.isUndoChange()) {
381 modID = UniqueID.next();
382 if (e.isMassChange())
384 if (e.isAerodynamicChange())
386 if (e.isTreeChange())
388 if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
389 functionalModID = modID;
392 // Check whether frozen
393 if (freezeList != null) {
394 log.debug("Rocket is in frozen state, adding event " + e + " info freeze list");
399 log.debug("Firing rocket change event " + e);
401 // Notify all components first
402 Iterator<RocketComponent> iterator = this.deepIterator(true);
403 while (iterator.hasNext()) {
404 iterator.next().componentChanged(e);
407 // Notify all listeners
408 Object[] listeners = listenerList.getListenerList();
409 for (int i = listeners.length - 2; i >= 0; i -= 2) {
410 if (listeners[i] == ComponentChangeListener.class) {
411 ((ComponentChangeListener) listeners[i + 1]).componentChanged(e);
412 } else if (listeners[i] == ChangeListener.class) {
413 ((ChangeListener) listeners[i + 1]).stateChanged(e);
420 * Freezes the rocket structure from firing any events. This may be performed to
421 * combine several actions on the structure into a single large action.
422 * <code>thaw()</code> must always be called afterwards.
424 * NOTE: Always use a try/finally to ensure <code>thaw()</code> is called:
426 * Rocket r = c.getRocket();
437 public void freeze() {
439 if (freezeList == null) {
440 freezeList = new LinkedList<ComponentChangeEvent>();
441 log.debug("Freezing Rocket");
443 ExceptionHandler.handleErrorCondition("Attempting to freeze Rocket when it is already frozen, " +
444 "freezeList=" + freezeList);
449 * Thaws a frozen rocket structure and fires a combination of the events fired during
450 * the freeze. The event type is a combination of those fired and the source is the
451 * last component to have been an event source.
457 if (freezeList == null) {
458 ExceptionHandler.handleErrorCondition("Attempting to thaw Rocket when it is not frozen");
461 if (freezeList.size() == 0) {
462 log.warn("Thawing rocket with no changes made");
467 log.debug("Thawing rocket, freezeList=" + freezeList);
471 for (ComponentChangeEvent e : freezeList) {
472 type = type | e.getType();
477 fireComponentChangeEvent(new ComponentChangeEvent((RocketComponent) c, type));
483 //////// Motor configurations ////////
487 * Return the default configuration. This should be used in the user interface
488 * to ensure a consistent rocket configuration between dialogs. It should NOT
489 * be used in simulations not relating to the UI.
491 * @return the default {@link Configuration}.
493 public Configuration getDefaultConfiguration() {
495 return defaultConfiguration;
500 * Return an array of the motor configuration IDs. This array is guaranteed
501 * to contain the <code>null</code> ID as the first element.
503 * @return an array of the motor configuration IDs.
505 public String[] getMotorConfigurationIDs() {
507 return motorConfigurationIDs.toArray(new String[0]);
511 * Add a new motor configuration ID to the motor configurations. The new ID
514 * @return the new motor configuration ID.
516 public String newMotorConfigurationID() {
518 String id = UUID.randomUUID().toString();
519 motorConfigurationIDs.add(id);
520 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
525 * Add a specified motor configuration ID to the motor configurations.
527 * @param id the motor configuration ID.
528 * @return true if successful, false if the ID was already used.
530 public boolean addMotorConfigurationID(String id) {
532 if (id == null || motorConfigurationIDs.contains(id))
534 motorConfigurationIDs.add(id);
535 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
540 * Remove a motor configuration ID from the configuration IDs. The <code>null</code>
541 * ID cannot be removed, and an attempt to remove it will be silently ignored.
543 * @param id the motor configuration ID to remove
545 public void removeMotorConfigurationID(String id) {
549 motorConfigurationIDs.remove(id);
550 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
555 * Check whether <code>id</code> is a valid motor configuration ID.
557 * @param id the configuration ID.
558 * @return whether a motor configuration with that ID exists.
560 public boolean isMotorConfigurationID(String id) {
562 return motorConfigurationIDs.contains(id);
568 * Check whether the given motor configuration ID has motors defined for it.
570 * @param id the motor configuration ID (may be invalid).
571 * @return whether any motors are defined for it.
573 public boolean hasMotors(String id) {
578 Iterator<RocketComponent> iterator = this.deepIterator();
579 while (iterator.hasNext()) {
580 RocketComponent c = iterator.next();
582 if (c instanceof MotorMount) {
583 MotorMount mount = (MotorMount) c;
584 if (!mount.isMotorMount())
586 if (mount.getMotor(id) != null) {
596 * Return the user-set name of the motor configuration. If no name has been set,
597 * returns an empty string (not null).
599 * @param id the motor configuration id
600 * @return the configuration name
602 public String getMotorConfigurationName(String id) {
604 if (!isMotorConfigurationID(id))
606 String s = motorConfigurationNames.get(id);
614 * Set the name of the motor configuration. A name can be unset by passing
615 * <code>null</code> or an empty string.
617 * @param id the motor configuration id
618 * @param name the name for the motor configuration
620 public void setMotorConfigurationName(String id, String name) {
622 motorConfigurationNames.put(id, name);
623 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
628 * Return either the motor configuration name (if set) or its description.
630 * @param id the motor configuration ID.
631 * @return a textual representation of the configuration
633 public String getMotorConfigurationNameOrDescription(String id) {
637 name = getMotorConfigurationName(id);
638 if (name != null && !name.equals(""))
641 return getMotorConfigurationDescription(id);
645 * Return a description for the motor configuration, generated from the motor
646 * designations of the components.
648 * @param id the motor configuration ID.
649 * @return a textual representation of the configuration
651 @SuppressWarnings("null")
652 public String getMotorConfigurationDescription(String id) {
657 // Generate the description
659 // First iterate over each stage and store the designations of each motor
660 List<List<String>> list = new ArrayList<List<String>>();
661 List<String> currentList = null;
663 Iterator<RocketComponent> iterator = this.deepIterator();
664 while (iterator.hasNext()) {
665 RocketComponent c = iterator.next();
667 if (c instanceof Stage) {
669 currentList = new ArrayList<String>();
670 list.add(currentList);
672 } else if (c instanceof MotorMount) {
674 MotorMount mount = (MotorMount) c;
675 Motor motor = mount.getMotor(id);
677 if (mount.isMotorMount() && motor != null) {
678 String designation = motor.getDesignation(mount.getMotorDelay(id));
680 for (int i = 0; i < mount.getMotorCount(); i++) {
681 currentList.add(designation);
689 if (motorCount == 0) {
690 return "[No motors]";
693 // Change multiple occurrences of a motor to n x motor
694 List<String> stages = new ArrayList<String>();
696 for (List<String> stage : list) {
697 String stageName = "";
698 String previous = null;
701 Collections.sort(stage);
702 for (String current : stage) {
703 if (current.equals(previous)) {
709 if (previous != null) {
712 s = "" + count + Chars.TIMES + previous;
717 if (stageName.equals(""))
720 stageName = stageName + "," + s;
728 if (previous != null) {
731 s = "" + count + Chars.TIMES + previous;
736 if (stageName.equals(""))
739 stageName = stageName + "," + s;
742 stages.add(stageName);
746 for (int i = 0; i < stages.size(); i++) {
747 String s = stages.get(i);
753 name = name + "; " + s;
761 //////// Obligatory component information
765 public String getComponentName() {
770 public Coordinate getComponentCG() {
771 return new Coordinate(0, 0, 0, 0);
775 public double getComponentMass() {
780 public double getLongitudalUnitInertia() {
785 public double getRotationalUnitInertia() {
790 public Collection<Coordinate> getComponentBounds() {
791 return Collections.emptyList();
795 public boolean isAerodynamic() {
800 public boolean isMassive() {
805 public boolean allowsChildren() {
810 * Allows only <code>Stage</code> components to be added to the type Rocket.
813 public boolean isCompatible(Class<? extends RocketComponent> type) {
814 return (Stage.class.isAssignableFrom(type));
818 * Accept a visitor to this Rocket in the component hierarchy.
820 * @param theVisitor the visitor that will be called back with a reference to this Rocket
823 public void accept (final ComponentVisitor theVisitor) {
824 theVisitor.visit(this);