OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 67 | Rev 180 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.openoffice;
15
 
21 ilm 16
import org.openconcerto.utils.FormatGroup;
25 ilm 17
import org.openconcerto.utils.TimeUtils;
80 ilm 18
import org.openconcerto.utils.XMLCalendarFormat;
21 ilm 19
import org.openconcerto.utils.XMLDateFormat;
20
 
17 ilm 21
import java.math.BigDecimal;
21 ilm 22
import java.text.Format;
17 ilm 23
import java.text.ParseException;
24
import java.text.SimpleDateFormat;
25
import java.util.Arrays;
26
import java.util.Calendar;
27
import java.util.Date;
28
import java.util.List;
80 ilm 29
import java.util.Locale;
30
import java.util.TimeZone;
17 ilm 31
 
21 ilm 32
import javax.xml.datatype.Duration;
33
 
80 ilm 34
import net.jcip.annotations.GuardedBy;
35
import net.jcip.annotations.Immutable;
36
 
17 ilm 37
/**
38
 * A type of value, as per 16.1 "Data Types" and 6.7.1 "Variable Value Types and Values"
39
 */
80 ilm 40
@Immutable
17 ilm 41
public enum ODValueType {
42
 
43
    /**
44
     * Parses to {@link BigDecimal} to return the exact number.
45
     */
46
    FLOAT("value", Number.class) {
47
 
48
        @Override
49
        public String format(Object o) {
50
            // avoid 1.23E+3
51
            if (o instanceof BigDecimal)
52
                return ((BigDecimal) o).toPlainString();
53
            else
54
                return ((Number) o).toString();
55
        }
56
 
57
        @Override
58
        public BigDecimal parse(String s) {
59
            return new BigDecimal(s);
60
        }
61
 
62
    },
63
    PERCENTAGE("value", Number.class) {
64
 
65
        @Override
66
        public String format(Object o) {
67
            return FLOAT.format(o);
68
        }
69
 
70
        @Override
71
        public Object parse(String s) {
72
            return FLOAT.parse(s);
73
        }
74
 
75
    },
76
    CURRENCY("value", Number.class) {
77
 
78
        @Override
79
        public String format(Object o) {
80
            return FLOAT.format(o);
81
        }
82
 
83
        @Override
84
        public Object parse(String s) {
85
            return FLOAT.parse(s);
86
        }
87
    },
88
    DATE("date-value", Date.class, Calendar.class) {
89
 
90
        @Override
91
        public String format(Object o) {
80 ilm 92
            return formatDate(o);
17 ilm 93
        }
94
 
95
        @Override
96
        public Date parse(String date) {
97
            if (date.length() == 0)
98
                return null;
99
            else {
100
                try {
80 ilm 101
                    return parseDateValue(date).getTime();
17 ilm 102
                } catch (ParseException e) {
103
                    throw new IllegalStateException("wrong date: " + date, e);
104
                }
105
            }
106
        }
107
 
108
    },
21 ilm 109
    TIME("time-value", Duration.class, Calendar.class) {
110
 
17 ilm 111
        @Override
112
        public String format(Object o) {
21 ilm 113
            if (o instanceof Duration) {
114
                return o.toString();
115
            } else {
116
                final Calendar cal = (Calendar) o;
25 ilm 117
                return TimeUtils.timePartToDuration(cal).toString();
21 ilm 118
            }
17 ilm 119
        }
120
 
121
        @Override
21 ilm 122
        public Duration parse(String date) {
17 ilm 123
            if (date.length() == 0)
124
                return null;
125
            else {
25 ilm 126
                return TimeUtils.getTypeFactory().newDuration(date);
17 ilm 127
            }
128
        }
129
 
130
    },
131
    BOOLEAN("boolean-value", Boolean.class) {
132
 
133
        @Override
134
        public String format(Object o) {
135
            return ((Boolean) o).toString().toLowerCase();
136
        }
137
 
138
        @Override
139
        public Boolean parse(String s) {
140
            return Boolean.valueOf(s);
141
        }
142
 
143
    },
144
    STRING("string-value", String.class) {
145
 
146
        @Override
147
        public String format(Object o) {
148
            return o.toString();
149
        }
150
 
151
        @Override
152
        public String parse(String s) {
153
            return s;
154
        }
155
    };
156
 
157
    private final String attr;
158
    private final List<Class<?>> acceptedClasses;
159
 
160
    private ODValueType(String attr, Class<?>... classes) {
161
        this.attr = attr;
162
        this.acceptedClasses = Arrays.asList(classes);
163
    }
164
 
165
    /**
166
     * The name of the value attribute for this value type.
167
     *
80 ilm 168
     * @return the value attribute, e.g. "boolean-value".
17 ilm 169
     */
170
    public final String getValueAttribute() {
171
        return this.attr;
172
    }
173
 
174
    public boolean canFormat(Class<?> toFormat) {
175
        for (final Class<?> c : this.acceptedClasses)
176
            if (c.isAssignableFrom(toFormat))
177
                return true;
178
        return false;
179
    }
180
 
181
    public abstract String format(Object o);
182
 
183
    public abstract Object parse(String s);
184
 
185
    /**
186
     * The value for the value-type attribute.
187
     *
80 ilm 188
     * @return the value for the value-type attribute, e.g. "float".
17 ilm 189
     */
190
    public final String getName() {
191
        return this.name().toLowerCase();
192
    }
193
 
194
    /**
195
     * The instance for the passed value type.
196
     *
67 ilm 197
     * @param name the value of the value-type attribute, e.g. "date".
198
     * @return the corresponding instance, never <code>null</code>, e.g. {@link #DATE}.
199
     * @throws IllegalArgumentException if <code>name</code> isn't a valid type.
17 ilm 200
     */
201
    public static ODValueType get(String name) {
202
        return ODValueType.valueOf(name.toUpperCase());
203
    }
204
 
205
    /**
206
     * Try to guess the value type for the passed object.
207
     *
208
     * @param o the object.
209
     * @return a value type capable of formatting <code>o</code> or <code>null</code>.
25 ilm 210
     * @throws NullPointerException if <code>o</code> is <code>null</code>.
17 ilm 211
     */
25 ilm 212
    public static ODValueType forObject(Object o) throws NullPointerException {
213
        if (o == null)
214
            throw new NullPointerException();
17 ilm 215
        if (o instanceof Number)
216
            return FLOAT;
217
        else if (o instanceof Boolean)
218
            return BOOLEAN;
219
        else if (o instanceof String)
220
            return STRING;
25 ilm 221
        else if (o instanceof Duration)
17 ilm 222
            return TIME;
223
        else if (DATE.canFormat(o.getClass()))
224
            return DATE;
225
        else
226
            return null;
227
    }
228
 
80 ilm 229
    static private final TimeZone UTC_TZ = TimeZone.getTimeZone("UTC");
230
    // use nulls and not (TimeZone|Locale).getDefault() so as to not need to listen to changes
231
    // LibreOffice behavior as of 4.1 is to no longer ignore explicit time zone when reading dates.
232
    @GuardedBy("ODValueType")
233
    static private DateConfig DATE_CONFIG = new DateConfig(null, null, null, Boolean.FALSE);
234
 
21 ilm 235
    // see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#isoformats
236
 
80 ilm 237
    @GuardedBy("ODValueType")
238
    static private Format DATE_FORMAT, DATE_PARSER;
21 ilm 239
    static {
80 ilm 240
        updateFormat();
21 ilm 241
    }
80 ilm 242
 
243
    static private synchronized String formatDate(Object obj) {
244
        return DATE_FORMAT.format(obj);
245
    }
246
 
247
    static private synchronized void updateFormat() {
248
        // always remove time zone on write
249
        DATE_FORMAT = new XMLCalendarFormat(getTimeZone(false), getLocale(false));
250
        DATE_PARSER = getFormatParser(DATE_CONFIG, true);
251
    }
252
 
253
    static private synchronized SimpleDateFormat createDateFormat(final String pattern, final DateConfig dateConf) {
254
        final SimpleDateFormat res = new SimpleDateFormat(pattern, dateConf.getLocale(true));
255
        res.setTimeZone(!dateConf.isTimeZoneIgnored() ? UTC_TZ : dateConf.getTimeZone(true));
256
        return res;
257
    }
258
 
259
    static private synchronized Format getFormatParser(final DateConfig dateConf, final boolean forceCreate) {
260
        if (!forceCreate && dateConf.equals(DATE_CONFIG)) {
261
            return DATE_PARSER;
262
        } else {
263
            final Format xmlDF;
264
            if (dateConf.isTimeZoneIgnored()) {
265
                xmlDF = new XMLCalendarFormat(dateConf.getTimeZone(false), dateConf.getLocale(false));
266
            } else {
267
                xmlDF = new XMLDateFormat(UTC_TZ, null);
268
            }
269
            // first date and time so we don't loose time information on format() or parse()
270
            // MAYBE add HH':'mm':'ss,SSS for OOo 1
271
            return new FormatGroup(xmlDF, createDateFormat("yyyy-MM-dd'T'HH':'mm':'ss", dateConf), createDateFormat("yyyy-MM-dd", dateConf));
272
        }
273
    }
274
 
275
    static private synchronized final void setDateConfig(final DateConfig newVal) {
276
        if (!newVal.equals(DATE_CONFIG)) {
277
            DATE_CONFIG = newVal;
278
            updateFormat();
279
        }
280
    }
281
 
282
    /**
283
     * Set the framework default time zone. Pass <code>null</code> to always use the VM default
284
     * (passing {@link TimeZone#getDefault()} would set the value once and for all and wouldn't be
285
     * changed by {@link TimeZone#setDefault(TimeZone)}).
286
     *
287
     * @param tz the new default time zone, <code>null</code> to use the VM default.
288
     */
289
    static public synchronized final void setTimeZone(final TimeZone tz) {
290
        setDateConfig(DATE_CONFIG.setTimeZone(tz));
291
    }
292
 
293
    /**
294
     * The framework default time zone.
295
     *
296
     * @param notNull <code>true</code> if <code>null</code> should be replaced by
297
     *        {@link TimeZone#getDefault()}.
298
     * @return the default time zone, can only be <code>null</code> if <code>notNull</code> is
299
     *         <code>false</code>.
300
     */
301
    static public synchronized final TimeZone getTimeZone(final boolean notNull) {
302
        return DATE_CONFIG.getTimeZone(notNull);
303
    }
304
 
305
    /**
306
     * Set the framework default locale. Pass <code>null</code> to always use the VM default
307
     * (passing {@link Locale#getDefault()} would set the value once and for all and wouldn't be
308
     * changed by {@link Locale#setDefault(Locale)}).
309
     *
310
     * @param locale the new default locale, <code>null</code> to use the VM default.
311
     */
312
    static public synchronized final void setLocale(final Locale locale) {
313
        setDateConfig(DATE_CONFIG.setLocale(locale));
314
    }
315
 
316
    /**
317
     * The framework default locale.
318
     *
319
     * @param notNull <code>true</code> if <code>null</code> should be replaced by
320
     *        {@link Locale#getDefault()}.
321
     * @return the default locale, can only be <code>null</code> if <code>notNull</code> is
322
     *         <code>false</code>.
323
     */
324
    static public synchronized final Locale getLocale(final boolean notNull) {
325
        return DATE_CONFIG.getLocale(notNull);
326
    }
327
 
328
    /**
329
     * Get the framework default calendar.
330
     *
331
     * @return the default calendar.
332
     * @see #getTimeZone(boolean)
333
     * @see #getLocale(boolean)
334
     */
335
    static public synchronized final Calendar getCalendar() {
336
        return DATE_CONFIG.getCalendar();
337
    }
338
 
339
    static public synchronized final void setTimeZoneIgnored(final boolean b) {
340
        setDateConfig(DATE_CONFIG.setTimeZoneIgnored(b));
341
    }
342
 
343
    /**
344
     * Whether to ignore explicit time zone in dates. Prior to 4.1 LibreOffice would ignore explicit
345
     * time zones, i.e. "2013-11-15T12:00:00.000" and "2013-11-15T12:00:00.000+01:00" would both
346
     * parse to noon. As of 4.1 the first one parse to noon, the second one to 11 AM.
347
     *
348
     * @return <code>true</code> if the time zone part should be ignored.
349
     */
350
    static public synchronized final boolean isTimeZoneIgnored() {
351
        return DATE_CONFIG.isTimeZoneIgnored();
352
    }
353
 
354
    /**
355
     * Parse an OpenDocument date value with the framework defaults.
356
     *
357
     * @param date the string formatted value.
358
     * @return a calendar with the local time of the passed date.
359
     * @throws ParseException if the value couldn't be parsed.
360
     * @see #parseDateValue(String, TimeZone, Locale, Boolean)
361
     */
362
    static public synchronized Calendar parseDateValue(final String date) throws ParseException {
363
        return parseDateValue(date, null, null, null);
364
    }
365
 
366
    /**
367
     * Parse an OpenDocument date value with the passed parameters.
368
     *
369
     * @param date the string formatted value.
370
     * @param tz the time zone of the returned calendar, <code>null</code> meaning
371
     *        {@link #getTimeZone(boolean)}.
372
     * @param locale the locale of the returned calendar, <code>null</code> meaning
373
     *        {@link #getLocale(boolean)}.
374
     * @param ignoreTZ whether to ignore the time zone part of <code>part</code>, <code>null</code>
375
     *        meaning {@link #isTimeZoneIgnored()}.
376
     * @return a calendar with the local time of the passed date.
377
     * @throws ParseException if the value couldn't be parsed.
378
     */
379
    static public synchronized Calendar parseDateValue(final String date, final TimeZone tz, final Locale locale, final Boolean ignoreTZ) throws ParseException {
380
        final DateConfig conf = new DateConfig(DATE_CONFIG, tz, locale, ignoreTZ);
381
        final Object parsed = getFormatParser(conf, false).parseObject(date);
382
        if (parsed instanceof Calendar) {
383
            // XMLCalendarFormat
384
            return (Calendar) parsed;
385
        } else {
386
            final Calendar res = conf.getCalendar();
387
            if (conf.isTimeZoneIgnored()) {
388
                // SimpleDateFormat
389
                res.setTime((Date) parsed);
390
                return res;
391
            } else {
392
                // XMLDateFormat or SimpleDateFormat
393
                final Calendar cal = Calendar.getInstance(UTC_TZ);
394
                cal.setTime((Date) parsed);
395
                return TimeUtils.copyLocalTime(cal, res);
396
            }
397
        }
398
    }
17 ilm 399
}