bug fixes
[debian/openrocket] / src / net / sf / openrocket / rocketcomponent / Configuration.java
1 package net.sf.openrocket.rocketcomponent;
2
3 import java.util.BitSet;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.Iterator;
7 import java.util.List;
8 import java.util.NoSuchElementException;
9
10 import javax.swing.event.ChangeEvent;
11 import javax.swing.event.ChangeListener;
12 import javax.swing.event.EventListenerList;
13
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;
20
21
22 /**
23  * A class defining a rocket configuration, including motors and which stages are active.
24  * 
25  * TODO: HIGH: Remove motor ignition times from this class.
26  * 
27  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
28  */
29 public class Configuration implements Cloneable, ChangeSource, ComponentChangeListener,
30                 Iterable<RocketComponent>, Monitorable {
31         
32         private Rocket rocket;
33         private BitSet stages = new BitSet();
34         
35         private String motorConfiguration = null;
36         
37         private EventListenerList listenerList = new EventListenerList();
38         
39
40         /* Cached data */
41         private int boundsModID = -1;
42         private ArrayList<Coordinate> cachedBounds = new ArrayList<Coordinate>();
43         private double cachedLength = -1;
44         
45         private int refLengthModID = -1;
46         private double cachedRefLength = -1;
47         
48
49         private int modID = 0;
50         
51         
52         /**
53          * Create a new configuration with the specified <code>Rocket</code> with 
54          * <code>null</code> motor configuration.
55          * 
56          * @param rocket  the rocket
57          */
58         public Configuration(Rocket rocket) {
59                 this.rocket = rocket;
60                 setAllStages();
61                 rocket.addComponentChangeListener(this);
62         }
63         
64         
65
66         public Rocket getRocket() {
67                 return rocket;
68         }
69         
70         
71         public void setAllStages() {
72                 stages.clear();
73                 stages.set(0, rocket.getStageCount());
74                 fireChangeEvent();
75         }
76         
77         
78         /**
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.
81          * 
82          * @param stage         the stage number.
83          */
84         public void setToStage(int stage) {
85                 stages.clear();
86                 stages.set(0, stage + 1, true);
87                 //              stages.set(stage+1, rocket.getStageCount(), false);
88                 fireChangeEvent();
89         }
90         
91         
92         /**
93          * Check whether the up-most stage of the rocket is in this configuration.
94          * 
95          * @return      <code>true</code> if the first stage is active in this configuration.
96          */
97         public boolean isHead() {
98                 return isStageActive(0);
99         }
100         
101         
102
103         /**
104          * Check whether the stage specified by the index is active.
105          */
106         public boolean isStageActive(int stage) {
107                 if (stage >= rocket.getStageCount())
108                         return false;
109                 return stages.get(stage);
110         }
111         
112         public int getStageCount() {
113                 return rocket.getStageCount();
114         }
115         
116         public int getActiveStageCount() {
117                 int count = 0;
118                 int s = rocket.getStageCount();
119                 
120                 for (int i = 0; i < s; i++) {
121                         if (stages.get(i))
122                                 count++;
123                 }
124                 return count;
125         }
126         
127         public int[] getActiveStages() {
128                 int stageCount = rocket.getStageCount();
129                 List<Integer> active = new ArrayList<Integer>();
130                 int[] ret;
131                 
132                 for (int i = 0; i < stageCount; i++) {
133                         if (stages.get(i)) {
134                                 active.add(i);
135                         }
136                 }
137                 
138                 ret = new int[active.size()];
139                 for (int i = 0; i < ret.length; i++) {
140                         ret[i] = active.get(i);
141                 }
142                 
143                 return ret;
144         }
145         
146         
147         /**
148          * Return the reference length associated with the current configuration.  The 
149          * reference length type is retrieved from the <code>Rocket</code>.
150          * 
151          * @return  the reference length for this configuration.
152          */
153         public double getReferenceLength() {
154                 if (rocket.getModID() != refLengthModID) {
155                         refLengthModID = rocket.getModID();
156                         cachedRefLength = rocket.getReferenceType().getReferenceLength(this);
157                 }
158                 return cachedRefLength;
159         }
160         
161         
162         public double getReferenceArea() {
163                 return Math.PI * MathUtil.pow2(getReferenceLength() / 2);
164         }
165         
166         
167         public String getMotorConfigurationID() {
168                 return motorConfiguration;
169         }
170         
171         public void setMotorConfigurationID(String id) {
172                 if ((motorConfiguration == null && id == null) ||
173                                 (id != null && id.equals(motorConfiguration)))
174                         return;
175                 
176                 motorConfiguration = id;
177                 fireChangeEvent();
178         }
179         
180         public String getMotorConfigurationDescription() {
181                 return rocket.getMotorConfigurationNameOrDescription(motorConfiguration);
182         }
183         
184         
185
186
187
188         /**
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!
191          */
192         public void release() {
193                 rocket.removeComponentChangeListener(this);
194                 listenerList = null;
195                 rocket = null;
196         }
197         
198         
199         ////////////////  Listeners  ////////////////
200         
201         @Override
202         public void addChangeListener(ChangeListener listener) {
203                 listenerList.add(ChangeListener.class, listener);
204         }
205         
206         @Override
207         public void removeChangeListener(ChangeListener listener) {
208                 listenerList.remove(ChangeListener.class, listener);
209         }
210         
211         protected void fireChangeEvent() {
212                 Object[] listeners = listenerList.getListenerList();
213                 ChangeEvent e = new ChangeEvent(this);
214                 
215                 this.modID++;
216                 boundsModID = -1;
217                 refLengthModID = -1;
218                 
219                 for (int i = listeners.length - 2; i >= 0; i -= 2) {
220                         if (listeners[i] == ChangeListener.class) {
221                                 ((ChangeListener) listeners[i + 1]).stateChanged(e);
222                         }
223                 }
224         }
225         
226         
227         @Override
228         public void componentChanged(ComponentChangeEvent e) {
229                 fireChangeEvent();
230         }
231         
232         
233         ///////////////  Helper methods  ///////////////
234         
235         /**
236          * Return whether this configuration has any motors defined to it.
237          * 
238          * @return  true if this configuration has active motor mounts with motors defined to them.
239          */
240         public boolean hasMotors() {
241                 for (RocketComponent c : this) {
242                         if (c instanceof MotorMount) {
243                                 MotorMount mount = (MotorMount) c;
244                                 if (!mount.isMotorMount())
245                                         continue;
246                                 if (mount.getMotor(this.motorConfiguration) != null) {
247                                         return true;
248                                 }
249                         }
250                 }
251                 return false;
252         }
253         
254         
255         /**
256          * Return whether a component is in the currently active stages.
257          */
258         public boolean isComponentActive(final RocketComponent c) {
259                 int stage = c.getStageNumber();
260                 return isStageActive(stage);
261         }
262         
263         
264         /**
265          * Return the bounds of the current configuration.  The bounds are cached.
266          * 
267          * @return      a <code>Collection</code> containing coordinates bouding the rocket.
268          */
269         public Collection<Coordinate> getBounds() {
270                 if (rocket.getModID() != boundsModID) {
271                         boundsModID = rocket.getModID();
272                         cachedBounds.clear();
273                         
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);
279                                                 if (coord.x < minX)
280                                                         minX = coord.x;
281                                                 if (coord.x > maxX)
282                                                         maxX = coord.x;
283                                         }
284                                 }
285                         }
286                         
287                         if (Double.isInfinite(minX) || Double.isInfinite(maxX)) {
288                                 cachedLength = 0;
289                         } else {
290                                 cachedLength = maxX - minX;
291                         }
292                 }
293                 return cachedBounds.clone();
294         }
295         
296         
297         /**
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.
300          * 
301          * @return      the length of the rocket in the X-direction.
302          */
303         public double getLength() {
304                 if (rocket.getModID() != boundsModID)
305                         getBounds(); // Calculates the length
306                         
307                 return cachedLength;
308         }
309         
310         
311
312
313         /**
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.
317          */
318         @Override
319         public Iterator<RocketComponent> iterator() {
320                 return new ConfigurationIterator();
321         }
322         
323         
324         /**
325          * Return an iterator that iterates over all <code>MotorMount</code>s within the
326          * current configuration that have an active motor.
327          * 
328          * @return  an iterator over active motor mounts.
329          */
330         public Iterator<MotorMount> motorIterator() {
331                 return new MotorIterator();
332         }
333         
334         
335         /**
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.
338          */
339         @Override
340         public Configuration clone() {
341                 try {
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);
349                         return config;
350                 } catch (CloneNotSupportedException e) {
351                         throw new BugException("clone not supported!", e);
352                 }
353         }
354         
355         
356         @Override
357         public int getModID() {
358                 return modID + rocket.getModID();
359         }
360         
361         
362         /**
363          * A class that iterates over all currently active components.
364          * 
365          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
366          */
367         private class ConfigurationIterator implements Iterator<RocketComponent> {
368                 Iterator<Iterator<RocketComponent>> iterators;
369                 Iterator<RocketComponent> current = null;
370                 
371                 public ConfigurationIterator() {
372                         List<Iterator<RocketComponent>> list = new ArrayList<Iterator<RocketComponent>>();
373                         
374                         for (RocketComponent stage : rocket.getChildren()) {
375                                 if (isComponentActive(stage)) {
376                                         list.add(stage.iterator(false));
377                                 }
378                         }
379                         
380                         // Get iterators and initialize current
381                         iterators = list.iterator();
382                         if (iterators.hasNext()) {
383                                 current = iterators.next();
384                         } else {
385                                 List<RocketComponent> l = Collections.emptyList();
386                                 current = l.iterator();
387                         }
388                 }
389                 
390                 
391                 @Override
392                 public boolean hasNext() {
393                         if (!current.hasNext())
394                                 getNextIterator();
395                         
396                         return current.hasNext();
397                 }
398                 
399                 @Override
400                 public RocketComponent next() {
401                         if (!current.hasNext())
402                                 getNextIterator();
403                         
404                         return current.next();
405                 }
406                 
407                 /**
408                  * Get the next iterator that has items.  If such an iterator does
409                  * not exist, current is left to an empty iterator.
410                  */
411                 private void getNextIterator() {
412                         while ((!current.hasNext()) && iterators.hasNext()) {
413                                 current = iterators.next();
414                         }
415                 }
416                 
417                 @Override
418                 public void remove() {
419                         throw new UnsupportedOperationException("remove unsupported");
420                 }
421         }
422         
423         private class MotorIterator implements Iterator<MotorMount> {
424                 private final Iterator<RocketComponent> iterator;
425                 private MotorMount next = null;
426                 
427                 public MotorIterator() {
428                         this.iterator = iterator();
429                 }
430                 
431                 @Override
432                 public boolean hasNext() {
433                         getNext();
434                         return (next != null);
435                 }
436                 
437                 @Override
438                 public MotorMount next() {
439                         getNext();
440                         if (next == null) {
441                                 throw new NoSuchElementException("iterator called for too long");
442                         }
443                         
444                         MotorMount ret = next;
445                         next = null;
446                         return ret;
447                 }
448                 
449                 @Override
450                 public void remove() {
451                         throw new UnsupportedOperationException("remove unsupported");
452                 }
453                 
454                 private void getNext() {
455                         if (next != null)
456                                 return;
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) {
462                                                 next = mount;
463                                                 return;
464                                         }
465                                 }
466                         }
467                 }
468         }
469         
470 }