1 package net.sf.openrocket.rocketcomponent;
3 import java.util.ArrayList;
4 import java.util.BitSet;
5 import java.util.Collection;
6 import java.util.Collections;
7 import java.util.HashMap;
8 import java.util.Iterator;
10 import java.util.NoSuchElementException;
12 import javax.swing.event.ChangeEvent;
13 import javax.swing.event.ChangeListener;
14 import javax.swing.event.EventListenerList;
16 import net.sf.openrocket.util.BugException;
17 import net.sf.openrocket.util.ChangeSource;
18 import net.sf.openrocket.util.Coordinate;
19 import net.sf.openrocket.util.MathUtil;
22 public class Configuration implements Cloneable, ChangeSource, ComponentChangeListener,
23 Iterable<RocketComponent> {
25 public static final double DEFAULT_IGNITION_TIME = Double.MAX_VALUE;
28 private Rocket rocket;
29 private BitSet stages = new BitSet();
31 private String motorConfiguration = null;
33 private EventListenerList listenerList = new EventListenerList();
35 private final HashMap<MotorMount, Double> ignitionTimes =
36 new HashMap<MotorMount, Double>();
40 private int boundsModID = -1;
41 private ArrayList<Coordinate> cachedBounds = new ArrayList<Coordinate>();
42 private double cachedLength = -1;
44 private int refLengthModID = -1;
45 private double cachedRefLength = -1;
50 * Create a new configuration with the specified <code>Rocket</code> with
51 * <code>null</code> motor configuration.
53 * @param rocket the rocket
55 public Configuration(Rocket rocket) {
58 rocket.addComponentChangeListener(this);
63 * Create a new configuration with the specified <code>Rocket</code> and motor
66 * @param rocket the rocket.
67 * @param motorID the motor configuration ID to use.
69 public Configuration(Rocket rocket, String motorID) {
71 this.motorConfiguration = motorID;
73 rocket.addComponentChangeListener(this);
79 public Rocket getRocket() {
84 public void setAllStages() {
86 stages.set(0,rocket.getStageCount());
92 * Set all stages up to and including the given stage number. For example,
93 * <code>setToStage(0)</code> will set only the first stage active.
95 * @param stage the stage number.
97 public void setToStage(int stage) {
99 stages.set(0, stage+1, true);
100 // stages.set(stage+1, rocket.getStageCount(), false);
106 * Check whether the up-most stage of the rocket is in this configuration.
108 * @return <code>true</code> if the first stage is active in this configuration.
110 public boolean isHead() {
111 return isStageActive(0);
114 public boolean isStageActive(RocketComponent stage) {
115 if (!(stage instanceof Stage)) {
116 throw new IllegalArgumentException("called with component "+stage);
118 return stages.get(stage.getParent().getChildPosition(stage));
122 public boolean isStageActive(int stage) {
123 if (stage >= rocket.getStageCount())
125 return stages.get(stage);
128 public int getStageCount() {
129 return rocket.getStageCount();
132 public int getActiveStageCount() {
134 int s = rocket.getStageCount();
136 for (int i=0; i < s; i++) {
143 public int[] getActiveStages() {
144 int stageCount = rocket.getStageCount();
145 List<Integer> active = new ArrayList<Integer>();
148 for (int i=0; i < stageCount; i++) {
154 ret = new int[active.size()];
155 for (int i=0; i < ret.length; i++) {
156 ret[i] = active.get(i);
164 * Return the reference length associated with the current configuration. The
165 * reference length type is retrieved from the <code>Rocket</code>.
167 * @return the reference length for this configuration.
169 public double getReferenceLength() {
170 if (rocket.getModID() != refLengthModID) {
171 refLengthModID = rocket.getModID();
172 cachedRefLength = rocket.getReferenceType().getReferenceLength(this);
174 return cachedRefLength;
178 public double getReferenceArea() {
179 return Math.PI * MathUtil.pow2(getReferenceLength()/2);
183 public String getMotorConfigurationID() {
184 return motorConfiguration;
187 public void setMotorConfigurationID(String id) {
188 if ((motorConfiguration == null && id == null) ||
189 (id != null && id.equals(motorConfiguration)))
192 motorConfiguration = id;
196 public String getMotorConfigurationDescription() {
197 return rocket.getMotorConfigurationNameOrDescription(motorConfiguration);
203 * Clear all motor ignition times. All values are reset to their default of
204 * {@link #DEFAULT_IGNITION_TIME}.
206 public void resetIgnitionTimes() {
207 ignitionTimes.clear();
211 * Set the ignition time of the motor in the specified motor mount. Negative or NaN
212 * time values will cause an <code>IllegalArgumentException</code>.
214 * @param mount the motor mount to specify.
215 * @param time the time at which to ignite the motors.
216 * @throws IllegalArgumentException if <code>time</code> is negative of NaN.
218 public void setIgnitionTime(MotorMount mount, double time) {
220 throw new IllegalArgumentException("time is negative: "+time);
222 ignitionTimes.put(mount, time);
223 // TODO: MEDIUM: Should this fire events?
227 * Return the ignition time of the motor in the specified motor mount. If no time
228 * has been specified, returns {@link #DEFAULT_IGNITION_TIME} as the default.
230 * @param mount the motor mount.
231 * @return ignition time of the motors in the mount.
233 public double getIgnitionTime(MotorMount mount) {
234 Double d = ignitionTimes.get(mount);
236 return DEFAULT_IGNITION_TIME;
244 * Removes the listener connection to the rocket and listeners of this object.
245 * This configuration may not be used after a call to this method!
247 public void release() {
248 rocket.removeComponentChangeListener(this);
254 //////////////// Listeners ////////////////
257 public void addChangeListener(ChangeListener listener) {
258 listenerList.add(ChangeListener.class, listener);
262 public void removeChangeListener(ChangeListener listener) {
263 listenerList.remove(ChangeListener.class, listener);
266 protected void fireChangeEvent() {
267 Object[] listeners = listenerList.getListenerList();
268 ChangeEvent e = new ChangeEvent(this);
273 for (int i = listeners.length-2; i>=0; i-=2) {
274 if (listeners[i] == ChangeListener.class) {
275 ((ChangeListener) listeners[i+1]).stateChanged(e);
282 public void componentChanged(ComponentChangeEvent e) {
287 /////////////// Helper methods ///////////////
290 * Return whether this configuration has any motors defined to it.
292 * @return true if this configuration has active motor mounts with motors defined to them.
294 public boolean hasMotors() {
295 for (RocketComponent c: this) {
296 if (c instanceof MotorMount) {
297 MotorMount mount = (MotorMount) c;
298 if (!mount.isMotorMount())
300 if (mount.getMotor(this.motorConfiguration) != null) {
310 * Return the bounds of the current configuration. The bounds are cached.
312 * @return a <code>Collection</code> containing coordinates bouding the rocket.
314 @SuppressWarnings("unchecked")
315 public Collection<Coordinate> getBounds() {
316 if (rocket.getModID() != boundsModID) {
317 boundsModID = rocket.getModID();
318 cachedBounds.clear();
320 double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY;
321 for (RocketComponent component: this) {
322 for (Coordinate c: component.getComponentBounds()) {
323 for (Coordinate coord: component.toAbsolute(c)) {
324 cachedBounds.add(coord);
333 if (Double.isInfinite(minX) || Double.isInfinite(maxX)) {
336 cachedLength = maxX - minX;
339 return (ArrayList<Coordinate>) cachedBounds.clone();
344 * Returns the length of the rocket configuration, from the foremost bound X-coordinate
345 * to the aft-most X-coordinate. The value is cached.
347 * @return the length of the rocket in the X-direction.
349 public double getLength() {
350 if (rocket.getModID() != boundsModID)
351 getBounds(); // Calculates the length
360 * Return an iterator that iterates over the currently active components.
361 * The <code>Rocket</code> and <code>Stage</code> components are not returned,
362 * but instead all components that are within currently active stages.
365 public Iterator<RocketComponent> iterator() {
366 return new ConfigurationIterator();
371 * Return an iterator that iterates over all <code>MotorMount</code>s within the
372 * current configuration that have an active motor.
374 * @return an iterator over active motor mounts.
376 public Iterator<MotorMount> motorIterator() {
377 return new MotorIterator();
382 * Perform a deep-clone. The object references are also cloned and no
383 * listeners are listening on the cloned object.
386 public Configuration clone() {
388 Configuration config = (Configuration) super.clone();
389 config.listenerList = new EventListenerList();
390 config.stages = (BitSet) this.stages.clone();
391 config.cachedBounds = new ArrayList<Coordinate>();
392 config.boundsModID = -1;
393 config.refLengthModID = -1;
394 rocket.addComponentChangeListener(config);
396 } catch (CloneNotSupportedException e) {
397 throw new BugException("BUG: clone not supported!",e);
404 * A class that iterates over all currently active components.
406 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
408 private class ConfigurationIterator implements Iterator<RocketComponent> {
409 Iterator<Iterator<RocketComponent>> iterators;
410 Iterator<RocketComponent> current = null;
412 public ConfigurationIterator() {
413 List<Iterator<RocketComponent>> list = new ArrayList<Iterator<RocketComponent>>();
415 for (RocketComponent stage: rocket.getChildren()) {
416 if (isStageActive(stage)) {
417 list.add(stage.deepIterator());
421 // Get iterators and initialize current
422 iterators = list.iterator();
423 if (iterators.hasNext()) {
424 current = iterators.next();
426 List<RocketComponent> l = Collections.emptyList();
427 current = l.iterator();
433 public boolean hasNext() {
434 if (!current.hasNext())
437 return current.hasNext();
441 public RocketComponent next() {
442 if (!current.hasNext())
445 return current.next();
449 * Get the next iterator that has items. If such an iterator does
450 * not exist, current is left to an empty iterator.
452 private void getNextIterator() {
453 while ((!current.hasNext()) && iterators.hasNext()) {
454 current = iterators.next();
459 public void remove() {
460 throw new UnsupportedOperationException("remove unsupported");
464 private class MotorIterator implements Iterator<MotorMount> {
465 private final Iterator<RocketComponent> iterator;
466 private MotorMount next = null;
468 public MotorIterator() {
469 this.iterator = iterator();
473 public boolean hasNext() {
475 return (next != null);
479 public MotorMount next() {
482 throw new NoSuchElementException("iterator called for too long");
485 MotorMount ret = next;
491 public void remove() {
492 throw new UnsupportedOperationException("remove unsupported");
495 private void getNext() {
498 while (iterator.hasNext()) {
499 RocketComponent c = iterator.next();
500 if (c instanceof MotorMount) {
501 MotorMount mount = (MotorMount) c;
502 if (mount.isMotorMount() && mount.getMotor(motorConfiguration) != null) {