| package org.apache.velocity.tools; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.io.File; |
| import java.lang.reflect.Array; |
| import java.net.URL; |
| import java.text.DateFormat; |
| import java.text.DecimalFormat; |
| import java.text.DecimalFormatSymbols; |
| import java.text.NumberFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Calendar; |
| import java.util.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.TimeZone; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| /** |
| * Utility methods for parsing or otherwise converting between types. |
| * Current supported types are Number, Date, Calendar, |
| * String, Boolean, Locale and URL |
| * |
| * @author Nathan Bubna |
| */ |
| public class ConversionUtils |
| { |
| public static final ConversionUtils INSTANCE = new ConversionUtils(); |
| |
| private static final int STYLE_NUMBER = 0; |
| private static final int STYLE_CURRENCY = 1; |
| private static final int STYLE_PERCENT = 2; |
| //NOTE: '3' belongs to a non-public "scientific" style |
| private static final int STYLE_INTEGER = 4; |
| |
| /* Java DateFormat standard constants extensions */ |
| private static final int STYLE_INTL = 5; /* international format without time zone*/ |
| private static final int STYLE_ISO = 6; /* international format with timezone (RFC 3339) */ |
| |
| |
| /* iso/intl date/time formats (locale-independant) */ |
| private static DateFormat isoDateFormat = new SimpleDateFormat("yyyy-MM-dd"); /* date for iso/intl */ |
| private static DateFormat intlTimeFormat = new SimpleDateFormat("HH:mm:ss"); /* time without timezone */ |
| private static DateFormat isoTimeFormat = new SimpleDateFormat("HH:mm:ssXXX"); /* time with ISO 8601 timezone */ |
| private static DateFormat intlTimestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /* timestamp without timezone */ |
| private static DateFormat isoTimestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssXXX"); /* timestamp with ISO 88601 timezone */ |
| |
| // cache custom formats |
| private static ConcurrentMap<String,NumberFormat> customFormatsCache = new ConcurrentHashMap<String,NumberFormat>(); |
| |
| private ConversionUtils() {} |
| |
| public ConversionUtils getInstance() |
| { |
| return INSTANCE; |
| } |
| |
| |
| /** |
| * Returns a {@link NumberFormat} instance for the specified |
| * format and {@link Locale}. If the format specified is a standard |
| * style pattern, then a number instance |
| * will be returned with the number style set to the |
| * specified style. If it is a custom format, then a customized |
| * {@link NumberFormat} will be returned. |
| * |
| * @param format the custom or standard formatting pattern to be used |
| * @param locale the {@link Locale} to be used |
| * @return an instance of {@link NumberFormat} |
| * @see NumberFormat |
| */ |
| public static NumberFormat getNumberFormat(String format, Locale locale) |
| { |
| if (format == null || locale == null) |
| { |
| return null; |
| } |
| |
| NumberFormat nf = null; |
| int style = getNumberStyleAsInt(format); |
| if (style < 0) |
| { |
| // we have a custom format |
| String cacheKey = format + "%" + locale.toString(); |
| nf = customFormatsCache.get(cacheKey); |
| if( nf == null ) |
| { |
| nf = new DecimalFormat(format, new DecimalFormatSymbols(locale)); |
| customFormatsCache.put(cacheKey,nf); |
| } |
| } |
| else |
| { |
| // we have a standard format |
| nf = getNumberFormat(style, locale); |
| } |
| return nf; |
| } |
| |
| /** |
| * Returns a {@link NumberFormat} instance for the specified |
| * number style and {@link Locale}. |
| * |
| * @param numberStyle the number style (number will be ignored if this is |
| * less than zero or the number style is not recognized) |
| * @param locale the {@link Locale} to be used |
| * @return an instance of {@link NumberFormat} or <code>null</code> |
| * if an instance cannot be constructed with the given |
| * parameters |
| */ |
| public static NumberFormat getNumberFormat(int numberStyle, Locale locale) |
| { |
| try |
| { |
| NumberFormat nf; |
| switch (numberStyle) |
| { |
| case STYLE_NUMBER: |
| nf = NumberFormat.getNumberInstance(locale); |
| break; |
| case STYLE_CURRENCY: |
| nf = NumberFormat.getCurrencyInstance(locale); |
| break; |
| case STYLE_PERCENT: |
| nf = NumberFormat.getPercentInstance(locale); |
| break; |
| case STYLE_INTEGER: |
| nf = NumberFormat.getIntegerInstance(locale); |
| break; |
| default: |
| // invalid style was specified, return null |
| nf = null; |
| } |
| return nf; |
| } |
| catch (Exception suppressed) |
| { |
| // let it go... |
| return null; |
| } |
| } |
| |
| /** |
| * Checks a string to see if it matches one of the standard |
| * NumberFormat style patterns: |
| * number, currency, percent, integer, or default. |
| * if it does it will return the integer constant for that pattern. |
| * if not, it will return -1. |
| * |
| * @see NumberFormat |
| * @param style the string to be checked |
| * @return the int identifying the style pattern |
| */ |
| public static int getNumberStyleAsInt(String style) |
| { |
| // avoid needlessly running through all the string comparisons |
| if (style == null || style.length() < 6 || style.length() > 8) { |
| return -1; |
| } |
| if (style.equalsIgnoreCase("default")) |
| { |
| //NOTE: java.text.NumberFormat returns "number" instances |
| // as the default (at least in Java 1.3 and 1.4). |
| return STYLE_NUMBER; |
| } |
| if (style.equalsIgnoreCase("number")) |
| { |
| return STYLE_NUMBER; |
| } |
| if (style.equalsIgnoreCase("currency")) |
| { |
| return STYLE_CURRENCY; |
| } |
| if (style.equalsIgnoreCase("percent")) |
| { |
| return STYLE_PERCENT; |
| } |
| if (style.equalsIgnoreCase("integer")) |
| { |
| return STYLE_INTEGER; |
| } |
| // ok, it's not any of the standard patterns |
| return -1; |
| } |
| |
| |
| // ----------------- number conversion methods --------------- |
| |
| /** |
| * Attempts to convert an unidentified {@link Object} into a {@link Number}, |
| * just short of turning it into a string and parsing it. In other words, |
| * this will convert to {@link Number} from a {@link Number}, {@link Calendar}, |
| * or {@link Date}. If it can't do that, it will get the string value and have |
| * {@link #toNumber(String,String,Locale)} try to parse it using the |
| * default Locale and format. |
| |
| * @param obj - the object to convert |
| */ |
| public static Number toNumber(Object obj) |
| { |
| return toNumber(obj, true); |
| } |
| |
| /** |
| * Just like {@link #toNumber(Object)} except that you can tell |
| * this to attempt parsing the object as a String by passing {@code true} |
| * as the second parameter. If you do so, then it will have |
| * {@link #toNumber(String,String,Locale)} try to parse it using the |
| * default Locale and format. |
| */ |
| public static Number toNumber(Object obj, boolean handleStrings) |
| { |
| if (obj == null) |
| { |
| return null; |
| } |
| if (obj instanceof Number) |
| { |
| return (Number)obj; |
| } |
| if (obj instanceof Date) |
| { |
| return Long.valueOf(((Date)obj).getTime()); |
| } |
| if (obj instanceof Calendar) |
| { |
| Date date = ((Calendar)obj).getTime(); |
| return Long.valueOf(date.getTime()); |
| } |
| if (handleStrings) |
| { |
| // try parsing with default format and locale |
| return toNumber(obj.toString(), "default", Locale.getDefault()); |
| } |
| return null; |
| } |
| |
| /** |
| * Converts a string to an instance of {@link Number} using the |
| * specified format and {@link Locale} to parse it. |
| * |
| * @param value - the string to convert |
| * @param format - the format the number is in |
| * @param locale - the {@link Locale} |
| * @return the string as a {@link Number} or <code>null</code> if no |
| * conversion is possible |
| * @see NumberFormat#parse |
| */ |
| public static Number toNumber(String value, String format, Locale locale) |
| { |
| if (value == null || format == null || locale == null) |
| { |
| return null; |
| } |
| try |
| { |
| NumberFormat parser = getNumberFormat(format, locale); |
| return parser.parse(value); |
| } |
| catch (Exception e) |
| { |
| return null; |
| } |
| } |
| |
| /** |
| * Converts an object to an instance of {@link Number} using the |
| * specified format and {@link Locale} to parse it, if necessary. |
| * |
| * @param value - the object to convert |
| * @param format - the format the number is in |
| * @param locale - the {@link Locale} |
| * @return the object as a {@link Number} or <code>null</code> if no |
| * conversion is possible |
| * @see NumberFormat#parse |
| */ |
| public static Number toNumber(Object value, String format, Locale locale) |
| { |
| // first try the easy stuff |
| Number number = toNumber(value, false); |
| if (number != null) |
| { |
| return number; |
| } |
| |
| // turn it into a string and try parsing it |
| return toNumber(String.valueOf(value), format, locale); |
| } |
| |
| |
| // -------------------------- DateFormat creation methods -------------- |
| |
| /** |
| * Returns a {@link DateFormat} instance for the specified |
| * format, {@link Locale}, and {@link TimeZone}. If the format |
| * specified is a standard style pattern, then a date-time instance |
| * will be returned with both the date and time styles set to the |
| * specified style. If it is a custom format, then a customized |
| * {@link SimpleDateFormat} will be returned. |
| * |
| * @param format the custom or standard formatting pattern to be used |
| * @param locale the {@link Locale} to be used |
| * @param timezone the {@link TimeZone} to be used |
| * @return an instance of {@link DateFormat} |
| * @see SimpleDateFormat |
| * @see DateFormat |
| */ |
| public static DateFormat getDateFormat(String format, Locale locale, |
| TimeZone timezone) |
| { |
| if (format == null) |
| { |
| return null; |
| } |
| |
| DateFormat df = null; |
| // do they want a date instance |
| if (format.endsWith("_date")) |
| { |
| String fmt = format.substring(0, format.length() - 5); |
| int style = getDateStyleAsInt(fmt); |
| df = getDateFormat(style, -1, locale, timezone); |
| } |
| // do they want a time instance? |
| else if (format.endsWith("_time")) |
| { |
| String fmt = format.substring(0, format.length() - 5); |
| int style = getDateStyleAsInt(fmt); |
| df = getDateFormat(-1, style, locale, timezone); |
| } |
| // ok, they either want a custom or date-time instance |
| else |
| { |
| int style = getDateStyleAsInt(format); |
| if (style < 0) |
| { |
| // we have a custom format |
| df = new SimpleDateFormat(format, locale); |
| df.setTimeZone(timezone); |
| } |
| else |
| { |
| // they want a date-time instance |
| df = getDateFormat(style, style, locale, timezone); |
| } |
| } |
| return df; |
| } |
| |
| /** |
| * Returns a {@link DateFormat} instance for the specified |
| * date style, time style, {@link Locale}, and {@link TimeZone}. |
| * |
| * @param dateStyle the date style |
| * @param timeStyle the time style |
| * @param locale the {@link Locale} to be used |
| * @param timezone the {@link TimeZone} to be used |
| * @return an instance of {@link DateFormat} |
| * @see #getDateFormat(int timeStyle, int dateStyle, Locale locale, TimeZone timezone) |
| */ |
| public static DateFormat getDateFormat(String dateStyle, String timeStyle, |
| Locale locale, TimeZone timezone) |
| { |
| int ds = getDateStyleAsInt(dateStyle); |
| int ts = getDateStyleAsInt(timeStyle); |
| return getDateFormat(ds, ts, locale, timezone); |
| } |
| |
| /** |
| * Returns a {@link DateFormat} instance for the specified |
| * time style, date style, {@link Locale}, and {@link TimeZone}. |
| * |
| * @param dateStyle the date style (date will be ignored if this is |
| * less than zero and the date style is not) |
| * @param timeStyle the time style (time will be ignored if this is |
| * less than zero and the date style is not) |
| * @param locale the {@link Locale} to be used |
| * @param timezone the {@link TimeZone} to be used |
| * @return an instance of {@link DateFormat} or <code>null</code> |
| * if an instance cannot be constructed with the given |
| * parameters |
| */ |
| public static DateFormat getDateFormat(int dateStyle, int timeStyle, |
| Locale locale, TimeZone timezone) |
| { |
| try |
| { |
| DateFormat df; |
| if (dateStyle < 0 && timeStyle < 0) |
| { |
| // no style was specified, use default instance |
| df = DateFormat.getInstance(); |
| } |
| else if (timeStyle < 0) |
| { |
| // only a date style was specified |
| switch (dateStyle) |
| { |
| case STYLE_INTL: |
| case STYLE_ISO: |
| df = isoDateFormat; |
| break; |
| default: |
| df = DateFormat.getDateInstance(dateStyle, locale); |
| df.setTimeZone(timezone); |
| break; |
| } |
| } |
| else if (dateStyle < 0) |
| { |
| // only a time style was specified |
| switch (timeStyle) |
| { |
| case STYLE_INTL: |
| df = intlTimeFormat; |
| break; |
| case STYLE_ISO: |
| df = (DateFormat) isoTimeFormat.clone(); |
| df.setTimeZone(timezone); |
| break; |
| default: |
| df = DateFormat.getTimeInstance(timeStyle, locale); |
| df.setTimeZone(timezone); |
| break; |
| } |
| } |
| else |
| { |
| switch (dateStyle) |
| { |
| case STYLE_INTL: |
| df = intlTimestampFormat; |
| break; |
| case STYLE_ISO: |
| df = (DateFormat) isoTimestampFormat.clone(); |
| df.setTimeZone(timezone); |
| break; |
| default: |
| df = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale); |
| df.setTimeZone(timezone); |
| break; |
| } |
| } |
| return df; |
| } |
| catch (Exception suppressed) |
| { |
| // let it go... |
| return null; |
| } |
| } |
| |
| static Map<String, Integer> stylesMap; |
| static |
| { |
| stylesMap = new HashMap<String, Integer>(); |
| stylesMap.put("FULL", DateFormat.FULL); |
| stylesMap.put("LONG", DateFormat.LONG); |
| stylesMap.put("MEDIUM", DateFormat.MEDIUM); |
| stylesMap.put("SHORT", DateFormat.SHORT); |
| stylesMap.put("DEFAULT", DateFormat.DEFAULT); |
| stylesMap.put("INTL", STYLE_INTL); |
| stylesMap.put("ISO", STYLE_ISO); |
| } |
| |
| /** |
| * Checks a string to see if it matches one of the standard DateFormat |
| * style patterns: full, long, medium, short, or default. If it does, |
| * it will return the integer constant for that pattern. If not, it |
| * will return -1. |
| * |
| * @see DateFormat |
| * @param style the string to be checked |
| * @return the int identifying the style pattern |
| */ |
| public static int getDateStyleAsInt(String style) |
| { |
| Integer intStyle = stylesMap.get(style.toUpperCase()); |
| return intStyle == null ? -1 : intStyle.intValue(); |
| } |
| |
| |
| // ----------------- date conversion methods --------------- |
| |
| /** |
| * Attempts to convert an unidentified {@link Object} into a {@link Date}, |
| * just short of turning it into a string and parsing it. In other words, |
| * this will convert to {@link Date} from a {@link Date}, {@link Calendar}, |
| * or {@link Number}. If it can't do that, it will return {@code null}. |
| * |
| * @param obj - the object to convert |
| */ |
| public static Date toDate(Object obj) |
| { |
| if (obj == null) |
| { |
| return null; |
| } |
| if (obj instanceof Date) |
| { |
| return (Date)obj; |
| } |
| if (obj instanceof Calendar) |
| { |
| return ((Calendar)obj).getTime(); |
| } |
| if (obj instanceof Number) |
| { |
| Date d = new Date(); |
| d.setTime(((Number)obj).longValue()); |
| return d; |
| } |
| return null; |
| } |
| |
| /** |
| * Converts an object to an instance of {@link Date} using the |
| * specified format, {@link Locale}, and {@link TimeZone} if the |
| * object is not already an instance of Date, Calendar, or Long. |
| * |
| * @param obj - the date to convert |
| * @param format - the format the date is in |
| * @param locale - the {@link Locale} |
| * @param timezone - the {@link TimeZone} |
| * @return the object as a {@link Date} or <code>null</code> if no |
| * conversion is possible |
| * @see #getDateFormat |
| * @see SimpleDateFormat#parse |
| */ |
| public static Date toDate(Object obj, String format, |
| Locale locale, TimeZone timezone) |
| { |
| // first try the easy stuff |
| Date date = toDate(obj); |
| if (date != null) |
| { |
| return date; |
| } |
| |
| // turn it into a string and try parsing it |
| return toDate(String.valueOf(obj), format, locale, timezone); |
| } |
| |
| /** |
| * Converts an object to an instance of {@link Date} using the |
| * specified format, {@link Locale}, and {@link TimeZone} if the |
| * object is not already an instance of Date, Calendar, or Long. |
| * |
| * @param str - the string to parse |
| * @param format - the format the date is in |
| * @param locale - the {@link Locale} |
| * @param timezone - the {@link TimeZone} |
| * @return the string as a {@link Date} or <code>null</code> if the |
| * parsing fails |
| * @see #getDateFormat |
| * @see SimpleDateFormat#parse |
| */ |
| public static Date toDate(String str, String format, |
| Locale locale, TimeZone timezone) |
| { |
| try |
| { |
| //try parsing w/a customized SimpleDateFormat |
| DateFormat parser = getDateFormat(format, locale, timezone); |
| return parser.parse(str); |
| } |
| catch (Exception e) |
| { |
| return null; |
| } |
| } |
| |
| public static Calendar toCalendar(Date date, Locale locale) |
| { |
| if (date == null) |
| { |
| return null; |
| } |
| |
| Calendar cal; |
| if (locale == null) |
| { |
| cal = Calendar.getInstance(); |
| } |
| else |
| { |
| cal = Calendar.getInstance(locale); |
| } |
| cal.setTime(date); |
| // HACK: Force all fields to update. see link for explanation of this. |
| //http://java.sun.com/j2se/1.4/docs/api/java/util/Calendar.html |
| cal.getTime(); |
| return cal; |
| } |
| |
| |
| // ----------------- misc conversion methods --------------- |
| |
| /** |
| * Converts objects to String in a more Tools-ish way than |
| * String.valueOf(Object), especially with nulls, Arrays and Collections. |
| * Null returns null, Arrays and Collections return their first value, |
| * or null if they have no values. |
| * |
| * @param value the object to be turned into a String |
| * @return the string value of the object or null if the value is null |
| * or it is an array whose first value is null |
| */ |
| public static String toString(Object value) |
| { |
| if (value instanceof String) |
| { |
| return (String)value; |
| } |
| if (value == null) |
| { |
| return null; |
| } |
| if (value.getClass().isArray()) |
| { |
| if (Array.getLength(value) > 0) |
| { |
| // recurse on the first value |
| return toString(Array.get(value, 0)); |
| } |
| return null; |
| } |
| return String.valueOf(value); |
| } |
| |
| /** |
| * Returns the first value as a String, if any; otherwise returns null. |
| * |
| * @param values the Collection to be turned into a string |
| * @return the string value of the first object in the collection |
| * or null if the collection is empty |
| */ |
| public static String toString(Collection values) |
| { |
| if (values != null && !values.isEmpty()) |
| { |
| // recurse on the first value |
| return toString(values.iterator().next()); |
| } |
| return null; |
| } |
| |
| /** |
| * Converts any Object to a boolean using {@link #toString(Object)} |
| * and {@link Boolean#valueOf(String)}. |
| * |
| * @param value the object to be converted |
| * @return a {@link Boolean} object for the specified value or |
| * <code>null</code> if the value is null or the conversion failed |
| */ |
| public static Boolean toBoolean(Object value) |
| { |
| if (value instanceof Boolean) |
| { |
| return (Boolean)value; |
| } |
| |
| String s = toString(value); |
| return (s != null) ? Boolean.valueOf(s) : null; |
| } |
| |
| /** |
| * Converts a string to a {@link Locale} |
| * |
| * @param value - the string to parse |
| * @return the {@link Locale} or <code>null</code> if the |
| * parsing fails |
| */ |
| public static Locale toLocale(String value) |
| { |
| if (value.indexOf('_') < 0) |
| { |
| return new Locale(value); |
| } |
| |
| String[] params = value.split("_"); |
| if (params.length == 2) |
| { |
| return new Locale(params[0], params[1]); |
| } |
| else if (params.length == 3) |
| { |
| return new Locale(params[0], params[1], params[2]); |
| } |
| else |
| { |
| // there's only 3 possible params, so this must be invalid |
| return null; |
| } |
| } |
| |
| /** |
| * Converts a string to a {@link URL}. It will first try to |
| * treat the string as a File name, then a classpath resource, |
| * then finally as a literal URL. If none of these work, then |
| * this will return {@code null}. |
| * |
| * @param value - the string to parse |
| * @return the {@link URL} form of the string or {@code null} |
| * @see File |
| * @see ClassUtils#getResource(String,Object) |
| * @see URL |
| */ |
| public static URL toURL(String value) |
| { |
| return toURL(value, ConversionUtils.class); |
| } |
| |
| /** |
| * Converts a string to a {@link URL}. It will first try to |
| * treat the string as a File name, then a classpath resource, |
| * then finally as a literal URL. If none of these work, then |
| * this will return {@code null}. |
| * |
| * @param value - the string to parse |
| * @param caller - the object or Class seeking the url |
| * @return the {@link URL} form of the string or {@code null} |
| * @see File |
| * @see ClassUtils#getResource(String,Object) |
| * @see URL |
| */ |
| public static URL toURL(String value, Object caller) |
| { |
| try |
| { |
| File file = new File(value); |
| if (file.exists()) |
| { |
| return file.toURI().toURL(); |
| } |
| } |
| catch (Exception e) {} |
| try |
| { |
| URL url = ClassUtils.getResource(value, caller); |
| if (url != null) |
| { |
| return url; |
| } |
| } |
| catch (Exception e) {} |
| try |
| { |
| return new URL(value); |
| } |
| catch (Exception e) {} |
| return null; |
| } |
| |
| } |