Initial commit
[debian/openrocket] / src / net / sf / openrocket / rocketcomponent / Configuration.java
diff --git a/src/net/sf/openrocket/rocketcomponent/Configuration.java b/src/net/sf/openrocket/rocketcomponent/Configuration.java
new file mode 100644 (file)
index 0000000..c133823
--- /dev/null
@@ -0,0 +1,509 @@
+package net.sf.openrocket.rocketcomponent;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
+
+import net.sf.openrocket.util.ChangeSource;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
+
+
+public class Configuration implements Cloneable, ChangeSource, ComponentChangeListener, 
+               Iterable<RocketComponent> {
+       
+       public static final double DEFAULT_IGNITION_TIME = Double.MAX_VALUE;
+       
+
+       private Rocket rocket;
+       private BitSet stages = new BitSet();
+       
+       private String motorConfiguration = null;
+       
+       private EventListenerList listenerList = new EventListenerList();
+       
+       private final HashMap<MotorMount, Double> ignitionTimes =
+               new HashMap<MotorMount, Double>();
+       
+
+       /* Cached data */
+       private int boundsModID = -1;
+       private ArrayList<Coordinate> cachedBounds = new ArrayList<Coordinate>();
+       private double cachedLength = -1;
+
+       private int refLengthModID = -1;
+       private double cachedRefLength = -1;
+       
+       
+       
+       /**
+        * Create a new configuration with the specified <code>Rocket</code> with 
+        * <code>null</code> motor configuration.
+        * 
+        * @param rocket  the rocket
+        */
+       public Configuration(Rocket rocket) {
+               this.rocket = rocket;
+               setAllStages();
+               rocket.addComponentChangeListener(this);
+       }
+
+       
+       /**
+        * Create a new configuration with the specified <code>Rocket</code> and motor
+        * configuration.
+        * 
+        * @param rocket        the rocket.
+        * @param motorID       the motor configuration ID to use.
+        */
+       public Configuration(Rocket rocket, String motorID) {
+               this.rocket = rocket;
+               this.motorConfiguration = motorID;
+               setAllStages();
+               rocket.addComponentChangeListener(this);
+       }
+       
+       
+       
+       
+       public Rocket getRocket() {
+               return rocket;
+       }
+       
+       
+       public void setAllStages() {
+               stages.clear();
+               stages.set(0,rocket.getStageCount());
+               fireChangeEvent();
+       }
+       
+       
+       /**
+        * Set all stages up to and including the given stage number.  For example,
+        * <code>setToStage(0)</code> will set only the first stage active.
+        * 
+        * @param stage         the stage number.
+        */
+       public void setToStage(int stage) {
+               stages.clear();
+               stages.set(0, stage+1, true);
+//             stages.set(stage+1, rocket.getStageCount(), false);
+               fireChangeEvent();
+       }
+       
+       
+       /**
+        * Check whether the up-most stage of the rocket is in this configuration.
+        * 
+        * @return      <code>true</code> if the first stage is active in this configuration.
+        */
+       public boolean isHead() {
+               return isStageActive(0);
+       }
+       
+       public boolean isStageActive(RocketComponent stage) {
+               if (!(stage instanceof Stage)) {
+                       throw new IllegalArgumentException("called with component "+stage);
+               }
+               return stages.get(stage.getParent().getChildPosition(stage));
+       }
+       
+       
+       public boolean isStageActive(int stage) {
+               if (stage >= rocket.getStageCount())
+                       return false;
+               return stages.get(stage);
+       }
+       
+       public int getStageCount() {
+               return rocket.getStageCount();
+       }
+
+       public int getActiveStageCount() {
+               int count = 0;
+               int s = rocket.getStageCount();
+               
+               for (int i=0; i < s; i++) {
+                       if (stages.get(i))
+                               count++;
+               }
+               return count;
+       }
+
+       public int[] getActiveStages() {
+               int stageCount = rocket.getStageCount();
+               List<Integer> active = new ArrayList<Integer>();
+               int[] ret;
+
+               for (int i=0; i < stageCount; i++) {
+                       if (stages.get(i)) {
+                               active.add(i);
+                       }
+               }
+               
+               ret = new int[active.size()];
+               for (int i=0; i < ret.length; i++) {
+                       ret[i] = active.get(i);
+               }
+               
+               return ret;
+       }
+       
+       
+       /**
+        * Return the reference length associated with the current configuration.  The 
+        * reference length type is retrieved from the <code>Rocket</code>.
+        * 
+        * @return  the reference length for this configuration.
+        */
+       public double getReferenceLength() {
+               if (rocket.getModID() != refLengthModID) {
+                       refLengthModID = rocket.getModID();
+                       cachedRefLength = rocket.getReferenceType().getReferenceLength(this);
+               }
+               return cachedRefLength;
+       }
+       
+       
+       public double getReferenceArea() {
+               return Math.PI * MathUtil.pow2(getReferenceLength()/2);
+       }
+       
+       
+       public String getMotorConfigurationID() {
+               return motorConfiguration;
+       }
+       
+       public void setMotorConfigurationID(String id) {
+               if ((motorConfiguration == null  &&  id == null) ||
+                               (id != null && id.equals(motorConfiguration)))
+                       return;
+               
+               motorConfiguration = id;
+               fireChangeEvent();
+       }
+       
+       public String getMotorConfigurationDescription() {
+               return rocket.getMotorConfigurationDescription(motorConfiguration);
+       }
+       
+       
+       
+       /**
+        * Clear all motor ignition times.  All values are reset to their default of
+        * {@link #DEFAULT_IGNITION_TIME}.
+        */
+       public void resetIgnitionTimes() {
+               ignitionTimes.clear();
+       }
+       
+       /**
+        * Set the ignition time of the motor in the specified motor mount.  Negative or NaN
+        * time values will cause an <code>IllegalArgumentException</code>.
+        * 
+        * @param mount the motor mount to specify.
+        * @param time  the time at which to ignite the motors.
+        * @throws IllegalArgumentException   if <code>time</code> is negative of NaN.
+        */
+       public void setIgnitionTime(MotorMount mount, double time) {
+               if (time < 0) {
+                       throw new IllegalArgumentException("time is negative: "+time);
+               }
+               ignitionTimes.put(mount, time);
+               // TODO: MEDIUM: Should this fire events?
+       }
+       
+       /**
+        * Return the ignition time of the motor in the specified motor mount.  If no time
+        * has been specified, returns {@link #DEFAULT_IGNITION_TIME} as the default.
+        * 
+        * @param mount   the motor mount.
+        * @return                ignition time of the motors in the mount.
+        */
+       public double getIgnitionTime(MotorMount mount) {
+               Double d = ignitionTimes.get(mount);
+               if (d == null)
+                       return DEFAULT_IGNITION_TIME;
+               return d;
+       }
+       
+       
+
+       
+       /**
+        * Removes the listener connection to the rocket and listeners of this object.
+        * This configuration may not be used after a call to this method!
+        */
+       public void release() {
+               rocket.removeComponentChangeListener(this);
+               listenerList = null;
+               rocket = null;
+       }
+       
+       
+       ////////////////  Listeners  ////////////////
+       
+       @Override
+       public void addChangeListener(ChangeListener listener) {
+               listenerList.add(ChangeListener.class, listener);
+       }
+
+       @Override
+       public void removeChangeListener(ChangeListener listener) {
+               listenerList.remove(ChangeListener.class, listener);
+       }
+       
+       protected void fireChangeEvent() {
+               Object[] listeners = listenerList.getListenerList();
+               ChangeEvent e = new ChangeEvent(this);
+               
+               boundsModID = -1;
+               refLengthModID = -1;
+               
+               for (int i = listeners.length-2; i>=0; i-=2) {
+                       if (listeners[i] == ChangeListener.class) {
+                               ((ChangeListener) listeners[i+1]).stateChanged(e);
+                       }
+               }
+       }
+
+       
+       @Override
+       public void componentChanged(ComponentChangeEvent e) {
+               fireChangeEvent();
+       }
+       
+       
+       ///////////////  Helper methods  ///////////////
+       
+       /**
+        * Return whether this configuration has any motors defined to it.
+        * 
+        * @return  true if this configuration has active motor mounts with motors defined to them.
+        */
+       public boolean hasMotors() {
+               for (RocketComponent c: this) {
+                       if (c instanceof MotorMount) {
+                               MotorMount mount = (MotorMount) c;
+                               if (!mount.isMotorMount())
+                                       continue;
+                               if (mount.getMotor(this.motorConfiguration) != null) {
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       }
+       
+       
+       /**
+        * Return the bounds of the current configuration.  The bounds are cached.
+        * 
+        * @return      a <code>Collection</code> containing coordinates bouding the rocket.
+        */
+       @SuppressWarnings("unchecked")
+       public Collection<Coordinate> getBounds() {
+               if (rocket.getModID() != boundsModID) {
+                       boundsModID = rocket.getModID();
+                       cachedBounds.clear();
+                       
+                       double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY;
+                       for (RocketComponent component: this) {
+                               for (Coordinate c: component.getComponentBounds()) {
+                                       for (Coordinate coord: component.toAbsolute(c)) {
+                                               cachedBounds.add(coord);
+                                               if (coord.x < minX)
+                                                       minX = coord.x;
+                                               if (coord.x > maxX)
+                                                       maxX = coord.x;
+                                       }
+                               }
+                       }
+                       
+                       if (Double.isInfinite(minX) || Double.isInfinite(maxX)) {
+                               cachedLength = 0;
+                       } else {
+                               cachedLength = maxX - minX;
+                       }
+               }
+               return (ArrayList<Coordinate>) cachedBounds.clone();
+       }
+       
+       
+       /**
+        * Returns the length of the rocket configuration, from the foremost bound X-coordinate
+        * to the aft-most X-coordinate.  The value is cached.
+        * 
+        * @return      the length of the rocket in the X-direction.
+        */
+       public double getLength() {
+               if (rocket.getModID() != boundsModID)
+                       getBounds();  // Calculates the length
+               
+               return cachedLength;
+       }
+       
+       
+       
+
+       /**
+        * Return an iterator that iterates over the currently active components.
+        * The <code>Rocket</code> and <code>Stage</code> components are not returned,
+        * but instead all components that are within currently active stages.
+        */
+       @Override
+       public Iterator<RocketComponent> iterator() {
+               return new ConfigurationIterator();
+       }
+       
+       
+       /**
+        * Return an iterator that iterates over all <code>MotorMount</code>s within the
+        * current configuration that have an active motor.
+        * 
+        * @return  an iterator over active motor mounts.
+        */
+       public Iterator<MotorMount> motorIterator() {
+               return new MotorIterator();
+       }
+       
+       
+       /**
+        * Perform a deep-clone.  The object references are also cloned and no
+        * listeners are listening on the cloned object.  
+        */
+       @Override
+       public Configuration clone() {
+               try {
+                       Configuration config = (Configuration) super.clone();
+                       config.listenerList = new EventListenerList();
+                       config.stages = (BitSet) this.stages.clone();
+                       config.cachedBounds = new ArrayList<Coordinate>();
+                       config.boundsModID = -1;
+                       config.refLengthModID = -1;
+                       rocket.addComponentChangeListener(config);
+                       return config;
+               } catch (CloneNotSupportedException e) {
+                       throw new RuntimeException("BUG: clone not supported!",e);
+               }
+       }
+
+       
+
+       /**
+        * A class that iterates over all currently active components.
+        * 
+        * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+        */
+       private class ConfigurationIterator implements Iterator<RocketComponent> {
+               Iterator<Iterator<RocketComponent>> iterators;
+               Iterator<RocketComponent> current = null;
+               
+               public ConfigurationIterator() {
+                       List<Iterator<RocketComponent>> list = new ArrayList<Iterator<RocketComponent>>();
+                       
+                       for (RocketComponent stage: rocket.getChildren()) {
+                               if (isStageActive(stage)) {
+                                       list.add(stage.deepIterator());
+                               }
+                       }
+                       
+                       // Get iterators and initialize current
+                       iterators = list.iterator();
+                       if (iterators.hasNext()) {
+                               current = iterators.next();
+                       } else {
+                               List<RocketComponent> l = Collections.emptyList();
+                               current = l.iterator();
+                       }
+               }
+               
+               
+               @Override
+               public boolean hasNext() {
+                       if (!current.hasNext())
+                               getNextIterator();
+                       
+                       return current.hasNext();
+               }
+
+               @Override
+               public RocketComponent next() {
+                       if (!current.hasNext())
+                               getNextIterator();
+                       
+                       return current.next();
+               }
+
+               /**
+                * Get the next iterator that has items.  If such an iterator does
+                * not exist, current is left to an empty iterator.
+                */
+               private void getNextIterator() {
+                       while ((!current.hasNext()) && iterators.hasNext()) {
+                               current = iterators.next();
+                       }
+               }
+               
+               @Override
+               public void remove() {
+                       throw new UnsupportedOperationException("remove unsupported");
+               }
+       }
+       
+       private class MotorIterator implements Iterator<MotorMount> {
+               private final Iterator<RocketComponent> iterator;
+               private MotorMount next = null;
+               
+               public MotorIterator() {
+                       this.iterator = iterator();
+               }
+
+               @Override
+               public boolean hasNext() {
+                       getNext();
+                       return (next != null);
+               }
+
+               @Override
+               public MotorMount next() {
+                       getNext();
+                       if (next == null) {
+                               throw new NoSuchElementException("iterator called for too long");
+                       }
+                       
+                       MotorMount ret = next;
+                       next = null;
+                       return ret;
+               }
+
+               @Override
+               public void remove() {
+                       throw new UnsupportedOperationException("remove unsupported");
+               }
+               
+               private void getNext() {
+                       if (next != null)
+                               return;
+                       while (iterator.hasNext()) {
+                               RocketComponent c = iterator.next();
+                               if (c instanceof MotorMount) {
+                                       MotorMount mount = (MotorMount) c;
+                                       if (mount.isMotorMount() && mount.getMotor(motorConfiguration) != null) {
+                                               next = mount;
+                                               return;
+                                       }
+                               }
+                       }
+               }
+       }
+}