package net.sf.openrocket.rocketcomponent;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
import java.util.UUID;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
+import net.sf.openrocket.gui.main.ExceptionHandler;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.ArrayList;
+import net.sf.openrocket.util.Chars;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.UniqueID;
/**
*/
public class Rocket extends RocketComponent {
- public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
- private static final boolean DEBUG_LISTENERS = false;
-
+ public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
- /**
- * The next modification ID to use. This variable may only be accessed via
- * the synchronized {@link #getNextModID()} method!
- */
- private static int nextModID = 1;
-
/**
* List of component change listeners.
*/
private List<ComponentChangeEvent> freezeList = null;
-
+
private int modID;
private int massModID;
private int aeroModID;
private int treeModID;
private int functionalModID;
-
- private ReferenceType refType = ReferenceType.MAXIMUM; // Set in constructor
+
+ private ReferenceType refType = ReferenceType.MAXIMUM; // Set in constructor
private double customReferenceLength = DEFAULT_REFERENCE_LENGTH;
-
+
// The default configuration used in dialogs
private final Configuration defaultConfiguration;
-
+
private String designer = "";
private String revision = "";
-
+
// Motor configuration list
- private List<String> motorConfigurationIDs = new ArrayList<String>();
- private Map<String, String> motorConfigurationNames = new HashMap<String, String>();
+ private ArrayList<String> motorConfigurationIDs = new ArrayList<String>();
+ private HashMap<String, String> motorConfigurationNames = new HashMap<String, String>();
{
motorConfigurationIDs.add(null);
}
-
+
// Does the rocket have a perfect finish (a notable amount of laminar flow)
private boolean perfectFinish = false;
-
+
///////////// Constructor /////////////
public Rocket() {
super(RocketComponent.Position.AFTER);
- modID = getNextModID();
+ modID = UniqueID.next();
massModID = modID;
aeroModID = modID;
treeModID = modID;
}
-
+
public String getDesigner() {
+ checkState();
return designer;
}
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
+
public String getRevision() {
+ checkState();
return revision;
}
}
-
+
/**
* Return the number of stages in this rocket.
* @return the number of stages in this rocket.
*/
public int getStageCount() {
+ checkState();
return this.getChildCount();
}
-
/**
* Return the non-negative modification ID of this rocket. The ID is changed
* every time any change occurs in the rocket. This can be used to check
}
-
-
+
+
public ReferenceType getReferenceType() {
+ checkState();
return refType;
}
public double getCustomReferenceLength() {
+ checkState();
return customReferenceLength;
}
if (MathUtil.equals(customReferenceLength, length))
return;
- this.customReferenceLength = Math.max(length,0.001);
+ this.customReferenceLength = Math.max(length, 0.001);
if (refType == ReferenceType.CUSTOM) {
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
-
+
+
+
/**
* Set whether the rocket has a perfect finish. This will affect whether the
* boundary layer is assumed to be fully turbulent or not.
this.perfectFinish = perfectFinish;
fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
}
-
-
+
+
/**
* Get whether the rocket has a perfect finish.
public boolean isPerfectFinish() {
return perfectFinish;
}
+
+
/**
- * Return a new unique modification ID. This method is thread-safe.
- *
- * @return a new modification ID unique to this session.
- */
- private synchronized int getNextModID() {
- return nextModID++;
- }
-
-
- /**
- * Make a deep copy of the Rocket structure. This is a helper method which simply
- * casts the result of the superclass method to a Rocket.
+ * Make a deep copy of the Rocket structure. This method is exposed as public to allow
+ * for undo/redo system functionality.
*/
+ @SuppressWarnings("unchecked")
@Override
- public Rocket copy() {
- Rocket copy = (Rocket)super.copy();
+ public Rocket copyWithOriginalID() {
+ Rocket copy = (Rocket) super.copyWithOriginalID();
+ copy.motorConfigurationIDs = this.motorConfigurationIDs.clone();
+ copy.motorConfigurationNames =
+ (HashMap<String, String>) this.motorConfigurationNames.clone();
copy.resetListeners();
+
return copy;
}
-
-
-
-
-
/**
* Load the rocket structure from the source. The method loads the fields of this
* Rocket object and copies the references to siblings from the <code>source</code>.
* and therefore fires an UNDO_EVENT, masked with all applicable mass/aerodynamic/tree
* changes.
*/
+ @SuppressWarnings("unchecked")
public void loadFrom(Rocket r) {
- super.copyFrom(r);
+
+ // Store list of components to invalidate after event has been fired
+ List<RocketComponent> toInvalidate = this.copyFrom(r);
int type = ComponentChangeEvent.UNDO_CHANGE | ComponentChangeEvent.NONFUNCTIONAL_CHANGE;
if (this.massModID != r.massModID)
type |= ComponentChangeEvent.MASS_CHANGE;
if (this.aeroModID != r.aeroModID)
type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
- if (this.treeModID != r.treeModID)
- type |= ComponentChangeEvent.TREE_CHANGE;
+ // Loading a rocket is always a tree change since the component objects change
+ type |= ComponentChangeEvent.TREE_CHANGE;
this.modID = r.modID;
this.massModID = r.massModID;
this.refType = r.refType;
this.customReferenceLength = r.customReferenceLength;
- this.motorConfigurationIDs = r.motorConfigurationIDs;
- this.motorConfigurationNames = r.motorConfigurationNames;
+ this.motorConfigurationIDs = r.motorConfigurationIDs.clone();
+ this.motorConfigurationNames =
+ (HashMap<String, String>) r.motorConfigurationNames.clone();
this.perfectFinish = r.perfectFinish;
+ String id = defaultConfiguration.getMotorConfigurationID();
+ if (!this.motorConfigurationIDs.contains(id))
+ defaultConfiguration.setMotorConfigurationID(null);
+
+ this.checkComponentStructure();
+
fireComponentChangeEvent(type);
+
+ // Invalidate obsolete components after event
+ for (RocketComponent c : toInvalidate) {
+ c.invalidate();
+ }
}
-
-
+
+
/////// Implement the ComponentChangeListener lists
/**
* the structure.
*/
public void resetListeners() {
-// System.out.println("RESETTING LISTENER LIST of Rocket "+this);
+ // System.out.println("RESETTING LISTENER LIST of Rocket "+this);
listenerList = new EventListenerList();
}
public void printListeners() {
- System.out.println(""+this+" has "+listenerList.getListenerCount()+" listeners:");
+ System.out.println("" + this + " has " + listenerList.getListenerCount() + " listeners:");
Object[] list = listenerList.getListenerList();
- for (int i=1; i<list.length; i+=2)
- System.out.println(" "+((i+1)/2)+": "+list[i]);
+ for (int i = 1; i < list.length; i += 2)
+ System.out.println(" " + ((i + 1) / 2) + ": " + list[i]);
}
@Override
public void addComponentChangeListener(ComponentChangeListener l) {
- listenerList.add(ComponentChangeListener.class,l);
- if (DEBUG_LISTENERS)
- System.out.println(this+": Added listner (now "+listenerList.getListenerCount()+
- " listeners): "+l);
+ checkState();
+ listenerList.add(ComponentChangeListener.class, l);
+ log.verbose("Added ComponentChangeListener " + l + ", current number of listeners is " +
+ listenerList.getListenerCount());
}
+
@Override
public void removeComponentChangeListener(ComponentChangeListener l) {
listenerList.remove(ComponentChangeListener.class, l);
- if (DEBUG_LISTENERS)
- System.out.println(this+": Removed listner (now "+listenerList.getListenerCount()+
- " listeners): "+l);
+ log.verbose("Removed ComponentChangeListener " + l + ", current number of listeners is " +
+ listenerList.getListenerCount());
}
-
+
@Override
public void addChangeListener(ChangeListener l) {
- listenerList.add(ChangeListener.class,l);
- if (DEBUG_LISTENERS)
- System.out.println(this+": Added listner (now "+listenerList.getListenerCount()+
- " listeners): "+l);
+ checkState();
+ listenerList.add(ChangeListener.class, l);
+ log.verbose("Added ChangeListener " + l + ", current number of listeners is " +
+ listenerList.getListenerCount());
}
+
@Override
public void removeChangeListener(ChangeListener l) {
listenerList.remove(ChangeListener.class, l);
- if (DEBUG_LISTENERS)
- System.out.println(this+": Removed listner (now "+listenerList.getListenerCount()+
- " listeners): "+l);
+ log.verbose("Removed ChangeListener " + l + ", current number of listeners is " +
+ listenerList.getListenerCount());
}
-
+
@Override
protected void fireComponentChangeEvent(ComponentChangeEvent e) {
-
- // Update modification ID's only for normal (not undo/redo) events
- if (!e.isUndoChange()) {
- modID = getNextModID();
- if (e.isMassChange())
- massModID = modID;
- if (e.isAerodynamicChange())
- aeroModID = modID;
- if (e.isTreeChange())
- treeModID = modID;
- if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
- functionalModID = modID;
- }
-
- if (DEBUG_LISTENERS)
- System.out.println("FIRING "+e);
-
- // Check whether frozen
- if (freezeList != null) {
- freezeList.add(e);
- return;
- }
-
- // Notify all components first
- Iterator<RocketComponent> iterator = this.deepIterator(true);
- while (iterator.hasNext()) {
- iterator.next().componentChanged(e);
- }
-
- // Notify all listeners
- Object[] listeners = listenerList.getListenerList();
- for (int i = listeners.length-2; i>=0; i-=2) {
- if (listeners[i] == ComponentChangeListener.class) {
- ((ComponentChangeListener) listeners[i+1]).componentChanged(e);
- } else if (listeners[i] == ChangeListener.class) {
- ((ChangeListener) listeners[i+1]).stateChanged(e);
+ mutex.lock("fireComponentChangeEvent");
+ try {
+ checkState();
+
+ // Update modification ID's only for normal (not undo/redo) events
+ if (!e.isUndoChange()) {
+ modID = UniqueID.next();
+ if (e.isMassChange())
+ massModID = modID;
+ if (e.isAerodynamicChange())
+ aeroModID = modID;
+ if (e.isTreeChange())
+ treeModID = modID;
+ if (e.getType() != ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
+ functionalModID = modID;
+ }
+
+ // Check whether frozen
+ if (freezeList != null) {
+ log.debug("Rocket is in frozen state, adding event " + e + " info freeze list");
+ freezeList.add(e);
+ return;
+ }
+
+ log.debug("Firing rocket change event " + e);
+
+ // Notify all components first
+ Iterator<RocketComponent> iterator = this.iterator(true);
+ while (iterator.hasNext()) {
+ iterator.next().componentChanged(e);
+ }
+
+ // Notify all listeners
+ Object[] listeners = listenerList.getListenerList();
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ComponentChangeListener.class) {
+ ((ComponentChangeListener) listeners[i + 1]).componentChanged(e);
+ } else if (listeners[i] == ChangeListener.class) {
+ ((ChangeListener) listeners[i + 1]).stateChanged(e);
+ }
}
+ } finally {
+ mutex.unlock("fireComponentChangeEvent");
}
}
-
+
/**
* Freezes the rocket structure from firing any events. This may be performed to
* combine several actions on the structure into a single large action.
* @see #thaw()
*/
public void freeze() {
- if (freezeList == null)
+ checkState();
+ if (freezeList == null) {
freezeList = new LinkedList<ComponentChangeEvent>();
+ log.debug("Freezing Rocket");
+ } else {
+ ExceptionHandler.handleErrorCondition("Attempting to freeze Rocket when it is already frozen, " +
+ "freezeList=" + freezeList);
+ }
}
/**
* @see #freeze()
*/
public void thaw() {
- if (freezeList == null)
+ checkState();
+ if (freezeList == null) {
+ ExceptionHandler.handleErrorCondition("Attempting to thaw Rocket when it is not frozen");
return;
- if (freezeList.size()==0) {
+ }
+ if (freezeList.size() == 0) {
+ log.warn("Thawing rocket with no changes made");
freezeList = null;
return;
}
+ log.debug("Thawing rocket, freezeList=" + freezeList);
+
int type = 0;
Object c = null;
- for (ComponentChangeEvent e: freezeList) {
+ for (ComponentChangeEvent e : freezeList) {
type = type | e.getType();
c = e.getSource();
}
freezeList = null;
- fireComponentChangeEvent(new ComponentChangeEvent((RocketComponent)c,type));
+ fireComponentChangeEvent(new ComponentChangeEvent((RocketComponent) c, type));
}
-
+
//////// Motor configurations ////////
-
+
/**
* Return the default configuration. This should be used in the user interface
* to ensure a consistent rocket configuration between dialogs. It should NOT
* @return the default {@link Configuration}.
*/
public Configuration getDefaultConfiguration() {
+ checkState();
return defaultConfiguration;
}
* @return an array of the motor configuration IDs.
*/
public String[] getMotorConfigurationIDs() {
+ checkState();
return motorConfigurationIDs.toArray(new String[0]);
}
* @return the new motor configuration ID.
*/
public String newMotorConfigurationID() {
+ checkState();
String id = UUID.randomUUID().toString();
motorConfigurationIDs.add(id);
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
* @return true if successful, false if the ID was already used.
*/
public boolean addMotorConfigurationID(String id) {
+ checkState();
if (id == null || motorConfigurationIDs.contains(id))
return false;
motorConfigurationIDs.add(id);
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
return true;
}
-
+
/**
* Remove a motor configuration ID from the configuration IDs. The <code>null</code>
* ID cannot be removed, and an attempt to remove it will be silently ignored.
* @param id the motor configuration ID to remove
*/
public void removeMotorConfigurationID(String id) {
+ checkState();
if (id == null)
return;
motorConfigurationIDs.remove(id);
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
}
+
+
+ /**
+ * Check whether <code>id</code> is a valid motor configuration ID.
+ *
+ * @param id the configuration ID.
+ * @return whether a motor configuration with that ID exists.
+ */
+ public boolean isMotorConfigurationID(String id) {
+ checkState();
+ return motorConfigurationIDs.contains(id);
+ }
+
+
+ /**
+ * Check whether the given motor configuration ID has motors defined for it.
+ *
+ * @param id the motor configuration ID (may be invalid).
+ * @return whether any motors are defined for it.
+ */
+ public boolean hasMotors(String id) {
+ checkState();
+ if (id == null)
+ return false;
+
+ Iterator<RocketComponent> iterator = this.iterator();
+ while (iterator.hasNext()) {
+ RocketComponent c = iterator.next();
+
+ if (c instanceof MotorMount) {
+ MotorMount mount = (MotorMount) c;
+ if (!mount.isMotorMount())
+ continue;
+ if (mount.getMotor(id) != null) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Return the user-set name of the motor configuration. If no name has been set,
* @return the configuration name
*/
public String getMotorConfigurationName(String id) {
+ checkState();
+ if (!isMotorConfigurationID(id))
+ return "";
String s = motorConfigurationNames.get(id);
if (s == null)
return "";
* @param name the name for the motor configuration
*/
public void setMotorConfigurationName(String id, String name) {
- motorConfigurationNames.put(id,name);
- fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
+ checkState();
+ motorConfigurationNames.put(id, name);
+ fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
+
+ /**
+ * Return either the motor configuration name (if set) or its description.
+ *
+ * @param id the motor configuration ID.
+ * @return a textual representation of the configuration
+ */
+ public String getMotorConfigurationNameOrDescription(String id) {
+ checkState();
+ String name;
+ name = getMotorConfigurationName(id);
+ if (name != null && !name.equals(""))
+ return name;
+
+ return getMotorConfigurationDescription(id);
+ }
+
/**
- * Return a description for the motor configuration. This is either the
- * name previously set by {@link #setMotorConfigurationName(String, String)} or
- * a string generated from the motor designations of the components.
+ * Return a description for the motor configuration, generated from the motor
+ * designations of the components.
*
* @param id the motor configuration ID.
* @return a textual representation of the configuration
*/
@SuppressWarnings("null")
public String getMotorConfigurationDescription(String id) {
+ checkState();
String name;
int motorCount = 0;
- if (!motorConfigurationIDs.contains(id)) {
- throw new IllegalArgumentException("Motor configuration ID does not exist: "+id);
- }
-
- name = motorConfigurationNames.get(id);
- if (name != null && !name.equals(""))
- return name;
-
// Generate the description
// First iterate over each stage and store the designations of each motor
List<List<String>> list = new ArrayList<List<String>>();
List<String> currentList = null;
- Iterator<RocketComponent> iterator = this.deepIterator();
+ Iterator<RocketComponent> iterator = this.iterator();
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
if (mount.isMotorMount() && motor != null) {
String designation = motor.getDesignation(mount.getMotorDelay(id));
- for (int i=0; i < mount.getMotorCount(); i++) {
+ for (int i = 0; i < mount.getMotorCount(); i++) {
currentList.add(designation);
motorCount++;
}
}
if (motorCount == 0) {
- return "[No motors]";
+ //// [No motors]
+ return trans.get("Rocket.motorCount.Nomotor");
}
// Change multiple occurrences of a motor to n x motor
List<String> stages = new ArrayList<String>();
- for (List<String> stage: list) {
+ for (List<String> stage : list) {
String stageName = "";
String previous = null;
int count = 0;
Collections.sort(stage);
- for (String current: stage) {
+ for (String current : stage) {
if (current.equals(previous)) {
count++;
if (previous != null) {
String s = "";
if (count > 1) {
- s = "" + count + "\u00d7" + previous;
+ s = "" + count + Chars.TIMES + previous;
} else {
s = previous;
}
if (previous != null) {
String s = "";
if (count > 1) {
- s = "" + count + "\u00d7" + previous;
+ s = "" + count + Chars.TIMES + previous;
} else {
s = previous;
}
}
name = "[";
- for (int i=0; i < stages.size(); i++) {
+ for (int i = 0; i < stages.size(); i++) {
String s = stages.get(i);
if (s.equals(""))
s = "None";
- if (i==0)
+ if (i == 0)
name = name + s;
else
name = name + "; " + s;
return name;
}
-
+
//////// Obligatory component information
-
+
@Override
public String getComponentName() {
- return "Rocket";
+ //// Rocket
+ return trans.get("Rocket.compname.Rocket");
}
-
+
@Override
public Coordinate getComponentCG() {
- return new Coordinate(0,0,0,0);
+ return new Coordinate(0, 0, 0, 0);
}
-
+
@Override
public double getComponentMass() {
return 0;
}
-
+
@Override
- public double getLongitudalUnitInertia() {
+ public double getLongitudinalUnitInertia() {
return 0;
}
-
+
@Override
public double getRotationalUnitInertia() {
return 0;
public Collection<Coordinate> getComponentBounds() {
return Collections.emptyList();
}
-
+
@Override
public boolean isAerodynamic() {
return false;
}
-
+
@Override
public boolean isMassive() {
return false;
}
-
+
+ @Override
+ public boolean allowsChildren() {
+ return true;
+ }
+
/**
* Allows only <code>Stage</code> components to be added to the type Rocket.
*/
public boolean isCompatible(Class<? extends RocketComponent> type) {
return (Stage.class.isAssignableFrom(type));
}
+
+ /**
+ * Accept a visitor to this Rocket in the component hierarchy.
+ *
+ * @param theVisitor the visitor that will be called back with a reference to this Rocket
+ */
+ @Override
+ public void accept(final ComponentVisitor theVisitor) {
+ theVisitor.visit(this);
+ }
}