You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FormatUtil.java 47KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540
  1. /*
  2. Copyright (c) 2018 James Ahlborn
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package com.healthmarketscience.jackcess.impl.expr;
  14. import java.math.BigDecimal;
  15. import java.text.DecimalFormat;
  16. import java.text.FieldPosition;
  17. import java.text.NumberFormat;
  18. import java.text.ParsePosition;
  19. import java.time.LocalDateTime;
  20. import java.time.format.DateTimeFormatter;
  21. import java.time.format.DateTimeFormatterBuilder;
  22. import java.time.temporal.ChronoField;
  23. import java.time.temporal.TemporalField;
  24. import java.time.temporal.WeekFields;
  25. import java.util.AbstractMap;
  26. import java.util.AbstractSet;
  27. import java.util.ArrayList;
  28. import java.util.Arrays;
  29. import java.util.HashMap;
  30. import java.util.Iterator;
  31. import java.util.List;
  32. import java.util.Map;
  33. import java.util.Set;
  34. import java.util.function.BiConsumer;
  35. import com.healthmarketscience.jackcess.expr.EvalContext;
  36. import com.healthmarketscience.jackcess.expr.EvalException;
  37. import com.healthmarketscience.jackcess.expr.NumericConfig;
  38. import com.healthmarketscience.jackcess.expr.TemporalConfig;
  39. import com.healthmarketscience.jackcess.expr.Value;
  40. import static com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.ExprBuf;
  41. /**
  42. *
  43. * @author James Ahlborn
  44. */
  45. public class FormatUtil
  46. {
  47. public enum NumPatternType {
  48. GENERAL,
  49. CURRENCY {
  50. @Override
  51. protected void appendPrefix(StringBuilder fmt) {
  52. fmt.append('\u00A4');
  53. }
  54. @Override
  55. protected boolean useParensForNegatives(NumericConfig cfg) {
  56. return cfg.useParensForCurrencyNegatives();
  57. }
  58. },
  59. EURO {
  60. @Override
  61. protected void appendPrefix(StringBuilder fmt) {
  62. fmt.append('\u20AC');
  63. }
  64. @Override
  65. protected boolean useParensForNegatives(NumericConfig cfg) {
  66. return cfg.useParensForCurrencyNegatives();
  67. }
  68. },
  69. PERCENT {
  70. @Override
  71. protected void appendSuffix(StringBuilder fmt) {
  72. fmt.append('%');
  73. }
  74. },
  75. SCIENTIFIC {
  76. @Override
  77. protected void appendSuffix(StringBuilder fmt) {
  78. fmt.append("E0");
  79. }
  80. };
  81. protected void appendPrefix(StringBuilder fmt) {}
  82. protected void appendSuffix(StringBuilder fmt) {}
  83. protected boolean useParensForNegatives(NumericConfig cfg) {
  84. return cfg.useParensForNegatives();
  85. }
  86. }
  87. private enum TextCase {
  88. NONE,
  89. UPPER {
  90. @Override public char apply(char c) {
  91. return Character.toUpperCase(c);
  92. }
  93. },
  94. LOWER {
  95. @Override public char apply(char c) {
  96. return Character.toLowerCase(c);
  97. }
  98. };
  99. public char apply(char c) {
  100. return c;
  101. }
  102. }
  103. private static final Map<String,Fmt> PREDEF_FMTS = new HashMap<String,Fmt>();
  104. static {
  105. PREDEF_FMTS.put("General Date", args -> ValueSupport.toValue(
  106. args.coerceToDateTimeValue().getAsString()));
  107. PREDEF_FMTS.put("Long Date",
  108. new PredefDateFmt(TemporalConfig.Type.LONG_DATE));
  109. PREDEF_FMTS.put("Medium Date",
  110. new PredefDateFmt(TemporalConfig.Type.MEDIUM_DATE));
  111. PREDEF_FMTS.put("Short Date",
  112. new PredefDateFmt(TemporalConfig.Type.SHORT_DATE));
  113. PREDEF_FMTS.put("Long Time",
  114. new PredefDateFmt(TemporalConfig.Type.LONG_TIME));
  115. PREDEF_FMTS.put("Medium Time",
  116. new PredefDateFmt(TemporalConfig.Type.MEDIUM_TIME));
  117. PREDEF_FMTS.put("Short Time",
  118. new PredefDateFmt(TemporalConfig.Type.SHORT_TIME));
  119. PREDEF_FMTS.put("General Number", args -> ValueSupport.toValue(
  120. args.coerceToNumberValue().getAsString()));
  121. PREDEF_FMTS.put("Currency",
  122. new PredefNumberFmt(NumericConfig.Type.CURRENCY));
  123. PREDEF_FMTS.put("Euro", new PredefNumberFmt(NumericConfig.Type.EURO));
  124. PREDEF_FMTS.put("Fixed",
  125. new PredefNumberFmt(NumericConfig.Type.FIXED));
  126. PREDEF_FMTS.put("Standard",
  127. new PredefNumberFmt(NumericConfig.Type.STANDARD));
  128. PREDEF_FMTS.put("Percent",
  129. new PredefNumberFmt(NumericConfig.Type.PERCENT));
  130. PREDEF_FMTS.put("Scientific", new ScientificPredefNumberFmt());
  131. PREDEF_FMTS.put("True/False", new PredefBoolFmt("True", "False"));
  132. PREDEF_FMTS.put("Yes/No", new PredefBoolFmt("Yes", "No"));
  133. PREDEF_FMTS.put("On/Off", new PredefBoolFmt("On", "Off"));
  134. }
  135. private static final Fmt NULL_FMT = args -> ValueSupport.EMPTY_STR_VAL;
  136. private static final char QUOTE_CHAR = '"';
  137. private static final char ESCAPE_CHAR = '\\';
  138. private static final char LEFT_ALIGN_CHAR = '!';
  139. private static final char START_COLOR_CHAR = '[';
  140. private static final char END_COLOR_CHAR = ']';
  141. private static final char CHOICE_SEP_CHAR = ';';
  142. // this only seems to be useful if you have fixed length string fields which
  143. // isn't a normal thing in ms access
  144. private static final char FILL_ESCAPE_CHAR = '*';
  145. private static final char REQ_PLACEHOLDER_CHAR = '@';
  146. private static final char OPT_PLACEHOLDER_CHAR = '&';
  147. private static final char TO_UPPER_CHAR = '>';
  148. private static final char TO_LOWER_CHAR = '<';
  149. private static final char DT_LIT_COLON_CHAR = ':';
  150. private static final char DT_LIT_SLASH_CHAR = '/';
  151. private static final char SINGLE_QUOTE_CHAR = '\'';
  152. private static final char EXP_E_CHAR = 'E';
  153. private static final char EXP_e_CHAR = 'e';
  154. private static final char PLUS_CHAR = '+';
  155. private static final char MINUS_CHAR = '-';
  156. private static final int NO_CHAR = -1;
  157. private static final byte FCT_UNKNOWN = 0;
  158. private static final byte FCT_LITERAL = 1;
  159. private static final byte FCT_GENERAL = 2;
  160. private static final byte FCT_DATE = 3;
  161. private static final byte FCT_NUMBER = 4;
  162. private static final byte FCT_TEXT = 5;
  163. private static final byte[] FORMAT_CODE_TYPES = new byte[127];
  164. static {
  165. setFormatCodeTypes(" $+-()", FCT_LITERAL);
  166. setFormatCodeTypes("\"!*\\[];", FCT_GENERAL);
  167. setFormatCodeTypes(":/cdwmqyhnstampmAMPM", FCT_DATE);
  168. setFormatCodeTypes(".,0#%Ee", FCT_NUMBER);
  169. setFormatCodeTypes("@&<>", FCT_TEXT);
  170. }
  171. @FunctionalInterface
  172. interface Fmt {
  173. public Value format(Args args);
  174. }
  175. @FunctionalInterface
  176. interface DateFormatBuilder {
  177. public void build(DateTimeFormatterBuilder dtfb, Args args,
  178. boolean hasAmPm, Value.Type dtType);
  179. }
  180. private static final DateFormatBuilder PARTIAL_PREFIX =
  181. (dtfb, args, hasAmPm, dtType) -> {
  182. throw new UnsupportedOperationException();
  183. };
  184. private static final Map<String,DateFormatBuilder> DATE_FMT_BUILDERS =
  185. new HashMap<>();
  186. static {
  187. DATE_FMT_BUILDERS.put("c",
  188. (dtfb, args, hasAmPm, dtType) ->
  189. dtfb.append(ValueSupport.getDateFormatForType(
  190. args._ctx, dtType)));
  191. DATE_FMT_BUILDERS.put("d", new SimpleDFB("d"));
  192. DATE_FMT_BUILDERS.put("dd", new SimpleDFB("dd"));
  193. DATE_FMT_BUILDERS.put("ddd", new SimpleDFB("eee"));
  194. DATE_FMT_BUILDERS.put("dddd", new SimpleDFB("eeee"));
  195. DATE_FMT_BUILDERS.put("ddddd", new PredefDFB(TemporalConfig.Type.SHORT_DATE));
  196. DATE_FMT_BUILDERS.put("dddddd", new PredefDFB(TemporalConfig.Type.LONG_DATE));
  197. DATE_FMT_BUILDERS.put("w", new WeekBasedDFB() {
  198. @Override
  199. protected TemporalField getField(WeekFields weekFields) {
  200. return weekFields.dayOfWeek();
  201. }
  202. });
  203. DATE_FMT_BUILDERS.put("ww", new WeekBasedDFB() {
  204. @Override
  205. protected TemporalField getField(WeekFields weekFields) {
  206. return weekFields.weekOfWeekBasedYear();
  207. }
  208. });
  209. DATE_FMT_BUILDERS.put("m", new SimpleDFB("L"));
  210. DATE_FMT_BUILDERS.put("mm", new SimpleDFB("LL"));
  211. DATE_FMT_BUILDERS.put("mmm", new SimpleDFB("MMM"));
  212. DATE_FMT_BUILDERS.put("mmmm", new SimpleDFB("MMMM"));
  213. DATE_FMT_BUILDERS.put("q", new SimpleDFB("Q"));
  214. DATE_FMT_BUILDERS.put("y", new SimpleDFB("D"));
  215. DATE_FMT_BUILDERS.put("yy", new SimpleDFB("yy"));
  216. DATE_FMT_BUILDERS.put("yyyy", new SimpleDFB("yyyy"));
  217. DATE_FMT_BUILDERS.put("h", new HourlyDFB("h", "H"));
  218. DATE_FMT_BUILDERS.put("hh", new HourlyDFB("hh", "HH"));
  219. DATE_FMT_BUILDERS.put("n", new SimpleDFB("m"));
  220. DATE_FMT_BUILDERS.put("nn", new SimpleDFB("mm"));
  221. DATE_FMT_BUILDERS.put("s", new SimpleDFB("s"));
  222. DATE_FMT_BUILDERS.put("ss", new SimpleDFB("ss"));
  223. DATE_FMT_BUILDERS.put("ttttt", new PredefDFB(TemporalConfig.Type.LONG_TIME));
  224. DATE_FMT_BUILDERS.put("AM/PM", new AmPmDFB("AM", "PM"));
  225. DATE_FMT_BUILDERS.put("am/pm", new AmPmDFB("am", "pm"));
  226. DATE_FMT_BUILDERS.put("A/P", new AmPmDFB("A", "P"));
  227. DATE_FMT_BUILDERS.put("a/p", new AmPmDFB("a", "p"));
  228. DATE_FMT_BUILDERS.put("AMPM",
  229. (dtfb, args, hasAmPm, dtType) -> {
  230. String[] amPmStrs = args._ctx.getTemporalConfig().getAmPmStrings();
  231. new AmPmDFB(amPmStrs[0], amPmStrs[1]).build(dtfb, args, hasAmPm, dtType);
  232. }
  233. );
  234. fillInPartialPrefixes();
  235. }
  236. private static final int NF_POS_IDX = 0;
  237. private static final int NF_NEG_IDX = 1;
  238. private static final int NF_ZERO_IDX = 2;
  239. private static final int NF_NULL_IDX = 3;
  240. private static final int NUM_NF_FMTS = 4;
  241. private static final NumberFormatter.NotationType[] NO_EXP_TYPES =
  242. new NumberFormatter.NotationType[NUM_NF_FMTS];
  243. private static final boolean[] NO_FMT_TYPES = new boolean[NUM_NF_FMTS];
  244. private static final class Args
  245. {
  246. private final EvalContext _ctx;
  247. private Value _expr;
  248. private final int _firstDay;
  249. private final int _firstWeekType;
  250. private Args(EvalContext ctx, Value expr, int firstDay, int firstWeekType) {
  251. _ctx = ctx;
  252. _expr = expr;
  253. _firstDay = firstDay;
  254. _firstWeekType = firstWeekType;
  255. }
  256. public boolean isNullOrEmptyString() {
  257. return(_expr.isNull() ||
  258. // only a string value could ever be an empty string
  259. (_expr.getType().isString() && getAsString().isEmpty()));
  260. }
  261. public boolean maybeCoerceToEmptyString() {
  262. if(isNullOrEmptyString()) {
  263. // ensure that we have a non-null value when formatting (null acts
  264. // like empty string)
  265. _expr = ValueSupport.EMPTY_STR_VAL;
  266. return true;
  267. }
  268. return false;
  269. }
  270. public Args coerceToDateTimeValue() {
  271. if(!_expr.getType().isTemporal()) {
  272. // format coerces boolean strings to numbers
  273. Value boolExpr = null;
  274. if(_expr.getType().isString() &&
  275. ((boolExpr = maybeGetStringAsBooleanValue()) != null)) {
  276. _expr = boolExpr;
  277. }
  278. // StringValue already handles most String -> Number -> Date/Time, so
  279. // most other convertions work here (and failures are thrown so that
  280. // default handling kicks in)
  281. _expr = _expr.getAsDateTimeValue(_ctx);
  282. }
  283. return this;
  284. }
  285. public Args coerceToNumberValue() {
  286. if(!_expr.getType().isNumeric()) {
  287. if(_expr.getType().isString()) {
  288. // format coerces "true"/"false" to boolean values
  289. Value boolExpr = maybeGetStringAsBooleanValue();
  290. if(boolExpr != null) {
  291. _expr = boolExpr;
  292. } else {
  293. BigDecimal bd = DefaultFunctions.maybeGetAsBigDecimal(_ctx, _expr);
  294. if(bd != null) {
  295. _expr = ValueSupport.toValue(bd);
  296. } else {
  297. // convert to date to number. this doesn't happen as part of the
  298. // default value coercion behavior, but the format method tries
  299. // harder
  300. Value maybe = DefaultFunctions.maybeGetAsDateTimeValue(
  301. _ctx, _expr);
  302. if(maybe != null) {
  303. _expr = ValueSupport.toValue(maybe.getAsDouble(_ctx));
  304. } else {
  305. // string which can't be converted to number force failure
  306. // here so default formatting will kick in
  307. throw new EvalException("invalid number value");
  308. }
  309. }
  310. }
  311. } else {
  312. // convert date to number
  313. _expr = ValueSupport.toValue(_expr.getAsDouble(_ctx));
  314. }
  315. }
  316. return this;
  317. }
  318. private Value maybeGetStringAsBooleanValue() {
  319. // format coerces "true"/"false" to boolean values
  320. String val = getAsString();
  321. if("true".equalsIgnoreCase(val)) {
  322. return ValueSupport.TRUE_VAL;
  323. }
  324. if("false".equalsIgnoreCase(val)) {
  325. return ValueSupport.FALSE_VAL;
  326. }
  327. return null;
  328. }
  329. public BigDecimal getAsBigDecimal() {
  330. coerceToNumberValue();
  331. return _expr.getAsBigDecimal(_ctx);
  332. }
  333. public LocalDateTime getAsLocalDateTime() {
  334. coerceToDateTimeValue();
  335. return _expr.getAsLocalDateTime(_ctx);
  336. }
  337. public boolean getAsBoolean() {
  338. // even though string values have a "boolean" value, for formatting,
  339. // strings which don't convert to valid boolean/number/date are just
  340. // returned as is. so we use coerceToNumberValue to force the exception
  341. // to be thrown which results in the "default" formatting behavior.
  342. coerceToNumberValue();
  343. return _expr.getAsBoolean(_ctx);
  344. }
  345. public String getAsString() {
  346. return _expr.getAsString(_ctx);
  347. }
  348. }
  349. private FormatUtil() {}
  350. public static Value format(EvalContext ctx, Value expr, String fmtStr,
  351. int firstDay, int firstWeekType) {
  352. try {
  353. Args args = new Args(ctx, expr, firstDay, firstWeekType);
  354. Fmt predefFmt = PREDEF_FMTS.get(fmtStr);
  355. if(predefFmt != null) {
  356. if(args.isNullOrEmptyString()) {
  357. // predefined formats return empty string for null
  358. return ValueSupport.EMPTY_STR_VAL;
  359. }
  360. return predefFmt.format(args);
  361. }
  362. // TODO implement caching for custom formats? put into Bindings. use
  363. // special "cache" prefix to know which caches to clear when evalconfig
  364. // is altered (could also cache other Format* functions)
  365. return parseCustomFormat(fmtStr, args).format(args);
  366. } catch(EvalException ee) {
  367. // values which cannot be formatted as the target type are just
  368. // returned "as is"
  369. return expr;
  370. }
  371. }
  372. private static Fmt parseCustomFormat(String fmtStr, Args args) {
  373. ExprBuf buf = new ExprBuf(fmtStr, null);
  374. // do partial pass to determine what type of format this is
  375. byte curFormatType = determineFormatType(buf);
  376. // reset buffer for real parse
  377. buf.reset(0);
  378. switch(curFormatType) {
  379. case FCT_GENERAL:
  380. return parseCustomGeneralFormat(buf, args);
  381. case FCT_DATE:
  382. return parseCustomDateFormat(buf, args);
  383. case FCT_NUMBER:
  384. return parseCustomNumberFormat(buf, args);
  385. case FCT_TEXT:
  386. return parseCustomTextFormat(buf, args);
  387. default:
  388. throw new EvalException("Invalid format type " + curFormatType);
  389. }
  390. }
  391. private static byte determineFormatType(ExprBuf buf) {
  392. while(buf.hasNext()) {
  393. char c = buf.next();
  394. byte fmtType = getFormatCodeType(c);
  395. switch(fmtType) {
  396. case FCT_UNKNOWN:
  397. case FCT_LITERAL:
  398. // meaningless, ignore for now
  399. break;
  400. case FCT_GENERAL:
  401. switch(c) {
  402. case QUOTE_CHAR:
  403. parseQuotedString(buf);
  404. break;
  405. case START_COLOR_CHAR:
  406. parseColorString(buf);
  407. break;
  408. case ESCAPE_CHAR:
  409. case FILL_ESCAPE_CHAR:
  410. if(buf.hasNext()) {
  411. buf.next();
  412. }
  413. break;
  414. default:
  415. // meaningless, ignore for now
  416. }
  417. break;
  418. case FCT_DATE:
  419. case FCT_NUMBER:
  420. case FCT_TEXT:
  421. // found specific type
  422. return fmtType;
  423. default:
  424. throw new EvalException("Invalid format type " + fmtType);
  425. }
  426. }
  427. // no specific type
  428. return FCT_GENERAL;
  429. }
  430. private static Fmt parseCustomGeneralFormat(ExprBuf buf, Args args) {
  431. // a "general" format is actually a "yes/no" format which functions almost
  432. // exactly like a number format (without any number format specific chars)
  433. StringBuilder sb = new StringBuilder();
  434. String[] fmtStrs = new String[NUM_NF_FMTS];
  435. int fmtIdx = 0;
  436. BUF_LOOP:
  437. while(buf.hasNext()) {
  438. char c = buf.next();
  439. int fmtType = getFormatCodeType(c);
  440. switch(fmtType) {
  441. case FCT_GENERAL:
  442. switch(c) {
  443. case LEFT_ALIGN_CHAR:
  444. // no effect
  445. break;
  446. case QUOTE_CHAR:
  447. parseQuotedString(buf, sb);
  448. break;
  449. case START_COLOR_CHAR:
  450. // color strings seem to be ignored
  451. parseColorString(buf);
  452. break;
  453. case ESCAPE_CHAR:
  454. if(buf.hasNext()) {
  455. sb.append(buf.next());
  456. }
  457. break;
  458. case FILL_ESCAPE_CHAR:
  459. // unclear what this actually does. online examples don't seem to
  460. // match with experimental results. for now, ignore
  461. if(buf.hasNext()) {
  462. buf.next();
  463. }
  464. break;
  465. case CHOICE_SEP_CHAR:
  466. // yes/no (number) format supports up to 4 formats: pos, neg, zero,
  467. // null. after that, ignore the rest
  468. if(fmtIdx == (NUM_NF_FMTS - 1)) {
  469. // last possible format, ignore remaining
  470. break BUF_LOOP;
  471. }
  472. addCustomGeneralFormat(fmtStrs, fmtIdx++, sb);
  473. break;
  474. default:
  475. sb.append(c);
  476. }
  477. break;
  478. default:
  479. sb.append(c);
  480. }
  481. }
  482. // fill in remaining formats
  483. while(fmtIdx < NUM_NF_FMTS) {
  484. addCustomGeneralFormat(fmtStrs, fmtIdx++, sb);
  485. }
  486. return new CustomGeneralFmt(
  487. ValueSupport.toValue(fmtStrs[NF_POS_IDX]),
  488. ValueSupport.toValue(fmtStrs[NF_NEG_IDX]),
  489. ValueSupport.toValue(fmtStrs[NF_ZERO_IDX]),
  490. ValueSupport.toValue(fmtStrs[NF_NULL_IDX]));
  491. }
  492. private static void addCustomGeneralFormat(String[] fmtStrs, int fmtIdx,
  493. StringBuilder sb)
  494. {
  495. addCustomNumberFormat(fmtStrs, NO_EXP_TYPES, NO_FMT_TYPES, fmtIdx, sb);
  496. }
  497. private static Fmt parseCustomDateFormat(ExprBuf buf, Args args) {
  498. // keep track of some extra state while parsing the format, whether or not
  499. // there was an am/pm pattern and whether or not there was a general
  500. // date/time pattern
  501. boolean[] fmtState = new boolean[]{false, false};
  502. List<DateFormatBuilder> dfbs = new ArrayList<>();
  503. BUF_LOOP:
  504. while(buf.hasNext()) {
  505. char c = buf.next();
  506. int fmtType = getFormatCodeType(c);
  507. switch(fmtType) {
  508. case FCT_GENERAL:
  509. switch(c) {
  510. case QUOTE_CHAR:
  511. String str = parseQuotedString(buf);
  512. dfbs.add((dtfb, argsParam, hasAmPmParam, dtType) ->
  513. dtfb.appendLiteral(str));
  514. break;
  515. case START_COLOR_CHAR:
  516. // color strings seem to be ignored
  517. parseColorString(buf);
  518. break;
  519. case ESCAPE_CHAR:
  520. if(buf.hasNext()) {
  521. dfbs.add(buildLiteralCharDFB(buf.next()));
  522. }
  523. break;
  524. case FILL_ESCAPE_CHAR:
  525. // unclear what this actually does. online examples don't seem to
  526. // match with experimental results. for now, ignore
  527. if(buf.hasNext()) {
  528. buf.next();
  529. }
  530. break;
  531. case CHOICE_SEP_CHAR:
  532. // date/time doesn't use multiple pattern choices, but it does
  533. // respect the char. ignore everything after the first choice
  534. break BUF_LOOP;
  535. default:
  536. dfbs.add(buildLiteralCharDFB(c));
  537. }
  538. break;
  539. case FCT_DATE:
  540. parseCustomDateFormatPattern(c, buf, dfbs, fmtState, args);
  541. break;
  542. default:
  543. dfbs.add(buildLiteralCharDFB(c));
  544. }
  545. }
  546. boolean hasAmPm = fmtState[0];
  547. boolean hasGeneralFormat = fmtState[1];
  548. if(!hasGeneralFormat) {
  549. // simple situation, one format for every value
  550. DateTimeFormatter dtf = createDateTimeFormatter(dfbs, args, hasAmPm, null);
  551. return new CustomFmt(argsParam -> ValueSupport.toValue(
  552. dtf.format(argsParam.getAsLocalDateTime())));
  553. }
  554. // we need separate formatters for date, time, and date/time values
  555. DateTimeFormatter dateFmt = createDateTimeFormatter(dfbs, args, hasAmPm,
  556. Value.Type.DATE);
  557. DateTimeFormatter timeFmt = createDateTimeFormatter(dfbs, args, hasAmPm,
  558. Value.Type.TIME);
  559. DateTimeFormatter dtFmt = createDateTimeFormatter(dfbs, args, hasAmPm,
  560. Value.Type.DATE_TIME);
  561. return new CustomFmt(argsParam -> formatDateTime(
  562. argsParam, dateFmt, timeFmt, dtFmt));
  563. }
  564. private static void parseCustomDateFormatPattern(
  565. char c, ExprBuf buf, List<DateFormatBuilder> dfbs,
  566. boolean[] fmtState, Args args) {
  567. if((c == DT_LIT_COLON_CHAR) || (c == DT_LIT_SLASH_CHAR)) {
  568. // date/time literal char, nothing more to do
  569. dfbs.add(buildLiteralCharDFB(c));
  570. return;
  571. }
  572. StringBuilder sb = buf.getScratchBuffer();
  573. sb.append(c);
  574. char firstChar = c;
  575. int firstPos = buf.curPos();
  576. String bestMatchPat = sb.toString();
  577. DateFormatBuilder bestMatch = DATE_FMT_BUILDERS.get(bestMatchPat);
  578. int bestPos = firstPos;
  579. while(buf.hasNext()) {
  580. sb.append(buf.next());
  581. String tmpPat = sb.toString();
  582. DateFormatBuilder dfb = DATE_FMT_BUILDERS.get(tmpPat);
  583. if(dfb == null) {
  584. // no more possible matches
  585. break;
  586. }
  587. if(dfb != PARTIAL_PREFIX) {
  588. // this is the longest, valid pattern we have seen so far
  589. bestMatch = dfb;
  590. bestPos = buf.curPos();
  591. bestMatchPat = tmpPat;
  592. }
  593. }
  594. if(bestMatch != PARTIAL_PREFIX) {
  595. // apply valid pattern
  596. buf.reset(bestPos);
  597. dfbs.add(bestMatch);
  598. switch(firstChar) {
  599. case 'a':
  600. case 'A':
  601. // this was an am/pm pattern
  602. fmtState[0] = true;
  603. break;
  604. case 'c':
  605. // this was a general date/time format
  606. fmtState[1] = true;
  607. break;
  608. default:
  609. // don't care
  610. }
  611. } else {
  612. // just consume the first char
  613. buf.reset(firstPos);
  614. dfbs.add(buildLiteralCharDFB(firstChar));
  615. }
  616. }
  617. private static DateFormatBuilder buildLiteralCharDFB(char c) {
  618. return (dtfb, args, hasAmPm, dtType) -> dtfb.appendLiteral(c);
  619. }
  620. private static DateTimeFormatter createDateTimeFormatter(
  621. List<DateFormatBuilder> dfbs, Args args, boolean hasAmPm,
  622. Value.Type dtType)
  623. {
  624. DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
  625. dfbs.forEach(d -> d.build(dtfb, args, hasAmPm, dtType));
  626. return dtfb.toFormatter(args._ctx.getTemporalConfig().getLocale());
  627. }
  628. private static Value formatDateTime(
  629. Args args, DateTimeFormatter dateFmt,
  630. DateTimeFormatter timeFmt, DateTimeFormatter dtFmt)
  631. {
  632. LocalDateTime ldt = args.getAsLocalDateTime();
  633. DateTimeFormatter fmt = null;
  634. switch(args._expr.getType()) {
  635. case DATE:
  636. fmt = dateFmt;
  637. break;
  638. case TIME:
  639. fmt = timeFmt;
  640. break;
  641. default:
  642. fmt = dtFmt;
  643. }
  644. return ValueSupport.toValue(fmt.format(ldt));
  645. }
  646. private static Fmt parseCustomNumberFormat(ExprBuf buf, Args args) {
  647. StringBuilder sb = new StringBuilder();
  648. String[] fmtStrs = new String[NUM_NF_FMTS];
  649. int fmtIdx = 0;
  650. StringBuilder pendingLiteral = new StringBuilder();
  651. NumberFormatter.NotationType[] expTypes =
  652. new NumberFormatter.NotationType[NUM_NF_FMTS];
  653. boolean[] hasFmts = new boolean[NUM_NF_FMTS];
  654. BUF_LOOP:
  655. while(buf.hasNext()) {
  656. char c = buf.next();
  657. int fmtType = getFormatCodeType(c);
  658. switch(fmtType) {
  659. case FCT_GENERAL:
  660. switch(c) {
  661. case LEFT_ALIGN_CHAR:
  662. // no effect
  663. break;
  664. case QUOTE_CHAR:
  665. parseQuotedString(buf, pendingLiteral);
  666. break;
  667. case START_COLOR_CHAR:
  668. // color strings seem to be ignored
  669. parseColorString(buf);
  670. break;
  671. case ESCAPE_CHAR:
  672. if(buf.hasNext()) {
  673. pendingLiteral.append(buf.next());
  674. }
  675. break;
  676. case FILL_ESCAPE_CHAR:
  677. // unclear what this actually does. online examples don't seem to
  678. // match with experimental results. for now, ignore
  679. if(buf.hasNext()) {
  680. buf.next();
  681. }
  682. break;
  683. case CHOICE_SEP_CHAR:
  684. // number format supports up to 4 formats: pos, neg, zero, null.
  685. // after that, ignore the rest
  686. if(fmtIdx == (NUM_NF_FMTS - 1)) {
  687. // last possible format, ignore remaining
  688. break BUF_LOOP;
  689. }
  690. flushPendingNumberLiteral(pendingLiteral, sb);
  691. addCustomNumberFormat(fmtStrs, expTypes, hasFmts, fmtIdx++, sb);
  692. break;
  693. default:
  694. pendingLiteral.append(c);
  695. }
  696. break;
  697. case FCT_NUMBER:
  698. hasFmts[fmtIdx] = true;
  699. switch(c) {
  700. case EXP_E_CHAR:
  701. int signChar = buf.peekNext();
  702. if((signChar == PLUS_CHAR) || (signChar == MINUS_CHAR)) {
  703. buf.next();
  704. expTypes[fmtIdx] = ((signChar == PLUS_CHAR) ?
  705. NumberFormatter.NotationType.EXP_E_PLUS :
  706. NumberFormatter.NotationType.EXP_E_MINUS);
  707. flushPendingNumberLiteral(pendingLiteral, sb);
  708. sb.append(EXP_E_CHAR);
  709. } else {
  710. pendingLiteral.append(c);
  711. }
  712. break;
  713. case EXP_e_CHAR:
  714. signChar = buf.peekNext();
  715. if((signChar == PLUS_CHAR) || (signChar == MINUS_CHAR)) {
  716. buf.next();
  717. expTypes[fmtIdx] = ((signChar == PLUS_CHAR) ?
  718. NumberFormatter.NotationType.EXP_e_PLUS :
  719. NumberFormatter.NotationType.EXP_e_MINUS);
  720. flushPendingNumberLiteral(pendingLiteral, sb);
  721. sb.append(EXP_E_CHAR);
  722. } else {
  723. pendingLiteral.append(c);
  724. }
  725. break;
  726. default:
  727. // most number format chars pass straight through
  728. flushPendingNumberLiteral(pendingLiteral, sb);
  729. sb.append(c);
  730. }
  731. break;
  732. default:
  733. pendingLiteral.append(c);
  734. }
  735. }
  736. // fill in remaining formats
  737. while(fmtIdx < NUM_NF_FMTS) {
  738. flushPendingNumberLiteral(pendingLiteral, sb);
  739. addCustomNumberFormat(fmtStrs, expTypes, hasFmts, fmtIdx++, sb);
  740. }
  741. return new CustomNumberFmt(
  742. createCustomNumberFormat(fmtStrs, expTypes, hasFmts, NF_POS_IDX, args),
  743. createCustomNumberFormat(fmtStrs, expTypes, hasFmts, NF_NEG_IDX, args),
  744. createCustomNumberFormat(fmtStrs, expTypes, hasFmts, NF_ZERO_IDX, args),
  745. createCustomNumberFormat(fmtStrs, expTypes, hasFmts, NF_NULL_IDX, args));
  746. }
  747. private static void addCustomNumberFormat(
  748. String[] fmtStrs, NumberFormatter.NotationType[] expTypes,
  749. boolean[] hasFmts, int fmtIdx, StringBuilder sb)
  750. {
  751. if(sb.length() == 0) {
  752. // do special empty format handling on a per-format-type basis
  753. switch(fmtIdx) {
  754. case NF_NEG_IDX:
  755. // re-use "pos" format
  756. sb.append('-').append(fmtStrs[NF_POS_IDX]);
  757. expTypes[NF_NEG_IDX] = expTypes[NF_POS_IDX];
  758. hasFmts[NF_NEG_IDX] = hasFmts[NF_POS_IDX];
  759. break;
  760. case NF_ZERO_IDX:
  761. // re-use "pos" format
  762. sb.append(fmtStrs[NF_POS_IDX]);
  763. expTypes[NF_ZERO_IDX] = expTypes[NF_POS_IDX];
  764. hasFmts[NF_ZERO_IDX] = hasFmts[NF_POS_IDX];
  765. break;
  766. default:
  767. // use empty string result
  768. }
  769. }
  770. fmtStrs[fmtIdx] = sb.toString();
  771. sb.setLength(0);
  772. }
  773. private static void flushPendingNumberLiteral(
  774. StringBuilder pendingLiteral, StringBuilder sb) {
  775. if(pendingLiteral.length() == 0) {
  776. return;
  777. }
  778. if((pendingLiteral.length() == 1) &&
  779. pendingLiteral.charAt(0) == SINGLE_QUOTE_CHAR) {
  780. // handle standalone single quote
  781. sb.append(SINGLE_QUOTE_CHAR).append(SINGLE_QUOTE_CHAR);
  782. pendingLiteral.setLength(0);
  783. return;
  784. }
  785. sb.append(SINGLE_QUOTE_CHAR);
  786. int startPos = sb.length();
  787. sb.append(pendingLiteral);
  788. // we need to quote any single quotes in the literal string
  789. for(int i = startPos; i < sb.length(); ++i) {
  790. char c = sb.charAt(i);
  791. if(c == SINGLE_QUOTE_CHAR) {
  792. sb.insert(++i, SINGLE_QUOTE_CHAR);
  793. }
  794. }
  795. sb.append(SINGLE_QUOTE_CHAR);
  796. pendingLiteral.setLength(0);
  797. }
  798. private static NumberFormat createCustomNumberFormat(
  799. String[] fmtStrs, NumberFormatter.NotationType[] expTypes,
  800. boolean[] hasFmts, int fmtIdx, Args args) {
  801. String fmtStr = fmtStrs[fmtIdx];
  802. if(!hasFmts[fmtIdx]) {
  803. // convert the literal string to a dummy number format
  804. if(fmtStr.length() > 0) {
  805. // strip quoting
  806. StringBuilder sb = new StringBuilder(fmtStr)
  807. .deleteCharAt(fmtStr.length() - 1)
  808. .deleteCharAt(0);
  809. if(sb.length() > 0) {
  810. for(int i = 0; i < sb.length(); ++i) {
  811. if(sb.charAt(i) == SINGLE_QUOTE_CHAR) {
  812. // delete next single quote char
  813. sb.deleteCharAt(++i);
  814. }
  815. }
  816. }
  817. fmtStr = sb.toString();
  818. }
  819. return new LiteralNumberFormat(fmtStr);
  820. }
  821. NumberFormatter.NotationType expType = expTypes[fmtIdx];
  822. NumberFormat nf = args._ctx.createDecimalFormat(fmtStr);
  823. DecimalFormat df = (DecimalFormat)nf;
  824. if(df.getMaximumFractionDigits() > 0) {
  825. // if the decimal is included in the format, access always shows it
  826. df.setDecimalSeparatorAlwaysShown(true);
  827. }
  828. if(expType != null) {
  829. nf = new NumberFormatter.ScientificFormat(nf, expType);
  830. }
  831. return nf;
  832. }
  833. private static Fmt parseCustomTextFormat(ExprBuf buf, Args args) {
  834. Fmt fmt = null;
  835. Fmt emptyFmt = null;
  836. List<BiConsumer<StringBuilder,CharSource>> subFmts = new ArrayList<>();
  837. int numPlaceholders = 0;
  838. boolean rightAligned = true;
  839. TextCase textCase = TextCase.NONE;
  840. StringBuilder pendingLiteral = new StringBuilder();
  841. boolean hasFmtChars = false;
  842. BUF_LOOP:
  843. while(buf.hasNext()) {
  844. char c = buf.next();
  845. hasFmtChars = true;
  846. int fmtType = getFormatCodeType(c);
  847. switch(fmtType) {
  848. case FCT_GENERAL:
  849. switch(c) {
  850. case LEFT_ALIGN_CHAR:
  851. rightAligned = false;
  852. break;
  853. case QUOTE_CHAR:
  854. parseQuotedString(buf, pendingLiteral);
  855. break;
  856. case START_COLOR_CHAR:
  857. // color strings seem to be ignored
  858. parseColorString(buf);
  859. break;
  860. case ESCAPE_CHAR:
  861. if(buf.hasNext()) {
  862. pendingLiteral.append(buf.next());
  863. }
  864. break;
  865. case FILL_ESCAPE_CHAR:
  866. // unclear what this actually does. online examples don't seem to
  867. // match with experimental results. for now, ignore
  868. if(buf.hasNext()) {
  869. buf.next();
  870. }
  871. break;
  872. case CHOICE_SEP_CHAR:
  873. // text format supports up to 2 formats: normal and empty/null.
  874. // after that, ignore the rest
  875. if(fmt != null) {
  876. // ignore remaining format
  877. break BUF_LOOP;
  878. }
  879. flushPendingTextLiteral(pendingLiteral, subFmts);
  880. fmt = new CharSourceFmt(subFmts, numPlaceholders, rightAligned,
  881. textCase);
  882. // reset for next format
  883. subFmts = new ArrayList<>();
  884. numPlaceholders = 0;
  885. rightAligned = true;
  886. textCase = TextCase.NONE;
  887. hasFmtChars = false;
  888. break;
  889. default:
  890. pendingLiteral.append(c);
  891. }
  892. break;
  893. case FCT_TEXT:
  894. switch(c) {
  895. case REQ_PLACEHOLDER_CHAR:
  896. flushPendingTextLiteral(pendingLiteral, subFmts);
  897. ++numPlaceholders;
  898. subFmts.add((sb,cs) -> {
  899. int tmp = cs.next();
  900. sb.append((tmp != NO_CHAR) ? (char)tmp : ' ');
  901. });
  902. break;
  903. case OPT_PLACEHOLDER_CHAR:
  904. flushPendingTextLiteral(pendingLiteral, subFmts);
  905. ++numPlaceholders;
  906. subFmts.add((sb,cs) -> {
  907. int tmp = cs.next();
  908. if(tmp != NO_CHAR) {
  909. sb.append((char)tmp);
  910. }
  911. });
  912. break;
  913. case TO_UPPER_CHAR:
  914. // an uppper and lower symbol cancel each other out
  915. textCase = ((textCase == TextCase.LOWER) ?
  916. TextCase.NONE : TextCase.UPPER);
  917. break;
  918. case TO_LOWER_CHAR:
  919. // an uppper and lower symbol cancel each other out
  920. textCase = ((textCase == TextCase.UPPER) ?
  921. TextCase.NONE : TextCase.LOWER);
  922. break;
  923. default:
  924. pendingLiteral.append(c);
  925. }
  926. break;
  927. default:
  928. pendingLiteral.append(c);
  929. }
  930. }
  931. flushPendingTextLiteral(pendingLiteral, subFmts);
  932. if(fmt == null) {
  933. fmt = new CharSourceFmt(subFmts, numPlaceholders, rightAligned,
  934. textCase);
  935. emptyFmt = NULL_FMT;
  936. } else if(emptyFmt == null) {
  937. emptyFmt = (hasFmtChars ?
  938. new CharSourceFmt(subFmts, numPlaceholders, rightAligned,
  939. textCase) :
  940. NULL_FMT);
  941. }
  942. return new CustomFmt(fmt, emptyFmt);
  943. }
  944. private static void flushPendingTextLiteral(
  945. StringBuilder pendingLiteral,
  946. List<BiConsumer<StringBuilder,CharSource>> subFmts) {
  947. if(pendingLiteral.length() == 0) {
  948. return;
  949. }
  950. String literal = pendingLiteral.toString();
  951. pendingLiteral.setLength(0);
  952. subFmts.add((sb, cs) -> sb.append(literal));
  953. }
  954. public static String createNumberFormatPattern(
  955. NumPatternType numPatType, int numDecDigits, boolean incLeadDigit,
  956. boolean negParens, int numGroupDigits) {
  957. StringBuilder fmt = new StringBuilder();
  958. numPatType.appendPrefix(fmt);
  959. if(numGroupDigits > 0) {
  960. fmt.append("#,");
  961. DefaultTextFunctions.nchars(fmt, numGroupDigits - 1, '#');
  962. }
  963. fmt.append(incLeadDigit ? "0" : "#");
  964. if(numDecDigits > 0) {
  965. fmt.append(".");
  966. DefaultTextFunctions.nchars(fmt, numDecDigits, '0');
  967. }
  968. numPatType.appendSuffix(fmt);
  969. if(negParens) {
  970. // the javadocs claim the second pattern does not need to be fully
  971. // defined, but it doesn't seem to work that way
  972. String mainPat = fmt.toString();
  973. fmt.append(";(").append(mainPat).append(")");
  974. }
  975. return fmt.toString();
  976. }
  977. private static byte getFormatCodeType(char c) {
  978. if((c >= 0) && (c < 127)) {
  979. return FORMAT_CODE_TYPES[c];
  980. }
  981. return FCT_UNKNOWN;
  982. }
  983. private static void setFormatCodeTypes(String chars, byte type) {
  984. for(char c : chars.toCharArray()) {
  985. FORMAT_CODE_TYPES[c] = type;
  986. }
  987. }
  988. private static String parseQuotedString(ExprBuf buf) {
  989. return ExpressionTokenizer.parseStringUntil(buf, null, QUOTE_CHAR, true);
  990. }
  991. private static void parseQuotedString(ExprBuf buf, StringBuilder sb) {
  992. ExpressionTokenizer.parseStringUntil(buf, null, QUOTE_CHAR, true, sb);
  993. }
  994. private static String parseColorString(ExprBuf buf) {
  995. return ExpressionTokenizer.parseStringUntil(
  996. buf, START_COLOR_CHAR, END_COLOR_CHAR, false);
  997. }
  998. private static void fillInPartialPrefixes() {
  999. List<String> validPrefixes = new ArrayList<>(DATE_FMT_BUILDERS.keySet());
  1000. for(String validPrefix : validPrefixes) {
  1001. int len = validPrefix.length();
  1002. while(len > 1) {
  1003. --len;
  1004. validPrefix = validPrefix.substring(0, len);
  1005. DATE_FMT_BUILDERS.putIfAbsent(validPrefix, PARTIAL_PREFIX);
  1006. }
  1007. }
  1008. }
  1009. private static final class PredefDateFmt implements Fmt
  1010. {
  1011. private final TemporalConfig.Type _type;
  1012. private PredefDateFmt(TemporalConfig.Type type) {
  1013. _type = type;
  1014. }
  1015. @Override
  1016. public Value format(Args args) {
  1017. DateTimeFormatter dtf = args._ctx.createDateFormatter(
  1018. args._ctx.getTemporalConfig().getDateTimeFormat(_type));
  1019. return ValueSupport.toValue(dtf.format(args.getAsLocalDateTime()));
  1020. }
  1021. }
  1022. private static final class PredefBoolFmt implements Fmt
  1023. {
  1024. private final Value _trueVal;
  1025. private final Value _falseVal;
  1026. private PredefBoolFmt(String trueStr, String falseStr) {
  1027. _trueVal = ValueSupport.toValue(trueStr);
  1028. _falseVal = ValueSupport.toValue(falseStr);
  1029. }
  1030. @Override
  1031. public Value format(Args args) {
  1032. return(args.getAsBoolean() ? _trueVal : _falseVal);
  1033. }
  1034. }
  1035. private static abstract class BaseNumberFmt implements Fmt
  1036. {
  1037. @Override
  1038. public Value format(Args args) {
  1039. NumberFormat df = getNumberFormat(args);
  1040. return ValueSupport.toValue(df.format(args.getAsBigDecimal()));
  1041. }
  1042. protected abstract NumberFormat getNumberFormat(Args args);
  1043. }
  1044. private static final class PredefNumberFmt extends BaseNumberFmt
  1045. {
  1046. private final NumericConfig.Type _type;
  1047. private PredefNumberFmt(NumericConfig.Type type) {
  1048. _type = type;
  1049. }
  1050. @Override
  1051. protected NumberFormat getNumberFormat(Args args) {
  1052. return args._ctx.createDecimalFormat(
  1053. args._ctx.getNumericConfig().getNumberFormat(_type));
  1054. }
  1055. }
  1056. private static final class ScientificPredefNumberFmt extends BaseNumberFmt
  1057. {
  1058. @Override
  1059. protected NumberFormat getNumberFormat(Args args) {
  1060. NumberFormat df = args._ctx.createDecimalFormat(
  1061. args._ctx.getNumericConfig().getNumberFormat(
  1062. NumericConfig.Type.SCIENTIFIC));
  1063. df = new NumberFormatter.ScientificFormat(df);
  1064. return df;
  1065. }
  1066. }
  1067. private static final class SimpleDFB implements DateFormatBuilder
  1068. {
  1069. private final String _pat;
  1070. private SimpleDFB(String pat) {
  1071. _pat = pat;
  1072. }
  1073. @Override
  1074. public void build(DateTimeFormatterBuilder dtfb, Args args,
  1075. boolean hasAmPm, Value.Type dtType) {
  1076. dtfb.appendPattern(_pat);
  1077. }
  1078. }
  1079. private static final class HourlyDFB implements DateFormatBuilder
  1080. {
  1081. private final String _pat12;
  1082. private final String _pat24;
  1083. private HourlyDFB(String pat12, String pat24) {
  1084. _pat12 = pat12;
  1085. _pat24 = pat24;
  1086. }
  1087. @Override
  1088. public void build(DateTimeFormatterBuilder dtfb, Args args,
  1089. boolean hasAmPm, Value.Type dtTypePm) {
  1090. // annoyingly the "hour" patterns are the same and depend on the
  1091. // existence of the am/pm pattern to determine how they function (12 vs
  1092. // 24 hour).
  1093. dtfb.appendPattern(hasAmPm ? _pat12 : _pat24);
  1094. }
  1095. }
  1096. private static final class PredefDFB implements DateFormatBuilder
  1097. {
  1098. private final TemporalConfig.Type _type;
  1099. private PredefDFB(TemporalConfig.Type type) {
  1100. _type = type;
  1101. }
  1102. @Override
  1103. public void build(DateTimeFormatterBuilder dtfb, Args args,
  1104. boolean hasAmPm, Value.Type dtType) {
  1105. dtfb.appendPattern(args._ctx.getTemporalConfig().getDateTimeFormat(_type));
  1106. }
  1107. }
  1108. private static abstract class WeekBasedDFB implements DateFormatBuilder
  1109. {
  1110. @Override
  1111. public void build(DateTimeFormatterBuilder dtfb, Args args,
  1112. boolean hasAmPm, Value.Type dtType) {
  1113. dtfb.appendValue(getField(DefaultDateFunctions.weekFields(
  1114. args._firstDay, args._firstWeekType)));
  1115. }
  1116. protected abstract TemporalField getField(WeekFields weekFields);
  1117. }
  1118. private static final class AmPmDFB extends AbstractMap<Long,String>
  1119. implements DateFormatBuilder
  1120. {
  1121. private static final Long ZERO_KEY = 0L;
  1122. private final String _am;
  1123. private final String _pm;
  1124. private AmPmDFB(String am, String pm) {
  1125. _am = am;
  1126. _pm = pm;
  1127. }
  1128. @Override
  1129. public void build(DateTimeFormatterBuilder dtfb, Args args,
  1130. boolean hasAmPm, Value.Type dtType) {
  1131. dtfb.appendText(ChronoField.AMPM_OF_DAY, this);
  1132. }
  1133. @Override
  1134. public int size() {
  1135. return 2;
  1136. }
  1137. @Override
  1138. public String get(Object key) {
  1139. return(ZERO_KEY.equals(key) ? _am : _pm);
  1140. }
  1141. @Override
  1142. public Set<Map.Entry<Long,String>> entrySet() {
  1143. return new AbstractSet<Map.Entry<Long,String>>() {
  1144. @Override
  1145. public int size() {
  1146. return 2;
  1147. }
  1148. @Override
  1149. public Iterator<Map.Entry<Long,String>> iterator() {
  1150. return Arrays.<Map.Entry<Long,String>>asList(
  1151. new AbstractMap.SimpleImmutableEntry<Long,String>(0L, _am),
  1152. new AbstractMap.SimpleImmutableEntry<Long,String>(1L, _pm))
  1153. .iterator();
  1154. }
  1155. };
  1156. }
  1157. }
  1158. private static final class CustomFmt implements Fmt
  1159. {
  1160. private final Fmt _fmt;
  1161. private final Fmt _emptyFmt;
  1162. private CustomFmt(Fmt fmt) {
  1163. this(fmt, NULL_FMT);
  1164. }
  1165. private CustomFmt(Fmt fmt, Fmt emptyFmt) {
  1166. _fmt = fmt;
  1167. _emptyFmt = emptyFmt;
  1168. }
  1169. @Override
  1170. public Value format(Args args) {
  1171. Fmt fmt = _fmt;
  1172. if(args.maybeCoerceToEmptyString()) {
  1173. fmt = _emptyFmt;
  1174. }
  1175. return fmt.format(args);
  1176. }
  1177. }
  1178. private static final class CharSourceFmt implements Fmt
  1179. {
  1180. private final List<BiConsumer<StringBuilder,CharSource>> _subFmts;
  1181. private final int _numPlaceholders;
  1182. private final boolean _rightAligned;
  1183. private final TextCase _textCase;
  1184. private CharSourceFmt(List<BiConsumer<StringBuilder,CharSource>> subFmts,
  1185. int numPlaceholders, boolean rightAligned,
  1186. TextCase textCase) {
  1187. _subFmts = subFmts;
  1188. _numPlaceholders = numPlaceholders;
  1189. _rightAligned = rightAligned;
  1190. _textCase = textCase;
  1191. }
  1192. @Override
  1193. public Value format(Args args) {
  1194. CharSource cs = new CharSource(args.getAsString(), _numPlaceholders,
  1195. _rightAligned, _textCase);
  1196. StringBuilder sb = new StringBuilder();
  1197. _subFmts.stream().forEach(fmt -> fmt.accept(sb, cs));
  1198. cs.appendRemaining(sb);
  1199. return ValueSupport.toValue(sb.toString());
  1200. }
  1201. }
  1202. private static final class CharSource
  1203. {
  1204. private int _prefLen;
  1205. private final String _str;
  1206. private int _strPos;
  1207. private final TextCase _textCase;
  1208. private CharSource(String str, int len, boolean rightAligned,
  1209. TextCase textCase) {
  1210. _str = str;
  1211. _textCase = textCase;
  1212. int strLen = str.length();
  1213. if(len > strLen) {
  1214. if(rightAligned) {
  1215. _prefLen = len - strLen;
  1216. }
  1217. } else if(len < strLen) {
  1218. // it doesn't make sense to me, but the meaning of "right aligned"
  1219. // seems to flip when the string is longer than the format length
  1220. if(!rightAligned) {
  1221. _strPos = strLen - len;
  1222. }
  1223. }
  1224. }
  1225. public int next() {
  1226. if(_prefLen > 0) {
  1227. --_prefLen;
  1228. return NO_CHAR;
  1229. }
  1230. if(_strPos < _str.length()) {
  1231. return _textCase.apply(_str.charAt(_strPos++));
  1232. }
  1233. return NO_CHAR;
  1234. }
  1235. public void appendRemaining(StringBuilder sb) {
  1236. int strLen = _str.length();
  1237. while(_strPos < strLen) {
  1238. sb.append(_textCase.apply(_str.charAt(_strPos++)));
  1239. }
  1240. }
  1241. }
  1242. private static abstract class BaseCustomNumberFmt implements Fmt
  1243. {
  1244. @Override
  1245. public Value format(Args args) {
  1246. if(args._expr.isNull()) {
  1247. return formatNull(args);
  1248. }
  1249. BigDecimal bd = args.getAsBigDecimal();
  1250. int cmp = BigDecimal.ZERO.compareTo(bd);
  1251. return ((cmp < 0) ? formatPos(bd, args) :
  1252. ((cmp > 0) ? formatNeg(bd, args) :
  1253. formatZero(bd, args)));
  1254. }
  1255. protected abstract Value formatNull(Args args);
  1256. protected abstract Value formatPos(BigDecimal bd, Args args);
  1257. protected abstract Value formatNeg(BigDecimal bd, Args args);
  1258. protected abstract Value formatZero(BigDecimal bd, Args args);
  1259. }
  1260. private static final class CustomNumberFmt extends BaseCustomNumberFmt
  1261. {
  1262. private final NumberFormat _posFmt;
  1263. private final NumberFormat _negFmt;
  1264. private final NumberFormat _zeroFmt;
  1265. private final NumberFormat _nullFmt;
  1266. private CustomNumberFmt(NumberFormat posFmt, NumberFormat negFmt,
  1267. NumberFormat zeroFmt, NumberFormat nullFmt) {
  1268. _posFmt = posFmt;
  1269. _negFmt = negFmt;
  1270. _zeroFmt = zeroFmt;
  1271. _nullFmt = nullFmt;
  1272. }
  1273. private Value formatMaybeZero(BigDecimal bd, NumberFormat fmt) {
  1274. // in theory we want to use the given format. however, if, due to
  1275. // rounding, we end up with a number equivalent to zero, then we fall
  1276. // back to the zero format
  1277. int maxDecDigits = fmt.getMaximumFractionDigits();
  1278. if(maxDecDigits < bd.scale()) {
  1279. bd = bd.setScale(maxDecDigits, NumberFormatter.ROUND_MODE);
  1280. }
  1281. if(BigDecimal.ZERO.compareTo(bd) == 0) {
  1282. // fall back to zero format
  1283. fmt = _zeroFmt;
  1284. }
  1285. return ValueSupport.toValue(fmt.format(bd));
  1286. }
  1287. @Override
  1288. protected Value formatNull(Args args) {
  1289. return ValueSupport.toValue(_nullFmt.format(BigDecimal.ZERO));
  1290. }
  1291. @Override
  1292. protected Value formatPos(BigDecimal bd, Args args) {
  1293. return formatMaybeZero(bd, _posFmt);
  1294. }
  1295. @Override
  1296. protected Value formatNeg(BigDecimal bd, Args args) {
  1297. return formatMaybeZero(bd.negate(), _negFmt);
  1298. }
  1299. @Override
  1300. protected Value formatZero(BigDecimal bd, Args args) {
  1301. return ValueSupport.toValue(_zeroFmt.format(bd));
  1302. }
  1303. }
  1304. private static final class CustomGeneralFmt extends BaseCustomNumberFmt
  1305. {
  1306. private final Value _posVal;
  1307. private final Value _negVal;
  1308. private final Value _zeroVal;
  1309. private final Value _nullVal;
  1310. private CustomGeneralFmt(Value posVal, Value negVal,
  1311. Value zeroVal, Value nullVal) {
  1312. _posVal = posVal;
  1313. _negVal = negVal;
  1314. _zeroVal = zeroVal;
  1315. _nullVal = nullVal;
  1316. }
  1317. @Override
  1318. protected Value formatNull(Args args) {
  1319. return _nullVal;
  1320. }
  1321. @Override
  1322. protected Value formatPos(BigDecimal bd, Args args) {
  1323. return _posVal;
  1324. }
  1325. @Override
  1326. protected Value formatNeg(BigDecimal bd, Args args) {
  1327. return _negVal;
  1328. }
  1329. @Override
  1330. protected Value formatZero(BigDecimal bd, Args args) {
  1331. return _zeroVal;
  1332. }
  1333. }
  1334. private static final class LiteralNumberFormat extends NumberFormat
  1335. {
  1336. private static final long serialVersionUID = 0L;
  1337. private final String _str;
  1338. private LiteralNumberFormat(String str) {
  1339. _str = str;
  1340. }
  1341. @Override
  1342. public StringBuffer format(Object number, StringBuffer toAppendTo,
  1343. FieldPosition pos)
  1344. {
  1345. return toAppendTo.append(_str);
  1346. }
  1347. @Override
  1348. public StringBuffer format(double number, StringBuffer toAppendTo,
  1349. FieldPosition pos) {
  1350. throw new UnsupportedOperationException();
  1351. }
  1352. @Override
  1353. public Number parse(String source, ParsePosition parsePosition) {
  1354. throw new UnsupportedOperationException();
  1355. }
  1356. @Override
  1357. public StringBuffer format(long number, StringBuffer toAppendTo,
  1358. FieldPosition pos) {
  1359. throw new UnsupportedOperationException();
  1360. }
  1361. }
  1362. }