package net.sf.openrocket.rocketcomponent;
+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.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.*;
-
-import javax.swing.event.ChangeListener;
-import javax.swing.event.EventListenerList;
-import java.util.*;
+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 {
private static final LogHelper log = Application.getLogger();
-
+ private static final Translator trans = Application.getTranslator();
+
public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
-
+
/**
* List of component change listeners.
*/
private EventListenerList listenerList = new EventListenerList();
-
+
/**
* When freezeList != null, events are not dispatched but stored in the list.
* When the structure is thawed, a single combined event will be fired.
*/
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 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 ArrayList<String> motorConfigurationIDs = new ArrayList<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 = UniqueID.next();
functionalModID = modID;
defaultConfiguration = new Configuration(this);
}
-
-
+
+
public String getDesigner() {
checkState();
return designer;
}
-
+
public void setDesigner(String s) {
if (s == null)
s = "";
designer = s;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
public String getRevision() {
checkState();
return revision;
}
-
+
public void setRevision(String s) {
if (s == null)
s = "";
revision = s;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
/**
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 int getModID() {
return modID;
}
-
+
/**
* Return the non-negative mass modification ID of this rocket. See
* {@link #getModID()} for details.
public int getMassModID() {
return massModID;
}
-
+
/**
* Return the non-negative aerodynamic modification ID of this rocket. See
* {@link #getModID()} for details.
public int getAerodynamicModID() {
return aeroModID;
}
-
+
/**
* Return the non-negative tree modification ID of this rocket. See
* {@link #getModID()} for details.
public int getTreeModID() {
return treeModID;
}
-
+
/**
* Return the non-negative functional modificationID of this rocket.
* This changes every time a functional change occurs.
public int getFunctionalModID() {
return functionalModID;
}
-
-
+
+
public ReferenceType getReferenceType() {
checkState();
return refType;
}
-
+
public void setReferenceType(ReferenceType type) {
if (refType == type)
return;
refType = type;
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
public double getCustomReferenceLength() {
checkState();
return customReferenceLength;
}
-
+
public void setCustomReferenceLength(double length) {
if (MathUtil.equals(customReferenceLength, length))
return;
-
+
this.customReferenceLength = Math.max(length, 0.001);
-
+
if (refType == ReferenceType.CUSTOM) {
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
}
-
-
+
+
this.perfectFinish = perfectFinish;
fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
}
-
-
+
+
/**
* Get whether the rocket has a perfect finish.
public boolean isPerfectFinish() {
return perfectFinish;
}
-
-
+
+
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>.
*/
@SuppressWarnings("unchecked")
public void loadFrom(Rocket 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;
type |= ComponentChangeEvent.AERODYNAMIC_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.aeroModID = r.aeroModID;
this.functionalModID = r.functionalModID;
this.refType = r.refType;
this.customReferenceLength = r.customReferenceLength;
-
+
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
-
+
/**
* Creates a new EventListenerList for this component. This is necessary when cloning
* the structure.
// System.out.println("RESETTING LISTENER LIST of Rocket "+this);
listenerList = new EventListenerList();
}
-
-
+
+
public void printListeners() {
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]);
}
-
+
@Override
public void addComponentChangeListener(ComponentChangeListener l) {
checkState();
log.verbose("Added ComponentChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
-
+
@Override
public void removeComponentChangeListener(ComponentChangeListener l) {
listenerList.remove(ComponentChangeListener.class, l);
log.verbose("Removed ComponentChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
-
-
+
+
@Override
public void addChangeListener(ChangeListener l) {
checkState();
log.verbose("Added ChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
-
+
@Override
public void removeChangeListener(ChangeListener l) {
listenerList.remove(ChangeListener.class, l);
log.verbose("Removed ChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
-
-
+
+
@Override
protected void fireComponentChangeEvent(ComponentChangeEvent 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.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) {
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.
"freezeList=" + freezeList);
}
}
-
+
/**
* Thaws a frozen rocket structure and fires a combination of the events fired during
* the freeze. The event type is a combination of those fired and the source is the
freezeList = null;
return;
}
-
+
log.debug("Thawing rocket, freezeList=" + freezeList);
-
+
int type = 0;
Object c = null;
for (ComponentChangeEvent e : freezeList) {
c = e.getSource();
}
freezeList = null;
-
+
fireComponentChangeEvent(new ComponentChangeEvent((RocketComponent) c, type));
}
-
-
+
+
//////// Motor configurations ////////
-
+
/**
* Return the default configuration. This should be used in the user interface
checkState();
return defaultConfiguration;
}
-
-
+
+
/**
* Return an array of the motor configuration IDs. This array is guaranteed
* to contain the <code>null</code> ID as the first element.
checkState();
return motorConfigurationIDs.toArray(new String[0]);
}
-
+
/**
* Add a new motor configuration ID to the motor configurations. The new ID
* is returned.
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
return id;
}
-
+
/**
* Add a specified motor configuration ID to the motor configurations.
*
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.
motorConfigurationIDs.remove(id);
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
}
-
-
+
+
/**
* Check whether <code>id</code> is a valid motor configuration ID.
*
checkState();
return motorConfigurationIDs.contains(id);
}
-
-
+
+
/**
* Check whether the given motor configuration ID has motors defined for it.
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())
}
return false;
}
-
-
+
+
/**
* Return the user-set name of the motor configuration. If no name has been set,
* returns an empty string (not null).
return "";
return s;
}
-
-
+
+
/**
* Set the name of the motor configuration. A name can be unset by passing
* <code>null</code> or an empty string.
motorConfigurationNames.put(id, name);
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
-
-
+
+
/**
* Return either the motor configuration name (if set) or its description.
*
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, generated from the motor
* designations of the components.
checkState();
String name;
int motorCount = 0;
-
+
// 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.iterator();
while (iterator.hasNext()) {
RocketComponent c = iterator.next();
-
+
if (c instanceof Stage) {
-
+
currentList = new ArrayList<String>();
list.add(currentList);
-
+
} else if (c instanceof MotorMount) {
-
+
MotorMount mount = (MotorMount) c;
Motor motor = mount.getMotor(id);
-
+
if (mount.isMotorMount() && motor != null) {
String designation = motor.getDesignation(mount.getMotorDelay(id));
-
+
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) {
String stageName = "";
String previous = null;
int count = 0;
-
+
Collections.sort(stage);
for (String current : stage) {
if (current.equals(previous)) {
-
+
count++;
-
+
} else {
-
+
if (previous != null) {
String s = "";
if (count > 1) {
} else {
s = previous;
}
-
+
if (stageName.equals(""))
stageName = s;
else
stageName = stageName + "," + s;
}
-
+
previous = current;
count = 1;
-
+
}
}
if (previous != null) {
} else {
s = previous;
}
-
+
if (stageName.equals(""))
stageName = s;
else
stageName = stageName + "," + s;
}
-
+
stages.add(stageName);
}
-
+
name = "[";
for (int i = 0; i < stages.size(); i++) {
String s = stages.get(i);
name += "]";
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);
}
-
+
@Override
public double getComponentMass() {
return 0;
}
-
+
@Override
public double getLongitudinalUnitInertia() {
return 0;
}
-
+
@Override
public double getRotationalUnitInertia() {
return 0;
}
-
+
@Override
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));
}
-
+
}