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 }