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;
11 import java.util.UUID;
13 import javax.swing.event.ChangeListener;
14 import javax.swing.event.EventListenerList;
16 import net.sf.openrocket.util.Coordinate;
17 import net.sf.openrocket.util.MathUtil;
21 * Base for all rocket components. This is the "starting point" for all rocket trees.
22 * It provides the actual implementations of several methods defined in RocketComponent
23 * (eg. the rocket listener lists) and the methods defined in RocketComponent call these.
24 * It also defines some other methods that concern the whole rocket, and helper methods
25 * that keep information about the program state.
27 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
30 public class Rocket extends RocketComponent {
31 public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
33 private static final boolean DEBUG_LISTENERS = false;
37 * The next modification ID to use. This variable may only be accessed via
38 * the synchronized {@link #getNextModID()} method!
40 private static int nextModID = 1;
44 * List of component change listeners.
46 private EventListenerList listenerList = new EventListenerList();
49 * When freezeList != null, events are not dispatched but stored in the list.
50 * When the structure is thawed, a single combined event will be fired.
52 private List<ComponentChangeEvent> freezeList = null;
56 private int massModID;
57 private int aeroModID;
58 private int treeModID;
59 private int functionalModID;
62 private ReferenceType refType = ReferenceType.MAXIMUM; // Set in constructor
63 private double customReferenceLength = DEFAULT_REFERENCE_LENGTH;
66 // The default configuration used in dialogs
67 private final Configuration defaultConfiguration;
70 private String designer = "";
71 private String revision = "";
74 // Motor configuration list
75 private List<String> motorConfigurationIDs = new ArrayList<String>();
76 private Map<String, String> motorConfigurationNames = new HashMap<String, String>();
78 motorConfigurationIDs.add(null);
82 // Does the rocket have a perfect finish (a notable amount of laminar flow)
83 private boolean perfectFinish = false;
87 ///////////// Constructor /////////////
90 super(RocketComponent.Position.AFTER);
91 modID = getNextModID();
95 functionalModID = modID;
96 defaultConfiguration = new Configuration(this);
101 public String getDesigner() {
105 public void setDesigner(String s) {
109 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
113 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() {
133 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() {
207 public void setReferenceType(ReferenceType type) {
211 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
215 public double getCustomReferenceLength() {
216 return customReferenceLength;
219 public void setCustomReferenceLength(double length) {
220 if (MathUtil.equals(customReferenceLength, length))
223 this.customReferenceLength = Math.max(length,0.001);
225 if (refType == ReferenceType.CUSTOM) {
226 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
235 * Set whether the rocket has a perfect finish. This will affect whether the
236 * boundary layer is assumed to be fully turbulent or not.
238 * @param perfectFinish whether the finish is perfect.
240 public void setPerfectFinish(boolean perfectFinish) {
241 if (this.perfectFinish == perfectFinish)
243 this.perfectFinish = perfectFinish;
244 fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
250 * Get whether the rocket has a perfect finish.
252 * @return the perfectFinish
254 public boolean isPerfectFinish() {
255 return perfectFinish;
261 * Return a new unique modification ID. This method is thread-safe.
263 * @return a new modification ID unique to this session.
265 private synchronized int getNextModID() {
271 * Make a deep copy of the Rocket structure. This is a helper method which simply
272 * casts the result of the superclass method to a Rocket.
275 public Rocket copy() {
276 Rocket copy = (Rocket)super.copy();
277 copy.resetListeners();
287 * Load the rocket structure from the source. The method loads the fields of this
288 * Rocket object and copies the references to siblings from the <code>source</code>.
289 * The object <code>source</code> should not be used after this call, as it is in
292 * This method is meant to be used in conjunction with undo/redo functionality,
293 * and therefore fires an UNDO_EVENT, masked with all applicable mass/aerodynamic/tree
296 public void loadFrom(Rocket r) {
299 int type = ComponentChangeEvent.UNDO_CHANGE | ComponentChangeEvent.NONFUNCTIONAL_CHANGE;
300 if (this.massModID != r.massModID)
301 type |= ComponentChangeEvent.MASS_CHANGE;
302 if (this.aeroModID != r.aeroModID)
303 type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
304 if (this.treeModID != r.treeModID)
305 type |= ComponentChangeEvent.TREE_CHANGE;
307 this.modID = r.modID;
308 this.massModID = r.massModID;
309 this.aeroModID = r.aeroModID;
310 this.treeModID = r.treeModID;
311 this.functionalModID = r.functionalModID;
312 this.refType = r.refType;
313 this.customReferenceLength = r.customReferenceLength;
315 this.motorConfigurationIDs = r.motorConfigurationIDs;
316 this.motorConfigurationNames = r.motorConfigurationNames;
317 this.perfectFinish = r.perfectFinish;
319 fireComponentChangeEvent(type);
325 /////// Implement the ComponentChangeListener lists
328 * Creates a new EventListenerList for this component. This is necessary when cloning
331 public void resetListeners() {
332 // System.out.println("RESETTING LISTENER LIST of Rocket "+this);
333 listenerList = new EventListenerList();
337 public void printListeners() {
338 System.out.println(""+this+" has "+listenerList.getListenerCount()+" listeners:");
339 Object[] list = listenerList.getListenerList();
340 for (int i=1; i<list.length; i+=2)
341 System.out.println(" "+((i+1)/2)+": "+list[i]);
345 public void addComponentChangeListener(ComponentChangeListener l) {
346 listenerList.add(ComponentChangeListener.class,l);
348 System.out.println(this+": Added listner (now "+listenerList.getListenerCount()+
352 public void removeComponentChangeListener(ComponentChangeListener l) {
353 listenerList.remove(ComponentChangeListener.class, l);
355 System.out.println(this+": Removed listner (now "+listenerList.getListenerCount()+
361 public void addChangeListener(ChangeListener l) {
362 listenerList.add(ChangeListener.class,l);
364 System.out.println(this+": Added listner (now "+listenerList.getListenerCount()+
368 public void removeChangeListener(ChangeListener l) {
369 listenerList.remove(ChangeListener.class, l);
371 System.out.println(this+": Removed listner (now "+listenerList.getListenerCount()+
377 protected void fireComponentChangeEvent(ComponentChangeEvent e) {
379 // Update modification ID's only for normal (not undo/redo) events
380 if (!e.isUndoChange()) {
381 modID = getNextModID();
382 if (e.isMassChange())
384 if (e.isAerodynamicChange())
386 if (e.isTreeChange())
388 if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
389 functionalModID = modID;
393 System.out.println("FIRING "+e);
395 // Check whether frozen
396 if (freezeList != null) {
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() {
438 if (freezeList == null)
439 freezeList = new LinkedList<ComponentChangeEvent>();
443 * Thaws a frozen rocket structure and fires a combination of the events fired during
444 * the freeze. The event type is a combination of those fired and the source is the
445 * last component to have been an event source.
450 if (freezeList == null)
452 if (freezeList.size()==0) {
459 for (ComponentChangeEvent e: freezeList) {
460 type = type | e.getType();
465 fireComponentChangeEvent(new ComponentChangeEvent((RocketComponent)c,type));
471 //////// Motor configurations ////////
475 * Return the default configuration. This should be used in the user interface
476 * to ensure a consistent rocket configuration between dialogs. It should NOT
477 * be used in simulations not relating to the UI.
479 * @return the default {@link Configuration}.
481 public Configuration getDefaultConfiguration() {
482 return defaultConfiguration;
487 * Return an array of the motor configuration IDs. This array is guaranteed
488 * to contain the <code>null</code> ID as the first element.
490 * @return an array of the motor configuration IDs.
492 public String[] getMotorConfigurationIDs() {
493 return motorConfigurationIDs.toArray(new String[0]);
497 * Add a new motor configuration ID to the motor configurations. The new ID
500 * @return the new motor configuration ID.
502 public String newMotorConfigurationID() {
503 String id = UUID.randomUUID().toString();
504 motorConfigurationIDs.add(id);
505 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
510 * Add a specified motor configuration ID to the motor configurations.
512 * @param id the motor configuration ID.
513 * @return true if successful, false if the ID was already used.
515 public boolean addMotorConfigurationID(String id) {
516 if (id == null || motorConfigurationIDs.contains(id))
518 motorConfigurationIDs.add(id);
519 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
524 * Remove a motor configuration ID from the configuration IDs. The <code>null</code>
525 * ID cannot be removed, and an attempt to remove it will be silently ignored.
527 * @param id the motor configuration ID to remove
529 public void removeMotorConfigurationID(String id) {
532 motorConfigurationIDs.remove(id);
533 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
538 * Return the user-set name of the motor configuration. If no name has been set,
539 * returns an empty string (not null).
541 * @param id the motor configuration id
542 * @return the configuration name
544 public String getMotorConfigurationName(String id) {
545 String s = motorConfigurationNames.get(id);
553 * Set the name of the motor configuration. A name can be unset by passing
554 * <code>null</code> or an empty string.
556 * @param id the motor configuration id
557 * @param name the name for the motor configuration
559 public void setMotorConfigurationName(String id, String name) {
560 motorConfigurationNames.put(id,name);
561 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
566 * Return a description for the motor configuration. This is either the
567 * name previously set by {@link #setMotorConfigurationName(String, String)} or
568 * a string generated from the motor designations of the components.
570 * @param id the motor configuration ID.
571 * @return a textual representation of the configuration
573 @SuppressWarnings("null")
574 public String getMotorConfigurationDescription(String id) {
578 if (!motorConfigurationIDs.contains(id)) {
579 throw new IllegalArgumentException("Motor configuration ID does not exist: "+id);
582 name = motorConfigurationNames.get(id);
583 if (name != null && !name.equals(""))
586 // Generate the description
588 // First iterate over each stage and store the designations of each motor
589 List<List<String>> list = new ArrayList<List<String>>();
590 List<String> currentList = null;
592 Iterator<RocketComponent> iterator = this.deepIterator();
593 while (iterator.hasNext()) {
594 RocketComponent c = iterator.next();
596 if (c instanceof Stage) {
598 currentList = new ArrayList<String>();
599 list.add(currentList);
601 } else if (c instanceof MotorMount) {
603 MotorMount mount = (MotorMount) c;
604 Motor motor = mount.getMotor(id);
606 if (mount.isMotorMount() && motor != null) {
607 String designation = motor.getDesignation(mount.getMotorDelay(id));
609 for (int i=0; i < mount.getMotorCount(); i++) {
610 currentList.add(designation);
618 if (motorCount == 0) {
619 return "[No motors]";
622 // Change multiple occurrences of a motor to n x motor
623 List<String> stages = new ArrayList<String>();
625 for (List<String> stage: list) {
626 String stageName = "";
627 String previous = null;
630 Collections.sort(stage);
631 for (String current: stage) {
632 if (current.equals(previous)) {
638 if (previous != null) {
641 s = "" + count + "\u00d7" + previous;
646 if (stageName.equals(""))
649 stageName = stageName + "," + s;
657 if (previous != null) {
660 s = "" + count + "\u00d7" + previous;
665 if (stageName.equals(""))
668 stageName = stageName + "," + s;
671 stages.add(stageName);
675 for (int i=0; i < stages.size(); i++) {
676 String s = stages.get(i);
682 name = name + "; " + s;
690 //////// Obligatory component information
694 public String getComponentName() {
699 public Coordinate getComponentCG() {
700 return new Coordinate(0,0,0,0);
704 public double getComponentMass() {
709 public double getLongitudalUnitInertia() {
714 public double getRotationalUnitInertia() {
719 public Collection<Coordinate> getComponentBounds() {
720 return Collections.emptyList();
724 public boolean isAerodynamic() {
729 public boolean isMassive() {
734 * Allows only <code>Stage</code> components to be added to the type Rocket.
737 public boolean isCompatible(Class<? extends RocketComponent> type) {
738 return (Stage.class.isAssignableFrom(type));