1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl.expr;
18
19 import java.math.BigDecimal;
20 import java.math.BigInteger;
21 import java.text.DecimalFormat;
22 import java.time.LocalDateTime;
23 import java.time.format.DateTimeFormatter;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.UUID;
27 import java.util.concurrent.ThreadLocalRandom;
28 import java.util.regex.Matcher;
29
30 import com.healthmarketscience.jackcess.expr.EvalContext;
31 import com.healthmarketscience.jackcess.expr.EvalException;
32 import com.healthmarketscience.jackcess.expr.Function;
33 import com.healthmarketscience.jackcess.expr.FunctionLookup;
34 import com.healthmarketscience.jackcess.expr.LocaleContext;
35 import com.healthmarketscience.jackcess.expr.NumericConfig;
36 import com.healthmarketscience.jackcess.expr.TemporalConfig;
37 import com.healthmarketscience.jackcess.expr.Value;
38 import com.healthmarketscience.jackcess.impl.ColumnImpl;
39 import com.healthmarketscience.jackcess.impl.DatabaseImpl;
40 import com.healthmarketscience.jackcess.impl.StringUtil;
41 import static com.healthmarketscience.jackcess.impl.expr.FunctionSupport.*;
42
43
44
45
46
47 public class DefaultFunctions
48 {
49 private static final Map<String,Function> FUNCS =
50 new HashMap<>();
51
52 static {
53
54 DefaultTextFunctions.init();
55 DefaultNumberFunctions.init();
56 DefaultDateFunctions.init();
57 DefaultFinancialFunctions.init();
58 }
59
60 public static final FunctionLookupexpr/FunctionLookup.html#FunctionLookup">FunctionLookup LOOKUP = new FunctionLookup() {
61 @Override
62 public Function getFunction(String name) {
63 return FUNCS.get(DatabaseImpl.toLookupName(name));
64 }
65 };
66
67 private DefaultFunctions() {}
68
69
70 public static final Function IIF = registerFunc(new Func3("IIf") {
71 @Override
72 protected Value eval3(EvalContext ctx,
73 Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param2, Value param3) {
74
75 return ((!param1.isNull() && param1.getAsBoolean(ctx)) ? param2 : param3);
76 }
77 });
78
79 public static final Function HEX = registerStringFunc(new Func1NullIsNull("Hex") {
80 @Override
81 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
82 if(param1.getType().isString() &&
83 (param1.getAsString(ctx).length() == 0)) {
84 return ValueSupport.ZERO_VAL;
85 }
86 int lv = param1.getAsLongInt(ctx);
87 return ValueSupport.toValue(
88 StringUtil.toUpperCase(Integer.toHexString(lv)));
89 }
90 });
91
92 public static final Function NZ = registerFunc(new FuncVar("Nz", 1, 2) {
93 @Override
94 protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
95 Value param1 = params[0];
96 if(!param1.isNull()) {
97 return param1;
98 }
99 if(params.length > 1) {
100 return params[1];
101 }
102 Value.Type resultType = ctx.getResultType();
103 return (((resultType == null) || resultType.isString()) ?
104 ValueSupport.EMPTY_STR_VAL : ValueSupport.ZERO_VAL);
105 }
106 });
107
108 public static final Function CHOOSE = registerFunc(new FuncVar("Choose", 1, Integer.MAX_VALUE) {
109 @Override
110 protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
111 Value param1 = params[0];
112 int idx = param1.getAsLongInt(ctx);
113 if((idx < 1) || (idx >= params.length)) {
114 return ValueSupport.NULL_VAL;
115 }
116 return params[idx];
117 }
118 });
119
120 public static final Function SWITCH = registerFunc(new FuncVar("Switch") {
121 @Override
122 protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
123 if((params.length % 2) != 0) {
124 throw new EvalException("Odd number of parameters");
125 }
126 for(int i = 0; i < params.length; i+=2) {
127 if(params[i].getAsBoolean(ctx)) {
128 return params[i + 1];
129 }
130 }
131 return ValueSupport.NULL_VAL;
132 }
133 });
134
135 public static final Function OCT = registerStringFunc(new Func1NullIsNull("Oct") {
136 @Override
137 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
138 if(param1.getType().isString() &&
139 (param1.getAsString(ctx).length() == 0)) {
140 return ValueSupport.ZERO_VAL;
141 }
142 int lv = param1.getAsLongInt(ctx);
143 return ValueSupport.toValue(Integer.toOctalString(lv));
144 }
145 });
146
147 public static final Function CBOOL = registerFunc(new Func1("CBool") {
148 @Override
149 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
150 boolean b = param1.getAsBoolean(ctx);
151 return ValueSupport.toValue(b);
152 }
153 });
154
155 public static final Function CBYTE = registerFunc(new Func1("CByte") {
156 @Override
157 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
158 int lv = param1.getAsLongInt(ctx);
159 if((lv < 0) || (lv > 255)) {
160 throw new EvalException("Byte code '" + lv + "' out of range ");
161 }
162 return ValueSupport.toValue(lv);
163 }
164 });
165
166 public static final Function CCUR = registerFunc(new Func1("CCur") {
167 @Override
168 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
169 BigDecimal bd = param1.getAsBigDecimal(ctx);
170 bd = bd.setScale(4, NumberFormatter.ROUND_MODE);
171 return ValueSupport.toValue(bd);
172 }
173 });
174
175 public static final Function CDATE = registerFunc(new Func1("CDate") {
176 @Override
177 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
178 return param1.getAsDateTimeValue(ctx);
179 }
180 });
181 static {
182 registerFunc("CVDate", CDATE);
183 }
184
185 public static final Function CDBL = registerFunc(new Func1("CDbl") {
186 @Override
187 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
188 Double dv = param1.getAsDouble(ctx);
189 return ValueSupport.toValue(dv);
190 }
191 });
192
193 public static final Function CDEC = registerFunc(new Func1("CDec") {
194 @Override
195 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
196 BigDecimal bd = param1.getAsBigDecimal(ctx);
197 return ValueSupport.toValue(bd);
198 }
199 });
200
201 public static final Function CINT = registerFunc(new Func1("CInt") {
202 @Override
203 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
204 int lv = param1.getAsLongInt(ctx);
205 if((lv < Short.MIN_VALUE) || (lv > Short.MAX_VALUE)) {
206 throw new EvalException("Int value '" + lv + "' out of range ");
207 }
208 return ValueSupport.toValue(lv);
209 }
210 });
211
212 public static final Function CLNG = registerFunc(new Func1("CLng") {
213 @Override
214 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
215 int lv = param1.getAsLongInt(ctx);
216 return ValueSupport.toValue(lv);
217 }
218 });
219
220 public static final Function CSNG = registerFunc(new Func1("CSng") {
221 @Override
222 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
223 Double dv = param1.getAsDouble(ctx);
224 if((dv < Float.MIN_VALUE) || (dv > Float.MAX_VALUE)) {
225 throw new EvalException("Single value '" + dv + "' out of range ");
226 }
227 return ValueSupport.toValue(dv.floatValue());
228 }
229 });
230
231 public static final Function CSTR = registerFunc(new Func1("CStr") {
232 @Override
233 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
234 return ValueSupport.toValue(param1.getAsString(ctx));
235 }
236 });
237
238 public static final Function CVAR = registerFunc(new Func1("CVar") {
239 @Override
240 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
241 return param1;
242 }
243 });
244
245 public static final Function ISNULL = registerFunc(new Func1("IsNull") {
246 @Override
247 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
248 return ValueSupport.toValue(param1.isNull());
249 }
250 });
251
252 public static final Function ISDATE = registerFunc(new Func1("IsDate") {
253 @Override
254 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
255 if(param1.getType().isTemporal()) {
256 return ValueSupport.TRUE_VAL;
257 }
258
259
260
261
262 if(param1.getType().isString() &&
263 (DefaultDateFunctions.stringToDateValue(
264 ctx, param1.getAsString(ctx)) != null)) {
265 return ValueSupport.TRUE_VAL;
266 }
267
268 return ValueSupport.FALSE_VAL;
269 }
270 });
271
272 public static final Function ISNUMERIC = registerFunc(new Func1("IsNumeric") {
273 @Override
274 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
275 if(param1.getType().isNumeric()) {
276 return ValueSupport.TRUE_VAL;
277 }
278
279
280
281 if(param1.getType().isString() && stringIsNumeric(ctx, param1)) {
282 return ValueSupport.TRUE_VAL;
283 }
284
285 return ValueSupport.FALSE_VAL;
286 }
287 });
288
289 public static final Function FORMATNUMBER = registerFunc(new FuncVar("FormatNumber", 1, 6) {
290 @Override
291 protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
292 return formatNumber(ctx, params, FormatUtil.NumPatternType.GENERAL);
293 }
294 });
295
296 public static final Function FORMATPERCENT = registerFunc(new FuncVar("FormatPercent", 1, 6) {
297 @Override
298 protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
299 return formatNumber(ctx, params, FormatUtil.NumPatternType.PERCENT);
300 }
301 });
302
303 public static final Function FORMATCURRENCY = registerFunc(new FuncVar("FormatCurrency", 1, 6) {
304 @Override
305 protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
306 return formatNumber(ctx, params, FormatUtil.NumPatternType.CURRENCY);
307 }
308 });
309
310 public static final Function FORMATDATETIME = registerFunc(new FuncVar("FormatDateTime", 1, 2) {
311 @Override
312 protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
313 Value param1 = params[0];
314 if(param1.isNull()) {
315 return ValueSupport.NULL_VAL;
316 }
317
318 LocalDateTime ldt = param1.getAsLocalDateTime(ctx);
319
320 int fmtType = getOptionalIntParam(ctx, params, 1, 0);
321 TemporalConfig.Type tempType = null;
322 switch(fmtType) {
323 case 0:
324
325 Value.Type valType = ValueSupport.getDateTimeType(ldt);
326 switch(valType) {
327 case DATE:
328 tempType = TemporalConfig.Type.SHORT_DATE;
329 break;
330 case TIME:
331 tempType = TemporalConfig.Type.LONG_TIME;
332 break;
333 default:
334 tempType = TemporalConfig.Type.GENERAL_DATE;
335 }
336 break;
337 case 1:
338
339 tempType = TemporalConfig.Type.LONG_DATE;
340 break;
341 case 2:
342
343 tempType = TemporalConfig.Type.SHORT_DATE;
344 break;
345 case 3:
346
347 tempType = TemporalConfig.Type.LONG_TIME;
348 break;
349 case 4:
350
351 tempType = TemporalConfig.Type.SHORT_TIME;
352 break;
353 default:
354 throw new EvalException("Unknown format " + fmtType);
355 }
356
357 DateTimeFormatter dtf = ctx.createDateFormatter(
358 ctx.getTemporalConfig().getDateTimeFormat(tempType));
359 return ValueSupport.toValue(dtf.format(ldt));
360 }
361 });
362
363 public static final Function GENGUID = registerFunc(new Func0("GenGUID") {
364 @Override
365 protected Value eval0(EvalContext ctx) {
366
367
368
369 return ValueSupport.toValue(
370 ColumnImpl.toGUIDString(UUID.randomUUID()));
371 }
372 });
373
374 public static final Function GENUNIQUEID = registerFunc(new Func0("GenUniqueID") {
375 @Override
376 protected Value eval0(EvalContext ctx) {
377
378
379
380 return ValueSupport.toValue(ThreadLocalRandom.current().nextInt());
381 }
382 });
383
384 public static final Function VARTYPE = registerFunc(new Func1("VarType") {
385 @Override
386 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
387 Value.Type type = param1.getType();
388 int vType = 0;
389 switch(type) {
390 case NULL:
391
392 vType = 1;
393 break;
394 case STRING:
395
396 vType = 8;
397 break;
398 case DATE:
399 case TIME:
400 case DATE_TIME:
401
402 vType = 7;
403 break;
404 case LONG:
405
406 vType = 3;
407 break;
408 case DOUBLE:
409
410 vType = 5;
411 break;
412 case BIG_DEC:
413
414 vType = 14;
415 break;
416 default:
417 throw new EvalException("Unknown type " + type);
418 }
419 return ValueSupport.toValue(vType);
420 }
421 });
422
423 public static final Function TYPENAME = registerFunc(new Func1("TypeName") {
424 @Override
425 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
426 Value.Type type = param1.getType();
427 String tName = null;
428 switch(type) {
429 case NULL:
430 tName = "Null";
431 break;
432 case STRING:
433 tName = "String";
434 break;
435 case DATE:
436 case TIME:
437 case DATE_TIME:
438 tName = "Date";
439 break;
440 case LONG:
441 tName = "Long";
442 break;
443 case DOUBLE:
444 tName = "Double";
445 break;
446 case BIG_DEC:
447 tName = "Decimal";
448 break;
449 default:
450 throw new EvalException("Unknown type " + type);
451 }
452 return ValueSupport.toValue(tName);
453 }
454 });
455
456 public static final Function VAL = registerStringFunc(new Func1NullIsNull("Val") {
457 @Override
458 protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
459
460
461 String str = ValueSupport.WHITESPACE_PAT.matcher(param1.getAsString(ctx))
462 .replaceAll("");
463
464 if(str.length() == 0) {
465 return ValueSupport.ZERO_D_VAL;
466 }
467
468 Matcher m = null;
469
470 if(str.charAt(0) == ValueSupport.NUMBER_BASE_PREFIX) {
471
472
473 BigInteger bi = null;
474 if((m = ValueSupport.HEX_PAT.matcher(str)).find()) {
475 bi = ValueSupport.parseIntegerString(m.group(), 16);
476 } else if((m = ValueSupport.OCTAL_PAT.matcher(str)).find()) {
477 bi = ValueSupport.parseIntegerString(m.group(), 8);
478 }
479
480 if(bi != null) {
481
482
483
484
485 int iVal = ((bi.bitLength() <= 16) ? bi.shortValue() : bi.intValue());
486 return ValueSupport.toValue((double)iVal);
487 }
488
489 } else {
490
491
492 if((m = ValueSupport.NUMBER_PAT.matcher(str)).find()) {
493 BigDecimal bd = new BigDecimal(m.group());
494 return ValueSupport.toValue(bd.doubleValue());
495 }
496 }
497
498 return ValueSupport.ZERO_D_VAL;
499 }
500 });
501
502 private static boolean stringIsNumeric(LocaleContext ctx, Value param) {
503 return (maybeGetAsBigDecimal(ctx, param) != null);
504 }
505
506 static BigDecimal maybeGetAsBigDecimal(LocaleContext ctx, Value param) {
507 try {
508 return param.getAsBigDecimal(ctx);
509 } catch(EvalException ignored) {
510
511 }
512 return null;
513 }
514
515 static Valuecience/jackcess/expr/Value.html#Value">Value maybeGetAsDateTimeValue(LocaleContext ctx, Value param) {
516 try {
517
518 return param.getAsDateTimeValue(ctx);
519 } catch(EvalException ignored) {
520
521 }
522 return null;
523 }
524
525 private static boolean getOptionalTriStateBoolean(
526 EvalContext ctx, Value[] params, int idx, boolean defValue) {
527 boolean bv = defValue;
528 if(params.length > idx) {
529 int val = params[idx].getAsLongInt(ctx);
530 switch(val) {
531 case 0:
532
533 bv = false;
534 break;
535 case -1:
536
537 bv = true;
538 break;
539 case -2:
540
541 bv = defValue;
542 break;
543 default:
544 throw new EvalException("Unsupported tri-state boolean value " + val);
545 }
546 }
547 return bv;
548 }
549
550 private static Value formatNumber(
551 EvalContext ctx, Value[] params, FormatUtil.NumPatternType numPatType) {
552
553 Value param1 = params[0];
554 if(param1.isNull()) {
555 return ValueSupport.NULL_VAL;
556 }
557
558 NumericConfig cfg = ctx.getNumericConfig();
559 int numDecDigits = getOptionalIntParam(
560 ctx, params, 1, cfg.getNumDecimalDigits(), -1);
561 boolean incLeadDigit = getOptionalTriStateBoolean(
562 ctx, params, 2, cfg.includeLeadingDigit());
563 boolean defNegParens = numPatType.useParensForNegatives(cfg);
564 boolean negParens = getOptionalTriStateBoolean(
565 ctx, params, 3, defNegParens);
566 int defNumGroupDigits = cfg.getNumGroupingDigits();
567 boolean groupDigits = getOptionalTriStateBoolean(
568 ctx, params, 4, (defNumGroupDigits > 0));
569 int numGroupDigits = (groupDigits ? defNumGroupDigits : 0);
570
571 String fmtStr = FormatUtil.createNumberFormatPattern(
572 numPatType, numDecDigits, incLeadDigit, negParens, numGroupDigits);
573
574 DecimalFormat df = ctx.createDecimalFormat(fmtStr);
575
576 return ValueSupport.toValue(df.format(param1.getAsBigDecimal(ctx)));
577 }
578
579
580
581
582 static Function/../../../com/healthmarketscience/jackcess/expr/Function.html#Function">Function registerFunc(Function func) {
583 registerFunc(func.getName(), func);
584 return func;
585 }
586
587 static Function/../com/healthmarketscience/jackcess/expr/Function.html#Function">Function registerStringFunc(Function func) {
588 registerFunc(func.getName(), func);
589 registerFunc(new StringFuncWrapper(func));
590 return func;
591 }
592
593 private static void registerFunc(String fname, Function func) {
594 String lookupFname = DatabaseImpl.toLookupName(fname);
595 if(FUNCS.put(lookupFname, func) != null) {
596 throw new IllegalStateException("Duplicate function " + fname);
597 }
598 }
599 }