/*
* 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.
*/
package org.apache.click.util;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.apache.click.Context;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
/**
* Provides the default object for formatting the display of model objects
* in Velocity templates and JSP pages.
* <p/>
* For Velocity templates a Format object is added to the Velocity Context using
* the name "<span class="blue">format</span>", while for JSP pages an instance
* is added as a request attribute using the same key.
* <p/>
* For example the following Page code adds a date to the model:
*
* <pre class="codeJava">
* <span class="kw">public void</span> onGet() {
* Date date = order.deliveryDate();
* addModel("<span class="st">deliveryDate</span>", date);
* } </pre>
*
* In the page template we use the format object:
*
* <pre class="codeHtml">
* Delivery date: <span class="red">$format</span>.date(<span class="st">$deliveryDate</span>, "dd MMM yyyy") </pre>
*
* Which renders the output as:
*
* <table class="htmlExample" cellspacing="12">
* <tr><td>
* Delivery date: 21 Jan 2004
* </td></tr>
* </table>
*
* The custom format class can be defined in the "click.xml" configuration file
* using the syntax:
*
* <pre class="codeConfig">
* <format classname="<span class="st">com.mycorp.utils.MyFormat</span>"/> </pre>
*
* After a Page is created its <a href="../Page.html#format">format</a> property
* is set.
* The ClickServlet will then add this property to the Velocity Context.
* <p/>
* When subclassing Format ensure it is light weight object, as a new format
* object will be created for every new Page.
*
* @see PageImports
*/
public class Format implements Serializable {
private static final long serialVersionUID = 1L;
/** The request context locale. */
protected Locale locale;
/** The empty string value. */
protected String emptyString = "";
// --------------------------------------------------------- Public Methods
/**
* Return the locale used to format objects.
*
* @return the locale used to format objects
*/
public Locale getLocale() {
if (locale == null) {
locale = Context.getThreadLocalContext().getLocale();
}
return locale;
}
/**
* Returns the format empty string value: <tt>""</tt>.
* <p/>
* This method is designed to be overridden. If you need a different
* empty string value simply override this method.
* <p/>
* Note the IE browser does not fully support CSS attribute:
* <tt>table { empty-cells: show }</tt>. Also note returning
* <tt>&nbsp;</tt>
* value will prevent AJAX XML responses being rendered in browsers.
*
* @return the formatter methods empty string value
*/
public String getEmptyString() {
return emptyString;
}
/**
* Set the format empty string value.
*
* @param value the format empty string value
*/
public void setEmptyString(String value) {
emptyString = value;
}
/**
* Return a currency formatted String value for the given number, using
* the default Locale.
* <p/>
* If the number is null this method will return the
* {@link #getEmptyString()} value.
*
* @param number the number to format
* @return a currency formatted number string
*/
public String currency(Number number) {
if (number != null) {
NumberFormat format = NumberFormat.getCurrencyInstance(getLocale());
return format.format(number.doubleValue());
} else {
return getEmptyString();
}
}
/**
* Return a formatted current date string using the default DateFormat.
*
* @return a formatted date string
*/
public String currentDate() {
DateFormat format =
DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale());
return format.format(new Date());
}
/**
* Return a formatted current date string using the given formatting
* pattern. See SimpleDateFormat for information on the format
* pattern string.
*
* @param pattern the SimpleDateFormat formatting pattern
* @return a formatted date string
* @throws IllegalArgumentException if the pattern string is null
*/
public String currentDate(String pattern) {
if (pattern == null) {
throw new IllegalArgumentException("Null pattern parameter");
}
SimpleDateFormat format =
new SimpleDateFormat(pattern, getLocale());
return format.format(new Date());
}
/**
* Return a formatted date string using the given date and formatting
* pattern. See SimpleDateFormat for information on the format
* pattern string.
* <p/>
* If the date is null this method will return the {@link #getEmptyString()}
* value.
*
* <h4>SimpleDateFormat Pattern Characters</h4>
*
* <table border="1" cellspacing="0" cellpadding="3">
* <tr bgcolor="#ccccff">
* <th align=left>Letter
* <th align=left>Date or Time Component
* <th align=left>Presentation
* <th align=left>Examples
* <tr>
* <td><code>G</code>
* <td>Era designator
* <td>Text
* <td><code>AD</code>
* <tr bgcolor="#eeeeff">
* <td><code>y</code>
* <td>Year
* <td>Year
* <td><code>1996</code>; <code>96</code>
* <tr>
* <td><code>M</code>
* <td>Month in year
* <td>Month
* <td><code>July</code>; <code>Jul</code>; <code>07</code>
* <tr bgcolor="#eeeeff">
* <td><code>w</code>
* <td>Week in year
* <td>Number
* <td><code>27</code>
* <tr>
* <td><code>W</code>
* <td>Week in month
* <td>Number
* <td><code>2</code>
* <tr bgcolor="#eeeeff">
* <td><code>D</code>
* <td>Day in year
* <td>Number
* <td><code>189</code>
* <tr>
* <td><code>d</code>
* <td>Day in month
* <td>Number
* <td><code>10</code>
* <tr bgcolor="#eeeeff">
* <td><code>F</code>
* <td>Day of week in month
* <td>Number
* <td><code>2</code>
* <tr>
* <td><code>E</code>
* <td>Day in week
* <td>Text
* <td><code>Tuesday</code>; <code>Tue</code>
* <tr bgcolor="#eeeeff">
* <td><code>a</code>
* <td>Am/pm marker
* <td>Text
* <td><code>PM</code>
* <tr>
* <td><code>H</code>
* <td>Hour in day (0-23)
* <td>Number
* <td><code>0</code>
* <tr bgcolor="#eeeeff">
* <td><code>k</code>
* <td>Hour in day (1-24)
* <td>Number
* <td><code>24</code>
* <tr>
* <td><code>K</code>
* <td>Hour in am/pm (0-11)
* <td>Number
* <td><code>0</code>
* <tr bgcolor="#eeeeff">
* <td><code>h</code>
* <td>Hour in am/pm (1-12)
* <td>Number
* <td><code>12</code>
* <tr>
* <td><code>m</code>
* <td>Minute in hour
* <td>Number
* <td><code>30</code>
* <tr bgcolor="#eeeeff">
* <td><code>s</code>
* <td>Second in minute
* <td>Number
* <td><code>55</code>
* <tr>
* <td><code>S</code>
* <td>Millisecond
* <td>Number
* <td><code>978</code>
* <tr bgcolor="#eeeeff">
* <td><code>z</code>
* <td>Time zone
* <td>General time zone
* <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code>
* <tr>
* <td><code>Z</code>
* <td>Time zone
* <td>RFC 822 time zone
* <td><code>-0800</code>
* </table>
*
* @param date the date value to format
* @param pattern the SimpleDateFormat formatting pattern
* @return a formatted date string
* @throws IllegalArgumentException if the pattern string is null
*/
public String date(Date date, String pattern) {
if (date != null) {
if (pattern == null) {
throw new IllegalArgumentException("Null pattern parameter");
}
SimpleDateFormat format =
new SimpleDateFormat(pattern, getLocale());
return format.format(date);
} else {
return getEmptyString();
}
}
/**
* Return a formatted date string using the given date and the default
* DateFormat.
* <p/>
* If the date is null this method will return the
* {@link #getEmptyString()} value.
*
* @param date the date value to format
* @return a formatted date string
*/
public String date(Date date) {
if (date != null) {
DateFormat format =
DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale());
return format.format(date);
} else {
return getEmptyString();
}
}
/**
* Return a decimal formatted string using the given number and pattern.
* See DecimalFormat for information on the format pattern string.
* <p/>
* If the number is null this method will return the
* {@link #getEmptyString()} value.
* <p/>
* <b>Please Note</b>: Velocity will not correctly parse the '#' decimal
* number pattern character and will throw a parsing exception. When using
* this method substitute the '#' decimal pattern character
* (Digit, zero shows as absent) with 'N'. Internally this method will
* reverse the substitution before invoking <tt>DecimalFormat</tt>.
* For example:
*
* <pre class="codeHtml">
* $format.decimal($value, "N,NN0.00") -> "#,##0.00"
* </pre>
*
* <h4>DecimalFormat Pattern Characters</h4>
*
* <table border='0' cellspacing='3' cellpadding='3'>
* <tr bgcolor="#ccccff">
* <th align=left>Symbol
* <th align=left>Location
* <th align=left>Localized?
* <th align=left>Meaning
* <tr valign=top>
* <td><code>0</code>
* <td>Number
* <td>Yes
* <td>Digit
* <tr valign=top bgcolor="#eeeeff">
* <td><code>#</code>
* <td>Number
* <td>Yes
* <td>Digit, zero shows as absent
* <tr valign=top>
* <td><code>.</code>
* <td>Number
* <td>Yes
* <td>Decimal separator or monetary decimal separator
* <tr valign=top bgcolor="#eeeeff">
* <td><code>-</code>
* <td>Number
* <td>Yes
* <td>Minus sign
* <tr valign=top>
* <td><code>,</code>
* <td>Number
* <td>Yes
* <td>Grouping separator
* <tr valign=top bgcolor="#eeeeff">
* <td><code>E</code>
* <td>Number
* <td>Yes
* <td>Separates mantissa and exponent in scientific notation.
* <em>Need not be quoted in prefix or suffix.</em>
* <tr valign=top>
* <td><code>;</code>
* <td>Subpattern boundary
* <td>Yes
* <td>Separates positive and negative subpatterns
* <tr valign=top bgcolor="#eeeeff">
* <td><code>%</code>
* <td>Prefix or suffix
* <td>Yes
* <td>Multiply by 100 and show as percentage
* <tr valign=top>
* <td><code>\u2030</code>
* <td>Prefix or suffix
* <td>Yes
* <td>Multiply by 1000 and show as per mille
* <tr valign=top bgcolor="#eeeeff">
* <td><code>¤</code> (<code>\u00A4</code>)
* <td>Prefix or suffix
* <td>No
* <td>Currency sign, replaced by currency symbol. If
* doubled, replaced by international currency symbol.
* If present in a pattern, the monetary decimal separator
* is used instead of the decimal separator.
* <tr valign=top>
* <td><code>'</code>
* <td>Prefix or suffix
* <td>No
* <td>Used to quote special characters in a prefix or suffix,
* for example, <code>"'#'#"</code> formats 123 to
* <code>"#123"</code>. To create a single quote
* itself, use two in a row: <code>"# o''clock"</code>.
* </table>
*
* @param number the number to format
* @param pattern the decimal format pattern
* @return the formatted decimal number
* @throws IllegalArgumentException if the pattern string is null
*/
public String decimal(Number number, String pattern) {
if (number != null) {
if (pattern == null) {
throw new IllegalArgumentException("Null pattern parameter");
}
if (pattern.indexOf('N') != -1) {
pattern = pattern.replace('N', '#');
}
DecimalFormat format = new DecimalFormat(pattern);
return format.format(number.doubleValue());
} else {
return getEmptyString();
}
}
/**
* Return a decimal formatted string using the given number and pattern.
* <p/>
* If the number is null this method will return the
* {@link #getEmptyString()} value.
*
* @param number the number to format
* @return the formatted decimal number
*/
public String decimal(Number number) {
if (number != null) {
DecimalFormat format = new DecimalFormat();
return format.format(number.doubleValue());
} else {
return getEmptyString();
}
}
/**
* Return an email hyperlink using the given email address. If the
* given value is not a valid email string it will be rendered as is
* and not as a hyperlink.
* <p/>
* If the given value is blank then the {@link #getEmptyString()} value
* will rendered instead.
* <p/>
* The format of the returned email string will be:
* <pre class="codeHtml">
* <a href='mailto:email'>email</a> </pre>
*
* @param email the email address to hyperlink
* @return a hyperlinked email
*/
public String email(String email) {
return email(email, null);
}
/**
* Return an email hyperlink using the given email address. If the
* given value is not a valid email string it will be rendered as is
* and not as a hyperlink.
* <p/>
* If the given value is blank then the {@link #getEmptyString()} value
* will rendered instead.
* <p/>
* The format of the returned email string will be:
* <pre class="codeHtml">
* <a href='mailto:email' attribute>email</a> </pre>
*
* @param email the email address to hyperlink
* @param attribute the anchor tag attribute to render
* @return a hyperlinked email
*/
public String email(String email, String attribute) {
if (StringUtils.isNotBlank(email)) {
if (email.indexOf('@') != -1
&& !email.startsWith("@")
&& !email.endsWith("@")) {
HtmlStringBuffer buffer = new HtmlStringBuffer(128);
buffer.elementStart("a");
buffer.appendAttribute("href", "mailto:" + email);
if (StringUtils.isNotBlank(attribute)) {
buffer.append(" ");
buffer.append(attribute);
}
buffer.closeTag();
buffer.appendEscaped(email);
buffer.elementEnd("a");
return buffer.toString();
} else {
return email;
}
} else {
return getEmptyString();
}
}
/**
* Escape the given object value as a HTML string.
* <p/>
* If the value is null this method will return the
* {@link #getEmptyString()} value.
*
* @param value unescaped HTML
* @return the HTML escaped string
*/
public String html(Object value) {
if (value != null) {
return ClickUtils.escapeHtml(value.toString());
} else {
return getEmptyString();
}
}
/**
* Escape the given object value as a JavaScript string, or "" if the object
* is null.
* <p>
* Implementation is provided by Jakarta Commons Lang utility:
* <tt>StringEscapeUtils.escapeJavaScript(String)</tt>
*
* @param value unescaped JavaScript
* @return the JavaScript escaped string
*/
public String javascript(String value) {
if (value != null) {
return StringEscapeUtils.escapeJavaScript(value);
} else {
return "";
}
}
/**
* Return the value string limited to maxlength characters. If the string
* gets curtailed, "..." is appended to it.
* <p/>
* Adapted from Velocity Tools Formatter.
*
* @param value the string value to limit the length of
* @param maxlength the maximum string length
* @return a length limited string
*/
public String limitLength(String value, int maxlength) {
return limitLength(value, maxlength, "...");
}
/**
* Return the value string limited to maxlength characters. If the string
* gets curtailed and the suffix parameter is appended to it.
* <p/>
* Adapted from Velocity Tools Formatter.
*
* @param value the string value to limit the length of
* @param maxlength the maximum string length
* @param suffix the suffix to append to the length limited string
* @return a length limited string
*/
public String limitLength(String value, int maxlength, String suffix) {
return ClickUtils.limitLength(value, maxlength, suffix);
}
/**
* Return an hyperlink using the given URL or email address value. If the
* given value is not a valid email string or URL it will note be
* hyperlinked and will be rendered as is.
* <p/>
* If the given value is blank then the {@link #getEmptyString()} value
* will rendered instead.
*
* @param value the URL or email address to hyperlink
* @return a hyperlinked URL or email address
*/
public String link(String value) {
return link(value, null);
}
/**
* Return an hyperlink using the given URL or email address value. If the
* given value is not a valid email string or URL it will note be
* hyperlinked and will be rendered as is.
* <p/>
* If the given value is blank then the {@link #getEmptyString()} value
* will rendered instead.
*
* @param value the URL or email address to hyperlink
* @param attribute the anchor tag attribute to render
* @return a hyperlinked URL or email address
*/
public String link(String value, String attribute) {
if (StringUtils.isNotBlank(value)) {
HtmlStringBuffer buffer = new HtmlStringBuffer(128);
// If email
if (value.indexOf('@') != -1
&& !value.startsWith("@")
&& !value.endsWith("@")) {
buffer.elementStart("a");
buffer.appendAttribute("href", "mailto:" + value);
if (StringUtils.isNotBlank(attribute)) {
buffer.append(" ");
buffer.append(attribute);
}
buffer.closeTag();
buffer.appendEscaped(value);
buffer.elementEnd("a");
} else if (value.startsWith("http")) {
int index = value.indexOf("//");
if (index != -1) {
index += 2;
} else {
index = 0;
}
buffer.elementStart("a");
buffer.appendAttribute("href", value);
if (StringUtils.isNotBlank(attribute)) {
buffer.append(" ");
buffer.append(attribute);
}
buffer.closeTag();
buffer.appendEscaped(value.substring(index));
buffer.elementEnd("a");
} else if (value.startsWith("www")) {
buffer.elementStart("a");
buffer.appendAttribute("href", "http://" + value);
if (StringUtils.isNotBlank(attribute)) {
buffer.append(" ");
buffer.append(attribute);
}
buffer.closeTag();
buffer.appendEscaped(value);
buffer.elementEnd("a");
} else {
buffer.append(value);
}
return buffer.toString();
}
return getEmptyString();
}
/**
* Return a formatted string using the given message pattern and argument.
* See {@link java.text.MessageFormat} for information on the message
* pattern string.
*
* @param pattern the message pattern
* @param argument the message argument
* @return the formatted string
*/
public String message(String pattern, Object argument) {
return message(pattern, new Object[] { argument });
}
/**
* Return a formatted string using the given message pattern and arguments.
* See {@link java.text.MessageFormat} for information on the message
* pattern string.
*
* @param pattern the message pattern
* @param arguments the message arguments
* @return the formatted string
*/
public String message(String pattern, Object[] arguments) {
MessageFormat format = new MessageFormat(pattern, getLocale());
return format.format(arguments, new StringBuffer(), null).toString();
}
/**
* Return a formatted string using the given message pattern and arguments.
* See {@link java.text.MessageFormat} for information on the message
* pattern string.
*
* @param pattern the message pattern
* @param arguments list of message arguments
* @return the formatted string
*/
public String message(String pattern, List arguments) {
return message(pattern, arguments.toArray());
}
/**
* Return a percentage formatted number string using number.
* <p/>
* If the number is null this method will return the
* {@link #getEmptyString()} value.
*
* @param number the number value to format
* @return a percentage formatted number string
*/
public String percentage(Number number) {
if (number != null) {
NumberFormat format = NumberFormat.getPercentInstance(getLocale());
return format.format(number.doubleValue());
} else {
return getEmptyString();
}
}
/**
* Return a formatted time string using the given date and the default
* DateFormat.
* <p/>
* If the date is null this method will return the
* {@link #getEmptyString()} value.
*
* @param date the date value to format
* @return a formatted time string
*/
public String time(Date date) {
if (date != null) {
DateFormat format =
DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale());
return format.format(date);
} else {
return getEmptyString();
}
}
/**
* Return the string representation of the given object.
* <p/>
* If the object is null this method will return the
* {@link #getEmptyString()} value.
*
* @param object the object to format
* @return the string representation of the object
*/
public String string(Object object) {
if (object != null) {
return object.toString();
} else {
return getEmptyString();
}
}
/**
* Return an encoded URL value for the given object.
*
* @param object the object value to encode as a URL string
* @return an encoded URL string
*/
public String url(Object object) {
return ClickUtils.encodeUrl(object, Context.getThreadLocalContext());
}
}