create changelog entry
[debian/openrocket] / core / 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.EventListener;
7 import java.util.EventObject;
8 import java.util.Iterator;
9 import java.util.List;
10 import java.util.NoSuchElementException;
11
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;
19
20
21 /**
22  * A class defining a rocket configuration, including motors and which stages are active.
23  * 
24  * TODO: HIGH: Remove motor ignition times from this class.
25  * 
26  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
27  */
28 public class Configuration implements Cloneable, ChangeSource, ComponentChangeListener,
29                 Iterable<RocketComponent>, Monitorable {
30         
31         private Rocket rocket;
32         private BitSet stages = new BitSet();
33         
34         private String motorConfiguration = null;
35         
36         private List<EventListener> listenerList = new ArrayList<EventListener>();
37         
38
39         /* Cached data */
40         private int boundsModID = -1;
41         private ArrayList<Coordinate> cachedBounds = new ArrayList<Coordinate>();
42         private double cachedLength = -1;
43         
44         private int refLengthModID = -1;
45         private double cachedRefLength = -1;
46         
47
48         private int modID = 0;
49         
50         
51         /**
52          * Create a new configuration with the specified <code>Rocket</code> with 
53          * <code>null</code> motor configuration.
54          * 
55          * @param rocket  the rocket
56          */
57         public Configuration(Rocket rocket) {
58                 this.rocket = rocket;
59                 setAllStages();
60                 rocket.addComponentChangeListener(this);
61         }
62         
63         
64
65         public Rocket getRocket() {
66                 return rocket;
67         }
68         
69         
70         public void setAllStages() {
71                 stages.clear();
72                 stages.set(0, rocket.getStageCount());
73                 fireChangeEvent();
74         }
75         
76         
77         /**
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.
80          * 
81          * @param stage         the stage number.
82          */
83         public void setToStage(int stage) {
84                 stages.clear();
85                 stages.set(0, stage + 1, true);
86                 //              stages.set(stage+1, rocket.getStageCount(), false);
87                 fireChangeEvent();
88         }
89         
90         
91         /**
92          * Check whether the up-most stage of the rocket is in this configuration.
93          * 
94          * @return      <code>true</code> if the first stage is active in this configuration.
95          */
96         public boolean isHead() {
97                 return isStageActive(0);
98         }
99         
100         
101
102         /**
103          * Check whether the stage specified by the index is active.
104          */
105         public boolean isStageActive(int stage) {
106                 if (stage >= rocket.getStageCount())
107                         return false;
108                 return stages.get(stage);
109         }
110         
111         public int getStageCount() {
112                 return rocket.getStageCount();
113         }
114         
115         public int getActiveStageCount() {
116                 int count = 0;
117                 int s = rocket.getStageCount();
118                 
119                 for (int i = 0; i < s; i++) {
120                         if (stages.get(i))
121                                 count++;
122                 }
123                 return count;
124         }
125         
126         public int[] getActiveStages() {
127                 int stageCount = rocket.getStageCount();
128                 List<Integer> active = new ArrayList<Integer>();
129                 int[] ret;
130                 
131                 for (int i = 0; i < stageCount; i++) {
132                         if (stages.get(i)) {
133                                 active.add(i);
134                         }
135                 }
136                 
137                 ret = new int[active.size()];
138                 for (int i = 0; i < ret.length; i++) {
139                         ret[i] = active.get(i);
140                 }
141                 
142                 return ret;
143         }
144         
145         
146         /**
147          * Return the reference length associated with the current configuration.  The 
148          * reference length type is retrieved from the <code>Rocket</code>.
149          * 
150          * @return  the reference length for this configuration.
151          */
152         public double getReferenceLength() {
153                 if (rocket.getModID() != refLengthModID) {
154                         refLengthModID = rocket.getModID();
155                         cachedRefLength = rocket.getReferenceType().getReferenceLength(this);
156                 }
157                 return cachedRefLength;
158         }
159         
160         
161         public double getReferenceArea() {
162                 return Math.PI * MathUtil.pow2(getReferenceLength() / 2);
163         }
164         
165         
166         public String getMotorConfigurationID() {
167                 return motorConfiguration;
168         }
169         
170         public void setMotorConfigurationID(String id) {
171                 if ((motorConfiguration == null && id == null) ||
172                                 (id != null && id.equals(motorConfiguration)))
173                         return;
174                 
175                 motorConfiguration = id;
176                 fireChangeEvent();
177         }
178         
179         public String getMotorConfigurationDescription() {
180                 return rocket.getMotorConfigurationNameOrDescription(motorConfiguration);
181         }
182         
183         
184
185
186
187         /**
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!
190          */
191         public void release() {
192                 rocket.removeComponentChangeListener(this);
193                 listenerList = null;
194                 rocket = null;
195         }
196         
197         
198         ////////////////  Listeners  ////////////////
199         
200         @Override
201         public void addChangeListener(EventListener listener) {
202                 listenerList.add(listener);
203         }
204         
205         @Override
206         public void removeChangeListener(EventListener listener) {
207                 listenerList.remove(listener);
208         }
209         
210         protected void fireChangeEvent() {
211                 EventObject e = new EventObject(this);
212                 
213                 this.modID++;
214                 boundsModID = -1;
215                 refLengthModID = -1;
216
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);
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 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);
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 }