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.

BuiltinOperators.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. /*
  2. Copyright (c) 2016 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.DateFormat;
  16. import java.util.Date;
  17. import java.util.regex.Pattern;
  18. import com.healthmarketscience.jackcess.expr.EvalContext;
  19. import com.healthmarketscience.jackcess.expr.EvalException;
  20. import com.healthmarketscience.jackcess.expr.Value;
  21. import com.healthmarketscience.jackcess.impl.ColumnImpl;
  22. import com.healthmarketscience.jackcess.impl.NumberFormatter;
  23. /**
  24. *
  25. * @author James Ahlborn
  26. */
  27. public class BuiltinOperators
  28. {
  29. private static final String DIV_BY_ZERO = "/ by zero";
  30. private static final double MIN_INT = Integer.MIN_VALUE;
  31. private static final double MAX_INT = Integer.MAX_VALUE;
  32. public static final Value NULL_VAL = new BaseValue() {
  33. @Override public boolean isNull() {
  34. return true;
  35. }
  36. public Type getType() {
  37. return Type.NULL;
  38. }
  39. public Object get() {
  40. return null;
  41. }
  42. };
  43. // access seems to like -1 for true and 0 for false (boolean values are
  44. // basically an illusion)
  45. public static final Value TRUE_VAL = new LongValue(-1);
  46. public static final Value FALSE_VAL = new LongValue(0);
  47. public static final Value EMPTY_STR_VAL = new StringValue("");
  48. public static final Value ZERO_VAL = FALSE_VAL;
  49. private enum CoercionType {
  50. SIMPLE(true, true), GENERAL(false, true), COMPARE(false, false);
  51. final boolean _preferTemporal;
  52. final boolean _allowCoerceStringToNum;
  53. private CoercionType(boolean preferTemporal,
  54. boolean allowCoerceStringToNum) {
  55. _preferTemporal = preferTemporal;
  56. _allowCoerceStringToNum = allowCoerceStringToNum;
  57. }
  58. }
  59. private BuiltinOperators() {}
  60. // null propagation rules:
  61. // http://www.utteraccess.com/wiki/index.php/Nulls_And_Their_Behavior
  62. // https://theaccessbuddy.wordpress.com/2012/10/24/6-logical-operators-in-ms-access-that-you-must-know-operator-types-3-of-5/
  63. // - number ops
  64. // - comparison ops
  65. // - logical ops (some "special")
  66. // - And - can be false if one arg is false
  67. // - Or - can be true if one arg is true
  68. // - between, not, like, in
  69. // - *NOT* concal op '&'
  70. public static Value negate(EvalContext ctx, Value param1) {
  71. if(param1.isNull()) {
  72. // null propagation
  73. return NULL_VAL;
  74. }
  75. Value.Type mathType = param1.getType();
  76. switch(mathType) {
  77. case DATE:
  78. case TIME:
  79. case DATE_TIME:
  80. // dates/times get converted to date doubles for arithmetic
  81. double result = -param1.getAsDouble();
  82. return toDateValue(ctx, mathType, result, param1, null);
  83. case LONG:
  84. return toValue(-param1.getAsLongInt());
  85. case DOUBLE:
  86. return toValue(-param1.getAsDouble());
  87. case STRING:
  88. case BIG_DEC:
  89. return toValue(param1.getAsBigDecimal().negate(
  90. NumberFormatter.DEC_MATH_CONTEXT));
  91. default:
  92. throw new EvalException("Unexpected type " + mathType);
  93. }
  94. }
  95. public static Value add(EvalContext ctx, Value param1, Value param2) {
  96. if(anyParamIsNull(param1, param2)) {
  97. // null propagation
  98. return NULL_VAL;
  99. }
  100. Value.Type mathType = getMathTypePrecedence(param1, param2,
  101. CoercionType.SIMPLE);
  102. switch(mathType) {
  103. case STRING:
  104. // string '+' is a null-propagation (handled above) concat
  105. return nonNullConcat(param1, param2);
  106. case DATE:
  107. case TIME:
  108. case DATE_TIME:
  109. // dates/times get converted to date doubles for arithmetic
  110. double result = param1.getAsDouble() + param2.getAsDouble();
  111. return toDateValue(ctx, mathType, result, param1, param2);
  112. case LONG:
  113. return toValue(param1.getAsLongInt() + param2.getAsLongInt());
  114. case DOUBLE:
  115. return toValue(param1.getAsDouble() + param2.getAsDouble());
  116. case BIG_DEC:
  117. return toValue(param1.getAsBigDecimal().add(
  118. param2.getAsBigDecimal(),
  119. NumberFormatter.DEC_MATH_CONTEXT));
  120. default:
  121. throw new EvalException("Unexpected type " + mathType);
  122. }
  123. }
  124. public static Value subtract(EvalContext ctx, Value param1, Value param2) {
  125. if(anyParamIsNull(param1, param2)) {
  126. // null propagation
  127. return NULL_VAL;
  128. }
  129. Value.Type mathType = getMathTypePrecedence(param1, param2,
  130. CoercionType.SIMPLE);
  131. switch(mathType) {
  132. // case STRING: break; unsupported
  133. case DATE:
  134. case TIME:
  135. case DATE_TIME:
  136. // dates/times get converted to date doubles for arithmetic
  137. double result = param1.getAsDouble() - param2.getAsDouble();
  138. return toDateValue(ctx, mathType, result, param1, param2);
  139. case LONG:
  140. return toValue(param1.getAsLongInt() - param2.getAsLongInt());
  141. case DOUBLE:
  142. return toValue(param1.getAsDouble() - param2.getAsDouble());
  143. case BIG_DEC:
  144. return toValue(param1.getAsBigDecimal().subtract(
  145. param2.getAsBigDecimal(),
  146. NumberFormatter.DEC_MATH_CONTEXT));
  147. default:
  148. throw new EvalException("Unexpected type " + mathType);
  149. }
  150. }
  151. public static Value multiply(Value param1, Value param2) {
  152. if(anyParamIsNull(param1, param2)) {
  153. // null propagation
  154. return NULL_VAL;
  155. }
  156. Value.Type mathType = getMathTypePrecedence(param1, param2,
  157. CoercionType.GENERAL);
  158. switch(mathType) {
  159. // case STRING: break; unsupported
  160. // case DATE: break; promoted to double
  161. // case TIME: break; promoted to double
  162. // case DATE_TIME: break; promoted to double
  163. case LONG:
  164. return toValue(param1.getAsLongInt() * param2.getAsLongInt());
  165. case DOUBLE:
  166. return toValue(param1.getAsDouble() * param2.getAsDouble());
  167. case BIG_DEC:
  168. return toValue(param1.getAsBigDecimal().multiply(
  169. param2.getAsBigDecimal(),
  170. NumberFormatter.DEC_MATH_CONTEXT));
  171. default:
  172. throw new EvalException("Unexpected type " + mathType);
  173. }
  174. }
  175. public static Value divide(Value param1, Value param2) {
  176. if(anyParamIsNull(param1, param2)) {
  177. // null propagation
  178. return NULL_VAL;
  179. }
  180. Value.Type mathType = getMathTypePrecedence(param1, param2,
  181. CoercionType.GENERAL);
  182. switch(mathType) {
  183. // case STRING: break; unsupported
  184. // case DATE: break; promoted to double
  185. // case TIME: break; promoted to double
  186. // case DATE_TIME: break; promoted to double
  187. case LONG:
  188. int lp1 = param1.getAsLongInt();
  189. int lp2 = param2.getAsLongInt();
  190. if((lp1 % lp2) == 0) {
  191. return toValue(lp1 / lp2);
  192. }
  193. return toValue((double)lp1 / (double)lp2);
  194. case DOUBLE:
  195. double d2 = param2.getAsDouble();
  196. if(d2 == 0.0d) {
  197. throw new ArithmeticException(DIV_BY_ZERO);
  198. }
  199. return toValue(param1.getAsDouble() / d2);
  200. case BIG_DEC:
  201. return toValue(divide(param1.getAsBigDecimal(), param2.getAsBigDecimal()));
  202. default:
  203. throw new EvalException("Unexpected type " + mathType);
  204. }
  205. }
  206. public static Value intDivide(Value param1, Value param2) {
  207. if(anyParamIsNull(param1, param2)) {
  208. // null propagation
  209. return NULL_VAL;
  210. }
  211. Value.Type mathType = getMathTypePrecedence(param1, param2,
  212. CoercionType.GENERAL);
  213. if(mathType == Value.Type.STRING) {
  214. throw new EvalException("Unexpected type " + mathType);
  215. }
  216. return toValue(param1.getAsLongInt() / param2.getAsLongInt());
  217. }
  218. public static Value exp(Value param1, Value param2) {
  219. if(anyParamIsNull(param1, param2)) {
  220. // null propagation
  221. return NULL_VAL;
  222. }
  223. Value.Type mathType = getMathTypePrecedence(param1, param2,
  224. CoercionType.GENERAL);
  225. if(mathType == Value.Type.BIG_DEC) {
  226. // see if we can handle the limited options supported for BigDecimal
  227. // (must be a positive int exponent)
  228. try {
  229. BigDecimal result = param1.getAsBigDecimal().pow(
  230. param2.getAsBigDecimal().intValueExact(),
  231. NumberFormatter.DEC_MATH_CONTEXT);
  232. return toValue(result);
  233. } catch(ArithmeticException ae) {
  234. // fall back to general handling via doubles...
  235. }
  236. }
  237. // jdk only supports general pow() as doubles, let's go with that
  238. double result = Math.pow(param1.getAsDouble(), param2.getAsDouble());
  239. // attempt to convert integral types back to integrals if possible
  240. if((mathType == Value.Type.LONG) && isIntegral(result)) {
  241. return toValue((int)result);
  242. }
  243. return toValue(result);
  244. }
  245. public static Value mod(Value param1, Value param2) {
  246. if(anyParamIsNull(param1, param2)) {
  247. // null propagation
  248. return NULL_VAL;
  249. }
  250. Value.Type mathType = getMathTypePrecedence(param1, param2,
  251. CoercionType.GENERAL);
  252. if(mathType == Value.Type.STRING) {
  253. throw new EvalException("Unexpected type " + mathType);
  254. }
  255. return toValue(param1.getAsLongInt() % param2.getAsLongInt());
  256. }
  257. public static Value concat(Value param1, Value param2) {
  258. // note, this op converts null to empty string
  259. if(param1.isNull()) {
  260. param1 = EMPTY_STR_VAL;
  261. }
  262. if(param2.isNull()) {
  263. param2 = EMPTY_STR_VAL;
  264. }
  265. return nonNullConcat(param1, param2);
  266. }
  267. private static Value nonNullConcat(Value param1, Value param2) {
  268. return toValue(param1.getAsString().concat(param2.getAsString()));
  269. }
  270. public static Value not(Value param1) {
  271. if(param1.isNull()) {
  272. // null propagation
  273. return NULL_VAL;
  274. }
  275. return toValue(!param1.getAsBoolean());
  276. }
  277. public static Value lessThan(Value param1, Value param2) {
  278. if(anyParamIsNull(param1, param2)) {
  279. // null propagation
  280. return NULL_VAL;
  281. }
  282. return toValue(nonNullCompareTo(param1, param2) < 0);
  283. }
  284. public static Value greaterThan(Value param1, Value param2) {
  285. if(anyParamIsNull(param1, param2)) {
  286. // null propagation
  287. return NULL_VAL;
  288. }
  289. return toValue(nonNullCompareTo(param1, param2) > 0);
  290. }
  291. public static Value lessThanEq(Value param1, Value param2) {
  292. if(anyParamIsNull(param1, param2)) {
  293. // null propagation
  294. return NULL_VAL;
  295. }
  296. return toValue(nonNullCompareTo(param1, param2) <= 0);
  297. }
  298. public static Value greaterThanEq(Value param1, Value param2) {
  299. if(anyParamIsNull(param1, param2)) {
  300. // null propagation
  301. return NULL_VAL;
  302. }
  303. return toValue(nonNullCompareTo(param1, param2) >= 0);
  304. }
  305. public static Value equals(Value param1, Value param2) {
  306. if(anyParamIsNull(param1, param2)) {
  307. // null propagation
  308. return NULL_VAL;
  309. }
  310. return toValue(nonNullCompareTo(param1, param2) == 0);
  311. }
  312. public static Value notEquals(Value param1, Value param2) {
  313. if(anyParamIsNull(param1, param2)) {
  314. // null propagation
  315. return NULL_VAL;
  316. }
  317. return toValue(nonNullCompareTo(param1, param2) != 0);
  318. }
  319. public static Value and(Value param1, Value param2) {
  320. // "and" uses short-circuit logic
  321. if(param1.isNull()) {
  322. return NULL_VAL;
  323. }
  324. boolean b1 = param1.getAsBoolean();
  325. if(!b1) {
  326. return FALSE_VAL;
  327. }
  328. if(param2.isNull()) {
  329. return NULL_VAL;
  330. }
  331. return toValue(param2.getAsBoolean());
  332. }
  333. public static Value or(Value param1, Value param2) {
  334. // "or" uses short-circuit logic
  335. if(param1.isNull()) {
  336. return NULL_VAL;
  337. }
  338. boolean b1 = param1.getAsBoolean();
  339. if(b1) {
  340. return TRUE_VAL;
  341. }
  342. if(param2.isNull()) {
  343. return NULL_VAL;
  344. }
  345. return toValue(param2.getAsBoolean());
  346. }
  347. public static Value eqv(Value param1, Value param2) {
  348. if(anyParamIsNull(param1, param2)) {
  349. // null propagation
  350. return NULL_VAL;
  351. }
  352. boolean b1 = param1.getAsBoolean();
  353. boolean b2 = param2.getAsBoolean();
  354. return toValue(b1 == b2);
  355. }
  356. public static Value xor(Value param1, Value param2) {
  357. if(anyParamIsNull(param1, param2)) {
  358. // null propagation
  359. return NULL_VAL;
  360. }
  361. boolean b1 = param1.getAsBoolean();
  362. boolean b2 = param2.getAsBoolean();
  363. return toValue(b1 ^ b2);
  364. }
  365. public static Value imp(Value param1, Value param2) {
  366. // "imp" uses short-circuit logic
  367. if(param1.isNull()) {
  368. if(param2.isNull() || !param2.getAsBoolean()) {
  369. // null propagation
  370. return NULL_VAL;
  371. }
  372. return TRUE_VAL;
  373. }
  374. boolean b1 = param1.getAsBoolean();
  375. if(!b1) {
  376. return TRUE_VAL;
  377. }
  378. if(param2.isNull()) {
  379. // null propagation
  380. return NULL_VAL;
  381. }
  382. return toValue(param2.getAsBoolean());
  383. }
  384. public static Value isNull(Value param1) {
  385. return toValue(param1.isNull());
  386. }
  387. public static Value isNotNull(Value param1) {
  388. return toValue(!param1.isNull());
  389. }
  390. public static Value like(Value param1, Pattern pattern) {
  391. if(param1.isNull()) {
  392. // null propagation
  393. return NULL_VAL;
  394. }
  395. return toValue(pattern.matcher(param1.getAsString()).matches());
  396. }
  397. public static Value notLike(Value param1, Pattern pattern) {
  398. return not(like(param1, pattern));
  399. }
  400. public static Value between(Value param1, Value param2, Value param3) {
  401. // null propagate any param. uses short circuit eval of params
  402. if(anyParamIsNull(param1, param2, param3)) {
  403. // null propagation
  404. return NULL_VAL;
  405. }
  406. // the between values can be in either order!?!
  407. Value min = param2;
  408. Value max = param3;
  409. Value gt = greaterThan(min, max);
  410. if(gt.getAsBoolean()) {
  411. min = param3;
  412. max = param2;
  413. }
  414. return and(greaterThanEq(param1, min), lessThanEq(param1, max));
  415. }
  416. public static Value notBetween(Value param1, Value param2, Value param3) {
  417. return not(between(param1, param2, param3));
  418. }
  419. public static Value in(Value param1, Value[] params) {
  420. // null propagate any param. uses short circuit eval of params
  421. if(param1.isNull()) {
  422. // null propagation
  423. return NULL_VAL;
  424. }
  425. for(Value val : params) {
  426. if(val.isNull()) {
  427. continue;
  428. }
  429. Value eq = equals(param1, val);
  430. if(eq.getAsBoolean()) {
  431. return TRUE_VAL;
  432. }
  433. }
  434. return FALSE_VAL;
  435. }
  436. public static Value notIn(Value param1, Value[] params) {
  437. return not(in(param1, params));
  438. }
  439. private static boolean anyParamIsNull(Value param1, Value param2) {
  440. return (param1.isNull() || param2.isNull());
  441. }
  442. private static boolean anyParamIsNull(Value param1, Value param2,
  443. Value param3) {
  444. return (param1.isNull() || param2.isNull() || param3.isNull());
  445. }
  446. protected static int nonNullCompareTo(
  447. Value param1, Value param2)
  448. {
  449. // note that comparison does not do string to num coercion
  450. Value.Type compareType = getMathTypePrecedence(param1, param2,
  451. CoercionType.COMPARE);
  452. switch(compareType) {
  453. case STRING:
  454. // string comparison is only valid if _both_ params are strings
  455. if(param1.getType() != param2.getType()) {
  456. throw new EvalException("Unexpected type " + compareType);
  457. }
  458. return param1.getAsString().compareToIgnoreCase(param2.getAsString());
  459. // case DATE: break; promoted to double
  460. // case TIME: break; promoted to double
  461. // case DATE_TIME: break; promoted to double
  462. case LONG:
  463. return param1.getAsLongInt().compareTo(param2.getAsLongInt());
  464. case DOUBLE:
  465. return param1.getAsDouble().compareTo(param2.getAsDouble());
  466. case BIG_DEC:
  467. return param1.getAsBigDecimal().compareTo(param2.getAsBigDecimal());
  468. default:
  469. throw new EvalException("Unexpected type " + compareType);
  470. }
  471. }
  472. public static Value toValue(boolean b) {
  473. return (b ? TRUE_VAL : FALSE_VAL);
  474. }
  475. public static Value toValue(String s) {
  476. return new StringValue(s);
  477. }
  478. public static Value toValue(int i) {
  479. return new LongValue(i);
  480. }
  481. public static Value toValue(Integer i) {
  482. return new LongValue(i);
  483. }
  484. public static Value toValue(float f) {
  485. return new DoubleValue((double)f);
  486. }
  487. public static Value toValue(double s) {
  488. return new DoubleValue(s);
  489. }
  490. public static Value toValue(Double s) {
  491. return new DoubleValue(s);
  492. }
  493. public static Value toValue(BigDecimal s) {
  494. return new BigDecimalValue(normalize(s));
  495. }
  496. public static Value toValue(Value.Type type, double dd, DateFormat fmt) {
  497. return toValue(type, new Date(ColumnImpl.fromDateDouble(
  498. dd, fmt.getCalendar())), fmt);
  499. }
  500. public static Value toValue(EvalContext ctx, Value.Type type, Date d) {
  501. return toValue(type, d, getDateFormatForType(ctx, type));
  502. }
  503. public static Value toValue(Value.Type type, Date d, DateFormat fmt) {
  504. switch(type) {
  505. case DATE:
  506. return new DateValue(d, fmt);
  507. case TIME:
  508. return new TimeValue(d, fmt);
  509. case DATE_TIME:
  510. return new DateTimeValue(d, fmt);
  511. default:
  512. throw new EvalException("Unexpected date/time type " + type);
  513. }
  514. }
  515. static Value toDateValue(EvalContext ctx, Value.Type type, double v,
  516. Value param1, Value param2)
  517. {
  518. DateFormat fmt = null;
  519. if((param1 instanceof BaseDateValue) && (param1.getType() == type)) {
  520. fmt = ((BaseDateValue)param1).getFormat();
  521. } else if((param2 instanceof BaseDateValue) && (param2.getType() == type)) {
  522. fmt = ((BaseDateValue)param2).getFormat();
  523. } else {
  524. fmt = getDateFormatForType(ctx, type);
  525. }
  526. Date d = new Date(ColumnImpl.fromDateDouble(v, fmt.getCalendar()));
  527. return toValue(type, d, fmt);
  528. }
  529. static DateFormat getDateFormatForType(EvalContext ctx, Value.Type type) {
  530. String fmtStr = null;
  531. switch(type) {
  532. case DATE:
  533. fmtStr = ctx.getTemporalConfig().getDefaultDateFormat();
  534. break;
  535. case TIME:
  536. fmtStr = ctx.getTemporalConfig().getDefaultTimeFormat();
  537. break;
  538. case DATE_TIME:
  539. fmtStr = ctx.getTemporalConfig().getDefaultDateTimeFormat();
  540. break;
  541. default:
  542. throw new EvalException("Unexpected date/time type " + type);
  543. }
  544. return ctx.createDateFormat(fmtStr);
  545. }
  546. private static Value.Type getMathTypePrecedence(
  547. Value param1, Value param2, CoercionType cType)
  548. {
  549. Value.Type t1 = param1.getType();
  550. Value.Type t2 = param2.getType();
  551. // note: for general math, date/time become double
  552. if(t1 == t2) {
  553. if(!cType._preferTemporal && t1.isTemporal()) {
  554. return t1.getPreferredNumericType();
  555. }
  556. return t1;
  557. }
  558. if((t1 == Value.Type.STRING) || (t2 == Value.Type.STRING)) {
  559. if(cType._allowCoerceStringToNum) {
  560. // see if this is mixed string/numeric and the string can be coerced
  561. // to a number
  562. Value.Type numericType = coerceStringToNumeric(param1, param2, cType);
  563. if(numericType != null) {
  564. // string can be coerced to number
  565. return numericType;
  566. }
  567. }
  568. // string always wins
  569. return Value.Type.STRING;
  570. }
  571. // for "simple" math, keep as date/times
  572. if(cType._preferTemporal &&
  573. (t1.isTemporal() || t2.isTemporal())) {
  574. return (t1.isTemporal() ?
  575. (t2.isTemporal() ?
  576. // for mixed temporal types, always go to date/time
  577. Value.Type.DATE_TIME : t1) :
  578. t2);
  579. }
  580. return getPreferredNumericType(t1.getPreferredNumericType(),
  581. t2.getPreferredNumericType());
  582. }
  583. private static Value.Type getPreferredNumericType(Value.Type t1, Value.Type t2)
  584. {
  585. // if both types are integral, choose "largest"
  586. if(t1.isIntegral() && t2.isIntegral()) {
  587. return max(t1, t2);
  588. }
  589. // choose largest relevant floating-point type
  590. return max(t1.getPreferredFPType(), t2.getPreferredFPType());
  591. }
  592. private static Value.Type coerceStringToNumeric(
  593. Value param1, Value param2, CoercionType cType) {
  594. Value.Type t1 = param1.getType();
  595. Value.Type t2 = param2.getType();
  596. Value.Type prefType = null;
  597. Value strParam = null;
  598. if(t1.isNumeric()) {
  599. prefType = t1;
  600. strParam = param2;
  601. } else if(t2.isNumeric()) {
  602. prefType = t2;
  603. strParam = param1;
  604. } else if(t1.isTemporal()) {
  605. prefType = (cType._preferTemporal ? t1 : t1.getPreferredNumericType());
  606. strParam = param2;
  607. } else if(t2.isTemporal()) {
  608. prefType = (cType._preferTemporal ? t2 : t2.getPreferredNumericType());
  609. strParam = param1;
  610. } else {
  611. // no numeric type involved
  612. return null;
  613. }
  614. try {
  615. // see if string can be coerced to a number
  616. strParam.getAsBigDecimal();
  617. if(prefType.isNumeric()) {
  618. // seems like when strings are coerced to numbers, they are usually
  619. // doubles, unless the current context is decimal
  620. prefType = ((prefType == Value.Type.BIG_DEC) ?
  621. Value.Type.BIG_DEC : Value.Type.DOUBLE);
  622. }
  623. return prefType;
  624. } catch(NumberFormatException ignored) {
  625. // not a number
  626. }
  627. return null;
  628. }
  629. private static Value.Type max(Value.Type t1, Value.Type t2) {
  630. return ((t1.compareTo(t2) > 0) ? t1 : t2);
  631. }
  632. static BigDecimal divide(BigDecimal num, BigDecimal denom) {
  633. return num.divide(denom, NumberFormatter.DEC_MATH_CONTEXT);
  634. }
  635. static boolean isIntegral(double d) {
  636. double id = Math.rint(d);
  637. return ((d == id) && (d >= MIN_INT) && (d <= MAX_INT) &&
  638. !Double.isInfinite(d) && !Double.isNaN(d));
  639. }
  640. /**
  641. * Converts the given BigDecimal to the minimal scale >= 0;
  642. */
  643. static BigDecimal normalize(BigDecimal bd) {
  644. if(bd.scale() == 0) {
  645. return bd;
  646. }
  647. // handle a bug in the jdk which doesn't strip zero values
  648. if(bd.compareTo(BigDecimal.ZERO) == 0) {
  649. return BigDecimal.ZERO;
  650. }
  651. bd = bd.stripTrailingZeros();
  652. if(bd.scale() < 0) {
  653. bd = bd.setScale(0);
  654. }
  655. return bd;
  656. }
  657. }