Source for org.jfree.formula.typing.DefaultTypeRegistry

   1: /**
   2:  * =========================================
   3:  * LibFormula : a free Java formula library
   4:  * =========================================
   5:  *
   6:  * Project Info:  http://reporting.pentaho.org/libformula/
   7:  *
   8:  * (C) Copyright 2006-2007, by Pentaho Corporation and Contributors.
   9:  *
  10:  * This library is free software; you can redistribute it and/or modify it under the terms
  11:  * of the GNU Lesser General Public License as published by the Free Software Foundation;
  12:  * either version 2.1 of the License, or (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  15:  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  16:  * See the GNU Lesser General Public License for more details.
  17:  *
  18:  * You should have received a copy of the GNU Lesser General Public License along with this
  19:  * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  20:  * Boston, MA 02111-1307, USA.
  21:  *
  22:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  23:  * in the United States and other countries.]
  24:  *
  25:  *
  26:  * ------------
  27:  * $Id: DefaultTypeRegistry.java 3521 2007-10-16 10:55:14Z tmorgner $
  28:  * ------------
  29:  * (C) Copyright 2006-2007, by Pentaho Corporation.
  30:  */
  31: package org.jfree.formula.typing;
  32: 
  33: import java.lang.reflect.Method;
  34: import java.math.BigDecimal;
  35: import java.sql.Time;
  36: import java.text.DateFormat;
  37: import java.text.DecimalFormat;
  38: import java.text.DecimalFormatSymbols;
  39: import java.text.NumberFormat;
  40: import java.text.ParseException;
  41: import java.text.SimpleDateFormat;
  42: import java.util.ArrayList;
  43: import java.util.Date;
  44: import java.util.Iterator;
  45: import java.util.List;
  46: import java.util.Locale;
  47: 
  48: import org.jfree.formula.EvaluationException;
  49: import org.jfree.formula.FormulaContext;
  50: import org.jfree.formula.LocalizationContext;
  51: import org.jfree.formula.lvalues.LValue;
  52: import org.jfree.formula.lvalues.TypeValuePair;
  53: import org.jfree.formula.typing.coretypes.AnyType;
  54: import org.jfree.formula.typing.coretypes.DateTimeType;
  55: import org.jfree.formula.typing.coretypes.LogicalType;
  56: import org.jfree.formula.typing.coretypes.NumberType;
  57: import org.jfree.formula.typing.coretypes.TextType;
  58: import org.jfree.formula.typing.sequence.NumberSequence;
  59: import org.jfree.formula.util.DateUtil;
  60: import org.jfree.util.Configuration;
  61: import org.jfree.util.ObjectUtilities;
  62: 
  63: /**
  64:  * Creation-Date: 02.11.2006, 12:46:08
  65:  *
  66:  * @author Thomas Morgner
  67:  */
  68: public class DefaultTypeRegistry implements TypeRegistry
  69: {
  70: 
  71:   private static class ArrayConverterCallback implements ArrayCallback
  72:   {
  73:     private Object retval;
  74:     private Type targetType;
  75: 
  76:     private ArrayConverterCallback(final Object retval, final Type targetType)
  77:     {
  78:       this.retval = retval;
  79:       this.targetType = targetType;
  80:     }
  81: 
  82:     public LValue getRaw(final int row, final int column)
  83:     {
  84:       return null;
  85:     }
  86: 
  87:     public Object getValue(final int row, final int column) throws EvaluationException
  88:     {
  89:       if (row == 0 && column == 0)
  90:       {
  91:         return retval;
  92:       }
  93:       return null;
  94:     }
  95: 
  96:     public Type getType(final int row, final int column) throws EvaluationException
  97:     {
  98:       if (row == 0 && column == 0)
  99:       {
 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: }