OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 142 | 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
 *
182 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
 /*
15
 * Field created on 4 mai 2004
16
 */
17
package org.openconcerto.sql.model;
18
 
19
import org.openconcerto.utils.CompareUtils;
67 ilm 20
import org.openconcerto.utils.StringUtils;
142 ilm 21
import org.openconcerto.xml.JDOM2Utils;
17 ilm 22
 
23
import java.math.BigDecimal;
24
import java.math.BigInteger;
25
import java.sql.Blob;
26
import java.sql.Clob;
27
import java.sql.Date;
20 ilm 28
import java.sql.Time;
17 ilm 29
import java.sql.Timestamp;
30
import java.sql.Types;
31
import java.util.Arrays;
32
import java.util.HashMap;
33
import java.util.List;
34
import java.util.Map;
35
 
142 ilm 36
import org.jdom2.Element;
37
 
83 ilm 38
import net.jcip.annotations.GuardedBy;
39
import net.jcip.annotations.ThreadSafe;
17 ilm 40
 
41
/**
83 ilm 42
 * The type of a SQL field. Allow one to convert a Java object to its SQL serialization.
17 ilm 43
 *
44
 * @see #toString(Object)
45
 * @see #check(Object)
46
 * @author Sylvain
47
 */
83 ilm 48
@ThreadSafe
17 ilm 49
public abstract class SQLType {
50
 
142 ilm 51
    private static Class<?> getClass(int type, final int size, SQLSyntax s) {
17 ilm 52
        switch (type) {
83 ilm 53
        case Types.BIT:
54
            // As of MySQL 5.0.3, BIT is for storing bit-field values
55
            // As of Connector/J 3.1.9, transformedBitIsBoolean can be used
56
            // MAYBE remove Boolean after testing it works against 4.1 servers
57
            if (size == 1) {
17 ilm 58
                return Boolean.class;
83 ilm 59
            } else if (size <= Integer.SIZE) {
17 ilm 60
                return Integer.class;
83 ilm 61
            } else if (size <= Long.SIZE) {
17 ilm 62
                return Long.class;
83 ilm 63
            } else
64
                return BigInteger.class;
65
        case Types.BOOLEAN:
66
            return Boolean.class;
67
        case Types.DOUBLE:
68
            return Double.class;
69
        case Types.FLOAT:
70
        case Types.REAL:
71
            return Float.class;
72
        case Types.TIMESTAMP:
73
            return Timestamp.class;
74
        case Types.DATE:
142 ilm 75
            return java.sql.Date.class;
83 ilm 76
        case Types.TIME:
77
            return java.sql.Time.class;
78
        case Types.INTEGER:
142 ilm 79
            return Integer.class;
83 ilm 80
        case Types.SMALLINT:
142 ilm 81
            /*
82
             * http://download.oracle.com/otndocs/jcp/jdbc-4_1-mrel-spec/index.html Appendix B : The
83
             * JDBC 1.0 specification defined the Java object mapping for the SMALLINT and TINYINT
84
             * JDBC types to be Integer. The Java language did not include the Byte and Short data
85
             * types when the JDBC 1.0 specification was finalized. The mapping of SMALLINT and
86
             * TINYINT to Integer is maintained to preserve backwards compatibility
87
             */
88
            return s.getSystem() == SQLSystem.H2 ? Short.class : Integer.class;
83 ilm 89
        case Types.TINYINT:
142 ilm 90
            return s.getSystem() == SQLSystem.H2 ? Byte.class : Integer.class;
83 ilm 91
        case Types.BINARY:
92
        case Types.VARBINARY:
93
        case Types.LONGVARBINARY:
94
        case Types.BLOB:
95
            return Blob.class;
96
        case Types.CLOB:
97
            return Clob.class;
98
        case Types.BIGINT:
99
            // 8 bytes
100
            return Long.class;
101
        case Types.DECIMAL:
102
        case Types.NUMERIC:
103
            return BigDecimal.class;
104
        case Types.CHAR:
105
        case Types.VARCHAR:
106
        case Types.LONGVARCHAR:
107
            return String.class;
108
        default:
109
            // eg view columns are OTHER
110
            return Object.class;
17 ilm 111
        }
112
    }
113
 
83 ilm 114
    @GuardedBy("instances")
17 ilm 115
    static private final Map<List<String>, SQLType> instances = new HashMap<List<String>, SQLType>();
116
 
80 ilm 117
    // useful when no SQLBase is known
118
    public static SQLType getBoolean(final SQLSyntax s) {
142 ilm 119
        return getFromSyntax(s, Types.BOOLEAN, 1);
80 ilm 120
    }
121
 
142 ilm 122
    public static SQLType getFromSyntax(final SQLSyntax s, final int type, final int size) {
123
        return get(s, type, size, null, s.getTypeNames(getClass(type, size, s)).iterator().next());
17 ilm 124
    }
125
 
142 ilm 126
    public static SQLType get(final SQLBase base, final int type, final int size) {
127
        return getFromSyntax(SQLSyntax.get(base), type, size);
128
    }
129
 
17 ilm 130
    /**
131
     * Get the corresponding type.
132
     *
142 ilm 133
     * @param base the base of this type, cannot be <code>null</code>.
17 ilm 134
     * @param type a value from java.sql.Types.
135
     * @param size the size as COLUMN_SIZE is defined in
136
     *        {@link java.sql.DatabaseMetaData#getColumns(java.lang.String, java.lang.String, java.lang.String, java.lang.String)}
137
     * @param decDigits the number of fractional digits, can be <code>null</code>.
138
     * @param typeName data source dependent type name.
139
     * @return the corresponding instance.
140
     * @throws IllegalStateException if type is unknown.
141
     */
142
    public static SQLType get(final SQLBase base, final int type, final int size, Integer decDigits, final String typeName) {
142 ilm 143
        return get(SQLSyntax.get(base), type, size, decDigits, typeName);
144
    }
145
 
146
    public static SQLType get(final SQLSyntax s, final int type, final int size, Integer decDigits, final String typeName) {
147
        final List<String> typeID = Arrays.asList(s.toString(), type + "", size + "", String.valueOf(decDigits), typeName);
83 ilm 148
        synchronized (instances) {
149
            SQLType res = instances.get(typeID);
150
            if (res == null) {
142 ilm 151
                final Class<?> clazz = getClass(type, size, s);
83 ilm 152
                if (Boolean.class.isAssignableFrom(clazz))
153
                    res = new BooleanType(type, size, typeName, clazz);
154
                else if (Number.class.isAssignableFrom(clazz))
155
                    res = new NumberType(type, size, decDigits, typeName, clazz);
156
                else if (Time.class.isAssignableFrom(clazz))
157
                    res = new TimeType(type, size, decDigits, typeName, clazz);
158
                else if (Timestamp.class.isAssignableFrom(clazz))
159
                    res = new TimestampType(type, size, decDigits, typeName, clazz);
160
                // Date en dernier surclasse des autres
161
                else if (java.util.Date.class.isAssignableFrom(clazz))
162
                    res = new DateType(type, size, decDigits, typeName, clazz);
163
                else if (String.class.isAssignableFrom(clazz))
164
                    res = new StringType(type, size, typeName, clazz);
165
                else
166
                    // BLOB & CLOB and the rest
167
                    res = new UnknownType(type, size, typeName, clazz);
142 ilm 168
                if (s != null)
169
                    res.setSyntax(s);
83 ilm 170
                instances.put(typeID, res);
171
            }
172
            return res;
17 ilm 173
        }
174
    }
175
 
176
    public static SQLType get(final SQLBase base, Element typeElement) {
177
        int type = Integer.valueOf(typeElement.getAttributeValue("type")).intValue();
178
        int size = Integer.valueOf(typeElement.getAttributeValue("size")).intValue();
179
        String typeName = typeElement.getAttributeValue("typeName");
180
        final String decDigitsS = typeElement.getAttributeValue("decimalDigits");
181
        final Integer decDigits = decDigitsS == null ? null : Integer.valueOf(decDigitsS);
182
        return get(base, type, size, decDigits, typeName);
183
    }
184
 
185
    // *** instance
186
 
187
    // a value from java.sql.Types
188
    private final int type;
189
    // COLUMN_SIZE
190
    private final int size;
191
    // DECIMAL_DIGITS
192
    private final Integer decimalDigits;
193
    // TYPE_NAME
194
    private final String typeName;
195
    // the class this type accepts
67 ilm 196
    private final Class<?> javaType;
17 ilm 197
 
83 ilm 198
    @GuardedBy("this")
17 ilm 199
    private SQLSyntax syntax;
200
 
83 ilm 201
    @GuardedBy("this")
17 ilm 202
    private String xml;
203
 
67 ilm 204
    private SQLType(int type, int size, Integer decDigits, String typeName, Class<?> javaType) {
17 ilm 205
        this.type = type;
206
        this.size = size;
207
        this.decimalDigits = decDigits;
208
        this.typeName = typeName;
209
        this.javaType = javaType;
210
        this.xml = null;
211
    }
212
 
213
    /**
214
     * The SQL type.
215
     *
216
     * @return a value from java.sql.Types.
217
     */
218
    public int getType() {
219
        return this.type;
220
    }
221
 
222
    private final boolean isNumeric() {
223
        return this.getType() == Types.DECIMAL || this.getType() == Types.NUMERIC;
224
    }
225
 
226
    public final int getSize() {
227
        return this.size;
228
    }
229
 
230
    public final Integer getDecimalDigits() {
231
        return this.decimalDigits;
232
    }
233
 
142 ilm 234
    /**
235
     * The type returned from the DB.
236
     *
237
     * @return the java class returned by {@link SQLResultSet#getObject(int)}.
238
     */
17 ilm 239
    public Class<?> getJavaType() {
240
        return this.javaType;
241
    }
242
 
243
    /**
244
     * Data source dependent type name.
245
     *
246
     * @return the data source dependent type name.
247
     */
248
    public final String getTypeName() {
249
        return this.typeName;
250
    }
251
 
83 ilm 252
    private synchronized final void setSyntax(SQLSyntax s) {
80 ilm 253
        // set only once
142 ilm 254
        if (this.syntax != null)
255
            throw new IllegalStateException("Already set to " + this.syntax);
256
        this.syntax = s;
80 ilm 257
    }
258
 
83 ilm 259
    public synchronized final SQLSyntax getSyntax() {
17 ilm 260
        return this.syntax;
261
    }
262
 
80 ilm 263
    protected final String quoteString(String s) {
142 ilm 264
        return SQLSyntax.quoteString(this.getSyntax(), s);
80 ilm 265
    }
266
 
17 ilm 267
    @Override
268
    public boolean equals(Object obj) {
269
        return equals(obj, null);
270
    }
271
 
182 ilm 272
    public boolean equals(Object obj, SQLSyntax otherSyntax) {
17 ilm 273
        if (obj instanceof SQLType) {
274
            final SQLType o = (SQLType) obj;
275
            final boolean javaTypeOK = this.getJavaType().equals(o.getJavaType());
276
            if (!javaTypeOK) {
277
                return false;
278
            } else if (this.getJavaType() == Boolean.class) {
279
                // can take many forms (e.g. BIT(1) or BOOLEAN) but type and size are meaningless
280
                return true;
281
            }
282
 
283
            // for all intents and purposes NUMERIC == DECIMAL
284
            final boolean typeOK = this.isNumeric() ? o.isNumeric() : this.getType() == o.getType();
285
            if (!typeOK)
286
                return false;
287
 
288
            final boolean sizeOK;
289
            // date has no precision so size is meaningless
290
            // Floating-Point Types have no precision (apart from single or double precision, but
291
            // this is handled by typeOK)
292
            if (this.getType() == Types.DATE || this.getJavaType() == Float.class || this.getJavaType() == Double.class) {
293
                sizeOK = true;
294
            } else {
182 ilm 295
                if (otherSyntax == null)
296
                    otherSyntax = o.getSyntax();
17 ilm 297
                final SQLSystem thisSystem = this.getSyntax() == null ? null : this.getSyntax().getSystem();
298
                final boolean isTime = this.getType() == Types.TIME || this.getType() == Types.TIMESTAMP;
299
                final boolean decDigitsOK;
300
                // only TIME and NUMERIC use DECIMAL_DIGITS, others like integer use only size
301
                if (!this.isNumeric() && !isTime) {
302
                    decDigitsOK = true;
303
                } else if (this.isNumeric() ||
304
                // isTime() : if we don't know the system, play it safe and compare
182 ilm 305
                        thisSystem == null || otherSyntax == null || thisSystem.isFractionalSecondsSupported() && otherSyntax.getSystem().isFractionalSecondsSupported()) {
17 ilm 306
                    decDigitsOK = CompareUtils.equals(this.getDecimalDigits(), o.getDecimalDigits());
307
                } else {
308
                    decDigitsOK = true;
309
                }
310
                // not all systems return the same size for TIME but only DECIMAL DIGITS matters
311
                sizeOK = decDigitsOK && (isTime || this.getSize() == o.getSize());
312
            }
313
            return sizeOK;
314
        } else {
315
            return super.equals(obj);
316
        }
317
    }
318
 
319
    @Override
320
    public int hashCode() {
321
        return this.getType() + this.getSize() + this.getJavaType().hashCode();
322
    }
323
 
67 ilm 324
    @Override
17 ilm 325
    public final String toString() {
326
        return "SQLType #" + this.getType() + "(" + this.getSize() + "," + this.getDecimalDigits() + "): " + this.getJavaType();
327
    }
328
 
83 ilm 329
    public synchronized final String toXML() {
17 ilm 330
        // this class is immutable and its instances shared so cache its XML
331
        if (this.xml == null) {
332
            final StringBuilder sb = new StringBuilder(128);
333
            sb.append("<type type=\"");
334
            sb.append(this.type);
335
            sb.append("\" size=\"");
336
            sb.append(this.size);
337
            if (this.decimalDigits != null) {
338
                sb.append("\" decimalDigits=\"");
339
                sb.append(this.decimalDigits);
340
            }
341
            sb.append("\" typeName=\"");
142 ilm 342
            sb.append(JDOM2Utils.OUTPUTTER.escapeAttributeEntities(this.typeName));
17 ilm 343
            sb.append("\"/>");
344
            this.xml = sb.toString();
345
        }
346
        return this.xml;
347
    }
348
 
349
    /**
67 ilm 350
     * Serialize an object to its SQL string.
17 ilm 351
     *
67 ilm 352
     * @param o an instance of getJavaType(), e.g. "it's".
353
     * @return the SQL representation, e.g. "'it''s'".
17 ilm 354
     * @throws IllegalArgumentException if o is not valid.
355
     * @see #isValid(Object)
356
     */
357
    public final String toString(Object o) {
358
        this.check(o);
359
        if (o == null)
360
            return "NULL";
361
        else
362
            return this.toStringRaw(o);
363
    }
364
 
67 ilm 365
    /**
366
     * Serialize an object to its CSV string. <code>null</code> is \N, other values are always
367
     * quoted.
368
     *
369
     * @param o an instance of getJavaType(), e.g. "it's" or 12.
370
     * @return the CSV representation, e.g. "it's" or "12".
371
     * @throws IllegalArgumentException if o is not valid.
372
     * @see #isValid(Object)
373
     */
374
    public final String toCSV(Object o) {
375
        this.check(o);
376
        if (o == null)
377
            return "\\N";
17 ilm 378
        else
67 ilm 379
            return StringUtils.doubleQuote(this.toCSVRaw(o));
17 ilm 380
    }
381
 
67 ilm 382
    // fromString(String s) is too complicated, e.g. see SQLBase#unquoteStringStd()
17 ilm 383
 
384
    /**
385
     * Check if o is valid, do nothing if it is else throw an exception.
386
     *
387
     * @param o the object to check.
388
     * @throws IllegalArgumentException if o is not valid.
389
     */
390
    public final void check(Object o) {
80 ilm 391
        if (!isValid(o)) {
392
            final String className = o == null ? "" : "(" + o.getClass() + ")";
393
            throw new IllegalArgumentException(o + className + " is not valid for " + this);
394
        }
17 ilm 395
    }
396
 
397
    /**
142 ilm 398
     * Test whether the passed parameter is valid for this type.
17 ilm 399
     *
400
     * @param o the object to test.
142 ilm 401
     * @return <code>true</code> if o can be passed to {@link #toString(Object)}, always
402
     *         <code>true</code> for an instance of {@link #getJavaType()}.
17 ilm 403
     * @see #check(Object)
404
     */
405
    public boolean isValid(Object o) {
406
        return o == null || this.getJavaType().isInstance(o);
407
    }
408
 
409
    abstract protected String toStringRaw(Object o);
410
 
67 ilm 411
    abstract protected String toCSVRaw(Object o);
412
 
17 ilm 413
    // ** static subclasses
414
 
415
    private static final class UnknownType extends SQLType {
67 ilm 416
        public UnknownType(int type, int size, String typeName, Class<?> clazz) {
17 ilm 417
            super(type, size, null, typeName, clazz);
418
        }
419
 
67 ilm 420
        @Override
17 ilm 421
        protected String toStringRaw(Object o) {
422
            throw new IllegalStateException("not implemented");
423
        }
67 ilm 424
 
425
        @Override
426
        protected String toCSVRaw(Object o) {
427
            throw new IllegalStateException("not implemented");
428
        }
17 ilm 429
    }
430
 
431
    private abstract static class ToStringType extends SQLType {
67 ilm 432
        public ToStringType(int type, int size, String typeName, Class<?> clazz) {
17 ilm 433
            this(type, size, null, typeName, clazz);
434
        }
435
 
67 ilm 436
        public ToStringType(int type, int size, Integer decDigits, String typeName, Class<?> clazz) {
17 ilm 437
            super(type, size, decDigits, typeName, clazz);
438
        }
439
 
67 ilm 440
        @Override
17 ilm 441
        protected String toStringRaw(Object o) {
442
            return o.toString();
443
        }
67 ilm 444
 
445
        @Override
446
        protected String toCSVRaw(Object o) {
447
            return toStringRaw(o);
448
        }
17 ilm 449
    }
450
 
451
    private static class BooleanType extends ToStringType {
67 ilm 452
        public BooleanType(int type, int size, String typeName, Class<?> clazz) {
17 ilm 453
            super(type, size, typeName, clazz);
454
        }
455
 
456
        @Override
457
        protected String toStringRaw(Object o) {
458
            if (this.getSyntax().getSystem() == SQLSystem.MSSQL) {
459
                // 'true'
80 ilm 460
                return this.quoteString(o.toString());
17 ilm 461
            } else
462
                return super.toStringRaw(o);
463
        }
67 ilm 464
 
465
        @Override
466
        protected String toCSVRaw(Object o) {
467
            // see SQLSyntaxPG.selectAll
468
            return Boolean.TRUE.equals(o) ? "1" : "0";
469
        }
17 ilm 470
    }
471
 
472
    private static class NumberType extends ToStringType {
67 ilm 473
        public NumberType(int type, int size, Integer decDigits, String typeName, Class<?> clazz) {
17 ilm 474
            super(type, size, decDigits, typeName, clazz);
475
        }
476
 
477
        @Override
478
        public boolean isValid(Object o) {
479
            return super.isValid(o) || o instanceof Number;
480
        }
481
    }
482
 
483
    private static abstract class DateOrTimeType extends SQLType {
67 ilm 484
        public DateOrTimeType(int type, int size, Integer decDigits, String typeName, Class<?> clazz) {
17 ilm 485
            super(type, size, decDigits, typeName, clazz);
486
        }
487
 
488
        @Override
489
        public final boolean isValid(Object o) {
490
            return super.isValid(o) || o instanceof java.util.Date || o instanceof java.util.Calendar || o instanceof Number;
491
        }
492
 
67 ilm 493
        @Override
494
        public final String toStringRaw(Object o) {
495
            // time has no special characters to escape
142 ilm 496
            return getSyntax().cast("'" + toCSVRaw(o) + "'", this);
67 ilm 497
        }
498
 
499
        static protected long getTime(Object o) {
17 ilm 500
            if (o instanceof java.util.Date)
501
                return ((java.util.Date) o).getTime();
502
            else if (o instanceof java.util.Calendar)
503
                return ((java.util.Calendar) o).getTimeInMillis();
504
            else
505
                return ((Number) o).longValue();
506
        }
507
    }
508
 
509
    private static class DateType extends DateOrTimeType {
67 ilm 510
        public DateType(int type, int size, Integer decDigits, String typeName, Class<?> clazz) {
17 ilm 511
            super(type, size, decDigits, typeName, clazz);
512
        }
513
 
67 ilm 514
        @Override
515
        protected String toCSVRaw(Object o) {
516
            final Date ts;
517
            if (o instanceof Date)
518
                ts = (Date) o;
519
            else
520
                ts = new Date(getTime(o));
521
            return ts.toString();
17 ilm 522
        }
523
    }
524
 
525
    private static class TimestampType extends DateOrTimeType {
67 ilm 526
        public TimestampType(int type, int size, Integer decDigits, String typeName, Class<?> clazz) {
17 ilm 527
            super(type, size, decDigits, typeName, clazz);
528
        }
529
 
67 ilm 530
        @Override
531
        protected String toCSVRaw(Object o) {
17 ilm 532
            final Timestamp ts;
533
            if (o instanceof Timestamp)
534
                ts = (Timestamp) o;
535
            else
536
                ts = new Timestamp(getTime(o));
67 ilm 537
            return ts.toString();
17 ilm 538
        }
539
    }
540
 
20 ilm 541
    private static class TimeType extends DateOrTimeType {
67 ilm 542
        public TimeType(int type, int size, Integer decDigits, String typeName, Class<?> clazz) {
20 ilm 543
            super(type, size, decDigits, typeName, clazz);
544
        }
545
 
546
        @Override
67 ilm 547
        protected String toCSVRaw(Object o) {
20 ilm 548
            final Time ts;
549
            if (o instanceof Time)
550
                ts = (Time) o;
551
            else
552
                ts = new Time(getTime(o));
67 ilm 553
            return ts.toString();
20 ilm 554
        }
555
    }
556
 
17 ilm 557
    private static class StringType extends SQLType {
67 ilm 558
        public StringType(int type, int size, String typeName, Class<?> clazz) {
17 ilm 559
            super(type, size, null, typeName, clazz);
560
        }
561
 
67 ilm 562
        @Override
17 ilm 563
        protected String toStringRaw(Object o) {
80 ilm 564
            return this.quoteString((String) o);
17 ilm 565
        }
566
 
567
        @Override
67 ilm 568
        protected String toCSVRaw(Object o) {
569
            return (String) o;
17 ilm 570
        }
571
    }
572
 
573
}