001 /** 002 * ========================================= 003 * LibFormula : a free Java formula library 004 * ========================================= 005 * 006 * Project Info: http://reporting.pentaho.org/libformula/ 007 * 008 * (C) Copyright 2006-2007, by Pentaho Corporation and Contributors. 009 * 010 * This library is free software; you can redistribute it and/or modify it under the terms 011 * of the GNU Lesser General Public License as published by the Free Software Foundation; 012 * either version 2.1 of the License, or (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 015 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 016 * See the GNU Lesser General Public License for more details. 017 * 018 * You should have received a copy of the GNU Lesser General Public License along with this 019 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, 020 * Boston, MA 02111-1307, USA. 021 * 022 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 023 * in the United States and other countries.] 024 * 025 * 026 * ------------ 027 * $Id: DefaultTypeRegistry.java 3521 2007-10-16 10:55:14Z tmorgner $ 028 * ------------ 029 * (C) Copyright 2006-2007, by Pentaho Corporation. 030 */ 031 package org.jfree.formula.typing; 032 033 import java.lang.reflect.Method; 034 import java.math.BigDecimal; 035 import java.sql.Time; 036 import java.text.DateFormat; 037 import java.text.DecimalFormat; 038 import java.text.DecimalFormatSymbols; 039 import java.text.NumberFormat; 040 import java.text.ParseException; 041 import java.text.SimpleDateFormat; 042 import java.util.ArrayList; 043 import java.util.Date; 044 import java.util.Iterator; 045 import java.util.List; 046 import java.util.Locale; 047 048 import org.jfree.formula.EvaluationException; 049 import org.jfree.formula.FormulaContext; 050 import org.jfree.formula.LocalizationContext; 051 import org.jfree.formula.lvalues.LValue; 052 import org.jfree.formula.lvalues.TypeValuePair; 053 import org.jfree.formula.typing.coretypes.AnyType; 054 import org.jfree.formula.typing.coretypes.DateTimeType; 055 import org.jfree.formula.typing.coretypes.LogicalType; 056 import org.jfree.formula.typing.coretypes.NumberType; 057 import org.jfree.formula.typing.coretypes.TextType; 058 import org.jfree.formula.typing.sequence.NumberSequence; 059 import org.jfree.formula.util.DateUtil; 060 import org.jfree.util.Configuration; 061 import org.jfree.util.ObjectUtilities; 062 063 /** 064 * Creation-Date: 02.11.2006, 12:46:08 065 * 066 * @author Thomas Morgner 067 */ 068 public class DefaultTypeRegistry implements TypeRegistry 069 { 070 071 private static class ArrayConverterCallback implements ArrayCallback 072 { 073 private Object retval; 074 private Type targetType; 075 076 private ArrayConverterCallback(final Object retval, final Type targetType) 077 { 078 this.retval = retval; 079 this.targetType = targetType; 080 } 081 082 public LValue getRaw(final int row, final int column) 083 { 084 return null; 085 } 086 087 public Object getValue(final int row, final int column) throws EvaluationException 088 { 089 if (row == 0 && column == 0) 090 { 091 return retval; 092 } 093 return null; 094 } 095 096 public Type getType(final int row, final int column) throws EvaluationException 097 { 098 if (row == 0 && column == 0) 099 { 100 return targetType; 101 } 102 return null; 103 } 104 105 public int getColumnCount() 106 { 107 return 1; 108 } 109 110 public int getRowCount() 111 { 112 return 1; 113 } 114 } 115 116 private FormulaContext context; 117 118 private static final BigDecimal ZERO = new BigDecimal(0); 119 120 private NumberFormat[] numberFormats; 121 122 public DefaultTypeRegistry() 123 { 124 } 125 126 /** 127 * Returns an comparator for the given types. 128 * 129 * @param type1 130 * @param type2 131 * @return 132 */ 133 public ExtendedComparator getComparator(final Type type1, final Type type2) 134 { 135 final DefaultComparator comparator = new DefaultComparator(); 136 comparator.inititalize(context); 137 return comparator; 138 } 139 140 /** 141 * converts the object of the given type into a number. If the object is not convertible, a NumberFormatException is 142 * thrown. If the given value is null or not parsable as number, return null. 143 * 144 * @param type1 145 * @param value 146 * @return 147 * @throws NumberFormatException if the type cannot be represented as number. 148 */ 149 public Number convertToNumber(final Type type1, final Object value) 150 throws TypeConversionException 151 { 152 final LocalizationContext localizationContext = context.getLocalizationContext(); 153 154 if (value == null) 155 { 156 // there's no point in digging deeper - there *is* no value .. 157 throw new TypeConversionException(); 158 } 159 160 if (type1.isFlagSet(Type.NUMERIC_TYPE) || type1.isFlagSet(Type.ANY_TYPE)) 161 { 162 if (type1.isFlagSet(Type.DATETIME_TYPE) 163 || type1.isFlagSet(Type.TIME_TYPE) || type1.isFlagSet(Type.DATE_TYPE) 164 || type1.isFlagSet(Type.ANY_TYPE)) 165 { 166 if (value instanceof Date) 167 { 168 final Number serial = DateUtil.toSerialDate((Date) value, localizationContext); 169 // System.out.println(serial); 170 // System.out.println(ret); 171 return DateUtil.normalizeDate(serial, type1); 172 } 173 } 174 175 if (value instanceof Number) 176 { 177 return (Number) value; 178 } 179 } 180 181 if (type1.isFlagSet(Type.LOGICAL_TYPE) || type1.isFlagSet(Type.ANY_TYPE)) 182 { 183 if (value instanceof Boolean) 184 { 185 if (Boolean.TRUE.equals(value)) 186 { 187 return new Integer(1); 188 } 189 else 190 { 191 return new Integer(0); 192 } 193 } 194 } 195 196 if (type1.isFlagSet(Type.TEXT_TYPE) || type1.isFlagSet(Type.ANY_TYPE)) 197 { 198 final String val = value.toString(); 199 200 // first, try to parse the value as a big-decimal. 201 try 202 { 203 return new BigDecimal(val); 204 } 205 catch (NumberFormatException e) 206 { 207 // ignore .. 208 } 209 210 // then checking for datetimes 211 final Iterator datetimeIterator = localizationContext.getDateFormats(DateTimeType.DATETIME_TYPE).iterator(); 212 while (datetimeIterator.hasNext()) 213 { 214 final DateFormat df = (DateFormat) datetimeIterator.next(); 215 try 216 { 217 final Date date = df.parse(val); 218 // return DateUtil.normalizeDate(serial, DateTimeType.TYPE); 219 return DateUtil.toSerialDate(date, localizationContext); 220 } 221 catch (ParseException e) 222 { 223 // ignore as well .. 224 } 225 } 226 // then checking for datetimes 227 final Iterator dateIterator = localizationContext.getDateFormats(DateTimeType.DATE_TYPE).iterator(); 228 while (dateIterator.hasNext()) 229 { 230 final DateFormat df = (DateFormat) dateIterator.next(); 231 try 232 { 233 final Date date = df.parse(val); 234 // return DateUtil.normalizeDate(serial, DateType.TYPE); 235 return DateUtil.toSerialDate(date, localizationContext); 236 } 237 catch (ParseException e) 238 { 239 // ignore as well .. 240 } 241 } 242 // then checking for datetimes 243 final Iterator timeIterator = localizationContext 244 .getDateFormats(DateTimeType.TIME_TYPE).iterator(); 245 while (timeIterator.hasNext()) 246 { 247 final DateFormat df = (DateFormat) timeIterator.next(); 248 try 249 { 250 final Date date = df.parse(val); 251 // return DateUtil.normalizeDate(serial, TimeType.TYPE); 252 return DateUtil.toSerialDate(date, localizationContext); 253 } 254 catch (ParseException e) 255 { 256 // ignore as well .. 257 } 258 } 259 260 // then checking for numbers 261 for (int i = 0; i < numberFormats.length; i++) 262 { 263 try 264 { 265 final NumberFormat format = numberFormats[i]; 266 return format.parse(val); 267 } 268 catch (ParseException e) 269 { 270 // ignore .. 271 } 272 } 273 } 274 275 throw new TypeConversionException(); 276 } 277 278 public void initialize(final Configuration configuration, 279 final FormulaContext formulaContext) 280 { 281 this.context = formulaContext; 282 this.numberFormats = loadNumberFormats(); 283 } 284 285 protected NumberFormat[] loadNumberFormats() 286 { 287 final ArrayList formats = new ArrayList(); 288 final DecimalFormat defaultFormat = new DecimalFormat("#0.###", 289 new DecimalFormatSymbols(Locale.US)); 290 activateBigDecimalMode(defaultFormat); 291 formats.add(defaultFormat); 292 293 return (NumberFormat[]) formats.toArray(new NumberFormat[formats.size()]); 294 } 295 296 private void activateBigDecimalMode(final DecimalFormat format) 297 { 298 if (ObjectUtilities.isJDK14()) 299 { 300 try 301 { 302 final Method method = DecimalFormat.class.getMethod( 303 "setParseBigDecimal", new Class[] 304 {Boolean.TYPE}); 305 method.invoke(format, new Object[] 306 {Boolean.TRUE}); 307 } 308 catch (Exception e) 309 { 310 // ignore it, as it will always fail on JDK 1.4 or lower .. 311 } 312 } 313 } 314 315 public String convertToText(final Type type1, final Object value) 316 throws TypeConversionException 317 { 318 if (value == null) 319 { 320 return ""; 321 } 322 323 // already converted or compatible 324 if (type1.isFlagSet(Type.TEXT_TYPE)) 325 { 326 // no need to check whatever it is a String 327 return value.toString(); 328 } 329 330 if (type1.isFlagSet(Type.LOGICAL_TYPE)) 331 { 332 if (value instanceof Boolean) 333 { 334 final Boolean b = (Boolean) value; 335 if (Boolean.TRUE.equals(b)) 336 { 337 return "TRUE"; 338 } 339 else 340 { 341 return "FALSE"; 342 } 343 } 344 else 345 { 346 throw new TypeConversionException(); 347 } 348 } 349 350 // 2 types of numeric : numbers and dates 351 if (type1.isFlagSet(Type.NUMERIC_TYPE)) 352 { 353 if (type1.isFlagSet(Type.DATETIME_TYPE) 354 || type1.isFlagSet(Type.DATE_TYPE) || type1.isFlagSet(Type.TIME_TYPE)) 355 { 356 final Date d = convertToDate(type1, value); 357 final List dateFormats = context.getLocalizationContext() 358 .getDateFormats(type1); 359 if (dateFormats != null && dateFormats.size() >= 1) 360 { 361 final DateFormat format = (DateFormat) dateFormats.get(0); 362 return format.format(d); 363 } 364 else 365 { 366 // fallback 367 return SimpleDateFormat.getDateTimeInstance( 368 SimpleDateFormat.FULL, SimpleDateFormat.FULL).format(d); 369 } 370 } 371 else 372 { 373 try 374 { 375 final Number n = convertToNumber(type1, value); 376 final NumberFormat format = getDefaultNumberFormat(); 377 return format.format(n); 378 } 379 catch (TypeConversionException nfe) 380 { 381 // ignore .. 382 } 383 } 384 } 385 386 // fallback 387 return value.toString(); 388 } 389 390 public Boolean convertToLogical(final Type type1, final Object value) 391 throws TypeConversionException 392 { 393 if (value == null) 394 { 395 return Boolean.FALSE; 396 } 397 398 // already converted or compatible 399 if (type1.isFlagSet(Type.LOGICAL_TYPE) || type1.isFlagSet(Type.ANY_TYPE)) 400 { 401 if (value instanceof Boolean) 402 { 403 return (Boolean) value; 404 } 405 406 // fallback 407 if ("true".equalsIgnoreCase(String.valueOf(value))) 408 { 409 return Boolean.TRUE; 410 } 411 return Boolean.FALSE; 412 } 413 414 if (type1.isFlagSet(Type.NUMERIC_TYPE)) 415 { 416 // no need to check between different types of numeric 417 if (value instanceof Number) 418 { 419 final Number num = (Number) value; 420 if (!ZERO.equals(num)) 421 { 422 return Boolean.TRUE; 423 } 424 } 425 426 // fallback 427 return Boolean.FALSE; 428 } 429 430 if (type1.isFlagSet(Type.TEXT_TYPE)) 431 { 432 // no need to convert it to String 433 final String str = value.toString(); 434 if ("TRUE".equalsIgnoreCase(str)) 435 { 436 return Boolean.TRUE; 437 } 438 else if ("FALSE".equalsIgnoreCase(str)) 439 { 440 return Boolean.FALSE; 441 } 442 } 443 444 throw new TypeConversionException(); 445 } 446 447 public Date convertToDate(final Type type1, final Object value) 448 throws TypeConversionException 449 { 450 if (type1.isFlagSet(Type.NUMERIC_TYPE) || type1.isFlagSet(Type.ANY_TYPE)) 451 { 452 if (type1.isFlagSet(Type.DATE_TYPE) 453 || type1.isFlagSet(Type.DATETIME_TYPE) 454 || type1.isFlagSet(Type.TIME_TYPE) || type1.isFlagSet(Type.ANY_TYPE)) 455 { 456 if (value instanceof Date) 457 { 458 return DateUtil.normalizeDate((Date) value, type1); 459 } 460 } 461 } 462 final Number serial = convertToNumber(type1, value); 463 return DateUtil.toJavaDate(serial, context.getLocalizationContext()); 464 } 465 466 protected NumberFormat getDefaultNumberFormat() 467 { 468 final Locale locale = context.getLocalizationContext().getLocale(); 469 return new DecimalFormat("#0.#########", new DecimalFormatSymbols(locale)); 470 } 471 472 private TypeValuePair convertToSequence(final Type targetType, final TypeValuePair valuePair) 473 throws TypeConversionException 474 { 475 if (targetType.isFlagSet(Type.NUMERIC_TYPE)) 476 { 477 return new TypeValuePair(targetType, convertToNumberSequence(valuePair.getType(), valuePair.getValue())); 478 } 479 throw new TypeConversionException(); 480 } 481 482 public NumberSequence convertToNumberSequence(final Type type, final Object value) throws TypeConversionException 483 { 484 // sequence array 485 if (type.isFlagSet(Type.NUMERIC_SEQUENCE_TYPE)) 486 { 487 if (value instanceof NumberSequence) 488 { 489 return (NumberSequence) value; 490 } 491 else 492 { 493 throw new TypeConversionException(); 494 } 495 } 496 // array 497 if (type.isFlagSet(Type.ARRAY_TYPE)) 498 { 499 if (value instanceof ArrayCallback) 500 { 501 return new NumberSequence((ArrayCallback) value, context); 502 } 503 else 504 { 505 throw new TypeConversionException(); 506 } 507 } 508 // else scalar 509 if (type.isFlagSet(Type.SCALAR_TYPE) && type.isFlagSet(Type.NUMERIC_TYPE)) 510 { 511 return new NumberSequence(convertToNumber(type, value), context); 512 } 513 else 514 { 515 throw new TypeConversionException(); 516 } 517 } 518 519 /** 520 * Checks, whether the target type would accept the specified value object and value type.<br/> This method is called 521 * for auto conversion of fonction parameters using the conversion type declared by the function metadata. 522 * 523 * @param targetType 524 * @param valuePair 525 */ 526 public TypeValuePair convertTo(final Type targetType, 527 final TypeValuePair valuePair) throws TypeConversionException 528 { 529 if (targetType.isFlagSet(Type.ARRAY_TYPE)) 530 { 531 // Array conversion requested. 532 if (valuePair.getType().isFlagSet(Type.ARRAY_TYPE)) 533 { 534 if (valuePair.getType().isFlagSet(Type.SEQUENCE_TYPE)) 535 { 536 return convertToSequence(targetType, valuePair); 537 } 538 else 539 { 540 if (targetType.isFlagSet(Type.SEQUENCE_TYPE)) 541 { 542 //System.out.println(targetType.isFlagSet(Type.ARRAY_TYPE) + " " + valuePair.getType().isFlagSet(Type.ARRAY_TYPE) + " " + valuePair.getValue()); 543 return convertToSequence(targetType, valuePair); 544 } 545 else 546 { 547 return convertArrayToArray(targetType, valuePair); 548 } 549 } 550 } 551 else // convertion of a scalar to an array 552 { 553 if (targetType.isFlagSet(Type.SEQUENCE_TYPE)) 554 { 555 return convertToSequence(targetType, valuePair); 556 } 557 else 558 { 559 final Object retval = convertPlainToPlain(targetType, valuePair.getType(), valuePair.getValue()); 560 return new TypeValuePair(targetType, new ArrayConverterCallback(retval, targetType)); 561 } 562 } 563 } 564 565 // else scalar 566 final Object value = valuePair.getValue(); 567 final Object o = convertPlainToPlain(targetType, valuePair.getType(), value); 568 if (value == o) 569 { 570 return valuePair; 571 } 572 return new TypeValuePair(targetType, o); 573 } 574 575 private Object convertPlainToPlain(final Type targetType, final Type type, 576 final Object value) throws TypeConversionException 577 { 578 if (targetType.isFlagSet(Type.NUMERIC_TYPE)) 579 { 580 if (targetType.isFlagSet(Type.LOGICAL_TYPE)) 581 { 582 if (type.isFlagSet(Type.LOGICAL_TYPE)) 583 { 584 return value; 585 } 586 587 return convertToLogical(type, value); 588 } 589 590 final Number serial = convertToNumber(type, value); 591 if (targetType.isFlagSet(Type.DATE_TYPE) 592 || targetType.isFlagSet(Type.DATETIME_TYPE) 593 || targetType.isFlagSet(Type.TIME_TYPE)) 594 { 595 final Number normalizedSerial = DateUtil.normalizeDate(serial, 596 targetType); 597 final Date toJavaDate = DateUtil.toJavaDate(normalizedSerial, context 598 .getLocalizationContext()); 599 return DateUtil.normalizeDate(toJavaDate, targetType, false); 600 } 601 return serial; 602 } 603 else if (targetType.isFlagSet(Type.TEXT_TYPE)) 604 { 605 return convertToText(type, value); 606 } 607 608 // Unknown type - ignore it, crash later :) 609 return value; 610 } 611 612 private TypeValuePair convertArrayToArray(final Type targetType, 613 final TypeValuePair pair) throws TypeConversionException 614 { 615 final Object value = pair.getValue(); 616 if (value instanceof ArrayCallback) 617 { 618 final ArrayCallback array = (ArrayCallback) value; 619 for (int i = 0; i < array.getRowCount(); i++) 620 { 621 for (int j = 0; j < array.getColumnCount(); j++) 622 { 623 //TODO 624 throw new UnsupportedOperationException("Not implemented exception"); 625 } 626 } 627 } 628 629 throw new TypeConversionException(); 630 } 631 632 public Type guessTypeOfObject(final Object o) 633 { 634 if (o instanceof Number) 635 { 636 return NumberType.GENERIC_NUMBER; 637 } 638 else if (o instanceof Time) 639 { 640 return DateTimeType.TIME_TYPE; 641 } 642 else if (o instanceof java.sql.Date) 643 { 644 return DateTimeType.DATE_TYPE; 645 } 646 else if (o instanceof Date) 647 { 648 return DateTimeType.DATETIME_TYPE; 649 } 650 else if (o instanceof Boolean) 651 { 652 return LogicalType.TYPE; 653 } 654 else if (o instanceof String) 655 { 656 return TextType.TYPE; 657 } 658 659 return AnyType.TYPE; 660 } 661 }