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: * MessageFormatSupport.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.filter;
030:
031: import java.io.Serializable;
032: import java.text.DateFormat;
033: import java.text.Format;
034: import java.text.MessageFormat;
035: import java.text.NumberFormat;
036: import java.util.ArrayList;
037: import java.util.Date;
038: import java.util.Locale;
039: import java.util.Arrays;
040:
041: import org.jfree.report.DataRow;
042: import org.jfree.report.util.CSVTokenizer;
043: import org.jfree.report.util.PropertyLookupParser;
044: import org.jfree.util.Log;
045: import org.jfree.util.ObjectUtilities;
046:
047: /**
048: * The message format support class helps to translate named references to fields in a message format string into
049: * numeric index positions. With the help of this mapping, we can use a standard Java MessageFormat object to reference
050: * fields by their name instead of an arbitrary index position.
051: * <p/>
052: * A field is referenced by the pattern "$(fieldname)". For additional formatting, all MessageFormat format options are
053: * available using the format "$(fieldname, <message option>)". To format a date field with the default short date
054: * format, one would use the pattern $(datefield,date,short).
055: *
056: * @author Thomas Morgner
057: */
058: public class MessageFormatSupport implements Serializable, Cloneable {
059: /**
060: * The message compiler maps all named references into numeric references.
061: */
062: protected static class MessageCompiler extends PropertyLookupParser {
063: /**
064: * The list of fields that have been encountered during the compile process.
065: */
066: private ArrayList fields;
067:
068: /**
069: * Default Constructor.
070: */
071: public MessageCompiler() {
072: this .fields = new ArrayList();
073: setMarkerChar('$');
074: setOpeningBraceChar('(');
075: setClosingBraceChar(')');
076: }
077:
078: /**
079: * Looks up the property with the given name. This replaces the name with the current index position.
080: *
081: * @param name the name of the property to look up.
082: * @return the translated value.
083: */
084: protected String lookupVariable(final String name) {
085: final CSVTokenizer tokenizer = new CSVTokenizer(name, ",",
086: "\"");
087: if (tokenizer.hasMoreTokens() == false) {
088: return null;
089: }
090: final String varName = tokenizer.nextToken();
091:
092: final StringBuffer b = new StringBuffer();
093: b.append('{');
094: b.append(String.valueOf(fields.size()));
095: while (tokenizer.hasMoreTokens()) {
096: b.append(',');
097: b.append(tokenizer.nextToken());
098: }
099: b.append('}');
100: final String formatString = b.toString();
101: fields.add(varName);
102: return formatString;
103: }
104:
105: /**
106: * Returns the collected fields as string-array. The order of the array contents matches the order of the
107: * index-position references in the translated message format.
108: *
109: * @return the fields as array.
110: */
111: public String[] getFields() {
112: return (String[]) fields.toArray(new String[fields.size()]);
113: }
114: }
115:
116: /**
117: * The fields that have been collected during the compile process. The array also acts as mapping of index positions
118: * to field names.
119: */
120: private String[] fields;
121: /**
122: * The message-format object that is used to format the text.
123: */
124: private MessageFormat format;
125: /**
126: * The original format string.
127: */
128: private String formatString;
129: /**
130: * The translated message format string. All named references have been resolved to numeric index positions.
131: */
132: private String compiledFormat;
133: /**
134: * The replacement text that is used if one of the referenced message parameters is null.
135: */
136: private String nullString;
137:
138: /**
139: * The current locale of the message format.
140: */
141: private transient Locale locale;
142:
143: /**
144: * An internal status array. It contains flags on whether the internal formatter has been replaced due to a type
145: * mismatch.
146: */
147: private transient boolean[] replaced;
148: /**
149: * An internal status array. It contains flags on whether the internal formatter has been replaced due to a type
150: * mismatch. This list is used to compare the current flags with the cached ones.
151: */
152: private transient boolean[] oldReplaced;
153: /**
154: * An internal list of formats used by the MessageFormat object.
155: */
156: private transient Format[] formats;
157: /**
158: * An internal list of all parameters.
159: */
160: private transient Object[] parameters;
161: /**
162: * An internal list of all parameters. This list is used to compare the current parameters with the cached ones.
163: */
164: private transient Object[] oldParameters;
165: /**
166: * The cached formatted string value.
167: */
168: private String cachedValue;
169:
170: /**
171: * Default Constructor.
172: */
173: public MessageFormatSupport() {
174: }
175:
176: /**
177: * Returns the original format string that is used to format the fields. This format string contains named
178: * references.
179: *
180: * @return the format string.
181: */
182: public String getFormatString() {
183: return formatString;
184: }
185:
186: /**
187: * Updates the named format string and compiles a new field list and message-format string.
188: *
189: * @param formatString the format string.
190: */
191: public void setFormatString(final String formatString) {
192: if (formatString == null) {
193: throw new NullPointerException("Format must not be null");
194: }
195:
196: if (ObjectUtilities.equal(formatString, this .formatString)) {
197: return;
198: }
199:
200: final MessageCompiler compiler = new MessageCompiler();
201: this .compiledFormat = compiler.translateAndLookup(formatString);
202: this .fields = compiler.getFields();
203:
204: if (fields.length > 0) {
205: this .format = new MessageFormat(this .compiledFormat);
206: } else {
207: this .format = null;
208: }
209: this .formatString = formatString;
210: this .formats = null;
211: this .parameters = null;
212: this .replaced = null;
213:
214: this .oldParameters = null;
215: this .oldReplaced = null;
216: this .cachedValue = null;
217: }
218:
219: /**
220: * Formats the message using the fields from the given data-row as values for the parameters.
221: *
222: * @param dataRow the data row.
223: * @return the formated message.
224: */
225: public String performFormat(final DataRow dataRow) {
226: if (fields == null) {
227: return null;
228: }
229: if (fields.length == 0) {
230: return formatString;
231: }
232:
233: final boolean fastProcessingPossible = (nullString == null);
234:
235: if (formats == null) {
236: formats = format.getFormats();
237: }
238: if (fields.length != formats.length) {
239: Log.warn("There is an error in the format string: "
240: + formatString);
241: return null;
242: }
243: if (parameters == null) {
244: parameters = new Object[fields.length];
245: }
246: if (replaced == null) {
247: replaced = new boolean[fields.length];
248: }
249:
250: final int parameterCount = parameters.length;
251: if (oldParameters == null) {
252: oldParameters = new Object[fields.length];
253: } else {
254: System.arraycopy(parameters, 0, oldParameters, 0,
255: parameterCount);
256: }
257:
258: final int replaceCount = replaced.length;
259: if (oldReplaced == null) {
260: oldReplaced = new boolean[fields.length];
261: } else {
262: System.arraycopy(replaced, 0, oldReplaced, 0, replaceCount);
263: }
264:
265: boolean fastProcessing = true;
266: for (int i = 0; i < parameterCount; i++) {
267: final Object value = dataRow.get(fields[i]);
268: final Format currentFormat = formats[i];
269: if (value == null) {
270: parameters[i] = nullString;
271: replaced[i] = currentFormat != null;
272: fastProcessing = (fastProcessing
273: && fastProcessingPossible && replaced[i] == false);
274: } else {
275: if (currentFormat instanceof DateFormat) {
276: if (value instanceof Date) {
277: parameters[i] = value;
278: replaced[i] = false;
279: } else {
280: parameters[i] = nullString;
281: replaced[i] = true;
282: fastProcessing = (fastProcessing
283: && fastProcessingPossible && replaced[i] == false);
284: }
285: } else if (currentFormat instanceof NumberFormat) {
286: if (value instanceof Number) {
287: parameters[i] = value;
288: replaced[i] = false;
289: } else {
290: parameters[i] = nullString;
291: replaced[i] = true;
292: fastProcessing = (fastProcessing
293: && fastProcessingPossible && replaced[i] == false);
294: }
295: } else {
296: parameters[i] = value;
297: replaced[i] = false;
298: }
299: }
300: }
301:
302: if (cachedValue != null && Arrays.equals(replaced, oldReplaced)
303: && Arrays.equals(parameters, oldParameters)) {
304: return cachedValue;
305: }
306:
307: if (fastProcessing) {
308: cachedValue = format.format(parameters);
309: return cachedValue;
310: }
311:
312: final MessageFormat effectiveFormat = (MessageFormat) format
313: .clone();
314: for (int i = 0; i < replaceCount; i++) {
315: final boolean b = replaced[i];
316: if (b) {
317: effectiveFormat.setFormat(i, null);
318: }
319: }
320: cachedValue = effectiveFormat.format(parameters);
321: return cachedValue;
322: }
323:
324: /**
325: * Returns the compiled message format string.
326: *
327: * @return the compiled message format string.
328: */
329: public String getCompiledFormat() {
330: return compiledFormat;
331: }
332:
333: /**
334: * Returns the locale that is used to format the messages.
335: *
336: * @return the locale in the message format.
337: */
338: public Locale getLocale() {
339: return format.getLocale();
340: }
341:
342: /**
343: * Updates the locale that is used to format the messages.
344: *
345: * @param locale the locale in the message format.
346: */
347: public void setLocale(final Locale locale) {
348: if (ObjectUtilities.equal(locale, this .locale)) {
349: return;
350: }
351: this .locale = locale;
352: if (format != null) {
353: this .format.setLocale(locale);
354: this .format.applyPattern(compiledFormat);
355: }
356:
357: this .formats = null;
358: this .parameters = null;
359: this .replaced = null;
360:
361: this .oldParameters = null;
362: this .oldReplaced = null;
363: this .cachedValue = null;
364: }
365:
366: /**
367: * Returns the replacement text that is used if one of the referenced message parameters is null.
368: *
369: * @return the replacement text for null-values.
370: */
371: public String getNullString() {
372: return nullString;
373: }
374:
375: /**
376: * Defines the replacement text that is used if one of the referenced message parameters is null.
377: *
378: * @param nullString the replacement text for null-values.
379: */
380: public void setNullString(final String nullString) {
381: if (ObjectUtilities.equal(nullString, this .nullString) == false) {
382: this .nullString = nullString;
383:
384: this .oldParameters = null;
385: this .oldReplaced = null;
386: this .cachedValue = null;
387: }
388: }
389:
390: /**
391: * Creates a copy of this message format support object.
392: *
393: * @return the copy.
394: * @throws CloneNotSupportedException if an error occured.
395: */
396: public Object clone() throws CloneNotSupportedException {
397: final MessageFormatSupport support = (MessageFormatSupport) super
398: .clone();
399: if (format != null) {
400: support.format = (MessageFormat) format.clone();
401: }
402: if (formats != null) {
403: support.formats = (Format[]) formats.clone();
404: }
405: if (replaced != null) {
406: support.replaced = (boolean[]) replaced.clone();
407: }
408: if (parameters != null) {
409: support.parameters = (Object[]) parameters.clone();
410: }
411:
412: this.oldParameters = null;
413: this.oldReplaced = null;
414: this.cachedValue = null;
415: return support;
416: }
417: }
|