Fix run.sh
[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.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;
15
16
17 /**
18  * Represents a single custom expression
19  * @author Richard Graham
20  *
21  */
22 public class CustomExpression implements Cloneable{
23         
24         private static final LogHelper log = Application.getLogger();
25         private static final Translator trans = Application.getTranslator();
26         
27         private String name, symbol, unit, expression;
28         private ExpressionBuilder builder;
29         private Simulation sim = null;
30         
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"));
55         }};  
56         
57         public CustomExpression(){
58                 setName("");
59                 setSymbol("");
60                 setUnit("");
61                 setExpression("");
62         }
63         
64         public CustomExpression(Simulation sim){
65                 this();
66                 setSimulation(sim);
67         }
68         
69         public CustomExpression(Simulation sim, String name, String symbol, String unit, String expression) {
70                 
71                 setName(name);
72                 setSymbol(symbol);
73                 setUnit(unit);
74                 setExpression(expression);
75                 setSimulation(sim);
76         }
77         
78         /*
79          * Use this to update the simulation this is associated with
80          */
81         public void setSimulation(Simulation sim){
82                 this.sim = sim;
83         }
84         
85         public Simulation getSimulation() {
86                 return this.sim;
87         }
88         
89         /*
90          * Returns the flight data branch 0 for this simulation, or an empty branch
91          * if no simulated data exists
92          */
93         private FlightDataBranch getBranch() {
94                 if (    sim == null || sim.getSimulatedData() == null || sim.getSimulatedData().getBranchCount() == 0){
95                         return new FlightDataBranch();
96                 }
97                 else {
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 (name.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("Name "+name+" 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                         builder.withVariable(type.getSymbol(), 0.0);
227                 }
228                 
229                 for (String symb : getAllSymbols()){
230                         builder.withVariable(symb, 0.0);
231                 }
232                 
233                 // Try to build
234                 try {
235                         builder.build();
236                 } catch (Exception e) {
237                         log.user("Custom expression invalid : " + e.toString());
238                         return false;
239                 }
240                 
241                 // Otherwise, all OK
242                 return true;
243         }
244         
245         /*
246          * Evaluate the expression using the last variable values from the simulation status.
247          * Returns NaN on any error.
248          */
249         public Double evaluate(SimulationStatus status){
250                 
251                 for (FlightDataType type : status.getFlightData().getTypes()){
252                         builder.withVariable(type.getSymbol(), status.getFlightData().getLast(type) );
253                 }
254                 
255                 Calculable calc;
256                 try {
257                         calc = builder.build();
258                         return new Double(calc.calculate());
259                 } catch (Exception e) {
260                         log.user("Could not calculate custom expression "+name);
261                         return Double.NaN;
262                 }
263         }
264
265         /*
266          * Returns the new flight data type corresponding to this calculated data
267          */
268         public FlightDataType getType(){
269                 
270                 UnitGroup ug = new FixedUnitGroup(unit);
271                 FlightDataType type =  FlightDataType.getType(name, symbol, ug);
272                 
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);
278                 //}
279                 
280                 return type;
281         }
282         
283         /*
284          * Add this expression to the simulation if valid and not already added
285          */
286         public void addToSimulation(){
287                 // Abort if exact expression already in
288                 if ( !sim.getCustomExpressions().contains(this) && this.checkAll() )
289                         sim.addCustomExpression( this );        
290         }
291         
292         /*
293          * Removes this expression from the simulation, replacing it with a given new expression
294          */
295         public void overwrite(CustomExpression newExpression){
296                 if (!sim.getCustomExpressions().contains(this)) 
297                         return;
298                 else {
299                         int index = sim.getCustomExpressions().indexOf(this);
300                         sim.getCustomExpressions().set(index, newExpression);
301                 }
302         }
303         
304         /*
305          * Add a copy to other simulations in this document if possible
306          * Will not overwrite existing expressions
307          */
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();
313                 }
314         }
315         
316         @Override
317         public String toString(){
318                 return "Custom expression : "+this.name.toString()+ " " + this.expression.toString();
319         }
320         
321         @Override
322         /*
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()
326          */
327         public Object clone() {
328               try {
329                   return super.clone();
330               }
331               catch( CloneNotSupportedException e )
332               {
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()));
338               }
339           } 
340         
341 }