diff --git a/core/src/main/java/info/openrocket/core/simulation/DataBranch.java b/core/src/main/java/info/openrocket/core/simulation/DataBranch.java index 8f1e6cae8..7c4913eb7 100644 --- a/core/src/main/java/info/openrocket/core/simulation/DataBranch.java +++ b/core/src/main/java/info/openrocket/core/simulation/DataBranch.java @@ -1,14 +1,133 @@ package info.openrocket.core.simulation; +import info.openrocket.core.util.ArrayList; +import info.openrocket.core.util.ModID; import info.openrocket.core.util.Monitorable; +import info.openrocket.core.util.Mutable; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Set; /** * A branch of data / collection of data points for a specific type of data. * @param the type of data in this branch */ -public interface DataBranch extends Monitorable { +public abstract class DataBranch implements Monitorable { + protected final String name; + protected final Map> values = new LinkedHashMap<>(); + protected final Map maxValues = new HashMap<>(); + protected final Map minValues = new HashMap<>(); + + protected final Mutable mutable = new Mutable(); + protected ModID modID = ModID.INVALID; + + /** + * Sole constructor. Defines the name of the DataBranch and at least one variable type. + * + * @param name the name of this DataBranch. + * @param types data types to include (must include at least one type). + */ + @SafeVarargs + public DataBranch(String name, T... types) { + if (types.length == 0) { + throw new IllegalArgumentException("Must specify at least one data type."); + } + + this.name = name; + + for (T t : types) { + addType(t); + } + } + + public DataBranch(String name) { + this.name = name; + } + + protected void addType(T type) { + if (values.containsKey(type)) { + throw new IllegalArgumentException("Value type " + type + " already exists."); + } + + values.put(type, new ArrayList<>()); + minValues.put(type, Double.NaN); + maxValues.put(type, Double.NaN); + } + + /** + * Adds a new point into the data branch. The value for all types is set to NaN by default. + * + * @throws IllegalStateException if this object has been made immutable. + */ + public void addPoint() { + mutable.check(); + + for (Map.Entry> entry : values.entrySet()) { + sanityCheckValues(entry.getKey(), Double.NaN); + entry.getValue().add(Double.NaN); + } + modID = new ModID(); + } + + private void sanityCheckValues(T type, Double value) { + ArrayList list = values.get(type); + + if (list == null) { + list = new info.openrocket.core.util.ArrayList<>(); + int n = getLength(); + for (int i = 0; i < n; i++) { + list.add(Double.NaN); + } + values.put(type, list); + minValues.put(type, value); + maxValues.put(type, value); + } + } + + /** + * Set the value for a specific data type at the latest point. New variable types can be + * added to the FlightDataBranch transparently. + * + * @param type the variable to set. + * @param value the value to set. + * @throws IllegalStateException if this object has been made immutable. + */ + public void setValue(T type, double value) { + mutable.check(); + + ArrayList list = values.computeIfAbsent(type, k -> { + ArrayList newList = new ArrayList<>(); + int n = getLength(); + for (int i = 0; i < n; i++) { + newList.add(Double.NaN); + } + minValues.put(k, Double.NaN); + maxValues.put(k, Double.NaN); + return newList; + }); + + if (list.size() > 0) { + list.set(list.size() - 1, value); + } + + double min = minValues.get(type); + double max = maxValues.get(type); + + if (Double.isNaN(min) || (value < min)) { + minValues.put(type, value); + } + if (Double.isNaN(max) || (value > max)) { + maxValues.put(type, value); + } + modID = new ModID(); + } + + /** * Return an array of values for the specified variable type. * @@ -16,7 +135,12 @@ public interface DataBranch extends Monitorable { * @return a list of the variable values, or null if * the variable type hasn't been added to this branch. */ - List get(T type); + public List get(T type) { + ArrayList list = values.get(type); + if (list == null) + return null; + return list.clone(); + } /** * Return the value of the specified type at the specified index. @@ -24,7 +148,16 @@ public interface DataBranch extends Monitorable { * @param index the data index of the value * @return the value at the specified index */ - Double getByIndex(T type, int index); + public Double getByIndex(T type, int index) { + if (index < 0 || index >= getLength()) { + throw new IllegalArgumentException("Index out of bounds"); + } + ArrayList list = values.get(type); + if (list == null) { + return null; + } + return list.get(index); + } /** * Return the last value of the specified type in the branch, or NaN if the type is @@ -33,7 +166,12 @@ public interface DataBranch extends Monitorable { * @param type the parameter type. * @return the last value in this branch, or NaN. */ - double getLast(T type); + public double getLast(T type) { + ArrayList list = values.get(type); + if (list == null || list.isEmpty()) + return Double.NaN; + return list.get(list.size() - 1); + } /** * Return the minimum value of the specified type in the branch, or NaN if the type @@ -42,7 +180,12 @@ public interface DataBranch extends Monitorable { * @param type the parameter type. * @return the minimum value in this branch, or NaN. */ - double getMinimum(T type); + public double getMinimum(T type) { + Double v = minValues.get(type); + if (v == null) + return Double.NaN; + return v; + } /** * Return the maximum value of the specified type in the branch, or NaN if the type @@ -51,21 +194,59 @@ public interface DataBranch extends Monitorable { * @param type the parameter type. * @return the maximum value in this branch, or NaN. */ - double getMaximum(T type); + public double getMaximum(T type) { + Double v = maxValues.get(type); + if (v == null) + return Double.NaN; + return v; + } /** * Return the number of data points in this branch. */ - int getLength(); + public int getLength() { + for (ArrayList doubles : values.values()) { + return doubles.size(); + } + return 0; + } /** * Return the variable types included in this branch. The types are sorted in their * natural order. */ - T[] getTypes(); + @SuppressWarnings("unchecked") + public T[] getTypes() { + Set keySet = values.keySet(); + T[] array = (T[]) Array.newInstance(keySet.iterator().next().getClass(), keySet.size()); + keySet.toArray(array); + Arrays.sort(array); + return array; + } /** * Return the branch name. */ - String getName(); + public String getName() { + return name; + } + + /** + * Make this FlightDataBranch immutable. Any calls to the set methods that would + * modify this object will after this call throw an IllegalStateException. + */ + public void immute() { + mutable.immute(); + } + + /** + * Return whether this branch is still mutable. + */ + public boolean isMutable() { + return mutable.isMutable(); + } + + public ModID getModID() { + return modID; + } } diff --git a/core/src/main/java/info/openrocket/core/simulation/FlightDataBranch.java b/core/src/main/java/info/openrocket/core/simulation/FlightDataBranch.java index e866abc9e..e844add41 100644 --- a/core/src/main/java/info/openrocket/core/simulation/FlightDataBranch.java +++ b/core/src/main/java/info/openrocket/core/simulation/FlightDataBranch.java @@ -1,8 +1,5 @@ package info.openrocket.core.simulation; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -11,8 +8,6 @@ import info.openrocket.core.rocketcomponent.Rocket; import info.openrocket.core.rocketcomponent.RocketComponent; import info.openrocket.core.util.ArrayList; import info.openrocket.core.util.ModID; -import info.openrocket.core.util.Monitorable; -import info.openrocket.core.util.Mutable; /** * A single branch of flight data. The data is ordered based on some variable, typically time. @@ -29,31 +24,11 @@ import info.openrocket.core.util.Mutable; * * @author Sampo Niskanen */ -public class FlightDataBranch implements DataBranch { - - /** The name of this flight data branch. */ - private final String name; - - private final Map> values = new LinkedHashMap<>(); - - private final Map maxValues = new HashMap<>(); - private final Map minValues = new HashMap<>(); - - /** - * time for the rocket to reach apogee if the flight had been no recovery deployment - */ +public class FlightDataBranch extends DataBranch { private double timeToOptimumAltitude = Double.NaN; - /** - * Altitude the rocket would reach if there had been no recovery deployment. - */ private double optimumAltitude = Double.NaN; - private final ArrayList events = new ArrayList<>(); - private final Mutable mutable = new Mutable(); - - private ModID modID = ModID.INVALID; - /** * Sole constructor. Defines the name of the FlightDataBranch and at least one variable type. * @@ -61,22 +36,7 @@ public class FlightDataBranch implements DataBranch { * @param types data types to include (must include at least one type). */ public FlightDataBranch(String name, FlightDataType... types) { - if (types.length == 0) { - throw new IllegalArgumentException("Must specify at least one data type."); - } - - this.name = name; - - for (FlightDataType t : types) { - if (values.containsKey(t)) { - throw new IllegalArgumentException("Value type " + t + " specified multiple " + - "times in constructor."); - } - - values.put(t, new ArrayList<>()); - minValues.put(t, Double.NaN); - maxValues.put(t, Double.NaN); - } + super(name, types); } /** @@ -89,7 +49,7 @@ public class FlightDataBranch implements DataBranch { * @param parent the parent branch to copy data from. */ public FlightDataBranch(String name, RocketComponent srcComponent, FlightDataBranch parent) { - this.name = name; + super(name); // Copy all the values from the parent copyValuesFromBranch(parent, srcComponent); @@ -99,72 +59,13 @@ public class FlightDataBranch implements DataBranch { * Makes an 'empty' flight data branch which has no data but all built in data types are defined. */ public FlightDataBranch() { - name = "Empty branch"; + super("Empty branch"); for (FlightDataType type : FlightDataType.ALL_TYPES) { this.setValue(type, Double.NaN); } this.immute(); } - - /** - * Adds a new point into the data branch. The value for all types is set to NaN by default. - * - * @throws IllegalStateException if this object has been made immutable. - */ - public void addPoint() { - mutable.check(); - - for (Map.Entry> entry : values.entrySet()) { - sanityCheckValues(entry.getKey(), Double.NaN); - entry.getValue().add(Double.NaN); - } - modID = new ModID(); - } - private void sanityCheckValues(FlightDataType type, Double value) { - ArrayList list = values.get(type); - - if (list == null) { - list = new ArrayList<>(); - int n = getLength(); - for (int i = 0; i < n; i++) { - list.add(Double.NaN); - } - values.put(type, list); - minValues.put(type, value); - maxValues.put(type, value); - } - } - - /** - * Set the value for a specific data type at the latest point. New variable types can be - * added to the FlightDataBranch transparently. - * - * @param type the variable to set. - * @param value the value to set. - * @throws IllegalStateException if this object has been made immutable. - */ - public void setValue(FlightDataType type, double value) { - mutable.check(); - - sanityCheckValues(type, value); - ArrayList list = values.get(type); - - if (list.size() > 0) { - list.set(list.size() - 1, value); - } - - double min = minValues.get(type); - double max = maxValues.get(type); - - if (Double.isNaN(min) || (value < min)) { - minValues.put(type, value); - } - if (Double.isNaN(max) || (value > max)) { - maxValues.put(type, value); - } - modID = new ModID(); - } /** * Clears all the current values in the branch and copies the values from the given branch. @@ -228,70 +129,6 @@ public class FlightDataBranch implements DataBranch { } } - @Override - public String getName() { - return name; - } - - @Override - public FlightDataType[] getTypes() { - FlightDataType[] array = values.keySet().toArray(new FlightDataType[0]); - Arrays.sort(array); - return array; - } - - @Override - public int getLength() { - for (ArrayList doubles : values.values()) { - return doubles.size(); - } - return 0; - } - - @Override - public List get(FlightDataType type) { - ArrayList list = values.get(type); - if (list == null) - return null; - return list.clone(); - } - - @Override - public Double getByIndex(FlightDataType type, int index) { - if (index < 0 || index >= getLength()) { - throw new IllegalArgumentException("Index out of bounds"); - } - ArrayList list = values.get(type); - if (list == null) { - return null; - } - return list.get(index); - } - - @Override - public double getLast(FlightDataType type) { - ArrayList list = values.get(type); - if (list == null || list.isEmpty()) - return Double.NaN; - return list.get(list.size() - 1); - } - - @Override - public double getMinimum(FlightDataType type) { - Double v = minValues.get(type); - if (v == null) - return Double.NaN; - return v; - } - - @Override - public double getMaximum(FlightDataType type) { - Double v = maxValues.get(type); - if (v == null) - return Double.NaN; - return v; - } - /** * @return the timeToOptimumAltitude @@ -387,28 +224,6 @@ public class FlightDataBranch implements DataBranch { } return retval; } - - /** - * Make this FlightDataBranch immutable. Any calls to the set methods that would - * modify this object will after this call throw an IllegalStateException. - */ - public void immute() { - mutable.immute(); - } - - - /** - * Return whether this branch is still mutable. - */ - public boolean isMutable() { - return mutable.isMutable(); - } - - - @Override - public ModID getModID() { - return modID; - } public FlightDataBranch clone() { FlightDataType[] types = getTypes();