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}