1 package net.sf.openrocket.rocketcomponent;
3 import java.util.BitSet;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.EventListener;
7 import java.util.EventObject;
8 import java.util.Iterator;
10 import java.util.NoSuchElementException;
12 import net.sf.openrocket.util.ArrayList;
13 import net.sf.openrocket.util.BugException;
14 import net.sf.openrocket.util.ChangeSource;
15 import net.sf.openrocket.util.Coordinate;
16 import net.sf.openrocket.util.MathUtil;
17 import net.sf.openrocket.util.Monitorable;
18 import net.sf.openrocket.util.StateChangeListener;
22 * A class defining a rocket configuration, including motors and which stages are active.
24 * TODO: HIGH: Remove motor ignition times from this class.
26 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
28 public class Configuration implements Cloneable, ChangeSource, ComponentChangeListener,
29 Iterable<RocketComponent>, Monitorable {
31 private Rocket rocket;
32 private BitSet stages = new BitSet();
34 private String motorConfiguration = null;
36 private List<EventListener> listenerList = new ArrayList<EventListener>();
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;
48 private int modID = 0;
52 * Create a new configuration with the specified <code>Rocket</code> with
53 * <code>null</code> motor configuration.
55 * @param rocket the rocket
57 public Configuration(Rocket rocket) {
60 rocket.addComponentChangeListener(this);
65 public Rocket getRocket() {
70 public void setAllStages() {
72 stages.set(0, rocket.getStageCount());
78 * Set all stages up to and including the given stage number. For example,
79 * <code>setToStage(0)</code> will set only the first stage active.
81 * @param stage the stage number.
83 public void setToStage(int stage) {
85 stages.set(0, stage + 1, true);
86 // stages.set(stage+1, rocket.getStageCount(), false);
92 * Check whether the up-most stage of the rocket is in this configuration.
94 * @return <code>true</code> if the first stage is active in this configuration.
96 public boolean isHead() {
97 return isStageActive(0);
103 * Check whether the stage specified by the index is active.
105 public boolean isStageActive(int stage) {
106 if (stage >= rocket.getStageCount())
108 return stages.get(stage);
111 public int getStageCount() {
112 return rocket.getStageCount();
115 public int getActiveStageCount() {
117 int s = rocket.getStageCount();
119 for (int i = 0; i < s; i++) {
126 public int[] getActiveStages() {
127 int stageCount = rocket.getStageCount();
128 List<Integer> active = new ArrayList<Integer>();
131 for (int i = 0; i < stageCount; i++) {
137 ret = new int[active.size()];
138 for (int i = 0; i < ret.length; i++) {
139 ret[i] = active.get(i);
147 * Return the reference length associated with the current configuration. The
148 * reference length type is retrieved from the <code>Rocket</code>.
150 * @return the reference length for this configuration.
152 public double getReferenceLength() {
153 if (rocket.getModID() != refLengthModID) {
154 refLengthModID = rocket.getModID();
155 cachedRefLength = rocket.getReferenceType().getReferenceLength(this);
157 return cachedRefLength;
161 public double getReferenceArea() {
162 return Math.PI * MathUtil.pow2(getReferenceLength() / 2);
166 public String getMotorConfigurationID() {
167 return motorConfiguration;
170 public void setMotorConfigurationID(String id) {
171 if ((motorConfiguration == null && id == null) ||
172 (id != null && id.equals(motorConfiguration)))
175 motorConfiguration = id;
179 public String getMotorConfigurationDescription() {
180 return rocket.getMotorConfigurationNameOrDescription(motorConfiguration);
188 * Removes the listener connection to the rocket and listeners of this object.
189 * This configuration may not be used after a call to this method!
191 public void release() {
192 rocket.removeComponentChangeListener(this);
198 //////////////// Listeners ////////////////
201 public void addChangeListener(EventListener listener) {
202 listenerList.add(listener);
206 public void removeChangeListener(EventListener listener) {
207 listenerList.remove(listener);
210 protected void fireChangeEvent() {
211 EventObject e = new EventObject(this);
217 // Copy the list before iterating to prevent concurrent modification exceptions.
218 EventListener[] listeners = listenerList.toArray(new EventListener[0]);
219 for (EventListener l : listeners) {
220 if ( l instanceof StateChangeListener ) {
221 ((StateChangeListener)l).stateChanged(e);
228 public void componentChanged(ComponentChangeEvent e) {
233 /////////////// Helper methods ///////////////
236 * Return whether this configuration has any motors defined to it.
238 * @return true if this configuration has active motor mounts with motors defined to them.
240 public boolean hasMotors() {
241 for (RocketComponent c : this) {
242 if (c instanceof MotorMount) {
243 MotorMount mount = (MotorMount) c;
244 if (!mount.isMotorMount())
246 if (mount.getMotor(this.motorConfiguration) != null) {
256 * Return whether a component is in the currently active stages.
258 public boolean isComponentActive(final RocketComponent c) {
259 int stage = c.getStageNumber();
260 return isStageActive(stage);
265 * Return the bounds of the current configuration. The bounds are cached.
267 * @return a <code>Collection</code> containing coordinates bouding the rocket.
269 public Collection<Coordinate> getBounds() {
270 if (rocket.getModID() != boundsModID) {
271 boundsModID = rocket.getModID();
272 cachedBounds.clear();
274 double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY;
275 for (RocketComponent component : this) {
276 for (Coordinate c : component.getComponentBounds()) {
277 for (Coordinate coord : component.toAbsolute(c)) {
278 cachedBounds.add(coord);
287 if (Double.isInfinite(minX) || Double.isInfinite(maxX)) {
290 cachedLength = maxX - minX;
293 return cachedBounds.clone();
298 * Returns the length of the rocket configuration, from the foremost bound X-coordinate
299 * to the aft-most X-coordinate. The value is cached.
301 * @return the length of the rocket in the X-direction.
303 public double getLength() {
304 if (rocket.getModID() != boundsModID)
305 getBounds(); // Calculates the length
314 * Return an iterator that iterates over the currently active components.
315 * The <code>Rocket</code> and <code>Stage</code> components are not returned,
316 * but instead all components that are within currently active stages.
319 public Iterator<RocketComponent> iterator() {
320 return new ConfigurationIterator();
325 * Return an iterator that iterates over all <code>MotorMount</code>s within the
326 * current configuration that have an active motor.
328 * @return an iterator over active motor mounts.
330 public Iterator<MotorMount> motorIterator() {
331 return new MotorIterator();
336 * Perform a deep-clone. The object references are also cloned and no
337 * listeners are listening on the cloned object. The rocket instance remains the same.
340 public Configuration clone() {
342 Configuration config = (Configuration) super.clone();
343 config.listenerList = new ArrayList<EventListener>();
344 config.stages = (BitSet) this.stages.clone();
345 config.cachedBounds = new ArrayList<Coordinate>();
346 config.boundsModID = -1;
347 config.refLengthModID = -1;
348 rocket.addComponentChangeListener(config);
350 } catch (CloneNotSupportedException e) {
351 throw new BugException("clone not supported!", e);
357 public int getModID() {
358 return modID + rocket.getModID();
363 * A class that iterates over all currently active components.
365 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
367 private class ConfigurationIterator implements Iterator<RocketComponent> {
368 Iterator<Iterator<RocketComponent>> iterators;
369 Iterator<RocketComponent> current = null;
371 public ConfigurationIterator() {
372 List<Iterator<RocketComponent>> list = new ArrayList<Iterator<RocketComponent>>();
374 for (RocketComponent stage : rocket.getChildren()) {
375 if (isComponentActive(stage)) {
376 list.add(stage.iterator(false));
380 // Get iterators and initialize current
381 iterators = list.iterator();
382 if (iterators.hasNext()) {
383 current = iterators.next();
385 List<RocketComponent> l = Collections.emptyList();
386 current = l.iterator();
392 public boolean hasNext() {
393 if (!current.hasNext())
396 return current.hasNext();
400 public RocketComponent next() {
401 if (!current.hasNext())
404 return current.next();
408 * Get the next iterator that has items. If such an iterator does
409 * not exist, current is left to an empty iterator.
411 private void getNextIterator() {
412 while ((!current.hasNext()) && iterators.hasNext()) {
413 current = iterators.next();
418 public void remove() {
419 throw new UnsupportedOperationException("remove unsupported");
423 private class MotorIterator implements Iterator<MotorMount> {
424 private final Iterator<RocketComponent> iterator;
425 private MotorMount next = null;
427 public MotorIterator() {
428 this.iterator = iterator();
432 public boolean hasNext() {
434 return (next != null);
438 public MotorMount next() {
441 throw new NoSuchElementException("iterator called for too long");
444 MotorMount ret = next;
450 public void remove() {
451 throw new UnsupportedOperationException("remove unsupported");
454 private void getNext() {
457 while (iterator.hasNext()) {
458 RocketComponent c = iterator.next();
459 if (c instanceof MotorMount) {
460 MotorMount mount = (MotorMount) c;
461 if (mount.isMotorMount() && mount.getMotor(motorConfiguration) != null) {