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.text.DecimalFormat;
21 import java.text.NumberFormat;
22 import java.time.LocalDateTime;
23 import java.time.format.DateTimeFormatter;
24 import java.time.format.DateTimeFormatterBuilder;
25 import java.time.temporal.ChronoField;
26 import java.time.temporal.TemporalField;
27 import java.time.temporal.WeekFields;
28 import java.util.AbstractMap;
29 import java.util.AbstractSet;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.function.BiConsumer;
38
39 import com.healthmarketscience.jackcess.expr.EvalContext;
40 import com.healthmarketscience.jackcess.expr.EvalException;
41 import com.healthmarketscience.jackcess.expr.NumericConfig;
42 import com.healthmarketscience.jackcess.expr.TemporalConfig;
43 import com.healthmarketscience.jackcess.expr.Value;
44 import org.apache.commons.lang3.StringUtils;
45 import static com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.ExprBuf;
46
47
48
49
50
51 public class FormatUtil
52 {
53 public enum NumPatternType {
54 GENERAL,
55 CURRENCY {
56 @Override
57 protected void appendPrefix(StringBuilder fmt) {
58 fmt.append('\u00A4');
59 }
60 @Override
61 protected boolean useParensForNegatives(NumericConfig cfg) {
62 return cfg.useParensForCurrencyNegatives();
63 }
64 },
65 EURO {
66 @Override
67 protected void appendPrefix(StringBuilder fmt) {
68 fmt.append('\u20AC');
69 }
70 @Override
71 protected boolean useParensForNegatives(NumericConfig cfg) {
72 return cfg.useParensForCurrencyNegatives();
73 }
74 },
75 PERCENT {
76 @Override
77 protected void appendSuffix(StringBuilder fmt) {
78 fmt.append('%');
79 }
80 },
81 SCIENTIFIC {
82 @Override
83 protected void appendSuffix(StringBuilder fmt) {
84 fmt.append("E0");
85 }
86 };
87
88 protected void appendPrefix(StringBuilder fmt) {}
89
90 protected void appendSuffix(StringBuilder fmt) {}
91
92 protected boolean useParensForNegatives(NumericConfig cfg) {
93 return cfg.useParensForNegatives();
94 }
95 }
96
97 private enum TextCase {
98 NONE,
99 UPPER {
100 @Override public char apply(char c) {
101 return Character.toUpperCase(c);
102 }
103 },
104 LOWER {
105 @Override public char apply(char c) {
106 return Character.toLowerCase(c);
107 }
108 };
109
110 public char apply(char c) {
111 return c;
112 }
113 }
114
115 private static final Map<String,Fmt> PREDEF_FMTS = new HashMap<String,Fmt>();
116
117 static {
118 putPredefFormat("General Date", args -> ValueSupport.toValue(
119 args.coerceToDateTimeValue().getAsString()));
120 putPredefFormat("Long Date",
121 new PredefDateFmt(TemporalConfig.Type.LONG_DATE));
122 putPredefFormat("Medium Date",
123 new PredefDateFmt(TemporalConfig.Type.MEDIUM_DATE));
124 putPredefFormat("Short Date",
125 new PredefDateFmt(TemporalConfig.Type.SHORT_DATE));
126 putPredefFormat("Long Time",
127 new PredefDateFmt(TemporalConfig.Type.LONG_TIME));
128 putPredefFormat("Medium Time",
129 new PredefDateFmt(TemporalConfig.Type.MEDIUM_TIME));
130 putPredefFormat("Short Time",
131 new PredefDateFmt(TemporalConfig.Type.SHORT_TIME));
132
133 putPredefFormat("General Number", args -> ValueSupport.toValue(
134 args.coerceToNumberValue().getAsString()));
135 putPredefFormat("Currency",
136 new PredefNumberFmt(NumericConfig.Type.CURRENCY));
137 putPredefFormat("Euro", new PredefNumberFmt(NumericConfig.Type.EURO));
138 putPredefFormat("Fixed",
139 new PredefNumberFmt(NumericConfig.Type.FIXED));
140 putPredefFormat("Standard",
141 new PredefNumberFmt(NumericConfig.Type.STANDARD));
142 putPredefFormat("Percent",
143 new PredefNumberFmt(NumericConfig.Type.PERCENT));
144 putPredefFormat("Scientific", new ScientificPredefNumberFmt());
145
146 putPredefFormat("True/False", new PredefBoolFmt("True", "False"));
147 putPredefFormat("Yes/No", new PredefBoolFmt("Yes", "No"));
148 putPredefFormat("On/Off", new PredefBoolFmt("On", "Off"));
149 }
150
151 private static final Fmt NULL_FMT = args -> ValueSupport.EMPTY_STR_VAL;
152 private static final Fmt DUMMY_FMT = args -> args.getNonNullExpr();
153
154 private static final char QUOTE_CHAR = '"';
155 private static final char ESCAPE_CHAR = '\\';
156 private static final char LEFT_ALIGN_CHAR = '!';
157 private static final char START_COLOR_CHAR = '[';
158 private static final char END_COLOR_CHAR = ']';
159 private static final char CHOICE_SEP_CHAR = ';';
160
161
162 private static final char FILL_ESCAPE_CHAR = '*';
163 private static final char REQ_PLACEHOLDER_CHAR = '@';
164 private static final char OPT_PLACEHOLDER_CHAR = '&';
165 private static final char TO_UPPER_CHAR = '>';
166 private static final char TO_LOWER_CHAR = '<';
167 private static final char DT_LIT_COLON_CHAR = ':';
168 private static final char DT_LIT_SLASH_CHAR = '/';
169 private static final char SINGLE_QUOTE_CHAR = '\'';
170 private static final char EXP_E_CHAR = 'E';
171 private static final char EXP_e_CHAR = 'e';
172 private static final char PLUS_CHAR = '+';
173 private static final char MINUS_CHAR = '-';
174 private static final char REQ_DIGIT_CHAR = '0';
175 private static final int NO_CHAR = -1;
176
177 private static final byte FCT_UNKNOWN = 0;
178 private static final byte FCT_LITERAL = 1;
179 private static final byte FCT_GENERAL = 2;
180 private static final byte FCT_DATE = 3;
181 private static final byte FCT_NUMBER = 4;
182 private static final byte FCT_TEXT = 5;
183
184 private static final byte[] FORMAT_CODE_TYPES = new byte[127];
185 static {
186 setFormatCodeTypes(" $+-()", FCT_LITERAL);
187 setFormatCodeTypes("\"!*\\[];", FCT_GENERAL);
188 setFormatCodeTypes(":/cdwmqyhnstampmAMPM", FCT_DATE);
189 setFormatCodeTypes(".,0#%Ee", FCT_NUMBER);
190 setFormatCodeTypes("@&<>", FCT_TEXT);
191 }
192
193 @FunctionalInterface
194 interface Fmt {
195 public Value format(Args args);
196 }
197
198 @FunctionalInterface
199 interface DateFormatBuilder {
200 public void build(DateTimeFormatterBuilder dtfb, Args args,
201 boolean hasAmPm, Value.Type dtType);
202 }
203
204 private static final DateFormatBuilder PARTIAL_PREFIX =
205 (dtfb, args, hasAmPm, dtType) -> {
206 throw new UnsupportedOperationException();
207 };
208
209 private static final Map<String,DateFormatBuilder> DATE_FMT_BUILDERS =
210 new HashMap<>();
211 static {
212 DATE_FMT_BUILDERS.put("c",
213 (dtfb, args, hasAmPm, dtType) ->
214 dtfb.append(ValueSupport.getDateFormatForType(
215 args._ctx, dtType)));
216 DATE_FMT_BUILDERS.put("d", new SimpleDFB("d"));
217 DATE_FMT_BUILDERS.put("dd", new SimpleDFB("dd"));
218 DATE_FMT_BUILDERS.put("ddd", new SimpleDFB("eee"));
219 DATE_FMT_BUILDERS.put("dddd", new SimpleDFB("eeee"));
220 DATE_FMT_BUILDERS.put("ddddd", new PredefDFB(TemporalConfig.Type.SHORT_DATE));
221 DATE_FMT_BUILDERS.put("dddddd", new PredefDFB(TemporalConfig.Type.LONG_DATE));
222 DATE_FMT_BUILDERS.put("w", new WeekBasedDFB() {
223 @Override
224 protected TemporalField getField(WeekFields weekFields) {
225 return weekFields.dayOfWeek();
226 }
227 });
228 DATE_FMT_BUILDERS.put("ww", new WeekBasedDFB() {
229 @Override
230 protected TemporalField getField(WeekFields weekFields) {
231 return weekFields.weekOfWeekBasedYear();
232 }
233 });
234 DATE_FMT_BUILDERS.put("m", new SimpleDFB("L"));
235 DATE_FMT_BUILDERS.put("mm", new SimpleDFB("LL"));
236 DATE_FMT_BUILDERS.put("mmm", new SimpleDFB("MMM"));
237 DATE_FMT_BUILDERS.put("mmmm", new SimpleDFB("MMMM"));
238 DATE_FMT_BUILDERS.put("q", new SimpleDFB("Q"));
239 DATE_FMT_BUILDERS.put("y", new SimpleDFB("D"));
240 DATE_FMT_BUILDERS.put("yy", new SimpleDFB("yy"));
241 DATE_FMT_BUILDERS.put("yyyy", new SimpleDFB("yyyy"));
242 DATE_FMT_BUILDERS.put("h", new HourlyDFB("h", "H"));
243 DATE_FMT_BUILDERS.put("hh", new HourlyDFB("hh", "HH"));
244 DATE_FMT_BUILDERS.put("n", new SimpleDFB("m"));
245 DATE_FMT_BUILDERS.put("nn", new SimpleDFB("mm"));
246 DATE_FMT_BUILDERS.put("s", new SimpleDFB("s"));
247 DATE_FMT_BUILDERS.put("ss", new SimpleDFB("ss"));
248 DATE_FMT_BUILDERS.put("ttttt", new PredefDFB(TemporalConfig.Type.LONG_TIME));
249 DATE_FMT_BUILDERS.put("AM/PM", new AmPmDFB("AM", "PM"));
250 DATE_FMT_BUILDERS.put("am/pm", new AmPmDFB("am", "pm"));
251 DATE_FMT_BUILDERS.put("A/P", new AmPmDFB("A", "P"));
252 DATE_FMT_BUILDERS.put("a/p", new AmPmDFB("a", "p"));
253 DATE_FMT_BUILDERS.put("AMPM",
254 (dtfb, args, hasAmPm, dtType) -> {
255 String[] amPmStrs = args._ctx.getTemporalConfig().getAmPmStrings();
256 new AmPmDFB(amPmStrs[0], amPmStrs[1]).build(dtfb, args, hasAmPm, dtType);
257 }
258 );
259 fillInPartialPrefixes();
260 }
261
262 private static final int NF_POS_IDX = 0;
263 private static final int NF_NEG_IDX = 1;
264 private static final int NF_ZERO_IDX = 2;
265 private static final int NF_NULL_IDX = 3;
266 private static final int NUM_NF_FMTS = 4;
267
268 private static final NumberFormatter.NotationType[] NO_EXP_TYPES =
269 new NumberFormatter.NotationType[NUM_NF_FMTS];
270 private static final boolean[] NO_FMT_TYPES = new boolean[NUM_NF_FMTS];
271
272
273 private static final class Args
274 {
275 private final EvalContext _ctx;
276 private Value _expr;
277 private final int _firstDay;
278 private final int _firstWeekType;
279
280 private Args(EvalContext ctx, Value expr, int firstDay, int firstWeekType) {
281 _ctx = ctx;
282 _expr = expr;
283 _firstDay = firstDay;
284 _firstWeekType = firstWeekType;
285 }
286
287 public Args setExpr(Value expr) {
288 _expr = expr;
289 return this;
290 }
291
292 public Value getNonNullExpr() {
293 return (_expr.isNull() ? ValueSupport.EMPTY_STR_VAL : _expr);
294 }
295
296 public boolean isNullOrEmptyString() {
297 return(_expr.isNull() ||
298
299 (_expr.getType().isString() && getAsString().isEmpty()));
300 }
301
302 public boolean maybeCoerceToEmptyString() {
303 if(isNullOrEmptyString()) {
304
305
306 _expr = ValueSupport.EMPTY_STR_VAL;
307 return true;
308 }
309 return false;
310 }
311
312 public Args coerceToDateTimeValue() {
313 if(!_expr.getType().isTemporal()) {
314
315
316 Value boolExpr = null;
317 if(_expr.getType().isString() &&
318 ((boolExpr = maybeGetStringAsBooleanValue()) != null)) {
319 _expr = boolExpr;
320 }
321
322
323
324
325 _expr = _expr.getAsDateTimeValue(_ctx);
326 }
327 return this;
328 }
329
330 public Args coerceToNumberValue() {
331 if(!_expr.getType().isNumeric()) {
332 if(_expr.getType().isString()) {
333
334
335 Value boolExpr = maybeGetStringAsBooleanValue();
336 if(boolExpr != null) {
337 _expr = boolExpr;
338 } else {
339 BigDecimal bd = DefaultFunctions.maybeGetAsBigDecimal(_ctx, _expr);
340 if(bd != null) {
341 _expr = ValueSupport.toValue(bd);
342 } else {
343
344
345
346 Value maybe = DefaultFunctions.maybeGetAsDateTimeValue(
347 _ctx, _expr);
348 if(maybe != null) {
349 _expr = ValueSupport.toValue(maybe.getAsDouble(_ctx));
350 } else {
351
352
353 throw new EvalException("invalid number value");
354 }
355 }
356 }
357 } else {
358
359 _expr = ValueSupport.toValue(_expr.getAsDouble(_ctx));
360 }
361 }
362 return this;
363 }
364
365 private Value maybeGetStringAsBooleanValue() {
366
367 String val = getAsString();
368 if("true".equalsIgnoreCase(val)) {
369 return ValueSupport.TRUE_VAL;
370 }
371 if("false".equalsIgnoreCase(val)) {
372 return ValueSupport.FALSE_VAL;
373 }
374 return null;
375 }
376
377 public BigDecimal getAsBigDecimal() {
378 coerceToNumberValue();
379 return _expr.getAsBigDecimal(_ctx);
380 }
381
382 public LocalDateTime getAsLocalDateTime() {
383 coerceToDateTimeValue();
384 return _expr.getAsLocalDateTime(_ctx);
385 }
386
387 public boolean getAsBoolean() {
388
389
390
391
392 coerceToNumberValue();
393 return _expr.getAsBoolean(_ctx);
394 }
395
396 public String getAsString() {
397 return _expr.getAsString(_ctx);
398 }
399
400 public Value format(Fmt fmt) {
401 Value origExpr = _expr;
402 try {
403 return fmt.format(this);
404 } catch(EvalException ee) {
405
406
407 return origExpr;
408 }
409 }
410 }
411
412 private FormatUtil() {}
413
414
415
416
417 public static class StandaloneFormatter
418 {
419 private final Fmt _fmt;
420 private final Args _args;
421
422 private StandaloneFormatter(Fmt fmt, Args args) {
423 _fmt = fmt;
424 _args = args;
425 }
426
427 public Valueef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value format(Value expr) {
428 return _args.setExpr(expr).format(_fmt);
429 }
430 }
431
432 public static Value./com/healthmarketscience/jackcess/expr/Value.html#Value">Value format(EvalContext ctx, Value expr, String fmtStr,
433 int firstDay, int firstWeekType) {
434 Args args = new Args(ctx, expr, firstDay, firstWeekType);
435 return args.format(createFormat(args, fmtStr));
436 }
437
438 public static StandaloneFormatter createStandaloneFormatter(
439 EvalContext ctx, String fmtStr, int firstDay, int firstWeekType) {
440 Args args = new Args(ctx, null, firstDay, firstWeekType);
441 Fmt fmt = createFormat(args, fmtStr);
442 return new StandaloneFormatter(fmt, args);
443 }
444
445 private static Fmt createFormat(Args args, String fmtStr) {
446 Fmt predefFmt = PREDEF_FMTS.get(fmtStr);
447 if(predefFmt != null) {
448 return predefFmt;
449 }
450
451 if(StringUtils.isEmpty(fmtStr)) {
452 return DUMMY_FMT;
453 }
454
455
456
457
458
459 return parseCustomFormat(fmtStr, args);
460 }
461
462 private static Fmt parseCustomFormat(String fmtStr, Args args) {
463
464 ExprBuf buf = new ExprBuf(fmtStr, null);
465
466
467 byte curFormatType = determineFormatType(buf);
468
469
470 buf.reset(0);
471
472 switch(curFormatType) {
473 case FCT_GENERAL:
474 return parseCustomGeneralFormat(buf);
475 case FCT_DATE:
476 return parseCustomDateFormat(buf, args);
477 case FCT_NUMBER:
478 return parseCustomNumberFormat(buf, args);
479 case FCT_TEXT:
480 return parseCustomTextFormat(buf);
481 default:
482 throw new EvalException("Invalid format type " + curFormatType);
483 }
484 }
485
486 private static byte determineFormatType(ExprBuf buf) {
487
488 while(buf.hasNext()) {
489 char c = buf.next();
490 byte fmtType = getFormatCodeType(c);
491 switch(fmtType) {
492 case FCT_UNKNOWN:
493 case FCT_LITERAL:
494
495 break;
496 case FCT_GENERAL:
497 switch(c) {
498 case QUOTE_CHAR:
499 parseQuotedString(buf);
500 break;
501 case START_COLOR_CHAR:
502 parseColorString(buf);
503 break;
504 case ESCAPE_CHAR:
505 case FILL_ESCAPE_CHAR:
506 if(buf.hasNext()) {
507 buf.next();
508 }
509 break;
510 default:
511
512 }
513 break;
514 case FCT_DATE:
515 case FCT_NUMBER:
516 case FCT_TEXT:
517
518 return fmtType;
519 default:
520 throw new EvalException("Invalid format type " + fmtType);
521 }
522 }
523
524
525 return FCT_GENERAL;
526 }
527
528 private static Fmt parseCustomGeneralFormat(ExprBuf buf) {
529
530
531
532
533 StringBuilder sb = new StringBuilder();
534 String[] fmtStrs = new String[NUM_NF_FMTS];
535 int fmtIdx = 0;
536
537 BUF_LOOP:
538 while(buf.hasNext()) {
539 char c = buf.next();
540 int fmtType = getFormatCodeType(c);
541 switch(fmtType) {
542 case FCT_GENERAL:
543 switch(c) {
544 case LEFT_ALIGN_CHAR:
545
546 break;
547 case QUOTE_CHAR:
548 parseQuotedString(buf, sb);
549 break;
550 case START_COLOR_CHAR:
551
552 parseColorString(buf);
553 break;
554 case ESCAPE_CHAR:
555 if(buf.hasNext()) {
556 sb.append(buf.next());
557 }
558 break;
559 case FILL_ESCAPE_CHAR:
560
561
562 if(buf.hasNext()) {
563 buf.next();
564 }
565 break;
566 case CHOICE_SEP_CHAR:
567
568
569 if(fmtIdx == (NUM_NF_FMTS - 1)) {
570
571 break BUF_LOOP;
572 }
573 addCustomGeneralFormat(fmtStrs, fmtIdx++, sb);
574 break;
575 default:
576 sb.append(c);
577 }
578 break;
579 default:
580 sb.append(c);
581 }
582 }
583
584
585 while(fmtIdx < NUM_NF_FMTS) {
586 addCustomGeneralFormat(fmtStrs, fmtIdx++, sb);
587 }
588
589 return new CustomGeneralFmt(
590 ValueSupport.toValue(fmtStrs[NF_POS_IDX]),
591 ValueSupport.toValue(fmtStrs[NF_NEG_IDX]),
592 ValueSupport.toValue(fmtStrs[NF_ZERO_IDX]),
593 ValueSupport.toValue(fmtStrs[NF_NULL_IDX]));
594 }
595
596 private static void addCustomGeneralFormat(String[] fmtStrs, int fmtIdx,
597 StringBuilder sb)
598 {
599 addCustomNumberFormat(fmtStrs, NO_EXP_TYPES, NO_FMT_TYPES, NO_FMT_TYPES,
600 fmtIdx, sb);
601 }
602
603 private static Fmt parseCustomDateFormat(ExprBuf buf, Args args) {
604
605
606
607
608 boolean[] fmtState = new boolean[]{false, false};
609 List<DateFormatBuilder> dfbs = new ArrayList<>();
610
611 BUF_LOOP:
612 while(buf.hasNext()) {
613 char c = buf.next();
614 int fmtType = getFormatCodeType(c);
615 switch(fmtType) {
616 case FCT_GENERAL:
617 switch(c) {
618 case QUOTE_CHAR:
619 String str = parseQuotedString(buf);
620 dfbs.add((dtfb, argsParam, hasAmPmParam, dtType) ->
621 dtfb.appendLiteral(str));
622 break;
623 case START_COLOR_CHAR:
624
625 parseColorString(buf);
626 break;
627 case ESCAPE_CHAR:
628 if(buf.hasNext()) {
629 dfbs.add(buildLiteralCharDFB(buf.next()));
630 }
631 break;
632 case FILL_ESCAPE_CHAR:
633
634
635 if(buf.hasNext()) {
636 buf.next();
637 }
638 break;
639 case CHOICE_SEP_CHAR:
640
641
642 break BUF_LOOP;
643 default:
644 dfbs.add(buildLiteralCharDFB(c));
645 }
646 break;
647 case FCT_DATE:
648 parseCustomDateFormatPattern(c, buf, dfbs, fmtState);
649 break;
650 default:
651 dfbs.add(buildLiteralCharDFB(c));
652 }
653 }
654
655 boolean hasAmPm = fmtState[0];
656 boolean hasGeneralFormat = fmtState[1];
657 if(!hasGeneralFormat) {
658
659 DateTimeFormatter dtf = createDateTimeFormatter(dfbs, args, hasAmPm, null);
660 return new CustomFmt(argsParam -> ValueSupport.toValue(
661 dtf.format(argsParam.getAsLocalDateTime())));
662 }
663
664
665 DateTimeFormatter dateFmt = createDateTimeFormatter(dfbs, args, hasAmPm,
666 Value.Type.DATE);
667 DateTimeFormatter timeFmt = createDateTimeFormatter(dfbs, args, hasAmPm,
668 Value.Type.TIME);
669 DateTimeFormatter dtFmt = createDateTimeFormatter(dfbs, args, hasAmPm,
670 Value.Type.DATE_TIME);
671
672 return new CustomFmt(argsParam -> formatDateTime(
673 argsParam, dateFmt, timeFmt, dtFmt));
674 }
675
676 private static void parseCustomDateFormatPattern(
677 char c, ExprBuf buf, List<DateFormatBuilder> dfbs,
678 boolean[] fmtState) {
679
680 if((c == DT_LIT_COLON_CHAR) || (c == DT_LIT_SLASH_CHAR)) {
681
682 dfbs.add(buildLiteralCharDFB(c));
683 return;
684 }
685
686 StringBuilder sb = buf.getScratchBuffer();
687 sb.append(c);
688
689 char firstChar = c;
690 int firstPos = buf.curPos();
691 String bestMatchPat = sb.toString();
692
693 DateFormatBuilder bestMatch = DATE_FMT_BUILDERS.get(bestMatchPat);
694 int bestPos = firstPos;
695 while(buf.hasNext()) {
696 sb.append(buf.next());
697 String tmpPat = sb.toString();
698 DateFormatBuilder dfb = DATE_FMT_BUILDERS.get(tmpPat);
699 if(dfb == null) {
700
701 break;
702 }
703 if(dfb != PARTIAL_PREFIX) {
704
705 bestMatch = dfb;
706 bestPos = buf.curPos();
707 bestMatchPat = tmpPat;
708 }
709 }
710
711 if(bestMatch != PARTIAL_PREFIX) {
712
713
714 buf.reset(bestPos);
715 dfbs.add(bestMatch);
716
717 switch(firstChar) {
718 case 'a':
719 case 'A':
720
721 fmtState[0] = true;
722 break;
723 case 'c':
724
725 fmtState[1] = true;
726 break;
727 default:
728
729 }
730
731 } else {
732
733
734 buf.reset(firstPos);
735 dfbs.add(buildLiteralCharDFB(firstChar));
736 }
737 }
738
739 private static DateFormatBuilder buildLiteralCharDFB(char c) {
740 return (dtfb, args, hasAmPm, dtType) -> dtfb.appendLiteral(c);
741 }
742
743 private static DateTimeFormatter createDateTimeFormatter(
744 List<DateFormatBuilder> dfbs, Args args, boolean hasAmPm,
745 Value.Type dtType)
746 {
747 DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
748 dfbs.forEach(d -> d.build(dtfb, args, hasAmPm, dtType));
749 return dtfb.toFormatter(args._ctx.getTemporalConfig().getLocale());
750 }
751
752 private static Value formatDateTime(
753 Args args, DateTimeFormatter dateFmt,
754 DateTimeFormatter timeFmt, DateTimeFormatter dtFmt)
755 {
756 LocalDateTime ldt = args.getAsLocalDateTime();
757 DateTimeFormatter fmt = null;
758 switch(args._expr.getType()) {
759 case DATE:
760 fmt = dateFmt;
761 break;
762 case TIME:
763 fmt = timeFmt;
764 break;
765 default:
766 fmt = dtFmt;
767 }
768
769 return ValueSupport.toValue(fmt.format(ldt));
770 }
771
772 private static Fmt parseCustomNumberFormat(ExprBuf buf, Args args) {
773
774 StringBuilder sb = new StringBuilder();
775 String[] fmtStrs = new String[NUM_NF_FMTS];
776 int fmtIdx = 0;
777 StringBuilder pendingLiteral = new StringBuilder();
778 NumberFormatter.NotationType[] expTypes =
779 new NumberFormatter.NotationType[NUM_NF_FMTS];
780 boolean[] hasFmts = new boolean[NUM_NF_FMTS];
781 boolean[] hasReqDigit = new boolean[NUM_NF_FMTS];
782
783 BUF_LOOP:
784 while(buf.hasNext()) {
785 char c = buf.next();
786 int fmtType = getFormatCodeType(c);
787 switch(fmtType) {
788 case FCT_GENERAL:
789 switch(c) {
790 case LEFT_ALIGN_CHAR:
791
792 break;
793 case QUOTE_CHAR:
794 parseQuotedString(buf, pendingLiteral);
795 break;
796 case START_COLOR_CHAR:
797
798 parseColorString(buf);
799 break;
800 case ESCAPE_CHAR:
801 if(buf.hasNext()) {
802 pendingLiteral.append(buf.next());
803 }
804 break;
805 case FILL_ESCAPE_CHAR:
806
807
808 if(buf.hasNext()) {
809 buf.next();
810 }
811 break;
812 case CHOICE_SEP_CHAR:
813
814
815 if(fmtIdx == (NUM_NF_FMTS - 1)) {
816
817 break BUF_LOOP;
818 }
819 flushPendingNumberLiteral(pendingLiteral, sb);
820 addCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
821 fmtIdx++, sb);
822 break;
823 default:
824 pendingLiteral.append(c);
825 }
826 break;
827 case FCT_NUMBER:
828 hasFmts[fmtIdx] = true;
829 switch(c) {
830 case EXP_E_CHAR:
831 int signChar = buf.peekNext();
832 if((signChar == PLUS_CHAR) || (signChar == MINUS_CHAR)) {
833 buf.next();
834 expTypes[fmtIdx] = ((signChar == PLUS_CHAR) ?
835 NumberFormatter.NotationType.EXP_E_PLUS :
836 NumberFormatter.NotationType.EXP_E_MINUS);
837 flushPendingNumberLiteral(pendingLiteral, sb);
838 sb.append(EXP_E_CHAR);
839 } else {
840 pendingLiteral.append(c);
841 }
842 break;
843 case EXP_e_CHAR:
844 signChar = buf.peekNext();
845 if((signChar == PLUS_CHAR) || (signChar == MINUS_CHAR)) {
846 buf.next();
847 expTypes[fmtIdx] = ((signChar == PLUS_CHAR) ?
848 NumberFormatter.NotationType.EXP_e_PLUS :
849 NumberFormatter.NotationType.EXP_e_MINUS);
850 flushPendingNumberLiteral(pendingLiteral, sb);
851 sb.append(EXP_E_CHAR);
852 } else {
853 pendingLiteral.append(c);
854 }
855 break;
856 case REQ_DIGIT_CHAR:
857 hasReqDigit[fmtIdx] = true;
858 flushPendingNumberLiteral(pendingLiteral, sb);
859 sb.append(c);
860 break;
861 default:
862
863 flushPendingNumberLiteral(pendingLiteral, sb);
864 sb.append(c);
865 }
866 break;
867 default:
868 pendingLiteral.append(c);
869 }
870 }
871
872
873 while(fmtIdx < NUM_NF_FMTS) {
874 flushPendingNumberLiteral(pendingLiteral, sb);
875 addCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
876 fmtIdx++, sb);
877 }
878
879 return new CustomNumberFmt(
880 createCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
881 NF_POS_IDX, false, args, buf),
882 createCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
883 NF_NEG_IDX, false, args, buf),
884 createCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
885 NF_ZERO_IDX, true, args, buf),
886 createCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
887 NF_NULL_IDX, true, args, buf));
888 }
889
890 private static void addCustomNumberFormat(
891 String[] fmtStrs, NumberFormatter.NotationType[] expTypes,
892 boolean[] hasFmts, boolean[] hasReqDigit, int fmtIdx, StringBuilder sb)
893 {
894 if(sb.length() == 0) {
895
896 switch(fmtIdx) {
897 case NF_NEG_IDX:
898
899 sb.append('-').append(fmtStrs[NF_POS_IDX]);
900 expTypes[NF_NEG_IDX] = expTypes[NF_POS_IDX];
901 hasFmts[NF_NEG_IDX] = hasFmts[NF_POS_IDX];
902 hasReqDigit[NF_NEG_IDX] = hasReqDigit[NF_POS_IDX];
903 break;
904 case NF_ZERO_IDX:
905
906 sb.append(fmtStrs[NF_POS_IDX]);
907 expTypes[NF_ZERO_IDX] = expTypes[NF_POS_IDX];
908 hasFmts[NF_ZERO_IDX] = hasFmts[NF_POS_IDX];
909 hasReqDigit[NF_ZERO_IDX] = hasReqDigit[NF_POS_IDX];
910 break;
911 default:
912
913 }
914 }
915
916 fmtStrs[fmtIdx] = sb.toString();
917 sb.setLength(0);
918 }
919
920 private static void flushPendingNumberLiteral(
921 StringBuilder pendingLiteral, StringBuilder sb) {
922 if(pendingLiteral.length() == 0) {
923 return;
924 }
925
926 if((pendingLiteral.length() == 1) &&
927 pendingLiteral.charAt(0) == SINGLE_QUOTE_CHAR) {
928
929 sb.append(SINGLE_QUOTE_CHAR).append(SINGLE_QUOTE_CHAR);
930 pendingLiteral.setLength(0);
931 return;
932 }
933
934 sb.append(SINGLE_QUOTE_CHAR);
935 int startPos = sb.length();
936 sb.append(pendingLiteral);
937
938
939 for(int i = startPos; i < sb.length(); ++i) {
940 char c = sb.charAt(i);
941 if(c == SINGLE_QUOTE_CHAR) {
942 sb.insert(++i, SINGLE_QUOTE_CHAR);
943 }
944 }
945
946 sb.append(SINGLE_QUOTE_CHAR);
947 pendingLiteral.setLength(0);
948 }
949
950 private static BDFormat createCustomNumberFormat(
951 String[] fmtStrs, NumberFormatter.NotationType[] expTypes,
952 boolean[] hasFmts, boolean[] hasReqDigit, int fmtIdx,
953 boolean isZeroFmt, Args args, ExprBuf buf) {
954
955 String fmtStr = fmtStrs[fmtIdx];
956 if(!hasFmts[fmtIdx]) {
957
958 if(fmtStr.length() > 0) {
959
960 StringBuilder sb = buf.getScratchBuffer().append(fmtStr)
961 .deleteCharAt(fmtStr.length() - 1)
962 .deleteCharAt(0);
963 if(sb.length() > 0) {
964 for(int i = 0; i < sb.length(); ++i) {
965 if(sb.charAt(i) == SINGLE_QUOTE_CHAR) {
966
967 sb.deleteCharAt(++i);
968 }
969 }
970 } else {
971
972 sb.append(SINGLE_QUOTE_CHAR);
973 }
974 fmtStr = sb.toString();
975 }
976 return new LiteralBDFormat(fmtStr);
977 }
978
979 NumberFormatter.NotationType expType = expTypes[fmtIdx];
980 DecimalFormat df = args._ctx.createDecimalFormat(fmtStr);
981
982 if(df.getMaximumFractionDigits() > 0) {
983
984 df.setDecimalSeparatorAlwaysShown(true);
985 }
986
987 if(expType != null) {
988 NumberFormat nf = new NumberFormatter.ScientificFormat(df, expType);
989 if(isZeroFmt) {
990 return new LiteralBDFormat(nf.format(BigDecimal.ZERO));
991 }
992 return new BaseBDFormat(nf);
993 }
994
995 if(!hasReqDigit[fmtIdx]) {
996
997 df.setMinimumIntegerDigits(0);
998 }
999
1000 if(isZeroFmt) {
1001
1002 String zeroVal = df.format(BigDecimal.ZERO);
1003 if(!hasReqDigit[fmtIdx]) {
1004
1005
1006 int prefLen = df.getPositivePrefix().length();
1007 int len = zeroVal.length() - df.getPositiveSuffix().length();
1008 StringBuilder sb = buf.getScratchBuffer().append(zeroVal);
1009 for(int i = len - 1; i >= prefLen; --i) {
1010 if(sb.charAt(i) == '0') {
1011 sb.deleteCharAt(i);
1012 }
1013 }
1014 zeroVal = sb.toString();
1015 }
1016
1017 return new LiteralBDFormat(zeroVal);
1018 }
1019
1020 return new DecimalBDFormat(df);
1021 }
1022
1023 private static Fmt parseCustomTextFormat(ExprBuf buf) {
1024
1025 Fmt fmt = null;
1026
1027 List<BiConsumer<StringBuilder,CharSource>> subFmts = new ArrayList<>();
1028 int numPlaceholders = 0;
1029 boolean rightAligned = true;
1030 TextCase textCase = TextCase.NONE;
1031 StringBuilder pendingLiteral = new StringBuilder();
1032 boolean hasFmtChars = false;
1033
1034 BUF_LOOP:
1035 while(buf.hasNext()) {
1036 char c = buf.next();
1037 hasFmtChars = true;
1038 int fmtType = getFormatCodeType(c);
1039 switch(fmtType) {
1040 case FCT_GENERAL:
1041 switch(c) {
1042 case LEFT_ALIGN_CHAR:
1043 rightAligned = false;
1044 break;
1045 case QUOTE_CHAR:
1046 parseQuotedString(buf, pendingLiteral);
1047 break;
1048 case START_COLOR_CHAR:
1049
1050 parseColorString(buf);
1051 break;
1052 case ESCAPE_CHAR:
1053 if(buf.hasNext()) {
1054 pendingLiteral.append(buf.next());
1055 }
1056 break;
1057 case FILL_ESCAPE_CHAR:
1058
1059
1060 if(buf.hasNext()) {
1061 buf.next();
1062 }
1063 break;
1064 case CHOICE_SEP_CHAR:
1065
1066
1067 if(fmt != null) {
1068
1069 break BUF_LOOP;
1070 }
1071 flushPendingTextLiteral(pendingLiteral, subFmts);
1072 fmt = new CharSourceFmt(subFmts, numPlaceholders, rightAligned,
1073 textCase);
1074
1075 subFmts = new ArrayList<>();
1076 numPlaceholders = 0;
1077 rightAligned = true;
1078 textCase = TextCase.NONE;
1079 hasFmtChars = false;
1080 break;
1081 default:
1082 pendingLiteral.append(c);
1083 }
1084 break;
1085 case FCT_TEXT:
1086 switch(c) {
1087 case REQ_PLACEHOLDER_CHAR:
1088 flushPendingTextLiteral(pendingLiteral, subFmts);
1089 ++numPlaceholders;
1090 subFmts.add((sb,cs) -> {
1091 int tmp = cs.next();
1092 sb.append((tmp != NO_CHAR) ? (char)tmp : ' ');
1093 });
1094 break;
1095 case OPT_PLACEHOLDER_CHAR:
1096 flushPendingTextLiteral(pendingLiteral, subFmts);
1097 ++numPlaceholders;
1098 subFmts.add((sb,cs) -> {
1099 int tmp = cs.next();
1100 if(tmp != NO_CHAR) {
1101 sb.append((char)tmp);
1102 }
1103 });
1104 break;
1105 case TO_UPPER_CHAR:
1106
1107 textCase = ((textCase == TextCase.LOWER) ?
1108 TextCase.NONE : TextCase.UPPER);
1109 break;
1110 case TO_LOWER_CHAR:
1111
1112 textCase = ((textCase == TextCase.UPPER) ?
1113 TextCase.NONE : TextCase.LOWER);
1114 break;
1115 default:
1116 pendingLiteral.append(c);
1117 }
1118 break;
1119 default:
1120 pendingLiteral.append(c);
1121 }
1122 }
1123
1124 flushPendingTextLiteral(pendingLiteral, subFmts);
1125
1126 Fmt emptyFmt = null;
1127 if(fmt == null) {
1128 fmt = new CharSourceFmt(subFmts, numPlaceholders, rightAligned,
1129 textCase);
1130 emptyFmt = NULL_FMT;
1131 } else {
1132 emptyFmt = (hasFmtChars ?
1133 new CharSourceFmt(subFmts, numPlaceholders, rightAligned,
1134 textCase) :
1135 NULL_FMT);
1136 }
1137
1138 return new CustomFmt(fmt, emptyFmt);
1139 }
1140
1141 private static void flushPendingTextLiteral(
1142 StringBuilder pendingLiteral,
1143 List<BiConsumer<StringBuilder,CharSource>> subFmts) {
1144 if(pendingLiteral.length() == 0) {
1145 return;
1146 }
1147
1148 String literal = pendingLiteral.toString();
1149 pendingLiteral.setLength(0);
1150 subFmts.add((sb, cs) -> sb.append(literal));
1151 }
1152
1153 public static String createNumberFormatPattern(
1154 NumPatternType numPatType, int numDecDigits, boolean incLeadDigit,
1155 boolean negParens, int numGroupDigits) {
1156
1157 StringBuilder fmt = new StringBuilder();
1158
1159 numPatType.appendPrefix(fmt);
1160
1161 if(numGroupDigits > 0) {
1162 fmt.append("#,");
1163 DefaultTextFunctions.nchars(fmt, numGroupDigits - 1, '#');
1164 }
1165
1166 fmt.append(incLeadDigit ? "0" : "#");
1167 if(numDecDigits > 0) {
1168 fmt.append(".");
1169 DefaultTextFunctions.nchars(fmt, numDecDigits, '0');
1170 }
1171
1172 numPatType.appendSuffix(fmt);
1173
1174 if(negParens) {
1175
1176
1177 String mainPat = fmt.toString();
1178 fmt.append(";(").append(mainPat).append(")");
1179 }
1180
1181 return fmt.toString();
1182 }
1183
1184 private static byte getFormatCodeType(char c) {
1185 if((c >= 0) && (c < 127)) {
1186 return FORMAT_CODE_TYPES[c];
1187 }
1188 return FCT_UNKNOWN;
1189 }
1190
1191 private static void setFormatCodeTypes(String chars, byte type) {
1192 for(char c : chars.toCharArray()) {
1193 FORMAT_CODE_TYPES[c] = type;
1194 }
1195 }
1196
1197 private static String parseQuotedString(ExprBuf buf) {
1198 return ExpressionTokenizer.parseStringUntil(buf, null, QUOTE_CHAR, true);
1199 }
1200
1201 private static void parseQuotedString(ExprBuf buf, StringBuilder sb) {
1202 ExpressionTokenizer.parseStringUntil(buf, null, QUOTE_CHAR, true, sb);
1203 }
1204
1205 private static String parseColorString(ExprBuf buf) {
1206 return ExpressionTokenizer.parseStringUntil(
1207 buf, START_COLOR_CHAR, END_COLOR_CHAR, false);
1208 }
1209
1210 private static void fillInPartialPrefixes() {
1211 List<String> validPrefixes = new ArrayList<>(DATE_FMT_BUILDERS.keySet());
1212 for(String validPrefix : validPrefixes) {
1213 int len = validPrefix.length();
1214 while(len > 1) {
1215 --len;
1216 validPrefix = validPrefix.substring(0, len);
1217 DATE_FMT_BUILDERS.putIfAbsent(validPrefix, PARTIAL_PREFIX);
1218 }
1219 }
1220 }
1221
1222 private static void putPredefFormat(String key, Fmt fmt) {
1223
1224 Fmt wrapFmt = args -> (args.isNullOrEmptyString() ?
1225 ValueSupport.EMPTY_STR_VAL :
1226 fmt.format(args));
1227 PREDEF_FMTS.put(key, wrapFmt);
1228 }
1229
1230 private static final class PredefDateFmt implements Fmt
1231 {
1232 private final TemporalConfig.Type _type;
1233
1234 private PredefDateFmt(TemporalConfig.Type type) {
1235 _type = type;
1236 }
1237
1238 @Override
1239 public Value format(Args args) {
1240 DateTimeFormatter dtf = args._ctx.createDateFormatter(
1241 args._ctx.getTemporalConfig().getDateTimeFormat(_type));
1242 return ValueSupport.toValue(dtf.format(args.getAsLocalDateTime()));
1243 }
1244 }
1245
1246 private static final class PredefBoolFmt implements Fmt
1247 {
1248 private final Value _trueVal;
1249 private final Value _falseVal;
1250
1251 private PredefBoolFmt(String trueStr, String falseStr) {
1252 _trueVal = ValueSupport.toValue(trueStr);
1253 _falseVal = ValueSupport.toValue(falseStr);
1254 }
1255
1256 @Override
1257 public Value format(Args args) {
1258 return(args.getAsBoolean() ? _trueVal : _falseVal);
1259 }
1260 }
1261
1262 private static abstract class BaseNumberFmt implements Fmt
1263 {
1264 @Override
1265 public Value format(Args args) {
1266 NumberFormat df = getNumberFormat(args);
1267 return ValueSupport.toValue(df.format(args.getAsBigDecimal()));
1268 }
1269
1270 protected abstract NumberFormat getNumberFormat(Args args);
1271 }
1272
1273 private static final class PredefNumberFmt extends BaseNumberFmt
1274 {
1275 private final NumericConfig.Type _type;
1276
1277 private PredefNumberFmt(NumericConfig.Type type) {
1278 _type = type;
1279 }
1280
1281 @Override
1282 protected NumberFormat getNumberFormat(Args args) {
1283 return args._ctx.createDecimalFormat(
1284 args._ctx.getNumericConfig().getNumberFormat(_type));
1285 }
1286 }
1287
1288 private static final class ScientificPredefNumberFmt extends BaseNumberFmt
1289 {
1290 @Override
1291 protected NumberFormat getNumberFormat(Args args) {
1292 NumberFormat df = args._ctx.createDecimalFormat(
1293 args._ctx.getNumericConfig().getNumberFormat(
1294 NumericConfig.Type.SCIENTIFIC));
1295 df = new NumberFormatter.ScientificFormat(df);
1296 return df;
1297 }
1298 }
1299
1300 private static final class SimpleDFB implements DateFormatBuilder
1301 {
1302 private final String _pat;
1303
1304 private SimpleDFB(String pat) {
1305 _pat = pat;
1306 }
1307 @Override
1308 public void build(DateTimeFormatterBuilder dtfb, Args args,
1309 boolean hasAmPm, Value.Type dtType) {
1310 dtfb.appendPattern(_pat);
1311 }
1312 }
1313
1314 private static final class HourlyDFB implements DateFormatBuilder
1315 {
1316 private final String _pat12;
1317 private final String _pat24;
1318
1319 private HourlyDFB(String pat12, String pat24) {
1320 _pat12 = pat12;
1321 _pat24 = pat24;
1322 }
1323 @Override
1324 public void build(DateTimeFormatterBuilder dtfb, Args args,
1325 boolean hasAmPm, Value.Type dtTypePm) {
1326
1327
1328
1329 dtfb.appendPattern(hasAmPm ? _pat12 : _pat24);
1330 }
1331 }
1332
1333 private static final class PredefDFB implements DateFormatBuilder
1334 {
1335 private final TemporalConfig.Type _type;
1336
1337 private PredefDFB(TemporalConfig.Type type) {
1338 _type = type;
1339 }
1340 @Override
1341 public void build(DateTimeFormatterBuilder dtfb, Args args,
1342 boolean hasAmPm, Value.Type dtType) {
1343 dtfb.appendPattern(args._ctx.getTemporalConfig().getDateTimeFormat(_type));
1344 }
1345 }
1346
1347 private static abstract class WeekBasedDFB implements DateFormatBuilder
1348 {
1349 @Override
1350 public void build(DateTimeFormatterBuilder dtfb, Args args,
1351 boolean hasAmPm, Value.Type dtType) {
1352 dtfb.appendValue(getField(DefaultDateFunctions.weekFields(
1353 args._firstDay, args._firstWeekType)));
1354 }
1355
1356 protected abstract TemporalField getField(WeekFields weekFields);
1357 }
1358
1359 private static final class AmPmDFB extends AbstractMap<Long,String>
1360 implements DateFormatBuilder
1361 {
1362 private static final Long ZERO_KEY = 0L;
1363 private final String _am;
1364 private final String _pm;
1365
1366 private AmPmDFB(String am, String pm) {
1367 _am = am;
1368 _pm = pm;
1369 }
1370 @Override
1371 public void build(DateTimeFormatterBuilder dtfb, Args args,
1372 boolean hasAmPm, Value.Type dtType) {
1373 dtfb.appendText(ChronoField.AMPM_OF_DAY, this);
1374 }
1375 @Override
1376 public int size() {
1377 return 2;
1378 }
1379 @Override
1380 public String get(Object key) {
1381 return(ZERO_KEY.equals(key) ? _am : _pm);
1382 }
1383 @Override
1384 public Set<Map.Entry<Long,String>> entrySet() {
1385 return new AbstractSet<Map.Entry<Long,String>>() {
1386 @Override
1387 public int size() {
1388 return 2;
1389 }
1390 @Override
1391 public Iterator<Map.Entry<Long,String>> iterator() {
1392 return Arrays.<Map.Entry<Long,String>>asList(
1393 new AbstractMap.SimpleImmutableEntry<Long,String>(0L, _am),
1394 new AbstractMap.SimpleImmutableEntry<Long,String>(1L, _pm))
1395 .iterator();
1396 }
1397 };
1398 }
1399 }
1400
1401 private static final class CustomFmt implements Fmt
1402 {
1403 private final Fmt _fmt;
1404 private final Fmt _emptyFmt;
1405
1406 private CustomFmt(Fmt fmt) {
1407 this(fmt, NULL_FMT);
1408 }
1409
1410 private CustomFmt(Fmt fmt, Fmt emptyFmt) {
1411 _fmt = fmt;
1412 _emptyFmt = emptyFmt;
1413 }
1414
1415 @Override
1416 public Value format(Args args) {
1417 Fmt fmt = _fmt;
1418 if(args.maybeCoerceToEmptyString()) {
1419 fmt = _emptyFmt;
1420 }
1421 return fmt.format(args);
1422 }
1423 }
1424
1425 private static final class CharSourceFmt implements Fmt
1426 {
1427 private final List<BiConsumer<StringBuilder,CharSource>> _subFmts;
1428 private final int _numPlaceholders;
1429 private final boolean _rightAligned;
1430 private final TextCase _textCase;
1431
1432 private CharSourceFmt(List<BiConsumer<StringBuilder,CharSource>> subFmts,
1433 int numPlaceholders, boolean rightAligned,
1434 TextCase textCase) {
1435 _subFmts = subFmts;
1436 _numPlaceholders = numPlaceholders;
1437 _rightAligned = rightAligned;
1438 _textCase = textCase;
1439 }
1440
1441 @Override
1442 public Value format(Args args) {
1443 CharSource cs = new CharSource(args.getAsString(), _numPlaceholders,
1444 _rightAligned, _textCase);
1445 StringBuilder sb = new StringBuilder();
1446 _subFmts.stream().forEach(fmt -> fmt.accept(sb, cs));
1447 cs.appendRemaining(sb);
1448 return ValueSupport.toValue(sb.toString());
1449 }
1450 }
1451
1452 private static final class CharSource
1453 {
1454 private int _prefLen;
1455 private final String _str;
1456 private int _strPos;
1457 private final TextCase _textCase;
1458
1459 private CharSource(String str, int len, boolean rightAligned,
1460 TextCase textCase) {
1461 _str = str;
1462 _textCase = textCase;
1463 int strLen = str.length();
1464 if(len > strLen) {
1465 if(rightAligned) {
1466 _prefLen = len - strLen;
1467 }
1468 } else if(len < strLen) {
1469
1470
1471 if(!rightAligned) {
1472 _strPos = strLen - len;
1473 }
1474 }
1475 }
1476
1477 public int next() {
1478 if(_prefLen > 0) {
1479 --_prefLen;
1480 return NO_CHAR;
1481 }
1482 if(_strPos < _str.length()) {
1483 return _textCase.apply(_str.charAt(_strPos++));
1484 }
1485 return NO_CHAR;
1486 }
1487
1488 public void appendRemaining(StringBuilder sb) {
1489 int strLen = _str.length();
1490 while(_strPos < strLen) {
1491 sb.append(_textCase.apply(_str.charAt(_strPos++)));
1492 }
1493 }
1494 }
1495
1496 private static abstract class BaseCustomNumberFmt implements Fmt
1497 {
1498 @Override
1499 public Value format(Args args) {
1500 if(args._expr.isNull()) {
1501 return formatNull(args);
1502 }
1503
1504 BigDecimal bd = args.getAsBigDecimal();
1505 int cmp = BigDecimal.ZERO.compareTo(bd);
1506
1507 return ((cmp < 0) ? formatPos(bd, args) :
1508 ((cmp > 0) ? formatNeg(bd, args) :
1509 formatZero(bd, args)));
1510 }
1511
1512 protected abstract Value formatNull(Args args);
1513 protected abstract Value formatPos(BigDecimal bd, Args args);
1514 protected abstract Value formatNeg(BigDecimal bd, Args args);
1515 protected abstract Value formatZero(BigDecimal bd, Args args);
1516 }
1517
1518 private static final class CustomGeneralFmt extends BaseCustomNumberFmt
1519 {
1520 private final Value _posVal;
1521 private final Value _negVal;
1522 private final Value _zeroVal;
1523 private final Value _nullVal;
1524
1525 private CustomGeneralFmt(Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value posVal, Value negVal,
1526 Value="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value zeroVal, Value nullVal) {
1527 _posVal = posVal;
1528 _negVal = negVal;
1529 _zeroVal = zeroVal;
1530 _nullVal = nullVal;
1531 }
1532
1533 @Override
1534 protected Value formatNull(Args args) {
1535 return _nullVal;
1536 }
1537 @Override
1538 protected Value formatPos(BigDecimal bd, Args args) {
1539 return _posVal;
1540 }
1541 @Override
1542 protected Value formatNeg(BigDecimal bd, Args args) {
1543 return _negVal;
1544 }
1545 @Override
1546 protected Value formatZero(BigDecimal bd, Args args) {
1547 return _zeroVal;
1548 }
1549 }
1550
1551 private static final class CustomNumberFmt extends BaseCustomNumberFmt
1552 {
1553 private final BDFormat _posFmt;
1554 private final BDFormat _negFmt;
1555 private final BDFormat _zeroFmt;
1556 private final BDFormat _nullFmt;
1557
1558 private CustomNumberFmt(BDFormat posFmt, BDFormat negFmt,
1559 BDFormat zeroFmt, BDFormat nullFmt) {
1560 _posFmt = posFmt;
1561 _negFmt = negFmt;
1562 _zeroFmt = zeroFmt;
1563 _nullFmt = nullFmt;
1564 }
1565
1566 private Value formatMaybeZero(BigDecimal bd, BDFormat fmt) {
1567
1568
1569
1570
1571 int maxDecDigits = fmt.getMaxDecimalDigits();
1572 if(maxDecDigits < bd.scale()) {
1573 bd = bd.setScale(maxDecDigits, NumberFormatter.ROUND_MODE);
1574 if(BigDecimal.ZERO.compareTo(bd) == 0) {
1575
1576 fmt = _zeroFmt;
1577 }
1578 }
1579
1580 return ValueSupport.toValue(fmt.format(bd));
1581 }
1582
1583 @Override
1584 protected Value formatNull(Args args) {
1585 return ValueSupport.toValue(_nullFmt.format(BigDecimal.ZERO));
1586 }
1587 @Override
1588 protected Value formatPos(BigDecimal bd, Args args) {
1589 return formatMaybeZero(bd, _posFmt);
1590 }
1591 @Override
1592 protected Value formatNeg(BigDecimal bd, Args args) {
1593 return formatMaybeZero(bd.negate(), _negFmt);
1594 }
1595 @Override
1596 protected Value formatZero(BigDecimal bd, Args args) {
1597 return ValueSupport.toValue(_zeroFmt.format(bd));
1598 }
1599 }
1600
1601 private static abstract class BDFormat
1602 {
1603 public int getMaxDecimalDigits() {
1604 return Integer.MAX_VALUE;
1605 }
1606
1607 public abstract String format(BigDecimal bd);
1608 }
1609
1610 private static final class LiteralBDFormat extends BDFormat
1611 {
1612 private final String _str;
1613
1614 private LiteralBDFormat(String str) {
1615 _str = str;
1616 }
1617
1618 @Override
1619 public String format(BigDecimal bd) {
1620 return _str;
1621 }
1622 }
1623
1624 private static class BaseBDFormat extends BDFormat
1625 {
1626 private final NumberFormat _nf;
1627
1628 private BaseBDFormat(NumberFormat nf) {
1629 _nf = nf;
1630 }
1631
1632 @Override
1633 public String format(BigDecimal bd) {
1634 return _nf.format(bd);
1635 }
1636 }
1637
1638 private static final class DecimalBDFormat extends BaseBDFormat
1639 {
1640 private final int _maxDecDigits;
1641
1642 private DecimalBDFormat(DecimalFormat df) {
1643 super(df);
1644
1645 int maxDecDigits = df.getMaximumFractionDigits();
1646 int mult = df.getMultiplier();
1647 while(mult > 1) {
1648 ++maxDecDigits;
1649 mult /= 10;
1650 }
1651 _maxDecDigits = maxDecDigits;
1652 }
1653
1654 @Override
1655 public int getMaxDecimalDigits() {
1656 return _maxDecDigits;
1657 }
1658 }
1659 }