001: /*
002: * Copyright (c) 2003-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.validation.formatter;
032:
033: import java.text.DateFormat;
034: import java.text.ParseException;
035: import java.util.Date;
036: import java.util.ListResourceBundle;
037: import java.util.MissingResourceException;
038: import java.util.ResourceBundle;
039: import java.util.logging.Level;
040: import java.util.logging.Logger;
041:
042: import com.jgoodies.validation.util.ValidationUtils;
043:
044: /**
045: * Adds relative dates and output shortcuts to its superclass
046: * EmptyDateFormatter.<p>
047: *
048: * If output shortcuts are enabled, Yesterday, Today and Tomorrow
049: * are formatted using their localized human-language print strings.<p>
050: *
051: * If relative input is allowed, the parser accepts signed integers
052: * that encode a date relative to today; this input would otherwise
053: * be considered invalid.
054: *
055: * @author Karsten Lentzsch
056: * @version $Revision: 1.9 $
057: *
058: * @see com.jgoodies.validation.util.ValidationUtils
059: * @see javax.swing.JFormattedTextField
060: */
061: public class RelativeDateFormatter extends EmptyDateFormatter {
062:
063: // Keys for the Shortcut Localization *************************************
064:
065: /**
066: * The resource bundle key used to localize 'Yesterday'.
067: */
068: public static final String KEY_YESTERDAY = "RelativeDate.yesterday";
069:
070: /**
071: * The resource bundle key used to localize 'Today'.
072: */
073: public static final String KEY_TODAY = "RelativeDate.today";
074:
075: /**
076: * The resource bundle key used to localize 'Tomorrow'.
077: */
078: public static final String KEY_TOMORROW = "RelativeDate.tomorrow";
079:
080: // Static Fields **********************************************************
081:
082: /**
083: * Holds the resource bundle that will be used as default
084: * for all RelativeDateFormatters. Individual instances
085: * can override this default using <code>#setResourceBundle</code>.<p>
086: *
087: * The default bundle is lazily initialized in
088: * <code>#getResourceBundle()</code>.
089: *
090: * @see #getResourceBundle()
091: * @see #setResourceBundle(ResourceBundle)
092: */
093: private static ResourceBundle defaultResourceBundle;
094:
095: // ************************************************************************
096:
097: /**
098: * Describes whether the date values for yesterday, today and tomorrow
099: * shall be converted to their natural strings or be formatted.
100: * If a ResourceBundle has been set, the strings will be localized.
101: *
102: * @see #valueToString(Object)
103: */
104: private final boolean useOutputShortcuts;
105:
106: /**
107: * Describes whether integers are valid edit values. If so, these
108: * are interpreted as day offset relative to today.
109: *
110: * @see #stringToValue(String)
111: */
112: private final boolean allowRelativeInput;
113:
114: /**
115: * Refers to an optional ResourceBundle that is used to localize
116: * the texts for the output shortcuts: Yesterday, Today, Tomorrow.
117: */
118: private ResourceBundle resourceBundle;
119:
120: // Instance Creation ****************************************************
121:
122: /**
123: * Constructs a RelativeDateFormatter using the default DateFormat
124: * with output shortcuts and relative input enabled.
125: */
126: public RelativeDateFormatter() {
127: this (true, true);
128: }
129:
130: /**
131: * Constructs a RelativeDateFormatter using the given DateFormat
132: * with output shortcuts and relative input enabled.
133: *
134: * @param format the DateFormat used to format and parse dates
135: */
136: public RelativeDateFormatter(DateFormat format) {
137: this (format, true, true);
138: }
139:
140: /**
141: * Constructs a RelativeDateFormatter using the default DateFormat
142: * with output shortcuts and relative input configured as specified.
143: *
144: * @param useOutputShortcuts true indicates that dates are formatted
145: * with shortcuts for yesterday, today, and tomorrow, where false
146: * always converts using absolute numbers for day, month and year.
147: * @param allowRelativeInput true indicates that the parser accepts
148: * signed integers that encode a date relative to today; if false
149: * such input is considered invalid
150: */
151: public RelativeDateFormatter(boolean useOutputShortcuts,
152: boolean allowRelativeInput) {
153: this .useOutputShortcuts = useOutputShortcuts;
154: this .allowRelativeInput = allowRelativeInput;
155: }
156:
157: /**
158: * Constructs a RelativeDateFormatter using the given DateFormat
159: * with output shortcuts and relative input configured as specified.
160: *
161: * @param format the DateFormat used to format and parse dates
162: * @param useOutputShortcuts true indicates that dates are formatted
163: * with shortcuts for yesterday, today, and tomorrow, where false
164: * always converts using absolute numbers for day, month and year.
165: * @param allowRelativeInput true indicates that the parser accepts
166: * signed integers that encode a date relative to today; if false
167: * such input is considered invalid
168: */
169: public RelativeDateFormatter(DateFormat format,
170: boolean useOutputShortcuts, boolean allowRelativeInput) {
171: super (format);
172: this .useOutputShortcuts = useOutputShortcuts;
173: this .allowRelativeInput = allowRelativeInput;
174: }
175:
176: // Overriding Superclass Behavior ****************************************
177:
178: /**
179: * Returns the Object representation of the String <code>text</code>.<p>
180: *
181: * In addition to the delegate's behavior, this methods accepts
182: * signed integers interpreted as days relative to today.
183: *
184: * @param text the String to convert
185: * @return the Object representation of text
186: * @throws ParseException if there is an error in the conversion
187: */
188: @Override
189: public Object stringToValue(String text) throws ParseException {
190: if (!allowRelativeInput) {
191: return super .stringToValue(text);
192: }
193:
194: if (text.startsWith("+")) {
195: text = text.substring(1);
196: }
197: try {
198: int offsetDays = Integer.parseInt(text);
199: return ValidationUtils.getRelativeDate(offsetDays);
200: } catch (NumberFormatException e) {
201: return super .stringToValue(text);
202: }
203: }
204:
205: /**
206: * Returns a String representation of the Object <code>value</code>.
207: * This invokes <code>format</code> on the current DateFormat.<p>
208: *
209: * In addition to the superclass behavior, this method formats the dates
210: * for yesterday, today and tomorrow to the natural language strings.
211: *
212: * @param value The value to convert
213: * @return a String representation for the value
214: * @throws ParseException if there is an error in the conversion
215: */
216: @Override
217: public String valueToString(Object value) throws ParseException {
218: if (value == null || !useOutputShortcuts
219: || !(value instanceof Date)) {
220: return super .valueToString(value);
221: }
222:
223: Date date = (Date) value;
224: if (ValidationUtils.isYesterday(date)) {
225: return getString(KEY_YESTERDAY);
226: } else if (ValidationUtils.isToday(date)) {
227: return getString(KEY_TODAY);
228: } else if (ValidationUtils.isTomorrow(date)) {
229: return getString(KEY_TOMORROW);
230: } else {
231: return super .valueToString(value);
232: }
233: }
234:
235: // Internationalization **************************************************
236:
237: /**
238: * Returns the ResourceBundle that is used as default
239: * unless overridden by an individual bundle.
240: *
241: * @return the ResourceBundle that is used as default
242: */
243: public static ResourceBundle getDefaultResourceBundle() {
244: if (defaultResourceBundle == null) {
245: defaultResourceBundle = new DefaultResources();
246: }
247: return defaultResourceBundle;
248: }
249:
250: /**
251: * Sets the ResourceBundle that is used as default for all
252: * RelativeDateFormatters that have no individual bundle set.
253: *
254: * @param newDefaultBundle the ResourceBundle to be used as default
255: */
256: public static void setDefaultResourceBundle(
257: ResourceBundle newDefaultBundle) {
258: defaultResourceBundle = newDefaultBundle;
259: }
260:
261: /**
262: * Returns the ResourceBundle used to lookup localized texts
263: * for Yesterday, Today, and Tomorrow. In case no bundle is set,
264: * these 3 dates will be formatted using the English texts.
265: *
266: * @return the ResourceBundle used to lookup localized texts
267: *
268: * @see #setResourceBundle(ResourceBundle)
269: * @see #getDefaultResourceBundle()
270: * @see #setDefaultResourceBundle(ResourceBundle)
271: */
272: public final ResourceBundle getResourceBundle() {
273: return resourceBundle;
274: }
275:
276: /**
277: * Sets a ResourceBundle that will be used to lookup localized texts
278: * for Yesterday, Today, and Tomorrow. In case no bundle is set,
279: * the default bundle will be used.
280: *
281: * @param newBundle the ResourceBundle to set
282: *
283: * @see #getResourceBundle()
284: * @see #getDefaultResourceBundle()
285: * @see #setDefaultResourceBundle(ResourceBundle)
286: */
287: public final void setResourceBundle(ResourceBundle newBundle) {
288: resourceBundle = newBundle;
289: }
290:
291: /**
292: * Retrieves and returns a String for the given key
293: * from this formatter's resource bundle. If no individual
294: * resource bundle has been set, the default bundle is used
295: * to lookup the localized resources.
296: *
297: * @param key the key used to lookup the localized string
298: * @return the localized text or default text
299: *
300: * @see #getResourceBundle()
301: * @see #setResourceBundle(ResourceBundle)
302: * @see #getDefaultResourceBundle()
303: * @see #setDefaultResourceBundle(ResourceBundle)
304: */
305: private String getString(String key) {
306: ResourceBundle bundle = getResourceBundle();
307: if (bundle == null) {
308: bundle = getDefaultResourceBundle();
309: }
310:
311: try {
312: return bundle.getString(key);
313: } catch (MissingResourceException e) {
314: Logger.getLogger(getClass().getName()).log(Level.WARNING,
315: "RelativeDateFormatter", e);
316: return "";
317: }
318: }
319:
320: // Default ResourceBundle *************************************************
321:
322: /**
323: * A default ResourceBundle that provides the default resources
324: * used to localize the strings for Yesterday, Today, and Tomorrow.
325: */
326: private static final class DefaultResources extends
327: ListResourceBundle {
328:
329: private static final Object[][] CONTENTS = {
330: { KEY_YESTERDAY, "Yesterday" }, { KEY_TODAY, "Today" },
331: { KEY_TOMORROW, "Tomorrow" }, };
332:
333: @Override
334: public Object[][] getContents() {
335: return CONTENTS;
336: }
337:
338: }
339:
340: }
|