001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032: package com.vividsolutions.jump.util;
033:
034: import com.vividsolutions.jts.util.Assert;
035:
036: import java.awt.Color;
037: import java.awt.Component;
038:
039: import java.io.IOException;
040: import java.io.InputStream;
041:
042: import java.text.DateFormat;
043: import java.text.ParseException;
044: import java.text.ParsePosition;
045: import java.text.SimpleDateFormat;
046:
047: import java.util.ArrayList;
048: import java.util.Calendar;
049: import java.util.Collection;
050: import java.util.Comparator;
051: import java.util.Date;
052: import java.util.Iterator;
053: import java.util.List;
054: import java.util.StringTokenizer;
055: import java.util.TreeSet;
056:
057: import javax.swing.DefaultCellEditor;
058: import javax.swing.JComponent;
059: import javax.swing.JTable;
060: import javax.swing.JTextField;
061: import javax.swing.border.LineBorder;
062: import javax.swing.table.TableCellEditor;
063:
064: /**
065: * Warning: This class can parse a wide variety of formats. This flexibility is fine for parsing user
066: * input because the user immediately sees whether the parser is correct and can fix it if
067: * necessary. However, GML files are advised to stick with a safe format like yyyy-MM-dd.
068: * yy/MM/dd is not as safe because while 99/03/04 will be parsed as yyyy/MM/dd,
069: * 02/03/04 will be parsed as MM/dd/yyyy (because MM/dd/yyyy appears earlier than yyyy/MM/dd
070: * in FlexibleDateParser.txt).
071: */
072: public class FlexibleDateParser {
073: private static Collection lenientFormatters = null;
074: private static Collection unlenientFormatters = null;
075:
076: //CellEditor used to be a static field CELL_EDITOR, but I was getting
077: //problems calling it from ESETextField (it simply didn't appear).
078: //The problems vanished when I turned it into a static class. I didn't
079: //investigate further. [Jon Aquino]
080: public static final class CellEditor extends DefaultCellEditor {
081: public CellEditor() {
082: super (new JTextField());
083: }
084:
085: private Object value;
086: private FlexibleDateParser parser = new FlexibleDateParser();
087:
088: public boolean stopCellEditing() {
089: try {
090: value = parser.parse((String) super
091: .getCellEditorValue(), true);
092: } catch (Exception e) {
093: ((JComponent) getComponent()).setBorder(new LineBorder(
094: Color.red));
095:
096: return false;
097: }
098:
099: return super .stopCellEditing();
100: }
101:
102: public Component getTableCellEditorComponent(JTable table,
103: Object value, boolean isSelected, int row, int column) {
104: this .value = null;
105: ((JComponent) getComponent()).setBorder(new LineBorder(
106: Color.black));
107:
108: return super .getTableCellEditorComponent(table,
109: format((Date) value), isSelected, row, column);
110: }
111:
112: private String format(Date date) {
113: return (date == null) ? "" : formatter.format(date);
114: }
115:
116: public Object getCellEditorValue() {
117: return value;
118: }
119:
120: //Same formatter as used by JTable.DateRenderer. [Jon Aquino]
121: private DateFormat formatter = DateFormat.getDateInstance();
122: };
123:
124: private boolean verbose = false;
125:
126: private Collection sortByComplexity(Collection patterns) {
127: //Least complex to most complex. [Jon Aquino]
128: TreeSet sortedPatterns = new TreeSet(new Comparator() {
129: public int compare(Object o1, Object o2) {
130: int result = complexity(o1.toString())
131: - complexity(o2.toString());
132: if (result == 0) {
133: //The two patterns have the same level of complexity.
134: //Sort by order of appearance (e.g. to resolve
135: //MM/dd/yyyy vs dd/MM/yyyy [Jon Aquino]
136: result = ((Pattern) o1).index
137: - ((Pattern) o2).index;
138: }
139: return result;
140: }
141:
142: private TreeSet uniqueCharacters = new TreeSet();
143:
144: private int complexity(String pattern) {
145: uniqueCharacters.clear();
146:
147: for (int i = 0; i < pattern.length(); i++) {
148: if (("" + pattern.charAt(i)).trim().length() > 0) {
149: uniqueCharacters.add("" + pattern.charAt(i));
150: }
151: }
152:
153: return uniqueCharacters.size();
154: }
155: });
156: sortedPatterns.addAll(patterns);
157:
158: return sortedPatterns;
159: }
160:
161: private Collection lenientFormatters() {
162: if (lenientFormatters == null) {
163: load();
164: }
165:
166: return lenientFormatters;
167: }
168:
169: private Collection unlenientFormatters() {
170: if (unlenientFormatters == null) {
171: load();
172: }
173:
174: return unlenientFormatters;
175: }
176:
177: /**
178: * @return null if s is empty
179: */
180: public Date parse(String s, boolean lenient) throws ParseException {
181: if (s.trim().length() == 0) {
182: return null;
183: }
184: //The deprecated Date#parse method is actually pretty flexible. [Jon Aquino]
185: try {
186: if (verbose) {
187: System.out.println(s + " -- Date constructor");
188: }
189: return new Date(s);
190: } catch (Exception e) {
191: //Eat it. [Jon Aquino]
192: }
193:
194: try {
195: return parse(s, unlenientFormatters());
196: } catch (ParseException e) {
197: if (lenient) {
198: return parse(s, lenientFormatters());
199: }
200:
201: throw e;
202: }
203: }
204:
205: private Date parse(String s, Collection formatters)
206: throws ParseException {
207: ParseException firstParseException = null;
208:
209: for (Iterator i = formatters.iterator(); i.hasNext();) {
210: SimpleDateFormat formatter = (SimpleDateFormat) i.next();
211:
212: if (verbose) {
213: System.out.println(s + " -- " + formatter.toPattern()
214: + (formatter.isLenient() ? "lenient" : ""));
215: }
216:
217: try {
218: return parse(s, formatter);
219: } catch (ParseException e) {
220: if (firstParseException == null) {
221: firstParseException = e;
222: }
223: }
224: }
225:
226: throw firstParseException;
227: }
228:
229: private Date parse(String s, SimpleDateFormat formatter)
230: throws ParseException {
231: ParsePosition pos = new ParsePosition(0);
232: Date date = formatter.parse(s, pos);
233:
234: if (pos.getIndex() == 0) {
235: throw new ParseException("Unparseable date: \"" + s + "\"",
236: pos.getErrorIndex());
237: }
238:
239: //SimpleDateFormat ignores trailing characters in the pattern string that it
240: //doesn't need. Don't allow it to ignore any characters. [Jon Aquino]
241: if (pos.getIndex() != s.length()) {
242: throw new ParseException("Unparseable date: \"" + s + "\"",
243: pos.getErrorIndex());
244: }
245:
246: Calendar calendar = Calendar.getInstance();
247: calendar.setTime(date);
248:
249: if ((calendar.get(Calendar.YEAR) == 1970)
250: && (s.indexOf("70") == -1)) {
251: calendar.set(Calendar.YEAR, Calendar.getInstance().get(
252: Calendar.YEAR));
253: }
254:
255: return calendar.getTime();
256: }
257:
258: private static class Pattern {
259: private String pattern;
260: private int index;
261:
262: public Pattern(String pattern, int index) {
263: this .pattern = pattern;
264: this .index = index;
265: }
266:
267: public String toString() {
268: return pattern;
269: }
270: }
271:
272: private void load() {
273: if (lenientFormatters == null) {
274: InputStream inputStream = getClass().getResourceAsStream(
275: "FlexibleDateParser.txt");
276:
277: try {
278: try {
279: Collection patterns = new ArrayList();
280: int index = 0;
281: for (Iterator i = FileUtil.getContents(inputStream)
282: .iterator(); i.hasNext();) {
283: String line = ((String) i.next()).trim();
284:
285: if (line.startsWith("#")) {
286: continue;
287: }
288:
289: if (line.length() == 0) {
290: continue;
291: }
292:
293: patterns.add(new Pattern(line, index));
294: index++;
295: }
296:
297: unlenientFormatters = toFormatters(false, patterns);
298: lenientFormatters = toFormatters(true, patterns);
299: } finally {
300: inputStream.close();
301: }
302: } catch (IOException e) {
303: Assert.shouldNeverReachHere(e.toString());
304: }
305: }
306: }
307:
308: private Collection toFormatters(boolean lenient, Collection patterns) {
309: ArrayList formatters = new ArrayList();
310: //Sort from least complex to most complex; otherwise, ddMMMyyyy
311: //instead of MMMd will match "May 15". [Jon Aquino]
312: for (Iterator i = sortByComplexity(patterns).iterator(); i
313: .hasNext();) {
314: Pattern pattern = (Pattern) i.next();
315: SimpleDateFormat formatter = new SimpleDateFormat(
316: pattern.pattern);
317: formatter.setLenient(lenient);
318: formatters.add(formatter);
319: }
320:
321: return formatters;
322: }
323:
324: public static void main(String[] args) throws Exception {
325: System.out.println(DateFormat.getDateInstance().parse(
326: "03-Mar-1998"));
327: }
328:
329: public void setVerbose(boolean b) {
330: verbose = b;
331: }
332: }
|