001package jmri.jmrit.logixng.util.parser.functions;
002
003import java.util.HashSet;
004import java.util.List;
005import java.util.Set;
006
007import jmri.JmriException;
008import jmri.jmrit.logixng.SymbolTable;
009import jmri.jmrit.logixng.util.parser.*;
010import jmri.util.TypeConversionUtil;
011
012import org.openide.util.lookup.ServiceProvider;
013
014/**
015 * Implementation of mathematical functions.
016 *
017 * @author Daniel Bergqvist 2019
018 */
019@ServiceProvider(service = FunctionFactory.class)
020public class MathFunctions implements FunctionFactory {
021
022    @Override
023    public String getModule() {
024        return "Math";
025    }
026
027    @Override
028    public Set<Function> getFunctions() {
029        Set<Function> functionClasses = new HashSet<>();
030
031        addRandomFunction(functionClasses);
032        addAbsFunction(functionClasses);
033        addSinFunction(functionClasses);
034        addCosFunction(functionClasses);
035        addTanFunction(functionClasses);
036        addArctanFunction(functionClasses);
037        addSqrFunction(functionClasses);
038        addSqrtFunction(functionClasses);
039        addRoundFunction(functionClasses);
040        addCeilFunction(functionClasses);
041        addFloorFunction(functionClasses);
042
043        return functionClasses;
044    }
045
046    @Override
047    public Set<Constant> getConstants() {
048        Set<Constant> constantClasses = new HashSet<>();
049        constantClasses.add(new Constant(getModule(), "MathPI", Math.PI));
050        constantClasses.add(new Constant(getModule(), "MathE", Math.E));
051        return constantClasses;
052    }
053
054    @Override
055    public String getConstantDescription() {
056        return Bundle.getMessage("Math.ConstantDescriptions");
057    }
058
059    private void addRandomFunction(Set<Function> functionClasses) {
060        functionClasses.add(new AbstractFunction(this, "random", Bundle.getMessage("Math.random_Descr")) {
061            @Override
062            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
063                    throws CalculateException, JmriException {
064
065                double min;
066                double max;
067                switch (parameterList.size()) {
068                    case 0:
069                        return Math.random();
070                    case 1:
071                        max = TypeConversionUtil.convertToDouble(
072                                parameterList.get(0).calculate(symbolTable), false);
073                        return Math.random() * max;
074                    case 2:
075                        min = TypeConversionUtil.convertToDouble(
076                                parameterList.get(0).calculate(symbolTable), false);
077                        max = TypeConversionUtil.convertToDouble(
078                                parameterList.get(1).calculate(symbolTable), false);
079                        return min + Math.random() * (max-min);
080                    default:
081                        throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
082                }
083            }
084        });
085    }
086
087    private void addAbsFunction(Set<Function> functionClasses) {
088        functionClasses.add(new AbstractFunction(this, "abs", Bundle.getMessage("Math.abs_Descr")) {
089            @Override
090            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
091                    throws CalculateException, JmriException {
092
093                switch (parameterList.size()) {
094                    case 1:
095                        Object value = parameterList.get(0).calculate(symbolTable);
096                        if ((value instanceof java.util.concurrent.atomic.AtomicInteger)
097                                || (value instanceof java.util.concurrent.atomic.AtomicLong)
098                                || (value instanceof java.math.BigInteger)
099                                || (value instanceof Byte)
100                                || (value instanceof Short)
101                                || (value instanceof Integer)
102                                || (value instanceof Long)
103                                || (value instanceof java.util.concurrent.atomic.LongAccumulator)
104                                || (value instanceof java.util.concurrent.atomic.LongAdder)) {
105                            return Math.abs(((Number)value).longValue());
106                        }
107                        if ((value instanceof java.math.BigDecimal)
108                                || (value instanceof Float)
109                                || (value instanceof Double)
110                                || (value instanceof java.util.concurrent.atomic.DoubleAccumulator)
111                                || (value instanceof java.util.concurrent.atomic.DoubleAdder)) {
112                            return Math.abs(((Number)value).doubleValue());
113                        }
114                        return Math.abs(TypeConversionUtil.convertToDouble(value, false));
115                    default:
116                        throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
117                }
118            }
119        });
120    }
121
122    private void addSinFunction(Set<Function> functionClasses) {
123        functionClasses.add(new AbstractFunction(this, "sin", Bundle.getMessage("Math.sin_Descr")) {
124            @Override
125            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
126                    throws JmriException {
127
128                if (parameterList.size() == 1) {
129                    double param = TypeConversionUtil.convertToDouble(
130                            parameterList.get(0).calculate(symbolTable), false);
131                    return Math.sin(param);
132                } else if (parameterList.size() >= 2) {
133                    double param0 = TypeConversionUtil.convertToDouble(
134                            parameterList.get(0).calculate(symbolTable), false);
135                    Object param1 = parameterList.get(1).calculate(symbolTable);
136                    double result;
137                    if (param1 instanceof String) {
138                        switch ((String)param1) {
139                            case "rad":
140                                result = Math.sin(param0);
141                                break;
142                            case "deg":
143                                result = Math.sin(Math.toRadians(param0));
144                                break;
145                            default:
146                                throw new CalculateException(Bundle.getMessage("IllegalParameter", 2, param1, getName()));
147                        }
148                    } else if (param1 instanceof Number) {
149                        double p1 = TypeConversionUtil.convertToDouble(param1, false);
150                        double angle = param0 / p1 * 2.0 * Math.PI;
151                        result = Math.sin(angle);
152                    } else {
153                        throw new CalculateException(Bundle.getMessage("IllegalParameter", 2, param1, getName()));
154                    }
155
156                    switch (parameterList.size()) {
157                        case 2:
158                            return result;
159                        case 4:
160                            double min = TypeConversionUtil.convertToDouble(
161                                    parameterList.get(2).calculate(symbolTable), false);
162                            double max = TypeConversionUtil.convertToDouble(
163                                    parameterList.get(3).calculate(symbolTable), false);
164                            return (result+1.0)/2.0 * (max-min) + min;
165                        default:
166                            throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
167                    }
168                }
169                throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
170            }
171        });
172    }
173
174    private void addCosFunction(Set<Function> functionClasses) {
175        functionClasses.add(new AbstractFunction(this, "cos", Bundle.getMessage("Math.cos_Descr")) {
176            @Override
177            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
178                    throws JmriException {
179
180                if (parameterList.size() == 1) {
181                    double param = TypeConversionUtil.convertToDouble(
182                            parameterList.get(0).calculate(symbolTable), false);
183                    return Math.cos(param);
184                } else if (parameterList.size() >= 2) {
185                    double param0 = TypeConversionUtil.convertToDouble(
186                            parameterList.get(0).calculate(symbolTable), false);
187                    Object param1 = parameterList.get(1).calculate(symbolTable);
188                    double result;
189                    if (param1 instanceof String) {
190                        switch ((String)param1) {
191                            case "rad":
192                                result = Math.cos(param0);
193                                break;
194                            case "deg":
195                                result = Math.cos(Math.toRadians(param0));
196                                break;
197                            default:
198                                throw new CalculateException(Bundle.getMessage("IllegalParameter", 2, param1, getName()));
199                        }
200                    } else if (param1 instanceof Number) {
201                        double p1 = TypeConversionUtil.convertToDouble(param1, false);
202                        double angle = param0 / p1 * 2.0 * Math.PI;
203                        result = Math.cos(angle);
204                    } else {
205                        throw new CalculateException(Bundle.getMessage("IllegalParameter", 2, param1, getName()));
206                    }
207
208                    switch (parameterList.size()) {
209                        case 2:
210                            return result;
211                        case 4:
212                            double min = TypeConversionUtil.convertToDouble(
213                                    parameterList.get(2).calculate(symbolTable), false);
214                            double max = TypeConversionUtil.convertToDouble(
215                                    parameterList.get(3).calculate(symbolTable), false);
216                            return (result+1.0)/2.0 * (max-min) + min;
217                        default:
218                            throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
219                    }
220                }
221                throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
222            }
223        });
224    }
225
226    private void addTanFunction(Set<Function> functionClasses) {
227        functionClasses.add(new AbstractFunction(this, "tan", Bundle.getMessage("Math.tan_Descr")) {
228            @Override
229            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
230                    throws JmriException {
231
232                if (parameterList.size() == 1) {
233                    double param = TypeConversionUtil.convertToDouble(
234                            parameterList.get(0).calculate(symbolTable), false);
235                    return Math.tan(param);
236                } else if (parameterList.size() == 2) {
237                    double param0 = TypeConversionUtil.convertToDouble(
238                            parameterList.get(0).calculate(symbolTable), false);
239                    Object param1 = parameterList.get(1).calculate(symbolTable);
240                    if (param1 instanceof String) {
241                        switch ((String)param1) {
242                            case "rad":
243                                return Math.tan(param0);
244                            case "deg":
245                                return Math.tan(Math.toRadians(param0));
246                            default:
247                                throw new CalculateException(Bundle.getMessage("IllegalParameter", 2, param1, getName()));
248                        }
249                    } else if (param1 instanceof Number) {
250                        double p1 = TypeConversionUtil.convertToDouble(param1, false);
251                        double angle = param0 / p1 * 2.0 * Math.PI;
252                        return Math.tan(angle);
253                    } else {
254                        throw new CalculateException(Bundle.getMessage("IllegalParameter", 2, param1, getName()));
255                    }
256                }
257                throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
258            }
259        });
260    }
261
262    private void addArctanFunction(Set<Function> functionClasses) {
263        functionClasses.add(new AbstractFunction(this, "atan", Bundle.getMessage("Math.atan_Descr")) {
264            @Override
265            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
266                    throws JmriException {
267
268                if (parameterList.size() == 1) {
269                    double param = TypeConversionUtil.convertToDouble(
270                            parameterList.get(0).calculate(symbolTable), false);
271                    return Math.atan(param);
272                } else if (parameterList.size() == 2) {
273                    double param0 = TypeConversionUtil.convertToDouble(
274                            parameterList.get(0).calculate(symbolTable), false);
275                    Object param1 = parameterList.get(1).calculate(symbolTable);
276                    if (param1 instanceof String) {
277                        switch ((String)param1) {
278                            case "rad":
279                                return Math.atan(param0);
280                            case "deg":
281                                return Math.toDegrees(Math.atan(param0));
282                            default:
283                                throw new CalculateException(Bundle.getMessage("IllegalParameter", 2, param1, getName()));
284                        }
285                    } else if (param1 instanceof Number) {
286                        double p1 = TypeConversionUtil.convertToDouble(param1, false);
287                        double angle = Math.atan(param0);
288                        return angle * p1 / 2.0 / Math.PI;
289                    } else {
290                        throw new CalculateException(Bundle.getMessage("IllegalParameter", 2, param1, getName()));
291                    }
292                }
293                throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
294            }
295        });
296    }
297
298    private void addSqrFunction(Set<Function> functionClasses) {
299        functionClasses.add(new AbstractFunction(this, "sqr", Bundle.getMessage("Math.sqr_Descr")) {
300            @Override
301            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
302                    throws CalculateException, JmriException {
303
304                switch (parameterList.size()) {
305                    case 1:
306                        Object value = parameterList.get(0).calculate(symbolTable);
307                        if ((value instanceof java.util.concurrent.atomic.AtomicInteger)
308                                || (value instanceof java.util.concurrent.atomic.AtomicLong)
309                                || (value instanceof java.math.BigInteger)
310                                || (value instanceof Byte)
311                                || (value instanceof Short)
312                                || (value instanceof Integer)
313                                || (value instanceof Long)
314                                || (value instanceof java.util.concurrent.atomic.LongAccumulator)
315                                || (value instanceof java.util.concurrent.atomic.LongAdder)) {
316
317                            long val = ((Number)value).longValue();
318                            return val * val;
319                        }
320                        if ((value instanceof java.math.BigDecimal)
321                                || (value instanceof Float)
322                                || (value instanceof Double)
323                                || (value instanceof java.util.concurrent.atomic.DoubleAccumulator)
324                                || (value instanceof java.util.concurrent.atomic.DoubleAdder)) {
325
326                            double val = ((Number)value).doubleValue();
327                            return val * val;
328                        }
329                        double val = TypeConversionUtil.convertToDouble(value, false);
330                        return val * val;
331                    default:
332                        throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
333                }
334            }
335        });
336    }
337
338    private void addSqrtFunction(Set<Function> functionClasses) {
339        functionClasses.add(new AbstractFunction(this, "sqrt", Bundle.getMessage("Math.sqrt_Descr")) {
340            @Override
341            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
342                    throws JmriException {
343
344                if (parameterList.size() == 1) {
345                    double param = TypeConversionUtil.convertToDouble(
346                            parameterList.get(0).calculate(symbolTable), false);
347                    return Math.sqrt(param);
348                } else {
349                    throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
350                }
351            }
352        });
353    }
354
355    private void addRoundFunction(Set<Function> functionClasses) {
356        functionClasses.add(new AbstractFunction(this, "round", Bundle.getMessage("Math.round_Descr")) {
357            @Override
358            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
359                    throws CalculateException, JmriException {
360
361                double value = TypeConversionUtil.convertToDouble(
362                        parameterList.get(0).calculate(symbolTable), false);
363
364                switch (parameterList.size()) {
365                    case 1:
366                        return (double) Math.round(value);
367                    case 2:
368                        long numDecimals = TypeConversionUtil.convertToLong(
369                                parameterList.get(1).calculate(symbolTable));
370                        double multiply = 1;
371                        for (int i=0; i < Math.abs(numDecimals); i++) {
372                            multiply *= 10;
373                        }
374                        if (numDecimals < 0) {
375                            multiply = 1.0 / multiply;
376                        }
377                        return Math.round(value*multiply) / multiply;
378                    default:
379                        throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
380                }
381            }
382        });
383    }
384
385    private void addCeilFunction(Set<Function> functionClasses) {
386        functionClasses.add(new AbstractFunction(this, "ceil", Bundle.getMessage("Math.ceil_Descr")) {
387            @Override
388            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
389                    throws CalculateException, JmriException {
390
391                double value = TypeConversionUtil.convertToDouble(
392                        parameterList.get(0).calculate(symbolTable), false);
393
394                switch (parameterList.size()) {
395                    case 1:
396                        return (double) Math.ceil(value);
397                    case 2:
398                        long numDecimals = TypeConversionUtil.convertToLong(
399                                parameterList.get(1).calculate(symbolTable));
400                        double multiply = 1;
401                        for (int i=0; i < Math.abs(numDecimals); i++) {
402                            multiply *= 10;
403                        }
404                        if (numDecimals < 0) {
405                            multiply = 1.0 / multiply;
406                        }
407                        return Math.ceil(value*multiply) / multiply;
408                    default:
409                        throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
410                }
411            }
412        });
413    }
414
415    private void addFloorFunction(Set<Function> functionClasses) {
416        functionClasses.add(new AbstractFunction(this, "floor", Bundle.getMessage("Math.floor_Descr")) {
417            @Override
418            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
419                    throws CalculateException, JmriException {
420
421                double value = TypeConversionUtil.convertToDouble(
422                        parameterList.get(0).calculate(symbolTable), false);
423
424                switch (parameterList.size()) {
425                    case 1:
426                        return (double) Math.floor(value);
427                    case 2:
428                        long numDecimals = TypeConversionUtil.convertToLong(
429                                parameterList.get(1).calculate(symbolTable));
430                        double multiply = 1;
431                        for (int i=0; i < Math.abs(numDecimals); i++) {
432                            multiply *= 10;
433                        }
434                        if (numDecimals < 0) {
435                            multiply = 1.0 / multiply;
436                        }
437                        return Math.floor(value*multiply) / multiply;
438                    default:
439                        throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName()));
440                }
441            }
442        });
443    }
444
445}