OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | 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
 *
185 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
17 ilm 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
    },
180 ilm 88
    // TODO support LocalDateTime
17 ilm 89
    DATE("date-value", Date.class, Calendar.class) {
90
 
91
        @Override
92
        public String format(Object o) {
80 ilm 93
            return formatDate(o);
17 ilm 94
        }
95
 
96
        @Override
97
        public Date parse(String date) {
98
            if (date.length() == 0)
99
                return null;
100
            else {
101
                try {
80 ilm 102
                    return parseDateValue(date).getTime();
17 ilm 103
                } catch (ParseException e) {
104
                    throw new IllegalStateException("wrong date: " + date, e);
105
                }
106
            }
107
        }
108
 
109
    },
180 ilm 110
    TIME("time-value", Duration.class, java.time.Duration.class, Calendar.class) {
21 ilm 111
 
17 ilm 112
        @Override
113
        public String format(Object o) {
21 ilm 114
            if (o instanceof Duration) {
115
                return o.toString();
180 ilm 116
            } else if (o instanceof java.time.Duration) {
117
                // w/o days or larger : PTnHnMnS
118
                return o.toString();
21 ilm 119
            } else {
120
                final Calendar cal = (Calendar) o;
25 ilm 121
                return TimeUtils.timePartToDuration(cal).toString();
21 ilm 122
            }
17 ilm 123
        }
124
 
125
        @Override
21 ilm 126
        public Duration parse(String date) {
17 ilm 127
            if (date.length() == 0)
128
                return null;
129
            else {
25 ilm 130
                return TimeUtils.getTypeFactory().newDuration(date);
17 ilm 131
            }
132
        }
133
 
134
    },
135
    BOOLEAN("boolean-value", Boolean.class) {
136
 
137
        @Override
138
        public String format(Object o) {
139
            return ((Boolean) o).toString().toLowerCase();
140
        }
141
 
142
        @Override
143
        public Boolean parse(String s) {
144
            return Boolean.valueOf(s);
145
        }
146
 
147
    },
148
    STRING("string-value", String.class) {
149
 
150
        @Override
151
        public String format(Object o) {
152
            return o.toString();
153
        }
154
 
155
        @Override
156
        public String parse(String s) {
157
            return s;
158
        }
159
    };
160
 
161
    private final String attr;
162
    private final List<Class<?>> acceptedClasses;
163
 
164
    private ODValueType(String attr, Class<?>... classes) {
165
        this.attr = attr;
166
        this.acceptedClasses = Arrays.asList(classes);
167
    }
168
 
169
    /**
170
     * The name of the value attribute for this value type.
171
     *
80 ilm 172
     * @return the value attribute, e.g. "boolean-value".
17 ilm 173
     */
174
    public final String getValueAttribute() {
175
        return this.attr;
176
    }
177
 
178
    public boolean canFormat(Class<?> toFormat) {
179
        for (final Class<?> c : this.acceptedClasses)
180
            if (c.isAssignableFrom(toFormat))
181
                return true;
182
        return false;
183
    }
184
 
185
    public abstract String format(Object o);
186
 
187
    public abstract Object parse(String s);
188
 
189
    /**
190
     * The value for the value-type attribute.
191
     *
80 ilm 192
     * @return the value for the value-type attribute, e.g. "float".
17 ilm 193
     */
194
    public final String getName() {
195
        return this.name().toLowerCase();
196
    }
197
 
198
    /**
199
     * The instance for the passed value type.
200
     *
67 ilm 201
     * @param name the value of the value-type attribute, e.g. "date".
202
     * @return the corresponding instance, never <code>null</code>, e.g. {@link #DATE}.
203
     * @throws IllegalArgumentException if <code>name</code> isn't a valid type.
17 ilm 204
     */
205
    public static ODValueType get(String name) {
206
        return ODValueType.valueOf(name.toUpperCase());
207
    }
208
 
209
    /**
210
     * Try to guess the value type for the passed object.
211
     *
212
     * @param o the object.
213
     * @return a value type capable of formatting <code>o</code> or <code>null</code>.
25 ilm 214
     * @throws NullPointerException if <code>o</code> is <code>null</code>.
17 ilm 215
     */
25 ilm 216
    public static ODValueType forObject(Object o) throws NullPointerException {
217
        if (o == null)
218
            throw new NullPointerException();
17 ilm 219
        if (o instanceof Number)
220
            return FLOAT;
221
        else if (o instanceof Boolean)
222
            return BOOLEAN;
223
        else if (o instanceof String)
224
            return STRING;
180 ilm 225
        else if (o instanceof Duration || o instanceof java.time.Duration)
17 ilm 226
            return TIME;
227
        else if (DATE.canFormat(o.getClass()))
228
            return DATE;
229
        else
230
            return null;
231
    }
232
 
80 ilm 233
    static private final TimeZone UTC_TZ = TimeZone.getTimeZone("UTC");
234
    // use nulls and not (TimeZone|Locale).getDefault() so as to not need to listen to changes
235
    // LibreOffice behavior as of 4.1 is to no longer ignore explicit time zone when reading dates.
236
    @GuardedBy("ODValueType")
237
    static private DateConfig DATE_CONFIG = new DateConfig(null, null, null, Boolean.FALSE);
238
 
21 ilm 239
    // see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#isoformats
240
 
80 ilm 241
    @GuardedBy("ODValueType")
242
    static private Format DATE_FORMAT, DATE_PARSER;
21 ilm 243
    static {
80 ilm 244
        updateFormat();
21 ilm 245
    }
80 ilm 246
 
247
    static private synchronized String formatDate(Object obj) {
248
        return DATE_FORMAT.format(obj);
249
    }
250
 
251
    static private synchronized void updateFormat() {
252
        // always remove time zone on write
253
        DATE_FORMAT = new XMLCalendarFormat(getTimeZone(false), getLocale(false));
254
        DATE_PARSER = getFormatParser(DATE_CONFIG, true);
255
    }
256
 
257
    static private synchronized SimpleDateFormat createDateFormat(final String pattern, final DateConfig dateConf) {
258
        final SimpleDateFormat res = new SimpleDateFormat(pattern, dateConf.getLocale(true));
259
        res.setTimeZone(!dateConf.isTimeZoneIgnored() ? UTC_TZ : dateConf.getTimeZone(true));
260
        return res;
261
    }
262
 
263
    static private synchronized Format getFormatParser(final DateConfig dateConf, final boolean forceCreate) {
264
        if (!forceCreate && dateConf.equals(DATE_CONFIG)) {
265
            return DATE_PARSER;
266
        } else {
267
            final Format xmlDF;
268
            if (dateConf.isTimeZoneIgnored()) {
269
                xmlDF = new XMLCalendarFormat(dateConf.getTimeZone(false), dateConf.getLocale(false));
270
            } else {
271
                xmlDF = new XMLDateFormat(UTC_TZ, null);
272
            }
273
            // first date and time so we don't loose time information on format() or parse()
274
            // MAYBE add HH':'mm':'ss,SSS for OOo 1
275
            return new FormatGroup(xmlDF, createDateFormat("yyyy-MM-dd'T'HH':'mm':'ss", dateConf), createDateFormat("yyyy-MM-dd", dateConf));
276
        }
277
    }
278
 
279
    static private synchronized final void setDateConfig(final DateConfig newVal) {
280
        if (!newVal.equals(DATE_CONFIG)) {
281
            DATE_CONFIG = newVal;
282
            updateFormat();
283
        }
284
    }
285
 
286
    /**
287
     * Set the framework default time zone. Pass <code>null</code> to always use the VM default
288
     * (passing {@link TimeZone#getDefault()} would set the value once and for all and wouldn't be
289
     * changed by {@link TimeZone#setDefault(TimeZone)}).
290
     *
291
     * @param tz the new default time zone, <code>null</code> to use the VM default.
292
     */
293
    static public synchronized final void setTimeZone(final TimeZone tz) {
294
        setDateConfig(DATE_CONFIG.setTimeZone(tz));
295
    }
296
 
297
    /**
298
     * The framework default time zone.
299
     *
300
     * @param notNull <code>true</code> if <code>null</code> should be replaced by
301
     *        {@link TimeZone#getDefault()}.
302
     * @return the default time zone, can only be <code>null</code> if <code>notNull</code> is
303
     *         <code>false</code>.
304
     */
305
    static public synchronized final TimeZone getTimeZone(final boolean notNull) {
306
        return DATE_CONFIG.getTimeZone(notNull);
307
    }
308
 
309
    /**
310
     * Set the framework default locale. Pass <code>null</code> to always use the VM default
311
     * (passing {@link Locale#getDefault()} would set the value once and for all and wouldn't be
312
     * changed by {@link Locale#setDefault(Locale)}).
313
     *
314
     * @param locale the new default locale, <code>null</code> to use the VM default.
315
     */
316
    static public synchronized final void setLocale(final Locale locale) {
317
        setDateConfig(DATE_CONFIG.setLocale(locale));
318
    }
319
 
320
    /**
321
     * The framework default locale.
322
     *
323
     * @param notNull <code>true</code> if <code>null</code> should be replaced by
324
     *        {@link Locale#getDefault()}.
325
     * @return the default locale, can only be <code>null</code> if <code>notNull</code> is
326
     *         <code>false</code>.
327
     */
328
    static public synchronized final Locale getLocale(final boolean notNull) {
329
        return DATE_CONFIG.getLocale(notNull);
330
    }
331
 
332
    /**
333
     * Get the framework default calendar.
334
     *
335
     * @return the default calendar.
336
     * @see #getTimeZone(boolean)
337
     * @see #getLocale(boolean)
338
     */
339
    static public synchronized final Calendar getCalendar() {
340
        return DATE_CONFIG.getCalendar();
341
    }
342
 
343
    static public synchronized final void setTimeZoneIgnored(final boolean b) {
344
        setDateConfig(DATE_CONFIG.setTimeZoneIgnored(b));
345
    }
346
 
347
    /**
348
     * Whether to ignore explicit time zone in dates. Prior to 4.1 LibreOffice would ignore explicit
349
     * time zones, i.e. "2013-11-15T12:00:00.000" and "2013-11-15T12:00:00.000+01:00" would both
350
     * parse to noon. As of 4.1 the first one parse to noon, the second one to 11 AM.
351
     *
352
     * @return <code>true</code> if the time zone part should be ignored.
353
     */
354
    static public synchronized final boolean isTimeZoneIgnored() {
355
        return DATE_CONFIG.isTimeZoneIgnored();
356
    }
357
 
358
    /**
359
     * Parse an OpenDocument date value with the framework defaults.
360
     *
361
     * @param date the string formatted value.
362
     * @return a calendar with the local time of the passed date.
363
     * @throws ParseException if the value couldn't be parsed.
364
     * @see #parseDateValue(String, TimeZone, Locale, Boolean)
365
     */
366
    static public synchronized Calendar parseDateValue(final String date) throws ParseException {
367
        return parseDateValue(date, null, null, null);
368
    }
369
 
370
    /**
371
     * Parse an OpenDocument date value with the passed parameters.
372
     *
373
     * @param date the string formatted value.
374
     * @param tz the time zone of the returned calendar, <code>null</code> meaning
375
     *        {@link #getTimeZone(boolean)}.
376
     * @param locale the locale of the returned calendar, <code>null</code> meaning
377
     *        {@link #getLocale(boolean)}.
378
     * @param ignoreTZ whether to ignore the time zone part of <code>part</code>, <code>null</code>
379
     *        meaning {@link #isTimeZoneIgnored()}.
380
     * @return a calendar with the local time of the passed date.
381
     * @throws ParseException if the value couldn't be parsed.
382
     */
383
    static public synchronized Calendar parseDateValue(final String date, final TimeZone tz, final Locale locale, final Boolean ignoreTZ) throws ParseException {
384
        final DateConfig conf = new DateConfig(DATE_CONFIG, tz, locale, ignoreTZ);
385
        final Object parsed = getFormatParser(conf, false).parseObject(date);
386
        if (parsed instanceof Calendar) {
387
            // XMLCalendarFormat
388
            return (Calendar) parsed;
389
        } else {
390
            final Calendar res = conf.getCalendar();
391
            if (conf.isTimeZoneIgnored()) {
392
                // SimpleDateFormat
393
                res.setTime((Date) parsed);
394
                return res;
395
            } else {
396
                // XMLDateFormat or SimpleDateFormat
397
                final Calendar cal = Calendar.getInstance(UTC_TZ);
398
                cal.setTime((Date) parsed);
399
                return TimeUtils.copyLocalTime(cal, res);
400
            }
401
        }
402
    }
17 ilm 403
}