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,v 1.17 2007/06/06 17:07:52 taqua Exp $
  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.FormulaContext;
  49: import org.jfree.formula.LocalizationContext;
  50: import org.jfree.formula.lvalues.TypeValuePair;
  51: import org.jfree.formula.typing.coretypes.AnyType;
  52: import org.jfree.formula.typing.coretypes.DateTimeType;
  53: import org.jfree.formula.typing.coretypes.LogicalType;
  54: import org.jfree.formula.typing.coretypes.NumberType;
  55: import org.jfree.formula.typing.coretypes.TextType;
  56: import org.jfree.formula.util.DateUtil;
  57: import org.jfree.util.Configuration;
  58: import org.jfree.util.ObjectUtilities;
  59: 
  60: /**
  61:  * Creation-Date: 02.11.2006, 12:46:08
  62:  * 
  63:  * @author Thomas Morgner
  64:  */
  65: public class DefaultTypeRegistry implements TypeRegistry
  66: {
  67:   private FormulaContext context;
  68: 
  69:   private static final long MILLISECS_PER_DAY = 24 * 60 * 60 * 1000;
  70: 
  71:   private static final BigDecimal ZERO = new BigDecimal(0);
  72: 
  73:   private NumberFormat[] numberFormats;
  74: 
  75:   public DefaultTypeRegistry()
  76:   {
  77:   }
  78: 
  79:   /**
  80:    * Returns an comparator for the given types.
  81:    * 
  82:    * @param type1
  83:    * @param type2
  84:    * @return
  85:    */
  86:   public ExtendedComparator getComparator(final Type type1, final Type type2)
  87:   {
  88:     final DefaultComparator comparator = new DefaultComparator();
  89:     comparator.inititalize(context);
  90:     return comparator;
  91:   }
  92: 
  93:   /**
  94:    * converts the object of the given type into a number. If the object is not
  95:    * convertible, a NumberFormatException is thrown. If the given value is null
  96:    * or not parsable as number, return null.
  97:    * 
  98:    * @param type1
  99:    * @param value
 100:    * @return
 101:    * @throws NumberFormatException
 102:    *           if the type cannot be represented as number.
 103:    */
 104:   public Number convertToNumber(final Type type1, final Object value)
 105:       throws TypeConversionException
 106:   {
 107:     final LocalizationContext localizationContext = context.getLocalizationContext();
 108: 
 109:     if (value == null)
 110:     {
 111:       // there's no point in digging deeper - there *is* no value ..
 112:       throw new TypeConversionException();
 113:     }
 114: 
 115:     if (type1.isFlagSet(Type.NUMERIC_TYPE) || type1.isFlagSet(Type.ANY_TYPE))
 116:     {
 117:       if (type1.isFlagSet(Type.DATETIME_TYPE)
 118:           || type1.isFlagSet(Type.TIME_TYPE) || type1.isFlagSet(Type.DATE_TYPE)
 119:           || type1.isFlagSet(Type.ANY_TYPE))
 120:       {
 121:         if (value instanceof Date)
 122:         {
 123:           final Number serial = DateUtil.toSerialDate((Date) value, localizationContext);
 124: //           System.out.println(serial);
 125:           final Number ret = DateUtil.normalizeDate(serial, type1);
 126:           // System.out.println(ret);
 127:           return ret;
 128:         }
 129:       }
 130: 
 131:       if (value instanceof Number)
 132:       {
 133:         return (Number) value;
 134:       }
 135:     }
 136: 
 137:     if (type1.isFlagSet(Type.LOGICAL_TYPE) || type1.isFlagSet(Type.ANY_TYPE))
 138:     {
 139:       if (value instanceof Boolean)
 140:       {
 141:         if (Boolean.TRUE.equals(value))
 142:         {
 143:           return new Integer(1);
 144:         }
 145:         else
 146:         {
 147:           return new Integer(0);
 148:         }
 149:       }
 150:     }
 151: 
 152:     if (type1.isFlagSet(Type.TEXT_TYPE) || type1.isFlagSet(Type.ANY_TYPE))
 153:     {
 154:       final String val = value.toString();
 155: 
 156:       // first, try to parse the value as a big-decimal.
 157:       try
 158:       {
 159:         return new BigDecimal(val);
 160:       }
 161:       catch (NumberFormatException e)
 162:       {
 163:         // ignore ..
 164:       }
 165: 
 166:       // then checking for datetimes
 167:       final Iterator datetimeIterator = localizationContext.getDateFormats(DateTimeType.DATETIME_TYPE).iterator();
 168:       while (datetimeIterator.hasNext())
 169:       {
 170:         final DateFormat df = (DateFormat) datetimeIterator.next();
 171:         try
 172:         {
 173:           final Date date = df.parse(val);
 174:           final Number serial = DateUtil.toSerialDate(date, localizationContext);
 175:           // return DateUtil.normalizeDate(serial, DateTimeType.TYPE);
 176:           return serial;
 177:         }
 178:         catch (ParseException e)
 179:         {
 180:           // ignore as well ..
 181:         }
 182:       }
 183:       // then checking for datetimes
 184:       final Iterator dateIterator = localizationContext.getDateFormats(DateTimeType.DATE_TYPE).iterator();
 185:       while (dateIterator.hasNext())
 186:       {
 187:         final DateFormat df = (DateFormat) dateIterator.next();
 188:         try
 189:         {
 190:           final Date date = df.parse(val);
 191:           final Number serial = DateUtil.toSerialDate(date, localizationContext);
 192:           // return DateUtil.normalizeDate(serial, DateType.TYPE);
 193:           return serial;
 194:         }
 195:         catch (ParseException e)
 196:         {
 197:           // ignore as well ..
 198:         }
 199:       }
 200:       // then checking for datetimes
 201:       final Iterator timeIterator = localizationContext
 202:           .getDateFormats(DateTimeType.TIME_TYPE).iterator();
 203:       while (timeIterator.hasNext())
 204:       {
 205:         final DateFormat df = (DateFormat) timeIterator.next();
 206:         try
 207:         {
 208:           final Date date = df.parse(val);
 209:           final Number serial = DateUtil.toSerialDate(date, localizationContext);
 210:           // return DateUtil.normalizeDate(serial, TimeType.TYPE);
 211:           return serial;
 212:         }
 213:         catch (ParseException e)
 214:         {
 215:           // ignore as well ..
 216:         }
 217:       }
 218: 
 219:       // then checking for numbers
 220:       for (int i = 0; i < numberFormats.length; i++)
 221:       {
 222:         try
 223:         {
 224:           final NumberFormat format = numberFormats[i];
 225:           return format.parse(val);
 226:         }
 227:         catch (ParseException e)
 228:         {
 229:           // ignore ..
 230:         }
 231:       }
 232:     }
 233: 
 234:     throw new TypeConversionException();
 235:   }
 236: 
 237:   public void initialize(final Configuration configuration,
 238:       final FormulaContext formulaContext)
 239:   {
 240:     this.context = formulaContext;
 241:     this.numberFormats = loadNumberFormats();
 242:   }
 243: 
 244:   protected NumberFormat[] loadNumberFormats()
 245:   {
 246:     final ArrayList formats = new ArrayList();
 247:     final DecimalFormat defaultFormat = new DecimalFormat("#0.###",
 248:         new DecimalFormatSymbols(Locale.US));
 249:     activateBigDecimalMode(defaultFormat);
 250:     formats.add(defaultFormat);
 251: 
 252:     return (NumberFormat[]) formats.toArray(new NumberFormat[formats.size()]);
 253:   }
 254: 
 255:   private void activateBigDecimalMode(final DecimalFormat format)
 256:   {
 257:     if (ObjectUtilities.isJDK14())
 258:     {
 259:       try
 260:       {
 261:         final Method method = DecimalFormat.class.getMethod(
 262:             "setParseBigDecimal", new Class[]
 263:             { Boolean.TYPE });
 264:         method.invoke(format, new Object[]
 265:         { Boolean.TRUE });
 266:       }
 267:       catch (Exception e)
 268:       {
 269:         // ignore it, as it will always fail on JDK 1.4 or lower ..
 270:       }
 271:     }
 272:   }
 273: 
 274:   public String convertToText(final Type type1, final Object value)
 275:       throws TypeConversionException
 276:   {
 277:     if (value == null)
 278:     {
 279:       return "";
 280:     }
 281: 
 282:     // already converted or compatible
 283:     if (type1.isFlagSet(Type.TEXT_TYPE))
 284:     {
 285:       // no need to check whatever it is a String
 286:       return value.toString();
 287:     }
 288: 
 289:     if (type1.isFlagSet(Type.LOGICAL_TYPE))
 290:     {
 291:       if (value instanceof Boolean)
 292:       {
 293:         final Boolean b = (Boolean) value;
 294:         if (Boolean.TRUE.equals(b))
 295:         {
 296:           return "TRUE";
 297:         }
 298:         else
 299:         {
 300:           return "FALSE";
 301:         }
 302:       }
 303:       else
 304:       {
 305:         throw new TypeConversionException();
 306:       }
 307:     }
 308: 
 309:     // 2 types of numeric : numbers and dates
 310:     if (type1.isFlagSet(Type.NUMERIC_TYPE))
 311:     {
 312:       if (type1.isFlagSet(Type.DATETIME_TYPE)
 313:           || type1.isFlagSet(Type.DATE_TYPE) || type1.isFlagSet(Type.TIME_TYPE))
 314:       {
 315:         final Date d = convertToDate(type1, value);
 316:         final List dateFormats = context.getLocalizationContext()
 317:             .getDateFormats(type1);
 318:         if (dateFormats != null && dateFormats.size() >= 1)
 319:         {
 320:           final DateFormat format = (DateFormat) dateFormats.get(0);
 321:           return format.format(d);
 322:         }
 323:         else
 324:         {
 325:           // fallback
 326:           return SimpleDateFormat.getDateTimeInstance(
 327:               SimpleDateFormat.FULL, SimpleDateFormat.FULL).format(d);
 328:         }
 329:       }
 330:       else
 331:       {
 332:         try
 333:         {
 334:           final Number n = convertToNumber(type1, value);
 335:           final NumberFormat format = getDefaultNumberFormat();
 336:           return format.format(n);
 337:         }
 338:         catch (TypeConversionException nfe)
 339:         {
 340:           // ignore ..
 341:         }
 342:       }
 343:     }
 344: 
 345:     // fallback
 346:     return value.toString();
 347:   }
 348: 
 349:   public Boolean convertToLogical(final Type type1, final Object value)
 350:       throws TypeConversionException
 351:   {
 352:     if (value == null)
 353:     {
 354:       return Boolean.FALSE;
 355:     }
 356: 
 357:     // already converted or compatible
 358:     if (type1.isFlagSet(Type.LOGICAL_TYPE) || type1.isFlagSet(Type.ANY_TYPE))
 359:     {
 360:       if (value instanceof Boolean)
 361:       {
 362:         return (Boolean) value;
 363:       }
 364: 
 365:       // fallback
 366:       return new Boolean(value.toString());
 367:     }
 368: 
 369:     if (type1.isFlagSet(Type.NUMERIC_TYPE))
 370:     {
 371:       // no need to check between different types of numeric
 372:       if (value instanceof Number)
 373:       {
 374:         final Number num = (Number) value;
 375:         if (!ZERO.equals(num))
 376:         {
 377:           return Boolean.TRUE;
 378:         }
 379:       }
 380: 
 381:       // fallback
 382:       return Boolean.FALSE;
 383:     }
 384: 
 385:     if (type1.isFlagSet(Type.TEXT_TYPE))
 386:     {
 387:       // no need to convert it to String
 388:       final String str = value.toString();
 389:       if ("TRUE".equalsIgnoreCase(str))
 390:       {
 391:         return Boolean.TRUE;
 392:       }
 393:       else if ("FALSE".equalsIgnoreCase(str))
 394:       {
 395:         return Boolean.FALSE;
 396:       }
 397:     }
 398: 
 399:     throw new TypeConversionException();
 400:   }
 401: 
 402:   public Date convertToDate(final Type type1, final Object value)
 403:       throws TypeConversionException
 404:   {
 405:     if (type1.isFlagSet(Type.NUMERIC_TYPE) || type1.isFlagSet(Type.ANY_TYPE))
 406:     {
 407:       if (type1.isFlagSet(Type.DATE_TYPE)
 408:           || type1.isFlagSet(Type.DATETIME_TYPE)
 409:           || type1.isFlagSet(Type.TIME_TYPE) || type1.isFlagSet(Type.ANY_TYPE))
 410:       {
 411:         if (value instanceof Date)
 412:         {
 413:           return DateUtil.normalizeDate((Date) value, type1);
 414:         }
 415:       }
 416:     }
 417:     final Number serial = convertToNumber(type1, value);
 418:     return DateUtil.toJavaDate(serial, context.getLocalizationContext());
 419:   }
 420: 
 421:   protected NumberFormat getDefaultNumberFormat()
 422:   {
 423:     final Locale locale = context.getLocalizationContext().getLocale();
 424:     return new DecimalFormat("#0.#########", new DecimalFormatSymbols(locale));
 425:   }
 426: 
 427:   /**
 428:    * Checks, whether the target type would accept the specified value object and
 429:    * value type.<br/> This method is called for auto conversion of fonction
 430:    * parameters using the conversion type declared by the function metadata.
 431:    * 
 432:    * @param targetType
 433:    * @param valueType
 434:    * @param value
 435:    */
 436:   public TypeValuePair convertTo(final Type targetType,
 437:       final TypeValuePair valuePair) throws TypeConversionException
 438:   {
 439:     if (targetType.isFlagSet(Type.ARRAY_TYPE))
 440:     {
 441:       // Array conversion requested.
 442:       if (valuePair.getType().isFlagSet(Type.ARRAY_TYPE))
 443:       {
 444:         return convertArrayToArray(targetType, valuePair);
 445:       }
 446:       else
 447:       {
 448:         final Object retval = convertPlainToPlain(targetType, valuePair
 449:             .getType(), valuePair.getValue());
 450:         return new TypeValuePair(targetType, new Object[]
 451:         { retval });
 452:       }
 453:     }
 454: 
 455:     final Object value = valuePair.getValue();
 456:     final Object o = convertPlainToPlain(targetType, valuePair.getType(), value);
 457:     if (value == o)
 458:     {
 459:       return valuePair;
 460:     }
 461:     return new TypeValuePair(targetType, o);
 462:   }
 463: 
 464:   private Object convertPlainToPlain(final Type targetType, final Type type,
 465:       final Object value) throws TypeConversionException
 466:   {
 467:     // lazy check
 468:     // if (targetType.equals(type))
 469:     // {
 470:     // return value;
 471:     // }
 472: 
 473:     if (targetType.isFlagSet(Type.NUMERIC_TYPE))
 474:     {
 475:       final Number serial = convertToNumber(type, value);
 476:       if (targetType.isFlagSet(Type.DATE_TYPE)
 477:           || targetType.isFlagSet(Type.DATETIME_TYPE)
 478:           || targetType.isFlagSet(Type.TIME_TYPE))
 479:       {
 480:         final Number normalizedSerial = DateUtil.normalizeDate(serial,
 481:             targetType);
 482:         final Date toJavaDate = DateUtil.toJavaDate(normalizedSerial, context
 483:                     .getLocalizationContext());
 484:         return DateUtil.normalizeDate(toJavaDate, targetType, false);
 485:       }
 486:       return serial;
 487:     }
 488:     else if (targetType.isFlagSet(Type.LOGICAL_TYPE))
 489:     {
 490:       if (type.isFlagSet(Type.LOGICAL_TYPE))
 491:       {
 492:         return value;
 493:       }
 494: 
 495:       return convertToLogical(type, value);
 496:     }
 497:     else if (targetType.isFlagSet(Type.TEXT_TYPE))
 498:     {
 499:       return convertToText(type, value);
 500:     }
 501: 
 502:     // Unknown type - ignore it, crash later :)
 503:     return value;
 504:   }
 505: 
 506:   private TypeValuePair convertArrayToArray(final Type targetType,
 507:       final TypeValuePair pair) throws TypeConversionException
 508:   {
 509:     final Type type = pair.getType();
 510:     // the pair value is also an array ...
 511:     final Object[] array = (Object[]) pair.getValue();
 512:     final Object[] target = new Object[array.length];
 513:     for (int i = 0; i < array.length; i++)
 514:     {
 515:       final Object converted = convertPlainToPlain(targetType, type, array[i]);
 516:       if (converted == null)
 517:       {
 518:         return null;
 519:       }
 520:       target[i] = converted;
 521:     }
 522:     return new TypeValuePair(targetType, target);
 523:   }
 524: 
 525:   public Type guessTypeOfObject(Object o)
 526:   {
 527:     if (o instanceof Number)
 528:     {
 529:       return NumberType.GENERIC_NUMBER;
 530:     }
 531:     else if (o instanceof Date)
 532:     {
 533:       return DateTimeType.DATETIME_TYPE;
 534:     }
 535:     else if (o instanceof Time)
 536:     {
 537:       return DateTimeType.TIME_TYPE;
 538:     }
 539:     else if (o instanceof java.sql.Date)
 540:     {
 541:       return DateTimeType.DATE_TYPE;
 542:     }
 543:     else if (o instanceof Boolean)
 544:     {
 545:       return LogicalType.TYPE;
 546:     }
 547:     else if (o instanceof String)
 548:     {
 549:       return TextType.TYPE;
 550:     }
 551: 
 552:     return AnyType.TYPE;
 553:   }
 554: }