Statistics
| Revision:

svn-gvsig-desktop / tags / v1_0_1_RELEASE / applications / appgvSIG / src / com / vividsolutions / jump / util / FlexibleDateParser.java @ 9531

History | View | Annotate | Download (10.4 KB)

1
/*
2
 * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI 
3
 * for visualizing and manipulating spatial features with geometry and attributes.
4
 *
5
 * Copyright (C) 2003 Vivid Solutions
6
 * 
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 * 
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 * 
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20
 * 
21
 * For more information, contact:
22
 *
23
 * Vivid Solutions
24
 * Suite #1A
25
 * 2328 Government Street
26
 * Victoria BC  V8T 5G5
27
 * Canada
28
 *
29
 * (250)385-6040
30
 * www.vividsolutions.com
31
 */
32
package com.vividsolutions.jump.util;
33

    
34
import java.awt.Color;
35
import java.awt.Component;
36
import java.io.IOException;
37
import java.io.InputStream;
38
import java.text.DateFormat;
39
import java.text.ParseException;
40
import java.text.ParsePosition;
41
import java.text.SimpleDateFormat;
42
import java.util.ArrayList;
43
import java.util.Calendar;
44
import java.util.Collection;
45
import java.util.Comparator;
46
import java.util.Date;
47
import java.util.Iterator;
48
import java.util.TreeSet;
49

    
50
import javax.swing.DefaultCellEditor;
51
import javax.swing.JComponent;
52
import javax.swing.JTable;
53
import javax.swing.JTextField;
54
import javax.swing.border.LineBorder;
55

    
56
import com.vividsolutions.jts.util.Assert;
57

    
58
/**
59
 * Warning: This class can parse a wide variety of formats. This flexibility is fine for parsing user
60
 * input because the user immediately sees whether the parser is correct and can fix it if
61
 * necessary. However, GML files are advised to stick with a safe format like yyyy-MM-dd.
62
 * yy/MM/dd is not as safe because while 99/03/04 will be parsed as yyyy/MM/dd, 
63
 * 02/03/04 will be parsed as MM/dd/yyyy (because MM/dd/yyyy appears earlier than yyyy/MM/dd
64
 * in FlexibleDateParser.txt).
65
 */
66
public class FlexibleDateParser {
67
    private static Collection lenientFormatters = null;
68
    private static Collection unlenientFormatters = null;
69
    //CellEditor used to be a static field CELL_EDITOR, but I was getting
70
    //problems calling it from ESETextField (it simply didn't appear).
71
    //The problems vanished when I turned it into a static class. I didn't
72
    //investigate further. [Jon Aquino]
73
    public static final class CellEditor extends DefaultCellEditor {
74
        public CellEditor() {
75
            super(new JTextField());
76
        }
77
        private Object value;
78
        private FlexibleDateParser parser = new FlexibleDateParser();
79

    
80
        public boolean stopCellEditing() {
81
            try {
82
                value = parser.parse((String) super.getCellEditorValue(), true);
83
            } catch (Exception e) {
84
                ((JComponent) getComponent()).setBorder(new LineBorder(Color.red));
85

    
86
                return false;
87
            }
88

    
89
            return super.stopCellEditing();
90
        }
91

    
92
        public Component getTableCellEditorComponent(
93
            JTable table,
94
            Object value,
95
            boolean isSelected,
96
            int row,
97
            int column) {
98
            this.value = null;
99
            ((JComponent) getComponent()).setBorder(new LineBorder(Color.black));
100

    
101
            return super.getTableCellEditorComponent(
102
                table,
103
                format((Date) value),
104
                isSelected,
105
                row,
106
                column);
107
        }
108

    
109
        private String format(Date date) {
110
            return (date == null) ? "" : formatter.format(date);
111
        }
112

    
113
        public Object getCellEditorValue() {
114
            return value;
115
        }
116

    
117
        //Same formatter as used by JTable.DateRenderer. [Jon Aquino]
118
        private DateFormat formatter = DateFormat.getDateInstance();
119
    };
120

    
121
    private boolean verbose = false;
122

    
123
    private Collection sortByComplexity(Collection patterns) {
124
        //Least complex to most complex. [Jon Aquino]
125
        TreeSet sortedPatterns = new TreeSet(new Comparator() {
126
            public int compare(Object o1, Object o2) {
127
                int result = complexity(o1.toString()) - complexity(o2.toString());
128
                if (result == 0) {
129
                    //The two patterns have the same level of complexity.
130
                    //Sort by order of appearance (e.g. to resolve
131
                    //MM/dd/yyyy vs dd/MM/yyyy [Jon Aquino]
132
                    result = ((Pattern) o1).index - ((Pattern) o2).index;
133
                }
134
                return result;
135
            }
136

    
137
            private TreeSet uniqueCharacters = new TreeSet();
138

    
139
            private int complexity(String pattern) {
140
                uniqueCharacters.clear();
141

    
142
                for (int i = 0; i < pattern.length(); i++) {
143
                    if (("" + pattern.charAt(i)).trim().length() > 0) {
144
                        uniqueCharacters.add("" + pattern.charAt(i));
145
                    }
146
                }
147

    
148
                return uniqueCharacters.size();
149
            }
150
        });
151
        sortedPatterns.addAll(patterns);
152

    
153
        return sortedPatterns;
154
    }
155

    
156
    private Collection lenientFormatters() {
157
        if (lenientFormatters == null) {
158
            load();
159
        }
160

    
161
        return lenientFormatters;
162
    }
163

    
164
    private Collection unlenientFormatters() {
165
        if (unlenientFormatters == null) {
166
            load();
167
        }
168

    
169
        return unlenientFormatters;
170
    }
171

    
172
    /**
173
     * @return null if s is empty
174
     */
175
    public Date parse(String s, boolean lenient) throws ParseException {
176
        if (s.trim().length() == 0) {
177
            return null;
178
        }
179
        //The deprecated Date#parse method is actually pretty flexible. [Jon Aquino]
180
        try {
181
            if (verbose) {
182
                System.out.println(s + " -- Date constructor");
183
            }
184
            return new Date(s);
185
        } catch (Exception e) {
186
            //Eat it. [Jon Aquino]
187
        }
188

    
189
        try {
190
            return parse(s, unlenientFormatters());
191
        } catch (ParseException e) {
192
            if (lenient) {
193
                return parse(s, lenientFormatters());
194
            }
195

    
196
            throw e;
197
        }
198
    }
199

    
200
    private Date parse(String s, Collection formatters) throws ParseException {
201
        ParseException firstParseException = null;
202

    
203
        for (Iterator i = formatters.iterator(); i.hasNext();) {
204
            SimpleDateFormat formatter = (SimpleDateFormat) i.next();
205

    
206
            if (verbose) {
207
                System.out.println(
208
                    s
209
                        + " -- "
210
                        + formatter.toPattern()
211
                        + (formatter.isLenient() ? "lenient" : ""));
212
            }
213

    
214
            try {
215
                return parse(s, formatter);
216
            } catch (ParseException e) {
217
                if (firstParseException == null) {
218
                    firstParseException = e;
219
                }
220
            }
221
        }
222

    
223
        throw firstParseException;
224
    }
225

    
226
    private Date parse(String s, SimpleDateFormat formatter) throws ParseException {
227
        ParsePosition pos = new ParsePosition(0);
228
        Date date = formatter.parse(s, pos);
229

    
230
        if (pos.getIndex() == 0) {
231
            throw new ParseException(
232
                "Unparseable date: \"" + s + "\"",
233
                pos.getErrorIndex());
234
        }
235

    
236
        //SimpleDateFormat ignores trailing characters in the pattern string that it
237
        //doesn't need. Don't allow it to ignore any characters. [Jon Aquino]
238
        if (pos.getIndex() != s.length()) {
239
            throw new ParseException(
240
                "Unparseable date: \"" + s + "\"",
241
                pos.getErrorIndex());
242
        }
243

    
244
        Calendar calendar = Calendar.getInstance();
245
        calendar.setTime(date);
246

    
247
        if ((calendar.get(Calendar.YEAR) == 1970) && (s.indexOf("70") == -1)) {
248
            calendar.set(Calendar.YEAR, Calendar.getInstance().get(Calendar.YEAR));
249
        }
250

    
251
        return calendar.getTime();
252
    }
253

    
254
    private static class Pattern {
255
        private String pattern;
256
        private int index;
257
        public Pattern(String pattern, int index) {
258
            this.pattern = pattern;
259
            this.index = index;
260
        }
261
        public String toString() {
262
            return pattern;
263
        }
264
    }
265

    
266
    private void load() {
267
        if (lenientFormatters == null) {
268
            InputStream inputStream =
269
                getClass().getResourceAsStream("FlexibleDateParser.txt");
270

    
271
            try {
272
                try {
273
                    Collection patterns = new ArrayList();
274
                    int index = 0;
275
                    for (Iterator i = FileUtil.getContents(inputStream).iterator();
276
                        i.hasNext();
277
                        ) {
278
                        String line = ((String) i.next()).trim();
279

    
280
                        if (line.startsWith("#")) {
281
                            continue;
282
                        }
283

    
284
                        if (line.length() == 0) {
285
                            continue;
286
                        }
287

    
288
                        patterns.add(new Pattern(line, index));
289
                        index++;
290
                    }
291

    
292
                    unlenientFormatters = toFormatters(false, patterns);
293
                    lenientFormatters = toFormatters(true, patterns);
294
                } finally {
295
                    inputStream.close();
296
                }
297
            } catch (IOException e) {
298
                Assert.shouldNeverReachHere(e.toString());
299
            }
300
        }
301
    }
302

    
303
    private Collection toFormatters(boolean lenient, Collection patterns) {
304
        ArrayList formatters = new ArrayList();
305
        //Sort from least complex to most complex; otherwise, ddMMMyyyy 
306
        //instead of MMMd will match "May 15". [Jon Aquino]
307
        for (Iterator i = sortByComplexity(patterns).iterator(); i.hasNext();) {
308
            Pattern pattern = (Pattern) i.next();
309
            SimpleDateFormat formatter = new SimpleDateFormat(pattern.pattern);
310
            formatter.setLenient(lenient);
311
            formatters.add(formatter);
312
        }
313

    
314
        return formatters;
315
    }
316

    
317
    public static void main(String[] args) throws Exception {
318
        System.out.println(DateFormat.getDateInstance().parse("03-Mar-1998"));
319
    }
320

    
321
    public void setVerbose(boolean b) {
322
        verbose = b;
323
    }
324
}