zcommon/src/main/java/org/zkoss/xel/fn/CommonFns.java
/* CommonFns.java
Purpose:
Description:
History:
Wed Apr 20 18:35:21 2005, Created by tomyeh
Copyright (C) 2005 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.xel.fn;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.List;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.lang.reflect.Field;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.lang.Classes;
import org.zkoss.lang.Objects;
import org.zkoss.mesg.Messages;
import org.zkoss.util.Locales;
import org.zkoss.util.TimeZones;
import org.zkoss.util.resource.Labels;
import org.zkoss.text.DateFormats;
import org.zkoss.text.MessageFormats;
/**
* Common functions used with EL.
*
* @author tomyeh
*/
public class CommonFns {
private static final Logger log = LoggerFactory.getLogger(CommonFns.class);
protected CommonFns() {}
/** Converts the specified object to a boolean.
*/
public static boolean toBoolean(Object val) {
return ((Boolean)Classes.coerce(boolean.class, val)).booleanValue();
}
/** Converts the specified object to a string.
*/
public static String toString(Object val) {
return (String)Classes.coerce(String.class, val);
}
/** Converts the specified object to a number.
*/
public static Number toNumber(Object val) {
return (Number)Classes.coerce(Number.class, val);
}
/** Converts the specified object to an integer.
*/
public static int toInt(Object val) {
return ((Integer)Classes.coerce(int.class, val)).intValue();
}
/** Converts the specified object to a (big) decimal.
*/
public static BigDecimal toDecimal(Object val) {
return (BigDecimal)Classes.coerce(BigDecimal.class, val);
}
/** Converts the specified object to an character.
*/
public static char toChar(Object val) {
return ((Character)Classes.coerce(char.class, val)).charValue();
}
/** Tests whether an object, o, is an instance of a class, c.
*/
public static boolean isInstance(Object c, Object o) {
if (c instanceof Class) {
return ((Class)c).isInstance(o);
} else if (c instanceof String) {
try {
return Classes.forNameByThread((String)c).isInstance(o);
} catch (ClassNotFoundException ex) {
throw new IllegalArgumentException("Class not found: "+c);
}
} else {
throw new IllegalArgumentException("Unknown class: "+c);
}
}
/** Returns the label or message of the specified key.
* <ul>
* <li>If key is "mesg:class:MMM", Messages.get(class.MMM) is called</li>
* <li>Otherwise, {@link Labels#getLabel(String)} is called.
* </ul>
* @see #getLabel(String, Object[])
*/
public static final String getLabel(String key) {
if (key == null)
return "";
if (key.startsWith("mesg:")) {
final int j = key.lastIndexOf(':');
if (j > 5) {
final String clsnm = key.substring(5, j);
final String fldnm = key.substring(j + 1);
try {
final Class cls = Classes.forNameByThread(clsnm);
final Field fld = cls.getField(fldnm);
return Messages.get(((Integer) fld.get(null)).intValue());
} catch (ClassNotFoundException ex) {
log.warn("Class not found: {}", clsnm, ex);
} catch (NoSuchFieldException ex) {
log.warn("Field not found: {}", fldnm, ex);
} catch (IllegalAccessException ex) {
log.warn("Field not accessible: {}", fldnm, ex);
}
} else if (log.isDebugEnabled()) {
log.debug("Not a valid format: {}", key);
}
}
return Labels.getLabel(key);
}
/** Returns the label of the specified key and formats it
* with the specified argument, or null if not found.
*
* <p>It first uses {@link #getLabel(String)} to load the label.
* Then, it, if not null, invokes {@link MessageFormats#format} to format it.
*
* <p>The current locale is given by {@link org.zkoss.util.Locales#getCurrent}.
* @since 3.0.6
*/
public static final String getLabel(String key, Object[] args) {
final String s = getLabel(key);
return s != null ? MessageFormats.format(s, args, null): null;
}
/** Returns the length of an array, string, collection or map.
*/
public static final int length(Object o) {
if (o instanceof String) {
return ((String)o).length();
} else if (o == null) {
return 0;
} else if (o instanceof Collection) {
return ((Collection)o).size();
} else if (o instanceof Map) {
return ((Map)o).size();
} else if (o.getClass().isArray()) {
return Array.getLength(o);
} else {
throw new IllegalArgumentException("Unknown object for length: "+o.getClass());
}
}
/** Returns the index of the given element.
* @param o the array/collection of objects to examine, or a string.
* If o is a map, then {@link Map#keySet} is assumed.
* @since 5.0.7
*/
public static final int indexOf(Object o, Object element) {
if (o instanceof String) {
return element instanceof String ?
((String)o).indexOf((String)element): -1;
} else if (o instanceof Collection) {
int j = 0;
for (Iterator it = ((Collection)o).iterator(); it.hasNext(); ++j)
if (Objects.equals(it.next(), element))
return j;
} else if (o instanceof Map) {
return indexOf(((Map)o).keySet(), element);
} else if (o instanceof Object[]) {
final Object[] ary = (Object[])o;
for (int j = 0; j < ary.length; j++)
if (Objects.equals(ary[j], element))
return j;
} else if (o instanceof int[]) {
if (element instanceof Number) {
int v = ((Number)element).intValue();
final int[] ary = (int[])o;
for (int j = 0; j < ary.length; j++)
if (ary[j] == v)
return j;
}
} else if (o instanceof long[]) {
if (element instanceof Number) {
long v = ((Number)element).longValue();
final long[] ary = (long[])o;
for (int j = 0; j < ary.length; j++)
if (ary[j] == v)
return j;
}
} else if (o instanceof short[]) {
if (element instanceof Number) {
short v = ((Number)element).shortValue();
final short[] ary = (short[])o;
for (int j = 0; j < ary.length; j++)
if (ary[j] == v)
return j;
}
} else if (o instanceof byte[]) {
if (element instanceof Number) {
byte v = ((Number)element).byteValue();
final byte[] ary = (byte[])o;
for (int j = 0; j < ary.length; j++)
if (ary[j] == v)
return j;
}
} else if (o instanceof double[]) {
if (element instanceof Number) {
double v = ((Number)element).doubleValue();
final double[] ary = (double[])o;
for (int j = 0; j < ary.length; j++)
if (Double.compare(ary[j], v) == 0)
return j;
}
} else if (o instanceof float[]) {
if (element instanceof Number) {
float v = ((Number)element).floatValue();
final float[] ary = (float[])o;
for (int j = 0; j < ary.length; j++)
if (Float.compare(ary[j], v) == 0)
return j;
}
} else if (o instanceof char[]) {
char v;
if (element instanceof Character)
v = ((Character)element).charValue();
else if (element instanceof String && ((String)element).length() > 0)
v = ((String)element).charAt(0);
else
return -1;
final char[] ary = (char[])o;
for (int j = 0; j < ary.length; j++)
if (ary[j] == v)
return j;
} else if (o != null) {
throw new IllegalArgumentException("Unknown object for indexOf: "+o.getClass());
}
return -1;
}
/** Returns the last index of the given element.
* @param o the array/list of objects to examine, or a string.
* @since 5.0.7
*/
public static final int lastIndexOf(Object o, Object element) {
if (o instanceof String) {
return element instanceof String ?
((String)o).lastIndexOf((String)element): -1;
} else if (o instanceof List) {
int j = ((List)o).size();
for (ListIterator it = ((List)o).listIterator(j); it.hasPrevious(); j--)
if (Objects.equals(it.previous(), element))
return j - 1;
} else if (o instanceof Object[]) {
final Object[] ary = (Object[])o;
for (int j = ary.length; --j >= 0;)
if (Objects.equals(ary[j], element))
return j;
} else if (o instanceof int[]) {
if (element instanceof Number) {
int v = ((Number)element).intValue();
final int[] ary = (int[])o;
for (int j = ary.length; --j >= 0;)
if (ary[j] == v)
return j;
}
} else if (o instanceof long[]) {
if (element instanceof Number) {
long v = ((Number)element).longValue();
final long[] ary = (long[])o;
for (int j = ary.length; --j >= 0;)
if (ary[j] == v)
return j;
}
} else if (o instanceof short[]) {
if (element instanceof Number) {
short v = ((Number)element).shortValue();
final short[] ary = (short[])o;
for (int j = ary.length; --j >= 0;)
if (ary[j] == v)
return j;
}
} else if (o instanceof byte[]) {
if (element instanceof Number) {
byte v = ((Number)element).byteValue();
final byte[] ary = (byte[])o;
for (int j = ary.length; --j >= 0;)
if (ary[j] == v)
return j;
}
} else if (o instanceof double[]) {
if (element instanceof Number) {
double v = ((Number)element).doubleValue();
final double[] ary = (double[])o;
for (int j = ary.length; --j >= 0;)
if (Double.compare(ary[j], v) == 0)
return j;
}
} else if (o instanceof float[]) {
if (element instanceof Number) {
float v = ((Number)element).floatValue();
final float[] ary = (float[])o;
for (int j = ary.length; --j >= 0;)
if (Float.compare(ary[j], v) == 0)
return j;
}
} else if (o instanceof char[]) {
char v;
if (element instanceof Character)
v = ((Character)element).charValue();
else if (element instanceof String && ((String)element).length() > 0)
v = ((String)element).charAt(0);
else
return -1;
final char[] ary = (char[])o;
for (int j = ary.length; --j >= 0;)
if (ary[j] == v)
return j;
} else if (o != null) {
throw new IllegalArgumentException("Unknown object for indexOf: "+o.getClass());
}
return -1;
}
/** Instantiates the specified class.
*/
public static final Object new_(Object o) throws Exception {
if (o instanceof String) {
return Classes.newInstanceByThread((String)o);
} else if (o instanceof Class) {
return ((Class)o).newInstance();
} else {
throw new IllegalArgumentException("Unknow object for new: "+o);
}
}
/** Instantiates the specified class, and argument.
* @param o the class name or class
* @param arg the argument
* @since 5.0.5
*/
public static final Object new_(Object o, Object arg) throws Exception {
if (o instanceof String) {
return Classes.newInstance(Classes.forNameByThread((String)o), new Object[] {arg});
} else if (o instanceof Class) {
return Classes.newInstance((Class)o, new Object[] {arg});
} else {
throw new IllegalArgumentException("Unknow object for new: "+o);
}
}
/** Instantiates the specified class, and two arguments.
* @param o the class name or class
* @param arg1 the first argument
* @param arg2 the second argument
* @since 5.0.5
*/
public static final Object new_(Object o, Object arg1, Object arg2) throws Exception {
if (o instanceof String) {
return Classes.newInstance(Classes.forNameByThread((String)o), new Object[] {arg1, arg2});
} else if (o instanceof Class) {
return Classes.newInstance((Class)o, new Object[] {arg1, arg2});
} else {
throw new IllegalArgumentException("Unknow object for new: "+o);
}
}
/** Instantiates the specified class, and two arguments.
* @param o the class name or class
* @param arg1 the first argument
* @param arg2 the second argument
* @since 5.0.5
*/
public static final Object new_(Object o, Object arg1, Object arg2, Object arg3) throws Exception {
if (o instanceof String) {
return Classes.newInstance(Classes.forNameByThread((String)o), new Object[] {arg1, arg2, arg3});
} else if (o instanceof Class) {
return Classes.newInstance((Class)o, new Object[] {arg1, arg2, arg3});
} else {
throw new IllegalArgumentException("Unknow object for new: "+o);
}
}
/**
* Formats a Date into a date/time string.
* @param date the time value to be formatted into a time string.
* @param pattern the pattern describing the date and time format
* @return the formatted time string.
* @since 6.0.0
*/
public static final String formatDate(Date date, String pattern) {
return formatDate(date, pattern, null, null, null, null);
}
/**
* Parses text from the beginning of the given string to produce a date.
* The method may not use the entire text of the given string.
* @param source A <code>String</code> whose beginning should be parsed.
* @param pattern the pattern describing the date and time format
* @return A <code>Date</code> parsed from the string.
* @throws Exception
* @since 6.0.0
*/
public static final Date parseDate(String source, String pattern) throws Exception {
return parseDate(source, pattern, null, null, null, null);
}
/**
* Formats a number (Integer, BigDecimal...) into a string.
* If null, an empty string is returned.
* <p>A utility to assist the handling of numeric data.
* @param value The number to format.
* @param format The pattern to apply, if it is null,
* the system's default format is used.
* @return the formatted number string.
* @since 6.0.1
*/
public static final String formatNumber(Object value, String format) {
return formatNumber(value, format, null);
}
/**
* Parses text from the beginning of the given string to produce a number.
* The method may not use the entire text of the given string.
* @param source A <code>String</code> whose beginning should be parsed.
* @param pattern the pattern describing the date and time format
* @return A <code>Number</code> parsed from the string.
* @throws Exception
* @since 6.0.1
*/
public static final Number parseNumber (String source, String pattern) throws Exception {
return parseNumber(source, pattern, null);
}
/**
* Formats a Date into a date/time string.
* @param date the time value to be formatted into a time string.
* @param pattern the pattern describing the date and time format
* @param locale The Locale to apply, if it is null,
* The current locale given by {@link org.zkoss.util.Locales#getCurrent} is used.
* @param timezone the time zone to apply, if it is null,
* The current timezone given by {@link org.zkoss.util.TimeZones#getCurrent} is used.
* @param dateStyle styling index of date.
* @param timeStyle styling index of time.
* @return the formatted time string.
* @since 6.0.0
*/
public static final String formatDate(Date date, String pattern, Locale locale, TimeZone timezone, String dateStyle, String timeStyle) {
return getDateFormat(pattern, locale, timezone, dateStyle, timeStyle).format(date);
}
/**
* Parses text from the beginning of the given string to produce a date.
* The method may not use the entire text of the given string.
* @param source A <code>String</code> whose beginning should be parsed.
* @param pattern the pattern describing the date and time format
* @param locale The Locale to apply, if it is null,
* The current locale given by {@link org.zkoss.util.Locales#getCurrent} is used.
* @param timezone the time zone to apply, if it is null,
* The current timezone given by {@link org.zkoss.util.TimeZones#getCurrent} is used.
* @param dateStyle styling index of date.
* @param timeStyle styling index of time.
* @return A <code>Date</code> parsed from the string.
* @throws Exception
* @since 6.0.0
*/
public static final Date parseDate(String source, String pattern, Locale locale, TimeZone timezone, String dateStyle, String timeStyle) throws Exception {
return getDateFormat(pattern, locale, timezone, dateStyle, timeStyle).parse(source);
}
/**
* Formats a number (Integer, BigDecimal...) into a string.
* If null, an empty string is returned.
* <p>A utility to assist the handling of numeric data.
* @param number The number to format.
* @param pattern The pattern to apply, if it is null,
* the system's default format is used.
* @param locale The Locale to apply, if it is null,
* The current locale given by {@link org.zkoss.util.Locales#getCurrent} is used.
* @return String The formatted number string.
* @since 6.0.1
*/
public static final String formatNumber (Object number, String pattern, Locale locale) {
if (number == null)
return "";
return getDecimalFormat(pattern, locale).format(number);
}
/**
* Parses text from the beginning of the given string to produce a number.
* The method may not use the entire text of the given string.
* @param source A <code>String</code> whose beginning should be parsed.
* @param pattern the pattern describing the date and time format
* @param locale The Locale to apply, if it is null,
* The current locale given by {@link org.zkoss.util.Locales#getCurrent} is used.
* @return A <code>Number</code> parsed from the string.
* @throws Exception
* @since 6.0.1
*/
public static final Number parseNumber (String source, String pattern, Locale locale) throws Exception {
return getDecimalFormat(pattern, locale).parse(source);
}
private static final DecimalFormat getDecimalFormat (String pattern, Locale locale) {
final DecimalFormat df = (DecimalFormat)
NumberFormat.getInstance(locale != null ? locale : Locales.getCurrent());
if (pattern != null)
df.applyPattern(pattern);
return df;
}
private static final DateFormat getDateFormat (String pattern, Locale locale, TimeZone timezone, String dateStyle, String timeStyle) {
if (locale == null)
locale = Locales.getCurrent();
if (timezone == null)
timezone = TimeZones.getCurrent();
pattern = getRealFormat(pattern, locale, dateStyle, timeStyle);
final DateFormat df = new SimpleDateFormat(pattern,
locale);
df.setTimeZone(timezone);
return df;
}
private static final String getRealFormat (String pattern, Locale locale, String dateStyle, String timeStyle) {
int ts, ds;
if (pattern.isEmpty())
pattern = null;
ds = toStyle(dateStyle);
if (ds != -111) {
ts = toStyle(timeStyle);
if (ts != -111) {
return DateFormats.getDateTimeFormat(ds, ts, locale, pattern);
}
return DateFormats.getDateFormat(ds, locale, pattern);
}
return pattern != null ? pattern : "M/d/yy";
}
private static final int toStyle (String style) {
if (style != null) {
style = style.trim().toLowerCase(java.util.Locale.ENGLISH);
return "short".equals(style) ? DateFormat.SHORT
: "medium".equals(style) ? DateFormat.MEDIUM
: "long".equals(style) ? DateFormat.LONG
: "full".equals(style) ? DateFormat.FULL
: -111; //not found
}
return -111;
}
}