diff --git a/core/fileformat.txt b/core/fileformat.txt index 3e58beee3..7f8fb9321 100644 --- a/core/fileformat.txt +++ b/core/fileformat.txt @@ -40,4 +40,8 @@ The following file format versions exist: elements to stage components (except sustainer). 1.5: Introduced with OpenRocket 12.xx. Added ComponentPresets. - Added lowerstageseparation as recovery device deployment event. \ No newline at end of file + Added lowerstageseparation as recovery device deployment event. + +1.6 (pre): + Added section for supporting datatypes other than + internal ones. Currently only supports datatypes from custom expressions. \ No newline at end of file diff --git a/core/lib/exp4j-rdg.jar b/core/lib/exp4j-rdg.jar new file mode 100644 index 000000000..0de4e9d98 Binary files /dev/null and b/core/lib/exp4j-rdg.jar differ diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 0715bc796..d8da23a54 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -454,14 +454,18 @@ customExpression.Description = Description ! Custom expression panel customExpressionPanel.but.NewExpression = New expression +customExpressionPanel.but.ttip.NewExpression = Add a new custom expression +customExpressionPanel.but.Import = Import +customExpressionPanel.but.ttip.Import = Import custom expressions from another .ork file customExpressionPanel.lbl.UpdateNote = You must run the simulation before data will be available for plotting. customExpressionPanel.lbl.CalcNote = Expressions will be calculated in the order shown. -customExpressionPanel.lbl.CustomExpressions = Custom Expressions : +customExpressionPanel.lbl.CustomExpressions = Custom Expressions customExpression.Units.but.ttip.Remove = Remove this expression customExpression.Units.but.ttip.Edit = Edit this expression customExpression.Units.but.ttip.MoveUp = Move expression up in calculation order customExpression.Units.but.ttip.MoveDown = Move expression down in calculation order + ! Custom expression builder window ExpressionBuilderDialog.title = Expression Builder ExpressionBuilderDialog.InsertVariable = Insert Variable @@ -482,12 +486,12 @@ CustomOperatorSelector.title = Operator Selector Operator.plus = Addition Operator.minus = Subtraction Operator.star = Multiplication -Operator.div = Divison +Operator.div = Division Operator.mod = Modulo Operator.pow = Exponentiation Operator.abs = Absolute value -Operator.ceil = Ceiling (next integer value -Operator.floor = Floor (previous integer value +Operator.ceil = Ceiling (next integer value) +Operator.floor = Floor (previous integer value) Operator.sqrt = Square root Operator.cbrt = Cubic root Operator.exp = Euler\'s number raised to the value (e^x) @@ -498,9 +502,24 @@ Operator.tan = Tangent Operator.asin = Arc sine Operator.acos = Arc cosine Operator.atan = Arc tangent -Operator.hsin = Hyerbolic sine +Operator.hsin = Hyperbolic sine Operator.hcos = Hyperbolic cosine Operator.htan = Hyperbolic tangent +Operator.log10 = Base 10 logarithm +Operator.round = Round to nearest integer value +Operator.random = Random number between zero and given value +Operator.expm1 = The same as exp(x)-1, but more accurate for small x +Operator.mean = The arithmetic mean of a given range +Operator.min = The minimum value in a given range +Operator.max = The maximum value in a given range +Operator.var = The variance of a given range +Operator.stdev = The standard deviation of a given range +Operator.rms = The root-mean-squared value of a given range +Operator.lclip = Clips a value (1st parameter) to be no less than a given value (2nd parameter) +Operator.uclip = Clips a value (1st parameter) to be no greater than a given value (2nd parameter) +Operator.binf = Gives the fraction of values in a given range (1st parameter) inside a bin with given lower (2nd parameter) and upper (3rd parameter) bounds +Operator.trapz = Integrates the given range using trapezoidal integration +Operator.tnear = Find the time corresponding to the point in a range (1st parameter) nearest to a given value (2nd parameter) ! MotorPlot MotorPlot.title.Motorplot = Motor plot @@ -1068,6 +1087,8 @@ main.menu.analyze.componentAnalysis = Component analysis main.menu.analyze.componentAnalysis.desc = Analyze the rocket components separately main.menu.analyze.optimization = Rocket optimization main.menu.analyze.optimization.desc = General rocket design optimization +main.menu.analyze.customExpressions = Custom expressions +main.menu.analyze.customExpressions.desc = Define new flight data types by writing custom mathematical expressions main.menu.help = Help main.menu.help.desc = Information about OpenRocket diff --git a/core/src/net/sf/openrocket/document/OpenRocketDocument.java b/core/src/net/sf/openrocket/document/OpenRocketDocument.java index cafc98f33..c7719346b 100644 --- a/core/src/net/sf/openrocket/document/OpenRocketDocument.java +++ b/core/src/net/sf/openrocket/document/OpenRocketDocument.java @@ -13,6 +13,7 @@ import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; import net.sf.openrocket.rocketcomponent.ComponentChangeListener; import net.sf.openrocket.rocketcomponent.Configuration; import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.simulation.customexpression.CustomExpression; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.ArrayList; @@ -50,7 +51,8 @@ public class OpenRocketDocument implements ComponentChangeListener { private final Configuration configuration; private final ArrayList simulations = new ArrayList(); - + private ArrayList customExpressions = new ArrayList(); + /* * The undo/redo variables and mechanism are documented in doc/undo-redo-flow.* @@ -103,7 +105,22 @@ public class OpenRocketDocument implements ComponentChangeListener { } - + public void addCustomExpression(CustomExpression expression){ + if (customExpressions.contains(expression)){ + log.user("Could not add custom expression "+expression.getName()+" to document as document alerady has a matching expression."); + } else { + customExpressions.add(expression); + } + } + + public void removeCustomExpression(CustomExpression expression){ + customExpressions.remove(expression); + } + + public ArrayList getCustomExpressions(){ + return customExpressions; + } + public Rocket getRocket() { return rocket; diff --git a/core/src/net/sf/openrocket/document/Simulation.java b/core/src/net/sf/openrocket/document/Simulation.java index c947e23d9..162ee20d0 100644 --- a/core/src/net/sf/openrocket/document/Simulation.java +++ b/core/src/net/sf/openrocket/document/Simulation.java @@ -13,7 +13,6 @@ import net.sf.openrocket.masscalc.MassCalculator; import net.sf.openrocket.rocketcomponent.Configuration; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.simulation.BasicEventSimulationEngine; -import net.sf.openrocket.simulation.CustomExpression; import net.sf.openrocket.simulation.FlightData; import net.sf.openrocket.simulation.RK4SimulationStepper; import net.sf.openrocket.simulation.SimulationConditions; @@ -72,7 +71,6 @@ public class Simulation implements ChangeSource, Cloneable { private SimulationOptions options; private ArrayList simulationListeners = new ArrayList(); - private ArrayList customExpressions = new ArrayList(); private final Class simulationEngineClass = BasicEventSimulationEngine.class; private Class simulationStepperClass = RK4SimulationStepper.class; @@ -161,21 +159,6 @@ public class Simulation implements ChangeSource, Cloneable { return document; } - public void addCustomExpression(CustomExpression expression){ - this.status = Simulation.Status.OUTDATED; - log.debug("Simulation must be run again to update custom expression."); - customExpressions.add(expression); - } - - public void removeCustomExpression(CustomExpression expression){ - customExpressions.remove(expression); - } - - public ArrayList getCustomExpressions(){ - return customExpressions; - } - - /** * Return the rocket associated with this simulation. * diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index 9378a4551..fa1c372f5 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -24,7 +24,7 @@ import net.sf.openrocket.rocketcomponent.RecoveryDevice.DeployEvent; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.TubeCoupler; -import net.sf.openrocket.simulation.CustomExpression; +import net.sf.openrocket.simulation.customexpression.CustomExpression; import net.sf.openrocket.simulation.FlightData; import net.sf.openrocket.simulation.FlightDataBranch; import net.sf.openrocket.simulation.FlightDataType; @@ -101,6 +101,9 @@ public class OpenRocketSaver extends RocketSaver { writeln(""); + // Save custom expressions; + saveCustomDatatypes(document); + // Save all simulations writeln(""); indent++; @@ -124,7 +127,37 @@ public class OpenRocketSaver extends RocketSaver { } } + /* + * Save all the custom expressions + */ + private void saveCustomDatatypes(OpenRocketDocument doc) throws IOException { + + if (doc.getCustomExpressions().isEmpty()) + return; + + writeln(""); indent++; + + for (CustomExpression exp : doc.getCustomExpressions()){ + saveCustomExpressionDatatype(exp); + } + + indent--; writeln(""); + writeln(""); + } + /* + * Save one custom expression datatype + */ + private void saveCustomExpressionDatatype(CustomExpression exp) throws IOException { + // Write out custom expression + + writeln(""); indent++; + writeln("" + exp.getName() + ""); + writeln("" + exp.getSymbol() + ""); + writeln("" + exp.getUnit() + ""); // auto unit type means it will be determined from string + writeln("" + exp.getExpressionString() + ""); + indent--; writeln(""); + } @Override public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) { @@ -327,20 +360,6 @@ public class OpenRocketSaver extends RocketSaver { writeln("RK4Simulator"); writeln("BarrowmanCalculator"); - // Write out custom expressions - if (!simulation.getCustomExpressions().isEmpty()){ - writeln(""); indent++; - for (CustomExpression expression : simulation.getCustomExpressions()){ - writeln(""); indent++; - writeElement("name", expression.getName()); - writeElement("symbol", expression.getSymbol()); - writeElement("unit", expression.getUnit()); - writeElement("expressionstring", expression.getExpressionString()); - indent--; writeln(""); - } - indent--; writeln(""); - } - writeln(""); indent++; writeElement("configid", cond.getMotorConfigurationID()); @@ -453,13 +472,16 @@ public class OpenRocketSaver extends RocketSaver { sb.append(" 0) sb.append(","); sb.append(escapeXML(types[i].getKey())); } + */ + sb.append("\" types=\""); for (int i = 0; i < types.length; i++) { if (i > 0) diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java index 7ef136b83..b8b34b3b9 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java @@ -67,7 +67,7 @@ import net.sf.openrocket.rocketcomponent.ThicknessRingComponent; import net.sf.openrocket.rocketcomponent.Transition; import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; import net.sf.openrocket.rocketcomponent.TubeCoupler; -import net.sf.openrocket.simulation.CustomExpression; +import net.sf.openrocket.simulation.customexpression.CustomExpression; import net.sf.openrocket.simulation.FlightData; import net.sf.openrocket.simulation.FlightDataBranch; import net.sf.openrocket.simulation.FlightDataType; @@ -649,6 +649,7 @@ class OpenRocketContentHandler extends AbstractElementHandler { private boolean rocketDefined = false; private boolean simulationsDefined = false; + private boolean datatypesDefined = false; public OpenRocketContentHandler(DocumentLoadingContext context) { this.context = context; @@ -656,7 +657,6 @@ class OpenRocketContentHandler extends AbstractElementHandler { this.doc = new OpenRocketDocument(rocket); } - public OpenRocketDocument getDocument() { if (!rocketDefined) return null; @@ -677,6 +677,15 @@ class OpenRocketContentHandler extends AbstractElementHandler { rocketDefined = true; return new ComponentParameterHandler(rocket, context); } + + if (element.equals("datatypes")){ + if (datatypesDefined) { + warnings.add(Warning.fromString("Multiple datatype blocks. Ignoring later ones.")); + return null; + } + datatypesDefined = true; + return new DatatypeHandler(this, context); + } if (element.equals("simulations")) { if (simulationsDefined) { @@ -697,6 +706,90 @@ class OpenRocketContentHandler extends AbstractElementHandler { +class DatatypeHandler extends AbstractElementHandler { + private final DocumentLoadingContext context; + private final OpenRocketContentHandler contentHandler; + private CustomExpressionHandler customExpressionHandler = null; + + public DatatypeHandler(OpenRocketContentHandler contentHandler, DocumentLoadingContext context) { + this.context = context; + this.contentHandler = contentHandler; + } + + @Override + public ElementHandler openElement(String element, + HashMap attributes, WarningSet warnings) + throws SAXException { + + if (element.equals("type") && attributes.get("source").equals("customexpression") ){ + customExpressionHandler = new CustomExpressionHandler(contentHandler, context); + return customExpressionHandler; + } + else { + warnings.add(Warning.fromString("Unknown datatype " + element + " defined, ignoring")); + } + + return this; + } + + @Override + public void closeElement(String element, HashMap attributes, String content, WarningSet warnings) throws SAXException { + attributes.remove("source"); + super.closeElement(element, attributes, content, warnings); + + if (customExpressionHandler != null){ + contentHandler.getDocument().addCustomExpression(customExpressionHandler.currentExpression); + } + + } + +} + +class CustomExpressionHandler extends AbstractElementHandler{ + private final DocumentLoadingContext context; + private final OpenRocketContentHandler contentHandler; + public CustomExpression currentExpression; + + public CustomExpressionHandler(OpenRocketContentHandler contentHandler, DocumentLoadingContext context) { + this.context = context; + this.contentHandler = contentHandler; + currentExpression = new CustomExpression(contentHandler.getDocument()); + + } + + @Override + public ElementHandler openElement(String element, + HashMap attributes, WarningSet warnings) + throws SAXException { + + return this; + } + + @Override + public void closeElement(String element, HashMap attributes, + String content, WarningSet warnings) throws SAXException { + + if (element.equals("type")) { + contentHandler.getDocument().addCustomExpression(currentExpression); + } + + if (element.equals("name")) { + currentExpression.setName(content); + } + + if (element.equals("symbol")) { + currentExpression.setSymbol(content); + } + + if (element.equals("unit") && attributes.get("unittype").equals("auto")) { + currentExpression.setUnit(content); + } + + if (element.equals("expression")){ + currentExpression.setExpression(content); + } + } +} /** * A handler that creates components from the corresponding elements. The control of the @@ -1211,9 +1304,7 @@ class SingleSimulationHandler extends AbstractElementHandler { private SimulationConditionsHandler conditionHandler; private FlightDataHandler dataHandler; - private CustomExpressionsHandler customExpressionsHandler; - - private ArrayList customExpressions = new ArrayList(); + private final List listeners = new ArrayList(); public SingleSimulationHandler(OpenRocketDocument doc, DocumentLoadingContext context) { @@ -1221,14 +1312,10 @@ class SingleSimulationHandler extends AbstractElementHandler { this.context = context; } - public void setCustomExpressions(ArrayList expressions){ - this.customExpressions = expressions; + public OpenRocketDocument getDocument(){ + return doc; } - - public ArrayList getCustomExpressions(){ - return customExpressions; - } - + @Override public ElementHandler openElement(String element, HashMap attributes, WarningSet warnings) { @@ -1236,9 +1323,6 @@ class SingleSimulationHandler extends AbstractElementHandler { if (element.equals("name") || element.equals("simulator") || element.equals("calculator") || element.equals("listener")) { return PlainTextHandler.INSTANCE; - } else if (element.equals("customexpressions")) { - customExpressionsHandler = new CustomExpressionsHandler(this, context); - return customExpressionsHandler; } else if (element.equals("conditions")) { conditionHandler = new SimulationConditionsHandler(doc.getRocket(), context); return conditionHandler; @@ -1301,70 +1385,11 @@ class SingleSimulationHandler extends AbstractElementHandler { Simulation simulation = new Simulation(doc, doc.getRocket(), status, name, conditions, listeners, data); - - // Note : arraylist implementation in simulation different from standard one - for (CustomExpression exp : customExpressions){ - exp.setSimulation(simulation); - if (exp.checkAll()) - simulation.addCustomExpression(exp); - } - + doc.addSimulation(simulation); } } - -class CustomExpressionsHandler extends AbstractElementHandler { - private final DocumentLoadingContext context; - private final SingleSimulationHandler simHandler; - public CustomExpression currentExpression = new CustomExpression(); - private final ArrayList customExpressions = new ArrayList(); - - - public CustomExpressionsHandler(SingleSimulationHandler simHandler, DocumentLoadingContext context) { - this.context = context; - this.simHandler = simHandler; - } - - @Override - public ElementHandler openElement(String element, - HashMap attributes, WarningSet warnings) - throws SAXException { - - if (element.equals("expression")){ - currentExpression = new CustomExpression(); - } - - return this; - } - - @Override - public void closeElement(String element, HashMap attributes, - String content, WarningSet warnings) { - - if (element.equals("expression")) - customExpressions.add(currentExpression); - - if (element.equals("name")) - currentExpression.setName(content); - - else if (element.equals("symbol")) - currentExpression.setSymbol(content); - - else if (element.equals("unit")) - currentExpression.setUnit(content); - - else if (element.equals("expressionstring")) - currentExpression.setExpression(content); - - } - - @Override - public void endHandler(String element, HashMap attributes, - String content, WarningSet warnings) { - simHandler.setCustomExpressions(customExpressions); - } -} - + class SimulationConditionsHandler extends AbstractElementHandler { private final DocumentLoadingContext context; private SimulationOptions conditions; @@ -1548,7 +1573,7 @@ class FlightDataHandler extends AbstractElementHandler { private FlightDataBranchHandler dataHandler; private WarningSet warningSet = new WarningSet(); private List branches = new ArrayList(); - + private SingleSimulationHandler simHandler; private FlightData data; @@ -1575,9 +1600,8 @@ class FlightDataHandler extends AbstractElementHandler { return null; } dataHandler = new FlightDataBranchHandler( attributes.get("name"), - attributes.get("typekeys"), - attributes.get("types"), - simHandler, context); + attributes.get("types"), + simHandler, context); return dataHandler; } @@ -1673,23 +1697,18 @@ class FlightDataBranchHandler extends AbstractElementHandler { private final DocumentLoadingContext context; private final FlightDataType[] types; private final FlightDataBranch branch; - + private static final LogHelper log = Application.getLogger(); private final SingleSimulationHandler simHandler; - - public FlightDataBranchHandler(String name, String typeKeyList, String typeList, SingleSimulationHandler simHandler, DocumentLoadingContext context) { + + public FlightDataBranchHandler(String name, String typeList, SingleSimulationHandler simHandler, DocumentLoadingContext context) { this.simHandler = simHandler; this.context = context; - String[] typeNames = typeList.split(","); - String[] typeKeys = null; - if ( typeKeyList != null ) { - typeKeys = typeKeyList.split(","); - } - types = new FlightDataType[typeNames.length]; - for (int i = 0; i < typeNames.length; i++) { - String typeName = typeNames[i]; - String typeKey = (typeKeys != null ) ? typeKeys[i] : null ; - FlightDataType matching = findFlightDataType(typeKey, typeName); + String[] split = typeList.split(","); + types = new FlightDataType[split.length]; + for (int i = 0; i < split.length; i++) { + String typeName = split[i]; + FlightDataType matching = findFlightDataType(typeName); types[i] = matching; //types[i] = FlightDataType.getType(typeName, matching.getSymbol(), matching.getUnitGroup()); } @@ -1697,13 +1716,14 @@ class FlightDataBranchHandler extends AbstractElementHandler { // TODO: LOW: May throw an IllegalArgumentException branch = new FlightDataBranch(name, types); } - + // Find the full flight data type given name only // Note: this way of doing it requires that custom expressions always come before flight data in the file, // not the nicest but this is always the case anyway. - private FlightDataType findFlightDataType(String key, String name){ - - // Look in built in types by key. + private FlightDataType findFlightDataType(String name){ + + // Kevins version with lookup by key. Not using right now + /* if ( key != null ) { for (FlightDataType t : FlightDataType.ALL_TYPES){ if (t.getKey().equals(key) ){ @@ -1711,33 +1731,22 @@ class FlightDataBranchHandler extends AbstractElementHandler { } } } - // Look in built in types by name. + */ + + // Look in built in types for (FlightDataType t : FlightDataType.ALL_TYPES){ if (t.getName().equals(name) ){ return t; } } - + // Look in custom expressions - for (CustomExpression exp : simHandler.getCustomExpressions()){ + for (CustomExpression exp : simHandler.getDocument().getCustomExpressions()){ if (exp.getName().equals(name) ){ return exp.getType(); } } - - // Look in custom expressions, meanwhile set priority based on order in file - /* - int totalExpressions = simHandler.getCustomExpressions().size(); - for (int i=0; i attributes, + WarningSet warnings) { + // FIXME - probably need more data in the warning messages - like what component preset... + String manufacturerName = attributes.get("manufacturer"); + if ( manufacturerName == null ) { + warnings.add(Warning.fromString("Invalid ComponentPreset, no manufacturer specified. Ignored")); + return; } - @Override - public void set(RocketComponent c, String name, HashMap attributes, - WarningSet warnings) { - // FIXME - probably need more data in the warning messages - like what component preset... - String manufacturerName = attributes.get("manufacturer"); - if ( manufacturerName == null ) { - warnings.add(Warning.fromString("Invalid ComponentPreset, no manufacturer specified. Ignored")); - return; - } - - String productNo = attributes.get("partno"); - if ( productNo == null ) { - warnings.add(Warning.fromString("Invalid ComponentPreset, no partno specified. Ignored")); - return; - } - - String digest = attributes.get("digest"); - if ( digest == null ) { - warnings.add(Warning.fromString("Invalid ComponentPreset, no digest specified.")); - } - - String type = attributes.get("type"); - if ( type == null ) { - warnings.add(Warning.fromString("Invalid ComponentPreset, no type specified.")); - } - - List presets = Application.getComponentPresetDao().find( manufacturerName, productNo ); - - ComponentPreset matchingPreset = null; - - for( ComponentPreset preset: presets ) { - if ( digest != null && preset.getDigest().equals(digest) ) { - // Found one with matching digest. Take it. - matchingPreset = preset; - break; - } - if ( type != null && preset.getType().name().equals(type) && matchingPreset != null) { - // Found the first one with matching type. - matchingPreset = preset; - } - } - - // Was any found? - if ( matchingPreset == null ) { - warnings.add(Warning.fromString("No matching ComponentPreset found " + manufacturerName + " " + productNo)); - return; - } - - if ( digest != null && !matchingPreset.getDigest().equals(digest) ) { - warnings.add(Warning.fromString("ComponentPreset has wrong digest")); - } - - setMethod.invoke(c, matchingPreset); + String productNo = attributes.get("partno"); + if ( productNo == null ) { + warnings.add(Warning.fromString("Invalid ComponentPreset, no partno specified. Ignored")); + return; } + + String digest = attributes.get("digest"); + if ( digest == null ) { + warnings.add(Warning.fromString("Invalid ComponentPreset, no digest specified.")); + } + + String type = attributes.get("type"); + if ( type == null ) { + warnings.add(Warning.fromString("Invalid ComponentPreset, no type specified.")); + } + + List presets = Application.getComponentPresetDao().find( manufacturerName, productNo ); + + ComponentPreset matchingPreset = null; + + for( ComponentPreset preset: presets ) { + if ( digest != null && preset.getDigest().equals(digest) ) { + // Found one with matching digest. Take it. + matchingPreset = preset; + break; + } + if ( type != null && preset.getType().name().equals(type) && matchingPreset != null) { + // Found the first one with matching type. + matchingPreset = preset; + } + } + + // Was any found? + if ( matchingPreset == null ) { + warnings.add(Warning.fromString("No matching ComponentPreset found " + manufacturerName + " " + productNo)); + return; + } + + if ( digest != null && !matchingPreset.getDigest().equals(digest) ) { + warnings.add(Warning.fromString("ComponentPreset has wrong digest")); + } + + setMethod.invoke(c, matchingPreset); +} } ////MaterialSetter - sets a Material value class MaterialSetter implements Setter { - private final Reflection.Method setMethod; - private final Material.Type type; +private final Reflection.Method setMethod; +private final Material.Type type; - public MaterialSetter(Reflection.Method set, Material.Type type) { - this.setMethod = set; - this.type = type; - } - - @Override - public void set(RocketComponent c, String name, HashMap attributes, - WarningSet warnings) { - - Material mat; - - // Check name != "" - name = name.trim(); - if (name.equals("")) { - warnings.add(Warning.fromString("Illegal material specification, ignoring.")); - return; - } - - // Parse density - double density; - String str; - str = attributes.remove("density"); - if (str == null) { - warnings.add(Warning.fromString("Illegal material specification, ignoring.")); - return; - } - try { - density = Double.parseDouble(str); - } catch (NumberFormatException e) { - warnings.add(Warning.fromString("Illegal material specification, ignoring.")); - return; - } - - // Parse thickness - // double thickness = 0; - // str = attributes.remove("thickness"); - // try { - // if (str != null) - // thickness = Double.parseDouble(str); - // } catch (NumberFormatException e){ - // warnings.add(Warning.fromString("Illegal material specification, ignoring.")); - // return; - // } - - // Check type if specified - str = attributes.remove("type"); - if (str != null && !type.name().toLowerCase(Locale.ENGLISH).equals(str)) { - warnings.add(Warning.fromString("Illegal material type specified, ignoring.")); - return; - } - - String key = attributes.remove("key"); - - mat = Databases.findMaterial(type, key, name, density); - - setMethod.invoke(c, mat); - } +public MaterialSetter(Reflection.Method set, Material.Type type) { + this.setMethod = set; + this.type = type; } +@Override +public void set(RocketComponent c, String name, HashMap attributes, + WarningSet warnings) { + + Material mat; + + // Check name != "" + name = name.trim(); + if (name.equals("")) { + warnings.add(Warning.fromString("Illegal material specification, ignoring.")); + return; + } + + // Parse density + double density; + String str; + str = attributes.remove("density"); + if (str == null) { + warnings.add(Warning.fromString("Illegal material specification, ignoring.")); + return; + } + try { + density = Double.parseDouble(str); + } catch (NumberFormatException e) { + warnings.add(Warning.fromString("Illegal material specification, ignoring.")); + return; + } + + // Parse thickness + // double thickness = 0; + // str = attributes.remove("thickness"); + // try { + // if (str != null) + // thickness = Double.parseDouble(str); + // } catch (NumberFormatException e){ + // warnings.add(Warning.fromString("Illegal material specification, ignoring.")); + // return; + // } + + // Check type if specified + str = attributes.remove("type"); + if (str != null && !type.name().toLowerCase(Locale.ENGLISH).equals(str)) { + warnings.add(Warning.fromString("Illegal material type specified, ignoring.")); + return; + } + + String key = attributes.remove("key"); + + mat = Databases.findMaterial(type, key, name, density); + + setMethod.invoke(c, mat); +} +} + + diff --git a/core/src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java index 44d5599d2..be7c52af8 100644 --- a/core/src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java @@ -7,7 +7,6 @@ import java.util.Locale; import net.sf.openrocket.rocketcomponent.ReferenceType; import net.sf.openrocket.rocketcomponent.Rocket; - public class RocketSaver extends RocketComponentSaver { private static final RocketSaver instance = new RocketSaver(); @@ -22,8 +21,6 @@ public class RocketSaver extends RocketComponentSaver { return list; } - - @Override protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List elements) { super.addParams(c, elements); diff --git a/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionDialog.java b/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionDialog.java new file mode 100644 index 000000000..b8dee9fb5 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionDialog.java @@ -0,0 +1,35 @@ +package net.sf.openrocket.gui.customexpression; + +import java.awt.Window; + +import javax.swing.BorderFactory; +import javax.swing.JDialog; +import javax.swing.JPanel; + +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.startup.Application; + +public class CustomExpressionDialog extends JDialog { + private static final Translator trans = Application.getTranslator(); + private static final LogHelper log = Application.getLogger(); + + private final Window parentWindow; + private final OpenRocketDocument doc; + + public CustomExpressionDialog(OpenRocketDocument doc, Window parent){ + super(parent, trans.get("customExpressionPanel.lbl.CustomExpressions")); + + this.doc = doc; + this.parentWindow = parent; + + JPanel panel = new CustomExpressionPanel(doc, this); + this.add( panel ); + + GUIUtil.setDisposableDialogOptions(this, null); + } +} diff --git a/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java b/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java index cf35cbb40..ec60f2500 100644 --- a/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java +++ b/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java @@ -4,17 +4,28 @@ import java.awt.Color; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import javax.swing.BorderFactory; import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; +import javax.swing.border.Border; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.file.DatabaseMotorFinder; +import net.sf.openrocket.file.GeneralRocketLoader; +import net.sf.openrocket.file.MotorFinder; +import net.sf.openrocket.file.RocketLoadException; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.gui.components.DescriptionArea; import net.sf.openrocket.gui.components.UnitSelector; @@ -22,7 +33,8 @@ import net.sf.openrocket.gui.customexpression.ExpressionBuilderDialog; import net.sf.openrocket.gui.util.Icons; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.LogHelper; -import net.sf.openrocket.simulation.CustomExpression; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.simulation.customexpression.CustomExpression; import net.sf.openrocket.startup.Application; public class CustomExpressionPanel extends JPanel { @@ -31,34 +43,91 @@ public class CustomExpressionPanel extends JPanel { private static final Translator trans = Application.getTranslator(); private JPanel expressionSelectorPanel; - private Simulation simulation; + private OpenRocketDocument doc; - public CustomExpressionPanel(final Simulation simulation) { + public CustomExpressionPanel(final OpenRocketDocument doc, final JDialog parentDialog) { super(new MigLayout("fill")); - this.simulation = simulation; + this.doc = doc; expressionSelectorPanel = new JPanel(new MigLayout("gapy rel")); - JScrollPane scroll = new JScrollPane(expressionSelectorPanel); - this.add(scroll, "spany 2, height 10px, wmin 400lp, grow 100, gapright para"); + expressionSelectorPanel.setToolTipText(trans.get("customExpressionPanel.lbl.CalcNote")); - DescriptionArea desc = new DescriptionArea(trans.get("customExpressionPanel.lbl.UpdateNote")+"\n\n"+trans.get("customExpressionPanel.lbl.CalcNote"), 8, -2f); - desc.setViewportBorder(BorderFactory.createEmptyBorder()); - this.add(desc, "width 1px, growx 1, wrap unrel"); + JScrollPane scroll = new JScrollPane(); + Border bdr = BorderFactory.createTitledBorder(trans.get("customExpressionPanel.lbl.CustomExpressions")); + + expressionSelectorPanel.setBorder(bdr); + expressionSelectorPanel.add(scroll); + + //this.add(expressionSelectorPanel, "spany 1, height 10px, wmin 600lp, grow 100, gapright para"); + this.add(expressionSelectorPanel, "hmin 200lp, wmin 700lp, grow 100, wrap"); + + //DescriptionArea desc = new DescriptionArea(trans.get("customExpressionPanel.lbl.UpdateNote")+"\n\n"+trans.get("customExpressionPanel.lbl.CalcNote"), 8, -2f); + //desc.setViewportBorder(BorderFactory.createEmptyBorder()); + //this.add(desc, "width 1px, growx 1, wrap unrel, wrap"); //// New expression JButton button = new JButton(trans.get("customExpressionPanel.but.NewExpression")); + button.setToolTipText(trans.get("customExpressionPanel.but.ttip.NewExpression")); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // Open window to configure expression - log.debug("Opening window to configure new expression"); + log.info("Opening window to configure new expression"); Window parent = SwingUtilities.getWindowAncestor(CustomExpressionPanel.this); - new ExpressionBuilderDialog(parent, simulation).setVisible(true); + new ExpressionBuilderDialog(parent, doc).setVisible(true); updateExpressions(); } }); + this.add(button, "split 4, width :100:200"); + + //// Import + final JButton importButton = new JButton(trans.get("customExpressionPanel.but.Import")); + importButton.setToolTipText(trans.get("customExpressionPanel.but.ttip.Import")); + importButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { - this.add(button, "left"); + //Create a file chooser + final JFileChooser fc = new JFileChooser(); + if (doc.getFile() != null){ + fc.setCurrentDirectory(doc.getFile().getParentFile()); + } + fc.setFileFilter(new FileNameExtensionFilter("Openrocket file", "ork")); + fc.setAcceptAllFileFilterUsed(false); + + int returnVal = fc.showOpenDialog(CustomExpressionPanel.this); + if (returnVal == JFileChooser.APPROVE_OPTION){ + File importFile = fc.getSelectedFile(); + log.info("User selected a file to import expressions from "+fc.getSelectedFile().toString()); + + //TODO: This should probably be somewhere else and ideally we would use an alternative minimal rocket loader. Still, it doesn't seem particularly slow this way. + + // Load expressions from selected document + GeneralRocketLoader loader = new GeneralRocketLoader(); + try { + OpenRocketDocument importedDocument = loader.load(importFile, new DatabaseMotorFinder()); + for (CustomExpression exp : importedDocument.getCustomExpressions()){ + doc.addCustomExpression(exp); + } + } catch (RocketLoadException e1) { + log.user("Error opening document to import expressions from."); + } + updateExpressions(); + } + } + }); + this.add(importButton, "width :100:200"); + + //// Close button + final JButton closeButton = new JButton(trans.get("dlg.but.close")); + closeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + parentDialog.dispose(); + } + }); + this.add(new JPanel(), "growx"); + this.add(closeButton, "width :100:200"); updateExpressions(); } @@ -69,19 +138,18 @@ public class CustomExpressionPanel extends JPanel { private void updateExpressions(){ expressionSelectorPanel.removeAll(); - int totalExpressions = simulation.getCustomExpressions().size(); + int totalExpressions = doc.getCustomExpressions().size(); for (int i=0; i expressions = simulation.getCustomExpressions(); + ArrayList expressions = doc.getCustomExpressions(); int i = expressions.indexOf(expression); if (i+move == expressions.size() || i+move < 0) return; @@ -128,6 +196,9 @@ public class CustomExpressionPanel extends JPanel { JLabel unitLabel = new JLabel( trans.get("customExpression.Units")+ " :"); UnitSelector unitSelector = new UnitSelector(expression.getType().getUnitGroup()); + //JLabel unitSelector = new JLabel ( expression.getUnit() ); + //unitSelector = setLabelStyle(unitSelector); + //unitSelector.setBackground(Color.WHITE); JButton editButton = new JButton(Icons.EDIT); editButton.setToolTipText(trans.get("customExpression.Units.but.ttip.Edit")); @@ -136,7 +207,7 @@ public class CustomExpressionPanel extends JPanel { @Override public void actionPerformed(ActionEvent e){ Window parent = SwingUtilities.getWindowAncestor(CustomExpressionPanel.this); - new ExpressionBuilderDialog(parent, expression.getSimulation(), expression).setVisible(true); + new ExpressionBuilderDialog(parent, doc, expression).setVisible(true); updateExpressions(); } }); diff --git a/core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java b/core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java index 77fa3a4b2..615fdc149 100644 --- a/core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java +++ b/core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java @@ -19,11 +19,13 @@ import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.gui.util.Icons; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.LogHelper; -import net.sf.openrocket.simulation.CustomExpression; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.simulation.customexpression.CustomExpression; import net.sf.openrocket.startup.Application; /** @@ -44,7 +46,7 @@ public class ExpressionBuilderDialog extends JDialog { private CustomExpression previousExpressionCopy; private final Window parentWindow; - private final Simulation simulation; + private final OpenRocketDocument doc; // Define these check indicators to show if fields are OK private final JLabel nameCheck = new JLabel(RedIcon); @@ -53,16 +55,16 @@ public class ExpressionBuilderDialog extends JDialog { private final JButton okButton = new JButton(trans.get("dlg.but.ok")); private final JTextField expressionField = new JTextField(20); - public ExpressionBuilderDialog(Window parent, Simulation simulation){ - this(parent, simulation, new CustomExpression(simulation)); + public ExpressionBuilderDialog(Window parent, OpenRocketDocument doc){ + this(parent, doc, new CustomExpression(doc)); } - public ExpressionBuilderDialog(Window parent, final Simulation simulation, final CustomExpression previousExpression){ + public ExpressionBuilderDialog(Window parent, final OpenRocketDocument doc, final CustomExpression previousExpression){ super(parent, trans.get("ExpressionBuilderDialog.title"), JDialog.ModalityType.DOCUMENT_MODAL); + this.doc = doc; this.parentWindow = parent; - this.simulation = simulation; this.previousExpressionCopy = (CustomExpression) previousExpression.clone(); this.expression = previousExpression; @@ -159,7 +161,7 @@ public class ExpressionBuilderDialog extends JDialog { public void actionPerformed(ActionEvent e) { log.debug("Opening insert variable window"); Window parentWindow = SwingUtilities.getWindowAncestor(ExpressionBuilderDialog.this); - new VariableSelector(parentWindow, ExpressionBuilderDialog.this, simulation).setVisible(true); + new VariableSelector(parentWindow, ExpressionBuilderDialog.this, doc).setVisible(true); } }); @@ -174,21 +176,13 @@ public class ExpressionBuilderDialog extends JDialog { } }); - //// Copy expression check box - final JCheckBox copyCheckBox = new JCheckBox(trans.get("ExpressionBuilderDialog.CopyToOtherSimulations")); - copyCheckBox.setHorizontalTextPosition(SwingConstants.LEFT); - copyCheckBox.setToolTipText(trans.get("ExpressionBuilderDialog.CopyToOtherSimulations.ttip")); - //// OK Button okButton.setEnabled(false); okButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // add to this simulation - expression.addToSimulation(); - if (copyCheckBox.isSelected()){ - expression.copyToOtherSimulations(); - } + expression.addToDocument(); // close window ExpressionBuilderDialog.this.dispose(); @@ -226,7 +220,6 @@ public class ExpressionBuilderDialog extends JDialog { mainPanel.add(expressionCheck, "wrap, center"); mainPanel.add(insertOperatorButton, "span 2, right, split 2"); mainPanel.add(insertVariableButton, "right, wrap"); - mainPanel.add(copyCheckBox, "span 2, right, wrap"); mainPanel.add(cancelButton, "span 2, right, width :50:100"); mainPanel.add(okButton, "right, width :50:100, wrap"); diff --git a/core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java b/core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java index bc1f8066c..49f486570 100644 --- a/core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java +++ b/core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java @@ -1,14 +1,24 @@ package net.sf.openrocket.gui.customexpression; +import java.awt.Point; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionAdapter; +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.InputMap; import javax.swing.JButton; +import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; +import javax.swing.KeyStroke; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; @@ -16,6 +26,7 @@ import net.miginfocom.swing.MigLayout; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.TextUtil; public class OperatorSelector extends JDialog { @@ -24,24 +35,76 @@ public class OperatorSelector extends JDialog { private final Window parentWindow; + private final JTable table; + private final OperatorTableModel tableModel; + private final ExpressionBuilderDialog parentBuilder; + public OperatorSelector(Window parent, final ExpressionBuilderDialog parentBuilder){ super(parent, trans.get("CustomOperatorSelector.title"), JDialog.ModalityType.DOCUMENT_MODAL); this.parentWindow = parent; + this.parentBuilder = parentBuilder; final JButton insertButton = new JButton(trans.get("ExpressionBuilderDialog.InsertOperator")); JPanel mainPanel = new JPanel(new MigLayout()); //// Table of variables and model - final OperatorTableModel tableModel = new OperatorTableModel(); - final JTable table = new JTable(tableModel); + tableModel = new OperatorTableModel(); + table = new JTable(tableModel); table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); int width = table.getColumnModel().getTotalColumnWidth(); - table.getColumnModel().getColumn(0).setPreferredWidth( (int) (.2 * width)); - table.getColumnModel().getColumn(1).setPreferredWidth( (int) (.8 * width)); + table.getColumnModel().getColumn(0).setPreferredWidth( (int) (.1 * width)); + table.getColumnModel().getColumn(1).setPreferredWidth( (int) (.9 * width)); + table.setAutoCreateRowSorter(true); + + table.addMouseMotionListener(new MouseMotionAdapter(){ + @Override + public void mouseMoved(MouseEvent e){ + Point p = e.getPoint(); + int row = table.rowAtPoint(p); + int col = table.columnAtPoint(p); + if (col == 1){ + String description = String.valueOf(table.getValueAt(row, 1)); + description = TextUtil.wrap(description, 60); + table.setToolTipText(description); + } else { + table.setToolTipText(null); + } + } + }); + + table.addMouseListener(new MouseListener(){ + @Override + public void mouseClicked(MouseEvent e){ + if (e.getClickCount() == 2){ + log.debug("Selected operator by double clicking."); + selectOperator(); + } + } + @Override + public void mouseEntered(MouseEvent e) {} + @Override + public void mouseExited(MouseEvent e) {} + @Override + public void mousePressed(MouseEvent e) {} + @Override + public void mouseReleased(MouseEvent e) {} + } ); + + InputMap inputMap = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + ActionMap actionMap = table.getActionMap(); + KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); + inputMap.put(enter, "select"); + actionMap.put("select", new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent arg0) { + log.debug("Selected operator by enter key"); + selectOperator(); + } + }); JScrollPane scrollPane = new JScrollPane(table); table.setFillsViewportHeight(true); @@ -57,7 +120,7 @@ public class OperatorSelector extends JDialog { } }); - mainPanel.add(scrollPane, "wrap"); + mainPanel.add(scrollPane, "wrap, push, grow"); //// Cancel button final JButton cancelButton = new JButton(trans.get("dlg.but.cancel")); @@ -73,10 +136,7 @@ public class OperatorSelector extends JDialog { insertButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - int row = table.getSelectedRow(); - String str = tableModel.getOperatorAt(row); - parentBuilder.pasteIntoExpression(str); - OperatorSelector.this.dispose(); + selectOperator(); } }); insertButton.setEnabled(false); // disabled by default, only enable when a variable selected @@ -87,4 +147,11 @@ public class OperatorSelector extends JDialog { this.pack(); this.setLocationByPlatform(true); } + + private void selectOperator(){ + int row = table.getSelectedRow(); + String str = tableModel.getOperatorAt(row); + parentBuilder.pasteIntoExpression(str); + OperatorSelector.this.dispose(); + } } diff --git a/core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java b/core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java index 76a1a8f2d..3b084da51 100644 --- a/core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java +++ b/core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java @@ -3,7 +3,7 @@ package net.sf.openrocket.gui.customexpression; import javax.swing.table.AbstractTableModel; import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.simulation.CustomExpression; +import net.sf.openrocket.simulation.customexpression.Functions; import net.sf.openrocket.startup.Application; public class OperatorTableModel extends AbstractTableModel { @@ -12,8 +12,8 @@ public class OperatorTableModel extends AbstractTableModel { private static final String[] columnNames = {trans.get("customExpression.Operator"), trans.get("customExpression.Description")}; - private final Object[] operators = CustomExpression.AVAILABLE_OPERATORS.keySet().toArray(); - private final Object[] descriptions = CustomExpression.AVAILABLE_OPERATORS.values().toArray(); + private final Object[] operators = Functions.AVAILABLE_OPERATORS.keySet().toArray(); + private final Object[] descriptions = Functions.AVAILABLE_OPERATORS.values().toArray(); public OperatorTableModel(){ @@ -26,7 +26,7 @@ public class OperatorTableModel extends AbstractTableModel { @Override public int getRowCount() { - return CustomExpression.AVAILABLE_OPERATORS.size(); + return Functions.AVAILABLE_OPERATORS.size(); } @Override diff --git a/core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java b/core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java index d432a18cf..5e0cfe9c1 100644 --- a/core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java +++ b/core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java @@ -3,19 +3,31 @@ package net.sf.openrocket.gui.customexpression; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.InputMap; import javax.swing.JButton; +import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; +import javax.swing.KeyStroke; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import javax.swing.table.JTableHeader; import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.startup.Application; /** @@ -25,50 +37,82 @@ import net.sf.openrocket.startup.Application; */ public class VariableSelector extends JDialog { - + private static final Translator trans = Application.getTranslator(); private static final LogHelper log = Application.getLogger(); - - private final Window parentWindow; - private final Simulation simulation; - public VariableSelector(Window parent, final ExpressionBuilderDialog parentBuilder, final Simulation simulation){ - + private final JTable table; + private final VariableTableModel tableModel; + private final ExpressionBuilderDialog parentBuilder; + + public VariableSelector(Window parent, final ExpressionBuilderDialog parentBuilder, final OpenRocketDocument doc){ + super(parent, trans.get("CustomVariableSelector.title"), JDialog.ModalityType.DOCUMENT_MODAL); - - this.parentWindow = parent; - this.simulation = simulation; - + + this.parentBuilder = parentBuilder; final JButton insertButton = new JButton(trans.get("ExpressionBuilderDialog.InsertVariable")); - + JPanel mainPanel = new JPanel(new MigLayout()); - + //// Table of variables and model - final VariableTableModel tableModel = new VariableTableModel(simulation); - final JTable table = new JTable(tableModel); + tableModel = new VariableTableModel(doc); + table = new JTable(tableModel); + table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); int width = table.getColumnModel().getTotalColumnWidth(); table.getColumnModel().getColumn(0).setPreferredWidth( (int) (.7 * width)); table.getColumnModel().getColumn(1).setPreferredWidth( (int) (.15 * width)); table.getColumnModel().getColumn(2).setPreferredWidth( (int) (.15 * width)); + table.setAutoCreateRowSorter(true); + + table.addMouseListener(new MouseListener(){ + @Override + public void mouseClicked(MouseEvent e){ + if (e.getClickCount() == 2){ + log.debug("Selected variable by double clicking."); + selectVariable(); + } + } + @Override + public void mouseEntered(MouseEvent e) {} + @Override + public void mouseExited(MouseEvent e) {} + @Override + public void mousePressed(MouseEvent e) {} + @Override + public void mouseReleased(MouseEvent e) {} + } ); + InputMap inputMap = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + ActionMap actionMap = table.getActionMap(); + KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); + inputMap.put(enter, "select"); + actionMap.put("select", new AbstractAction(){ + @Override + public void actionPerformed(ActionEvent arg0) { + log.debug("Selected variable by enter key"); + selectVariable(); + } + }); + + JScrollPane scrollPane = new JScrollPane(table); table.setFillsViewportHeight(true); table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e){ - if (table.getSelectedRowCount() == 1){ - insertButton.setEnabled(true); - } - else { - insertButton.setEnabled(false); - } + @Override + public void valueChanged(ListSelectionEvent e){ + if (table.getSelectedRowCount() == 1){ + insertButton.setEnabled(true); } - }); - - mainPanel.add(scrollPane, "wrap"); - + else { + insertButton.setEnabled(false); + } + } + }); + + mainPanel.add(scrollPane, "wrap, push, grow"); + //// Cancel button final JButton cancelButton = new JButton(trans.get("dlg.but.cancel")); cancelButton.addActionListener(new ActionListener() { @@ -78,15 +122,12 @@ public class VariableSelector extends JDialog { } }); mainPanel.add(cancelButton, "right, width :100:200, split 2"); - + //// Insert button insertButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - int row = table.getSelectedRow(); - String str = tableModel.getSymbolAt(row); - parentBuilder.pasteIntoExpression(str); - VariableSelector.this.dispose(); + selectVariable(); } }); insertButton.setEnabled(false); // disabled by default, only enable when a variable selected @@ -97,4 +138,12 @@ public class VariableSelector extends JDialog { this.pack(); this.setLocationByPlatform(true); } + + private void selectVariable(){ + int row = table.getSelectedRow(); + String str = tableModel.getSymbolAt(row); + parentBuilder.pasteIntoExpression(str); + VariableSelector.this.dispose(); + } + } diff --git a/core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java b/core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java index b81a6b05e..950828add 100644 --- a/core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java +++ b/core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java @@ -3,15 +3,20 @@ */ package net.sf.openrocket.gui.customexpression; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; +import java.util.Vector; +import javax.swing.JTable; import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; -import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.simulation.CustomExpression; +import net.sf.openrocket.simulation.customexpression.CustomExpression; import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.startup.Application; @@ -29,14 +34,13 @@ public class VariableTableModel extends AbstractTableModel { /* * Table model will be constructed with all the built in variables and any custom variables defined */ - public VariableTableModel(Simulation sim){ + public VariableTableModel(OpenRocketDocument doc){ Collections.addAll(types, FlightDataType.ALL_TYPES); - for (CustomExpression expression : sim.getCustomExpressions()){ + for (CustomExpression expression : doc.getCustomExpressions()){ types.add(expression.getType()); } - } @Override @@ -56,7 +60,7 @@ public class VariableTableModel extends AbstractTableModel { else if (col == 1) return types.get(row).getSymbol(); else if (col == 2) - return types.get(row).getUnitGroup().getDefaultUnit().toString(); + return types.get(row).getUnitGroup().getSIUnit().toString(); return null; } diff --git a/core/src/net/sf/openrocket/gui/main/BasicFrame.java b/core/src/net/sf/openrocket/gui/main/BasicFrame.java index 9f758c347..ab6174825 100644 --- a/core/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/core/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -11,6 +11,7 @@ import net.sf.openrocket.file.openrocket.OpenRocketSaver; import net.sf.openrocket.file.rocksim.export.RocksimSaver; import net.sf.openrocket.gui.StorageOptionChooser; import net.sf.openrocket.gui.configdialog.ComponentConfigDialog; +import net.sf.openrocket.gui.customexpression.CustomExpressionDialog; import net.sf.openrocket.gui.dialogs.AboutDialog; import net.sf.openrocket.gui.dialogs.BugReportDialog; import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog; @@ -637,7 +638,7 @@ public class BasicFrame extends JFrame { }); menu.add(item); - + //// Optimize item = new JMenuItem(trans.get("main.menu.analyze.optimization"), KeyEvent.VK_O); item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.optimization.desc")); item.addActionListener(new ActionListener() { @@ -649,7 +650,17 @@ public class BasicFrame extends JFrame { }); menu.add(item); - + //// Custom expressions + item = new JMenuItem(trans.get("main.menu.analyze.customExpressions"), KeyEvent.VK_E); + item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.customExpressions.desc")); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + log.debug("Custom expressions selected"); + new CustomExpressionDialog(document, BasicFrame.this).setVisible(true); + } + }); + menu.add(item); //// Debug // (shown if openrocket.debug.menu is defined) diff --git a/core/src/net/sf/openrocket/gui/main/SimulationEditDialog.java b/core/src/net/sf/openrocket/gui/main/SimulationEditDialog.java index 0f9f6656c..b8a59ea79 100644 --- a/core/src/net/sf/openrocket/gui/main/SimulationEditDialog.java +++ b/core/src/net/sf/openrocket/gui/main/SimulationEditDialog.java @@ -78,7 +78,7 @@ public class SimulationEditDialog extends JDialog { public static final int DEFAULT = -1; public static final int EDIT = 1; - public static final int PLOT = 3; + public static final int PLOT = 2; private final Window parentWindow; @@ -139,8 +139,6 @@ public class SimulationEditDialog extends JDialog { tabbedPane.addTab(trans.get("simedtdlg.tab.Launchcond"), flightConditionsTab()); //// Simulation options tabbedPane.addTab(trans.get("simedtdlg.tab.Simopt"), simulationOptionsTab()); - //// Custom expressions tab - tabbedPane.addTab(trans.get("simedtdlg.tab.CustomExpressions"), customExpressionsTab()); //// Plot data tabbedPane.addTab(trans.get("simedtdlg.tab.Plotdata"), plotTab()); //// Export data @@ -150,7 +148,7 @@ public class SimulationEditDialog extends JDialog { if (tab == EDIT) { tabbedPane.setSelectedIndex(0); } else if (tab == PLOT) { - tabbedPane.setSelectedIndex(3); + tabbedPane.setSelectedIndex(2); } else { FlightData data = s.getSimulatedData(); if (data == null || data.getBranchCount() == 0) @@ -837,11 +835,6 @@ public class SimulationEditDialog extends JDialog { return new SimulationExportPanel(simulation); } - - private JPanel customExpressionsTab() { - return new CustomExpressionPanel(simulation); - } - /** * Return a panel stating that there is no data available, and that the user diff --git a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java index 226f9527c..d493ca493 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -42,7 +42,7 @@ public class Rocket extends RocketComponent { * List of component change listeners. */ private List listenerList = new ArrayList(); - + /** * When freezeList != null, events are not dispatched but stored in the list. * When the structure is thawed, a single combined event will be fired. @@ -121,9 +121,6 @@ public class Rocket extends RocketComponent { fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); } - - - /** * Return the number of stages in this rocket. * diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index b0139fbbd..d8f44ba05 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -18,6 +18,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.Stage; +import net.sf.openrocket.simulation.customexpression.CustomExpression; import net.sf.openrocket.simulation.exception.MotorIgnitionException; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationLaunchException; @@ -91,9 +92,9 @@ public class BasicEventSimulationEngine implements SimulationEngine { // Calculate values for custom expressions FlightDataBranch data = status.getFlightData(); - ArrayList allExpressions = status.getSimulationConditions().getSimulation().getCustomExpressions(); + ArrayList allExpressions = status.getSimulationConditions().getSimulation().getDocument().getCustomExpressions(); for (CustomExpression expression : allExpressions ) { - data.setValue(expression.getType(), expression.evaluate(status)); + data.setValue(expression.getType(), expression.evaluateDouble(status)); } // Check for NaN values in the simulation status diff --git a/core/src/net/sf/openrocket/simulation/CustomExpression.java b/core/src/net/sf/openrocket/simulation/CustomExpression.java deleted file mode 100644 index a383dd873..000000000 --- a/core/src/net/sf/openrocket/simulation/CustomExpression.java +++ /dev/null @@ -1,341 +0,0 @@ -package net.sf.openrocket.simulation; - -import java.util.SortedMap; -import java.util.TreeMap; - -import net.sf.openrocket.document.Simulation; -import net.sf.openrocket.l10n.Translator; -import net.sf.openrocket.logging.LogHelper; -import net.sf.openrocket.startup.Application; -import net.sf.openrocket.unit.FixedUnitGroup; -import net.sf.openrocket.unit.UnitGroup; -import net.sf.openrocket.util.ArrayList; -import de.congrace.exp4j.Calculable; -import de.congrace.exp4j.ExpressionBuilder; - - -/** - * Represents a single custom expression - * @author Richard Graham - * - */ -public class CustomExpression implements Cloneable{ - - private static final LogHelper log = Application.getLogger(); - private static final Translator trans = Application.getTranslator(); - - private String name, symbol, unit, expression; - private ExpressionBuilder builder; - private Simulation sim = null; - - // A map of available operator strings (keys) and description of function (value) - public static final SortedMap AVAILABLE_OPERATORS = new TreeMap() {{ - put("+" , trans.get("Operator.plus")); - put("-" , trans.get("Operator.minus")); - put("*" , trans.get("Operator.star")); - put("/" , trans.get("Operator.div")); - put("%" , trans.get("Operator.mod")); - put("^" , trans.get("Operator.pow")); - put("abs()" , trans.get("Operator.abs")); - put("ceil()" , trans.get("Operator.ceil")); - put("floor()" , trans.get("Operator.floor")); - put("sqrt()" , trans.get("Operator.sqrt")); - put("cbrt()" , trans.get("Operator.cbrt")); - put("exp()" , trans.get("Operator.exp")); - put("log()" , trans.get("Operator.ln")); - put("sin()" , trans.get("Operator.sin")); - put("cos()" , trans.get("Operator.cos")); - put("tan()" , trans.get("Operator.tan")); - put("asin()" , trans.get("Operator.asin")); - put("acos()" , trans.get("Operator.acos")); - put("atan()" , trans.get("Operator.atan")); - put("sinh()" , trans.get("Operator.hsin")); - put("cosh()" , trans.get("Operator.hcos")); - put("tanh()" , trans.get("Operator.htan")); - }}; - - public CustomExpression(){ - setName(""); - setSymbol(""); - setUnit(""); - setExpression(""); - } - - public CustomExpression(Simulation sim){ - this(); - setSimulation(sim); - } - - public CustomExpression(Simulation sim, String name, String symbol, String unit, String expression) { - - setName(name); - setSymbol(symbol); - setUnit(unit); - setExpression(expression); - setSimulation(sim); - } - - /* - * Use this to update the simulation this is associated with - */ - public void setSimulation(Simulation sim){ - this.sim = sim; - } - - public Simulation getSimulation() { - return this.sim; - } - - /* - * Returns the flight data branch 0 for this simulation, or an empty branch - * if no simulated data exists - */ - private FlightDataBranch getBranch() { - if ( sim == null || sim.getSimulatedData() == null || sim.getSimulatedData().getBranchCount() == 0){ - return new FlightDataBranch(); - } - else { - return sim.getSimulatedData().getBranch(0); - } - } - - - public void setName(String name){ - this.name = name; - } - - public void setUnit(String unit){ - this.unit = unit; - } - - public void setSymbol(String symbol){ - this.symbol = symbol; - } - - public void setExpression(String expression){ - this.expression = expression; - builder = new ExpressionBuilder(expression); - } - - // get a list of all the names of all the available variables - private ArrayList getAllNames(){ - ArrayList names = new ArrayList(); - for (FlightDataType type : FlightDataType.ALL_TYPES) - names.add(type.getName()); - for (CustomExpression exp : sim.getCustomExpressions() ){ - if (exp != this) - names.add(exp.getName()); - } - return names; - } - - // get a list of all the symbols of the available variables ignoring this one - private ArrayList getAllSymbols(){ - ArrayList symbols = new ArrayList(); - for (FlightDataType type : FlightDataType.ALL_TYPES) - symbols.add(type.getSymbol()); - for (CustomExpression exp : sim.getCustomExpressions() ){ - if (exp != this) - symbols.add(exp.getSymbol()); - } - return symbols; - } - - public boolean checkSymbol(){ - if (symbol.trim().isEmpty()) - return false; - - // No bad characters - for (char c : "0123456789.,()[]{}<> ".toCharArray()) - if (symbol.indexOf(c) != -1 ) - return false; - - // No operators (ignoring brackets) - for (String s : CustomExpression.AVAILABLE_OPERATORS.keySet()){ - if (symbol.contains(s.replaceAll("\\(|\\)", ""))) - return false; - } - - // No already defined symbols - ArrayList symbols = getAllSymbols().clone(); - if (symbols.contains(symbol.trim())){ - int index = symbols.indexOf(symbol.trim()); - log.user("Symbol "+symbol+" already exists, found "+symbols.get(index)); - return false; - } - - return true; - } - - public boolean checkName(){ - if (name.trim().isEmpty()) - return false; - - // No characters that could mess things up saving etc - for (char c : ",()[]{}<>".toCharArray()) - if (name.indexOf(c) != -1 ) - return false; - - ArrayList names = getAllNames().clone(); - if (names.contains(name.trim())){ - int index = names.indexOf(name.trim()); - log.user("Name "+name+" already exists, found "+names.get(index)); - return false; - } - - return true; - } - - // Currently no restrictions on unit - public boolean checkUnit(){ - return true; - } - - public boolean checkAll(){ - return checkUnit() && checkSymbol() && checkName(); - } - - public String getName(){ - return name; - } - - public String getSymbol(){ - return symbol; - } - - public String getUnit(){ - return unit; - } - - public String getExpressionString(){ - return expression; - } - - - /* - * Check if the current expression is valid - */ - public boolean checkExpression(){ - - if (expression.trim().isEmpty()){ - return false; - } - - // Define the available variables as 0 - for (FlightDataType type : getBranch().getTypes()){ - builder.withVariable(type.getSymbol(), 0.0); - } - - for (String symb : getAllSymbols()){ - builder.withVariable(symb, 0.0); - } - - // Try to build - try { - builder.build(); - } catch (Exception e) { - log.user("Custom expression invalid : " + e.toString()); - return false; - } - - // Otherwise, all OK - return true; - } - - /* - * Evaluate the expression using the last variable values from the simulation status. - * Returns NaN on any error. - */ - public Double evaluate(SimulationStatus status){ - - for (FlightDataType type : status.getFlightData().getTypes()){ - builder.withVariable(type.getSymbol(), status.getFlightData().getLast(type) ); - } - - Calculable calc; - try { - calc = builder.build(); - return new Double(calc.calculate()); - } catch (Exception e) { - log.user("Could not calculate custom expression "+name); - return Double.NaN; - } - } - - /* - * Returns the new flight data type corresponding to this calculated data - */ - public FlightDataType getType(){ - - UnitGroup ug = new FixedUnitGroup(unit); - FlightDataType type = FlightDataType.getType(name, symbol, ug); - - // If in a simulation, figure out priority from order in array so that customs expressions are always at the top - //if (sim != null && sim.getCustomExpressions().contains(this)){ - // int totalExpressions = sim.getCustomExpressions().size(); - // int p = -1*(totalExpressions-sim.getCustomExpressions().indexOf(this)); - // type.setPriority(p); - //} - - return type; - } - - /* - * Add this expression to the simulation if valid and not already added - */ - public void addToSimulation(){ - // Abort if exact expression already in - if ( !sim.getCustomExpressions().contains(this) && this.checkAll() ) - sim.addCustomExpression( this ); - } - - /* - * Removes this expression from the simulation, replacing it with a given new expression - */ - public void overwrite(CustomExpression newExpression){ - if (!sim.getCustomExpressions().contains(this)) - return; - else { - int index = sim.getCustomExpressions().indexOf(this); - sim.getCustomExpressions().set(index, newExpression); - } - } - - /* - * Add a copy to other simulations in this document if possible - * Will not overwrite existing expressions - */ - public void copyToOtherSimulations(){ - for (Simulation s : this.getSimulation().getDocument().getSimulations()){ - CustomExpression newExpression = (CustomExpression) this.clone(); - newExpression.setSimulation(s); - newExpression.addToSimulation(); - } - } - - @Override - public String toString(){ - return "Custom expression : "+this.name.toString()+ " " + this.expression.toString(); - } - - @Override - /* - * Clone method makes a deep copy of everything except the simulation. - * If you want to apply this to another simulation, set simulation manually after cloning. - * @see java.lang.Object#clone() - */ - public Object clone() { - try { - return super.clone(); - } - catch( CloneNotSupportedException e ) - { - return new CustomExpression( sim , - new String(this.getName()), - new String(this.getSymbol()), - new String(this.getUnit()), - new String(this.getExpressionString())); - } - } - -} diff --git a/core/src/net/sf/openrocket/simulation/FlightDataType.java b/core/src/net/sf/openrocket/simulation/FlightDataType.java index 8d1242610..6a95c3045 100644 --- a/core/src/net/sf/openrocket/simulation/FlightDataType.java +++ b/core/src/net/sf/openrocket/simulation/FlightDataType.java @@ -5,6 +5,7 @@ import java.util.Locale; import java.util.Map; import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.UnitGroup; @@ -23,159 +24,160 @@ import net.sf.openrocket.unit.UnitGroup; */ public class FlightDataType implements Comparable { private static final Translator trans = Application.getTranslator(); + private static final LogHelper log = Application.getLogger(); /** Priority of custom-created variables */ private static final int DEFAULT_PRIORITY = 999; /** List of existing types. MUST BE DEFINED BEFORE ANY TYPES!! */ + /** NOTE: The String key here is now the symbol */ private static final Map EXISTING_TYPES = new HashMap(); - //// Time - public static final FlightDataType TYPE_TIME = newType("TYPE_TIME", "t", UnitGroup.UNITS_FLIGHT_TIME, 1); + public static final FlightDataType TYPE_TIME = newType(trans.get("FlightDataType.TYPE_TIME"), "t", UnitGroup.UNITS_FLIGHT_TIME, 1); //// Vertical position and motion //// Altitude - public static final FlightDataType TYPE_ALTITUDE = newType("TYPE_ALTITUDE", "h", UnitGroup.UNITS_DISTANCE, 10); + public static final FlightDataType TYPE_ALTITUDE = newType(trans.get("FlightDataType.TYPE_ALTITUDE"), "h", UnitGroup.UNITS_DISTANCE, 10); //// Vertical velocity - public static final FlightDataType TYPE_VELOCITY_Z = newType("TYPE_VELOCITY_Z", "Vz", UnitGroup.UNITS_VELOCITY, 11); + public static final FlightDataType TYPE_VELOCITY_Z = newType(trans.get("FlightDataType.TYPE_VELOCITY_Z"), "Vz", UnitGroup.UNITS_VELOCITY, 11); //// Vertical acceleration - public static final FlightDataType TYPE_ACCELERATION_Z = newType("TYPE_ACCELERATION_Z", "Az", UnitGroup.UNITS_ACCELERATION, 12); + public static final FlightDataType TYPE_ACCELERATION_Z = newType(trans.get("FlightDataType.TYPE_ACCELERATION_Z"), "Az", UnitGroup.UNITS_ACCELERATION, 12); //// Total motion //// Total velocity - public static final FlightDataType TYPE_VELOCITY_TOTAL = newType("TYPE_VELOCITY_TOTAL", "Vt", UnitGroup.UNITS_VELOCITY, 20); + public static final FlightDataType TYPE_VELOCITY_TOTAL = newType(trans.get("FlightDataType.TYPE_VELOCITY_TOTAL"), "Vt", UnitGroup.UNITS_VELOCITY, 20); //// Total acceleration - public static final FlightDataType TYPE_ACCELERATION_TOTAL = newType("TYPE_ACCELERATION_TOTAL", "At", UnitGroup.UNITS_ACCELERATION, 21); + public static final FlightDataType TYPE_ACCELERATION_TOTAL = newType(trans.get("FlightDataType.TYPE_ACCELERATION_TOTAL"), "At", UnitGroup.UNITS_ACCELERATION, 21); //// Lateral position and motion //// Position upwind - public static final FlightDataType TYPE_POSITION_X = newType("TYPE_POSITION_X", "Px", UnitGroup.UNITS_DISTANCE, 30); + public static final FlightDataType TYPE_POSITION_X = newType(trans.get("FlightDataType.TYPE_POSITION_X"), "Px", UnitGroup.UNITS_DISTANCE, 30); //// Position parallel to wind - public static final FlightDataType TYPE_POSITION_Y = newType("TYPE_POSITION_Y", "Py", UnitGroup.UNITS_DISTANCE, 31); + public static final FlightDataType TYPE_POSITION_Y = newType(trans.get("FlightDataType.TYPE_POSITION_Y"), "Py", UnitGroup.UNITS_DISTANCE, 31); //// Lateral distance - public static final FlightDataType TYPE_POSITION_XY = newType("TYPE_POSITION_XY", "Pl", UnitGroup.UNITS_DISTANCE, 32); + public static final FlightDataType TYPE_POSITION_XY = newType(trans.get("FlightDataType.TYPE_POSITION_XY"), "Pl", UnitGroup.UNITS_DISTANCE, 32); //// Lateral direction - public static final FlightDataType TYPE_POSITION_DIRECTION = newType("TYPE_POSITION_DIRECTION", "\u03b8l", UnitGroup.UNITS_ANGLE, 33); + public static final FlightDataType TYPE_POSITION_DIRECTION = newType(trans.get("FlightDataType.TYPE_POSITION_DIRECTION"), "\u03b8l", UnitGroup.UNITS_ANGLE, 33); //// Lateral velocity - public static final FlightDataType TYPE_VELOCITY_XY = newType("TYPE_VELOCITY_XY", "Vl", UnitGroup.UNITS_VELOCITY, 34); + public static final FlightDataType TYPE_VELOCITY_XY = newType(trans.get("FlightDataType.TYPE_VELOCITY_XY"), "Vl", UnitGroup.UNITS_VELOCITY, 34); //// Lateral acceleration - public static final FlightDataType TYPE_ACCELERATION_XY = newType("TYPE_ACCELERATION_XY", "Al", UnitGroup.UNITS_ACCELERATION, 35); + public static final FlightDataType TYPE_ACCELERATION_XY = newType(trans.get("FlightDataType.TYPE_ACCELERATION_XY"), "Al", UnitGroup.UNITS_ACCELERATION, 35); //// Latitude - public static final FlightDataType TYPE_LATITUDE = newType("TYPE_LATITUDE", "\u03c6", UnitGroup.UNITS_ANGLE, 36); + public static final FlightDataType TYPE_LATITUDE = newType(trans.get("FlightDataType.TYPE_LATITUDE"), "\u03c6", UnitGroup.UNITS_ANGLE, 36); //// Longitude - public static final FlightDataType TYPE_LONGITUDE = newType("TYPE_LONGITUDE", "\u03bb", UnitGroup.UNITS_ANGLE, 37); + public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), "\u03bb", UnitGroup.UNITS_ANGLE, 37); //// Angular motion //// Angle of attack - public static final FlightDataType TYPE_AOA = newType("TYPE_AOA", "\u03b1", UnitGroup.UNITS_ANGLE, 40); + public static final FlightDataType TYPE_AOA = newType(trans.get("FlightDataType.TYPE_AOA"), "\u03b1", UnitGroup.UNITS_ANGLE, 40); //// Roll rate - public static final FlightDataType TYPE_ROLL_RATE = newType("TYPE_ROLL_RATE", "d\u03a6", UnitGroup.UNITS_ROLL, 41); + public static final FlightDataType TYPE_ROLL_RATE = newType(trans.get("FlightDataType.TYPE_ROLL_RATE"), "d\u03a6", UnitGroup.UNITS_ROLL, 41); //// Pitch rate - public static final FlightDataType TYPE_PITCH_RATE = newType("TYPE_PITCH_RATE", "d\u03b8", UnitGroup.UNITS_ROLL, 42); + public static final FlightDataType TYPE_PITCH_RATE = newType(trans.get("FlightDataType.TYPE_PITCH_RATE"), "d\u03b8", UnitGroup.UNITS_ROLL, 42); //// Yaw rate - public static final FlightDataType TYPE_YAW_RATE = newType("TYPE_YAW_RATE", "d\u03a8", UnitGroup.UNITS_ROLL, 43); + public static final FlightDataType TYPE_YAW_RATE = newType(trans.get("FlightDataType.TYPE_YAW_RATE"), "d\u03a8", UnitGroup.UNITS_ROLL, 43); //// Stability information //// Mass - public static final FlightDataType TYPE_MASS = newType("TYPE_MASS", "m", UnitGroup.UNITS_MASS, 50); + public static final FlightDataType TYPE_MASS = newType(trans.get("FlightDataType.TYPE_MASS"), "m", UnitGroup.UNITS_MASS, 50); //// Longitudinal moment of inertia - public static final FlightDataType TYPE_LONGITUDINAL_INERTIA = newType("TYPE_LONGITUDINAL_INERTIA", "Il", UnitGroup.UNITS_INERTIA, 51); + public static final FlightDataType TYPE_LONGITUDINAL_INERTIA = newType(trans.get("FlightDataType.TYPE_LONGITUDINAL_INERTIA"), "Il", UnitGroup.UNITS_INERTIA, 51); //// Rotational moment of inertia - public static final FlightDataType TYPE_ROTATIONAL_INERTIA = newType("TYPE_ROTATIONAL_INERTIA", "Ir", UnitGroup.UNITS_INERTIA, 52); + public static final FlightDataType TYPE_ROTATIONAL_INERTIA = newType(trans.get("FlightDataType.TYPE_ROTATIONAL_INERTIA"), "Ir", UnitGroup.UNITS_INERTIA, 52); //// CP location - public static final FlightDataType TYPE_CP_LOCATION = newType("TYPE_CP_LOCATION", "Cp", UnitGroup.UNITS_LENGTH, 53); + public static final FlightDataType TYPE_CP_LOCATION = newType(trans.get("FlightDataType.TYPE_CP_LOCATION"), "Cp", UnitGroup.UNITS_LENGTH, 53); //// CG location - public static final FlightDataType TYPE_CG_LOCATION = newType("TYPE_CG_LOCATION", "Cg", UnitGroup.UNITS_LENGTH, 54); + public static final FlightDataType TYPE_CG_LOCATION = newType(trans.get("FlightDataType.TYPE_CG_LOCATION"), "Cg", UnitGroup.UNITS_LENGTH, 54); //// Stability margin calibers - public static final FlightDataType TYPE_STABILITY = newType("TYPE_STABILITY", "S", UnitGroup.UNITS_COEFFICIENT, 55); + public static final FlightDataType TYPE_STABILITY = newType(trans.get("FlightDataType.TYPE_STABILITY"), "S", UnitGroup.UNITS_COEFFICIENT, 55); //// Characteristic numbers //// Mach number - public static final FlightDataType TYPE_MACH_NUMBER = newType("TYPE_MACH_NUMBER", "M", UnitGroup.UNITS_COEFFICIENT, 60); + public static final FlightDataType TYPE_MACH_NUMBER = newType(trans.get("FlightDataType.TYPE_MACH_NUMBER"), "M", UnitGroup.UNITS_COEFFICIENT, 60); //// Reynolds number - public static final FlightDataType TYPE_REYNOLDS_NUMBER = newType("TYPE_REYNOLDS_NUMBER", "R", UnitGroup.UNITS_COEFFICIENT, 61); + public static final FlightDataType TYPE_REYNOLDS_NUMBER = newType(trans.get("FlightDataType.TYPE_REYNOLDS_NUMBER"), "R", UnitGroup.UNITS_COEFFICIENT, 61); //// Thrust and drag //// Thrust - public static final FlightDataType TYPE_THRUST_FORCE = newType("TYPE_THRUST_FORCE", "Ft", UnitGroup.UNITS_FORCE, 70); + public static final FlightDataType TYPE_THRUST_FORCE = newType(trans.get("FlightDataType.TYPE_THRUST_FORCE"), "Ft", UnitGroup.UNITS_FORCE, 70); //// Drag force - public static final FlightDataType TYPE_DRAG_FORCE = newType("TYPE_DRAG_FORCE", "Fd", UnitGroup.UNITS_FORCE, 71); + public static final FlightDataType TYPE_DRAG_FORCE = newType(trans.get("FlightDataType.TYPE_DRAG_FORCE"), "Fd", UnitGroup.UNITS_FORCE, 71); //// Drag coefficient - public static final FlightDataType TYPE_DRAG_COEFF = newType("TYPE_DRAG_COEFF", "Cd", UnitGroup.UNITS_COEFFICIENT, 72); + public static final FlightDataType TYPE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_DRAG_COEFF"), "Cd", UnitGroup.UNITS_COEFFICIENT, 72); //// Axial drag coefficient - public static final FlightDataType TYPE_AXIAL_DRAG_COEFF = newType("TYPE_AXIAL_DRAG_COEFF", "Cda", UnitGroup.UNITS_COEFFICIENT, 73); + public static final FlightDataType TYPE_AXIAL_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_AXIAL_DRAG_COEFF"), "Cda", UnitGroup.UNITS_COEFFICIENT, 73); //// Component drag coefficients //// Friction drag coefficient - public static final FlightDataType TYPE_FRICTION_DRAG_COEFF = newType("TYPE_FRICTION_DRAG_COEFF", "Cdf", UnitGroup.UNITS_COEFFICIENT, 80); + public static final FlightDataType TYPE_FRICTION_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_FRICTION_DRAG_COEFF"), "Cdf", UnitGroup.UNITS_COEFFICIENT, 80); //// Pressure drag coefficient - public static final FlightDataType TYPE_PRESSURE_DRAG_COEFF = newType("TYPE_PRESSURE_DRAG_COEFF", "Cdp", UnitGroup.UNITS_COEFFICIENT, 81); + public static final FlightDataType TYPE_PRESSURE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_PRESSURE_DRAG_COEFF"), "Cdp", UnitGroup.UNITS_COEFFICIENT, 81); //// Base drag coefficient - public static final FlightDataType TYPE_BASE_DRAG_COEFF = newType("TYPE_BASE_DRAG_COEFF", "Cdb", UnitGroup.UNITS_COEFFICIENT, 82); + public static final FlightDataType TYPE_BASE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_BASE_DRAG_COEFF"), "Cdb", UnitGroup.UNITS_COEFFICIENT, 82); //// Other coefficients //// Normal force coefficient - public static final FlightDataType TYPE_NORMAL_FORCE_COEFF = newType("TYPE_NORMAL_FORCE_COEFF", "Cn", UnitGroup.UNITS_COEFFICIENT, 90); + public static final FlightDataType TYPE_NORMAL_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_NORMAL_FORCE_COEFF"), "Cn", UnitGroup.UNITS_COEFFICIENT, 90); //// Pitch moment coefficient - public static final FlightDataType TYPE_PITCH_MOMENT_COEFF = newType("TYPE_PITCH_MOMENT_COEFF", "C\u03b8", UnitGroup.UNITS_COEFFICIENT, 91); + public static final FlightDataType TYPE_PITCH_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_MOMENT_COEFF"), "C\u03b8", UnitGroup.UNITS_COEFFICIENT, 91); //// Yaw moment coefficient - public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType("TYPE_YAW_MOMENT_COEFF", "C\u03c4\u03a8", UnitGroup.UNITS_COEFFICIENT, 92); + public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_MOMENT_COEFF"), "C\u03c4\u03a8", UnitGroup.UNITS_COEFFICIENT, 92); //// Side force coefficient - public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType("TYPE_SIDE_FORCE_COEFF", "C\u03c4s", UnitGroup.UNITS_COEFFICIENT, 93); + public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_SIDE_FORCE_COEFF"), "C\u03c4s", UnitGroup.UNITS_COEFFICIENT, 93); //// Roll moment coefficient - public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType("TYPE_ROLL_MOMENT_COEFF", "C\u03c4\u03a6", UnitGroup.UNITS_COEFFICIENT, 94); + public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_MOMENT_COEFF"), "C\u03c4\u03a6", UnitGroup.UNITS_COEFFICIENT, 94); //// Roll forcing coefficient - public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType("TYPE_ROLL_FORCING_COEFF", "Cf\u03a6", UnitGroup.UNITS_COEFFICIENT, 95); + public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_FORCING_COEFF"), "Cf\u03a6", UnitGroup.UNITS_COEFFICIENT, 95); //// Roll damping coefficient - public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType("TYPE_ROLL_DAMPING_COEFF", "C\u03b6\u03a6", UnitGroup.UNITS_COEFFICIENT, 96); + public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_DAMPING_COEFF"), "C\u03b6\u03a6", UnitGroup.UNITS_COEFFICIENT, 96); //// Pitch damping coefficient - public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType("TYPE_PITCH_DAMPING_MOMENT_COEFF", "C\u03b6\u03b8", UnitGroup.UNITS_COEFFICIENT, 97); + public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_DAMPING_MOMENT_COEFF"), "C\u03b6\u03b8", UnitGroup.UNITS_COEFFICIENT, 97); //// Yaw damping coefficient - public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType("TYPE_YAW_DAMPING_MOMENT_COEFF", "C\u03b6\u03a8", UnitGroup.UNITS_COEFFICIENT, 98); + public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_DAMPING_MOMENT_COEFF"), "C\u03b6\u03a8", UnitGroup.UNITS_COEFFICIENT, 98); //// Coriolis acceleration - public static final FlightDataType TYPE_CORIOLIS_ACCELERATION = newType("TYPE_CORIOLIS_ACCELERATION", "Ac", UnitGroup.UNITS_ACCELERATION, 99); + public static final FlightDataType TYPE_CORIOLIS_ACCELERATION = newType(trans.get("FlightDataType.TYPE_CORIOLIS_ACCELERATION"), "Ac", UnitGroup.UNITS_ACCELERATION, 99); //// Reference length + area //// Reference length - public static final FlightDataType TYPE_REFERENCE_LENGTH = newType("TYPE_REFERENCE_LENGTH", "Lr", UnitGroup.UNITS_LENGTH, 100); + public static final FlightDataType TYPE_REFERENCE_LENGTH = newType(trans.get("FlightDataType.TYPE_REFERENCE_LENGTH"), "Lr", UnitGroup.UNITS_LENGTH, 100); //// Reference area - public static final FlightDataType TYPE_REFERENCE_AREA = newType("TYPE_REFERENCE_AREA", "Ar", UnitGroup.UNITS_AREA, 101); + public static final FlightDataType TYPE_REFERENCE_AREA = newType(trans.get("FlightDataType.TYPE_REFERENCE_AREA"), "Ar", UnitGroup.UNITS_AREA, 101); //// Orientation //// Vertical orientation (zenith) - public static final FlightDataType TYPE_ORIENTATION_THETA = newType("TYPE_ORIENTATION_THETA", "\u0398", UnitGroup.UNITS_ANGLE, 106); + public static final FlightDataType TYPE_ORIENTATION_THETA = newType(trans.get("FlightDataType.TYPE_ORIENTATION_THETA"), "\u0398", UnitGroup.UNITS_ANGLE, 106); //// Lateral orientation (azimuth) - public static final FlightDataType TYPE_ORIENTATION_PHI = newType("TYPE_ORIENTATION_PHI", "\u03a6", UnitGroup.UNITS_ANGLE, 107); + public static final FlightDataType TYPE_ORIENTATION_PHI = newType(trans.get("FlightDataType.TYPE_ORIENTATION_PHI"), "\u03a6", UnitGroup.UNITS_ANGLE, 107); //// Atmospheric conditions //// Wind velocity - public static final FlightDataType TYPE_WIND_VELOCITY = newType("TYPE_WIND_VELOCITY", "Vw", UnitGroup.UNITS_VELOCITY, 110); + public static final FlightDataType TYPE_WIND_VELOCITY = newType(trans.get("FlightDataType.TYPE_WIND_VELOCITY"), "Vw", UnitGroup.UNITS_VELOCITY, 110); //// Air temperature - public static final FlightDataType TYPE_AIR_TEMPERATURE = newType("TYPE_AIR_TEMPERATURE", "T", UnitGroup.UNITS_TEMPERATURE, 111); + public static final FlightDataType TYPE_AIR_TEMPERATURE = newType(trans.get("FlightDataType.TYPE_AIR_TEMPERATURE"), "T", UnitGroup.UNITS_TEMPERATURE, 111); //// Air pressure - public static final FlightDataType TYPE_AIR_PRESSURE = newType("TYPE_AIR_PRESSURE", "p", UnitGroup.UNITS_PRESSURE, 112); + public static final FlightDataType TYPE_AIR_PRESSURE = newType(trans.get("FlightDataType.TYPE_AIR_PRESSURE"), "p", UnitGroup.UNITS_PRESSURE, 112); //// Speed of sound - public static final FlightDataType TYPE_SPEED_OF_SOUND = newType("TYPE_SPEED_OF_SOUND", "Vs", UnitGroup.UNITS_VELOCITY, 113); + public static final FlightDataType TYPE_SPEED_OF_SOUND = newType(trans.get("FlightDataType.TYPE_SPEED_OF_SOUND"), "Vs", UnitGroup.UNITS_VELOCITY, 113); //// Simulation information //// Simulation time step - public static final FlightDataType TYPE_TIME_STEP = newType("TYPE_TIME_STEP", "dt", UnitGroup.UNITS_TIME_STEP, 200); + public static final FlightDataType TYPE_TIME_STEP = newType(trans.get("FlightDataType.TYPE_TIME_STEP"), "dt", UnitGroup.UNITS_TIME_STEP, 200); //// Computation time - public static final FlightDataType TYPE_COMPUTATION_TIME = newType("TYPE_COMPUTATION_TIME", "tc", UnitGroup.UNITS_SHORT_TIME, 201); + public static final FlightDataType TYPE_COMPUTATION_TIME = newType(trans.get("FlightDataType.TYPE_COMPUTATION_TIME"), "tc", UnitGroup.UNITS_SHORT_TIME, 201); // An array of all the built in types public static final FlightDataType[] ALL_TYPES = { @@ -235,46 +237,90 @@ public class FlightDataType implements Comparable { }; /** - * Return a {@link FlightDataType} based on a string description. This returns known data types - * if possible, or a new type otherwise. + * Return a {@link FlightDataType} with a given string description, symbol and unitgroup. + * This returns an existing data type if the symbol matches that of an existing type. + * + * If the symbol matches but the unit and description information differ, then the old stored datatype + * is erased and the updated version based on the given parametes is returned. + * The only exception is if the description or unitgroup are undefined (null or empty string). In this case + * we just get these parameters from the existing type when making the new one. * * @param s the string description of the type. * @param u the unit group the new type should belong to if a new group is created. * @return a data type. */ + @SuppressWarnings("null") public static synchronized FlightDataType getType(String s, String symbol, UnitGroup u) { - // modified to include the unit - FlightDataType type = EXISTING_TYPES.get(s.toLowerCase(Locale.ENGLISH)); + + // if symbol is null : try finding by name + // if unit is null : don't do anything to the unit if found, just return datatype if found and generate an error and an empty unit otherwise + int oldPriority = DEFAULT_PRIORITY; - // added this for backward compatibility. Will update type if symbol undefined - //if (type != null && type.getSymbol() != symbol){ - // EXISTING_TYPES.remove(type); - // type = null; - //} + //FlightDataType type = findFromSymbol(symbol); + FlightDataType type = EXISTING_TYPES.get(symbol); if (type != null) { - return type; + // found it from symbol + + // if name was not give (empty string), can use the one we found name + if ( s.equals("") || s == null ){ + s = type.getName(); + } + if ( u == null ){ + u = type.getUnitGroup(); + } + + // if something has changed, then we need to remove the old one + // otherwise, just return what we found + if ( !u.equals(type.getUnitGroup()) || + !s.equals(type.getName()) + ) + { + oldPriority = type.priority; + + EXISTING_TYPES.remove(type); + log.info("Something changed with the type "+type.getName()+", removed old version."); + } + else{ + return type; + } } - type = newType("UserDefined." + s, s, symbol, u, DEFAULT_PRIORITY); - return type; + + if (u == null){ + u = UnitGroup.UNITS_NONE; + log.error("Made a new flightdatatype, but did not know what units to use."); + } + + // make a new one + type = newType(s, symbol, u, oldPriority); + return type; } + /* + * Get the flightdatatype from existing types based on the symbol. + */ + /* + private static FlightDataType findFromSymbol(String symbol){ + for (FlightDataType t : EXISTING_TYPES.values()){ + if (t.getSymbol().equals(symbol)){ + return t; + } + } + return null; + } + */ + /** * Used while initializing the class. */ - - private static FlightDataType newType( String key , String symbol, UnitGroup u, int priority ) { - String name = trans.get("FlightDataType." + key ); - return newType( key, name, symbol, u, priority ); - } - - private static synchronized FlightDataType newType(String key, String s, String symbol, UnitGroup u, int priority) { - FlightDataType type = new FlightDataType(key, s, symbol, u, priority); - EXISTING_TYPES.put(s.toLowerCase(Locale.ENGLISH), type); + private static synchronized FlightDataType newType(String s, String symbol, UnitGroup u, int priority) { + FlightDataType type = new FlightDataType(s, symbol, u, priority); + //EXISTING_TYPES.put(s.toLowerCase(Locale.ENGLISH), type); + EXISTING_TYPES.put(symbol, type); return type; } - private final String key; + private final String name; private final String symbol; private final UnitGroup units; @@ -282,8 +328,7 @@ public class FlightDataType implements Comparable { private final int hashCode; - private FlightDataType(String key, String typeName, String symbol, UnitGroup units, int priority) { - this.key = key; + private FlightDataType(String typeName, String symbol, UnitGroup units, int priority) { if (typeName == null) throw new IllegalArgumentException("typeName is null"); if (units == null) @@ -292,7 +337,7 @@ public class FlightDataType implements Comparable { this.symbol = symbol; this.units = units; this.priority = priority; - this.hashCode = this.key.hashCode(); + this.hashCode = this.name.toLowerCase(Locale.ENGLISH).hashCode(); } /* @@ -301,10 +346,6 @@ public class FlightDataType implements Comparable { } */ - public String getKey() { - return key; - } - public String getName() { return name; } @@ -326,7 +367,7 @@ public class FlightDataType implements Comparable { public boolean equals(Object other) { if (!(other instanceof FlightDataType)) return false; - return this.hashCode == other.hashCode(); + return this.name.equalsIgnoreCase(((FlightDataType) other).name); } @Override diff --git a/core/src/net/sf/openrocket/simulation/customexpression/CustomExpression.java b/core/src/net/sf/openrocket/simulation/customexpression/CustomExpression.java new file mode 100644 index 000000000..2ac41ffc0 --- /dev/null +++ b/core/src/net/sf/openrocket/simulation/customexpression/CustomExpression.java @@ -0,0 +1,496 @@ +package net.sf.openrocket.simulation.customexpression; + +import java.util.List; +import java.util.regex.*; + +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.simulation.FlightDataType; +import net.sf.openrocket.simulation.SimulationStatus; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.unit.FixedUnitGroup; +import net.sf.openrocket.unit.UnitGroup; +import net.sf.openrocket.util.ArrayList; +import de.congrace.exp4j.Calculable; +import de.congrace.exp4j.ExpressionBuilder; +import de.congrace.exp4j.UnknownFunctionException; +import de.congrace.exp4j.UnparsableExpressionException; +import de.congrace.exp4j.Variable; + +/** + * Represents a single custom expression + * @author Richard Graham + * + */ +public class CustomExpression implements Cloneable{ + + private static final LogHelper log = Application.getLogger(); + + private OpenRocketDocument doc; + private String name, symbol, unit; + + protected String expression; + private ExpressionBuilder builder; + private List subExpressions = new ArrayList(); + + public CustomExpression(OpenRocketDocument doc){ + setName(""); + setSymbol(""); + setUnit(""); + setExpression(""); + this.doc = doc; + } + + public CustomExpression(OpenRocketDocument doc, + String name, + String symbol, + String unit, + String expression) { + this.doc = doc; + + setName(name); + setSymbol(symbol); + setUnit(unit); + setExpression(expression); + } + + /* + * Sets the long name of this expression, e.g. 'Kinetic energy' + */ + public void setName(String name){ + this.name = name; + } + + /* + * Sets the string for the units of the result of this expression. + */ + public void setUnit(String unit){ + this.unit = unit; + } + + /* + * Sets the symbol string. This is the short, locale independent symbol for this whole expression + */ + public void setSymbol(String symbol){ + this.symbol = symbol; + } + + /* + * Sets the actual expression string for this expression + */ + public void setExpression(String expression){ + + // This is the expression as supplied + this.expression = expression; + + // Replace any indexed variables + expression = subTimeIndexes(expression); + expression = subTimeRanges(expression); + + builder = new ExpressionBuilder(expression); + for (String n : getAllSymbols()){ + builder.withVariable(new Variable(n)); + } + for (CustomExpression exp : this.subExpressions){ + builder.withVariable(new Variable(exp.hash())); + } + + builder.withCustomFunctions(Functions.getInstance().getAllFunction()); + log.info("Built expression "+expression); + } + + /* + * Replaces expressions of the form: + * a[x:y] with a hash and creates an associated RangeExpression from x to y + */ + private String subTimeRanges(String str){ + + Pattern p = Pattern.compile(variableRegex()+"\\[[^\\]]*:.*?\\]"); + Matcher m = p.matcher(str); + + // for each match, make a new custom expression (in subExpressions) with a hashed name + // and replace the expression and variable in the original expression string with [hash]. + while (m.find()){ + String match = m.group(); + + int start = match.indexOf("["); + int end = match.indexOf("]"); + int colon = match.indexOf(":"); + + String startTime = match.substring(start+1, colon); + String endTime = match.substring(colon+1, end); + String variableType = match.substring(0, start); + + RangeExpression exp = new RangeExpression(doc, startTime, endTime, variableType); + subExpressions.add( exp ); + str = str.replace(match, exp.hash()); + } + return str; + } + + /* + * Replaces expressions of the form + * a[x] with a hash and creates an associated IndexExpression with x + */ + private String subTimeIndexes(String str){ + + // find any matches of the time-indexed variable notation, e.g. m[1.2] for mass at 1.2 sec + Pattern p = Pattern.compile(variableRegex()+"\\[[^:]*?\\]"); + Matcher m = p.matcher(str); + + // for each match, make a new custom expression (in subExpressions) with a hashed name + // and replace the expression and variable in the original expression string with [hash]. + while (m.find()){ + String match = m.group(); + // just the index part (in the square brackets) : + String indexText = match.substring(match.indexOf("[")+1, match.length()-1); + // just the flight data type + String typeText = match.substring(0, match.indexOf("[")); + + // Do the replacement and add a corresponding new IndexExpression to the list + IndexExpression exp = new IndexExpression(doc, indexText, typeText); + subExpressions.add( exp ); + str = str.replace(match, exp.hash()); + } + return str; + } + + /* + * Returns a string of the form (t|a| ... ) with all variable symbols available + * This is useful for regex evaluation + */ + protected String variableRegex(){ + String regex = "("; + for (String s : getAllSymbols()){ + regex = regex + s + "|"; + } + regex = regex.substring(0, regex.length()-1) + ")"; + return regex; + } + + // get a list of all the names of all the available variables + protected ArrayList getAllNames(){ + ArrayList names = new ArrayList(); + for (FlightDataType type : FlightDataType.ALL_TYPES) + names.add(type.getName()); + + if (doc != null){ + ArrayList expressions = doc.getCustomExpressions(); + for (CustomExpression exp : expressions ){ + if (exp != this) + names.add(exp.getName()); + } + } + return names; + } + + // get a list of all the symbols of the available variables ignoring this one + protected ArrayList getAllSymbols(){ + ArrayList symbols = new ArrayList(); + for (FlightDataType type : FlightDataType.ALL_TYPES) + symbols.add(type.getSymbol()); + + if (doc != null){ + for (CustomExpression exp : doc.getCustomExpressions() ){ + if (exp != this) + symbols.add(exp.getSymbol()); + } + } + return symbols; + } + + public boolean checkSymbol(){ + if (symbol.trim().isEmpty()) + return false; + + // No bad characters + for (char c : "0123456789.,()[]{}<>:#@%^&* ".toCharArray()) + if (symbol.indexOf(c) != -1 ) + return false; + + // No operators (ignoring brackets) + for (String s : Functions.AVAILABLE_OPERATORS.keySet()){ + if (symbol.equals(s.trim().replaceAll("\\(|\\)|\\]|\\[|:", ""))) + return false; + } + + // No already defined symbols + ArrayList symbols = getAllSymbols().clone(); + if (symbols.contains(symbol.trim())){ + int index = symbols.indexOf(symbol.trim()); + log.user("Symbol "+symbol+" already exists, found "+symbols.get(index)); + return false; + } + + return true; + } + + public boolean checkName(){ + if (name.trim().isEmpty()) + return false; + + // No characters that could mess things up saving etc + for (char c : ",()[]{}<>#".toCharArray()) + if (name.indexOf(c) != -1 ) + return false; + + ArrayList names = getAllNames().clone(); + if (names.contains(name.trim())){ + int index = names.indexOf(name.trim()); + log.user("Name "+name+" already exists, found "+names.get(index)); + return false; + } + + return true; + } + + // Currently no restrictions on unit + public boolean checkUnit(){ + return true; + } + + public boolean checkAll(){ + return checkUnit() && checkSymbol() && checkName() && checkExpression(); + } + + public String getName(){ + return name; + } + + public String getSymbol(){ + return symbol; + } + + public String getUnit(){ + return unit; + } + + public String getExpressionString(){ + return expression; + } + + /** + * Performs a basic check to see if the current expression string is valid + * This includes checking for bad characters and balanced brackets and test + * building the expression. + */ + public boolean checkExpression(){ + if (expression.trim().isEmpty()){ + return false; + } + + int round = 0, square = 0; // count of bracket openings + for (char c : expression.toCharArray()){ + switch (c) { + case '(' : round++; break; + case ')' : round--; break; + case '[' : square++; break; + case ']' : square--; break; + case ':' : + if (square <= 0){ + log.user(": found outside range expression"); + return false; + } + else break; + case '#' : return false; + case '=' : return false; + } + } + if (round != 0 || square != 0) { + log.user("Expression has unballanced brackets"); + return false; + } + + + //// Define the available variables as empty + // The built in data types + for (FlightDataType type : FlightDataType.ALL_TYPES){ + builder.withVariable(new Variable(type.getSymbol())); + } + + for (String symb : getAllSymbols()){ + builder.withVariable(new Variable(symb)); + } + + // Try to build + try { + builder.build(); + } catch (Exception e) { + log.user("Custom expression invalid : " + e.toString()); + return false; + } + + + // Otherwise, all OK + return true; + } + + public Double evaluateDouble(SimulationStatus status){ + return evaluate(status).getDoubleValue(); + } + + /* + * Builds the expression, done automatically during evaluation. Logs any errors. Returns null in case of error. + */ + protected Calculable buildExpression(){ + return buildExpression(builder); + } + + /* + * Builds a specified expression, log any errors and returns null in case of error. + */ + protected Calculable buildExpression(ExpressionBuilder b){ + Calculable calc; + try { + calc = b.build(); + } catch (UnknownFunctionException e1) { + log.user("Unknown function. Could not build custom expression "+name); + return null; + } catch (UnparsableExpressionException e1) { + log.user("Unparsable expression. Could not build custom expression "+name+". "+e1.getMessage()); + return null; + } + + return calc; + } + + /* + * Evaluate the expression using the last variable values from the simulation status. + * Returns NaN on any error. + */ + public Variable evaluate(SimulationStatus status){ + + Calculable calc = buildExpression(builder); + if (calc == null){ + return new Variable("Unknown"); + } + + // Evaluate any sub expressions and set associated variables in the calculable + for (CustomExpression expr : this.subExpressions){ + calc.setVariable( expr.evaluate(status) ); + } + + // Set all the built-in variables. Strictly we surely won't need all of them + // Going through and checking them to include only the ones used *might* give a speedup + for (FlightDataType type : status.getFlightData().getTypes()){ + double value = status.getFlightData().getLast(type); + calc.setVariable( new Variable(type.getSymbol(), value ) ); + } + + double result = Double.NaN; + try{ + result = calc.calculate().getDoubleValue(); + } + catch (java.util.EmptyStackException e){ + log.user("Unable to calculate expression "+this.expression+" due to empty stack exception"); + } + + return new Variable(name, result); + } + + /* + * Returns the new flight data type corresponding to this calculated data + * If the unit matches a SI unit string then the datatype will have the corresponding unitgroup. + * Otherwise, a fixed unit group will be created + */ + public FlightDataType getType(){ + + UnitGroup ug = UnitGroup.SIUNITS.get(unit); + if ( ug == null ){ + ug = new FixedUnitGroup(unit); + } + + FlightDataType type = FlightDataType.getType(name, symbol, ug); + + //log.debug(this.getClass().getSimpleName()+" returned type "+type.getName()+" (" + type.getSymbol() + ")" ); + + return type; + } + + /* + * Add this expression to the document if valid and not in document already + */ + public void addToDocument(){ + // Abort if exact expression already in + ArrayList expressions = doc.getCustomExpressions(); + if ( !expressions.isEmpty() ) { + // check if expression already exists + if ( expressions.contains(this) ){ + log.user("Expression already in document. This unit : "+this.getUnit()+", existing unit : "+expressions.get(0).getUnit()); + return; + } + } + + if (this.checkAll()){ + log.user("Custom expression added to rocket document"); + doc.addCustomExpression( this ); + } + } + + /* + * Removes this expression from the document, replacing it with a given new expression + */ + public void overwrite(CustomExpression newExpression){ + if (!doc.getCustomExpressions().contains(this)) + return; + else { + int index = doc.getCustomExpressions().indexOf(this); + doc.getCustomExpressions().set(index, newExpression); + } + } + + @Override + public String toString(){ + return "Custom expression : "+this.name.toString()+ " " + this.expression.toString(); + } + + @Override + /* + * Clone method makes a deep copy of everything except the reference to the document. + * If you want to apply this to another simulation, set simulation manually after cloning. + * @see java.lang.Object#clone() + */ + public Object clone() { + try { + return super.clone(); + } + catch( CloneNotSupportedException e ) + { + return new CustomExpression( doc , + new String(this.getName()), + new String(this.getSymbol()), + new String(this.getUnit()), + new String(this.getExpressionString())); + } + } + + /* + * Returns a simple all upper case string hash code with a proceeding # mark. + * Used for temporary substitution when evaluating index and range expressions. + */ + public String hash(){ + Integer hashint = new Integer(this.getExpressionString().hashCode()); + String hash = "#"; + for (char c : hashint.toString().toCharArray()){ + char newc = (char) (c + 17); + hash = hash + newc; + } + return hash; + } + + @Override + public boolean equals(Object obj){ + CustomExpression other = (CustomExpression) obj; + + return ( this.getName().equals( other.getName() ) && + this.getSymbol().equals( other.getSymbol() ) && + this.getExpressionString().equals( other.getExpressionString() ) && + this.getUnit().equals( other.getUnit() ) + ); + } + + @Override + public int hashCode() { + return hash().hashCode(); + } +} diff --git a/core/src/net/sf/openrocket/simulation/customexpression/Functions.java b/core/src/net/sf/openrocket/simulation/customexpression/Functions.java new file mode 100644 index 000000000..6e638d91a --- /dev/null +++ b/core/src/net/sf/openrocket/simulation/customexpression/Functions.java @@ -0,0 +1,269 @@ +package net.sf.openrocket.simulation.customexpression; + +import java.util.ArrayList; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.ArrayUtils; + +import de.congrace.exp4j.CustomFunction; +import de.congrace.exp4j.InvalidCustomFunctionException; +import de.congrace.exp4j.Variable; + +/* + * This is a singleton class which contains all the functions for custom expressions not provided by exp4j + */ +public class Functions { + private static Functions instance = null; + + private static final LogHelper log = Application.getLogger(); + private static final Translator trans = Application.getTranslator(); + + private List allFunctions = new ArrayList(); + + public static Functions getInstance() { + if(instance == null) { + try { + instance = new Functions(); + } catch (InvalidCustomFunctionException e) { + log.error("Invalid custom function."); + } + } + return instance; + } + + public List getAllFunction(){ + return allFunctions; + } + + // A map of available operator strings (keys) and description of function (value) + public static final SortedMap AVAILABLE_OPERATORS = new TreeMap() {{ + put("+" , trans.get("Operator.plus")); + put("-" , trans.get("Operator.minus")); + put("*" , trans.get("Operator.star")); + put("/" , trans.get("Operator.div")); + put("%" , trans.get("Operator.mod")); + put("^" , trans.get("Operator.pow")); + put("abs()" , trans.get("Operator.abs")); + put("ceil()" , trans.get("Operator.ceil")); + put("floor()" , trans.get("Operator.floor")); + put("sqrt()" , trans.get("Operator.sqrt")); + put("cbrt()" , trans.get("Operator.cbrt")); + put("exp()" , trans.get("Operator.exp")); + put("log()" , trans.get("Operator.ln")); + put("sin()" , trans.get("Operator.sin")); + put("cos()" , trans.get("Operator.cos")); + put("tan()" , trans.get("Operator.tan")); + put("asin()" , trans.get("Operator.asin")); + put("acos()" , trans.get("Operator.acos")); + put("atan()" , trans.get("Operator.atan")); + put("sinh()" , trans.get("Operator.hsin")); + put("cosh()" , trans.get("Operator.hcos")); + put("tanh()" , trans.get("Operator.htan")); + put("log10()" , trans.get("Operator.log10")); + put("round()" , trans.get("Operator.round")); + put("random()" , trans.get("Operator.random")); + put("expm1()" , trans.get("Operator.expm1")); + put("mean([:])" , trans.get("Operator.mean")); + put("min([:])" , trans.get("Operator.min")); + put("max([:])" , trans.get("Operator.max")); + put("var([:])" , trans.get("Operator.var")); + put("rms([:])" , trans.get("Operator.rms")); + put("stdev([:])", trans.get("Operator.stdev")); + put("lclip(,)" , trans.get("Operator.lclip")); + put("uclip(,)" , trans.get("Operator.uclip")); + put("binf([:],,)" , trans.get("Operator.binf")); + put("trapz([:])" , trans.get("Operator.trapz")); + put("tnear([:],)" , trans.get("Operator.tnear")); + }}; + + + protected Functions() throws InvalidCustomFunctionException { + + CustomFunction meanFn = new CustomFunction("mean") { + @Override + public Variable applyFunction(List vars) { + double[] vals; + try{ + vals = vars.get(0).getArrayValue(); + } catch (Exception e) { + return new Variable("Invalid"); + } + return new Variable("double MEAN result, ", ArrayUtils.mean(vals)); + } + }; + allFunctions.add(meanFn); + + CustomFunction minFn = new CustomFunction("min") { + @Override + public Variable applyFunction(List vars) { + double[] vals; + try{ + vals = vars.get(0).getArrayValue(); + } catch (Exception e) { + return new Variable("Invalid"); + } + return new Variable("double MIN result, ", ArrayUtils.min(vals)); + } + }; + allFunctions.add(minFn); + + CustomFunction maxFn = new CustomFunction("max") { + @Override + public Variable applyFunction(List vars) { + double[] vals; + try{ + vals = vars.get(0).getArrayValue(); + } catch (Exception e) { + return new Variable("Invalid"); + } + return new Variable("double MAX result, ", ArrayUtils.max(vals)); + } + }; + allFunctions.add(maxFn); + + CustomFunction varFn = new CustomFunction("var") { + @Override + public Variable applyFunction(List vars) { + double[] vals; + try{ + vals = vars.get(0).getArrayValue(); + } catch (Exception e) { + return new Variable("Invalid"); + } + return new Variable("double VAR result, ", ArrayUtils.variance(vals)); + } + }; + allFunctions.add(varFn); + + CustomFunction stdevFn = new CustomFunction("stdev") { + @Override + public Variable applyFunction(List vars) { + double[] vals; + try{ + vals = vars.get(0).getArrayValue(); + } catch (Exception e) { + return new Variable("Invalid"); + } + return new Variable("double STDEV result, ", ArrayUtils.stdev(vals)); + } + }; + allFunctions.add(stdevFn); + + CustomFunction rmsFn = new CustomFunction("rms") { + @Override + public Variable applyFunction(List vars) { + double[] vals; + try{ + vals = vars.get(0).getArrayValue(); + } catch (Exception e) { + return new Variable("Invalid"); + } + return new Variable("double RMS result, ", ArrayUtils.rms(vals)); + } + }; + allFunctions.add(rmsFn); + + CustomFunction lclipFn = new CustomFunction("lclip",2) { + @Override + public Variable applyFunction(List vars) { + double val, clip; + try{ + val = vars.get(0).getDoubleValue(); + clip = vars.get(1).getDoubleValue(); + } catch (Exception e) { + return new Variable("Invalid"); + } + if (val < clip){ + val = clip; + } + return new Variable("double LCLIP result, ", val); + } + }; + allFunctions.add(lclipFn); + + CustomFunction uclipFn = new CustomFunction("uclip",2) { + @Override + public Variable applyFunction(List vars) { + double val, clip; + try{ + val = vars.get(0).getDoubleValue(); + clip = vars.get(1).getDoubleValue(); + } catch (Exception e) { + return new Variable("Invalid"); + } + if (val > clip){ + val = clip; + } + return new Variable("double UCLIP result, ", val); + } + }; + allFunctions.add(uclipFn); + + CustomFunction binfFn = new CustomFunction("binf", 3) { + @Override + public Variable applyFunction(List vars) { + double[] range; + double min, max; + try{ + range = vars.get(0).getArrayValue(); + min = vars.get(1).getDoubleValue(); + max = vars.get(2).getDoubleValue(); + } catch (Exception e) { + return new Variable("Invalid"); + } + + int ins = 0; + for (double x: range){ + if (x < max && x > min){ + ins++; + } + } + return new Variable("double BINF result", (double) ins/ (double) range.length); + } + }; + allFunctions.add(binfFn); + + CustomFunction rombintFn = new CustomFunction("trapz") { + @Override + public Variable applyFunction(List vars) { + double[] range; + double dt = 0; + try{ + range = vars.get(0).getArrayValue(); + dt = vars.get(0).getStep(); + } catch (Exception e) { + return new Variable("Invalid"); + } + + return new Variable("double TRAPZ result", ArrayUtils.trapz(range, dt) ); + } + }; + allFunctions.add(rombintFn); + + CustomFunction tnearFn = new CustomFunction("tnear", 2) { + @Override + public Variable applyFunction(List vars) { + double[] range; + double dt = 0; + double start = 0; + double near = 0; + try{ + range = vars.get(0).getArrayValue(); + dt = vars.get(0).getStep(); + start = vars.get(0).getStart(); + near = vars.get(1).getDoubleValue(); + } catch (Exception e) { + return new Variable("Invalid"); + } + + return new Variable("double TNEAR result", ArrayUtils.tnear(range, near, start, dt) ); + } + }; + allFunctions.add(tnearFn); + } +} diff --git a/core/src/net/sf/openrocket/simulation/customexpression/IndexExpression.java b/core/src/net/sf/openrocket/simulation/customexpression/IndexExpression.java new file mode 100644 index 000000000..ab7e1572c --- /dev/null +++ b/core/src/net/sf/openrocket/simulation/customexpression/IndexExpression.java @@ -0,0 +1,54 @@ +package net.sf.openrocket.simulation.customexpression; + +import java.util.List; + +import de.congrace.exp4j.Calculable; +import de.congrace.exp4j.Variable; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.simulation.customexpression.CustomExpression; +import net.sf.openrocket.simulation.FlightDataType; +import net.sf.openrocket.simulation.SimulationStatus; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.LinearInterpolator; + +public class IndexExpression extends CustomExpression { + + FlightDataType type; + private static final LogHelper log = Application.getLogger(); + + public IndexExpression(OpenRocketDocument doc, String indexText, String typeText){ + super(doc); + + setExpression(indexText); + this.setName(""); + this.setSymbol(typeText); + + } + + @Override + public Variable evaluate(SimulationStatus status){ + + Calculable calc = buildExpression(); + if (calc == null){ + return new Variable("Unknown"); + } + + // From the given datatype, get the time and function values and make an interpolator + FlightDataType type = getType(); + List data = status.getFlightData().get(type); + List time = status.getFlightData().get(FlightDataType.TYPE_TIME); + LinearInterpolator interp = new LinearInterpolator(time, data); + + // Evaluate this expression to get the t value + try{ + double tvalue = calc.calculate().getDoubleValue(); + return new Variable(hash(), interp.getValue( tvalue ) ); + } + catch (java.util.EmptyStackException e){ + log.user("Unable to calculate time index for indexed expression "+getExpressionString()+" due to empty stack exception"); + return new Variable("Unknown"); + } + + } +} diff --git a/core/src/net/sf/openrocket/simulation/customexpression/RangeExpression.java b/core/src/net/sf/openrocket/simulation/customexpression/RangeExpression.java new file mode 100644 index 000000000..c479b3104 --- /dev/null +++ b/core/src/net/sf/openrocket/simulation/customexpression/RangeExpression.java @@ -0,0 +1,116 @@ +/* + * A range expression contains two indexExpressions for the beginning and end time index of a range + */ + +package net.sf.openrocket.simulation.customexpression; + +import java.util.List; + +import de.congrace.exp4j.Calculable; +import de.congrace.exp4j.ExpressionBuilder; +import de.congrace.exp4j.Variable; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.simulation.customexpression.CustomExpression; +import net.sf.openrocket.simulation.FlightDataType; +import net.sf.openrocket.simulation.SimulationStatus; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.ArrayUtils; +import net.sf.openrocket.util.LinearInterpolator; +import net.sf.openrocket.util.MathUtil; + +public class RangeExpression extends CustomExpression { + private static final LogHelper log = Application.getLogger(); + + private ExpressionBuilder startBuilder, endBuilder; + + public RangeExpression(OpenRocketDocument doc, String startTime, String endTime, String variableType) { + super(doc); + + if (startTime.isEmpty()){ + startTime = "0"; + } + if (endTime.isEmpty()){ + endTime = "t"; + } + + this.setName(""); + this.setSymbol(variableType); + this.setExpressions(startTime, endTime); + this.expression = variableType+startTime+endTime; // this is used just for generating the hash + + log.info("New range expression, "+startTime + " to "+endTime); + + } + + /* + * Sets the actual expression string for this expression + */ + private void setExpressions(String start, String end){ + + startBuilder = new ExpressionBuilder(start); + endBuilder = new ExpressionBuilder(end); + for (String n : getAllSymbols()){ + startBuilder.withVariable(new Variable(n)); + endBuilder.withVariable(new Variable(n)); + } + } + + @Override + public Variable evaluate(SimulationStatus status){ + + Calculable startCalc = buildExpression(startBuilder); + Calculable endCalc = buildExpression(endBuilder); + if (startCalc == null || endCalc == null){ + return new Variable("Unknown"); + } + + // Set the variables in the start and end calculators + for (FlightDataType type : status.getFlightData().getTypes()){ + double value = status.getFlightData().getLast(type); + startCalc.setVariable( new Variable(type.getSymbol(), value ) ); + endCalc.setVariable( new Variable(type.getSymbol(), value ) ); + } + + // From the given datatype, get the time and function values and make an interpolator + FlightDataType type = getType(); + + List data = status.getFlightData().get(type); + List time = status.getFlightData().get(FlightDataType.TYPE_TIME); + LinearInterpolator interp = new LinearInterpolator(time, data); + + // Evaluate the expression to get the start and end of the range + double startTime, endTime; + try{ + startTime = startCalc.calculate().getDoubleValue(); + startTime = MathUtil.clamp(startTime, 0, Double.MAX_VALUE); + + endTime = endCalc.calculate().getDoubleValue(); + endTime = MathUtil.clamp(endTime, 0, time.get(time.size()-1)); + } + catch (java.util.EmptyStackException e){ + log.user("Unable to calculate time index for range expression "+getSymbol()+" due to empty stack exception"); + return new Variable("Unknown"); + } + + // generate an array representing the range + double step = status.getSimulationConditions().getSimulation().getOptions().getTimeStep(); + double[] t = ArrayUtils.range(startTime, endTime, step); + double[] y = new double[t.length]; + int i = 0; + for (double tval : t){ + y[i] = interp.getValue( tval ); + i++; + } + + Variable result; + if (y.length == 0){ + result = new Variable("Unknown"); + } + else { + result = new Variable(hash(), y, startTime, step); + } + + return result; + } +} diff --git a/core/src/net/sf/openrocket/unit/FixedUnitGroup.java b/core/src/net/sf/openrocket/unit/FixedUnitGroup.java index a0984961b..8cb6fde49 100644 --- a/core/src/net/sf/openrocket/unit/FixedUnitGroup.java +++ b/core/src/net/sf/openrocket/unit/FixedUnitGroup.java @@ -24,6 +24,10 @@ public class FixedUnitGroup extends UnitGroup { return new GeneralUnit(1, unitString); } + public Unit getSIUnit(){ + return new GeneralUnit(1, unitString); + } + public boolean contains(Unit u){ return true; } diff --git a/core/src/net/sf/openrocket/unit/UnitGroup.java b/core/src/net/sf/openrocket/unit/UnitGroup.java index 88ab4a77f..48ae67a9e 100644 --- a/core/src/net/sf/openrocket/unit/UnitGroup.java +++ b/core/src/net/sf/openrocket/unit/UnitGroup.java @@ -27,6 +27,7 @@ public class UnitGroup { public static final UnitGroup UNITS_MOTOR_DIMENSIONS; public static final UnitGroup UNITS_LENGTH; + public static final UnitGroup UNITS_ALL_LENGTHS; public static final UnitGroup UNITS_DISTANCE; public static final UnitGroup UNITS_AREA; @@ -63,11 +64,17 @@ public class UnitGroup { public static final UnitGroup UNITS_ROUGHNESS; public static final UnitGroup UNITS_COEFFICIENT; + public static final UnitGroup UNITS_FREQUENCY; - // public static final UnitGroup UNITS_FREQUENCY; + public static final UnitGroup UNITS_ENERGY; + public static final UnitGroup UNITS_POWER; + public static final UnitGroup UNITS_MOMENTUM; + public static final UnitGroup UNITS_VOLTAGE; + public static final UnitGroup UNITS_CURRENT; - public static final Map UNITS; + public static final Map UNITS; // keys such as "LENGTH", "VELOCITY" + public static final Map SIUNITS; // keys such a "m", "m/s" /* @@ -80,6 +87,36 @@ public class UnitGroup { UNITS_NONE = new UnitGroup(); UNITS_NONE.addUnit(Unit.NOUNIT2); + UNITS_ENERGY = new UnitGroup(); + UNITS_ENERGY.addUnit(new GeneralUnit(1, "J")); + UNITS_ENERGY.addUnit(new GeneralUnit(1e-7, "erg")); + UNITS_ENERGY.addUnit(new GeneralUnit(1.055, "BTU")); + UNITS_ENERGY.addUnit(new GeneralUnit(4.184, "cal")); + UNITS_ENERGY.addUnit(new GeneralUnit(1.3558179483314, "ft"+DOT+"lbf")); + UNITS_ENERGY.setDefaultUnit(0); + + UNITS_POWER = new UnitGroup(); + UNITS_POWER.addUnit(new GeneralUnit(1e-3, "mW")); + UNITS_POWER.addUnit(new GeneralUnit(1, "W")); + UNITS_POWER.addUnit(new GeneralUnit(1e3, "kW")); + UNITS_POWER.addUnit(new GeneralUnit(1e-7, "ergs")); + UNITS_POWER.addUnit(new GeneralUnit(745.699872, "hp")); + UNITS_POWER.setDefaultUnit(1); + + UNITS_MOMENTUM = new UnitGroup(); + UNITS_MOMENTUM.addUnit(new GeneralUnit(1, "kg"+DOT+"m/s")); + UNITS_MOMENTUM.setDefaultUnit(0); + + UNITS_VOLTAGE = new UnitGroup(); + UNITS_VOLTAGE.addUnit(new GeneralUnit(1e-3, "mV")); + UNITS_VOLTAGE.addUnit(new GeneralUnit(1, "V")); + UNITS_VOLTAGE.setDefaultUnit(1); + + UNITS_CURRENT = new UnitGroup(); + UNITS_CURRENT.addUnit(new GeneralUnit(1e-3, "mA")); + UNITS_CURRENT.addUnit(new GeneralUnit(1, "A")); + UNITS_CURRENT.setDefaultUnit(1); + UNITS_LENGTH = new UnitGroup(); UNITS_LENGTH.addUnit(new GeneralUnit(0.001, "mm")); UNITS_LENGTH.addUnit(new GeneralUnit(0.01, "cm")); @@ -90,6 +127,7 @@ public class UnitGroup { UNITS_LENGTH.setDefaultUnit(1); UNITS_MOTOR_DIMENSIONS = new UnitGroup(); + UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(1, "m")); // just added UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.001, "mm")); UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.01, "cm")); UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.0254, "in")); @@ -103,6 +141,19 @@ public class UnitGroup { UNITS_DISTANCE.addUnit(new GeneralUnit(1609.344, "mi")); UNITS_DISTANCE.addUnit(new GeneralUnit(1852, "nmi")); + UNITS_ALL_LENGTHS = new UnitGroup(); + UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.001, "mm")); + UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.01, "cm")); + UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1, "m")); + UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1000, "km")); + UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.0254, "in")); + UNITS_ALL_LENGTHS.addUnit(new FractionalUnit(0.0254, "in/64", "in", 64, 1d / 16d, 0.5d / 64d)); + UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.3048, "ft")); + UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.9144, "yd")); + UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1609.344, "mi")); + UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1852, "nmi")); + UNITS_ALL_LENGTHS.setDefaultUnit(2); + UNITS_AREA = new UnitGroup(); UNITS_AREA.addUnit(new GeneralUnit(pow2(0.001), "mm" + SQUARED)); UNITS_AREA.addUnit(new GeneralUnit(pow2(0.01), "cm" + SQUARED)); @@ -113,6 +164,7 @@ public class UnitGroup { UNITS_STABILITY = new UnitGroup(); + UNITS_STABILITY.addUnit(new GeneralUnit(1, "m")); UNITS_STABILITY.addUnit(new GeneralUnit(0.001, "mm")); UNITS_STABILITY.addUnit(new GeneralUnit(0.01, "cm")); UNITS_STABILITY.addUnit(new GeneralUnit(0.0254, "in")); @@ -234,6 +286,7 @@ public class UnitGroup { UNITS_ROUGHNESS = new UnitGroup(); + UNITS_ROUGHNESS.addUnit(new GeneralUnit(1, "m")); // just added UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.000001, MICRO + "m")); UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.0000254, "mil")); @@ -243,18 +296,20 @@ public class UnitGroup { // This is not used by OpenRocket, and not extensively tested: - // UNITS_FREQUENCY = new UnitGroup(); + UNITS_FREQUENCY = new UnitGroup(); // UNITS_FREQUENCY.addUnit(new GeneralUnit(1, "s")); // UNITS_FREQUENCY.addUnit(new GeneralUnit(0.001, "ms")); // UNITS_FREQUENCY.addUnit(new GeneralUnit(0.000001, MICRO + "s")); - // UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz")); - // UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz")); - // UNITS_FREQUENCY.setDefaultUnit(3); + UNITS_FREQUENCY.addUnit(new FrequencyUnit(.001, "mHz")); + UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz")); + UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz")); + UNITS_FREQUENCY.setDefaultUnit(1); HashMap map = new HashMap(); map.put("NONE", UNITS_NONE); map.put("LENGTH", UNITS_LENGTH); + map.put("ALL_LENGTHS", UNITS_ALL_LENGTHS); map.put("MOTOR_DIMENSIONS", UNITS_MOTOR_DIMENSIONS); map.put("DISTANCE", UNITS_DISTANCE); map.put("VELOCITY", UNITS_VELOCITY); @@ -278,8 +333,36 @@ public class UnitGroup { map.put("RELATIVE", UNITS_RELATIVE); map.put("ROUGHNESS", UNITS_ROUGHNESS); map.put("COEFFICIENT", UNITS_COEFFICIENT); + map.put("VOLTAGE", UNITS_VOLTAGE); + map.put("CURRENT", UNITS_CURRENT); + map.put("ENERGY", UNITS_ENERGY); + map.put("POWER", UNITS_POWER); + map.put("MOMENTUM", UNITS_MOMENTUM); + map.put("FREQUENCY", UNITS_FREQUENCY); UNITS = Collections.unmodifiableMap(map); + + HashMap simap = new HashMap(); + simap.put("m", UNITS_ALL_LENGTHS); + simap.put("m^2", UNITS_AREA); + simap.put("m/s", UNITS_VELOCITY); + simap.put("m/s^2", UNITS_ACCELERATION); + simap.put("kg", UNITS_MASS); + simap.put("kg m^2", UNITS_INERTIA); + simap.put("kg/m^3", UNITS_DENSITY_BULK); + simap.put("N", UNITS_FORCE); + simap.put("Ns", UNITS_IMPULSE); + simap.put("s", UNITS_FLIGHT_TIME); + simap.put("Pa", UNITS_PRESSURE); + simap.put("V", UNITS_VOLTAGE); + simap.put("A", UNITS_CURRENT); + simap.put("J", UNITS_ENERGY); + simap.put("W", UNITS_POWER); + simap.put("kg m/s", UNITS_MOMENTUM); + simap.put("Hz", UNITS_FREQUENCY); + simap.put("K", UNITS_TEMPERATURE); + + SIUNITS = Collections.unmodifiableMap(simap); } public static void setDefaultMetricUnits() { @@ -393,7 +476,14 @@ public class UnitGroup { defaultUnit = n; } - + public Unit getSIUnit(){ + for (Unit u : units){ + if (u.multiplier == 1){ + return u; + } + } + return UNITS_NONE.getDefaultUnit(); + } /** * Find a unit by approximate unit name. Only letters and (ordinary) numbers are @@ -510,7 +600,28 @@ public class UnitGroup { return this.getDefaultUnit().toValue(value); } + @Override + public String toString(){ + return this.getClass().getSimpleName()+":"+this.getSIUnit().toString(); + } + @Override + public boolean equals(Object o){ + UnitGroup u = (UnitGroup) o; + int size = units.size(); + if (size != u.units.size()){ + return false; + } + + for (int i=0; iLinearInterpolator with the given points. * @@ -27,8 +29,11 @@ public class LinearInterpolator implements Cloneable { public LinearInterpolator(double[] x, double[] y) { addPoints(x,y); } - - + + public LinearInterpolator(List x, List y) { + addPoints(x,y); + } + /** * Add the point to the linear interpolation. * @@ -38,7 +43,7 @@ public class LinearInterpolator implements Cloneable { public void addPoint(double x, double y) { sortMap.put(x, y); } - + /** * Add the points to the linear interpolation. * @@ -56,66 +61,50 @@ public class LinearInterpolator implements Cloneable { sortMap.put(x[i],y[i]); } } - - - + + public void addPoints(List x, List y){ + if (x.size() != y.size()) { + throw new IllegalArgumentException("Array lengths do not match, x="+x.size() + + " y="+y.size()); + } + for (int i=0; i < x.size(); i++) { + sortMap.put( (Double) x.toArray()[i], (Double) y.toArray()[i]); + } + } + + public double getValue(double x) { + Map.Entry e1, e2; double x1, x2; - Double y1, y2; - // Froyo does not support floorEntry, firstEntry or higherEntry. We instead have to - // resort to using other more awkward methods. - - y1 = sortMap.get(x); - - if ( y1 != null ) { - // Wow, x was a key in the map. Such luck. - return y1.doubleValue(); - } - - // we now know that x is not in the map, so we need to find the lower and higher keys. + double y1, y2; - // let's just make certain that our map is not empty. - if ( sortMap.isEmpty() ) { - throw new IllegalStateException("No points added yet to the interpolator."); + e1 = sortMap.floorEntry(x); + + if (e1 == null) { + // x smaller than any value in the set + e1 = sortMap.firstEntry(); + if (e1 == null) { + throw new IllegalStateException("No points added yet to the interpolator."); + } + return e1.getValue(); } - // firstKey in the map - cannot be null since the map is not empty. - Double firstKey = sortMap.firstKey(); + x1 = e1.getKey(); + e2 = sortMap.higherEntry(x1); - // x is smaller than the first entry in the map. - if ( x < firstKey.doubleValue() ) { - y1 = sortMap.get(firstKey); - return y1.doubleValue(); + if (e2 == null) { + // x larger than any value in the set + return e1.getValue(); } - // floor key is the largest key smaller than x - since we have at least one key, - // and x>=firstKey, we know that floorKey != null. - Double floorKey = sortMap.subMap(firstKey, x).lastKey(); - - x1 = floorKey.doubleValue(); - y1 = sortMap.get(floorKey); - - // Now we need to find the key that is greater or equal to x - SortedMap tailMap = sortMap.tailMap(x); - - // Check if x is bigger than all the entries. - if ( tailMap.isEmpty() ) { - return y1.doubleValue(); - } - Double ceilKey = tailMap.firstKey(); + x2 = e2.getKey(); + y1 = e1.getValue(); + y2 = e2.getValue(); - // Check if x is bigger than all the entries. - if ( ceilKey == null ) { - return y1.doubleValue(); - } - - x2 = ceilKey.doubleValue(); - y2 = sortMap.get(ceilKey); - return (x - x1)/(x2-x1) * (y2-y1) + y1; } - - + + public double[] getXPoints() { double[] x = new double[sortMap.size()]; Iterator iter = sortMap.keySet().iterator(); @@ -124,8 +113,8 @@ public class LinearInterpolator implements Cloneable { } return x; } - - + + @SuppressWarnings("unchecked") @Override public LinearInterpolator clone() { @@ -138,4 +127,25 @@ public class LinearInterpolator implements Cloneable { } } + + public static void main(String[] args) { + LinearInterpolator interpolator = new LinearInterpolator( + new double[] {1, 1.5, 2, 4, 5}, + new double[] {0, 1, 0, 2, 2} + ); + + for (double x=0; x < 6; x+=0.1) { + System.out.printf("%.1f: %.2f\n", x, interpolator.getValue(x)); + } + + // Should be the same + + ArrayList time = new ArrayList( Arrays.asList( new Double[] {1.0, 1.5, 2.0, 4.0, 5.0} )); + ArrayList y = new ArrayList( Arrays.asList( new Double[] {0.0, 1.0, 0.0, 2.0, 2.0} )); + + LinearInterpolator interpolator2 = new LinearInterpolator(time,y); + for (double x=0; x < 6; x+=0.1) { + System.out.printf("%.1f: %.2f\n", x, interpolator2.getValue(x)); + } + } } diff --git a/core/src/net/sf/openrocket/util/MathUtil.java b/core/src/net/sf/openrocket/util/MathUtil.java index c88e85853..09a316899 100644 --- a/core/src/net/sf/openrocket/util/MathUtil.java +++ b/core/src/net/sf/openrocket/util/MathUtil.java @@ -310,7 +310,7 @@ public class MathUtil { return sorted.get(n / 2).doubleValue(); } } - + /** * Use interpolation to determine the value of the function at point t. * Current implementation uses simple linear interpolation. The domain diff --git a/core/src/net/sf/openrocket/util/TextUtil.java b/core/src/net/sf/openrocket/util/TextUtil.java index 0189545e0..0b86876de 100644 --- a/core/src/net/sf/openrocket/util/TextUtil.java +++ b/core/src/net/sf/openrocket/util/TextUtil.java @@ -184,4 +184,17 @@ public class TextUtil { s = s.replace(">", ">"); return s; } + + /* + * Returns a word-wrapped version of given input string using HTML syntax, wrapped to len characters. + */ + public static String wrap(String in,int len) { + in=in.trim(); + if(in.length()"+in.substring(0,place).trim()+"
"+wrap(in.substring(place),len); + } + } diff --git a/core/test/net/sf/openrocket/simulation/customexpression/TestExpressions.java b/core/test/net/sf/openrocket/simulation/customexpression/TestExpressions.java new file mode 100644 index 000000000..d67bca0a9 --- /dev/null +++ b/core/test/net/sf/openrocket/simulation/customexpression/TestExpressions.java @@ -0,0 +1,23 @@ +package net.sf.openrocket.simulation.customexpression; + +import org.junit.Test; + +import static org.junit.Assert.*; +import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.rocketcomponent.Rocket; + +public class TestExpressions { + + @Test + public void testExpressions() { + // TODO Auto-generated constructor stub + + OpenRocketDocument doc = new OpenRocketDocument(new Rocket()); + + //CustomExpression exp = new CustomExpression(doc, "Kinetic energy", "Ek", "J", ".5*m*Vt^2"); + + CustomExpression exp = new CustomExpression(doc, "Average mass", "Mavg", "kg", "mean(m[0:t])"); + System.out.println( exp.getExpressionString() ); + + } +} diff --git a/core/test/net/sf/openrocket/util/MathUtilTest.java b/core/test/net/sf/openrocket/util/MathUtilTest.java index f11ba89c3..6b9c1e1ad 100644 --- a/core/test/net/sf/openrocket/util/MathUtilTest.java +++ b/core/test/net/sf/openrocket/util/MathUtilTest.java @@ -13,6 +13,23 @@ public class MathUtilTest { public static final double EPS = 0.00000000001; + /* + @Test + public void rangeTest() { + double[] a; + + a = MathUtil.range(0, 10, 2); + assertEquals(0, a[0], 0); + assertEquals(10, a[5], 0); + assertEquals(6, a.length, 0); + + a = MathUtil.range(1, 2, 2); + assertEquals(1, a[0], 0); + assertEquals(1, a.length, 0); + + } + */ + @Test public void miscMathTest() {