Fixed issue where symbols / units for FlightDataTypes would not be defined after...
[debian/openrocket] / core / src / net / sf / openrocket / simulation / CustomExpression.java
1 package net.sf.openrocket.simulation;
2
3 import java.util.SortedMap;
4 import java.util.TreeMap;
5
6 import net.sf.openrocket.document.Simulation;
7 import net.sf.openrocket.logging.LogHelper;
8 import net.sf.openrocket.startup.Application;
9 import net.sf.openrocket.unit.FixedUnitGroup;
10 import net.sf.openrocket.unit.UnitGroup;
11 import net.sf.openrocket.util.ArrayList;
12 import de.congrace.exp4j.Calculable;
13 import de.congrace.exp4j.ExpressionBuilder;
14
15
16 /**
17  * Represents a single custom expression
18  * @author Richard Graham
19  *
20  */
21 public class CustomExpression implements Cloneable{
22         
23         private static final LogHelper log = Application.getLogger();
24         
25         private String name, symbol, unit, expression;
26         private ExpressionBuilder builder;
27         private Simulation sim = null;
28         
29         // A map of available operator strings (keys) and description of function (value)
30         public static final SortedMap<String, String> AVAILABLE_OPERATORS = new TreeMap<String, String>() {{
31             put("+"             , "Addition");
32             put("-"                     , "Subtraction");
33             put("*"                     , "Multiplication");
34             put("/"                     , "Divison");
35             put("%"                     , "Modulo");
36             put("^"                     , "Exponentiation");
37             put("abs()"         , "Absolute value");
38             put("ceil()"        , "Ceiling (next integer value");
39             put("floor()"       , "Floor (previous integer value");
40             put("sqrt()"        , "Square root");
41             put("cbrt()"        , "Cubic root");
42             put("exp()"         , "Euler\'s number raised to the value (e^x)");
43             put("log()"         , "Natural logarithm");
44             put("sin()"         , "Sine");
45             put("cos()"         , "Cosine");
46             put("tan()"         , "Tangent");
47             put("asin()"        , "Arc sine");
48             put("acos()"        , "Arc cosine");
49             put("atan()"        , "Arc tangent");
50             put("sinh()"        , "Hyerbolic sine");
51             put("cosh()"        , "Hyperbolic cosine");
52             put("tanh()"        , "Hyperbolic tangent");
53         }};  
54         
55         
56         public CustomExpression(){
57                 setName("");
58                 setSymbol("");
59                 setUnit("");
60                 setExpression("");
61         }
62         
63         public CustomExpression(Simulation sim){
64                 this();
65                 setSimulation(sim);
66         }
67         
68         public CustomExpression(Simulation sim, String name, String symbol, String unit, String expression) {
69                 
70                 setName(name);
71                 setSymbol(symbol);
72                 setUnit(unit);
73                 setExpression(expression);
74                 setSimulation(sim);
75         }
76         
77         /*
78          * Use this to update the simulation this is associated with
79          */
80         public void setSimulation(Simulation sim){
81                 this.sim = sim;
82         }
83         
84         public Simulation getSimulation() {
85                 return this.sim;
86         }
87         
88         /*
89          * Returns the flight data branch 0 for this simulation, or an empty branch
90          * if no simulated data exists
91          */
92         private FlightDataBranch getBranch() {
93                 if (    sim == null ||  sim.getSimulatedData().getBranchCount() == 0){//sim.getSimulatedData().getBranch(0) == null) {
94                         return new FlightDataBranch();
95                 }
96                 else {
97                         System.out.println("Using existing branch");
98                         return sim.getSimulatedData().getBranch(0);
99                 }
100         }
101         
102         
103         public void setName(String name){
104                 this.name = name;
105         }
106         
107         public void setUnit(String unit){
108                 this.unit = unit;
109         }
110         
111         public void setSymbol(String symbol){
112                 this.symbol = symbol;
113         }
114         
115         public void setExpression(String expression){
116                 this.expression = expression;
117                 builder = new ExpressionBuilder(expression);
118         }
119         
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() ){
126                         if (exp != this)
127                                 names.add(exp.getName());
128                 }
129                 return names;
130         }
131         
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() ){
138                         if (exp != this)
139                                 symbols.add(exp.getSymbol());
140                 }
141                 return symbols;
142         }
143         
144         public boolean checkSymbol(){
145                 if (symbol.trim().isEmpty())
146                         return false;
147                 
148                 // No bad characters
149                 for (char c : "0123456789.,()[]{}<> ".toCharArray())
150                         if (symbol.indexOf(c) != -1 )
151                                 return false;
152                 
153                 // No operators (ignoring brackets)
154                 for (String s : CustomExpression.AVAILABLE_OPERATORS.keySet()){
155                         if (symbol.contains(s.replaceAll("\\(|\\)", "")))
156                                 return false;
157                 }
158                 
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));
164                         return false;
165                 }
166                 
167                 return true;
168         }
169         
170         public boolean checkName(){
171                 if (name.trim().isEmpty())
172                         return false;
173                 
174                 // No characters that could mess things up saving etc
175                 for (char c : ",()[]{}<>".toCharArray())
176                         if (symbol.indexOf(c) != -1 )
177                                 return false;
178                 
179                 ArrayList<String> names = getAllNames().clone();
180                 if (names.contains(name.trim())){
181                         int index = names.indexOf(name.trim());
182                         log.user("Symbol "+symbol+" already exists, found "+names.get(index));
183                         return false;
184                 }
185                 
186                 return true;
187         }
188         
189         // Currently no restrictions on unit
190         public boolean checkUnit(){
191                 return true;
192         }
193         
194         public boolean checkAll(){
195                 return checkUnit() && checkSymbol() && checkName();
196         }
197         
198         public String getName(){
199                 return name;
200         }
201         
202         public String getSymbol(){
203                 return symbol;
204         }
205         
206         public String getUnit(){
207                 return unit;
208         }
209         
210         public String getExpressionString(){
211                 return expression;
212         }
213         
214         
215         /*
216          * Check if the current expression is valid
217          */
218         public boolean checkExpression(){
219                 
220                 if (expression.trim().isEmpty()){
221                         return false;
222                 }
223                 
224                 // Define the available variables as 0
225                 for (FlightDataType type : getBranch().getTypes()){
226                         System.out.println( " " + type.getSymbol() );
227                         builder.withVariable(type.getSymbol(), 0.0);
228                 }
229                 
230                 for (String symb : getAllSymbols()){
231                         builder.withVariable(symb, 0.0);
232                 }
233                 
234                 // Try to build
235                 try {
236                         builder.build();
237                 } catch (Exception e) {
238                         log.user("Custom expression invalid : " + e.toString());
239                         return false;
240                 }
241                 
242                 // Otherwise, all OK
243                 return true;
244         }
245         
246         /*
247          * Evaluate the expression using the last variable values from the simulation status.
248          * Returns NaN on any error.
249          */
250         public Double evaluate(SimulationStatus status){
251                 
252                 for (FlightDataType type : status.getFlightData().getTypes()){
253                         builder.withVariable(type.getSymbol(), status.getFlightData().getLast(type) );
254                 }
255                 
256                 Calculable calc;
257                 try {
258                         calc = builder.build();
259                         return new Double(calc.calculate());
260                 } catch (Exception e) {
261                         log.user("Could not calculate custom expression "+name);
262                         return Double.NaN;
263                 }
264         }
265
266         /*
267          * Returns the new flight data type corresponding to this calculated data
268          */
269         public FlightDataType getType(){
270                 // Figure out priority from order in array so that customs expressions are always at the top
271                 
272                 int totalExpressions = sim.getCustomExpressions().size();
273                 int p = -1*(totalExpressions-sim.getCustomExpressions().indexOf(this));
274                 UnitGroup ug = new FixedUnitGroup(unit);
275                 FlightDataType type =  FlightDataType.getType(name, symbol, ug);
276                 type.setPriority(p);
277                 return type;
278         }
279         
280         /*
281          * Add this expression to the simulation if not already added
282          */
283         public void addToSimulation(){
284                 if (! sim.getCustomExpressions().contains(this))
285                         sim.addCustomExpression( this );
286         }
287         
288         /*
289          * Removes this expression from the simulation, replacing it with a given new expression
290          */
291         public void overwrite(CustomExpression newExpression){
292                 if (!sim.getCustomExpressions().contains(this)) 
293                         return;
294                 else {
295                         int index = sim.getCustomExpressions().indexOf(this);
296                         sim.getCustomExpressions().set(index, newExpression);
297                 }
298         }
299         
300         @Override
301         public String toString(){
302                 return "Custom expression : "+this.name.toString()+ " " + this.expression.toString();
303         }
304         
305         @Override
306         /*
307          * Clone method makes a deep copy of everything except the simulation
308          * @see java.lang.Object#clone()
309          */
310         public Object clone() {
311               try {
312                   return super.clone();
313               }
314               catch( CloneNotSupportedException e )
315               {
316                   return new CustomExpression(  sim  , 
317                                 new String(this.getName()), 
318                                 new String(this.getSymbol()),
319                                 new String(this.getUnit()),
320                                 new String(this.getExpressionString()));
321               }
322           } 
323         
324 }