1 package net.sf.openrocket.simulation;
3 import java.util.SortedMap;
4 import java.util.TreeMap;
6 import net.sf.openrocket.document.Simulation;
7 import net.sf.openrocket.l10n.Translator;
8 import net.sf.openrocket.logging.LogHelper;
9 import net.sf.openrocket.startup.Application;
10 import net.sf.openrocket.unit.FixedUnitGroup;
11 import net.sf.openrocket.unit.UnitGroup;
12 import net.sf.openrocket.util.ArrayList;
13 import de.congrace.exp4j.Calculable;
14 import de.congrace.exp4j.ExpressionBuilder;
18 * Represents a single custom expression
19 * @author Richard Graham
22 public class CustomExpression implements Cloneable{
24 private static final LogHelper log = Application.getLogger();
25 private static final Translator trans = Application.getTranslator();
27 private String name, symbol, unit, expression;
28 private ExpressionBuilder builder;
29 private Simulation sim = null;
31 // A map of available operator strings (keys) and description of function (value)
32 public static final SortedMap<String, String> AVAILABLE_OPERATORS = new TreeMap<String, String>() {{
33 put("+" , trans.get("Operator.plus"));
34 put("-" , trans.get("Operator.minus"));
35 put("*" , trans.get("Operator.star"));
36 put("/" , trans.get("Operator.div"));
37 put("%" , trans.get("Operator.mod"));
38 put("^" , trans.get("Operator.pow"));
39 put("abs()" , trans.get("Operator.abs"));
40 put("ceil()" , trans.get("Operator.ceil"));
41 put("floor()" , trans.get("Operator.floor"));
42 put("sqrt()" , trans.get("Operator.sqrt"));
43 put("cbrt()" , trans.get("Operator.cbrt"));
44 put("exp()" , trans.get("Operator.exp"));
45 put("log()" , trans.get("Operator.ln"));
46 put("sin()" , trans.get("Operator.sin"));
47 put("cos()" , trans.get("Operator.cos"));
48 put("tan()" , trans.get("Operator.tan"));
49 put("asin()" , trans.get("Operator.asin"));
50 put("acos()" , trans.get("Operator.acos"));
51 put("atan()" , trans.get("Operator.atan"));
52 put("sinh()" , trans.get("Operator.hsin"));
53 put("cosh()" , trans.get("Operator.hcos"));
54 put("tanh()" , trans.get("Operator.htan"));
57 public CustomExpression(){
64 public CustomExpression(Simulation sim){
69 public CustomExpression(Simulation sim, String name, String symbol, String unit, String expression) {
74 setExpression(expression);
79 * Use this to update the simulation this is associated with
81 public void setSimulation(Simulation sim){
85 public Simulation getSimulation() {
90 * Returns the flight data branch 0 for this simulation, or an empty branch
91 * if no simulated data exists
93 private FlightDataBranch getBranch() {
94 if ( sim == null || sim.getSimulatedData() == null || sim.getSimulatedData().getBranchCount() == 0){
95 return new FlightDataBranch();
98 return sim.getSimulatedData().getBranch(0);
103 public void setName(String name){
107 public void setUnit(String unit){
111 public void setSymbol(String symbol){
112 this.symbol = symbol;
115 public void setExpression(String expression){
116 this.expression = expression;
117 builder = new ExpressionBuilder(expression);
120 // get a list of all the names of all the available variables
121 private ArrayList<String> getAllNames(){
122 ArrayList<String> names = new ArrayList<String>();
123 for (FlightDataType type : FlightDataType.ALL_TYPES)
124 names.add(type.getName());
125 for (CustomExpression exp : sim.getCustomExpressions() ){
127 names.add(exp.getName());
132 // get a list of all the symbols of the available variables ignoring this one
133 private ArrayList<String> getAllSymbols(){
134 ArrayList<String> symbols = new ArrayList<String>();
135 for (FlightDataType type : FlightDataType.ALL_TYPES)
136 symbols.add(type.getSymbol());
137 for (CustomExpression exp : sim.getCustomExpressions() ){
139 symbols.add(exp.getSymbol());
144 public boolean checkSymbol(){
145 if (symbol.trim().isEmpty())
149 for (char c : "0123456789.,()[]{}<> ".toCharArray())
150 if (symbol.indexOf(c) != -1 )
153 // No operators (ignoring brackets)
154 for (String s : CustomExpression.AVAILABLE_OPERATORS.keySet()){
155 if (symbol.contains(s.replaceAll("\\(|\\)", "")))
159 // No already defined symbols
160 ArrayList<String> symbols = getAllSymbols().clone();
161 if (symbols.contains(symbol.trim())){
162 int index = symbols.indexOf(symbol.trim());
163 log.user("Symbol "+symbol+" already exists, found "+symbols.get(index));
170 public boolean checkName(){
171 if (name.trim().isEmpty())
174 // No characters that could mess things up saving etc
175 for (char c : ",()[]{}<>".toCharArray())
176 if (name.indexOf(c) != -1 )
179 ArrayList<String> names = getAllNames().clone();
180 if (names.contains(name.trim())){
181 int index = names.indexOf(name.trim());
182 log.user("Name "+name+" already exists, found "+names.get(index));
189 // Currently no restrictions on unit
190 public boolean checkUnit(){
194 public boolean checkAll(){
195 return checkUnit() && checkSymbol() && checkName();
198 public String getName(){
202 public String getSymbol(){
206 public String getUnit(){
210 public String getExpressionString(){
216 * Check if the current expression is valid
218 public boolean checkExpression(){
220 if (expression.trim().isEmpty()){
224 // Define the available variables as 0
225 for (FlightDataType type : getBranch().getTypes()){
226 builder.withVariable(type.getSymbol(), 0.0);
229 for (String symb : getAllSymbols()){
230 builder.withVariable(symb, 0.0);
236 } catch (Exception e) {
237 log.user("Custom expression invalid : " + e.toString());
246 * Evaluate the expression using the last variable values from the simulation status.
247 * Returns NaN on any error.
249 public Double evaluate(SimulationStatus status){
251 for (FlightDataType type : status.getFlightData().getTypes()){
252 builder.withVariable(type.getSymbol(), status.getFlightData().getLast(type) );
257 calc = builder.build();
258 return new Double(calc.calculate());
259 } catch (Exception e) {
260 log.user("Could not calculate custom expression "+name);
266 * Returns the new flight data type corresponding to this calculated data
268 public FlightDataType getType(){
270 UnitGroup ug = new FixedUnitGroup(unit);
271 FlightDataType type = FlightDataType.getType(name, symbol, ug);
273 // If in a simulation, figure out priority from order in array so that customs expressions are always at the top
274 //if (sim != null && sim.getCustomExpressions().contains(this)){
275 // int totalExpressions = sim.getCustomExpressions().size();
276 // int p = -1*(totalExpressions-sim.getCustomExpressions().indexOf(this));
277 // type.setPriority(p);
284 * Add this expression to the simulation if valid and not already added
286 public void addToSimulation(){
287 // Abort if exact expression already in
288 if ( !sim.getCustomExpressions().contains(this) && this.checkAll() )
289 sim.addCustomExpression( this );
293 * Removes this expression from the simulation, replacing it with a given new expression
295 public void overwrite(CustomExpression newExpression){
296 if (!sim.getCustomExpressions().contains(this))
299 int index = sim.getCustomExpressions().indexOf(this);
300 sim.getCustomExpressions().set(index, newExpression);
305 * Add a copy to other simulations in this document if possible
306 * Will not overwrite existing expressions
308 public void copyToOtherSimulations(){
309 for (Simulation s : this.getSimulation().getDocument().getSimulations()){
310 CustomExpression newExpression = (CustomExpression) this.clone();
311 newExpression.setSimulation(s);
312 newExpression.addToSimulation();
317 public String toString(){
318 return "Custom expression : "+this.name.toString()+ " " + this.expression.toString();
323 * Clone method makes a deep copy of everything except the simulation.
324 * If you want to apply this to another simulation, set simulation manually after cloning.
325 * @see java.lang.Object#clone()
327 public Object clone() {
329 return super.clone();
331 catch( CloneNotSupportedException e )
333 return new CustomExpression( sim ,
334 new String(this.getName()),
335 new String(this.getSymbol()),
336 new String(this.getUnit()),
337 new String(this.getExpressionString()));