/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.function;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.DataRow;
import org.pentaho.reporting.engine.classic.core.ResourceBundleFactory;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.formatting.FastMessageFormat;
import org.pentaho.reporting.libraries.formula.util.URLEncoder;
/**
* A TextFormatExpression uses a java.text.MessageFormat to concat and format one or more values evaluated from an
* expression, function or report datasource.
* <p/>
* The TextFormatExpression uses the pattern property to define the global format-pattern used when evaluating the
* expression. The dataRow fields used to fill the expressions placeholders are defined in a list of properties where
* the property-names are numbers. The property counting starts at "0".
* <p/>
* The Syntax of the <code>pattern</code> property is explained in the class {@link java.text.MessageFormat}.
* <p/>
* Example:
* <pre>
* <expression name="expr" class="org.pentaho.reporting.engine.classic.core.function.TextFormatExpression">
* <properties>
* <property name="pattern">Invoice for your order from {0, date, EEE, MMM d,
* yyyy}</property>
* <property name="fields[0]">printdate</property>
* </properties>
* </expression>
* </pre>
* <p/>
* The {@link org.pentaho.reporting.engine.classic.core.function.strings.MessageFormatExpression} allows to specify
* named field-references in the pattern, which greatly simplifies the pattern-definition.
*
* @author Thomas Morgner
* @deprecated Use the MessageFormatExpression instead.
*/
public class TextFormatExpression extends AbstractExpression
{
private static final Log logger = LogFactory.getLog(TextFormatExpression.class);
/**
* An ordered list containing the fieldnames used in the expression.
*/
private ArrayList fields;
/**
* A temporary array to reduce the number of object creations.
*/
private transient Object[] fieldValues;
/**
* A temporary array to reduce the number of object creations. This array is used to compute the cache's validity.
*/
private transient Object[] oldFieldValues;
/**
* The current locale. This is used to optimize the expression evaluation.
*/
private transient Locale locale;
/**
* The current message format object.
*/
private transient FastMessageFormat messageFormat;
/**
* The message-format pattern used to compute the result.
*/
private String pattern;
/**
* A flag indicating whether the data read from the fields should be URL encoded.
*/
private boolean urlEncodeData;
/**
* A flag indicating whether the whole result string should be URL encoded.
*/
private boolean urlEncodeResult;
/**
* The byte-encoding used for the URL encoding.
*/
private String encoding;
/**
* A cached result.
*/
private String cachedResult;
/**
* Default constructor, creates a new unnamed TextFormatExpression.
*/
public TextFormatExpression()
{
fields = new ArrayList();
encoding = "iso-8859-1";
}
/**
* Returns the defined character encoding that is used to transform the Java-Unicode strings into bytes.
*
* @return the encoding.
*/
public String getEncoding()
{
return encoding;
}
/**
* Defines the character encoding that is used to transform the Java-Unicode strings into bytes.
*
* @param encoding the encoding.
*/
public void setEncoding(final String encoding)
{
if (encoding == null)
{
throw new NullPointerException();
}
this.encoding = encoding;
}
/**
* Defines, whether the values read from the data-row should be URL encoded. Dates and Number objects are never
* encoded.
*
* @param urlEncode true, if the values from the data-row should be URL encoded before they are passed to the
* MessageFormat, false otherwise.
*/
public void setUrlEncodeValues(final boolean urlEncode)
{
this.urlEncodeData = urlEncode;
}
/**
* Queries, whether the values read from the data-row should be URL encoded.
*
* @return true, if the values are encoded, false otherwise.
*/
public boolean isUrlEncodeValues()
{
return urlEncodeData;
}
/**
* Queries, whether the formatted result-string will be URL encoded.
*
* @return true, if the formatted result will be encoded, false otherwise.
*/
public boolean isUrlEncodeResult()
{
return urlEncodeResult;
}
/**
* Defines, whether the formatted result-string will be URL encoded.
*
* @param urlEncodeResult true, if the formatted result will be encoded, false otherwise.
*/
public void setUrlEncodeResult(final boolean urlEncodeResult)
{
this.urlEncodeResult = urlEncodeResult;
}
/**
* Evaluates the expression by collecting all values defined in the fieldlist from the datarow. The collected values
* are then parsed and formated by the MessageFormat-object.
*
* @return a string containing the pattern inclusive the formatted values from the datarow
*/
public Object getValue()
{
if (fields.isEmpty())
{
return getPattern();
}
final ResourceBundleFactory factory = getResourceBundleFactory();
if (messageFormat == null || ObjectUtilities.equal(locale, factory.getLocale()) == false)
{
messageFormat = new FastMessageFormat(getPattern(), factory.getLocale());
this.locale = factory.getLocale();
}
try
{
if (oldFieldValues == null || oldFieldValues.length != fields.size())
{
oldFieldValues = new Object[fields.size()];
}
else if (fieldValues != null && fieldValues.length == oldFieldValues.length)
{
System.arraycopy(fieldValues, 0, oldFieldValues, 0, fields.size());
}
fieldValues = getFieldValues(fieldValues);
final String result;
if (cachedResult != null &&
Arrays.equals(oldFieldValues, fieldValues))
{
result = cachedResult;
}
else
{
result = messageFormat.format(fieldValues);
cachedResult = result;
}
if (isUrlEncodeResult())
{
return URLEncoder.encode(result, getEncoding());
}
else
{
return result;
}
}
catch (UnsupportedEncodingException e)
{
TextFormatExpression.logger.debug("Unsupported Encoding: " + encoding);
return null;
}
}
/**
* Collects the values of all fields defined in the fieldList.
*
* @param retval an optional array that will receive the field values.
* @return an Object-array containing all defined values from the datarow
* @throws java.io.UnsupportedEncodingException
* if the character encoding is not recognized by the JDK.
*/
protected Object[] getFieldValues(Object[] retval)
throws UnsupportedEncodingException
{
final int size = fields.size();
if (retval == null || retval.length != size)
{
retval = new Object[size];
}
final DataRow dataRow = getDataRow();
for (int i = 0; i < size; i++)
{
final String field = (String) fields.get(i);
if (field == null)
{
retval[i] = null;
continue;
}
final Object fieldValue = dataRow.get(field);
if (isUrlEncodeValues())
{
if (fieldValue == null)
{
retval[i] = null;
}
else if (fieldValue instanceof Date)
{
retval[i] = fieldValue;
}
else if (fieldValue instanceof Number)
{
retval[i] = fieldValue;
}
else if (isUrlEncodeValues())
{
retval[i] = URLEncoder.encode(String.valueOf(fieldValue), encoding);
}
else
{
retval[i] = fieldValue;
}
}
else
{
retval[i] = fieldValue;
}
}
return retval;
}
/**
* Returns the pattern defined for this expression.
*
* @return the pattern.
*/
public String getPattern()
{
return pattern;
}
/**
* Defines the pattern for this expression. The pattern syntax is defined by the java.text.MessageFormat object and
* the given pattern string has to be valid according to the rules defined there.
*
* @param pattern the pattern string
*/
public void setPattern(final String pattern)
{
if (pattern == null)
{
throw new NullPointerException();
}
this.messageFormat = null;
this.pattern = pattern;
this.cachedResult = null;
}
/**
* Return a completly separated copy of this function. The copy does no longer share any changeable objects with the
* original function.
*
* @return a copy of this function.
*/
public Expression getInstance()
{
final TextFormatExpression tex = (TextFormatExpression) super.getInstance();
tex.fields = (ArrayList) fields.clone();
tex.fieldValues = null;
tex.oldFieldValues = null;
tex.cachedResult = null;
return tex;
}
/**
* Defines the field in the field-list at the given index.
*
* @param index the position in the list, where the field should be defined.
* @param field the name of the field.
*/
public void setField(final int index, final String field)
{
if (fields.size() == index)
{
fields.add(field);
}
else
{
fields.set(index, field);
}
fieldValues = null;
oldFieldValues = null;
cachedResult = null;
}
/**
* Returns the defined field at the given index-position.
*
* @param index the position of the field name that should be queried.
* @return the field name at the given position.
*/
public String getField(final int index)
{
return (String) fields.get(index);
}
/**
* Returns the number of fields defined in this expression.
*
* @return the number of fields.
*/
public int getFieldCount()
{
return fields.size();
}
/**
* Returns all defined fields as array of strings.
*
* @return all the fields.
*/
public String[] getField()
{
return (String[]) fields.toArray(new String[fields.size()]);
}
/**
* Defines all fields as array. This completely replaces any previously defined fields.
*
* @param fields the new list of fields.
*/
public void setField(final String[] fields)
{
this.fields.clear();
this.fields.addAll(Arrays.asList(fields));
fieldValues = null;
oldFieldValues = null;
cachedResult = null;
}
}