001: /**
002: * ===========================================
003: * JFreeReport : a free Java reporting library
004: * ===========================================
005: *
006: * Project Info: http://reporting.pentaho.org/
007: *
008: * (C) Copyright 2001-2007, by Object Refinery Ltd, 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: * TextFormatExpression.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.function;
030:
031: import java.io.UnsupportedEncodingException;
032: import java.text.MessageFormat;
033: import java.util.ArrayList;
034: import java.util.Arrays;
035: import java.util.Date;
036: import java.util.Locale;
037:
038: import org.jfree.report.DataRow;
039: import org.jfree.report.ResourceBundleFactory;
040: import org.jfree.report.util.UTFEncodingUtil;
041: import org.jfree.util.Log;
042: import org.jfree.util.ObjectUtilities;
043:
044: /**
045: * A TextFormatExpression uses a java.text.MessageFormat to concat and format one or more values evaluated from an
046: * expression, function or report datasource.
047: * <p/>
048: * The TextFormatExpression uses the pattern property to define the global format-pattern used when evaluating the
049: * expression. The dataRow fields used to fill the expressions placeholders are defined in a list of properties where
050: * the property-names are numbers. The property counting starts at "0".
051: * <p/>
052: * The Syntax of the <code>pattern</code> property is explained in the class {@link java.text.MessageFormat}.
053: * <p/>
054: * Example:
055: * <pre>
056: * <expression name="expr" class="org.jfree.report.function.TextFormatExpression">
057: * <properties>
058: * <property name="pattern">Invoice for your order from {0, date, EEE, MMM d,
059: * yyyy}</property>
060: * <property name="fields[0]">printdate</property>
061: * </properties>
062: * </expression>
063: * </pre>
064: * <p/>
065: * The {@link org.jfree.report.function.strings.MessageFormatExpression} allows to specify named field-references in the
066: * pattern, which greatly simplifies the pattern-definition.
067: *
068: * @author Thomas Morgner
069: */
070: public class TextFormatExpression extends AbstractExpression {
071: /**
072: * An ordered list containing the fieldnames used in the expression.
073: */
074: private ArrayList fields;
075:
076: /**
077: * A temporary array to reduce the number of object creations.
078: */
079: private transient Object[] fieldValues;
080: /**
081: * A temporary array to reduce the number of object creations. This array is used to compute the cache's validity.
082: */
083: private transient Object[] oldFieldValues;
084:
085: /**
086: * The current locale. This is used to optimize the expression evaluation.
087: */
088: private transient Locale locale;
089: /**
090: * The current message format object.
091: */
092: private transient MessageFormat messageFormat;
093:
094: /**
095: * The message-format pattern used to compute the result.
096: */
097: private String pattern;
098: /**
099: * A flag indicating whether the data read from the fields should be URL encoded.
100: */
101: private boolean urlEncodeData;
102: /**
103: * A flag indicating whether the whole result string should be URL encoded.
104: */
105: private boolean urlEncodeResult;
106: /**
107: * The byte-encoding used for the URL encoding.
108: */
109: private String encoding;
110: /**
111: * A cached result.
112: */
113: private String cachedResult;
114:
115: /**
116: * Default constructor, creates a new unnamed TextFormatExpression.
117: */
118: public TextFormatExpression() {
119: fields = new ArrayList();
120: encoding = "iso-8859-1";
121: }
122:
123: /**
124: * Returns the defined character encoding that is used to transform the Java-Unicode strings into bytes.
125: *
126: * @return the encoding.
127: */
128: public String getEncoding() {
129: return encoding;
130: }
131:
132: /**
133: * Defines the character encoding that is used to transform the Java-Unicode strings into bytes.
134: *
135: * @param encoding the encoding.
136: */
137: public void setEncoding(final String encoding) {
138: if (encoding == null) {
139: throw new NullPointerException();
140: }
141: this .encoding = encoding;
142: }
143:
144: /**
145: * Defines, whether the values read from the data-row should be URL encoded. Dates and Number objects are never
146: * encoded.
147: *
148: * @param urlEncode true, if the values from the data-row should be URL encoded before they are passed to the
149: * MessageFormat, false otherwise.
150: */
151: public void setUrlEncodeValues(final boolean urlEncode) {
152: this .urlEncodeData = urlEncode;
153: }
154:
155: /**
156: * Queries, whether the values read from the data-row should be URL encoded.
157: *
158: * @return true, if the values are encoded, false otherwise.
159: */
160: public boolean isUrlEncodeValues() {
161: return urlEncodeData;
162: }
163:
164: /**
165: * Queries, whether the formatted result-string will be URL encoded.
166: *
167: * @return true, if the formatted result will be encoded, false otherwise.
168: */
169: public boolean isUrlEncodeResult() {
170: return urlEncodeResult;
171: }
172:
173: /**
174: * Defines, whether the formatted result-string will be URL encoded.
175: *
176: * @param urlEncodeResult true, if the formatted result will be encoded, false otherwise.
177: */
178: public void setUrlEncodeResult(final boolean urlEncodeResult) {
179: this .urlEncodeResult = urlEncodeResult;
180: }
181:
182: /**
183: * Evaluates the expression by collecting all values defined in the fieldlist from the datarow. The collected values
184: * are then parsed and formated by the MessageFormat-object.
185: *
186: * @return a string containing the pattern inclusive the formatted values from the datarow
187: */
188: public Object getValue() {
189: if (fields.isEmpty()) {
190: return getPattern();
191: }
192:
193: final ResourceBundleFactory factory = getResourceBundleFactory();
194: if (messageFormat == null
195: || ObjectUtilities.equal(locale, factory.getLocale()) == false) {
196: messageFormat = new MessageFormat("");
197: messageFormat.setLocale(factory.getLocale());
198: messageFormat.applyPattern(getPattern());
199: this .locale = factory.getLocale();
200: }
201:
202: try {
203: if (oldFieldValues == null
204: || oldFieldValues.length != fields.size()) {
205: oldFieldValues = new Object[fields.size()];
206: } else if (fieldValues != null
207: && fieldValues.length == oldFieldValues.length) {
208: System.arraycopy(fieldValues, 0, oldFieldValues, 0,
209: fields.size());
210: }
211:
212: fieldValues = getFieldValues(fieldValues);
213: final String result;
214: if (cachedResult != null
215: && Arrays.equals(oldFieldValues, fieldValues)) {
216: result = cachedResult;
217: } else {
218: result = messageFormat.format(fieldValues);
219: cachedResult = result;
220: }
221:
222: if (isUrlEncodeResult()) {
223: return UTFEncodingUtil.encode(result, getEncoding());
224: } else {
225: return result;
226: }
227: } catch (UnsupportedEncodingException e) {
228: Log.debug("Unsupported Encoding: " + encoding);
229: return null;
230: }
231: }
232:
233: /**
234: * Collects the values of all fields defined in the fieldList.
235: *
236: * @param retval an optional array that will receive the field values.
237: * @return an Object-array containing all defined values from the datarow
238: * @throws java.io.UnsupportedEncodingException if the character encoding is not recognized by the JDK.
239: */
240: protected Object[] getFieldValues(Object[] retval)
241: throws UnsupportedEncodingException {
242: final int size = fields.size();
243: if (retval == null || retval.length != size) {
244: retval = new Object[size];
245: }
246:
247: final DataRow dataRow = getDataRow();
248: for (int i = 0; i < size; i++) {
249: final String field = (String) fields.get(i);
250: if (field == null) {
251: retval[i] = null;
252: continue;
253: }
254: final Object fieldValue = dataRow.get(field);
255: if (isUrlEncodeValues()) {
256: if (fieldValue == null) {
257: retval[i] = null;
258: } else if (fieldValue instanceof Date) {
259: retval[i] = fieldValue;
260: } else if (fieldValue instanceof Number) {
261: retval[i] = fieldValue;
262: } else if (isUrlEncodeValues()) {
263: retval[i] = UTFEncodingUtil.encode(String
264: .valueOf(fieldValue), encoding);
265: } else {
266: retval[i] = fieldValue;
267: }
268: } else {
269: retval[i] = fieldValue;
270: }
271: }
272: return retval;
273: }
274:
275: /**
276: * Returns the pattern defined for this expression.
277: *
278: * @return the pattern.
279: */
280: public String getPattern() {
281: return pattern;
282: }
283:
284: /**
285: * Defines the pattern for this expression. The pattern syntax is defined by the java.text.MessageFormat object and
286: * the given pattern string has to be valid according to the rules defined there.
287: *
288: * @param pattern the pattern string
289: */
290: public void setPattern(final String pattern) {
291: if (pattern == null) {
292: throw new NullPointerException();
293: }
294: this .messageFormat = null;
295: this .pattern = pattern;
296: this .cachedResult = null;
297: }
298:
299: /**
300: * Return a completly separated copy of this function. The copy does no longer share any changeable objects with the
301: * original function.
302: *
303: * @return a copy of this function.
304: */
305: public Expression getInstance() {
306: final TextFormatExpression tex = (TextFormatExpression) super
307: .getInstance();
308: tex.fields = (ArrayList) fields.clone();
309: tex.fieldValues = null;
310: tex.oldFieldValues = null;
311: tex.cachedResult = null;
312: return tex;
313: }
314:
315: /**
316: * Defines the field in the field-list at the given index.
317: *
318: * @param index the position in the list, where the field should be defined.
319: * @param field the name of the field.
320: */
321: public void setField(final int index, final String field) {
322: if (fields.size() == index) {
323: fields.add(field);
324: } else {
325: fields.set(index, field);
326: }
327: fieldValues = null;
328: oldFieldValues = null;
329: cachedResult = null;
330: }
331:
332: /**
333: * Returns the defined field at the given index-position.
334: *
335: * @param index the position of the field name that should be queried.
336: * @return the field name at the given position.
337: */
338: public String getField(final int index) {
339: return (String) fields.get(index);
340: }
341:
342: /**
343: * Returns the number of fields defined in this expression.
344: * @return the number of fields.
345: */
346: public int getFieldCount() {
347: return fields.size();
348: }
349:
350: /**
351: * Returns all defined fields as array of strings.
352: *
353: * @return all the fields.
354: */
355: public String[] getField() {
356: return (String[]) fields.toArray(new String[fields.size()]);
357: }
358:
359: /**
360: * Defines all fields as array. This completely replaces any previously defined fields.
361: *
362: * @param fields the new list of fields.
363: */
364: public void setField(final String[] fields) {
365: this.fields.clear();
366: this.fields.addAll(Arrays.asList(fields));
367: fieldValues = null;
368: oldFieldValues = null;
369: cachedResult = null;
370: }
371: }
|