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.ChangeSource;
17 import net.sf.openrocket.util.Coordinate;
18 import net.sf.openrocket.util.MathUtil;
21 public class Configuration implements Cloneable, ChangeSource, ComponentChangeListener,
22 Iterable<RocketComponent> {
24 public static final double DEFAULT_IGNITION_TIME = Double.MAX_VALUE;
27 private Rocket rocket;
28 private BitSet stages = new BitSet();
30 private String motorConfiguration = null;
32 private EventListenerList listenerList = new EventListenerList();
34 private final HashMap<MotorMount, Double> ignitionTimes =
35 new HashMap<MotorMount, Double>();
39 private int boundsModID = -1;
40 private ArrayList<Coordinate> cachedBounds = new ArrayList<Coordinate>();
41 private double cachedLength = -1;
43 private int refLengthModID = -1;
44 private double cachedRefLength = -1;
49 * Create a new configuration with the specified <code>Rocket</code> with
50 * <code>null</code> motor configuration.
52 * @param rocket the rocket
54 public Configuration(Rocket rocket) {
57 rocket.addComponentChangeListener(this);
62 * Create a new configuration with the specified <code>Rocket</code> and motor
65 * @param rocket the rocket.
66 * @param motorID the motor configuration ID to use.
68 public Configuration(Rocket rocket, String motorID) {
70 this.motorConfiguration = motorID;
72 rocket.addComponentChangeListener(this);
78 public Rocket getRocket() {
83 public void setAllStages() {
85 stages.set(0,rocket.getStageCount());
91 * Set all stages up to and including the given stage number. For example,
92 * <code>setToStage(0)</code> will set only the first stage active.
94 * @param stage the stage number.
96 public void setToStage(int stage) {
98 stages.set(0, stage+1, true);
99 // stages.set(stage+1, rocket.getStageCount(), false);
105 * Check whether the up-most stage of the rocket is in this configuration.
107 * @return <code>true</code> if the first stage is active in this configuration.
109 public boolean isHead() {
110 return isStageActive(0);
113 public boolean isStageActive(RocketComponent stage) {
114 if (!(stage instanceof Stage)) {
115 throw new IllegalArgumentException("called with component "+stage);
117 return stages.get(stage.getParent().getChildPosition(stage));
121 public boolean isStageActive(int stage) {
122 if (stage >= rocket.getStageCount())
124 return stages.get(stage);
127 public int getStageCount() {
128 return rocket.getStageCount();
131 public int getActiveStageCount() {
133 int s = rocket.getStageCount();
135 for (int i=0; i < s; i++) {
142 public int[] getActiveStages() {
143 int stageCount = rocket.getStageCount();
144 List<Integer> active = new ArrayList<Integer>();
147 for (int i=0; i < stageCount; i++) {
153 ret = new int[active.size()];
154 for (int i=0; i < ret.length; i++) {
155 ret[i] = active.get(i);
163 * Return the reference length associated with the current configuration. The
164 * reference length type is retrieved from the <code>Rocket</code>.
166 * @return the reference length for this configuration.
168 public double getReferenceLength() {
169 if (rocket.getModID() != refLengthModID) {
170 refLengthModID = rocket.getModID();
171 cachedRefLength = rocket.getReferenceType().getReferenceLength(this);
173 return cachedRefLength;
177 public double getReferenceArea() {
178 return Math.PI * MathUtil.pow2(getReferenceLength()/2);
182 public String getMotorConfigurationID() {
183 return motorConfiguration;
186 public void setMotorConfigurationID(String id) {
187 if ((motorConfiguration == null && id == null) ||
188 (id != null && id.equals(motorConfiguration)))
191 motorConfiguration = id;
195 public String getMotorConfigurationDescription() {
196 return rocket.getMotorConfigurationDescription(motorConfiguration);
202 * Clear all motor ignition times. All values are reset to their default of
203 * {@link #DEFAULT_IGNITION_TIME}.
205 public void resetIgnitionTimes() {
206 ignitionTimes.clear();
210 * Set the ignition time of the motor in the specified motor mount. Negative or NaN
211 * time values will cause an <code>IllegalArgumentException</code>.
213 * @param mount the motor mount to specify.
214 * @param time the time at which to ignite the motors.
215 * @throws IllegalArgumentException if <code>time</code> is negative of NaN.
217 public void setIgnitionTime(MotorMount mount, double time) {
219 throw new IllegalArgumentException("time is negative: "+time);
221 ignitionTimes.put(mount, time);
222 // TODO: MEDIUM: Should this fire events?
226 * Return the ignition time of the motor in the specified motor mount. If no time
227 * has been specified, returns {@link #DEFAULT_IGNITION_TIME} as the default.
229 * @param mount the motor mount.
230 * @return ignition time of the motors in the mount.
232 public double getIgnitionTime(MotorMount mount) {
233 Double d = ignitionTimes.get(mount);
235 return DEFAULT_IGNITION_TIME;
243 * Removes the listener connection to the rocket and listeners of this object.
244 * This configuration may not be used after a call to this method!
246 public void release() {
247 rocket.removeComponentChangeListener(this);
253 //////////////// Listeners ////////////////
256 public void addChangeListener(ChangeListener listener) {
257 listenerList.add(ChangeListener.class, listener);
261 public void removeChangeListener(ChangeListener listener) {
262 listenerList.remove(ChangeListener.class, listener);
265 protected void fireChangeEvent() {
266 Object[] listeners = listenerList.getListenerList();
267 ChangeEvent e = new ChangeEvent(this);
272 for (int i = listeners.length-2; i>=0; i-=2) {
273 if (listeners[i] == ChangeListener.class) {
274 ((ChangeListener) listeners[i+1]).stateChanged(e);
281 public void componentChanged(ComponentChangeEvent e) {
286 /////////////// Helper methods ///////////////
289 * Return whether this configuration has any motors defined to it.
291 * @return true if this configuration has active motor mounts with motors defined to them.
293 public boolean hasMotors() {
294 for (RocketComponent c: this) {
295 if (c instanceof MotorMount) {
296 MotorMount mount = (MotorMount) c;
297 if (!mount.isMotorMount())
299 if (mount.getMotor(this.motorConfiguration) != null) {
309 * Return the bounds of the current configuration. The bounds are cached.
311 * @return a <code>Collection</code> containing coordinates bouding the rocket.
313 @SuppressWarnings("unchecked")
314 public Collection<Coordinate> getBounds() {
315 if (rocket.getModID() != boundsModID) {
316 boundsModID = rocket.getModID();
317 cachedBounds.clear();
319 double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY;
320 for (RocketComponent component: this) {
321 for (Coordinate c: component.getComponentBounds()) {
322 for (Coordinate coord: component.toAbsolute(c)) {
323 cachedBounds.add(coord);
332 if (Double.isInfinite(minX) || Double.isInfinite(maxX)) {
335 cachedLength = maxX - minX;
338 return (ArrayList<Coordinate>) cachedBounds.clone();
343 * Returns the length of the rocket configuration, from the foremost bound X-coordinate
344 * to the aft-most X-coordinate. The value is cached.
346 * @return the length of the rocket in the X-direction.
348 public double getLength() {
349 if (rocket.getModID() != boundsModID)
350 getBounds(); // Calculates the length
359 * Return an iterator that iterates over the currently active components.
360 * The <code>Rocket</code> and <code>Stage</code> components are not returned,
361 * but instead all components that are within currently active stages.
364 public Iterator<RocketComponent> iterator() {
365 return new ConfigurationIterator();
370 * Return an iterator that iterates over all <code>MotorMount</code>s within the
371 * current configuration that have an active motor.
373 * @return an iterator over active motor mounts.
375 public Iterator<MotorMount> motorIterator() {
376 return new MotorIterator();
381 * Perform a deep-clone. The object references are also cloned and no
382 * listeners are listening on the cloned object.
385 public Configuration clone() {
387 Configuration config = (Configuration) super.clone();
388 config.listenerList = new EventListenerList();
389 config.stages = (BitSet) this.stages.clone();
390 config.cachedBounds = new ArrayList<Coordinate>();
391 config.boundsModID = -1;
392 config.refLengthModID = -1;
393 rocket.addComponentChangeListener(config);
395 } catch (CloneNotSupportedException e) {
396 throw new RuntimeException("BUG: clone not supported!",e);
403 * A class that iterates over all currently active components.
405 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
407 private class ConfigurationIterator implements Iterator<RocketComponent> {
408 Iterator<Iterator<RocketComponent>> iterators;
409 Iterator<RocketComponent> current = null;
411 public ConfigurationIterator() {
412 List<Iterator<RocketComponent>> list = new ArrayList<Iterator<RocketComponent>>();
414 for (RocketComponent stage: rocket.getChildren()) {
415 if (isStageActive(stage)) {
416 list.add(stage.deepIterator());
420 // Get iterators and initialize current
421 iterators = list.iterator();
422 if (iterators.hasNext()) {
423 current = iterators.next();
425 List<RocketComponent> l = Collections.emptyList();
426 current = l.iterator();
432 public boolean hasNext() {
433 if (!current.hasNext())
436 return current.hasNext();
440 public RocketComponent next() {
441 if (!current.hasNext())
444 return current.next();
448 * Get the next iterator that has items. If such an iterator does
449 * not exist, current is left to an empty iterator.
451 private void getNextIterator() {
452 while ((!current.hasNext()) && iterators.hasNext()) {
453 current = iterators.next();
458 public void remove() {
459 throw new UnsupportedOperationException("remove unsupported");
463 private class MotorIterator implements Iterator<MotorMount> {
464 private final Iterator<RocketComponent> iterator;
465 private MotorMount next = null;
467 public MotorIterator() {
468 this.iterator = iterator();
472 public boolean hasNext() {
474 return (next != null);
478 public MotorMount next() {
481 throw new NoSuchElementException("iterator called for too long");
484 MotorMount ret = next;
490 public void remove() {
491 throw new UnsupportedOperationException("remove unsupported");
494 private void getNext() {
497 while (iterator.hasNext()) {
498 RocketComponent c = iterator.next();
499 if (c instanceof MotorMount) {
500 MotorMount mount = (MotorMount) c;
501 if (mount.isMotorMount() && mount.getMotor(motorConfiguration) != null) {