Undo/redo system enhancements, DnD for component tree, bug fixes
[debian/openrocket] / src / net / sf / openrocket / rocketcomponent / Configuration.java
1 package net.sf.openrocket.rocketcomponent;
2
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;
8 import java.util.List;
9 import java.util.NoSuchElementException;
10
11 import javax.swing.event.ChangeEvent;
12 import javax.swing.event.ChangeListener;
13 import javax.swing.event.EventListenerList;
14
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         @SuppressWarnings("unchecked")
264         public Collection<Coordinate> getBounds() {
265                 if (rocket.getModID() != boundsModID) {
266                         boundsModID = rocket.getModID();
267                         cachedBounds.clear();
268                         
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);
274                                                 if (coord.x < minX)
275                                                         minX = coord.x;
276                                                 if (coord.x > maxX)
277                                                         maxX = coord.x;
278                                         }
279                                 }
280                         }
281                         
282                         if (Double.isInfinite(minX) || Double.isInfinite(maxX)) {
283                                 cachedLength = 0;
284                         } else {
285                                 cachedLength = maxX - minX;
286                         }
287                 }
288                 return (ArrayList<Coordinate>) cachedBounds.clone();
289         }
290         
291         
292         /**
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.
295          * 
296          * @return      the length of the rocket in the X-direction.
297          */
298         public double getLength() {
299                 if (rocket.getModID() != boundsModID)
300                         getBounds(); // Calculates the length
301                         
302                 return cachedLength;
303         }
304         
305         
306
307
308         /**
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.
312          */
313         @Override
314         public Iterator<RocketComponent> iterator() {
315                 return new ConfigurationIterator();
316         }
317         
318         
319         /**
320          * Return an iterator that iterates over all <code>MotorMount</code>s within the
321          * current configuration that have an active motor.
322          * 
323          * @return  an iterator over active motor mounts.
324          */
325         public Iterator<MotorMount> motorIterator() {
326                 return new MotorIterator();
327         }
328         
329         
330         /**
331          * Perform a deep-clone.  The object references are also cloned and no
332          * listeners are listening on the cloned object.  
333          */
334         @Override
335         public Configuration clone() {
336                 try {
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);
344                         return config;
345                 } catch (CloneNotSupportedException e) {
346                         throw new BugException("clone not supported!", e);
347                 }
348         }
349         
350         
351         @Override
352         public int getModID() {
353                 return modID + rocket.getModID();
354         }
355         
356         
357         /**
358          * A class that iterates over all currently active components.
359          * 
360          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
361          */
362         private class ConfigurationIterator implements Iterator<RocketComponent> {
363                 Iterator<Iterator<RocketComponent>> iterators;
364                 Iterator<RocketComponent> current = null;
365                 
366                 public ConfigurationIterator() {
367                         List<Iterator<RocketComponent>> list = new ArrayList<Iterator<RocketComponent>>();
368                         
369                         for (RocketComponent stage : rocket.getChildren()) {
370                                 if (isStageActive(stage)) {
371                                         list.add(stage.deepIterator());
372                                 }
373                         }
374                         
375                         // Get iterators and initialize current
376                         iterators = list.iterator();
377                         if (iterators.hasNext()) {
378                                 current = iterators.next();
379                         } else {
380                                 List<RocketComponent> l = Collections.emptyList();
381                                 current = l.iterator();
382                         }
383                 }
384                 
385                 
386                 @Override
387                 public boolean hasNext() {
388                         if (!current.hasNext())
389                                 getNextIterator();
390                         
391                         return current.hasNext();
392                 }
393                 
394                 @Override
395                 public RocketComponent next() {
396                         if (!current.hasNext())
397                                 getNextIterator();
398                         
399                         return current.next();
400                 }
401                 
402                 /**
403                  * Get the next iterator that has items.  If such an iterator does
404                  * not exist, current is left to an empty iterator.
405                  */
406                 private void getNextIterator() {
407                         while ((!current.hasNext()) && iterators.hasNext()) {
408                                 current = iterators.next();
409                         }
410                 }
411                 
412                 @Override
413                 public void remove() {
414                         throw new UnsupportedOperationException("remove unsupported");
415                 }
416         }
417         
418         private class MotorIterator implements Iterator<MotorMount> {
419                 private final Iterator<RocketComponent> iterator;
420                 private MotorMount next = null;
421                 
422                 public MotorIterator() {
423                         this.iterator = iterator();
424                 }
425                 
426                 @Override
427                 public boolean hasNext() {
428                         getNext();
429                         return (next != null);
430                 }
431                 
432                 @Override
433                 public MotorMount next() {
434                         getNext();
435                         if (next == null) {
436                                 throw new NoSuchElementException("iterator called for too long");
437                         }
438                         
439                         MotorMount ret = next;
440                         next = null;
441                         return ret;
442                 }
443                 
444                 @Override
445                 public void remove() {
446                         throw new UnsupportedOperationException("remove unsupported");
447                 }
448                 
449                 private void getNext() {
450                         if (next != null)
451                                 return;
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) {
457                                                 next = mount;
458                                                 return;
459                                         }
460                                 }
461                         }
462                 }
463         }
464         
465 }