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.Iterator;
8 import java.util.NoSuchElementException;
10 import javax.swing.event.ChangeEvent;
11 import javax.swing.event.ChangeListener;
12 import javax.swing.event.EventListenerList;
14 import net.sf.openrocket.util.ArrayList;
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);
104 * Check whether the stage specified by the index is active.
106 public boolean isStageActive(int stage) {
107 if (stage >= rocket.getStageCount())
109 return stages.get(stage);
112 public int getStageCount() {
113 return rocket.getStageCount();
116 public int getActiveStageCount() {
118 int s = rocket.getStageCount();
120 for (int i = 0; i < s; i++) {
127 public int[] getActiveStages() {
128 int stageCount = rocket.getStageCount();
129 List<Integer> active = new ArrayList<Integer>();
132 for (int i = 0; i < stageCount; i++) {
138 ret = new int[active.size()];
139 for (int i = 0; i < ret.length; i++) {
140 ret[i] = active.get(i);
148 * Return the reference length associated with the current configuration. The
149 * reference length type is retrieved from the <code>Rocket</code>.
151 * @return the reference length for this configuration.
153 public double getReferenceLength() {
154 if (rocket.getModID() != refLengthModID) {
155 refLengthModID = rocket.getModID();
156 cachedRefLength = rocket.getReferenceType().getReferenceLength(this);
158 return cachedRefLength;
162 public double getReferenceArea() {
163 return Math.PI * MathUtil.pow2(getReferenceLength() / 2);
167 public String getMotorConfigurationID() {
168 return motorConfiguration;
171 public void setMotorConfigurationID(String id) {
172 if ((motorConfiguration == null && id == null) ||
173 (id != null && id.equals(motorConfiguration)))
176 motorConfiguration = id;
180 public String getMotorConfigurationDescription() {
181 return rocket.getMotorConfigurationNameOrDescription(motorConfiguration);
189 * Removes the listener connection to the rocket and listeners of this object.
190 * This configuration may not be used after a call to this method!
192 public void release() {
193 rocket.removeComponentChangeListener(this);
199 //////////////// Listeners ////////////////
202 public void addChangeListener(ChangeListener listener) {
203 listenerList.add(ChangeListener.class, listener);
207 public void removeChangeListener(ChangeListener listener) {
208 listenerList.remove(ChangeListener.class, listener);
211 protected void fireChangeEvent() {
212 Object[] listeners = listenerList.getListenerList();
213 ChangeEvent e = new ChangeEvent(this);
219 for (int i = listeners.length - 2; i >= 0; i -= 2) {
220 if (listeners[i] == ChangeListener.class) {
221 ((ChangeListener) listeners[i + 1]).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 EventListenerList();
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) {