OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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