OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 144 | 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
 /*
15
 * SQLRow created on 20 mai 2004
16
 */
17
package org.openconcerto.sql.model;
18
 
19
import org.openconcerto.sql.Log;
20
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
142 ilm 21
import org.openconcerto.sql.model.SQLSelect.LockStrength;
132 ilm 22
import org.openconcerto.sql.model.SQLTable.VirtualFields;
17 ilm 23
import org.openconcerto.sql.model.graph.Link;
67 ilm 24
import org.openconcerto.sql.model.graph.Link.Direction;
17 ilm 25
import org.openconcerto.sql.model.graph.Path;
19 ilm 26
import org.openconcerto.sql.utils.ReOrder;
17 ilm 27
import org.openconcerto.utils.DecimalUtils;
83 ilm 28
import org.openconcerto.utils.ListMap;
93 ilm 29
import org.openconcerto.utils.SetMap;
17 ilm 30
 
31
import java.math.BigDecimal;
32
import java.sql.ResultSet;
33
import java.sql.ResultSetMetaData;
34
import java.sql.SQLException;
35
import java.util.ArrayList;
36
import java.util.Arrays;
37
import java.util.Collection;
38
import java.util.Collections;
39
import java.util.HashMap;
40
import java.util.HashSet;
41
import java.util.Iterator;
42
import java.util.LinkedHashSet;
43
import java.util.List;
44
import java.util.Map;
93 ilm 45
import java.util.Map.Entry;
17 ilm 46
import java.util.Set;
132 ilm 47
import java.util.logging.Level;
17 ilm 48
 
49
import org.apache.commons.dbutils.ResultSetHandler;
50
 
51
/**
52
 * Une ligne d'une table. Cette classe décrit une ligne et ne représente pas exactement une ligne
53
 * réelle, il n'y a pas unicité (cela reviendrait à recréer la base en Java !). Pour charger les
54
 * valeurs depuis la base manuellement à tout moment utiliser fetchValues(), cette méthode est
55
 * appelée automatiquement si nécessaire. Les valeurs des champs sont stockées, ainsi toutes les
56
 * méthodes renvoient l'état de la ligne réelle au moment du dernier fetchValues().
57
 * <p>
58
 * Une ligne peut ne pas exister ou être archivée, de plus elle peut ne pas contenir tous les champs
59
 * de la table. Pour accéder à la valeur des champs il existe getString() et getInt(), pour des
60
 * demandes plus complexes passer par getObject(). Si un champ qui n'est pas dans la ligne est
61
 * demandé, un fetchValues() est automatiquement fait.
62
 * </p>
63
 * <p>
64
 * On peut obtenir un ligne en la demandant à sa table, mais si l'on souhaite une SQLRow décrivant
65
 * une ligne n'existant pas dans la base il faut passer par le constructeur.
66
 * </p>
67
 *
68
 * @author ILM Informatique 20 mai 2004
69
 * @see #isValid()
70
 * @see #getObject(String)
71
 * @see org.openconcerto.sql.model.SQLTable#getRow(int)
72
 */
73
public class SQLRow extends SQLRowAccessor {
74
 
75
    /**
76
     * Each table must have a row with this ID, that others refer to to indicate the absence of a
77
     * link.
78
     *
79
     * @deprecated use either {@link SQLRowAccessor#isForeignEmpty(String)} /
80
     *             {@link SQLRowValues#putEmptyLink(String)} or if you must
81
     *             {@link SQLTable#getUndefinedID()}
82
     */
83
    public static final int UNDEFINED_ID = 1;
84
    /**
85
     * No valid database rows should have an ID thats less than MIN_VALID_ID. But remember, you CAN
86
     * have a SQLRow with any ID.
87
     */
88
    public static final int MIN_VALID_ID = 0;
89
    /** Value representing no ID, no table can have a row with this ID. */
90
    public static final int NONEXISTANT_ID = MIN_VALID_ID - 1;
91
    /** <code>true</code> to print a stack trace when fetching missing values */
92
    public static final boolean printSTForMissingField = false;
93
 
94
    /**
95
     * Crée une ligne avec les valeurs du ResultSet.
96
     *
97
     * @param table la table de la ligne.
98
     * @param rs les valeurs.
99
     * @param onlyTable pass <code>true</code> if <code>rs</code> only contains columns from
100
     *        <code>table</code>, if unsure pass <code>false</code>. This allows to avoid calling
101
     *        {@link ResultSetMetaData#getTableName(int)} which is expensive on some systems.
102
     * @return la ligne correspondante.
103
     * @throws SQLException si problème lors de l'accès au ResultSet.
104
     * @see SQLRow#SQLRow(SQLTable, Map)
105
     * @deprecated use {@link SQLRowListRSH} or {@link SQLRowValuesListFetcher} instead or if you
106
     *             must use a {@link ResultSet} call
107
     *             {@link #createFromRS(SQLTable, ResultSet, ResultSetMetaData, boolean)} thus
108
     *             avoiding the potentially costly {@link ResultSet#getMetaData()}
109
     */
110
    public static final SQLRow createFromRS(SQLTable table, ResultSet rs, final boolean onlyTable) throws SQLException {
111
        return createFromRS(table, rs, rs.getMetaData(), onlyTable);
112
    }
113
 
114
    public static final SQLRow createFromRS(SQLTable table, ResultSet rs, final ResultSetMetaData rsmd, final boolean onlyTable) throws SQLException {
115
        return createFromRS(table, rs, getFieldNames(table, rsmd, onlyTable));
116
    }
117
 
118
    private static final List<String> getFieldNames(SQLTable table, final ResultSetMetaData rsmd, final boolean tableOnly) throws SQLException {
119
        final int colCount = rsmd.getColumnCount();
120
        final List<String> names = new ArrayList<String>(colCount);
121
        for (int i = 1; i <= colCount; i++) {
122
            // n'inclure que les colonnes de la table demandée
123
            // use a boolean since some systems (eg pg) require a request to the db to return the
124
            // table name
125
            if (tableOnly || rsmd.getTableName(i).equals(table.getName())) {
126
                names.add(rsmd.getColumnName(i));
127
            } else {
128
                names.add(null);
129
            }
130
        }
131
 
132
        return names;
133
    }
134
 
135
    // MAYBE create an opaque class holding names so that we can make this method, getFieldNames()
136
    // and createListFromRS() public
137
    static final SQLRow createFromRS(SQLTable table, ResultSet rs, final List<String> names) throws SQLException {
138
        final int indexCount = names.size();
139
 
140
        final Map<String, Object> m = new HashMap<String, Object>(indexCount);
141
        for (int i = 0; i < indexCount; i++) {
142
            final String colName = names.get(i);
143
            if (colName != null)
144
                m.put(colName, rs.getObject(i + 1));
145
        }
146
 
83 ilm 147
        final Number id = getID(m, table, true);
148
        // e.g. LEFT JOIN : missing values are null
149
        if (id == null)
150
            return null;
151
 
152
        // pass already found ID
153
        return new SQLRow(table, id, m);
17 ilm 154
    }
155
 
156
    /**
157
     * Create a list of rows using the metadata to find the columns' names.
158
     *
159
     * @param table the table of the rows.
160
     * @param rs the result set.
161
     * @param tableOnly <code>true</code> if <code>rs</code> only contains columns from
162
     *        <code>table</code>.
163
     * @return the data of the result set as SQLRows.
164
     * @throws SQLException if an error occurs while reading <code>rs</code>.
165
     */
166
    public static final List<SQLRow> createListFromRS(SQLTable table, ResultSet rs, final boolean tableOnly) throws SQLException {
167
        return createListFromRS(table, rs, getFieldNames(table, rs.getMetaData(), tableOnly));
168
    }
169
 
170
    /**
171
     * Create a list of rows without using the metadata.
172
     *
173
     * @param table the table of the rows.
174
     * @param rs the result set.
175
     * @param names the name of the field for each column, nulls are ignored, e.g. ["DESIGNATION",
176
     *        null, "ID"].
177
     * @return the data of the result set as SQLRows.
178
     * @throws SQLException if an error occurs while reading <code>rs</code>.
179
     */
180
    static final List<SQLRow> createListFromRS(SQLTable table, ResultSet rs, final List<String> names) throws SQLException {
181
        final List<SQLRow> res = new ArrayList<SQLRow>();
182
        while (rs.next()) {
83 ilm 183
            final SQLRow row = createFromRS(table, rs, names);
184
            if (row != null)
185
                res.add(row);
17 ilm 186
        }
187
        return res;
188
    }
189
 
142 ilm 190
    static final SQLRow createFromSelect(final SQLTable t, final VirtualFields vfs, final int id, final LockStrength l) {
191
        final SQLSelect sel = new SQLSelect(true).addAllSelect(t.getFields(vfs));
192
        sel.setLockStrength(l);
193
        sel.setWhere(new Where(t.getKey(), "=", id));
144 ilm 194
        @SuppressWarnings("unchecked")
195
        final Map<String, ?> map = (Map<String, ?>) t.getDBSystemRoot().getDataSource().execute(sel.asString(), new IResultSetHandler(SQLDataSource.MAP_HANDLER, l.equals(LockStrength.NONE)));
196
        return new SQLRow(t, id, map);
142 ilm 197
    }
198
 
199
    /**
200
     * Create an empty existing row (without checking the DB).
201
     *
202
     * @param t the table.
203
     * @param id the ID.
204
     * @return a new {@link #exists() existing} {@link #isFilled() filled} {@link #getFields()
205
     *         empty} row.
206
     */
207
    static final SQLRow createEmpty(final SQLTable t, final int id) {
208
        return new SQLRow(t, id, Collections.<String, Object> emptyMap());
209
    }
210
 
17 ilm 211
    private final int ID;
212
    private final Number idNumber;
213
    private Map<String, Object> values;
214
    private boolean fetched;
215
 
216
    private SQLRow(SQLTable table, Number id) {
217
        super(table);
218
        this.fetched = false;
219
        this.ID = id.intValue();
220
        this.idNumber = id;
221
        this.checkTable();
222
    }
223
 
224
    // public pour pouvoir créer une ligne n'exisant pas
225
    public SQLRow(SQLTable table, int ID) {
226
        // have to cast to Number, if you use Integer.valueOf() (or cast to Integer) the resulting
227
        // Integer is converted to Long
228
        this(table, table.getKey().getType().getJavaType() == Integer.class ? (Number) ID : Long.valueOf(ID));
229
    }
230
 
231
    private void checkTable() {
232
        if (!this.getTable().isRowable())
233
            throw new IllegalArgumentException(this.getTable() + " is not rowable");
234
    }
235
 
236
    /**
237
     * Crée une ligne avec les valeurs fournies. Evite une requête à la base.
238
     *
239
     * @param table la table.
240
     * @param values les valeurs de la lignes.
241
     * @throws IllegalArgumentException si values ne contient pas la clef de la table.
242
     */
19 ilm 243
    public SQLRow(SQLTable table, Map<String, ?> values) {
83 ilm 244
        this(table, null, values);
245
    }
246
 
247
    // allow to call getID() only once
248
    private SQLRow(SQLTable table, final Number id, Map<String, ?> values) {
249
        this(table, id == null ? getID(values, table, false) : id);
17 ilm 250
        // faire une copie, sinon backdoor pour changer les valeurs sans qu'on s'en aperçoive
142 ilm 251
        this.setValues(values == null ? null : new HashMap<String, Object>(values));
17 ilm 252
    }
253
 
83 ilm 254
    // return ID, must always be present but may be null if <code>nullAllowed</code>
255
    private static Number getID(Map<String, ?> values, final SQLTable table, final boolean nullAllowed) {
17 ilm 256
        final String keyName = table.getKey().getName();
83 ilm 257
        if (!values.containsKey(keyName))
17 ilm 258
            throw new IllegalArgumentException(values + " does not contain the key of " + table);
83 ilm 259
        final Object keyValue = values.get(keyName);
260
        if (keyValue instanceof Number) {
261
            return (Number) keyValue;
262
        } else if (nullAllowed && keyValue == null) {
263
            return null;
264
        } else {
265
            final String valS = keyValue == null ? "' is null" : "' isn't a Number : " + keyValue.getClass() + " " + keyValue;
266
            throw new IllegalArgumentException("The value of '" + keyName + valS);
267
        }
17 ilm 268
    }
269
 
132 ilm 270
    /**
271
     * Whether this contains values or just the {@link #getIDNumber() id}. NOTE that
272
     * {@link #getObject(String)} (and thus any other methods that call it) will access the DB if
273
     * the requested field is {@link #getFields() missing} even if this returns <code>true</code>.
274
     *
275
     * @return <code>true</code> if {@link #exists()} and {@link #getAbsolutelyAll()} and some other
276
     *         methods won't access the DB, <code>false</code> if any call to a method about values
277
     *         will access the DB.
278
     */
279
    public final boolean isFilled() {
280
        return this.fetched;
281
    }
282
 
151 ilm 283
    @Override
284
    protected void initValues() {
132 ilm 285
        if (!this.isFilled())
17 ilm 286
            this.fetchValues();
151 ilm 287
    }
288
 
289
    private Map<String, Object> getValues() {
290
        this.initValues();
17 ilm 291
        return this.values;
292
    }
293
 
294
    /**
295
     * Recharge les valeurs des champs depuis la base.
296
     */
144 ilm 297
    public final void fetchValues() {
17 ilm 298
        this.fetchValues(true);
299
    }
300
 
144 ilm 301
    public final SQLRow fetchValues(final boolean useCache) {
17 ilm 302
        return this.fetchValues(useCache, useCache);
303
    }
304
 
305
    @SuppressWarnings("unchecked")
306
    SQLRow fetchValues(final boolean readCache, final boolean writeCache) {
132 ilm 307
        final IResultSetHandler handler = new IResultSetHandler(SQLDataSource.MAP_HANDLER, readCache, writeCache) {
17 ilm 308
            @Override
309
            public Set<SQLRow> getCacheModifiers() {
310
                return Collections.singleton(SQLRow.this);
311
            }
312
        };
313
        this.setValues((Map<String, Object>) this.getTable().getBase().getDataSource().execute(this.getQuery(), handler, false));
314
        return this;
315
    }
316
 
317
    // attention ne vérifie pas que tous les champs soient présents
318
    private final void setValues(Map<String, Object> values) {
319
        this.values = values;
320
        if (!this.fetched)
321
            this.fetched = true;
322
    }
323
 
324
    /**
325
     * Retourne les noms des champs qui ont été chargé depuis la base.
326
     *
327
     * @return les noms des champs qui ont été chargé depuis la base.
328
     */
132 ilm 329
    @Override
17 ilm 330
    public Set<String> getFields() {
132 ilm 331
        return this.fetched ? Collections.unmodifiableSet(this.getValues().keySet()) : Collections.<String> emptySet();
17 ilm 332
    }
333
 
151 ilm 334
    // avoid Collections.unmodifiableSet() allocation
335
    @Override
336
    public boolean contains(String fieldName) {
337
        return this.fetched ? this.getValues().containsKey(fieldName) : false;
338
    }
339
 
17 ilm 340
    private String getQuery() {
341
        return "SELECT * FROM " + this.getTable().getSQLName().quote() + " WHERE " + this.getWhere().getClause();
342
    }
343
 
344
    public Where getWhere() {
345
        return new Where(this.getTable().getKey(), "=", this.getID());
346
    }
347
 
348
    /**
349
     * Est ce que cette ligne existe dans la base de donnée.
350
     *
351
     * @return <code>true</code> si la ligne existait lors de son instanciation.
352
     */
353
    public boolean exists() {
354
        return this.getValues() != null;
355
    }
356
 
357
    /**
358
     * Est ce que cette ligne existe et n'est pas archivée.
359
     *
360
     * @return <code>true</code> si cette ligne est valide.
361
     */
362
    public boolean isValid() {
363
        return this.exists() && this.getID() >= MIN_VALID_ID && !this.isArchived();
364
    }
365
 
366
    public boolean isData() {
367
        return this.isValid() && !this.isUndefined();
368
    }
369
 
370
    /**
371
     * Retourne le champ nommé <code>field</code> de cette ligne.
372
     *
373
     * @param field le nom du champ que l'on veut.
374
     * @return la valeur du champ sous forme d'objet Java, ou <code>null</code> si la valeur est
375
     *         NULL.
376
     * @throws IllegalStateException si cette ligne n'existe pas.
377
     * @throws IllegalArgumentException si cette ligne ne contient pas le champ demandé.
378
     */
132 ilm 379
    @Override
17 ilm 380
    public final Object getObject(String field) {
381
        if (!this.exists())
382
            throw new IllegalStateException("The row " + this + "does not exist.");
383
        if (!this.getTable().contains(field))
384
            throw new IllegalArgumentException("The table of the row " + this + " doesn't contain the field '" + field + "'.");
385
        // pour différencier entre la valeur est NULL (SQL) et la ligne ne contient pas ce champ
386
        if (!this.getValues().containsKey(field)) {
387
            // on ne l'a pas fetché
388
            this.fetchValues();
389
            // MAYBE mettre un boolean pour choisir si on accède à la base ou pas
390
            // since we just made a trip to the db we can afford to print at least a message
391
            final String msg = "The row " + this.simpleToString() + " doesn't contain the field '" + field + "' ; refetching.";
392
            Log.get().warning(msg);
393
            if (printSTForMissingField)
394
                new IllegalArgumentException(msg).printStackTrace();
395
        }
132 ilm 396
        assert this.getValues().containsKey(field);
17 ilm 397
        return this.getValues().get(field);
398
    }
399
 
73 ilm 400
    public final SQLRow getRow(boolean after) {
17 ilm 401
        final SQLTable t = this.getTable();
402
        final BigDecimal destOrder = this.getOrder();
403
        final int diff = (!after) ? -1 : 1;
404
 
73 ilm 405
        final SQLSelect sel = new SQLSelect();
19 ilm 406
        // undefined must not move
407
        sel.setExcludeUndefined(true);
17 ilm 408
        // unique index prend aussi en compte les archivés
409
        sel.setArchivedPolicy(SQLSelect.BOTH);
410
        sel.addSelect(t.getKey());
411
        sel.addSelect(t.getOrderField());
73 ilm 412
        if (t.isArchivable())
413
            sel.addSelect(t.getArchiveField());
17 ilm 414
        sel.setWhere(new Where(t.getOrderField(), diff < 0 ? "<" : ">", destOrder));
65 ilm 415
        sel.addFieldOrder(t.getOrderField(), diff < 0 ? Order.desc() : Order.asc());
19 ilm 416
        sel.setLimit(1);
17 ilm 417
 
418
        final SQLDataSource ds = t.getBase().getDataSource();
21 ilm 419
        @SuppressWarnings("unchecked")
19 ilm 420
        final Map<String, Object> otherMap = ds.execute1(sel.asString());
421
        if (otherMap != null) {
73 ilm 422
            return new SQLRow(t, otherMap);
423
        } else {
424
            return null;
425
        }
426
    }
427
 
428
    /**
429
     * The free order just after or before this row.
430
     *
431
     * @param after whether to look before or after this row.
432
     * @return a free order, or <code>null</code> if there's no room left.
433
     */
434
    public final BigDecimal getOrder(boolean after) {
435
        final BigDecimal destOrder = this.getOrder();
436
        final SQLRow otherRow = this.getRow(after);
437
        final BigDecimal otherOrder;
438
        if (otherRow != null) {
19 ilm 439
            otherOrder = otherRow.getOrder();
440
        } else if (after) {
17 ilm 441
            // dernière ligne de la table
19 ilm 442
            otherOrder = destOrder.add(ReOrder.DISTANCE);
17 ilm 443
        } else {
19 ilm 444
            // première ligne
445
            otherOrder = ReOrder.MIN_ORDER;
17 ilm 446
        }
447
 
19 ilm 448
        final int decDigits = this.getTable().getOrderDecimalDigits();
17 ilm 449
        final BigDecimal least = BigDecimal.ONE.scaleByPowerOfTen(-decDigits);
450
        final BigDecimal distance = destOrder.subtract(otherOrder).abs();
451
        if (distance.compareTo(least) <= 0)
452
            return null;
453
        else {
19 ilm 454
            final BigDecimal mean = destOrder.add(otherOrder).divide(BigDecimal.valueOf(2));
17 ilm 455
            return DecimalUtils.round(mean, decDigits);
456
        }
457
    }
458
 
25 ilm 459
    @Override
17 ilm 460
    public SQLRow getForeign(String fieldName) {
461
        return this.getForeignRow(fieldName);
462
    }
463
 
464
    /**
465
     * Retourne la ligne sur laquelle pointe le champ passé. Elle peut être archivé ou indéfinie.
466
     *
467
     * @param field le nom de la clef externe.
25 ilm 468
     * @return la ligne sur laquelle pointe le champ passé.
17 ilm 469
     * @throws IllegalArgumentException si <code>field</code> n'est pas une clef étrangère de la
470
     *         table de cette ligne.
471
     * @throws IllegalStateException si <code>field</code> contient l'ID d'une ligne inexistante.
472
     */
473
    public SQLRow getForeignRow(String field) {
474
        return this.getForeignRow(field, SQLRowMode.EXIST);
475
    }
476
 
477
    /**
478
     * Retourne la ligne sur laquelle pointe le champ passé.
479
     *
480
     * @param field le nom de la clef externe.
481
     * @param mode quel type de ligne retourner.
482
     * @return la ligne sur laquelle pointe le champ passé, ou <code>null</code> si elle ne
483
     *         correspond pas au mode.
484
     * @throws IllegalArgumentException si <code>field</code> n'est pas une clef étrangère de la
485
     *         table de cette ligne.
486
     * @throws IllegalStateException si <code>field</code> contient l'ID d'une ligne inexistante et
487
     *         que l'on n'en veut pas (mode.wantExisting() == <code>true</code>).
488
     */
489
    public SQLRow getForeignRow(String field, SQLRowMode mode) {
490
        final SQLField f = this.getTable().getField(field);
93 ilm 491
        final Link foreignLink = this.getTable().getDBSystemRoot().getGraph().getForeignLink(f);
492
        if (foreignLink == null)
17 ilm 493
            throw new IllegalArgumentException(field + " is not a foreign key of " + this.getTable());
93 ilm 494
        return this.getUncheckedForeignRow(foreignLink, mode);
17 ilm 495
    }
496
 
93 ilm 497
    public SQLRow getForeignRow(Link foreignLink, SQLRowMode mode) {
498
        if (!foreignLink.getSource().equals(this.getTable()))
499
            throw new IllegalArgumentException(foreignLink + " is not a foreign key of " + this.getTable());
500
        return this.getUncheckedForeignRow(foreignLink, mode);
501
    }
502
 
17 ilm 503
    private SQLRow getUncheckedForeignRow(Link foreignLink, SQLRowMode mode) {
504
        final SQLField field = foreignLink.getLabel();
505
        final SQLTable foreignTable = foreignLink.getTarget();
506
        if (this.getObject(field.getName()) == null) {
507
            return null;
508
        } else {
509
            final int foreignID = this.getInt(field.getName());
510
            final SQLRow foreignRow = new SQLRow(foreignTable, foreignID);
511
            // we used to check coherence here before all our dbs had real foreign keys
512
            return mode.filter(foreignRow);
513
        }
514
    }
515
 
516
    /**
517
     * Retourne l'ensemble des lignes de destTable liées à cette ligne.
518
     *
519
     * @param destTable la table dont on veut les lignes, eg "CPI_BT".
520
     * @return l'ensemble des lignes liées à cette ligne, eg les cpis de LOCAL[5822].
521
     * @see #getLinkedRows(String)
522
     */
523
    public Set<SQLRow> getLinkedRows(String destTable) {
524
        return this.getDistantRows(Collections.singletonList(destTable));
525
    }
526
 
527
    /**
528
     * Retourne l'ensemble des lignes de destTable qui sont pointées par celle-ci.
529
     *
530
     * @param destTable la table dont on veut les lignes, eg "OBSERVATION".
531
     * @return l'ensemble des lignes liées à cette ligne, eg les lignes pointées par
532
     *         "ID_OBSERVATION", "ID_OBSERVATION_2", etc.
533
     * @see #getLinkedRows(String)
534
     */
535
    public Set<SQLRow> getForeignRows(String destTable) {
536
        return this.getForeignRows(destTable, SQLRowMode.DATA);
537
    }
538
 
539
    public Set<SQLRow> getForeignRows(String destTable, SQLRowMode mode) {
540
        return new HashSet<SQLRow>(this.getForeignRowsMap(destTable, mode).values());
541
    }
542
 
543
    public Set<SQLRow> getForeignRows() {
544
        return this.getForeignRows(SQLRowMode.DATA);
545
    }
546
 
547
    public Set<SQLRow> getForeignRows(SQLRowMode mode) {
548
        return new HashSet<SQLRow>(this.getForeignRowsMap(mode).values());
549
    }
550
 
551
    /**
552
     * Retourne les lignes de destTable liées à cette ligne, indexées par les clefs externes.
553
     *
554
     * @param destTable la table dont on veut les lignes.
555
     * @return les lignes de destTable liées à cette ligne.
556
     */
557
    public Map<SQLField, SQLRow> getForeignRowsMap(String destTable) {
558
        return this.getForeignRowsMap(destTable, SQLRowMode.DATA);
559
    }
560
 
561
    public Map<SQLField, SQLRow> getForeignRowsMap(String destTable, SQLRowMode mode) {
562
        final Set<Link> links = this.getTable().getDBSystemRoot().getGraph().getForeignLinks(this.getTable(), this.getTable().getTable(destTable));
563
        return this.foreignLinksToMap(links, mode);
564
    }
565
 
566
    public Map<SQLField, SQLRow> getForeignRowsMap() {
567
        return this.getForeignRowsMap(SQLRowMode.DATA);
568
    }
569
 
570
    public Map<SQLField, SQLRow> getForeignRowsMap(SQLRowMode mode) {
132 ilm 571
        return this.foreignLinksToMap(this.getTable().getForeignLinks(), mode);
17 ilm 572
    }
573
 
574
    private Map<SQLField, SQLRow> foreignLinksToMap(Collection<Link> links, SQLRowMode mode) {
575
        final Map<SQLField, SQLRow> res = new HashMap<SQLField, SQLRow>();
576
        for (final Link l : links) {
577
            final SQLRow fr = this.getUncheckedForeignRow(l, mode);
578
            if (fr != null)
579
                res.put(l.getLabel(), fr);
580
        }
581
        return res;
582
    }
583
 
584
    /**
585
     * Fait la jointure entre cette ligne et les tables passées.
586
     *
587
     * @param path le chemin de la jointure.
588
     * @return la ligne correspondante.
589
     * @throws IllegalArgumentException si le path est mauvais.
590
     * @throws IllegalStateException si le path ne méne pas à une ligne unique.
591
     * @see #getDistantRows(List)
592
     */
593
    public SQLRow getDistantRow(List<String> path) {
80 ilm 594
        return this.getDistantRow(Path.get(this.getTable()).addTables(path));
65 ilm 595
    }
596
 
597
    public SQLRow getDistantRow(final Path path) {
19 ilm 598
        final Set<SQLRow> rows = this.getDistantRows(path);
17 ilm 599
        if (rows.size() != 1)
600
            throw new IllegalStateException("the path " + path + " does not lead to a unique row (" + rows.size() + ")");
65 ilm 601
        return rows.iterator().next();
17 ilm 602
    }
603
 
604
    /**
605
     * Fait la jointure entre cette ligne et les tables passées.
606
     *
607
     * @param path le chemin de la jointure.
608
     * @return un ensemble de lignes de la dernière table du chemin, dans l'ordre.
609
     * @throws IllegalArgumentException si le path est mauvais.
610
     */
611
    public Set<SQLRow> getDistantRows(List<String> path) {
80 ilm 612
        return this.getDistantRows(Path.get(this.getTable()).addTables(path));
65 ilm 613
    }
614
 
615
    public Set<SQLRow> getDistantRows(final Path path) {
132 ilm 616
        return this.getDistantRows(path, ArchiveMode.UNARCHIVED);
617
    }
618
 
619
    public Set<SQLRow> getDistantRows(final Path path, final ArchiveMode archiveMode) {
144 ilm 620
        return getDistantRows(path, archiveMode, true);
621
    }
622
 
623
    public Set<SQLRow> getDistantRows(final Path path, final ArchiveMode archiveMode, final boolean orderLast) {
624
        return (Set<SQLRow>) getDistantRows(path, archiveMode, orderLast, false);
625
    }
626
 
627
    public List<SQLRow> getDistantRowsList(final Path path, final ArchiveMode archiveMode) {
628
        // this method can return the same row multiple times, so don't use its order or the
629
        // duplicated rows will always be grouped together.
630
        return getDistantRowsList(path, archiveMode, false);
631
    }
632
 
633
    public List<SQLRow> getDistantRowsList(final Path path, final ArchiveMode archiveMode, final boolean orderLast) {
634
        return (List<SQLRow>) getDistantRows(path, archiveMode, orderLast, true);
635
    }
636
 
637
    private Collection<SQLRow> getDistantRows(final Path path, final ArchiveMode archiveMode, final boolean orderLast, final boolean list) {
638
        if (path.length() == 0) {
639
            if (SQLRowMode.check(archiveMode, this))
640
                return list ? Collections.singletonList(this) : Collections.singleton(this);
641
            else
642
                return list ? Collections.<SQLRow> emptyList() : Collections.<SQLRow> emptySet();
643
        } else {
644
            // on veut tous les champs de la derniere table et rien d'autre
645
            final List<List<String>> fields = new ArrayList<List<String>>(Collections.nCopies(path.length() - 1, Collections.<String> emptyList()));
646
            fields.add(null);
647
            final List<List<SQLRow>> s = this.getRowsOnPath(path, fields, archiveMode, orderLast);
648
            final List<SQLRow> resList = list ? new ArrayList<SQLRow>(s.size()) : null;
649
            final Set<SQLRow> resSet = list ? null : new LinkedHashSet<SQLRow>(s.size());
650
            final Collection<SQLRow> res = list ? resList : resSet;
651
            assert res != null;
652
            for (final List<SQLRow> l : s) {
653
                assert l.size() == 1 : "Too many rows were created : " + l;
654
                res.add(l.get(0));
655
            }
656
            return list ? Collections.unmodifiableList(resList) : Collections.unmodifiableSet(resSet);
17 ilm 657
        }
658
    }
659
 
660
    /**
661
     * Retourne les lignes distantes, plus les lignes intermédiaire du chemin. Par exemple
662
     * SITE[128].getRowsOnPath("BATIMENT,LOCAL", [null, "DESIGNATION"]) retourne tous les locaux du
663
     * site (seul DESIGNATION est chargé) avec tous les champs de leurs bâtiments.
664
     *
65 ilm 665
     * @param path le chemin dans le graphe de la base, see {@link Path#addTables(List)}.
17 ilm 666
     * @param fields un liste de des champs, chaque élément est :
667
     *        <ul>
668
     *        <li><code>null</code> pour tous les champs</li>
21 ilm 669
     *        <li>une Collection de nom de champs, e.g. ["DESIGNATION","NUMERO"]</li>
17 ilm 670
     *        </ul>
144 ilm 671
     * @return a list with one item per distant row, and each item has all the rows on the passed
672
     *         path.
17 ilm 673
     */
144 ilm 674
    public List<List<SQLRow>> getRowsOnPath(final List<String> path, final List<? extends Collection<String>> fields) {
80 ilm 675
        return this.getRowsOnPath(Path.get(this.getTable()).addTables(path), fields);
65 ilm 676
    }
677
 
144 ilm 678
    public List<List<SQLRow>> getRowsOnPath(final Path p, final List<? extends Collection<String>> fields) {
132 ilm 679
        return this.getRowsOnPath(p, fields, ArchiveMode.UNARCHIVED);
680
    }
681
 
144 ilm 682
    public List<List<SQLRow>> getRowsOnPath(final Path p, final List<? extends Collection<String>> fields, final ArchiveMode archiveMode) {
683
        return this.getRowsOnPath(p, fields, archiveMode, true);
684
    }
685
 
686
    // returns a List since the same row might be linked several times to another
687
    public List<List<SQLRow>> getRowsOnPath(final Path p, final List<? extends Collection<String>> fields, final ArchiveMode archiveMode, final boolean orderLast) {
65 ilm 688
        final int pathSize = p.length();
17 ilm 689
        if (pathSize == 0)
690
            throw new IllegalArgumentException("path is empty");
691
        if (pathSize != fields.size())
692
            throw new IllegalArgumentException("path and fields size mismatch : " + pathSize + " != " + fields.size());
65 ilm 693
        if (p.getFirst() != this.getTable())
694
            throw new IllegalArgumentException("path doesn't start with us : " + p.getFirst() + " != " + this.getTable());
144 ilm 695
        final List<List<SQLRow>> res = new ArrayList<List<SQLRow>>();
17 ilm 696
 
65 ilm 697
        final DBSystemRoot sysRoot = this.getTable().getDBSystemRoot();
698
        Where where = sysRoot.getGraph().getJointure(p);
17 ilm 699
        // ne pas oublier de sélectionner notre ligne
700
        where = where.and(this.getWhere());
701
 
80 ilm 702
        final SQLSelect select = new SQLSelect();
132 ilm 703
        select.setArchivedPolicy(archiveMode);
17 ilm 704
 
21 ilm 705
        final List<Collection<String>> fieldsCols = new ArrayList<Collection<String>>(pathSize);
17 ilm 706
        for (int i = 0; i < pathSize; i++) {
21 ilm 707
            final Collection<String> tableFields = fields.get(i);
17 ilm 708
            // +1 car p contient cette ligne
709
            final SQLTable t = p.getTable(i + 1);
21 ilm 710
            final Collection<String> fieldsCol;
711
            if (tableFields == null) {
712
                fieldsCol = t.getFieldsName();
17 ilm 713
            } else {
21 ilm 714
                fieldsCol = tableFields;
17 ilm 715
            }
716
            fieldsCols.add(fieldsCol);
717
 
718
            // les tables qui ne nous interessent pas
719
            if (fieldsCol.size() > 0) {
720
                // toujours mettre l'ID
721
                select.addSelect(t.getKey());
722
                // plus les champs demandés
21 ilm 723
                select.addAllSelect(t, fieldsCol);
17 ilm 724
            }
144 ilm 725
            if (!orderLast) {
726
                select.addOrder(t);
727
            }
17 ilm 728
        }
729
        // dans tous les cas mettre l'ID de la dernière table
730
        final SQLTable lastTable = p.getLast();
731
        select.addSelect(lastTable.getKey());
732
 
93 ilm 733
        select.setWhere(where);
144 ilm 734
        if (orderLast) {
735
            // determinist order even if there's no order field or invalid values in it
736
            select.addOrderSilent(lastTable.getName());
737
            select.addFieldOrder(lastTable.getKey());
738
        }
93 ilm 739
 
17 ilm 740
        // on ajoute une SQLRow pour chaque ID trouvé
65 ilm 741
        sysRoot.getDataSource().execute(select.asString(), new ResultSetHandler() {
17 ilm 742
 
743
            public Object handle(ResultSet rs) throws SQLException {
744
                final ResultSetMetaData rsmd = rs.getMetaData();
745
                while (rs.next()) {
746
                    final List<SQLRow> rows = new ArrayList<SQLRow>(pathSize);
747
                    for (int i = 0; i < pathSize; i++) {
748
                        // les tables qui ne nous interessent pas
749
                        if (fieldsCols.get(i).size() > 0) {
750
                            // +1 car p contient cette ligne
751
                            final SQLTable t = p.getTable(i + 1);
752
                            rows.add(SQLRow.createFromRS(t, rs, rsmd, pathSize == 1));
753
                        }
754
                    }
755
                    res.add(rows);
756
                }
757
                return null;
758
            }
759
        });
760
 
761
        return res;
762
    }
763
 
764
    /**
765
     * Retourne les lignes pointant sur celle ci.
766
     *
767
     * @return les lignes pointant sur celle ci.
768
     */
769
    public final List<SQLRow> getReferentRows() {
770
        return this.getReferentRows((Set<SQLTable>) null);
771
    }
772
 
773
    @Override
774
    public final List<SQLRow> getReferentRows(SQLTable refTable) {
775
        return this.getReferentRows(Collections.singleton(refTable));
776
    }
777
 
778
    /**
779
     * Retourne les lignes des tables spécifiées pointant sur celle ci.
780
     *
781
     * @param tables les tables voulues, <code>null</code> pour toutes.
782
     * @return les SQLRow pointant sur celle ci.
783
     */
784
    public final List<SQLRow> getReferentRows(Set<SQLTable> tables) {
785
        return this.getReferentRows(tables, SQLSelect.UNARCHIVED);
786
    }
787
 
93 ilm 788
    private final SetMap<SQLTable, Link> getReferentLinks(Set<SQLTable> tables) {
789
        final Set<Link> links = this.getTable().getBase().getGraph().getReferentLinks(this.getTable());
790
        final SetMap<SQLTable, Link> byTable = new SetMap<SQLTable, Link>();
791
        for (final Link l : links) {
792
            final SQLTable src = l.getSource();
793
            if (tables == null || tables != null && tables.contains(src)) {
794
                byTable.add(src, l);
795
            }
796
        }
797
        return byTable;
798
    }
799
 
17 ilm 800
    /**
801
     * Returns the rows of tables that points to this row.
802
     *
803
     * @param tables a Set of tables, or <code>null</code> for all of them.
804
     * @param archived <code>SQLSelect.UNARCHIVED</code>, <code>SQLSelect.ARCHIVED</code> or
805
     *        <code>SQLSelect.BOTH</code>.
806
     * @return a List of SQLRow that points to this.
807
     */
808
    public final List<SQLRow> getReferentRows(Set<SQLTable> tables, ArchiveMode archived) {
93 ilm 809
        final SetMap<SQLTable, Link> byTable = getReferentLinks(tables);
810
        final Set<SQLRow> res = new LinkedHashSet<SQLRow>();
811
        for (final Entry<SQLTable, Set<Link>> e : byTable.entrySet()) {
812
            res.addAll(this.getReferentRows(e.getValue(), archived, null));
813
        }
814
        return new ArrayList<SQLRow>(res);
17 ilm 815
    }
816
 
83 ilm 817
    public final ListMap<Link, SQLRow> getReferentRowsByLink() {
17 ilm 818
        return this.getReferentRowsByLink(null);
819
    }
820
 
83 ilm 821
    public final ListMap<Link, SQLRow> getReferentRowsByLink(Set<SQLTable> tables) {
17 ilm 822
        return this.getReferentRowsByLink(tables, SQLSelect.UNARCHIVED);
823
    }
824
 
83 ilm 825
    public final ListMap<Link, SQLRow> getReferentRowsByLink(Set<SQLTable> tables, ArchiveMode archived) {
826
        // List since getReferentRows() is ordered
827
        final ListMap<Link, SQLRow> res = new ListMap<Link, SQLRow>();
93 ilm 828
        final SetMap<SQLTable, Link> byTable = getReferentLinks(tables);
829
        for (final Entry<SQLTable, Set<Link>> e : byTable.entrySet()) {
830
            final Set<Link> links = e.getValue();
831
            final List<SQLRow> rows = this.getReferentRows(links, archived, null);
832
            for (final Link l : links) {
833
                // put all referent links, even if there's no referent row
834
                res.put(l, Collections.<SQLRow> emptyList());
835
                for (final SQLRow r : rows) {
836
                    if (r.getForeignID(l.getLabel().getName()) == this.getID())
837
                        res.add(l, r);
838
                }
17 ilm 839
            }
840
        }
841
        return res;
842
    }
843
 
844
    /**
845
     * Returns the rows that points to this row by the refField.
846
     *
847
     * @param refField a SQLField that points to the table of this row, eg BATIMENT.ID_SITE.
848
     * @return a List of SQLRow that points to this, eg [BATIMENT[123], BATIMENT[124]].
849
     */
850
    public List<SQLRow> getReferentRows(final SQLField refField) {
851
        return this.getReferentRows(refField, SQLSelect.UNARCHIVED);
852
    }
853
 
854
    public List<SQLRow> getReferentRows(final SQLField refField, final ArchiveMode archived) {
855
        return this.getReferentRows(refField, archived, null);
856
    }
857
 
858
    /**
859
     * Returns the rows that points to this row by <code>refField</code>.
860
     *
861
     * @param refField a SQLField that points to the table of this row, eg BATIMENT.ID_SITE.
862
     * @param archived specify which rows should be returned.
863
     * @param fields the list of fields the rows will have, <code>null</code> meaning all.
864
     * @return a List of SQLRow that points to this, eg [BATIMENT[123], BATIMENT[124]].
865
     */
866
    public List<SQLRow> getReferentRows(final SQLField refField, final ArchiveMode archived, final Collection<String> fields) {
93 ilm 867
        return getReferentRows(Collections.singleton(refField.getTable().getDBSystemRoot().getGraph().getForeignLink(refField)), archived, fields);
868
    }
869
 
870
    // fetch all rows from the same table at once : less requests than one per link and thus rows
871
    // are ordered across links.
872
    private List<SQLRow> getReferentRows(final Set<Link> links, final ArchiveMode archived, final Collection<String> fields) {
873
        if (links.isEmpty())
874
            return Collections.emptyList();
875
 
876
        SQLTable src = null;
877
        Where w = null;
878
        for (final Link l : links) {
879
            if (src == null) {
880
                src = l.getSource();
881
            } else if (!l.getSource().equals(src)) {
882
                throw new IllegalArgumentException(l + " doesn't come from " + src);
883
            }
884
            if (!l.getTarget().equals(this.getTable())) {
885
                throw new IllegalArgumentException(l + " doesn't point to " + this.getTable());
886
            }
887
            w = new Where(l.getLabel(), "=", this.getID()).or(w);
17 ilm 888
        }
889
 
80 ilm 890
        final SQLSelect sel = new SQLSelect();
93 ilm 891
        if (fields == null) {
17 ilm 892
            sel.addSelectStar(src);
93 ilm 893
        } else {
17 ilm 894
            sel.addSelect(src.getKey());
895
            for (final String f : fields)
896
                sel.addSelect(src.getField(f));
897
        }
93 ilm 898
        sel.setWhere(w);
17 ilm 899
        sel.setArchivedPolicy(archived);
900
        sel.addOrderSilent(src.getName());
901
        // - if some other criteria need to be applied, we could pass an SQLRowMode (instead of
902
        // just ArchiveMode) and modify the SQLSelect accordingly
903
 
21 ilm 904
        return SQLRowListRSH.execute(sel);
17 ilm 905
    }
906
 
907
    /**
908
     * Toutes les lignes qui touchent cette lignes. C'est à dire les lignes pointées par les clefs
909
     * externes plus lignes qui pointent sur cette ligne.
910
     *
911
     * @return les lignes qui touchent cette lignes.
912
     */
913
    private Set<SQLRow> getConnectedRows() {
914
        Set<SQLRow> res = new HashSet<SQLRow>();
915
        res.addAll(this.getReferentRows((Set<SQLTable>) null, SQLSelect.BOTH));
916
        res.addAll(this.getForeignRows(SQLRowMode.EXIST));
917
        return res;
918
    }
919
 
67 ilm 920
    @Override
921
    public Collection<SQLRow> followLink(Link l, Direction direction) {
922
        // Path checks that one end of l is this table and that direction is valid (e.g. not ANY for
923
        // self-reference links)
80 ilm 924
        final boolean backwards = Path.get(getTable()).add(l, direction).isBackwards(0);
67 ilm 925
        if (backwards)
926
            return getReferentRows(l.getSingleField());
927
        else
928
            return Collections.singletonList(getForeign(l.getSingleField().getName()));
929
    }
930
 
17 ilm 931
    /**
932
     * Trouve les lignes archivées reliées à celle ci par moins de maxLength liens.
933
     *
934
     * @param maxLength la longeur maximale du chemin entre les lignes retournées et celle ci.
935
     * @return les lignes archivées reliées à celle ci.
936
     */
937
    public Set<SQLRow> findDistantArchived(int maxLength) {
938
        return this.findDistantArchived(maxLength, new HashSet<SQLRow>(), 0);
939
    }
940
 
941
    private Set<SQLRow> findDistantArchived(final int maxLength, final Set<SQLRow> been, int length) {
942
        final Set<SQLRow> res = new HashSet<SQLRow>();
943
 
944
        if (maxLength == length)
945
            return res;
946
 
947
        // on avance d'un cran
948
        been.add(this);
949
        length++;
950
 
951
        // on garde les lignes à appeler récursivement pour la fin
952
        // car on veut parcourir en largeur d'abord
953
        final Set<SQLRow> rec = new HashSet<SQLRow>();
954
        Iterator<SQLRow> iter = this.getConnectedRows().iterator();
955
        while (iter.hasNext()) {
956
            final SQLRow row = iter.next();
957
            if (!been.contains(row)) {
958
                if (row.isArchived()) {
959
                    res.add(row);
960
                } else {
961
                    rec.add(row);
962
                }
963
            }
964
        }
965
        iter = rec.iterator();
966
        while (iter.hasNext()) {
967
            final SQLRow row = iter.next();
968
            res.addAll(row.findDistantArchived(maxLength, been, length));
969
        }
970
        return res;
971
    }
972
 
132 ilm 973
    @Override
17 ilm 974
    public String toString() {
132 ilm 975
        return fullToString(false);
976
    }
977
 
978
    public String fullToString(final boolean allowDBAccess) {
17 ilm 979
        String res = this.simpleToString();
132 ilm 980
        final Boolean exists = allowDBAccess || this.isFilled() ? this.exists() : null;
981
        if (exists == null) {
17 ilm 982
            res = "?" + res + "?";
132 ilm 983
        } else if (!exists) {
984
            res = "-" + res + "-";
985
        } else {
986
            // the row exists
987
 
988
            Boolean archived = null;
989
            try {
990
                archived = this.isArchived(allowDBAccess);
991
            } catch (Exception e) {
992
                Log.get().log(Level.FINER, "Couldn't determine archive status", e);
993
                assert archived == null;
994
            }
995
            if (archived == null) {
996
                res = "?" + res + "?";
997
            } else if (archived) {
998
                res = "(" + res + ")";
999
            }
17 ilm 1000
        }
1001
        return res;
1002
    }
1003
 
1004
    public String simpleToString() {
1005
        return this.getTable().getName() + "[" + this.ID + "]";
1006
    }
1007
 
151 ilm 1008
    @Override
1009
    public String mapToString() {
1010
        final String result = this.fullToString(false) + " : ";
1011
        return result + (this.values != null ? this.values : (this.isFilled() ? "not in DB" : "not filled"));
1012
    }
1013
 
17 ilm 1014
    /**
1015
     * Renvoie tous les champs de cette ligne, clef comprises. En général on ne veut pas les valeurs
1016
     * des clefs, voir getAllValues().
1017
     * <p>
1018
     * Les valeurs de cette map sont les valeurs retournées par getObject().
1019
     * </p>
1020
     *
1021
     * @return tous les champs de cette ligne.
1022
     * @see #getAllValues()
1023
     * @see #getObject(String)
1024
     */
1025
    @Override
1026
    public Map<String, Object> getAbsolutelyAll() {
1027
        return Collections.unmodifiableMap(this.getValues());
1028
    }
1029
 
132 ilm 1030
    private static final VirtualFields ALL_VALUES_FIELDS = VirtualFields.ALL.difference(VirtualFields.KEYS, VirtualFields.ARCHIVE, VirtualFields.ORDER);
1031
 
17 ilm 1032
    /**
1033
     * Retourne toutes les valeurs de cette lignes, sans les clefs ni les champs d'ordre et
1034
     * d'archive.
1035
     *
1036
     * @return toutes les valeurs de cette lignes.
1037
     * @see #getAbsolutelyAll()
1038
     */
1039
    public Map<String, Object> getAllValues() {
132 ilm 1040
        return this.getValues(ALL_VALUES_FIELDS);
17 ilm 1041
    }
1042
 
1043
    /**
1044
     * Creates a SQLRowValues with absolutely all the values of this row. ATTN the values are as
1045
     * always the ones at the moment of the last fetching.
1046
     *
1047
     * <pre>
1048
     * SQLRow r = table.getRow(123); // [a=&gt;'26', b=&gt; '25']
1049
     * r.createUpdateRow().put(&quot;a&quot;, 1).update();
1050
     * r.createUpdateRow().put(&quot;b&quot;, 2).update();
1051
     * </pre>
1052
     *
1053
     * You could think that r now equals [a=>1, b=>2]. No, actually it's [a=>'26', b=>2], because
1054
     * the second line overwrote the first one. The best solution is to use only one SQLRowValues
1055
     * (hence only one access to the DB), otherwise use createEmptyUpdateRow().
1056
     *
1057
     * @see #createEmptyUpdateRow()
1058
     * @return a SQLRowValues on this SQLRow.
1059
     */
1060
    public SQLRowValues createUpdateRow() {
83 ilm 1061
        return new SQLRowValues(this.getTable(), this.getValues());
17 ilm 1062
    }
1063
 
1064
    /**
1065
     * Gets the unique (among this table at least) identifier of this row.
1066
     *
1067
     * @return an int greater than {@link #MIN_VALID_ID} if this is valid.
1068
     */
1069
    @Override
1070
    public int getID() {
1071
        return this.ID;
1072
    }
1073
 
1074
    @Override
1075
    public Number getIDNumber() {
1076
        return this.idNumber;
1077
    }
1078
 
1079
    @Override
1080
    public SQLRow asRow() {
1081
        return this;
1082
    }
1083
 
1084
    @Override
1085
    public final SQLRowValues asRowValues() {
1086
        return this.createUpdateRow();
1087
    }
1088
 
1089
    /**
1090
     * Note : ne compare pas les valeurs des champs de cette ligne.
1091
     *
1092
     * @see java.lang.Object#equals(java.lang.Object)
1093
     */
1094
    public boolean equals(Object other) {
1095
        if (!(other instanceof SQLRow))
1096
            return false;
1097
        SQLRow o = (SQLRow) other;
1098
        return this.equalsAsRow(o);
1099
    }
1100
 
1101
    public int hashCode() {
1102
        return this.hashCodeAsRow();
1103
    }
1104
 
1105
    /**
1106
     * Transforme un chemin en une liste de nom de table. Si path est "" alors retourne une liste
1107
     * vide.
1108
     *
1109
     * @param path le chemin, eg "BATIMENT,LOCAL".
1110
     * @return une liste de String, eg ["BATIMENT","LOCAL"].
1111
     */
1112
    static public List<String> toList(String path) {
1113
        return Arrays.asList(toArray(path));
1114
    }
1115
 
1116
    static private String[] toArray(String path) {
1117
        if (path.length() == 0)
1118
            return new String[0];
1119
        else
1120
            // ATTN ',' : no spaces
1121
            return path.split(",");
1122
    }
1123
 
25 ilm 1124
    @Override
1125
    public SQLTableModifiedListener createTableListener(SQLDataListener l) {
17 ilm 1126
        return new SQLTableListenerData<SQLRow>(this, l);
1127
    }
1128
 
1129
}