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.Iterator;
9 import java.util.NoSuchElementException;
11 import javax.swing.event.ChangeEvent;
12 import javax.swing.event.ChangeListener;
13 import javax.swing.event.EventListenerList;
15 import net.sf.openrocket.util.BugException;
16 import net.sf.openrocket.util.ChangeSource;
17 import net.sf.openrocket.util.Coordinate;
18 import net.sf.openrocket.util.MathUtil;
19 import net.sf.openrocket.util.Monitorable;
23 * A class defining a rocket configuration, including motors and which stages are active.
25 * TODO: HIGH: Remove motor ignition times from this class.
27 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
29 public class Configuration implements Cloneable, ChangeSource, ComponentChangeListener,
30 Iterable<RocketComponent>, Monitorable {
32 private Rocket rocket;
33 private BitSet stages = new BitSet();
35 private String motorConfiguration = null;
37 private EventListenerList listenerList = new EventListenerList();
41 private int boundsModID = -1;
42 private ArrayList<Coordinate> cachedBounds = new ArrayList<Coordinate>();
43 private double cachedLength = -1;
45 private int refLengthModID = -1;
46 private double cachedRefLength = -1;
49 private int modID = 0;
53 * Create a new configuration with the specified <code>Rocket</code> with
54 * <code>null</code> motor configuration.
56 * @param rocket the rocket
58 public Configuration(Rocket rocket) {
61 rocket.addComponentChangeListener(this);
66 public Rocket getRocket() {
71 public void setAllStages() {
73 stages.set(0, rocket.getStageCount());
79 * Set all stages up to and including the given stage number. For example,
80 * <code>setToStage(0)</code> will set only the first stage active.
82 * @param stage the stage number.
84 public void setToStage(int stage) {
86 stages.set(0, stage + 1, true);
87 // stages.set(stage+1, rocket.getStageCount(), false);
93 * Check whether the up-most stage of the rocket is in this configuration.
95 * @return <code>true</code> if the first stage is active in this configuration.
97 public boolean isHead() {
98 return isStageActive(0);
101 public boolean isStageActive(RocketComponent stage) {
102 if (!(stage instanceof Stage)) {
103 throw new IllegalArgumentException("called with component " + stage);
105 return stages.get(stage.getParent().getChildPosition(stage));
109 public boolean isStageActive(int stage) {
110 if (stage >= rocket.getStageCount())
112 return stages.get(stage);
115 public int getStageCount() {
116 return rocket.getStageCount();
119 public int getActiveStageCount() {
121 int s = rocket.getStageCount();
123 for (int i = 0; i < s; i++) {
130 public int[] getActiveStages() {
131 int stageCount = rocket.getStageCount();
132 List<Integer> active = new ArrayList<Integer>();
135 for (int i = 0; i < stageCount; i++) {
141 ret = new int[active.size()];
142 for (int i = 0; i < ret.length; i++) {
143 ret[i] = active.get(i);
151 * Return the reference length associated with the current configuration. The
152 * reference length type is retrieved from the <code>Rocket</code>.
154 * @return the reference length for this configuration.
156 public double getReferenceLength() {
157 if (rocket.getModID() != refLengthModID) {
158 refLengthModID = rocket.getModID();
159 cachedRefLength = rocket.getReferenceType().getReferenceLength(this);
161 return cachedRefLength;
165 public double getReferenceArea() {
166 return Math.PI * MathUtil.pow2(getReferenceLength() / 2);
170 public String getMotorConfigurationID() {
171 return motorConfiguration;
174 public void setMotorConfigurationID(String id) {
175 if ((motorConfiguration == null && id == null) ||
176 (id != null && id.equals(motorConfiguration)))
179 motorConfiguration = id;
183 public String getMotorConfigurationDescription() {
184 return rocket.getMotorConfigurationNameOrDescription(motorConfiguration);
192 * Removes the listener connection to the rocket and listeners of this object.
193 * This configuration may not be used after a call to this method!
195 public void release() {
196 rocket.removeComponentChangeListener(this);
202 //////////////// Listeners ////////////////
205 public void addChangeListener(ChangeListener listener) {
206 listenerList.add(ChangeListener.class, listener);
210 public void removeChangeListener(ChangeListener listener) {
211 listenerList.remove(ChangeListener.class, listener);
214 protected void fireChangeEvent() {
215 Object[] listeners = listenerList.getListenerList();
216 ChangeEvent e = new ChangeEvent(this);
222 for (int i = listeners.length - 2; i >= 0; i -= 2) {
223 if (listeners[i] == ChangeListener.class) {
224 ((ChangeListener) listeners[i + 1]).stateChanged(e);
231 public void componentChanged(ComponentChangeEvent e) {
236 /////////////// Helper methods ///////////////
239 * Return whether this configuration has any motors defined to it.
241 * @return true if this configuration has active motor mounts with motors defined to them.
243 public boolean hasMotors() {
244 for (RocketComponent c : this) {
245 if (c instanceof MotorMount) {
246 MotorMount mount = (MotorMount) c;
247 if (!mount.isMotorMount())
249 if (mount.getMotor(this.motorConfiguration) != null) {
259 * Return the bounds of the current configuration. The bounds are cached.
261 * @return a <code>Collection</code> containing coordinates bouding the rocket.
263 @SuppressWarnings("unchecked")
264 public Collection<Coordinate> getBounds() {
265 if (rocket.getModID() != boundsModID) {
266 boundsModID = rocket.getModID();
267 cachedBounds.clear();
269 double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY;
270 for (RocketComponent component : this) {
271 for (Coordinate c : component.getComponentBounds()) {
272 for (Coordinate coord : component.toAbsolute(c)) {
273 cachedBounds.add(coord);
282 if (Double.isInfinite(minX) || Double.isInfinite(maxX)) {
285 cachedLength = maxX - minX;
288 return (ArrayList<Coordinate>) cachedBounds.clone();
293 * Returns the length of the rocket configuration, from the foremost bound X-coordinate
294 * to the aft-most X-coordinate. The value is cached.
296 * @return the length of the rocket in the X-direction.
298 public double getLength() {
299 if (rocket.getModID() != boundsModID)
300 getBounds(); // Calculates the length
309 * Return an iterator that iterates over the currently active components.
310 * The <code>Rocket</code> and <code>Stage</code> components are not returned,
311 * but instead all components that are within currently active stages.
314 public Iterator<RocketComponent> iterator() {
315 return new ConfigurationIterator();
320 * Return an iterator that iterates over all <code>MotorMount</code>s within the
321 * current configuration that have an active motor.
323 * @return an iterator over active motor mounts.
325 public Iterator<MotorMount> motorIterator() {
326 return new MotorIterator();
331 * Perform a deep-clone. The object references are also cloned and no
332 * listeners are listening on the cloned object.
335 public Configuration clone() {
337 Configuration config = (Configuration) super.clone();
338 config.listenerList = new EventListenerList();
339 config.stages = (BitSet) this.stages.clone();
340 config.cachedBounds = new ArrayList<Coordinate>();
341 config.boundsModID = -1;
342 config.refLengthModID = -1;
343 rocket.addComponentChangeListener(config);
345 } catch (CloneNotSupportedException e) {
346 throw new BugException("clone not supported!", e);
352 public int getModID() {
353 return modID + rocket.getModID();
358 * A class that iterates over all currently active components.
360 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
362 private class ConfigurationIterator implements Iterator<RocketComponent> {
363 Iterator<Iterator<RocketComponent>> iterators;
364 Iterator<RocketComponent> current = null;
366 public ConfigurationIterator() {
367 List<Iterator<RocketComponent>> list = new ArrayList<Iterator<RocketComponent>>();
369 for (RocketComponent stage : rocket.getChildren()) {
370 if (isStageActive(stage)) {
371 list.add(stage.deepIterator());
375 // Get iterators and initialize current
376 iterators = list.iterator();
377 if (iterators.hasNext()) {
378 current = iterators.next();
380 List<RocketComponent> l = Collections.emptyList();
381 current = l.iterator();
387 public boolean hasNext() {
388 if (!current.hasNext())
391 return current.hasNext();
395 public RocketComponent next() {
396 if (!current.hasNext())
399 return current.next();
403 * Get the next iterator that has items. If such an iterator does
404 * not exist, current is left to an empty iterator.
406 private void getNextIterator() {
407 while ((!current.hasNext()) && iterators.hasNext()) {
408 current = iterators.next();
413 public void remove() {
414 throw new UnsupportedOperationException("remove unsupported");
418 private class MotorIterator implements Iterator<MotorMount> {
419 private final Iterator<RocketComponent> iterator;
420 private MotorMount next = null;
422 public MotorIterator() {
423 this.iterator = iterator();
427 public boolean hasNext() {
429 return (next != null);
433 public MotorMount next() {
436 throw new NoSuchElementException("iterator called for too long");
439 MotorMount ret = next;
445 public void remove() {
446 throw new UnsupportedOperationException("remove unsupported");
449 private void getNext() {
452 while (iterator.hasNext()) {
453 RocketComponent c = iterator.next();
454 if (c instanceof MotorMount) {
455 MotorMount mount = (MotorMount) c;
456 if (mount.isMotorMount() && mount.getMotor(motorConfiguration) != null) {