major optimization updates
[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         public boolean isStageActive(RocketComponent stage) {
102                 if (!(stage instanceof Stage)) {
103                         throw new IllegalArgumentException("called with component " + stage);
104                 }
105                 return stages.get(stage.getParent().getChildPosition(stage));
106         }
107         
108         
109         public boolean isStageActive(int stage) {
110                 if (stage >= rocket.getStageCount())
111                         return false;
112                 return stages.get(stage);
113         }
114         
115         public int getStageCount() {
116                 return rocket.getStageCount();
117         }
118         
119         public int getActiveStageCount() {
120                 int count = 0;
121                 int s = rocket.getStageCount();
122                 
123                 for (int i = 0; i < s; i++) {
124                         if (stages.get(i))
125                                 count++;
126                 }
127                 return count;
128         }
129         
130         public int[] getActiveStages() {
131                 int stageCount = rocket.getStageCount();
132                 List<Integer> active = new ArrayList<Integer>();
133                 int[] ret;
134                 
135                 for (int i = 0; i < stageCount; i++) {
136                         if (stages.get(i)) {
137                                 active.add(i);
138                         }
139                 }
140                 
141                 ret = new int[active.size()];
142                 for (int i = 0; i < ret.length; i++) {
143                         ret[i] = active.get(i);
144                 }
145                 
146                 return ret;
147         }
148         
149         
150         /**
151          * Return the reference length associated with the current configuration.  The 
152          * reference length type is retrieved from the <code>Rocket</code>.
153          * 
154          * @return  the reference length for this configuration.
155          */
156         public double getReferenceLength() {
157                 if (rocket.getModID() != refLengthModID) {
158                         refLengthModID = rocket.getModID();
159                         cachedRefLength = rocket.getReferenceType().getReferenceLength(this);
160                 }
161                 return cachedRefLength;
162         }
163         
164         
165         public double getReferenceArea() {
166                 return Math.PI * MathUtil.pow2(getReferenceLength() / 2);
167         }
168         
169         
170         public String getMotorConfigurationID() {
171                 return motorConfiguration;
172         }
173         
174         public void setMotorConfigurationID(String id) {
175                 if ((motorConfiguration == null && id == null) ||
176                                 (id != null && id.equals(motorConfiguration)))
177                         return;
178                 
179                 motorConfiguration = id;
180                 fireChangeEvent();
181         }
182         
183         public String getMotorConfigurationDescription() {
184                 return rocket.getMotorConfigurationNameOrDescription(motorConfiguration);
185         }
186         
187         
188
189
190
191         /**
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!
194          */
195         public void release() {
196                 rocket.removeComponentChangeListener(this);
197                 listenerList = null;
198                 rocket = null;
199         }
200         
201         
202         ////////////////  Listeners  ////////////////
203         
204         @Override
205         public void addChangeListener(ChangeListener listener) {
206                 listenerList.add(ChangeListener.class, listener);
207         }
208         
209         @Override
210         public void removeChangeListener(ChangeListener listener) {
211                 listenerList.remove(ChangeListener.class, listener);
212         }
213         
214         protected void fireChangeEvent() {
215                 Object[] listeners = listenerList.getListenerList();
216                 ChangeEvent e = new ChangeEvent(this);
217                 
218                 this.modID++;
219                 boundsModID = -1;
220                 refLengthModID = -1;
221                 
222                 for (int i = listeners.length - 2; i >= 0; i -= 2) {
223                         if (listeners[i] == ChangeListener.class) {
224                                 ((ChangeListener) listeners[i + 1]).stateChanged(e);
225                         }
226                 }
227         }
228         
229         
230         @Override
231         public void componentChanged(ComponentChangeEvent e) {
232                 fireChangeEvent();
233         }
234         
235         
236         ///////////////  Helper methods  ///////////////
237         
238         /**
239          * Return whether this configuration has any motors defined to it.
240          * 
241          * @return  true if this configuration has active motor mounts with motors defined to them.
242          */
243         public boolean hasMotors() {
244                 for (RocketComponent c : this) {
245                         if (c instanceof MotorMount) {
246                                 MotorMount mount = (MotorMount) c;
247                                 if (!mount.isMotorMount())
248                                         continue;
249                                 if (mount.getMotor(this.motorConfiguration) != null) {
250                                         return true;
251                                 }
252                         }
253                 }
254                 return false;
255         }
256         
257         
258         /**
259          * Return the bounds of the current configuration.  The bounds are cached.
260          * 
261          * @return      a <code>Collection</code> containing coordinates bouding the rocket.
262          */
263         public Collection<Coordinate> getBounds() {
264                 if (rocket.getModID() != boundsModID) {
265                         boundsModID = rocket.getModID();
266                         cachedBounds.clear();
267                         
268                         double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY;
269                         for (RocketComponent component : this) {
270                                 for (Coordinate c : component.getComponentBounds()) {
271                                         for (Coordinate coord : component.toAbsolute(c)) {
272                                                 cachedBounds.add(coord);
273                                                 if (coord.x < minX)
274                                                         minX = coord.x;
275                                                 if (coord.x > maxX)
276                                                         maxX = coord.x;
277                                         }
278                                 }
279                         }
280                         
281                         if (Double.isInfinite(minX) || Double.isInfinite(maxX)) {
282                                 cachedLength = 0;
283                         } else {
284                                 cachedLength = maxX - minX;
285                         }
286                 }
287                 return cachedBounds.clone();
288         }
289         
290         
291         /**
292          * Returns the length of the rocket configuration, from the foremost bound X-coordinate
293          * to the aft-most X-coordinate.  The value is cached.
294          * 
295          * @return      the length of the rocket in the X-direction.
296          */
297         public double getLength() {
298                 if (rocket.getModID() != boundsModID)
299                         getBounds(); // Calculates the length
300                         
301                 return cachedLength;
302         }
303         
304         
305
306
307         /**
308          * Return an iterator that iterates over the currently active components.
309          * The <code>Rocket</code> and <code>Stage</code> components are not returned,
310          * but instead all components that are within currently active stages.
311          */
312         @Override
313         public Iterator<RocketComponent> iterator() {
314                 return new ConfigurationIterator();
315         }
316         
317         
318         /**
319          * Return an iterator that iterates over all <code>MotorMount</code>s within the
320          * current configuration that have an active motor.
321          * 
322          * @return  an iterator over active motor mounts.
323          */
324         public Iterator<MotorMount> motorIterator() {
325                 return new MotorIterator();
326         }
327         
328         
329         /**
330          * Perform a deep-clone.  The object references are also cloned and no
331          * listeners are listening on the cloned object.  The rocket instance remains the same.
332          */
333         @Override
334         public Configuration clone() {
335                 try {
336                         Configuration config = (Configuration) super.clone();
337                         config.listenerList = new EventListenerList();
338                         config.stages = (BitSet) this.stages.clone();
339                         config.cachedBounds = new ArrayList<Coordinate>();
340                         config.boundsModID = -1;
341                         config.refLengthModID = -1;
342                         rocket.addComponentChangeListener(config);
343                         return config;
344                 } catch (CloneNotSupportedException e) {
345                         throw new BugException("clone not supported!", e);
346                 }
347         }
348         
349         
350         @Override
351         public int getModID() {
352                 return modID + rocket.getModID();
353         }
354         
355         
356         /**
357          * A class that iterates over all currently active components.
358          * 
359          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
360          */
361         private class ConfigurationIterator implements Iterator<RocketComponent> {
362                 Iterator<Iterator<RocketComponent>> iterators;
363                 Iterator<RocketComponent> current = null;
364                 
365                 public ConfigurationIterator() {
366                         List<Iterator<RocketComponent>> list = new ArrayList<Iterator<RocketComponent>>();
367                         
368                         for (RocketComponent stage : rocket.getChildren()) {
369                                 if (isStageActive(stage)) {
370                                         list.add(stage.iterator(false));
371                                 }
372                         }
373                         
374                         // Get iterators and initialize current
375                         iterators = list.iterator();
376                         if (iterators.hasNext()) {
377                                 current = iterators.next();
378                         } else {
379                                 List<RocketComponent> l = Collections.emptyList();
380                                 current = l.iterator();
381                         }
382                 }
383                 
384                 
385                 @Override
386                 public boolean hasNext() {
387                         if (!current.hasNext())
388                                 getNextIterator();
389                         
390                         return current.hasNext();
391                 }
392                 
393                 @Override
394                 public RocketComponent next() {
395                         if (!current.hasNext())
396                                 getNextIterator();
397                         
398                         return current.next();
399                 }
400                 
401                 /**
402                  * Get the next iterator that has items.  If such an iterator does
403                  * not exist, current is left to an empty iterator.
404                  */
405                 private void getNextIterator() {
406                         while ((!current.hasNext()) && iterators.hasNext()) {
407                                 current = iterators.next();
408                         }
409                 }
410                 
411                 @Override
412                 public void remove() {
413                         throw new UnsupportedOperationException("remove unsupported");
414                 }
415         }
416         
417         private class MotorIterator implements Iterator<MotorMount> {
418                 private final Iterator<RocketComponent> iterator;
419                 private MotorMount next = null;
420                 
421                 public MotorIterator() {
422                         this.iterator = iterator();
423                 }
424                 
425                 @Override
426                 public boolean hasNext() {
427                         getNext();
428                         return (next != null);
429                 }
430                 
431                 @Override
432                 public MotorMount next() {
433                         getNext();
434                         if (next == null) {
435                                 throw new NoSuchElementException("iterator called for too long");
436                         }
437                         
438                         MotorMount ret = next;
439                         next = null;
440                         return ret;
441                 }
442                 
443                 @Override
444                 public void remove() {
445                         throw new UnsupportedOperationException("remove unsupported");
446                 }
447                 
448                 private void getNext() {
449                         if (next != null)
450                                 return;
451                         while (iterator.hasNext()) {
452                                 RocketComponent c = iterator.next();
453                                 if (c instanceof MotorMount) {
454                                         MotorMount mount = (MotorMount) c;
455                                         if (mount.isMotorMount() && mount.getMotor(motorConfiguration) != null) {
456                                                 next = mount;
457                                                 return;
458                                         }
459                                 }
460                         }
461                 }
462         }
463         
464 }