OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 151 | Rev 174 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 151 Rev 156
1
/*
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
3
 * 
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 * 
5
 * 
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
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
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
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.
9
 * language governing permissions and limitations under the License.
10
 * 
10
 * 
11
 * When distributing the software, include this License Header Notice in each file.
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
12
 */
13
 
13
 
14
 package org.openconcerto.sql.model;
14
 package org.openconcerto.sql.model;
15
 
15
 
16
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
16
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
17
import org.openconcerto.sql.model.SQLRowValuesCluster.ValueChangeListener;
17
import org.openconcerto.sql.model.SQLRowValuesCluster.ValueChangeListener;
18
import org.openconcerto.sql.model.SQLTable.FieldGroup;
18
import org.openconcerto.sql.model.SQLTable.FieldGroup;
19
import org.openconcerto.sql.model.SQLTableEvent.Mode;
19
import org.openconcerto.sql.model.SQLTableEvent.Mode;
20
import org.openconcerto.sql.model.graph.Link;
20
import org.openconcerto.sql.model.graph.Link;
21
import org.openconcerto.sql.model.graph.Link.Direction;
21
import org.openconcerto.sql.model.graph.Link.Direction;
22
import org.openconcerto.sql.model.graph.Path;
22
import org.openconcerto.sql.model.graph.Path;
23
import org.openconcerto.sql.model.graph.SQLKey.Type;
23
import org.openconcerto.sql.model.graph.SQLKey.Type;
24
import org.openconcerto.sql.model.graph.Step;
24
import org.openconcerto.sql.model.graph.Step;
25
import org.openconcerto.sql.request.Inserter;
25
import org.openconcerto.sql.request.Inserter;
26
import org.openconcerto.sql.request.Inserter.Insertion;
26
import org.openconcerto.sql.request.Inserter.Insertion;
27
import org.openconcerto.sql.request.Inserter.ReturnMode;
27
import org.openconcerto.sql.request.Inserter.ReturnMode;
28
import org.openconcerto.sql.users.UserManager;
28
import org.openconcerto.sql.users.UserManager;
29
import org.openconcerto.sql.utils.ReOrder;
29
import org.openconcerto.sql.utils.ReOrder;
30
import org.openconcerto.utils.CollectionMap2Itf.SetMapItf;
30
import org.openconcerto.utils.CollectionMap2Itf.SetMapItf;
31
import org.openconcerto.utils.CollectionUtils;
31
import org.openconcerto.utils.CollectionUtils;
32
import org.openconcerto.utils.CompareUtils;
32
import org.openconcerto.utils.CompareUtils;
33
import org.openconcerto.utils.CopyUtils;
33
import org.openconcerto.utils.CopyUtils;
34
import org.openconcerto.utils.ExceptionUtils;
34
import org.openconcerto.utils.ExceptionUtils;
35
import org.openconcerto.utils.ListMap;
35
import org.openconcerto.utils.ListMap;
36
import org.openconcerto.utils.RecursionType;
36
import org.openconcerto.utils.RecursionType;
37
import org.openconcerto.utils.SetMap;
37
import org.openconcerto.utils.SetMap;
38
import org.openconcerto.utils.Tuple2;
38
import org.openconcerto.utils.Tuple2;
39
import org.openconcerto.utils.cc.IClosure;
39
import org.openconcerto.utils.cc.IClosure;
40
import org.openconcerto.utils.cc.ITransformer;
40
import org.openconcerto.utils.cc.ITransformer;
41
import org.openconcerto.utils.cc.IdentitySet;
41
import org.openconcerto.utils.cc.IdentitySet;
42
import org.openconcerto.utils.cc.LinkedIdentitySet;
42
import org.openconcerto.utils.cc.LinkedIdentitySet;
43
import org.openconcerto.utils.cc.TransformedMap;
43
import org.openconcerto.utils.cc.TransformedMap;
44
import org.openconcerto.utils.convertor.NumberConvertor;
44
import org.openconcerto.utils.convertor.NumberConvertor;
45
import org.openconcerto.utils.convertor.ValueConvertor;
45
import org.openconcerto.utils.convertor.ValueConvertor;
46
import org.openconcerto.utils.convertor.ValueConvertorFactory;
46
import org.openconcerto.utils.convertor.ValueConvertorFactory;
47
 
47
 
48
import java.math.BigDecimal;
48
import java.math.BigDecimal;
49
import java.sql.Connection;
49
import java.sql.Connection;
50
import java.sql.PreparedStatement;
50
import java.sql.PreparedStatement;
51
import java.sql.ResultSet;
51
import java.sql.ResultSet;
52
import java.sql.SQLException;
52
import java.sql.SQLException;
53
import java.sql.Statement;
53
import java.sql.Statement;
54
import java.sql.Timestamp;
54
import java.sql.Timestamp;
55
import java.util.ArrayList;
55
import java.util.ArrayList;
56
import java.util.Arrays;
56
import java.util.Arrays;
57
import java.util.Collection;
57
import java.util.Collection;
58
import java.util.Collections;
58
import java.util.Collections;
59
import java.util.EventListener;
59
import java.util.EventListener;
60
import java.util.EventObject;
60
import java.util.EventObject;
61
import java.util.HashMap;
61
import java.util.HashMap;
62
import java.util.HashSet;
62
import java.util.HashSet;
63
import java.util.Iterator;
63
import java.util.Iterator;
64
import java.util.LinkedHashMap;
64
import java.util.LinkedHashMap;
65
import java.util.LinkedHashSet;
65
import java.util.LinkedHashSet;
66
import java.util.List;
66
import java.util.List;
67
import java.util.ListIterator;
67
import java.util.ListIterator;
68
import java.util.Map;
68
import java.util.Map;
69
import java.util.Map.Entry;
69
import java.util.Map.Entry;
70
import java.util.Set;
70
import java.util.Set;
71
 
71
 
72
import net.jcip.annotations.GuardedBy;
72
import net.jcip.annotations.GuardedBy;
73
 
73
 
74
/**
74
/**
75
 * A class that represent a row of a table that can be modified before being inserted or updated.
75
 * A class that represent a row of a table that can be modified before being inserted or updated.
76
 * The row might not actually exists in the database, and it might not define all the fields. One
76
 * The row might not actually exists in the database, and it might not define all the fields. One
77
 * can put SQLRowValues as a foreign key value, so that it will be inserted as well.
77
 * can put SQLRowValues as a foreign key value, so that it will be inserted as well.
78
 * 
78
 * 
79
 * @author Sylvain CUAZ
79
 * @author Sylvain CUAZ
80
 * @see #load(SQLRowAccessor, Set)
80
 * @see #load(SQLRowAccessor, Set)
81
 * @see #put(String, Object)
81
 * @see #put(String, Object)
82
 * @see #insert()
82
 * @see #insert()
83
 * @see #update(int)
83
 * @see #update(int)
84
 */
84
 */
85
public final class SQLRowValues extends SQLRowAccessor {
85
public final class SQLRowValues extends SQLRowAccessor {
86
 
86
 
87
    public static enum ForeignCopyMode {
87
    public static enum ForeignCopyMode {
88
        /**
88
        /**
89
         * Copy no SQLRowValues.
89
         * Copy no SQLRowValues.
90
         */
90
         */
91
        NO_COPY,
91
        NO_COPY,
92
        /**
92
        /**
93
         * Put <code>null</code> instead of the SQLRowValues. This keeps all fields.
93
         * Put <code>null</code> instead of the SQLRowValues. This keeps all fields.
94
         */
94
         */
95
        COPY_NULL,
95
        COPY_NULL,
96
        /**
96
        /**
97
         * Copy the id of SQLRowValues if any, otherwise don't copy anything. This keeps the maximum
97
         * Copy the id of SQLRowValues if any, otherwise don't copy anything. This keeps the maximum
98
         * of information without any foreign rowValues.
98
         * of information without any foreign rowValues.
99
         */
99
         */
100
        COPY_ID_OR_RM,
100
        COPY_ID_OR_RM,
101
        /**
101
        /**
102
         * Copy the id of SQLRowValues if any, otherwise copy the row. This keeps all the
102
         * Copy the id of SQLRowValues if any, otherwise copy the row. This keeps all the
103
         * information.
103
         * information.
104
         */
104
         */
105
        COPY_ID_OR_ROW,
105
        COPY_ID_OR_ROW,
106
        /**
106
        /**
107
         * Copy every SQLRowValues.
107
         * Copy every SQLRowValues.
108
         */
108
         */
109
        COPY_ROW
109
        COPY_ROW
110
    }
110
    }
111
 
111
 
112
    static public enum CreateMode {
112
    static public enum CreateMode {
113
        /**
113
        /**
114
         * Never create rows.
114
         * Never create rows.
115
         */
115
         */
116
        CREATE_NONE,
116
        CREATE_NONE,
117
        /**
117
        /**
118
         * For non-full step, create one row with all links. For example with a step of 3 links :
118
         * For non-full step, create one row with all links. For example with a step of 3 links :
119
         * <ul>
119
         * <ul>
120
         * <li>if they are all filled, do nothing</li>
120
         * <li>if they are all filled, do nothing</li>
121
         * <li>if they are all empty, create one row</li>
121
         * <li>if they are all empty, create one row</li>
122
         * <li>if one link is filled with a row, add all empty links to it</li>
122
         * <li>if one link is filled with a row, add all empty links to it</li>
123
         * <li>if more than one link is filled (but not all of them), error out as it would leave a
123
         * <li>if more than one link is filled (but not all of them), error out as it would leave a
124
         * hybrid state : neither 2 rows joined by all the links, nor one row per link</li>
124
         * hybrid state : neither 2 rows joined by all the links, nor one row per link</li>
125
         * </ul>
125
         * </ul>
126
         * Then follow all existing plus created rows.
126
         * Then follow all existing plus created rows.
127
         */
127
         */
128
        CREATE_ONE,
128
        CREATE_ONE,
129
        /**
129
        /**
130
         * Create one row for each empty link, then follow all existing plus created rows.
130
         * Create one row for each empty link, then follow all existing plus created rows.
131
         */
131
         */
132
        CREATE_MANY
132
        CREATE_MANY
133
    }
133
    }
134
 
134
 
135
    public static final Object SQL_DEFAULT = new Object() {
135
    public static final Object SQL_DEFAULT = new Object() {
136
        @Override
136
        @Override
137
        public String toString() {
137
        public String toString() {
138
            return SQLRowValues.class.getSimpleName() + ".SQL_DEFAULT";
138
            return SQLRowValues.class.getSimpleName() + ".SQL_DEFAULT";
139
        }
139
        }
140
    };
140
    };
141
    /**
141
    /**
142
     * Empty foreign field value.
142
     * Empty foreign field value.
143
     * 
143
     * 
144
     * @see #putEmptyLink(String)
144
     * @see #putEmptyLink(String)
145
     */
145
     */
146
    public static final Object SQL_EMPTY_LINK = new Object() {
146
    public static final Object SQL_EMPTY_LINK = new Object() {
147
        @Override
147
        @Override
148
        public String toString() {
148
        public String toString() {
149
            return SQLRowValues.class.getSimpleName() + ".SQL_EMPTY_LINK";
149
            return SQLRowValues.class.getSimpleName() + ".SQL_EMPTY_LINK";
150
        }
150
        }
151
    };
151
    };
152
 
152
 
153
    static public enum ValidityCheck {
153
    static public enum ValidityCheck {
154
        /**
154
        /**
155
         * The check is never performed.
155
         * The check is never performed.
156
         */
156
         */
157
        FORBIDDEN {
157
        FORBIDDEN {
158
            @Override
158
            @Override
159
            public boolean shouldCheck(final Boolean asked) {
159
            public boolean shouldCheck(final Boolean asked) {
160
                return false;
160
                return false;
161
            }
161
            }
162
        },
162
        },
163
        /**
163
        /**
164
         * The check is only performed if requested.
164
         * The check is only performed if requested.
165
         */
165
         */
166
        FALSE_BY_DEFAULT {
166
        FALSE_BY_DEFAULT {
167
            @Override
167
            @Override
168
            public boolean shouldCheck(Boolean asked) {
168
            public boolean shouldCheck(Boolean asked) {
169
                return asked == null ? false : asked.booleanValue();
169
                return asked == null ? false : asked.booleanValue();
170
            }
170
            }
171
        },
171
        },
172
        /**
172
        /**
173
         * The check is performed unless specified.
173
         * The check is performed unless specified.
174
         */
174
         */
175
        TRUE_BY_DEFAULT {
175
        TRUE_BY_DEFAULT {
176
            @Override
176
            @Override
177
            public boolean shouldCheck(Boolean asked) {
177
            public boolean shouldCheck(Boolean asked) {
178
                return asked == null ? true : asked.booleanValue();
178
                return asked == null ? true : asked.booleanValue();
179
            }
179
            }
180
        },
180
        },
181
        /**
181
        /**
182
         * The check is always performed. This is not generally recommended as some methods of the
182
         * The check is always performed. This is not generally recommended as some methods of the
183
         * framework will fail.
183
         * framework will fail.
184
         */
184
         */
185
        FORCED {
185
        FORCED {
186
            @Override
186
            @Override
187
            public boolean shouldCheck(Boolean asked) {
187
            public boolean shouldCheck(Boolean asked) {
188
                return true;
188
                return true;
189
            }
189
            }
190
        };
190
        };
191
 
191
 
192
        public abstract boolean shouldCheck(final Boolean asked);
192
        public abstract boolean shouldCheck(final Boolean asked);
193
    }
193
    }
194
 
194
 
195
    @GuardedBy("this")
195
    @GuardedBy("this")
196
    private static ValidityCheck checkValidity;
196
    private static ValidityCheck checkValidity;
197
 
197
 
198
    /**
198
    /**
199
     * Set how {@link #getInvalid()} should be called before each data modification. Initially set
199
     * Set how {@link #getInvalid()} should be called before each data modification. Initially set
200
     * to {@link ValidityCheck#TRUE_BY_DEFAULT}. NOTE : the check is performed outside the
200
     * to {@link ValidityCheck#TRUE_BY_DEFAULT}. NOTE : the check is performed outside the
201
     * transaction and thus not always accurate. Only the DB can make sure of the validity
201
     * transaction and thus not always accurate. Only the DB can make sure of the validity
202
     * efficiently : with foreign keys and a trigger to check that unarchived rows reference
202
     * efficiently : with foreign keys and a trigger to check that unarchived rows reference
203
     * unarchived rows.
203
     * unarchived rows.
204
     * 
204
     * 
205
     * @param vc the new mode, <code>null</code> to set the default.
205
     * @param vc the new mode, <code>null</code> to set the default.
206
     */
206
     */
207
    public synchronized static void setValidityChecked(final ValidityCheck vc) {
207
    public synchronized static void setValidityChecked(final ValidityCheck vc) {
208
        checkValidity = vc == null ? ValidityCheck.TRUE_BY_DEFAULT : vc;
208
        checkValidity = vc == null ? ValidityCheck.TRUE_BY_DEFAULT : vc;
209
    }
209
    }
210
 
210
 
211
    /**
211
    /**
212
     * Whether or not {@link #getInvalid()} should be called.
212
     * Whether or not {@link #getInvalid()} should be called.
213
     * 
213
     * 
214
     * @param asked what the caller requested.
214
     * @param asked what the caller requested.
215
     * @return <code>true</code> if the validity is checked.
215
     * @return <code>true</code> if the validity is checked.
216
     */
216
     */
217
    public synchronized static boolean isValidityChecked(final Boolean asked) {
217
    public synchronized static boolean isValidityChecked(final Boolean asked) {
218
        return checkValidity.shouldCheck(asked);
218
        return checkValidity.shouldCheck(asked);
219
    }
219
    }
220
 
220
 
221
    static {
221
    static {
222
        setValidityChecked(null);
222
        setValidityChecked(null);
223
    }
223
    }
224
 
224
 
225
    private static final boolean DEFAULT_ALLOW_BACKTRACK = true;
225
    private static final boolean DEFAULT_ALLOW_BACKTRACK = true;
226
 
226
 
227
    // i.e. no re-hash for up to 6 entries (8*0.8=6.4)
227
    // i.e. no re-hash for up to 6 entries (8*0.8=6.4)
228
    private static final int DEFAULT_VALUES_CAPACITY = 8;
228
    private static final int DEFAULT_VALUES_CAPACITY = 8;
229
    private static final float DEFAULT_LOAD_FACTOR = 0.8f;
229
    private static final float DEFAULT_LOAD_FACTOR = 0.8f;
230
 
230
 
231
    // Assure there's no copy. Don't just return plannedSize : e.g. for HashMap if it's 15
231
    // Assure there's no copy. Don't just return plannedSize : e.g. for HashMap if it's 15
232
    // the initial capacity will be 16 (the nearest power of 2) and threshold will be 12.8 (with
232
    // the initial capacity will be 16 (the nearest power of 2) and threshold will be 12.8 (with
233
    // our load of 0.8) so there would be a rehash at the 13th items.
233
    // our load of 0.8) so there would be a rehash at the 13th items.
234
    private static final int getCapacity(final int plannedSize, final int defaultCapacity) {
234
    private static final int getCapacity(final int plannedSize, final int defaultCapacity) {
235
        return plannedSize < 0 ? defaultCapacity : Math.max((int) (plannedSize / DEFAULT_LOAD_FACTOR) + 1, 4);
235
        return plannedSize < 0 ? defaultCapacity : Math.max((int) (plannedSize / DEFAULT_LOAD_FACTOR) + 1, 4);
236
    }
236
    }
237
 
237
 
238
    private static final LinkedHashMap<String, Object> createLinkedHashMap(final int plannedSize) {
238
    private static final LinkedHashMap<String, Object> createLinkedHashMap(final int plannedSize) {
239
        if (plannedSize < 0)
239
        if (plannedSize < 0)
240
            throw new IllegalArgumentException("Negative capacity");
240
            throw new IllegalArgumentException("Negative capacity");
241
        return createLinkedHashMap(plannedSize, -1);
241
        return createLinkedHashMap(plannedSize, -1);
242
    }
242
    }
243
 
243
 
244
    private static final <K, V> LinkedHashMap<K, V> createLinkedHashMap(final int plannedSize, final int defaultCapacity) {
244
    private static final <K, V> LinkedHashMap<K, V> createLinkedHashMap(final int plannedSize, final int defaultCapacity) {
245
        return new LinkedHashMap<K, V>(getCapacity(plannedSize, defaultCapacity), DEFAULT_LOAD_FACTOR);
245
        return new LinkedHashMap<K, V>(getCapacity(plannedSize, defaultCapacity), DEFAULT_LOAD_FACTOR);
246
    }
246
    }
247
 
247
 
248
    private static final <K> SetMap<K, SQLRowValues> createSetMap(final int plannedSize, final int defaultCapacity) {
248
    private static final <K> SetMap<K, SQLRowValues> createSetMap(final int plannedSize, final int defaultCapacity) {
249
        return new SetMap<K, SQLRowValues>(new HashMap<K, Set<SQLRowValues>>(getCapacity(plannedSize, defaultCapacity), DEFAULT_LOAD_FACTOR), org.openconcerto.utils.CollectionMap2.Mode.NULL_FORBIDDEN, false) {
249
        return new SetMap<K, SQLRowValues>(new HashMap<K, Set<SQLRowValues>>(getCapacity(plannedSize, defaultCapacity), DEFAULT_LOAD_FACTOR), org.openconcerto.utils.CollectionMap2.Mode.NULL_FORBIDDEN, false) {
250
            @Override
250
            @Override
251
            public Set<SQLRowValues> createCollection(Collection<? extends SQLRowValues> coll) {
251
            public Set<SQLRowValues> createCollection(Collection<? extends SQLRowValues> coll) {
252
                // use LinkedHashSet so that the order is preserved, eg we can iterate over LOCALs
252
                // use LinkedHashSet so that the order is preserved, eg we can iterate over LOCALs
253
                // pointing to a BATIMENT with consistent and predictable (insertion-based) order.
253
                // pointing to a BATIMENT with consistent and predictable (insertion-based) order.
254
                // use IdentitySet to be able to put two equal instances
254
                // use IdentitySet to be able to put two equal instances
255
                return coll == null ? new LinkedIdentitySet<SQLRowValues>() : new LinkedIdentitySet<SQLRowValues>(coll);
255
                return coll == null ? new LinkedIdentitySet<SQLRowValues>() : new LinkedIdentitySet<SQLRowValues>(coll);
256
            }
256
            }
257
        };
257
        };
258
    }
258
    }
259
 
259
 
260
    private final Map<String, Object> values;
260
    private final Map<String, Object> values;
261
    private final Map<String, SQLRowValues> foreigns;
261
    private final Map<String, SQLRowValues> foreigns;
262
    private final SetMap<SQLField, SQLRowValues> referents;
262
    private final SetMap<SQLField, SQLRowValues> referents;
263
    private SQLRowValuesCluster graph;
263
    private SQLRowValuesCluster graph;
264
    private ListMap<SQLField, ReferentChangeListener> referentsListener;
264
    private ListMap<SQLField, ReferentChangeListener> referentsListener;
265
 
265
 
266
    public SQLRowValues(SQLTable t) {
266
    public SQLRowValues(SQLTable t) {
267
        this(t, -1, -1, -1);
267
        this(t, -1, -1, -1);
268
    }
268
    }
269
 
269
 
270
    /**
270
    /**
271
     * Create a new instance.
271
     * Create a new instance.
272
     * 
272
     * 
273
     * @param t the table.
273
     * @param t the table.
274
     * @param valuesPlannedSize no further allocations will be made until that number of
274
     * @param valuesPlannedSize no further allocations will be made until that number of
275
     *        {@link #getAbsolutelyAll() values}, pass a negative value to use a default.
275
     *        {@link #getAbsolutelyAll() values}, pass a negative value to use a default.
276
     * @param foreignsPlannedSize no further allocations will be made until that number of
276
     * @param foreignsPlannedSize no further allocations will be made until that number of
277
     *        {@link #getForeigns() foreigns}, pass a negative value to use a default.
277
     *        {@link #getForeigns() foreigns}, pass a negative value to use a default.
278
     * @param referentsPlannedSize no further allocations will be made until that number of
278
     * @param referentsPlannedSize no further allocations will be made until that number of
279
     *        {@link #getReferentsMap() referents}, pass a negative value to use a default.
279
     *        {@link #getReferentsMap() referents}, pass a negative value to use a default.
280
     * 
280
     * 
281
     */
281
     */
282
    public SQLRowValues(SQLTable t, final int valuesPlannedSize, final int foreignsPlannedSize, final int referentsPlannedSize) {
282
    public SQLRowValues(SQLTable t, final int valuesPlannedSize, final int foreignsPlannedSize, final int referentsPlannedSize) {
283
        super(t);
283
        super(t);
284
        // use LinkedHashSet so that the order is preserved, see #walkFields()
284
        // use LinkedHashSet so that the order is preserved, see #walkFields()
285
        this.values = createLinkedHashMap(valuesPlannedSize, DEFAULT_VALUES_CAPACITY);
285
        this.values = createLinkedHashMap(valuesPlannedSize, DEFAULT_VALUES_CAPACITY);
286
        // foreigns order should be coherent with values
286
        // foreigns order should be coherent with values
287
        this.foreigns = createLinkedHashMap(foreignsPlannedSize, 4);
287
        this.foreigns = createLinkedHashMap(foreignsPlannedSize, 4);
288
        this.referents = createSetMap(referentsPlannedSize, 4);
288
        this.referents = createSetMap(referentsPlannedSize, 4);
289
        // no used much so lazy init
289
        // no used much so lazy init
290
        this.referentsListener = null;
290
        this.referentsListener = null;
291
        // Allow to reduce memory for lonely rows, and even for linked rows since before :
291
        // Allow to reduce memory for lonely rows, and even for linked rows since before :
292
        // 1. create a row, create a cluster
292
        // 1. create a row, create a cluster
293
        // 2. create a second row, create a second cluster
293
        // 2. create a second row, create a second cluster
294
        // 3. put, the second row uses the first cluster, the second one can be collected
294
        // 3. put, the second row uses the first cluster, the second one can be collected
295
        // Now the second cluster is never created, see SQLRowValuesCluster.add().
295
        // Now the second cluster is never created, see SQLRowValuesCluster.add().
296
        this.graph = null;
296
        this.graph = null;
297
    }
297
    }
298
 
298
 
299
    public SQLRowValues(SQLTable t, Map<String, ?> values) {
299
    public SQLRowValues(SQLTable t, Map<String, ?> values) {
300
        this(t, values.size(), -1, -1);
300
        this(t, values.size(), -1, -1);
301
        this.setAll(values);
301
        this.setAll(values);
302
    }
302
    }
303
 
303
 
304
    public SQLRowValues(SQLRowValues vals) {
304
    public SQLRowValues(SQLRowValues vals) {
305
        this(vals, ForeignCopyMode.COPY_ROW);
305
        this(vals, ForeignCopyMode.COPY_ROW);
306
    }
306
    }
307
 
307
 
308
    /**
308
    /**
309
     * Create a new instance with the same values. If <code>copyForeigns</code> is <code>true</code>
309
     * Create a new instance with the same values. If <code>copyForeigns</code> is <code>true</code>
310
     * the new instance will have exactly the same values, ie it will point to the same
310
     * the new instance will have exactly the same values, ie it will point to the same
311
     * SQLRowValues. If <code>copyForeigns</code> is <code>false</code> all SQLRowValues will be
311
     * SQLRowValues. If <code>copyForeigns</code> is <code>false</code> all SQLRowValues will be
312
     * left out.
312
     * left out.
313
     * 
313
     * 
314
     * @param vals the instance to copy.
314
     * @param vals the instance to copy.
315
     * @param copyForeigns whether to copy foreign SQLRowValues.
315
     * @param copyForeigns whether to copy foreign SQLRowValues.
316
     */
316
     */
317
    public SQLRowValues(SQLRowValues vals, ForeignCopyMode copyForeigns) {
317
    public SQLRowValues(SQLRowValues vals, ForeignCopyMode copyForeigns) {
318
        // setAll() takes care of foreigns and referents
318
        // setAll() takes care of foreigns and referents
319
        this(vals.getTable(), vals.getAllValues(copyForeigns));
319
        this(vals.getTable(), vals.getAllValues(copyForeigns));
320
    }
320
    }
321
 
321
 
322
    /**
322
    /**
323
     * Copy this rowValues and all others connected to it. Ie contrary to
323
     * Copy this rowValues and all others connected to it. Ie contrary to
324
     * {@link #SQLRowValues(SQLRowValues)} the result will not point to the same rowValues, but to
324
     * {@link #SQLRowValues(SQLRowValues)} the result will not point to the same rowValues, but to
325
     * copy of them.
325
     * copy of them.
326
     * 
326
     * 
327
     * @return a copy of this.
327
     * @return a copy of this.
328
     */
328
     */
329
    public final SQLRowValues deepCopy() {
329
    public final SQLRowValues deepCopy() {
330
        return this.getGraph().deepCopy(this, false);
330
        return this.getGraph().deepCopy(this, false);
331
    }
331
    }
332
 
332
 
333
    public final SQLRowValues copy(final SQLRowValues graph) {
333
    public final SQLRowValues copy(final SQLRowValues graph) {
334
        return this.getGraph().copy(this, graph).get(this);
334
        return this.getGraph().copy(this, graph).get(this);
335
    }
335
    }
336
 
336
 
337
    /**
337
    /**
338
     * Get a frozen version of this. If not already {@link #isFrozen() frozen}, copy this rowValues
338
     * Get a frozen version of this. If not already {@link #isFrozen() frozen}, copy this rowValues
339
     * and all others connected to it and {@link SQLRowValuesCluster#freeze()} the copy. I.e. if the
339
     * and all others connected to it and {@link SQLRowValuesCluster#freeze()} the copy. I.e. if the
340
     * result is to be shared among threads, it still needs to be safely published.
340
     * result is to be shared among threads, it still needs to be safely published.
341
     * 
341
     * 
342
     * @return this if already frozen, otherwise a frozen copy of this.
342
     * @return this if already frozen, otherwise a frozen copy of this.
343
     */
343
     */
344
    public final SQLRowValues toImmutable() {
344
    public final SQLRowValues toImmutable() {
345
        if (this.isFrozen())
345
        if (this.isFrozen())
346
            return this;
346
            return this;
347
        return this.getGraph().deepCopy(this, true);
347
        return this.getGraph().deepCopy(this, true);
348
    }
348
    }
349
 
349
 
350
    // *** graph
350
    // *** graph
351
 
351
 
352
    private void updateLinks(String fieldName, Object old, Object value) {
352
    private void updateLinks(String fieldName, Object old, Object value) {
353
        // try to avoid getTable().getField() (which takes 1/3 of put() for nothing when there is no
353
        // try to avoid getTable().getField() (which takes 1/3 of put() for nothing when there is no
354
        // rowvalues)
354
        // rowvalues)
355
        final boolean oldRowVals = old instanceof SQLRowValues;
355
        final boolean oldRowVals = old instanceof SQLRowValues;
356
        final boolean newRowVals = value instanceof SQLRowValues;
356
        final boolean newRowVals = value instanceof SQLRowValues;
357
        if (!oldRowVals && !newRowVals)
357
        if (!oldRowVals && !newRowVals)
358
            return;
358
            return;
359
 
359
 
360
        final SQLField f = this.getTable().getField(fieldName);
360
        final SQLField f = this.getTable().getField(fieldName);
361
 
361
 
362
        if (oldRowVals) {
362
        if (oldRowVals) {
363
            final SQLRowValues vals = (SQLRowValues) old;
363
            final SQLRowValues vals = (SQLRowValues) old;
364
            vals.referents.removeOne(f, this);
364
            vals.referents.removeOne(f, this);
365
            this.foreigns.remove(fieldName);
365
            this.foreigns.remove(fieldName);
366
            assert this.graph == vals.graph;
366
            assert this.graph == vals.graph;
367
            this.graph.remove(this, f, vals);
367
            this.graph.remove(this, f, vals);
368
            vals.fireRefChange(f, false, this);
368
            vals.fireRefChange(f, false, this);
369
        }
369
        }
370
        if (newRowVals) {
370
        if (newRowVals) {
371
            final SQLRowValues vals = (SQLRowValues) value;
371
            final SQLRowValues vals = (SQLRowValues) value;
372
            vals.referents.add(f, this);
372
            vals.referents.add(f, this);
373
            this.foreigns.put(fieldName, vals);
373
            this.foreigns.put(fieldName, vals);
374
            // prefer vals' graph as add() is faster that way
374
            // prefer vals' graph as add() is faster that way
375
            final SQLRowValuesCluster usedGraph = this.graph != null && vals.graph == null ? this.graph : vals.getGraph();
375
            final SQLRowValuesCluster usedGraph = this.graph != null && vals.graph == null ? this.graph : vals.getGraph();
376
            usedGraph.add(this, f, vals);
376
            usedGraph.add(this, f, vals);
377
            assert this.graph == vals.graph;
377
            assert this.graph == vals.graph;
378
            vals.fireRefChange(f, true, this);
378
            vals.fireRefChange(f, true, this);
379
        }
379
        }
380
    }
380
    }
381
 
381
 
382
    /**
382
    /**
383
     * Return the graph for this instance. NOTE: for single row values the graph is only created on
383
     * Return the graph for this instance. NOTE: for single row values the graph is only created on
384
     * demand.
384
     * demand.
385
     * 
385
     * 
386
     * @return the graph.
386
     * @return the graph.
387
     */
387
     */
388
    public final SQLRowValuesCluster getGraph() {
388
    public final SQLRowValuesCluster getGraph() {
389
        return this.getGraph(true);
389
        return this.getGraph(true);
390
    }
390
    }
391
 
391
 
392
    final SQLRowValuesCluster getGraph(final boolean create) {
392
    final SQLRowValuesCluster getGraph(final boolean create) {
393
        if (create && this.graph == null)
393
        if (create && this.graph == null)
394
            this.graph = new SQLRowValuesCluster(this);
394
            this.graph = new SQLRowValuesCluster(this);
395
        return this.graph;
395
        return this.graph;
396
    }
396
    }
397
 
397
 
398
    /**
398
    /**
399
     * The number of items in our graph. NOTE: this method doesn't allocate a graph.
399
     * The number of items in our graph. NOTE: this method doesn't allocate a graph.
400
     * 
400
     * 
401
     * @return the number of items in our graph.
401
     * @return the number of items in our graph.
402
     * @see SQLRowValuesCluster#size()
402
     * @see SQLRowValuesCluster#size()
403
     */
403
     */
404
    public final int getGraphSize() {
404
    public final int getGraphSize() {
405
        final SQLRowValuesCluster g = this.getGraph(false);
405
        final SQLRowValuesCluster g = this.getGraph(false);
406
        return g == null ? 1 : g.size();
406
        return g == null ? 1 : g.size();
407
    }
407
    }
408
 
408
 
409
    public final Set<SQLTable> getGraphTables() {
409
    public final Set<SQLTable> getGraphTables() {
410
        final SQLRowValuesCluster g = this.getGraph(false);
410
        final SQLRowValuesCluster g = this.getGraph(false);
411
        if (g == null)
411
        if (g == null)
412
            return Collections.singleton(this.getTable());
412
            return Collections.singleton(this.getTable());
413
        else
413
        else
414
            return g.getTables();
414
            return g.getTables();
415
    }
415
    }
416
 
416
 
417
    public final <T> void walkGraph(T acc, ITransformer<State<T>, T> closure) {
417
    public final <T> void walkGraph(T acc, ITransformer<State<T>, T> closure) {
418
        this.getGraph().walk(this, acc, closure);
418
        this.getGraph().walk(this, acc, closure);
419
    }
419
    }
420
 
420
 
421
    /**
421
    /**
422
     * Walk through the fields of the rowValues in order. Eg if you added DESIGNATION, ID_BATIMENT
422
     * Walk through the fields of the rowValues in order. Eg if you added DESIGNATION, ID_BATIMENT
423
     * pointing to {DESIGNATION}, then INCLURE, <code>closure</code> would be called with
423
     * pointing to {DESIGNATION}, then INCLURE, <code>closure</code> would be called with
424
     * LOCAL.DESIGNATION, LOCAL.ID_BATIMENT.DESIGNATION, LOCAL.INCLURE. This can't be done using
424
     * LOCAL.DESIGNATION, LOCAL.ID_BATIMENT.DESIGNATION, LOCAL.INCLURE. This can't be done using
425
     * {@link SQLRowValuesCluster#walk(SQLRowValues, Object, ITransformer, RecursionType)} since it
425
     * {@link SQLRowValuesCluster#walk(SQLRowValues, Object, ITransformer, RecursionType)} since it
426
     * walks through rowValues so if you use {@link RecursionType#BREADTH_FIRST} you'll be passed
426
     * walks through rowValues so if you use {@link RecursionType#BREADTH_FIRST} you'll be passed
427
     * LOCAL, then BATIMENT and the reverse if you use {@link RecursionType#DEPTH_FIRST}.
427
     * LOCAL, then BATIMENT and the reverse if you use {@link RecursionType#DEPTH_FIRST}.
428
     * 
428
     * 
429
     * @param closure what to do on each field.
429
     * @param closure what to do on each field.
430
     */
430
     */
431
    public final void walkFields(IClosure<FieldPath> closure) {
431
    public final void walkFields(IClosure<FieldPath> closure) {
432
        this.walkFields(closure, false);
432
        this.walkFields(closure, false);
433
    }
433
    }
434
 
434
 
435
    public final void walkFields(IClosure<FieldPath> closure, final boolean includeFK) {
435
    public final void walkFields(IClosure<FieldPath> closure, final boolean includeFK) {
436
        this.getGraph().walkFields(this, closure, includeFK);
436
        this.getGraph().walkFields(this, closure, includeFK);
437
    }
437
    }
438
 
438
 
439
    public final SQLRowValues prune(SQLRowValues graph) {
439
    public final SQLRowValues prune(SQLRowValues graph) {
440
        return this.prune(graph, true);
440
        return this.prune(graph, true);
441
    }
441
    }
442
 
442
 
443
    /**
443
    /**
444
     * Prune this graph.
444
     * Prune this graph.
445
     * 
445
     * 
446
     * @param graph the rows and fields to keep.
446
     * @param graph the rows and fields to keep.
447
     * @param copy <code>true</code> if a pruned copy should be returned, <code>false</code> to
447
     * @param copy <code>true</code> if a pruned copy should be returned, <code>false</code> to
448
     *        modify this instance in-place.
448
     *        modify this instance in-place.
449
     * @return a graph no bigger than the passed parameter.
449
     * @return a graph no bigger than the passed parameter.
450
     * @see SQLRowValuesCluster#prune(SQLRowValues, SQLRowValues, boolean)
450
     * @see SQLRowValuesCluster#prune(SQLRowValues, SQLRowValues, boolean)
451
     * @see SQLRowValuesCluster#pruneWithoutCopy(SQLRowValues, SQLRowValues, boolean)
451
     * @see SQLRowValuesCluster#pruneWithoutCopy(SQLRowValues, SQLRowValues, boolean)
452
     */
452
     */
453
    public final SQLRowValues prune(SQLRowValues graph, final boolean copy) {
453
    public final SQLRowValues prune(SQLRowValues graph, final boolean copy) {
454
        return copy ? this.getGraph().prune(this, graph) : this.getGraph().pruneWithoutCopy(this, graph);
454
        return copy ? this.getGraph().prune(this, graph) : this.getGraph().pruneWithoutCopy(this, graph);
455
    }
455
    }
456
 
456
 
457
    /**
457
    /**
458
     * Fetch if necessary and store in this the foreign row.
458
     * Fetch if necessary and store in this the foreign row.
459
     * 
459
     * 
460
     * @param fk a foreign key, eg "ID_FAMILLE_2".
460
     * @param fk a foreign key, eg "ID_FAMILLE_2".
461
     * @return the foreign row, eg FAMILLE[1].
461
     * @return the foreign row, eg FAMILLE[1].
462
     */
462
     */
463
    public final SQLRowValues grow(String fk) {
463
    public final SQLRowValues grow(String fk) {
464
        final Object val = this.getContainedObject(fk);
464
        final Object val = this.getContainedObject(fk);
465
        // if fk is in our map with a null value, nothing to grow
465
        // if fk is in our map with a null value, nothing to grow
466
        if (val != null && !(val instanceof SQLRowValues)) {
466
        if (val != null && !(val instanceof SQLRowValues)) {
467
            final SQLRowValues vals = new SQLRowValues(this.getTable());
467
            final SQLRowValues vals = new SQLRowValues(this.getTable());
468
            vals.putRowValues(fk).setAllToNull();
468
            vals.putRowValues(fk).setAllToNull();
469
            this.grow(vals, true);
469
            this.grow(vals, true);
470
        }
470
        }
471
        return (SQLRowValues) this.getForeign(fk);
471
        return (SQLRowValues) this.getForeign(fk);
472
    }
472
    }
473
 
473
 
474
    public final SQLRowValues grow(SQLRowValues graph) {
474
    public final SQLRowValues grow(SQLRowValues graph) {
475
        return this.grow(graph, true);
475
        return this.grow(graph, true);
476
    }
476
    }
477
 
477
 
478
    /**
478
    /**
479
     * Grow this rowValues to match the passed graph. If this was /RECEPTEUR/ : {DESIGNATION="des";
479
     * Grow this rowValues to match the passed graph. If this was /RECEPTEUR/ : {DESIGNATION="des";
480
     * ID_LOCAL=2} and <code>graph</code> is /RECEPTEUR/ : {DESIGNATION=null; ID_LOCAL:
480
     * ID_LOCAL=2} and <code>graph</code> is /RECEPTEUR/ : {DESIGNATION=null; ID_LOCAL:
481
     * /LOCAL/:{DESIGNATION=null}}, then now this is /RECEPTEUR/ : {DESIGNATION="des"; ID_LOCAL:
481
     * /LOCAL/:{DESIGNATION=null}}, then now this is /RECEPTEUR/ : {DESIGNATION="des"; ID_LOCAL:
482
     * /LOCAL/:{ID=2, DESIGNATION="local"}}
482
     * /LOCAL/:{ID=2, DESIGNATION="local"}}
483
     * 
483
     * 
484
     * @param graph the target graph.
484
     * @param graph the target graph.
485
     * @param checkFields <code>true</code> if missing fields should be fetched.
485
     * @param checkFields <code>true</code> if missing fields should be fetched.
486
     * @return this.
486
     * @return this.
487
     * @throws IllegalArgumentException if this couldn't be grown.
487
     * @throws IllegalArgumentException if this couldn't be grown.
488
     */
488
     */
489
    public final SQLRowValues grow(SQLRowValues graph, final boolean checkFields) {
489
    public final SQLRowValues grow(SQLRowValues graph, final boolean checkFields) {
490
        return this.grow(graph, checkFields, false);
490
        return this.grow(graph, checkFields, false);
491
    }
491
    }
492
 
492
 
493
    public final SQLRowValues grow(SQLRowValues graph, final boolean checkFields, final boolean growUndefined) {
493
    public final SQLRowValues grow(SQLRowValues graph, final boolean checkFields, final boolean growUndefined) {
494
        graph.getGraph().grow(graph, this, checkFields, growUndefined);
494
        graph.getGraph().grow(graph, this, checkFields, growUndefined);
495
        return this;
495
        return this;
496
    }
496
    }
497
 
497
 
498
    public final boolean graphContains(SQLRowValues graph) {
498
    public final boolean graphContains(SQLRowValues graph) {
499
        return this.getGraph().contains(this, graph) == null;
499
        return this.getGraph().contains(this, graph) == null;
500
    }
500
    }
501
 
501
 
502
    void setGraph(SQLRowValuesCluster g) {
502
    void setGraph(SQLRowValuesCluster g) {
503
        assert g != null;
503
        assert g != null;
504
        this.graph = g;
504
        this.graph = g;
505
    }
505
    }
506
 
506
 
507
    public final boolean hasForeigns() {
507
    public final boolean hasForeigns() {
508
        // OK since updateLinks() removes empty map entries
508
        // OK since updateLinks() removes empty map entries
509
        return !this.foreigns.isEmpty();
509
        return !this.foreigns.isEmpty();
510
    }
510
    }
511
 
511
 
512
    public final Map<String, SQLRowValues> getForeigns() {
512
    public final Map<String, SQLRowValues> getForeigns() {
513
        return Collections.unmodifiableMap(this.foreigns);
513
        return Collections.unmodifiableMap(this.foreigns);
514
    }
514
    }
515
 
515
 
516
    final int getForeignsSize() {
516
    final int getForeignsSize() {
517
        return this.foreigns.size();
517
        return this.foreigns.size();
518
    }
518
    }
519
 
519
 
520
    final Map<SQLField, SQLRowValues> getForeignsBySQLField() {
520
    final Map<SQLField, SQLRowValues> getForeignsBySQLField() {
521
        return new TransformedMap<String, SQLField, SQLRowValues>(this.getForeigns(), new ITransformer<String, SQLField>() {
521
        return new TransformedMap<String, SQLField, SQLRowValues>(this.getForeigns(), new ITransformer<String, SQLField>() {
522
            @Override
522
            @Override
523
            public SQLField transformChecked(String input) {
523
            public SQLField transformChecked(String input) {
524
                return getTable().getField(input);
524
                return getTable().getField(input);
525
            }
525
            }
526
        }, new ITransformer<SQLField, String>() {
526
        }, new ITransformer<SQLField, String>() {
527
            @Override
527
            @Override
528
            public String transformChecked(SQLField input) {
528
            public String transformChecked(SQLField input) {
529
                return input.getName();
529
                return input.getName();
530
            }
530
            }
531
        });
531
        });
532
    }
532
    }
533
 
533
 
534
    // package private since the result is modifiable, see below for the public version
534
    // package private since the result is modifiable, see below for the public version
535
    final SetMap<SQLField, SQLRowValues> getReferents() {
535
    final SetMap<SQLField, SQLRowValues> getReferents() {
536
        return this.referents;
536
        return this.referents;
537
    }
537
    }
538
 
538
 
539
    public final SetMapItf<SQLField, SQLRowValues> getReferentsMap() {
539
    public final SetMapItf<SQLField, SQLRowValues> getReferentsMap() {
540
        return SetMap.unmodifiableMap(this.referents);
540
        return SetMap.unmodifiableMap(this.referents);
541
    }
541
    }
542
 
542
 
543
    public final boolean hasReferents() {
543
    public final boolean hasReferents() {
544
        // OK since updateLinks() removes empty map entries
544
        // OK since updateLinks() removes empty map entries
545
        return !this.referents.isEmpty();
545
        return !this.referents.isEmpty();
546
    }
546
    }
547
 
547
 
548
    @Override
548
    @Override
549
    public Collection<SQLRowValues> getReferentRows() {
549
    public Collection<SQLRowValues> getReferentRows() {
550
        // remove the backdoor since values() returns a view
550
        // remove the backdoor since values() returns a view
551
        // remove duplicates (e.g. this is a CONTACT referenced by ID_CONTACT_RAPPORT &
551
        // remove duplicates (e.g. this is a CONTACT referenced by ID_CONTACT_RAPPORT &
552
        // ID_CONTACT_RDV from the same site)
552
        // ID_CONTACT_RDV from the same site)
553
        return this.referents.createCollection(this.referents.allValues());
553
        return this.referents.createCollection(this.referents.allValues());
554
    }
554
    }
555
 
555
 
556
    @Override
556
    @Override
557
    public Set<SQLRowValues> getReferentRows(SQLField refField) {
557
    public Set<SQLRowValues> getReferentRows(SQLField refField) {
558
        return Collections.unmodifiableSet(this.referents.getNonNull(refField));
558
        return Collections.unmodifiableSet(this.referents.getNonNull(refField));
559
    }
559
    }
560
 
560
 
561
    @Override
561
    @Override
562
    public Collection<SQLRowValues> getReferentRows(SQLTable refTable) {
562
    public Collection<SQLRowValues> getReferentRows(SQLTable refTable) {
563
        // remove duplicates
563
        // remove duplicates
564
        final Collection<SQLRowValues> res = this.referents.createCollection(null);
564
        final Collection<SQLRowValues> res = this.referents.createCollection(null);
565
        assert res.isEmpty();
565
        assert res.isEmpty();
566
        for (final Map.Entry<SQLField, Set<SQLRowValues>> e : this.referents.entrySet()) {
566
        for (final Map.Entry<SQLField, Set<SQLRowValues>> e : this.referents.entrySet()) {
567
            if (e.getKey().getTable().equals(refTable))
567
            if (e.getKey().getTable().equals(refTable))
568
                res.addAll(e.getValue());
568
                res.addAll(e.getValue());
569
        }
569
        }
570
        return res;
570
        return res;
571
    }
571
    }
572
 
572
 
573
    /**
573
    /**
574
     * Remove all links pointing to this from the referent rows.
574
     * Remove all links pointing to this from the referent rows.
575
     * 
575
     * 
576
     * @return this.
576
     * @return this.
577
     */
577
     */
578
    public final SQLRowValues clearReferents() {
578
    public final SQLRowValues clearReferents() {
579
        return this.changeReferents(ForeignCopyMode.NO_COPY);
579
        return this.changeReferents(ForeignCopyMode.NO_COPY);
580
    }
580
    }
581
 
581
 
582
    public final SQLRowValues changeReferents(final ForeignCopyMode mode) {
582
    public final SQLRowValues changeReferents(final ForeignCopyMode mode) {
583
        return this.changeReferents(null, false, mode);
583
        return this.changeReferents(null, false, mode);
584
    }
584
    }
585
 
585
 
586
    public final SQLRowValues removeReferents(final SQLField f) {
586
    public final SQLRowValues removeReferents(final SQLField f) {
587
        // don't use changeReferents() as it's less optimal
587
        // don't use changeReferents() as it's less optimal
588
        for (final SQLRowValues ref : new ArrayList<SQLRowValues>(this.getReferentRows(f))) {
588
        for (final SQLRowValues ref : new ArrayList<SQLRowValues>(this.getReferentRows(f))) {
589
            ref.remove(f.getName());
589
            ref.remove(f.getName());
590
        }
590
        }
591
        return this;
591
        return this;
592
    }
592
    }
593
 
593
 
594
    public final SQLRowValues removeReferentFields(final Collection<SQLField> fields) {
594
    public final SQLRowValues removeReferentFields(final Collection<SQLField> fields) {
595
        return this.changeReferents(fields, false);
595
        return this.changeReferents(fields, false);
596
    }
596
    }
597
 
597
 
598
    public final SQLRowValues retainReferentFields(final Collection<SQLField> fields) {
598
    public final SQLRowValues retainReferentFields(final Collection<SQLField> fields) {
599
        return this.changeReferents(fields, true);
599
        return this.changeReferents(fields, true);
600
    }
600
    }
601
 
601
 
602
    private final SQLRowValues changeReferents(final Collection<SQLField> fields, final boolean retain) {
602
    private final SQLRowValues changeReferents(final Collection<SQLField> fields, final boolean retain) {
603
        return this.changeReferents(fields, retain, ForeignCopyMode.NO_COPY);
603
        return this.changeReferents(fields, retain, ForeignCopyMode.NO_COPY);
604
    }
604
    }
605
 
605
 
606
    /**
606
    /**
607
     * Change referents. NOTE : depending on the {@link ForeignCopyMode mode} this method may detach
607
     * Change referents. NOTE : depending on the {@link ForeignCopyMode mode} this method may detach
608
     * this row from some of its referents.
608
     * this row from some of its referents.
609
     * 
609
     * 
610
     * @param fields the fields to change or to exclude from change.
610
     * @param fields the fields to change or to exclude from change.
611
     * @param exclude <code>true</code> if fields passed to this method must be excluded from the
611
     * @param exclude <code>true</code> if fields passed to this method must be excluded from the
612
     *        change, <code>false</code> to only change fields passed to this method.
612
     *        change, <code>false</code> to only change fields passed to this method.
613
     * @param mode how the referent row will be changed.
613
     * @param mode how the referent row will be changed.
614
     * @return this.
614
     * @return this.
615
     */
615
     */
616
    public final SQLRowValues changeReferents(final Collection<SQLField> fields, final boolean exclude, final ForeignCopyMode mode) {
616
    public final SQLRowValues changeReferents(final Collection<SQLField> fields, final boolean exclude, final ForeignCopyMode mode) {
617
        if (!isEmpty(fields, exclude) && mode != ForeignCopyMode.COPY_ROW) {
617
        if (!isEmpty(fields, exclude) && mode != ForeignCopyMode.COPY_ROW) {
618
            // copy otherwise ConcurrentModificationException
618
            // copy otherwise ConcurrentModificationException
619
            for (final Entry<SQLField, Set<SQLRowValues>> e : CopyUtils.copy(this.getReferents()).entrySet()) {
619
            for (final Entry<SQLField, Set<SQLRowValues>> e : CopyUtils.copy(this.getReferents()).entrySet()) {
620
                // fields == null means !retain thanks to the above if
620
                // fields == null means !retain thanks to the above if
621
                if (fields == null || fields.contains(e.getKey()) != exclude) {
621
                if (fields == null || fields.contains(e.getKey()) != exclude) {
622
                    for (final SQLRowValues ref : e.getValue()) {
622
                    for (final SQLRowValues ref : e.getValue()) {
623
                        ref.flatten(e.getKey().getName(), mode);
623
                        ref.flatten(e.getKey().getName(), mode);
624
                    }
624
                    }
625
                }
625
                }
626
            }
626
            }
627
        }
627
        }
628
        return this;
628
        return this;
629
    }
629
    }
630
 
630
 
631
    public SQLRowValues retainReferent(SQLRowValues toRetain) {
631
    public SQLRowValues retainReferent(SQLRowValues toRetain) {
632
        return this.retainReferents(Collections.singleton(toRetain));
632
        return this.retainReferents(Collections.singleton(toRetain));
633
    }
633
    }
634
 
634
 
635
    public SQLRowValues retainReferents(Collection<SQLRowValues> toRetain) {
635
    public SQLRowValues retainReferents(Collection<SQLRowValues> toRetain) {
636
        toRetain = CollectionUtils.toIdentitySet(toRetain);
636
        toRetain = CollectionUtils.toIdentitySet(toRetain);
637
        // copy otherwise ConcurrentModificationException
637
        // copy otherwise ConcurrentModificationException
638
        for (final Entry<SQLField, Set<SQLRowValues>> e : CopyUtils.copy(this.getReferents()).entrySet()) {
638
        for (final Entry<SQLField, Set<SQLRowValues>> e : CopyUtils.copy(this.getReferents()).entrySet()) {
639
            for (final SQLRowValues ref : e.getValue()) {
639
            for (final SQLRowValues ref : e.getValue()) {
640
                if (!toRetain.contains(ref))
640
                if (!toRetain.contains(ref))
641
                    ref.remove(e.getKey().getName());
641
                    ref.remove(e.getKey().getName());
642
            }
642
            }
643
        }
643
        }
644
        return this;
644
        return this;
645
    }
645
    }
646
 
646
 
647
    // *** get
647
    // *** get
648
 
648
 
649
    public int size() {
649
    public int size() {
650
        return this.values.size();
650
        return this.values.size();
651
    }
651
    }
652
 
652
 
653
    @Override
653
    @Override
654
    public final int getID() {
654
    public final int getID() {
655
        final Number res = this.getIDNumber(false);
655
        final Number res = this.getIDNumber(false);
656
        if (res != null)
656
        if (res != null)
657
            return res.intValue();
657
            return res.intValue();
658
        else
658
        else
659
            return SQLRow.NONEXISTANT_ID;
659
            return SQLRow.NONEXISTANT_ID;
660
    }
660
    }
661
 
661
 
662
    @Override
662
    @Override
663
    public Number getIDNumber() {
663
    public Number getIDNumber() {
664
        // We never have rows in the DB with NULL primary key, so a null result means no value was
664
        // We never have rows in the DB with NULL primary key, so a null result means no value was
665
        // specified (or null was programmatically specified)
665
        // specified (or null was programmatically specified)
666
        return this.getIDNumber(false);
666
        return this.getIDNumber(false);
667
    }
667
    }
668
 
668
 
669
    public final Number getIDNumber(final boolean mustBePresent) {
669
    public final Number getIDNumber(final boolean mustBePresent) {
670
        final Object res = this.getObject(this.getTable().getKey().getName(), mustBePresent);
670
        final Object res = this.getObject(this.getTable().getKey().getName(), mustBePresent);
671
        if (res == null) {
671
        if (res == null) {
672
            return null;
672
            return null;
673
        } else {
673
        } else {
674
            return (Number) res;
674
            return (Number) res;
675
        }
675
        }
676
    }
676
    }
677
 
677
 
678
    @Override
678
    @Override
679
    public final Object getObject(String fieldName) {
679
    public final Object getObject(String fieldName) {
680
        return this.values.get(fieldName);
680
        return this.values.get(fieldName);
681
    }
681
    }
682
 
682
 
683
    @Override
683
    @Override
684
    public Map<String, Object> getAbsolutelyAll() {
684
    public Map<String, Object> getAbsolutelyAll() {
685
        return getAllValues(ForeignCopyMode.COPY_ROW);
685
        return getAllValues(ForeignCopyMode.COPY_ROW);
686
    }
686
    }
687
 
687
 
688
    protected final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns) {
688
    protected final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns) {
689
        return this.getAllValues(copyForeigns, false);
689
        return this.getAllValues(copyForeigns, false);
690
    }
690
    }
691
 
691
 
692
    private final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns, final boolean copy) {
692
    private final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns, final boolean copy) {
693
        final Map<String, Object> toAdd;
693
        final Map<String, Object> toAdd;
694
        if (copyForeigns == ForeignCopyMode.COPY_ROW || this.foreigns.size() == 0) {
694
        if (copyForeigns == ForeignCopyMode.COPY_ROW || this.foreigns.size() == 0) {
695
            if (copy) {
695
            if (copy) {
696
                toAdd = createLinkedHashMap(this.size());
696
                toAdd = createLinkedHashMap(this.size());
697
                toAdd.putAll(this.values);
697
                toAdd.putAll(this.values);
698
            } else {
698
            } else {
699
                toAdd = this.values;
699
                toAdd = this.values;
700
            }
700
            }
701
        } else {
701
        } else {
702
            final Set<Entry<String, Object>> entrySet = this.values.entrySet();
702
            final Set<Entry<String, Object>> entrySet = this.values.entrySet();
703
            toAdd = createLinkedHashMap(entrySet.size());
703
            toAdd = createLinkedHashMap(entrySet.size());
704
            for (final Map.Entry<String, Object> e : entrySet) {
704
            for (final Map.Entry<String, Object> e : entrySet) {
705
                if (!(e.getValue() instanceof SQLRowValues)) {
705
                if (!(e.getValue() instanceof SQLRowValues)) {
706
                    toAdd.put(e.getKey(), e.getValue());
706
                    toAdd.put(e.getKey(), e.getValue());
707
                } else if (copyForeigns == ForeignCopyMode.COPY_NULL) {
707
                } else if (copyForeigns == ForeignCopyMode.COPY_NULL) {
708
                    toAdd.put(e.getKey(), null);
708
                    toAdd.put(e.getKey(), null);
709
                } else if (copyForeigns != ForeignCopyMode.NO_COPY) {
709
                } else if (copyForeigns != ForeignCopyMode.NO_COPY) {
710
                    final SQLRowValues foreign = (SQLRowValues) e.getValue();
710
                    final SQLRowValues foreign = (SQLRowValues) e.getValue();
711
                    if (foreign.hasID())
711
                    if (foreign.hasID())
712
                        toAdd.put(e.getKey(), foreign.getIDNumber());
712
                        toAdd.put(e.getKey(), foreign.getIDNumber());
713
                    else if (copyForeigns == ForeignCopyMode.COPY_ID_OR_ROW)
713
                    else if (copyForeigns == ForeignCopyMode.COPY_ID_OR_ROW)
714
                        toAdd.put(e.getKey(), foreign);
714
                        toAdd.put(e.getKey(), foreign);
715
                }
715
                }
716
            }
716
            }
717
        }
717
        }
718
        return copy ? toAdd : Collections.unmodifiableMap(toAdd);
718
        return copy ? toAdd : Collections.unmodifiableMap(toAdd);
719
    }
719
    }
720
 
720
 
721
    /**
721
    /**
722
     * All current groups of this row.
722
     * All current groups of this row.
723
     * 
723
     * 
724
     * @return the ordered groups.
724
     * @return the ordered groups.
725
     * @throws IllegalStateException if a group is incomplete (e.g. a primary key has only one of
725
     * @throws IllegalStateException if a group is incomplete (e.g. a primary key has only one of
726
     *         its two values).
726
     *         its two values).
727
     */
727
     */
728
    public final Set<FieldGroup> getFieldGroups() throws IllegalStateException {
728
    public final Set<FieldGroup> getFieldGroups() throws IllegalStateException {
729
        final Set<String> fields = this.getFields();
729
        final Set<String> fields = this.getFields();
730
        // keep order
730
        // keep order
731
        final LinkedHashSet<FieldGroup> set = new LinkedHashSet<FieldGroup>();
731
        final LinkedHashSet<FieldGroup> set = new LinkedHashSet<FieldGroup>();
732
        final Map<String, FieldGroup> tableGroups = this.getTable().getFieldGroups();
732
        final Map<String, FieldGroup> tableGroups = this.getTable().getFieldGroups();
733
        for (final String fieldName : fields) {
733
        for (final String fieldName : fields) {
734
            final FieldGroup group = tableGroups.get(fieldName);
734
            final FieldGroup group = tableGroups.get(fieldName);
735
            // check that groups are complete
735
            // check that groups are complete
736
            if (set.add(group)) {
736
            if (set.add(group)) {
737
                if (!fields.containsAll(group.getFields()))
737
                if (!fields.containsAll(group.getFields()))
738
                    throw new IllegalStateException("Missing fields for " + group + ", current fields : " + fields);
738
                    throw new IllegalStateException("Missing fields for " + group + ", current fields : " + fields);
739
            }
739
            }
740
        }
740
        }
741
        return set;
741
        return set;
742
    }
742
    }
743
 
743
 
744
    /**
744
    /**
745
     * Return the foreign row, if any, for the passed field.
745
     * Return the foreign row, if any, for the passed field.
746
     * 
746
     * 
747
     * @param fieldName name of the foreign field.
747
     * @param fieldName name of the foreign field.
748
     * @return if <code>null</code> or a SQLRowValues one was put at <code>fieldName</code>, return
748
     * @return if <code>null</code> or a SQLRowValues one was put at <code>fieldName</code>, return
749
     *         it ; else assume that an ID was put at <code>fieldName</code> and return a new SQLRow
749
     *         it ; else assume that an ID was put at <code>fieldName</code> and return a new SQLRow
750
     *         with it.
750
     *         with it.
751
     * @throws IllegalArgumentException if fieldName is not a foreign field or if it isn't contained
751
     * @throws IllegalArgumentException if fieldName is not a foreign field or if it isn't contained
752
     *         in this instance.
752
     *         in this instance.
753
     * @throws ClassCastException if the value is neither a SQLRowValues, nor <code>null</code> nor
753
     * @throws ClassCastException if the value is neither a SQLRowValues, nor <code>null</code> nor
754
     *         a Number.
754
     *         a Number.
755
     */
755
     */
756
    @Override
756
    @Override
757
    public final SQLRowAccessor getForeign(String fieldName) throws IllegalArgumentException, ClassCastException {
757
    public final SQLRowAccessor getForeign(String fieldName) throws IllegalArgumentException, ClassCastException {
758
        return this.getForeign(this.getForeignLink(Collections.singletonList(fieldName)));
758
        return this.getForeign(this.getForeignLink(Collections.singletonList(fieldName)));
759
    }
759
    }
760
 
760
 
761
    public final SQLRowAccessor getForeign(final Link l) throws IllegalArgumentException {
761
    public final SQLRowAccessor getForeign(final Link l) throws IllegalArgumentException {
762
        if (!l.getSource().equals(this.getTable()))
762
        if (!l.getSource().equals(this.getTable()))
763
            throw new IllegalArgumentException(l + " not from " + this);
763
            throw new IllegalArgumentException(l + " not from " + this);
764
        final String fieldName = l.getSingleField().getName();
764
        final String fieldName = l.getSingleField().getName();
765
        final Object val = this.getContainedObject(fieldName);
765
        final Object val = this.getContainedObject(fieldName);
766
        if (val instanceof SQLRowAccessor) {
766
        if (val instanceof SQLRowAccessor) {
767
            return (SQLRowAccessor) val;
767
            return (SQLRowAccessor) val;
768
        } else if (val == null) {
768
        } else if (val == null) {
769
            // since we used getContainedObject(), it means that a null was put in our map, not that
769
            // since we used getContainedObject(), it means that a null was put in our map, not that
770
            // fieldName wasn't there
770
            // fieldName wasn't there
771
            return null;
771
            return null;
772
        } else if (this.isDefault(fieldName)) {
772
        } else if (this.isDefault(fieldName)) {
773
            throw new IllegalStateException(fieldName + " is DEFAULT");
773
            throw new IllegalStateException(fieldName + " is DEFAULT");
774
        } else {
774
        } else {
775
            return new SQLRow(l.getTarget(), this.getInt(fieldName));
775
            return new SQLRow(l.getTarget(), this.getInt(fieldName));
776
        }
776
        }
777
    }
777
    }
778
 
778
 
779
    public boolean isDefault(String fieldName) {
779
    public boolean isDefault(String fieldName) {
780
        return SQL_DEFAULT.equals(this.getObject(fieldName));
780
        return SQL_DEFAULT.equals(this.getObject(fieldName));
781
    }
781
    }
782
 
782
 
783
    /**
783
    /**
784
     * Retourne les champs spécifiés par cette instance.
784
     * Retourne les champs spécifiés par cette instance.
785
     * 
785
     * 
786
     * @return l'ensemble des noms des champs.
786
     * @return l'ensemble des noms des champs.
787
     */
787
     */
788
    @Override
788
    @Override
789
    public Set<String> getFields() {
789
    public Set<String> getFields() {
790
        return Collections.unmodifiableSet(this.values.keySet());
790
        return Collections.unmodifiableSet(this.values.keySet());
791
    }
791
    }
792
 
792
 
793
    // avoid Collections.unmodifiableSet() allocation
793
    // avoid Collections.unmodifiableSet() allocation
794
    @Override
794
    @Override
795
    public boolean contains(String fieldName) {
795
    public boolean contains(String fieldName) {
796
        return this.values.containsKey(fieldName);
796
        return this.values.containsKey(fieldName);
797
    }
797
    }
798
 
798
 
799
    @Override
799
    @Override
800
    public final SQLRow asRow() {
800
    public final SQLRow asRow() {
801
        if (!this.hasID())
801
        if (!this.hasID())
802
            throw new IllegalStateException(this + " has no ID");
802
            throw new IllegalStateException(this + " has no ID");
803
        return new SQLRow(this.getTable(), this.getAllValues(ForeignCopyMode.COPY_ID_OR_RM));
803
        return new SQLRow(this.getTable(), this.getAllValues(ForeignCopyMode.COPY_ID_OR_RM));
804
    }
804
    }
805
 
805
 
806
    @Override
806
    @Override
807
    public final SQLRowValues asRowValues() {
807
    public final SQLRowValues asRowValues() {
808
        return this;
808
        return this;
809
    }
809
    }
810
 
810
 
811
    // *** set
811
    // *** set
812
 
812
 
813
    /**
813
    /**
814
     * Whether this can be modified.
814
     * Whether this can be modified.
815
     * 
815
     * 
816
     * @return <code>true</code> if this (and its graph) is not modifiable.
816
     * @return <code>true</code> if this (and its graph) is not modifiable.
817
     */
817
     */
818
    public final boolean isFrozen() {
818
    public final boolean isFrozen() {
819
        final SQLRowValuesCluster g = this.getGraph(false);
819
        final SQLRowValuesCluster g = this.getGraph(false);
820
        return g != null && g.isFrozen();
820
        return g != null && g.isFrozen();
821
    }
821
    }
822
 
822
 
823
    private void checkFrozen() {
823
    private void checkFrozen() {
824
        if (this.isFrozen())
824
        if (this.isFrozen())
825
            throw new IllegalStateException("Graph is not modifiable");
825
            throw new IllegalStateException("Graph is not modifiable");
826
    }
826
    }
827
 
827
 
828
    /**
828
    /**
829
     * Retains only the fields in this that are contained in the specified collection. In other
829
     * Retains only the fields in this that are contained in the specified collection. In other
830
     * words, removes all of its elements that are not contained in the specified collection.
830
     * words, removes all of its elements that are not contained in the specified collection.
831
     * 
831
     * 
832
     * @param fields collection containing elements to be retained, <code>null</code> meaning all.
832
     * @param fields collection containing elements to be retained, <code>null</code> meaning all.
833
     * @return this.
833
     * @return this.
834
     */
834
     */
835
    public final SQLRowValues retainAll(Collection<String> fields) {
835
    public final SQLRowValues retainAll(Collection<String> fields) {
836
        return this.changeFields(fields, true);
836
        return this.changeFields(fields, true);
837
    }
837
    }
838
 
838
 
839
    private final SQLRowValues changeFields(Collection<String> fields, final boolean retain) {
839
    private final SQLRowValues changeFields(Collection<String> fields, final boolean retain) {
840
        return this.changeFields(fields, retain, false);
840
        return this.changeFields(fields, retain, false);
841
    }
841
    }
842
 
842
 
843
    public final SQLRowValues changeFields(Collection<String> fields, final boolean retain, final boolean protectGraph) {
843
    public final SQLRowValues changeFields(Collection<String> fields, final boolean retain, final boolean protectGraph) {
844
        if (isEmpty(fields, retain))
844
        if (isEmpty(fields, retain))
845
            return this;
845
            return this;
846
        // clear all on an empty values == no-op
846
        // clear all on an empty values == no-op
847
        if (!retain && fields == null && this.size() == 0)
847
        if (!retain && fields == null && this.size() == 0)
848
            return this;
848
            return this;
849
 
849
 
850
        final Set<String> toRm = new HashSet<String>(this.values.keySet());
850
        final Set<String> toRm = new HashSet<String>(this.values.keySet());
851
        if (protectGraph)
851
        if (protectGraph)
852
            toRm.removeAll(this.foreigns.keySet());
852
            toRm.removeAll(this.foreigns.keySet());
853
        // fields == null => !retain => clear()
853
        // fields == null => !retain => clear()
854
        if (fields != null) {
854
        if (fields != null) {
855
            if (retain) {
855
            if (retain) {
856
                toRm.removeAll(fields);
856
                toRm.removeAll(fields);
857
            } else {
857
            } else {
858
                toRm.retainAll(fields);
858
                toRm.retainAll(fields);
859
            }
859
            }
860
        }
860
        }
861
        // nothing to change
861
        // nothing to change
862
        if (toRm.isEmpty())
862
        if (toRm.isEmpty())
863
            return this;
863
            return this;
864
        // handle links
864
        // handle links
865
        final Map<String, FieldGroup> fieldGroups = getTable().getFieldGroups();
865
        final Map<String, FieldGroup> fieldGroups = getTable().getFieldGroups();
866
        for (final String fieldName : toRm) {
866
        for (final String fieldName : toRm) {
867
            if (fieldGroups.get(fieldName).getKeyType() == Type.FOREIGN_KEY)
867
            if (fieldGroups.get(fieldName).getKeyType() == Type.FOREIGN_KEY)
868
                // name is OK since it is a foreign key
868
                // name is OK since it is a foreign key
869
                // value null is also OK
869
                // value null is also OK
870
                this._put(fieldName, null, false, ValueOperation.CHECK);
870
                this._put(fieldName, null, false, ValueOperation.CHECK);
871
        }
871
        }
872
        if (fields == null && !protectGraph) {
872
        if (fields == null && !protectGraph) {
873
            assert !retain && toRm.equals(this.values.keySet());
873
            assert !retain && toRm.equals(this.values.keySet());
874
            this.values.clear();
874
            this.values.clear();
875
        } else {
875
        } else {
876
            this.values.keySet().removeAll(toRm);
876
            this.values.keySet().removeAll(toRm);
877
        }
877
        }
878
        // if there's no graph, there can't be any listeners
878
        // if there's no graph, there can't be any listeners
879
        final SQLRowValuesCluster graph = this.getGraph(false);
879
        final SQLRowValuesCluster graph = this.getGraph(false);
880
        if (graph != null)
880
        if (graph != null)
881
            graph.fireModification(this, toRm);
881
            graph.fireModification(this, toRm);
882
        return this;
882
        return this;
883
    }
883
    }
884
 
884
 
885
    /**
885
    /**
886
     * Removes from this all fields that are contained in the specified collection.
886
     * Removes from this all fields that are contained in the specified collection.
887
     * 
887
     * 
888
     * @param fields collection containing elements to be removed, <code>null</code> meaning all.
888
     * @param fields collection containing elements to be removed, <code>null</code> meaning all.
889
     * @return this.
889
     * @return this.
890
     */
890
     */
891
    public final SQLRowValues removeAll(Collection<String> fields) {
891
    public final SQLRowValues removeAll(Collection<String> fields) {
892
        return this.changeFields(fields, false);
892
        return this.changeFields(fields, false);
893
    }
893
    }
894
 
894
 
895
    public final void remove(String field) {
895
    public final void remove(String field) {
896
        // check arg & handle links
896
        // check arg & handle links
897
        this.put(field, null);
897
        this.put(field, null);
898
        // really remove
898
        // really remove
899
        assert !this.isFrozen() : "Should already be checked by put(null)";
899
        assert !this.isFrozen() : "Should already be checked by put(null)";
900
        this.values.remove(field);
900
        this.values.remove(field);
901
    }
901
    }
902
 
902
 
903
    public final void clear() {
903
    public final void clear() {
904
        this.removeAll(null);
904
        this.removeAll(null);
905
    }
905
    }
906
 
906
 
907
    public final void clearPrimaryKeys() {
907
    public final void clearPrimaryKeys() {
908
        checkFrozen();
908
        checkFrozen();
909
        this.clearPrimaryKeys(this.values);
909
        this.clearPrimaryKeys(this.values);
910
        // by definition primary keys are not foreign keys, so no need to updateLinks()
910
        // by definition primary keys are not foreign keys, so no need to updateLinks()
911
    }
911
    }
912
 
912
 
913
    private Map<String, Object> clearPrimaryKeys(final Map<String, Object> values) {
913
    private Map<String, Object> clearPrimaryKeys(final Map<String, Object> values) {
914
        return clearFields(values, this.getTable().getPrimaryKeys());
914
        return clearFields(values, this.getTable().getPrimaryKeys());
915
    }
915
    }
916
 
916
 
917
    private Map<String, Object> clearFields(final Map<String, Object> values, final Set<SQLField> fields) {
917
    private Map<String, Object> clearFields(final Map<String, Object> values, final Set<SQLField> fields) {
918
        return changeFields(values, fields, false);
918
        return changeFields(values, fields, false);
919
    }
919
    }
920
 
920
 
921
    private Map<String, Object> changeFields(final Map<String, Object> values, final Set<SQLField> fields, final boolean retain) {
921
    private Map<String, Object> changeFields(final Map<String, Object> values, final Set<SQLField> fields, final boolean retain) {
922
        final Iterator<String> iter = values.keySet().iterator();
922
        final Iterator<String> iter = values.keySet().iterator();
923
        while (iter.hasNext()) {
923
        while (iter.hasNext()) {
924
            final String fieldName = iter.next();
924
            final String fieldName = iter.next();
925
            if (fields.contains(this.getTable().getField(fieldName)) ^ retain)
925
            if (fields.contains(this.getTable().getField(fieldName)) ^ retain)
926
                iter.remove();
926
                iter.remove();
927
        }
927
        }
928
        return values;
928
        return values;
929
    }
929
    }
930
 
930
 
931
    /**
931
    /**
932
     * Change foreign and referent rows. NOTE : this doesn't change all foreign keys, only those
932
     * Change foreign and referent rows. NOTE : this doesn't change all foreign keys, only those
933
     * that contain an {@link SQLRowValues}.
933
     * that contain an {@link SQLRowValues}.
934
     * 
934
     * 
935
     * @param paths the first steps are to be changed or to be excluded from change,
935
     * @param paths the first steps are to be changed or to be excluded from change,
936
     *        <code>null</code> meaning all.
936
     *        <code>null</code> meaning all.
937
     * @param exclude <code>true</code> if steps passed to this method must be excluded from the
937
     * @param exclude <code>true</code> if steps passed to this method must be excluded from the
938
     *        change, <code>false</code> to only change steps passed to this method.
938
     *        change, <code>false</code> to only change steps passed to this method.
939
     * @param mode how the rows will be changed.
939
     * @param mode how the rows will be changed.
940
     * @return this.
940
     * @return this.
941
     */
941
     */
942
    public final SQLRowValues changeGraph(final Collection<Path> paths, final boolean exclude, ForeignCopyMode mode) {
942
    public final SQLRowValues changeGraph(final Collection<Path> paths, final boolean exclude, ForeignCopyMode mode) {
943
        if (this.getGraphSize() == 1)
943
        if (this.getGraphSize() == 1)
944
            return this;
944
            return this;
945
 
945
 
946
        final Set<SQLField> refFields;
946
        final Set<SQLField> refFields;
947
        final Set<String> foreignFields;
947
        final Set<String> foreignFields;
948
        if (paths == null) {
948
        if (paths == null) {
949
            refFields = null;
949
            refFields = null;
950
            foreignFields = null;
950
            foreignFields = null;
951
        } else {
951
        } else {
952
            refFields = new HashSet<SQLField>();
952
            refFields = new HashSet<SQLField>();
953
            foreignFields = new HashSet<String>();
953
            foreignFields = new HashSet<String>();
954
            for (final Path p : paths) {
954
            for (final Path p : paths) {
955
                if (p.getFirst() != this.getTable())
955
                if (p.getFirst() != this.getTable())
956
                    throw new IllegalArgumentException("Path not from this : " + p);
956
                    throw new IllegalArgumentException("Path not from this : " + p);
957
                if (p.length() > 0) {
957
                if (p.length() > 0) {
958
                    final Step step = p.getStep(0);
958
                    final Step step = p.getStep(0);
959
                    for (final Link l : step.getLinks()) {
959
                    for (final Link l : step.getLinks()) {
960
                        if (step.getDirection(l) == Direction.REFERENT)
960
                        if (step.getDirection(l) == Direction.REFERENT)
961
                            refFields.addAll(l.getFields());
961
                            refFields.addAll(l.getFields());
962
                        else
962
                        else
963
                            foreignFields.addAll(l.getCols());
963
                            foreignFields.addAll(l.getCols());
964
                    }
964
                    }
965
                }
965
                }
966
            }
966
            }
967
        }
967
        }
968
        changeForeigns(foreignFields, exclude, mode);
968
        changeForeigns(foreignFields, exclude, mode);
969
        changeReferents(refFields, exclude, mode);
969
        changeReferents(refFields, exclude, mode);
970
        return this;
970
        return this;
971
    }
971
    }
972
 
972
 
973
    public final void detach() {
973
    public final void detach() {
974
        // keep the most information
974
        // keep the most information
975
        this.detach(ForeignCopyMode.COPY_ID_OR_RM);
975
        this.detach(ForeignCopyMode.COPY_ID_OR_RM);
976
    }
976
    }
977
 
977
 
978
    public final void detach(final ForeignCopyMode mode) {
978
    public final void detach(final ForeignCopyMode mode) {
979
        if (mode.compareTo(ForeignCopyMode.COPY_ID_OR_ROW) >= 0)
979
        if (mode.compareTo(ForeignCopyMode.COPY_ID_OR_ROW) >= 0)
980
            throw new IllegalArgumentException("Might keep row and not detach : " + mode);
980
            throw new IllegalArgumentException("Might keep row and not detach : " + mode);
981
        this.changeGraph(null, false, mode);
981
        this.changeGraph(null, false, mode);
982
        assert this.getGraphSize() == 1;
982
        assert this.getGraphSize() == 1;
983
    }
983
    }
984
 
984
 
985
    // puts
985
    // puts
986
 
986
 
987
    public SQLRowValues put(String fieldName, Object value) {
987
    public SQLRowValues put(String fieldName, Object value) {
988
        return this.put(fieldName, value, true);
988
        return this.put(fieldName, value, true);
989
    }
989
    }
990
 
990
 
991
    SQLRowValues put(String fieldName, Object value, final boolean check) {
991
    SQLRowValues put(String fieldName, Object value, final boolean check) {
992
        return this.put(fieldName, value, check, check ? ValueOperation.CONVERT : ValueOperation.PASS);
992
        return this.put(fieldName, value, check, check ? ValueOperation.CONVERT : ValueOperation.PASS);
993
    }
993
    }
994
 
994
 
995
    SQLRowValues put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
995
    SQLRowValues put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
996
        _put(fieldName, value, checkName, checkValue);
996
        _put(fieldName, value, checkName, checkValue);
997
        // if there's no graph, there can't be any listeners
997
        // if there's no graph, there can't be any listeners
998
        final SQLRowValuesCluster graph = this.getGraph(false);
998
        final SQLRowValuesCluster graph = this.getGraph(false);
999
        if (graph != null)
999
        if (graph != null)
1000
            graph.fireModification(this, fieldName, value);
1000
            graph.fireModification(this, fieldName, value);
1001
        return this;
1001
        return this;
1002
    }
1002
    }
1003
 
1003
 
1004
    static public enum ValueOperation {
1004
    static public enum ValueOperation {
1005
        CONVERT, CHECK, PASS
1005
        CONVERT, CHECK, PASS
1006
    }
1006
    }
1007
 
1007
 
1008
    // TODO support java.time.LocalDateTime in Java 8
1008
    // TODO support java.time.LocalDateTime in Java 8
1009
    static private <T, U> U convert(final Class<T> source, final Object value, final Class<U> dest) {
1009
    static private <T, U> U convert(final Class<T> source, final Object value, final Class<U> dest) {
1010
        final ValueConvertor<T, U> conv = ValueConvertorFactory.find(source, dest);
1010
        final ValueConvertor<T, U> conv = ValueConvertorFactory.find(source, dest);
1011
        if (conv == null)
1011
        if (conv == null)
1012
            throw new IllegalArgumentException("No convertor to " + dest + " from " + source);
1012
            throw new IllegalArgumentException("No convertor to " + dest + " from " + source);
1013
        assert source == value.getClass();
1013
        assert source == value.getClass();
1014
        @SuppressWarnings("unchecked")
1014
        @SuppressWarnings("unchecked")
1015
        final T tVal = (T) value;
1015
        final T tVal = (T) value;
1016
        return conv.convert(tVal);
1016
        return conv.convert(tVal);
1017
    }
1017
    }
1018
 
1018
 
1019
    private void _put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
1019
    private void _put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
1020
        // table.contains() can take up to 35% of this method
1020
        // table.contains() can take up to 35% of this method
1021
        if (checkName && !this.getTable().contains(fieldName))
1021
        if (checkName && !this.getTable().contains(fieldName))
1022
            throw new IllegalArgumentException(fieldName + " is not in table " + this.getTable());
1022
            throw new IllegalArgumentException(fieldName + " is not in table " + this.getTable());
1023
        if (value == SQL_EMPTY_LINK) {
1023
        if (value == SQL_EMPTY_LINK) {
1024
            // keep getForeignTable since it does the check
1024
            // keep getForeignTable since it does the check
1025
            value = this.getForeignTable(fieldName).getUndefinedIDNumber();
1025
            value = this.getForeignTable(fieldName).getUndefinedIDNumber();
1026
        } else if (value != null && value != SQL_DEFAULT && checkValue != ValueOperation.PASS) {
1026
        } else if (value != null && value != SQL_DEFAULT && checkValue != ValueOperation.PASS) {
1027
            final SQLField field = this.getTable().getField(fieldName);
1027
            final SQLField field = this.getTable().getField(fieldName);
1028
            if (value instanceof SQLRowValues) {
1028
            if (value instanceof SQLRowValues) {
1029
                if (!field.isForeignKey())
1029
                if (!field.isForeignKey())
1030
                    throw new IllegalArgumentException("Since value is a SQLRowValues, expected a foreign key but got " + field);
1030
                    throw new IllegalArgumentException("Since value is a SQLRowValues, expected a foreign key but got " + field);
1031
            } else {
1031
            } else {
1032
                final Class<?> javaType = field.getType().getJavaType();
1032
                final Class<?> javaType = field.getType().getJavaType();
1033
                if (!javaType.isInstance(value)) {
1033
                if (!javaType.isInstance(value)) {
1034
                    if (checkValue == ValueOperation.CONVERT) {
1034
                    if (checkValue == ValueOperation.CONVERT) {
1035
                        value = convert(value.getClass(), value, javaType);
1035
                        value = convert(value.getClass(), value, javaType);
1036
                    } else {
1036
                    } else {
1037
                        throw new IllegalArgumentException("Wrong type for " + fieldName + ", expected " + javaType + " but got " + value.getClass());
1037
                        throw new IllegalArgumentException("Wrong type for " + fieldName + ", expected " + javaType + " but got " + value.getClass());
1038
                    }
1038
                    }
1039
                }
1039
                }
1040
            }
1040
            }
1041
        }
1041
        }
1042
        checkFrozen();
1042
        checkFrozen();
1043
        this.updateLinks(fieldName, this.values.put(fieldName, value), value);
1043
        this.updateLinks(fieldName, this.values.put(fieldName, value), value);
1044
    }
1044
    }
1045
 
1045
 
1046
    public SQLRowValues put(String fieldName, int value) {
1046
    public SQLRowValues put(String fieldName, int value) {
1047
        return this.put(fieldName, Integer.valueOf(value));
1047
        return this.put(fieldName, Integer.valueOf(value));
1048
    }
1048
    }
1049
 
1049
 
1050
    public SQLRowValues putDefault(String fieldName) {
1050
    public SQLRowValues putDefault(String fieldName) {
1051
        return this.put(fieldName, SQL_DEFAULT);
1051
        return this.put(fieldName, SQL_DEFAULT);
1052
    }
1052
    }
1053
 
1053
 
1054
    /**
1054
    /**
1055
     * To empty a foreign key.
1055
     * To empty a foreign key.
1056
     * 
1056
     * 
1057
     * @param fieldName the name of the foreign key to empty.
1057
     * @param fieldName the name of the foreign key to empty.
1058
     * @return this.
1058
     * @return this.
1059
     */
1059
     */
1060
    public SQLRowValues putEmptyLink(String fieldName) {
1060
    public SQLRowValues putEmptyLink(String fieldName) {
1061
        return this.put(fieldName, SQL_EMPTY_LINK);
1061
        return this.put(fieldName, SQL_EMPTY_LINK);
1062
    }
1062
    }
1063
 
1063
 
1064
    /**
1064
    /**
1065
     * Set a new {@link SQLRowValues} as the value of <code>fieldName</code>. ATTN contrary to many
1065
     * Set a new {@link SQLRowValues} as the value of <code>fieldName</code>. ATTN contrary to many
1066
     * methods this one do not return <code>this</code>.
1066
     * methods this one do not return <code>this</code>.
1067
     * 
1067
     * 
1068
     * @param fieldName the name of a foreign field.
1068
     * @param fieldName the name of a foreign field.
1069
     * @return the newly created values.
1069
     * @return the newly created values.
1070
     * @throws IllegalArgumentException if <code>fieldName</code> is not a foreign field.
1070
     * @throws IllegalArgumentException if <code>fieldName</code> is not a foreign field.
1071
     */
1071
     */
1072
    public final SQLRowValues putRowValues(String fieldName) throws IllegalArgumentException {
1072
    public final SQLRowValues putRowValues(String fieldName) throws IllegalArgumentException {
1073
        // getForeignTable checks
1073
        // getForeignTable checks
1074
        final SQLRowValues vals = new SQLRowValues(this.getForeignTable(fieldName));
1074
        final SQLRowValues vals = new SQLRowValues(this.getForeignTable(fieldName));
1075
        this.put(fieldName, vals);
1075
        this.put(fieldName, vals);
1076
        return vals;
1076
        return vals;
1077
    }
1077
    }
1078
 
1078
 
1079
    public final SQLRowValues putRowValues(final Path p, final boolean createPath) throws IllegalArgumentException {
1079
    public final SQLRowValues putRowValues(final Path p, final boolean createPath) throws IllegalArgumentException {
1080
        return this.put(p, createPath, null);
1080
        return this.put(p, createPath, null);
1081
    }
1081
    }
1082
 
1082
 
1083
    /**
1083
    /**
1084
     * Create or follow the passed path and put the passed row at the end.
1084
     * Create or follow the passed path and put the passed row at the end.
1085
     * 
1085
     * 
1086
     * @param p the {@link Path#isSingleLink() single link} path.
1086
     * @param p the {@link Path#isSingleLink() single link} path.
1087
     * @param createPath <code>true</code> if new rows must {@link #createPathToOne(Path) always be
1087
     * @param createPath <code>true</code> if new rows must {@link #createPathToOne(Path) always be
1088
     *        created}, <code>false</code> if existing rows can be {@link #assurePath(Path) used}.
1088
     *        created}, <code>false</code> if existing rows can be {@link #assurePath(Path) used}.
1089
     * @param vals the row to {@link #put(Step, SQLRowValues) put}, <code>null</code> to create a
1089
     * @param vals the row to {@link #put(Step, SQLRowValues) put}, <code>null</code> to create a
1090
     *        new one.
1090
     *        new one.
1091
     * @return the row that was added.
1091
     * @return the row that was added.
1092
     * @throws IllegalArgumentException if the path is invalid.
1092
     * @throws IllegalArgumentException if the path is invalid.
1093
     */
1093
     */
1094
    public final SQLRowValues put(final Path p, final boolean createPath, final SQLRowValues vals) throws IllegalArgumentException {
1094
    public final SQLRowValues put(final Path p, final boolean createPath, final SQLRowValues vals) throws IllegalArgumentException {
1095
        if (p.length() == 0)
1095
        if (p.length() == 0)
1096
            throw new IllegalArgumentException("Empty path");
1096
            throw new IllegalArgumentException("Empty path");
1097
        if (!p.isSingleLink())
1097
        if (!p.isSingleLink())
1098
            throw new IllegalArgumentException("Multi-link path " + p);
1098
            throw new IllegalArgumentException("Multi-link path " + p);
1099
        // checks first table
1099
        // checks first table
1100
        final SQLRowValues beforeLast = createPath ? this.createPathToOne(p.minusLast()) : this.assurePath(p.minusLast());
1100
        final SQLRowValues beforeLast = createPath ? this.createPathToOne(p.minusLast()) : this.assurePath(p.minusLast());
1101
        // checks last table
1101
        // checks last table
1102
        return beforeLast.put(p.getStep(-1), vals);
1102
        return beforeLast.put(p.getStep(-1), vals);
1103
    }
1103
    }
1104
 
1104
 
1105
    public final SQLRowValues putRowValues(final Step step) throws IllegalArgumentException {
1105
    public final SQLRowValues putRowValues(final Step step) throws IllegalArgumentException {
1106
        return this.put(step, null);
1106
        return this.put(step, null);
1107
    }
1107
    }
1108
 
1108
 
1109
    /**
1109
    /**
1110
     * Add all links of the passed step from this to the passed row.
1110
     * Add all links of the passed step from this to the passed row.
1111
     * 
1111
     * 
1112
     * @param step a step.
1112
     * @param step a step.
1113
     * @param vals a row, <code>null</code> to create a new one.
1113
     * @param vals a row, <code>null</code> to create a new one.
1114
     * @return the row that was linked.
1114
     * @return the row that was linked.
1115
     * @throws IllegalArgumentException if the step is not from <code>this</code> to
1115
     * @throws IllegalArgumentException if the step is not from <code>this</code> to
1116
     *         <code>vals</code>.
1116
     *         <code>vals</code>.
1117
     */
1117
     */
1118
    public final SQLRowValues put(final Step step, SQLRowValues vals) throws IllegalArgumentException {
1118
    public final SQLRowValues put(final Step step, SQLRowValues vals) throws IllegalArgumentException {
1119
        if (!step.getFrom().equals(this.getTable()))
1119
        if (!step.getFrom().equals(this.getTable()))
1120
            throw new IllegalArgumentException(step + " not from " + this);
1120
            throw new IllegalArgumentException(step + " not from " + this);
1121
        if (vals == null)
1121
        if (vals == null)
1122
            vals = new SQLRowValues(step.getTo());
1122
            vals = new SQLRowValues(step.getTo());
1123
        else if (!step.getTo().equals(vals.getTable()))
1123
        else if (!step.getTo().equals(vals.getTable()))
1124
            throw new IllegalArgumentException(step + " not to " + vals);
1124
            throw new IllegalArgumentException(step + " not to " + vals);
1125
        for (final Link l : step.getLinks()) {
1125
        for (final Link l : step.getLinks()) {
1126
            final Direction dir = step.getDirection(l);
1126
            final Direction dir = step.getDirection(l);
1127
            if (dir == Direction.REFERENT) {
1127
            if (dir == Direction.REFERENT) {
1128
                vals._putForeign(l, this);
1128
                vals._putForeign(l, this);
1129
            } else {
1129
            } else {
1130
                assert dir == Direction.FOREIGN;
1130
                assert dir == Direction.FOREIGN;
1131
                this._putForeign(l, vals);
1131
                this._putForeign(l, vals);
1132
            }
1132
            }
1133
        }
1133
        }
1134
        return vals;
1134
        return vals;
1135
    }
1135
    }
1136
 
1136
 
1137
    private final SQLRowValues _putForeign(final Link l, SQLRowValues vals) {
1137
    private final SQLRowValues _putForeign(final Link l, SQLRowValues vals) {
1138
        this.put(l.getSingleField().getName(), vals);
1138
        this.put(l.getSingleField().getName(), vals);
1139
        return vals;
1139
        return vals;
1140
    }
1140
    }
1141
 
1141
 
1142
    public final SQLRowValues putForeign(final Link l, SQLRowValues vals) throws IllegalArgumentException {
1142
    public final SQLRowValues putForeign(final Link l, SQLRowValues vals) throws IllegalArgumentException {
1143
        if (!l.getSource().equals(this.getTable()))
1143
        if (!l.getSource().equals(this.getTable()))
1144
            throw new IllegalArgumentException(l + " not from " + this);
1144
            throw new IllegalArgumentException(l + " not from " + this);
1145
        return _putForeign(l, vals);
1145
        return _putForeign(l, vals);
1146
    }
1146
    }
1147
 
1147
 
1148
    public final void remove(final Step step) {
1148
    public final void remove(final Step step) {
1149
        for (final Link l : step.getLinks()) {
1149
        for (final Link l : step.getLinks()) {
1150
            if (step.getDirection(l) == Direction.FOREIGN)
1150
            if (step.getDirection(l) == Direction.FOREIGN)
1151
                this.removeForeignKey(l);
1151
                this.removeForeignKey(l);
1152
            else
1152
            else
1153
                this.removeReferentFields(l.getFields());
1153
                this.removeReferentFields(l.getFields());
1154
        }
1154
        }
1155
    }
1155
    }
1156
 
1156
 
1157
    /**
1157
    /**
1158
     * Safely set the passed field to the value of the primary key of <code>r</code>.
1158
     * Safely set the passed field to the value of the primary key of <code>r</code>.
1159
     * 
1159
     * 
1160
     * @param fk the field to change.
1160
     * @param fk the field to change.
1161
     * @param r the row, <code>null</code> meaning {@link #SQL_EMPTY_LINK empty} foreign key.
1161
     * @param r the row, <code>null</code> meaning {@link #SQL_EMPTY_LINK empty} foreign key.
1162
     * @return this.
1162
     * @return this.
1163
     * @throws IllegalArgumentException if <code>fk</code> doesn't point to the table of
1163
     * @throws IllegalArgumentException if <code>fk</code> doesn't point to the table of
1164
     *         <code>r</code>.
1164
     *         <code>r</code>.
1165
     */
1165
     */
1166
    public final SQLRowValues putForeignID(final String fk, final SQLRowAccessor r) throws IllegalArgumentException {
1166
    public final SQLRowValues putForeignID(final String fk, final SQLRowAccessor r) throws IllegalArgumentException {
1167
        return this.putForeignKey(Collections.singletonList(fk), r);
1167
        return this.putForeignKey(Collections.singletonList(fk), r);
1168
    }
1168
    }
1169
 
1169
 
1170
    public final SQLRowValues putForeignKey(final List<String> cols, final SQLRowAccessor r) throws IllegalArgumentException {
1170
    public final SQLRowValues putForeignKey(final List<String> cols, final SQLRowAccessor r) throws IllegalArgumentException {
1171
        // first check that cols are indeed a foreign key
1171
        // first check that cols are indeed a foreign key
1172
        return this.putForeignKey(this.getForeignLink(cols), r);
1172
        return this.putForeignKey(this.getForeignLink(cols), r);
1173
    }
1173
    }
1174
 
1174
 
1175
    public final SQLRowValues putForeignKey(final Link foreignLink, final SQLRowAccessor r) throws IllegalArgumentException {
1175
    public final SQLRowValues putForeignKey(final Link foreignLink, final SQLRowAccessor r) throws IllegalArgumentException {
1176
        checkForeignLink(foreignLink);
1176
        checkForeignLink(foreignLink);
1177
        final List<String> cols = foreignLink.getCols();
1177
        final List<String> cols = foreignLink.getCols();
1178
        if (r == null) {
1178
        if (r == null) {
1179
            if (cols.size() == 1) {
1179
            if (cols.size() == 1) {
1180
                return this.putEmptyLink(cols.get(0));
1180
                return this.putEmptyLink(cols.get(0));
1181
            } else {
1181
            } else {
1182
                return this.putNulls(cols);
1182
                return this.putNulls(cols);
1183
            }
1183
            }
1184
        } else {
1184
        } else {
1185
            checkSameTable(r, foreignLink.getTarget());
1185
            checkSameTable(r, foreignLink.getTarget());
1186
            final Iterator<String> iter = cols.iterator();
1186
            final Iterator<String> iter = cols.iterator();
1187
            final Iterator<String> refIter = foreignLink.getRefCols().iterator();
1187
            final Iterator<String> refIter = foreignLink.getRefCols().iterator();
1188
            while (iter.hasNext()) {
1188
            while (iter.hasNext()) {
1189
                final String col = iter.next();
1189
                final String col = iter.next();
1190
                final String refCol = refIter.next();
1190
                final String refCol = refIter.next();
1191
                this.put(col, r.getObject(refCol));
1191
                this.put(col, r.getObject(refCol));
1192
            }
1192
            }
1193
            return this;
1193
            return this;
1194
        }
1194
        }
1195
    }
1195
    }
1196
 
1196
 
1197
    private void checkForeignLink(final Link foreignLink) {
1197
    private void checkForeignLink(final Link foreignLink) {
1198
        if (foreignLink.getSource() != this.getTable())
1198
        if (foreignLink.getSource() != this.getTable())
1199
            throw new IllegalArgumentException("Link not from " + this.getTable() + " : " + foreignLink);
1199
            throw new IllegalArgumentException("Link not from " + this.getTable() + " : " + foreignLink);
1200
    }
1200
    }
1201
 
1201
 
1202
    public final void removeForeignKey(final Link foreignLink) {
1202
    public final void removeForeignKey(final Link foreignLink) {
1203
        checkForeignLink(foreignLink);
1203
        checkForeignLink(foreignLink);
1204
        this.removeAll(foreignLink.getCols());
1204
        this.removeAll(foreignLink.getCols());
1205
    }
1205
    }
1206
 
1206
 
1207
    private void checkSameTable(final SQLRowAccessor r, final SQLTable t) {
1207
    private void checkSameTable(final SQLRowAccessor r, final SQLTable t) {
1208
        if (r.getTable() != t)
1208
        if (r.getTable() != t)
1209
            throw new IllegalArgumentException("Table mismatch : " + r.getTable().getSQLName() + " != " + t.getSQLName());
1209
            throw new IllegalArgumentException("Table mismatch : " + r.getTable().getSQLName() + " != " + t.getSQLName());
1210
    }
1210
    }
1211
 
1211
 
1212
    /**
1212
    /**
1213
     * Set the order of this row so that it will be just after/before <code>r</code>. NOTE: this may
1213
     * Set the order of this row so that it will be just after/before <code>r</code>. NOTE: this may
1214
     * reorder the table to make room.
1214
     * reorder the table to make room.
1215
     * 
1215
     * 
1216
     * @param r the row to be next to.
1216
     * @param r the row to be next to.
1217
     * @param after whether this row will be before or after <code>r</code>.
1217
     * @param after whether this row will be before or after <code>r</code>.
1218
     * @return this.
1218
     * @return this.
1219
     */
1219
     */
1220
    public SQLRowValues setOrder(SQLRow r, boolean after) {
1220
    public SQLRowValues setOrder(SQLRow r, boolean after) {
1221
        return this.setOrder(r, after, ReOrder.DISTANCE.movePointRight(2).intValue(), 0);
1221
        setOrder(Collections.singletonList(this), r, after);
-
 
1222
        return this;
1222
    }
1223
    }
1223
 
1224
 
1224
    private SQLRowValues setOrder(SQLRow r, boolean after, int nbToReOrder, int nbReOrdered) {
1225
    public static void setOrder(final List<SQLRowValues> values, final SQLRow r, boolean after) {
1225
        final BigDecimal freeOrder = r.getOrder(after);
1226
        final int valuesCount = values.size();
1226
        final String orderName = this.getTable().getOrderField().getName();
-
 
1227
        if (freeOrder != null)
-
 
1228
            return this.put(orderName, freeOrder);
1227
        final List<BigDecimal> orders;
1229
        else if (nbReOrdered > r.getTable().getRowCount()) {
-
 
1230
            throw new IllegalStateException("cannot reorder " + r.getTable().getSQLName());
-
 
1231
        } else {
-
 
1232
            // make room
-
 
1233
            try {
1228
        try {
1234
                ReOrder.create(this.getTable(), r.getOrder().intValue() - (nbToReOrder / 2), nbToReOrder).exec();
1229
            orders = ReOrder.getFreeOrderValuesFor(valuesCount, after, r).get0();
1235
            } catch (SQLException e) {
1230
        } catch (SQLException e) {
1236
                throw ExceptionUtils.createExn(IllegalStateException.class, "reorder failed for " + this.getTable() + " at " + r.getOrder(), e);
1231
            throw ExceptionUtils.createExn(IllegalStateException.class, "reorder failed for " + r.getTable() + " at " + r.getOrder(), e);
1237
            }
1232
        }
-
 
1233
        final String orderName = r.getTable().getOrderField().getName();
1238
            r.fetchValues();
1234
        for (int i = 0; i < valuesCount; i++) {
1239
            return this.setOrder(r, after, nbToReOrder * 10, nbToReOrder);
1235
            values.get(i).put(orderName, orders.get(i));
1240
        }
1236
        }
1241
    }
1237
    }
1242
 
1238
 
1243
    public final SQLRowValues setID(Number id) {
1239
    public final SQLRowValues setID(Number id) {
1244
        // faster
1240
        // faster
1245
        return this.setID(id, false);
1241
        return this.setID(id, false);
1246
    }
1242
    }
1247
 
1243
 
1248
    /***
1244
    /***
1249
     * Set the {@link #getIDNumber() ID} of this row. Convert is useful to compare a row created in
1245
     * Set the {@link #getIDNumber() ID} of this row. Convert is useful to compare a row created in
1250
     * Java and a row returned from the database, since in Java the ID will be an integer whereas
1246
     * Java and a row returned from the database, since in Java the ID will be an integer whereas
1251
     * the DB can return anything.
1247
     * the DB can return anything.
1252
     * 
1248
     * 
1253
     * @param id the new ID.
1249
     * @param id the new ID.
1254
     * @param convert <code>true</code> if <code>id</code> should be converted to type of the
1250
     * @param convert <code>true</code> if <code>id</code> should be converted to type of the
1255
     *        primary key.
1251
     *        primary key.
1256
     * @return this.
1252
     * @return this.
1257
     */
1253
     */
1258
    public final SQLRowValues setID(Number id, final boolean convert) {
1254
    public final SQLRowValues setID(Number id, final boolean convert) {
1259
        final SQLField key = this.getTable().getKey();
1255
        final SQLField key = this.getTable().getKey();
1260
        if (convert)
1256
        if (convert)
1261
            id = NumberConvertor.convert(id, key.getType().getJavaType().asSubclass(Number.class));
1257
            id = NumberConvertor.convert(id, key.getType().getJavaType().asSubclass(Number.class));
1262
 
1258
 
1263
        return this.put(key.getName(), id);
1259
        return this.put(key.getName(), id);
1264
    }
1260
    }
1265
 
1261
 
1266
    public final SQLRowValues setPrimaryKey(final SQLRowAccessor r) {
1262
    public final SQLRowValues setPrimaryKey(final SQLRowAccessor r) {
1267
        if (r == null) {
1263
        if (r == null) {
1268
            return this.putNulls(this.getTable().getPKsNames(), false);
1264
            return this.putNulls(this.getTable().getPKsNames(), false);
1269
        } else {
1265
        } else {
1270
            checkSameTable(r, this.getTable());
1266
            checkSameTable(r, this.getTable());
1271
            // required since we don't want only half of the fields of the primary key
1267
            // required since we don't want only half of the fields of the primary key
1272
            return this.loadAll(r.getAbsolutelyAll(), this.getTable().getPKsNames(new HashSet<String>()), true, FillMode.OVERWRITE);
1268
            return this.loadAll(r.getAbsolutelyAll(), this.getTable().getPKsNames(new HashSet<String>()), true, FillMode.OVERWRITE);
1273
        }
1269
        }
1274
    }
1270
    }
1275
 
1271
 
1276
    public final SQLRowValues setAll(Map<String, ?> m) {
1272
    public final SQLRowValues setAll(Map<String, ?> m) {
1277
        return this.loadAll(m, FillMode.CLEAR);
1273
        return this.loadAll(m, FillMode.CLEAR);
1278
    }
1274
    }
1279
 
1275
 
1280
    public final SQLRowValues putAll(Map<String, ?> m) {
1276
    public final SQLRowValues putAll(Map<String, ?> m) {
1281
        return this.putAll(m, null);
1277
        return this.putAll(m, null);
1282
    }
1278
    }
1283
 
1279
 
1284
    public final SQLRowValues putAll(Map<String, ?> m, final Collection<String> keys) {
1280
    public final SQLRowValues putAll(Map<String, ?> m, final Collection<String> keys) {
1285
        return this.putAll(m, keys, FillMode.OVERWRITE);
1281
        return this.putAll(m, keys, FillMode.OVERWRITE);
1286
    }
1282
    }
1287
 
1283
 
1288
    final SQLRowValues putAll(Map<String, ?> m, final Collection<String> keys, final FillMode fillMode) {
1284
    final SQLRowValues putAll(Map<String, ?> m, final Collection<String> keys, final FillMode fillMode) {
1289
        return this.loadAll(m, keys, false, fillMode);
1285
        return this.loadAll(m, keys, false, fillMode);
1290
    }
1286
    }
1291
 
1287
 
1292
    static enum FillMode {
1288
    static enum FillMode {
1293
        CLEAR, OVERWRITE, DONT_OVERWRITE
1289
        CLEAR, OVERWRITE, DONT_OVERWRITE
1294
    }
1290
    }
1295
 
1291
 
1296
    private final SQLRowValues loadAll(Map<String, ?> m, final FillMode fillMode) {
1292
    private final SQLRowValues loadAll(Map<String, ?> m, final FillMode fillMode) {
1297
        return this.loadAll(m, null, false, fillMode);
1293
        return this.loadAll(m, null, false, fillMode);
1298
    }
1294
    }
1299
 
1295
 
1300
    private final SQLRowValues loadAll(Map<String, ?> m, final Collection<String> keys, final boolean required, final FillMode fillMode) {
1296
    private final SQLRowValues loadAll(Map<String, ?> m, final Collection<String> keys, final boolean required, final FillMode fillMode) {
1301
        final Collection<String> keySet = keys == null ? m.keySet() : keys;
1297
        final Collection<String> keySet = keys == null ? m.keySet() : keys;
1302
        if (!this.getTable().getFieldsName().containsAll(keySet)) {
1298
        if (!this.getTable().getFieldsName().containsAll(keySet)) {
1303
            final List<String> l1 = new ArrayList<String>(keySet);
1299
            final List<String> l1 = new ArrayList<String>(keySet);
1304
            final List<String> l2 = new ArrayList<String>(this.getTable().getFieldsName());
1300
            final List<String> l2 = new ArrayList<String>(this.getTable().getFieldsName());
1305
            Collections.sort(l1);
1301
            Collections.sort(l1);
1306
            Collections.sort(l2);
1302
            Collections.sort(l2);
1307
            throw new IllegalArgumentException("fields " + l1 + " are not a subset of " + this.getTable() + " : " + l2);
1303
            throw new IllegalArgumentException("fields " + l1 + " are not a subset of " + this.getTable() + " : " + l2);
1308
        }
1304
        }
1309
        // copy before passing to fire()
1305
        // copy before passing to fire()
1310
        final Map<String, Object> toLoad = new LinkedHashMap<String, Object>(m);
1306
        final Map<String, Object> toLoad = new LinkedHashMap<String, Object>(m);
1311
        if (keys != null) {
1307
        if (keys != null) {
1312
            if (required && !m.keySet().containsAll(keys))
1308
            if (required && !m.keySet().containsAll(keys))
1313
                throw new IllegalArgumentException("Not all are keys " + keys + " are in " + m);
1309
                throw new IllegalArgumentException("Not all are keys " + keys + " are in " + m);
1314
            toLoad.keySet().retainAll(keys);
1310
            toLoad.keySet().retainAll(keys);
1315
        }
1311
        }
1316
        if (fillMode == FillMode.CLEAR) {
1312
        if (fillMode == FillMode.CLEAR) {
1317
            clear();
1313
            clear();
1318
        } else if (fillMode == FillMode.DONT_OVERWRITE) {
1314
        } else if (fillMode == FillMode.DONT_OVERWRITE) {
1319
            toLoad.keySet().removeAll(this.getFields());
1315
            toLoad.keySet().removeAll(this.getFields());
1320
        }
1316
        }
1321
        for (final Map.Entry<String, ?> e : toLoad.entrySet()) {
1317
        for (final Map.Entry<String, ?> e : toLoad.entrySet()) {
1322
            // names are checked at the start
1318
            // names are checked at the start
1323
            this._put(e.getKey(), e.getValue(), false, ValueOperation.CONVERT);
1319
            this._put(e.getKey(), e.getValue(), false, ValueOperation.CONVERT);
1324
        }
1320
        }
1325
        // if there's no graph, there can't be any listeners
1321
        // if there's no graph, there can't be any listeners
1326
        final SQLRowValuesCluster graph = this.getGraph(false);
1322
        final SQLRowValuesCluster graph = this.getGraph(false);
1327
        if (graph != null)
1323
        if (graph != null)
1328
            graph.fireModification(this, toLoad);
1324
            graph.fireModification(this, toLoad);
1329
        return this;
1325
        return this;
1330
    }
1326
    }
1331
 
1327
 
1332
    public final SQLRowValues putNulls(String... fields) {
1328
    public final SQLRowValues putNulls(String... fields) {
1333
        return this.putNulls(Arrays.asList(fields));
1329
        return this.putNulls(Arrays.asList(fields));
1334
    }
1330
    }
1335
 
1331
 
1336
    public final SQLRowValues putNulls(Collection<String> fields) {
1332
    public final SQLRowValues putNulls(Collection<String> fields) {
1337
        return this.putNulls(fields, false);
1333
        return this.putNulls(fields, false);
1338
    }
1334
    }
1339
 
1335
 
1340
    /**
1336
    /**
1341
     * Set the passed fields to <code>null</code>.
1337
     * Set the passed fields to <code>null</code>.
1342
     * 
1338
     * 
1343
     * @param fields which fields to put.
1339
     * @param fields which fields to put.
1344
     * @param ignoreInexistant <code>true</code> if non existing field should be ignored,
1340
     * @param ignoreInexistant <code>true</code> if non existing field should be ignored,
1345
     *        <code>false</code> will throw an exception if a field doesn't exist.
1341
     *        <code>false</code> will throw an exception if a field doesn't exist.
1346
     * @return this.
1342
     * @return this.
1347
     */
1343
     */
1348
    public final SQLRowValues putNulls(Collection<String> fields, final boolean ignoreInexistant) {
1344
    public final SQLRowValues putNulls(Collection<String> fields, final boolean ignoreInexistant) {
1349
        return this.fill(fields, null, ignoreInexistant, false);
1345
        return this.fill(fields, null, ignoreInexistant, false);
1350
    }
1346
    }
1351
 
1347
 
1352
    /**
1348
    /**
1353
     * Put the same value in all the passed fields.
1349
     * Put the same value in all the passed fields.
1354
     * 
1350
     * 
1355
     * @param fields fields to change, <code>null</code> meaning all the fields of the table.
1351
     * @param fields fields to change, <code>null</code> meaning all the fields of the table.
1356
     * @param val the value to put, can be <code>null</code>.
1352
     * @param val the value to put, can be <code>null</code>.
1357
     * @param ignoreInexistant if <code>fields</code> that aren't in the table should be ignored
1353
     * @param ignoreInexistant if <code>fields</code> that aren't in the table should be ignored
1358
     *        (not used if <code>fields</code> is <code>null</code>).
1354
     *        (not used if <code>fields</code> is <code>null</code>).
1359
     * @param ignoreExisting <code>true</code> if no value should be overwritten.
1355
     * @param ignoreExisting <code>true</code> if no value should be overwritten.
1360
     * @return this.
1356
     * @return this.
1361
     * @throws IllegalArgumentException if <code>!ignoreInexistant</code> and some fields aren't in
1357
     * @throws IllegalArgumentException if <code>!ignoreInexistant</code> and some fields aren't in
1362
     *         the table.
1358
     *         the table.
1363
     */
1359
     */
1364
    public final SQLRowValues fill(final Collection<String> fields, final Object val, final boolean ignoreInexistant, final boolean ignoreExisting) throws IllegalArgumentException {
1360
    public final SQLRowValues fill(final Collection<String> fields, final Object val, final boolean ignoreInexistant, final boolean ignoreExisting) throws IllegalArgumentException {
1365
        final Set<String> tableFieldsNames = getTable().getFieldsName();
1361
        final Set<String> tableFieldsNames = getTable().getFieldsName();
1366
        // keep order
1362
        // keep order
1367
        final Set<String> actualFields = fields == null ? tableFieldsNames : new LinkedHashSet<String>(fields);
1363
        final Set<String> actualFields = fields == null ? tableFieldsNames : new LinkedHashSet<String>(fields);
1368
        final Map<String, Object> m = createLinkedHashMap(actualFields.size());
1364
        final Map<String, Object> m = createLinkedHashMap(actualFields.size());
1369
        for (final String fn : actualFields) {
1365
        for (final String fn : actualFields) {
1370
            if (fields == null || !ignoreInexistant || tableFieldsNames.contains(fn))
1366
            if (fields == null || !ignoreInexistant || tableFieldsNames.contains(fn))
1371
                m.put(fn, val);
1367
                m.put(fn, val);
1372
        }
1368
        }
1373
        return this.loadAll(m, ignoreExisting ? FillMode.DONT_OVERWRITE : FillMode.OVERWRITE);
1369
        return this.loadAll(m, ignoreExisting ? FillMode.DONT_OVERWRITE : FillMode.OVERWRITE);
1374
    }
1370
    }
1375
 
1371
 
1376
    /**
1372
    /**
1377
     * Fill all fields with the passed value.
1373
     * Fill all fields with the passed value.
1378
     * 
1374
     * 
1379
     * @param val the value to put, can be <code>null</code>.
1375
     * @param val the value to put, can be <code>null</code>.
1380
     * @param overwrite <code>true</code> if existing values must be replaced.
1376
     * @param overwrite <code>true</code> if existing values must be replaced.
1381
     * @return this.
1377
     * @return this.
1382
     */
1378
     */
1383
    public final SQLRowValues fillWith(final Object val, final boolean overwrite) {
1379
    public final SQLRowValues fillWith(final Object val, final boolean overwrite) {
1384
        return this.fill(null, val, false, !overwrite);
1380
        return this.fill(null, val, false, !overwrite);
1385
    }
1381
    }
1386
 
1382
 
1387
    /**
1383
    /**
1388
     * Set all the fields (including primary and foreign keys) of this row to <code>null</code>.
1384
     * Set all the fields (including primary and foreign keys) of this row to <code>null</code>.
1389
     * 
1385
     * 
1390
     * @return this.
1386
     * @return this.
1391
     */
1387
     */
1392
    public final SQLRowValues setAllToNull() {
1388
    public final SQLRowValues setAllToNull() {
1393
        return this.fillWith(null, true);
1389
        return this.fillWith(null, true);
1394
    }
1390
    }
1395
 
1391
 
1396
    // listener
1392
    // listener
1397
 
1393
 
1398
    public class ReferentChangeEvent extends EventObject {
1394
    public class ReferentChangeEvent extends EventObject {
1399
 
1395
 
1400
        private final SQLField f;
1396
        private final SQLField f;
1401
        private final SQLRowValues vals;
1397
        private final SQLRowValues vals;
1402
        private final boolean put;
1398
        private final boolean put;
1403
 
1399
 
1404
        public ReferentChangeEvent(SQLField f, boolean put, SQLRowValues vals) {
1400
        public ReferentChangeEvent(SQLField f, boolean put, SQLRowValues vals) {
1405
            super(SQLRowValues.this);
1401
            super(SQLRowValues.this);
1406
            assert f != null && f.getDBSystemRoot().getGraph().getForeignTable(f) == getSource().getTable() && f.getTable() == vals.getTable();
1402
            assert f != null && f.getDBSystemRoot().getGraph().getForeignTable(f) == getSource().getTable() && f.getTable() == vals.getTable();
1407
            this.f = f;
1403
            this.f = f;
1408
            this.put = put;
1404
            this.put = put;
1409
            this.vals = vals;
1405
            this.vals = vals;
1410
        }
1406
        }
1411
 
1407
 
1412
        // eg SITE[2]
1408
        // eg SITE[2]
1413
        @Override
1409
        @Override
1414
        public SQLRowValues getSource() {
1410
        public SQLRowValues getSource() {
1415
            return (SQLRowValues) super.getSource();
1411
            return (SQLRowValues) super.getSource();
1416
        }
1412
        }
1417
 
1413
 
1418
        // eg ID_SITE
1414
        // eg ID_SITE
1419
        public final SQLField getField() {
1415
        public final SQLField getField() {
1420
            return this.f;
1416
            return this.f;
1421
        }
1417
        }
1422
 
1418
 
1423
        // eg BATIMENT[3]
1419
        // eg BATIMENT[3]
1424
        public final SQLRowValues getChangedReferent() {
1420
        public final SQLRowValues getChangedReferent() {
1425
            return this.vals;
1421
            return this.vals;
1426
        }
1422
        }
1427
 
1423
 
1428
        // true if getChangedReferent() is a new referent of getSource(), false if it has been
1424
        // true if getChangedReferent() is a new referent of getSource(), false if it has been
1429
        // removed from getSource()
1425
        // removed from getSource()
1430
        public final boolean isAddition() {
1426
        public final boolean isAddition() {
1431
            return this.put;
1427
            return this.put;
1432
        }
1428
        }
1433
 
1429
 
1434
        public final boolean isRemoval() {
1430
        public final boolean isRemoval() {
1435
            return !this.isAddition();
1431
            return !this.isAddition();
1436
        }
1432
        }
1437
 
1433
 
1438
        @Override
1434
        @Override
1439
        public String toString() {
1435
        public String toString() {
1440
            return this.getClass().getSimpleName() + (this.isAddition() ? " added" : " removed") + " on field " + getField() + " from " + this.getSource().asRow() + " : " + getChangedReferent();
1436
            return this.getClass().getSimpleName() + (this.isAddition() ? " added" : " removed") + " on field " + getField() + " from " + this.getSource().asRow() + " : " + getChangedReferent();
1441
        }
1437
        }
1442
    }
1438
    }
1443
 
1439
 
1444
    public static interface ReferentChangeListener extends EventListener {
1440
    public static interface ReferentChangeListener extends EventListener {
1445
 
1441
 
1446
        void referentChange(ReferentChangeEvent evt);
1442
        void referentChange(ReferentChangeEvent evt);
1447
 
1443
 
1448
    }
1444
    }
1449
 
1445
 
1450
    /**
1446
    /**
1451
     * Adds a listener to referent rows.
1447
     * Adds a listener to referent rows.
1452
     * 
1448
     * 
1453
     * @param field the referent field to listen to, <code>null</code> meaning all.
1449
     * @param field the referent field to listen to, <code>null</code> meaning all.
1454
     * @param l the listener.
1450
     * @param l the listener.
1455
     */
1451
     */
1456
    public final void addReferentListener(SQLField field, ReferentChangeListener l) {
1452
    public final void addReferentListener(SQLField field, ReferentChangeListener l) {
1457
        if (this.referentsListener == null)
1453
        if (this.referentsListener == null)
1458
            this.referentsListener = new ListMap<SQLField, ReferentChangeListener>();
1454
            this.referentsListener = new ListMap<SQLField, ReferentChangeListener>();
1459
        this.referentsListener.add(field, l);
1455
        this.referentsListener.add(field, l);
1460
    }
1456
    }
1461
 
1457
 
1462
    public final void removeReferentListener(SQLField field, ReferentChangeListener l) {
1458
    public final void removeReferentListener(SQLField field, ReferentChangeListener l) {
1463
        if (this.referentsListener != null) {
1459
        if (this.referentsListener != null) {
1464
            this.referentsListener.removeOne(field, l);
1460
            this.referentsListener.removeOne(field, l);
1465
        }
1461
        }
1466
    }
1462
    }
1467
 
1463
 
1468
    private void fireRefChange(SQLField f, boolean put, SQLRowValues vals) {
1464
    private void fireRefChange(SQLField f, boolean put, SQLRowValues vals) {
1469
        // only create event if needed
1465
        // only create event if needed
1470
        if (this.referentsListener != null || this.getGraph().referentFireNeeded(put)) {
1466
        if (this.referentsListener != null || this.getGraph().referentFireNeeded(put)) {
1471
            final ReferentChangeEvent evt = new ReferentChangeEvent(f, put, vals);
1467
            final ReferentChangeEvent evt = new ReferentChangeEvent(f, put, vals);
1472
            if (this.referentsListener != null) {
1468
            if (this.referentsListener != null) {
1473
                for (final ReferentChangeListener l : this.referentsListener.getNonNull(f))
1469
                for (final ReferentChangeListener l : this.referentsListener.getNonNull(f))
1474
                    l.referentChange(evt);
1470
                    l.referentChange(evt);
1475
                for (final ReferentChangeListener l : this.referentsListener.getNonNull(null))
1471
                for (final ReferentChangeListener l : this.referentsListener.getNonNull(null))
1476
                    l.referentChange(evt);
1472
                    l.referentChange(evt);
1477
            }
1473
            }
1478
            // no need to avoid creating graph, as this is called when the graph change
1474
            // no need to avoid creating graph, as this is called when the graph change
1479
            assert this.graph != null;
1475
            assert this.graph != null;
1480
            this.getGraph().fireModification(evt);
1476
            this.getGraph().fireModification(evt);
1481
        }
1477
        }
1482
    }
1478
    }
1483
 
1479
 
1484
    public final void addValueListener(ValueChangeListener l) {
1480
    public final void addValueListener(ValueChangeListener l) {
1485
        this.getGraph().addValueListener(this, l);
1481
        this.getGraph().addValueListener(this, l);
1486
    }
1482
    }
1487
 
1483
 
1488
    public final void removeValueListener(ValueChangeListener l) {
1484
    public final void removeValueListener(ValueChangeListener l) {
1489
        this.getGraph().removeValueListener(this, l);
1485
        this.getGraph().removeValueListener(this, l);
1490
    }
1486
    }
1491
 
1487
 
1492
    @Override
1488
    @Override
1493
    public final Collection<SQLRowValues> followLink(final Link l, final Direction direction) {
1489
    public final Collection<SQLRowValues> followLink(final Link l, final Direction direction) {
1494
        return this.followPath(Path.get(getTable()).add(l, direction), CreateMode.CREATE_NONE, false);
1490
        return this.followPath(Path.get(getTable()).add(l, direction), CreateMode.CREATE_NONE, false);
1495
    }
1491
    }
1496
 
1492
 
1497
    /**
1493
    /**
1498
     * Create the necessary SQLRowValues so that the graph of this row goes along the passed path.
1494
     * Create the necessary SQLRowValues so that the graph of this row goes along the passed path.
1499
     * 
1495
     * 
1500
     * @param p the path of SQLRowValues, eg "LOCAL.ID_BATIMENT,BATIMENT.ID_SITE".
1496
     * @param p the path of SQLRowValues, eg "LOCAL.ID_BATIMENT,BATIMENT.ID_SITE".
1501
     * @return the SQLRowValues at the end of the path, eg a SQLRowValues on /SITE/.
1497
     * @return the SQLRowValues at the end of the path, eg a SQLRowValues on /SITE/.
1502
     */
1498
     */
1503
    public final SQLRowValues assurePath(final Path p) {
1499
    public final SQLRowValues assurePath(final Path p) {
1504
        return this.followPath(p, true);
1500
        return this.followPath(p, true);
1505
    }
1501
    }
1506
 
1502
 
1507
    /**
1503
    /**
1508
     * Return the row at the end of passed path.
1504
     * Return the row at the end of passed path.
1509
     * 
1505
     * 
1510
     * @param p the path to follow, e.g. SITE,SITE.ID_CONTACT_CHEF.
1506
     * @param p the path to follow, e.g. SITE,SITE.ID_CONTACT_CHEF.
1511
     * @return the row at the end or <code>null</code> if none exists, e.g. SQLRowValues on
1507
     * @return the row at the end or <code>null</code> if none exists, e.g. SQLRowValues on
1512
     *         /CONTACT/.
1508
     *         /CONTACT/.
1513
     */
1509
     */
1514
    public final SQLRowValues followPath(final Path p) {
1510
    public final SQLRowValues followPath(final Path p) {
1515
        return this.followPath(p, false);
1511
        return this.followPath(p, false);
1516
    }
1512
    }
1517
 
1513
 
1518
    private final SQLRowValues followPath(final Path p, final boolean create) {
1514
    private final SQLRowValues followPath(final Path p, final boolean create) {
1519
        return followPathToOne(p, create ? CreateMode.CREATE_ONE : CreateMode.CREATE_NONE, DEFAULT_ALLOW_BACKTRACK);
1515
        return followPathToOne(p, create ? CreateMode.CREATE_ONE : CreateMode.CREATE_NONE, DEFAULT_ALLOW_BACKTRACK);
1520
    }
1516
    }
1521
 
1517
 
1522
    /**
1518
    /**
1523
     * Follow path to at most one row.
1519
     * Follow path to at most one row.
1524
     * 
1520
     * 
1525
     * @param p the path to follow.
1521
     * @param p the path to follow.
1526
     * @param create if and how to create new rows.
1522
     * @param create if and how to create new rows.
1527
     * @param allowBackTrack <code>true</code> to allow encountering the same row more than once.
1523
     * @param allowBackTrack <code>true</code> to allow encountering the same row more than once.
1528
     * @return the destination row or <code>null</code> if none exists and <code>create</code> was
1524
     * @return the destination row or <code>null</code> if none exists and <code>create</code> was
1529
     *         {@link CreateMode#CREATE_NONE}
1525
     *         {@link CreateMode#CREATE_NONE}
1530
     * @see #followPath(Path, CreateMode, boolean, boolean)
1526
     * @see #followPath(Path, CreateMode, boolean, boolean)
1531
     */
1527
     */
1532
    public final SQLRowValues followPathToOne(final Path p, final CreateMode create, final boolean allowBackTrack) {
1528
    public final SQLRowValues followPathToOne(final Path p, final CreateMode create, final boolean allowBackTrack) {
1533
        final Collection<SQLRowValues> res = this.followPath(p, create, true, allowBackTrack);
1529
        final Collection<SQLRowValues> res = this.followPath(p, create, true, allowBackTrack);
1534
        // since we passed onlyOne=true
1530
        // since we passed onlyOne=true
1535
        assert res.size() <= 1;
1531
        assert res.size() <= 1;
1536
        return CollectionUtils.getSole(res);
1532
        return CollectionUtils.getSole(res);
1537
    }
1533
    }
1538
 
1534
 
1539
    /**
1535
    /**
1540
     * Return the rows at the end of the passed path.
1536
     * Return the rows at the end of the passed path.
1541
     * 
1537
     * 
1542
     * @param path a path, e.g. SITE, BATIMENT, LOCAL.
1538
     * @param path a path, e.g. SITE, BATIMENT, LOCAL.
1543
     * @return the existing rows at the end of <code>path</code>, never <code>null</code>, e.g.
1539
     * @return the existing rows at the end of <code>path</code>, never <code>null</code>, e.g.
1544
     *         [LOCAL[3], LOCAL[5]].
1540
     *         [LOCAL[3], LOCAL[5]].
1545
     */
1541
     */
1546
    public final Collection<SQLRowValues> getDistantRows(final Path path) {
1542
    public final Collection<SQLRowValues> getDistantRows(final Path path) {
1547
        return followPath(path, CreateMode.CREATE_NONE, false);
1543
        return followPath(path, CreateMode.CREATE_NONE, false);
1548
    }
1544
    }
1549
 
1545
 
1550
    /**
1546
    /**
1551
     * Create all rows on the passed path and add them to this. There's {@link CreateMode#CREATE_ONE
1547
     * Create all rows on the passed path and add them to this. There's {@link CreateMode#CREATE_ONE
1552
     * one row} per step.
1548
     * one row} per step.
1553
     * 
1549
     * 
1554
     * @param p the path.
1550
     * @param p the path.
1555
     * @return the row at the end of the path.
1551
     * @return the row at the end of the path.
1556
     * @throws IllegalStateException if the first step is a non-empty foreign link.
1552
     * @throws IllegalStateException if the first step is a non-empty foreign link.
1557
     */
1553
     */
1558
    public final SQLRowValues createPathToOne(final Path p) {
1554
    public final SQLRowValues createPathToOne(final Path p) {
1559
        final Collection<SQLRowValues> res = this.createPath(p, true);
1555
        final Collection<SQLRowValues> res = this.createPath(p, true);
1560
        assert res.size() == 1;
1556
        assert res.size() == 1;
1561
        return res.iterator().next();
1557
        return res.iterator().next();
1562
    }
1558
    }
1563
 
1559
 
1564
    /**
1560
    /**
1565
     * Create all rows on the passed path and add them to this.
1561
     * Create all rows on the passed path and add them to this.
1566
     * 
1562
     * 
1567
     * @param p the path.
1563
     * @param p the path.
1568
     * @param createOne <code>true</code> to {@link CreateMode#CREATE_ONE create one} row per step,
1564
     * @param createOne <code>true</code> to {@link CreateMode#CREATE_ONE create one} row per step,
1569
     *        <code>false</code> to {@link CreateMode#CREATE_MANY create} one row per link.
1565
     *        <code>false</code> to {@link CreateMode#CREATE_MANY create} one row per link.
1570
     * @return the rows at the end of the path.
1566
     * @return the rows at the end of the path.
1571
     * @throws IllegalStateException if the first step is a non-empty foreign link.
1567
     * @throws IllegalStateException if the first step is a non-empty foreign link.
1572
     */
1568
     */
1573
    public final Collection<SQLRowValues> createPath(final Path p, final boolean createOne) {
1569
    public final Collection<SQLRowValues> createPath(final Path p, final boolean createOne) {
1574
        return this.followPath(p, createOne ? CreateMode.CREATE_ONE : CreateMode.CREATE_MANY, true, false, null);
1570
        return this.followPath(p, createOne ? CreateMode.CREATE_ONE : CreateMode.CREATE_MANY, true, false, null);
1575
    }
1571
    }
1576
 
1572
 
1577
    public final Collection<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean onlyOne) {
1573
    public final Collection<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean onlyOne) {
1578
        return followPath(p, create, onlyOne, DEFAULT_ALLOW_BACKTRACK);
1574
        return followPath(p, create, onlyOne, DEFAULT_ALLOW_BACKTRACK);
1579
    }
1575
    }
1580
 
1576
 
1581
    /**
1577
    /**
1582
     * Follow path through the graph.
1578
     * Follow path through the graph.
1583
     * 
1579
     * 
1584
     * @param p the path to follow.
1580
     * @param p the path to follow.
1585
     * @param create if and how to create new rows.
1581
     * @param create if and how to create new rows.
1586
     * @param onlyOne <code>true</code> if this method should return at most one row.
1582
     * @param onlyOne <code>true</code> if this method should return at most one row.
1587
     * @param allowBackTrack <code>true</code> to allow encountering the same row more than once.
1583
     * @param allowBackTrack <code>true</code> to allow encountering the same row more than once.
1588
     * @return the destination rows, can be empty.
1584
     * @return the destination rows, can be empty.
1589
     * @throws IllegalArgumentException if <code>p</code> doesn't start with this table.
1585
     * @throws IllegalArgumentException if <code>p</code> doesn't start with this table.
1590
     * @throws IllegalStateException if <code>onlyOne</code> and there's more than one row on the
1586
     * @throws IllegalStateException if <code>onlyOne</code> and there's more than one row on the
1591
     *         path.
1587
     *         path.
1592
     */
1588
     */
1593
    public final Collection<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean onlyOne, final boolean allowBackTrack)
1589
    public final Collection<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean onlyOne, final boolean allowBackTrack)
1594
            throws IllegalArgumentException, IllegalStateException {
1590
            throws IllegalArgumentException, IllegalStateException {
1595
        return followPath(p, create, false, onlyOne, allowBackTrack ? null : new LinkedIdentitySet<SQLRowValues>());
1591
        return followPath(p, create, false, onlyOne, allowBackTrack ? null : new LinkedIdentitySet<SQLRowValues>());
1596
    }
1592
    }
1597
 
1593
 
1598
    // if alwaysCreate : CREATE_NONE is invalid and existing rows are ignored (i.e. rows are always
1594
    // if alwaysCreate : CREATE_NONE is invalid and existing rows are ignored (i.e. rows are always
1599
    // created and an exception is thrown if there's a non-empty foreign link (perhaps add a force
1595
    // created and an exception is thrown if there's a non-empty foreign link (perhaps add a force
1600
    // mode to replace it))
1596
    // mode to replace it))
1601
    private final IdentitySet<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean alwaysCreate, final boolean onlyOne, final IdentitySet<SQLRowValues> beenThere) {
1597
    private final IdentitySet<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean alwaysCreate, final boolean onlyOne, final IdentitySet<SQLRowValues> beenThere) {
1602
        if (p.getFirst() != this.getTable())
1598
        if (p.getFirst() != this.getTable())
1603
            throw new IllegalArgumentException("path " + p + " doesn't start with us " + this);
1599
            throw new IllegalArgumentException("path " + p + " doesn't start with us " + this);
1604
        final boolean neverCreate = create == CreateMode.CREATE_NONE;
1600
        final boolean neverCreate = create == CreateMode.CREATE_NONE;
1605
        if (alwaysCreate && neverCreate)
1601
        if (alwaysCreate && neverCreate)
1606
            throw new IllegalArgumentException("If alwaysCreate, don't pass " + create);
1602
            throw new IllegalArgumentException("If alwaysCreate, don't pass " + create);
1607
        if (alwaysCreate && beenThere != null)
1603
        if (alwaysCreate && beenThere != null)
1608
            throw new IllegalArgumentException("If alwaysCreate, existing rows are ignored so the same row can never be visited more than once");
1604
            throw new IllegalArgumentException("If alwaysCreate, existing rows are ignored so the same row can never be visited more than once");
1609
        if (p.length() > 0) {
1605
        if (p.length() > 0) {
1610
            // fail-fast : avoid creating rows
1606
            // fail-fast : avoid creating rows
1611
            if (onlyOne && create == CreateMode.CREATE_MANY && !p.isSingleLink())
1607
            if (onlyOne && create == CreateMode.CREATE_MANY && !p.isSingleLink())
1612
                throw new IllegalStateException("more than one link with " + create + " and onlyOne : " + p);
1608
                throw new IllegalStateException("more than one link with " + create + " and onlyOne : " + p);
1613
 
1609
 
1614
            final Step firstStep = p.getStep(0);
1610
            final Step firstStep = p.getStep(0);
1615
            final Set<Link> ffs = firstStep.getLinks();
1611
            final Set<Link> ffs = firstStep.getLinks();
1616
            final SetMap<Link, SQLRowValues> existingRows = createSetMap(-1, 6);
1612
            final SetMap<Link, SQLRowValues> existingRows = createSetMap(-1, 6);
1617
            final Set<Link> linksToCreate = neverCreate ? Collections.<Link> emptySet() : new HashSet<Link>();
1613
            final Set<Link> linksToCreate = neverCreate ? Collections.<Link> emptySet() : new HashSet<Link>();
1618
            for (final Link l : ffs) {
1614
            for (final Link l : ffs) {
1619
                final SQLField ff = l.getLabel();
1615
                final SQLField ff = l.getLabel();
1620
                if (firstStep.isForeign(l)) {
1616
                if (firstStep.isForeign(l)) {
1621
                    final Object fkValue = this.getObject(ff.getName());
1617
                    final Object fkValue = this.getObject(ff.getName());
1622
                    if (fkValue instanceof SQLRowValues && (beenThere == null || !beenThere.contains(fkValue))) {
1618
                    if (fkValue instanceof SQLRowValues && (beenThere == null || !beenThere.contains(fkValue))) {
1623
                        if (alwaysCreate)
1619
                        if (alwaysCreate)
1624
                            throw new IllegalStateException("alwaysCreate=true but foreign link is not empty : " + l);
1620
                            throw new IllegalStateException("alwaysCreate=true but foreign link is not empty : " + l);
1625
                        existingRows.add(l, (SQLRowValues) fkValue);
1621
                        existingRows.add(l, (SQLRowValues) fkValue);
1626
                    } else if (!neverCreate) {
1622
                    } else if (!neverCreate) {
1627
                        linksToCreate.add(l);
1623
                        linksToCreate.add(l);
1628
                    }
1624
                    }
1629
                } else {
1625
                } else {
1630
                    final Set<SQLRowValues> referentRows = this.getReferentRows(ff);
1626
                    final Set<SQLRowValues> referentRows = this.getReferentRows(ff);
1631
                    final Set<SQLRowValues> validReferentRows;
1627
                    final Set<SQLRowValues> validReferentRows;
1632
                    if (beenThere == null || beenThere.size() == 0) {
1628
                    if (beenThere == null || beenThere.size() == 0) {
1633
                        validReferentRows = referentRows;
1629
                        validReferentRows = referentRows;
1634
                    } else {
1630
                    } else {
1635
                        validReferentRows = new LinkedIdentitySet<SQLRowValues>(referentRows);
1631
                        validReferentRows = new LinkedIdentitySet<SQLRowValues>(referentRows);
1636
                        validReferentRows.removeAll(beenThere);
1632
                        validReferentRows.removeAll(beenThere);
1637
                    }
1633
                    }
1638
                    final boolean hasRef = validReferentRows.size() > 0;
1634
                    final boolean hasRef = validReferentRows.size() > 0;
1639
                    if (hasRef) {
1635
                    if (hasRef) {
1640
                        existingRows.addAll(l, validReferentRows);
1636
                        existingRows.addAll(l, validReferentRows);
1641
                    }
1637
                    }
1642
                    if (alwaysCreate || !neverCreate && !hasRef) {
1638
                    if (alwaysCreate || !neverCreate && !hasRef) {
1643
                        linksToCreate.add(l);
1639
                        linksToCreate.add(l);
1644
                    }
1640
                    }
1645
                }
1641
                }
1646
            }
1642
            }
1647
            assert !alwaysCreate || linksToCreate.size() > 0;
1643
            assert !alwaysCreate || linksToCreate.size() > 0;
1648
            // Set is needed when a row is multi-linked to another (to avoid calling recursively
1644
            // Set is needed when a row is multi-linked to another (to avoid calling recursively
1649
            // followPath() on the same instance)
1645
            // followPath() on the same instance)
1650
            // IdentitySet is needed since multiple rows can be equal, e.g. empty rows :
1646
            // IdentitySet is needed since multiple rows can be equal, e.g. empty rows :
1651
            // SITE -- chef -> CONTACT
1647
            // SITE -- chef -> CONTACT
1652
            // _____-- rapport -> CONTACT
1648
            // _____-- rapport -> CONTACT
1653
            final Set<SQLRowValues> next = new LinkedIdentitySet<SQLRowValues>();
1649
            final Set<SQLRowValues> next = new LinkedIdentitySet<SQLRowValues>();
1654
            // by definition alwaysCreate implies ignoring existing rows
1650
            // by definition alwaysCreate implies ignoring existing rows
1655
            if (!alwaysCreate)
1651
            if (!alwaysCreate)
1656
                next.addAll(existingRows.allValues());
1652
                next.addAll(existingRows.allValues());
1657
            final int existingCount = next.size();
1653
            final int existingCount = next.size();
1658
            if (onlyOne && existingCount > 1)
1654
            if (onlyOne && existingCount > 1)
1659
                throw new IllegalStateException("more than one row exist and onlyOne=true : " + existingRows);
1655
                throw new IllegalStateException("more than one row exist and onlyOne=true : " + existingRows);
1660
 
1656
 
1661
            final int newCount;
1657
            final int newCount;
1662
            if (create == CreateMode.CREATE_MANY) {
1658
            if (create == CreateMode.CREATE_MANY) {
1663
                newCount = existingCount + linksToCreate.size();
1659
                newCount = existingCount + linksToCreate.size();
1664
            } else if (create == CreateMode.CREATE_ONE) {
1660
            } else if (create == CreateMode.CREATE_ONE) {
1665
                // only enforce if we're creating rows, otherwise use "onlyOne"
1661
                // only enforce if we're creating rows, otherwise use "onlyOne"
1666
                if (linksToCreate.size() > 0 && existingCount > 1)
1662
                if (linksToCreate.size() > 0 && existingCount > 1)
1667
                    throw new IllegalStateException("more than one row exist and " + create + ", this step won't be between two rows : " + existingRows);
1663
                    throw new IllegalStateException("more than one row exist and " + create + ", this step won't be between two rows : " + existingRows);
1668
                newCount = Math.max(existingCount, 1);
1664
                newCount = Math.max(existingCount, 1);
1669
            } else {
1665
            } else {
1670
                assert neverCreate;
1666
                assert neverCreate;
1671
                newCount = existingCount;
1667
                newCount = existingCount;
1672
            }
1668
            }
1673
            if (onlyOne && newCount > 1)
1669
            if (onlyOne && newCount > 1)
1674
                throw new IllegalStateException("Will have more than one row and onlyOne=true : " + existingRows + " to create : " + linksToCreate);
1670
                throw new IllegalStateException("Will have more than one row and onlyOne=true : " + existingRows + " to create : " + linksToCreate);
1675
 
1671
 
1676
            for (final Link l : linksToCreate) {
1672
            for (final Link l : linksToCreate) {
1677
                final SQLField ff = l.getLabel();
1673
                final SQLField ff = l.getLabel();
1678
                final boolean isForeign = firstStep.isForeign(l);
1674
                final boolean isForeign = firstStep.isForeign(l);
1679
 
1675
 
1680
                final SQLRowValues nextOne;
1676
                final SQLRowValues nextOne;
1681
                if (create == CreateMode.CREATE_ONE && next.size() == 1) {
1677
                if (create == CreateMode.CREATE_ONE && next.size() == 1) {
1682
                    nextOne = next.iterator().next();
1678
                    nextOne = next.iterator().next();
1683
                } else {
1679
                } else {
1684
                    assert create == CreateMode.CREATE_MANY || (create == CreateMode.CREATE_ONE && next.size() == 0) : "Creating more than one, already " + next.size();
1680
                    assert create == CreateMode.CREATE_MANY || (create == CreateMode.CREATE_ONE && next.size() == 0) : "Creating more than one, already " + next.size();
1685
                    nextOne = new SQLRowValues(firstStep.getTo());
1681
                    nextOne = new SQLRowValues(firstStep.getTo());
1686
                    if (isForeign) {
1682
                    if (isForeign) {
1687
                        // keep the id, if present
1683
                        // keep the id, if present
1688
                        final Object fkValue = this.getObject(ff.getName());
1684
                        final Object fkValue = this.getObject(ff.getName());
1689
                        if (fkValue instanceof Number)
1685
                        if (fkValue instanceof Number)
1690
                            nextOne.setID((Number) fkValue);
1686
                            nextOne.setID((Number) fkValue);
1691
                    }
1687
                    }
1692
                    next.add(nextOne);
1688
                    next.add(nextOne);
1693
                }
1689
                }
1694
                if (isForeign) {
1690
                if (isForeign) {
1695
                    this.put(ff.getName(), nextOne);
1691
                    this.put(ff.getName(), nextOne);
1696
                } else {
1692
                } else {
1697
                    nextOne.put(ff.getName(), this);
1693
                    nextOne.put(ff.getName(), this);
1698
                }
1694
                }
1699
            }
1695
            }
1700
            // already checked above
1696
            // already checked above
1701
            assert !(onlyOne && next.size() > 1);
1697
            assert !(onlyOne && next.size() > 1);
1702
 
1698
 
1703
            // see comment above for IdentitySet
1699
            // see comment above for IdentitySet
1704
            final IdentitySet<SQLRowValues> res = new LinkedIdentitySet<SQLRowValues>();
1700
            final IdentitySet<SQLRowValues> res = new LinkedIdentitySet<SQLRowValues>();
1705
            for (final SQLRowValues n : next) {
1701
            for (final SQLRowValues n : next) {
1706
                final IdentitySet<SQLRowValues> newBeenThere;
1702
                final IdentitySet<SQLRowValues> newBeenThere;
1707
                if (beenThere == null) {
1703
                if (beenThere == null) {
1708
                    newBeenThere = null;
1704
                    newBeenThere = null;
1709
                } else {
1705
                } else {
1710
                    newBeenThere = new LinkedIdentitySet<SQLRowValues>(beenThere);
1706
                    newBeenThere = new LinkedIdentitySet<SQLRowValues>(beenThere);
1711
                    final boolean added = newBeenThere.add(this);
1707
                    final boolean added = newBeenThere.add(this);
1712
                    assert added;
1708
                    assert added;
1713
                }
1709
                }
1714
                res.addAll(n.followPath(p.minusFirst(), create, alwaysCreate, onlyOne, newBeenThere));
1710
                res.addAll(n.followPath(p.minusFirst(), create, alwaysCreate, onlyOne, newBeenThere));
1715
            }
1711
            }
1716
            return res;
1712
            return res;
1717
        } else {
1713
        } else {
1718
            return CollectionUtils.createIdentitySet(this);
1714
            return CollectionUtils.createIdentitySet(this);
1719
        }
1715
        }
1720
    }
1716
    }
1721
 
1717
 
1722
    public final SQLRowValues changeForeigns(ForeignCopyMode mode) {
1718
    public final SQLRowValues changeForeigns(ForeignCopyMode mode) {
1723
        return this.changeForeigns(null, false, mode);
1719
        return this.changeForeigns(null, false, mode);
1724
    }
1720
    }
1725
 
1721
 
1726
    static private final boolean isEmpty(final Collection<?> coll, final boolean exclude) {
1722
    static private final boolean isEmpty(final Collection<?> coll, final boolean exclude) {
1727
        if (exclude) {
1723
        if (exclude) {
1728
            return coll == null;
1724
            return coll == null;
1729
        } else {
1725
        } else {
1730
            return coll != null && coll.isEmpty();
1726
            return coll != null && coll.isEmpty();
1731
        }
1727
        }
1732
    }
1728
    }
1733
 
1729
 
1734
    public final SQLRowValues changeForeigns(final Collection<String> fields, final boolean exclude, final ForeignCopyMode mode) {
1730
    public final SQLRowValues changeForeigns(final Collection<String> fields, final boolean exclude, final ForeignCopyMode mode) {
1735
        if (!isEmpty(fields, exclude) && mode != ForeignCopyMode.COPY_ROW) {
1731
        if (!isEmpty(fields, exclude) && mode != ForeignCopyMode.COPY_ROW) {
1736
            // copy otherwise ConcurrentModificationException
1732
            // copy otherwise ConcurrentModificationException
1737
            for (final String ff : new ArrayList<String>(this.getForeigns().keySet())) {
1733
            for (final String ff : new ArrayList<String>(this.getForeigns().keySet())) {
1738
                // fields == null means include all thanks to the above if
1734
                // fields == null means include all thanks to the above if
1739
                if (fields == null || fields.contains(ff) != exclude) {
1735
                if (fields == null || fields.contains(ff) != exclude) {
1740
                    this.flatten(ff, mode);
1736
                    this.flatten(ff, mode);
1741
                }
1737
                }
1742
            }
1738
            }
1743
        }
1739
        }
1744
        return this;
1740
        return this;
1745
    }
1741
    }
1746
 
1742
 
1747
    /**
1743
    /**
1748
     * Flatten a foreign row values. NOTE : if there's no foreign row in <code>ff</code>, this
1744
     * Flatten a foreign row values. NOTE : if there's no foreign row in <code>ff</code>, this
1749
     * method does nothing.
1745
     * method does nothing.
1750
     * 
1746
     * 
1751
     * @param ff a foreign field.
1747
     * @param ff a foreign field.
1752
     * @param mode how to flatten.
1748
     * @param mode how to flatten.
1753
     * @return this.
1749
     * @return this.
1754
     */
1750
     */
1755
    public final SQLRowValues flatten(final String ff, final ForeignCopyMode mode) {
1751
    public final SQLRowValues flatten(final String ff, final ForeignCopyMode mode) {
1756
        if (mode != ForeignCopyMode.COPY_ROW) {
1752
        if (mode != ForeignCopyMode.COPY_ROW) {
1757
            final SQLRowValues foreign = this.foreigns.get(ff);
1753
            final SQLRowValues foreign = this.foreigns.get(ff);
1758
            if (foreign != null) {
1754
            if (foreign != null) {
1759
                if (mode == ForeignCopyMode.COPY_NULL) {
1755
                if (mode == ForeignCopyMode.COPY_NULL) {
1760
                    this.put(ff, null);
1756
                    this.put(ff, null);
1761
                } else if (mode == ForeignCopyMode.NO_COPY) {
1757
                } else if (mode == ForeignCopyMode.NO_COPY) {
1762
                    this.remove(ff);
1758
                    this.remove(ff);
1763
                } else if (foreign.hasID()) {
1759
                } else if (foreign.hasID()) {
1764
                    assert mode == ForeignCopyMode.COPY_ID_OR_ROW || mode == ForeignCopyMode.COPY_ID_OR_RM;
1760
                    assert mode == ForeignCopyMode.COPY_ID_OR_ROW || mode == ForeignCopyMode.COPY_ID_OR_RM;
1765
                    this.put(ff, foreign.getIDNumber());
1761
                    this.put(ff, foreign.getIDNumber());
1766
                } else if (mode == ForeignCopyMode.COPY_ID_OR_RM) {
1762
                } else if (mode == ForeignCopyMode.COPY_ID_OR_RM) {
1767
                    this.remove(ff);
1763
                    this.remove(ff);
1768
                } else {
1764
                } else {
1769
                    assert mode == ForeignCopyMode.COPY_ID_OR_ROW && !foreign.hasID();
1765
                    assert mode == ForeignCopyMode.COPY_ID_OR_ROW && !foreign.hasID();
1770
                }
1766
                }
1771
            }
1767
            }
1772
        }
1768
        }
1773
        return this;
1769
        return this;
1774
    }
1770
    }
1775
 
1771
 
1776
    // *** load
1772
    // *** load
1777
 
1773
 
1778
    public void loadAbsolutelyAll(SQLRow row) {
1774
    public void loadAbsolutelyAll(SQLRow row) {
1779
        this.setAll(row.getAbsolutelyAll());
1775
        this.setAll(row.getAbsolutelyAll());
1780
    }
1776
    }
1781
 
1777
 
1782
    /**
1778
    /**
1783
     * Load values from the passed row (and remove them if possible).
1779
     * Load values from the passed row (and remove them if possible).
1784
     * 
1780
     * 
1785
     * @param row the row to load values from.
1781
     * @param row the row to load values from.
1786
     * @param fieldsNames what fields to load, <code>null</code> meaning all.
1782
     * @param fieldsNames what fields to load, <code>null</code> meaning all.
1787
     */
1783
     */
1788
    public void load(SQLRowAccessor row, final Collection<String> fieldsNames) {
1784
    public void load(SQLRowAccessor row, final Collection<String> fieldsNames) {
1789
        // make sure we only define keys that row has
1785
        // make sure we only define keys that row has
1790
        // allow load( {'A':a, 'B':b}, {'A', 'B', 'C' } ) to not define 'C' to null
1786
        // allow load( {'A':a, 'B':b}, {'A', 'B', 'C' } ) to not define 'C' to null
1791
        final Map<String, Object> m = new LinkedHashMap<String, Object>(row.getAbsolutelyAll());
1787
        final Map<String, Object> m = new LinkedHashMap<String, Object>(row.getAbsolutelyAll());
1792
        if (fieldsNames != null)
1788
        if (fieldsNames != null)
1793
            m.keySet().retainAll(fieldsNames);
1789
            m.keySet().retainAll(fieldsNames);
1794
 
1790
 
1795
        // rm the added fields otherwise this and row will be linked
1791
        // rm the added fields otherwise this and row will be linked
1796
        // eg load LOCAL->BATIMENT into a LOCAL will result in the BATIMENT
1792
        // eg load LOCAL->BATIMENT into a LOCAL will result in the BATIMENT
1797
        // being pointed to by both LOCAL
1793
        // being pointed to by both LOCAL
1798
        if (row instanceof SQLRowValues)
1794
        if (row instanceof SQLRowValues)
1799
            ((SQLRowValues) row).removeAll(m.keySet());
1795
            ((SQLRowValues) row).removeAll(m.keySet());
1800
 
1796
 
1801
        // put after remove so that this graph never contains row (and thus avoids unneeded events)
1797
        // put after remove so that this graph never contains row (and thus avoids unneeded events)
1802
        this.putAll(m);
1798
        this.putAll(m);
1803
    }
1799
    }
1804
 
1800
 
1805
    public void merge(final SQLRowValues v) {
1801
    public void merge(final SQLRowValues v) {
1806
        this.getGraph().merge(this, v);
1802
        this.getGraph().merge(this, v);
1807
    }
1803
    }
1808
 
1804
 
1809
    // *** modify
1805
    // *** modify
1810
 
1806
 
1811
    void checkValidity() {
1807
    void checkValidity() {
1812
        // this checks archived which the DB doesn't with just foreign constraints
1808
        // this checks archived which the DB doesn't with just foreign constraints
1813
        // it also locks foreign rows so that they don't *become* archived
1809
        // it also locks foreign rows so that they don't *become* archived
1814
        final Object[] pb = this.getInvalid();
1810
        final Object[] pb = this.getInvalid();
1815
        if (pb != null)
1811
        if (pb != null)
1816
            throw new IllegalStateException("can't update " + this + " : the field " + pb[0] + " points to " + pb[1]);
1812
            throw new IllegalStateException("can't update " + this + " : the field " + pb[0] + " points to " + pb[1]);
1817
    }
1813
    }
1818
 
1814
 
1819
    /**
1815
    /**
1820
     * Return the first problem with a foreign key.
1816
     * Return the first problem with a foreign key.
1821
     * 
1817
     * 
1822
     * @return <code>null</code> si pas de pb, sinon un Object[] :
1818
     * @return <code>null</code> si pas de pb, sinon un Object[] :
1823
     *         <ol>
1819
     *         <ol>
1824
     *         <li>en 0 le nom du champ posant pb, eg "ID_OBSERVATION_2"</li>
1820
     *         <li>en 0 le nom du champ posant pb, eg "ID_OBSERVATION_2"</li>
1825
     *         <li>en 1 une SQLRow décrivant le pb, eg "(OBSERVATION[123])"</li>
1821
     *         <li>en 1 une SQLRow décrivant le pb, eg "(OBSERVATION[123])"</li>
1826
     *         </ol>
1822
     *         </ol>
1827
     */
1823
     */
1828
    public Object[] getInvalid() {
1824
    public Object[] getInvalid() {
1829
        final Map<String, Link> foreignLinks = new HashMap<String, Link>();
1825
        final Map<String, Link> foreignLinks = new HashMap<String, Link>();
1830
        for (final Link foreignLink : this.getTable().getForeignLinks()) {
1826
        for (final Link foreignLink : this.getTable().getForeignLinks()) {
1831
            for (final String f : foreignLink.getCols()) {
1827
            for (final String f : foreignLink.getCols()) {
1832
                foreignLinks.put(f, foreignLink);
1828
                foreignLinks.put(f, foreignLink);
1833
            }
1829
            }
1834
        }
1830
        }
1835
 
1831
 
1836
        for (final String fieldName : this.values.keySet()) {
1832
        for (final String fieldName : this.values.keySet()) {
1837
            final Link foreignLink = foreignLinks.remove(fieldName);
1833
            final Link foreignLink = foreignLinks.remove(fieldName);
1838
            if (foreignLink != null) {
1834
            if (foreignLink != null) {
1839
                final SQLTable foreignTable = foreignLink.getTarget();
1835
                final SQLTable foreignTable = foreignLink.getTarget();
1840
                if (foreignTable.isRowable()) {
1836
                if (foreignTable.isRowable()) {
1841
                    // otherwise would have to check more than field
1837
                    // otherwise would have to check more than field
1842
                    assert foreignLink.getCols().size() == 1;
1838
                    assert foreignLink.getCols().size() == 1;
1843
                    // verifie l'intégrité (a rowValues is obviously correct, as is EMPTY,
1839
                    // verifie l'intégrité (a rowValues is obviously correct, as is EMPTY,
1844
                    // DEFAULT is the responsability of the DB)
1840
                    // DEFAULT is the responsability of the DB)
1845
                    final Object fieldVal = this.getObject(fieldName);
1841
                    final Object fieldVal = this.getObject(fieldName);
1846
                    if (fieldVal != null && fieldVal != SQL_DEFAULT && !(fieldVal instanceof SQLRowValues)) {
1842
                    if (fieldVal != null && fieldVal != SQL_DEFAULT && !(fieldVal instanceof SQLRowValues)) {
1847
                        final SQLRow pb = foreignTable.checkValidity(((Number) fieldVal).intValue());
1843
                        final SQLRow pb = foreignTable.checkValidity(((Number) fieldVal).intValue());
1848
                        if (pb != null)
1844
                        if (pb != null)
1849
                            return new Object[] { fieldName, pb };
1845
                            return new Object[] { fieldName, pb };
1850
                    }
1846
                    }
1851
                } else {
1847
                } else {
1852
                    // check that the foreign key is complete
1848
                    // check that the foreign key is complete
1853
                    for (final String ff : foreignLink.getCols()) {
1849
                    for (final String ff : foreignLink.getCols()) {
1854
                        if (!this.contains(ff))
1850
                        if (!this.contains(ff))
1855
                            return new Object[] { ff, null };
1851
                            return new Object[] { ff, null };
1856
                    }
1852
                    }
1857
                    foreignLinks.keySet().removeAll(foreignLink.getCols());
1853
                    foreignLinks.keySet().removeAll(foreignLink.getCols());
1858
                    // MAYBE also check foreign row is valid
1854
                    // MAYBE also check foreign row is valid
1859
                }
1855
                }
1860
            } // else not a foreign key or already checked
1856
            } // else not a foreign key or already checked
1861
        }
1857
        }
1862
        return null;
1858
        return null;
1863
    }
1859
    }
1864
 
1860
 
1865
    // * insert
1861
    // * insert
1866
 
1862
 
1867
    /**
1863
    /**
1868
     * Insert a new line (strips the primary key, it must be db generated and strips order, added at
1864
     * Insert a new line (strips the primary key, it must be db generated and strips order, added at
1869
     * the end).
1865
     * the end).
1870
     * 
1866
     * 
1871
     * @return the newly inserted line, or <code>null</code> if the table has not exactly one
1867
     * @return the newly inserted line, or <code>null</code> if the table has not exactly one
1872
     *         primary key.
1868
     *         primary key.
1873
     * @throws SQLException if an error occurs while inserting.
1869
     * @throws SQLException if an error occurs while inserting.
1874
     * @throws IllegalStateException if the ID of the new line cannot be retrieved.
1870
     * @throws IllegalStateException if the ID of the new line cannot be retrieved.
1875
     */
1871
     */
1876
    public SQLRow insert() throws SQLException {
1872
    public SQLRow insert() throws SQLException {
1877
        // remove unwanted fields, keep ARCHIVE
1873
        // remove unwanted fields, keep ARCHIVE
1878
        return this.store(SQLRowValuesCluster.StoreMode.INSERT);
1874
        return this.store(SQLRowValuesCluster.StoreMode.INSERT);
1879
    }
1875
    }
1880
 
1876
 
1881
    /**
1877
    /**
1882
     * Insert a new line verbatim. ATTN the primary key must not exist.
1878
     * Insert a new line verbatim. ATTN the primary key must not exist.
1883
     * 
1879
     * 
1884
     * @return the newly inserted line, or <code>null</code> if the table has not exactly one
1880
     * @return the newly inserted line, or <code>null</code> if the table has not exactly one
1885
     *         primary key.
1881
     *         primary key.
1886
     * @throws SQLException if an error occurs while inserting.
1882
     * @throws SQLException if an error occurs while inserting.
1887
     * @throws IllegalStateException if the ID of the new line cannot be retrieved.
1883
     * @throws IllegalStateException if the ID of the new line cannot be retrieved.
1888
     */
1884
     */
1889
    public SQLRow insertVerbatim() throws SQLException {
1885
    public SQLRow insertVerbatim() throws SQLException {
1890
        return this.store(SQLRowValuesCluster.StoreMode.INSERT_VERBATIM);
1886
        return this.store(SQLRowValuesCluster.StoreMode.INSERT_VERBATIM);
1891
    }
1887
    }
1892
 
1888
 
1893
    public SQLRow insert(final boolean insertPK, final boolean insertOrder) throws SQLException {
1889
    public SQLRow insert(final boolean insertPK, final boolean insertOrder) throws SQLException {
1894
        return this.store(new SQLRowValuesCluster.Insert(insertPK, insertOrder));
1890
        return this.store(new SQLRowValuesCluster.Insert(insertPK, insertOrder));
1895
    }
1891
    }
1896
 
1892
 
1897
    public SQLRow store(final SQLRowValuesCluster.StoreMode mode) throws SQLException {
1893
    public SQLRow store(final SQLRowValuesCluster.StoreMode mode) throws SQLException {
1898
        return this.getGraph().store(mode).getStoredRow(this);
1894
        return this.getGraph().store(mode).getStoredRow(this);
1899
    }
1895
    }
1900
 
1896
 
1901
    SQLTableEvent insertJustThis(final boolean fetchStoredRow, final Set<SQLField> autoFields) throws SQLException {
1897
    SQLTableEvent insertJustThis(final boolean fetchStoredRow, final Set<SQLField> autoFields) throws SQLException {
1902
        final Map<String, Object> copy = this.clearFields(new HashMap<String, Object>(this.values), autoFields);
1898
        final Map<String, Object> copy = this.clearFields(new HashMap<String, Object>(this.values), autoFields);
1903
 
1899
 
1904
        try {
1900
        try {
1905
            final Tuple2<List<String>, Number> fieldsAndID = this.getTable().getBase().getDataSource().useConnection(new ConnectionHandlerNoSetup<Tuple2<List<String>, Number>, SQLException>() {
1901
            final Tuple2<List<String>, Number> fieldsAndID = this.getTable().getBase().getDataSource().useConnection(new ConnectionHandlerNoSetup<Tuple2<List<String>, Number>, SQLException>() {
1906
                @Override
1902
                @Override
1907
                public Tuple2<List<String>, Number> handle(SQLDataSource ds) throws SQLException {
1903
                public Tuple2<List<String>, Number> handle(SQLDataSource ds) throws SQLException {
1908
                    final Tuple2<PreparedStatement, List<String>> pStmt = createInsertStatement(getTable(), copy);
1904
                    final Tuple2<PreparedStatement, List<String>> pStmt = createInsertStatement(getTable(), copy);
1909
                    try {
1905
                    try {
1910
                        final Number newID = insert(pStmt.get0(), getTable());
1906
                        final Number newID = insert(pStmt.get0(), getTable());
1911
                        // MAYBE keep the pStmt around while values.keySet() doesn't change
1907
                        // MAYBE keep the pStmt around while values.keySet() doesn't change
1912
                        pStmt.get0().close();
1908
                        pStmt.get0().close();
1913
                        return Tuple2.create(pStmt.get1(), newID);
1909
                        return Tuple2.create(pStmt.get1(), newID);
1914
                    } catch (Exception e) {
1910
                    } catch (Exception e) {
1915
                        throw new SQLException("Unable to insert " + pStmt.get0(), e);
1911
                        throw new SQLException("Unable to insert " + pStmt.get0(), e);
1916
                    }
1912
                    }
1917
                }
1913
                }
1918
            });
1914
            });
1919
 
1915
 
1920
            assert this.getTable().isRowable() == (fieldsAndID.get1() != null);
1916
            assert this.getTable().isRowable() == (fieldsAndID.get1() != null);
1921
            if (this.getTable().isRowable()) {
1917
            if (this.getTable().isRowable()) {
1922
                // pour pouvoir avoir les valeurs des champs non précisés
1918
                // pour pouvoir avoir les valeurs des champs non précisés
1923
                return new SQLTableEvent(getEventRow(fieldsAndID.get1().intValue(), fetchStoredRow), Mode.ROW_ADDED, fieldsAndID.get0());
1919
                return new SQLTableEvent(getEventRow(fieldsAndID.get1().intValue(), fetchStoredRow), Mode.ROW_ADDED, fieldsAndID.get0());
1924
            } else
1920
            } else
1925
                return new SQLTableEvent(getTable(), SQLRow.NONEXISTANT_ID, Mode.ROW_ADDED, fieldsAndID.get0());
1921
                return new SQLTableEvent(getTable(), SQLRow.NONEXISTANT_ID, Mode.ROW_ADDED, fieldsAndID.get0());
1926
        } catch (SQLException e) {
1922
        } catch (SQLException e) {
1927
            throw new SQLException("unable to insert " + this + " using " + copy, e);
1923
            throw new SQLException("unable to insert " + this + " using " + copy, e);
1928
        }
1924
        }
1929
    }
1925
    }
1930
 
1926
 
1931
    private SQLRow getEventRow(final int newID, final boolean fetch) {
1927
    private SQLRow getEventRow(final int newID, final boolean fetch) {
1932
        final SQLRow res;
1928
        final SQLRow res;
1933
        if (fetch) {
1929
        if (fetch) {
1934
            // don't read the cache since no event has been fired yet
1930
            // don't read the cache since no event has been fired yet
1935
            // don't write to it since the transaction isn't committed yet, so other threads
1931
            // don't write to it since the transaction isn't committed yet, so other threads
1936
            // should not see the new values.
1932
            // should not see the new values.
1937
            res = new SQLRow(getTable(), newID).fetchValues(false);
1933
            res = new SQLRow(getTable(), newID).fetchValues(false);
1938
        } else {
1934
        } else {
1939
            res = SQLRow.createEmpty(getTable(), newID);
1935
            res = SQLRow.createEmpty(getTable(), newID);
1940
        }
1936
        }
1941
        assert res.isFilled();
1937
        assert res.isFilled();
1942
        return res;
1938
        return res;
1943
    }
1939
    }
1944
 
1940
 
1945
    // * update
1941
    // * update
1946
 
1942
 
1947
    public SQLRow update() throws SQLException {
1943
    public SQLRow update() throws SQLException {
1948
        if (!hasID()) {
1944
        if (!hasID()) {
1949
            throw new IllegalStateException("can't update : no ID specified, use update(int) or set ID for " + this);
1945
            throw new IllegalStateException("can't update : no ID specified, use update(int) or set ID for " + this);
1950
        }
1946
        }
1951
        return this.commit();
1947
        return this.commit();
1952
    }
1948
    }
1953
 
1949
 
1954
    public SQLRow update(final int id) throws SQLException {
1950
    public SQLRow update(final int id) throws SQLException {
1955
        this.put(this.getTable().getKey().getName(), id);
1951
        this.put(this.getTable().getKey().getName(), id);
1956
        return this.commit();
1952
        return this.commit();
1957
    }
1953
    }
1958
 
1954
 
1959
    /**
1955
    /**
1960
     * Permet de mettre à jour une ligne existante avec les valeurs courantes.
1956
     * Permet de mettre à jour une ligne existante avec les valeurs courantes.
1961
     * 
1957
     * 
1962
     * @param fetchStoredRow <code>true</code> to fetch the just stored row.
1958
     * @param fetchStoredRow <code>true</code> to fetch the just stored row.
1963
     * @param id l'id à mettre à jour.
1959
     * @param id l'id à mettre à jour.
1964
     * @return the updated row.
1960
     * @return the updated row.
1965
     * @throws SQLException si pb lors de la maj.
1961
     * @throws SQLException si pb lors de la maj.
1966
     */
1962
     */
1967
    SQLTableEvent updateJustThis(boolean fetchStoredRow, final int id) throws SQLException {
1963
    SQLTableEvent updateJustThis(boolean fetchStoredRow, final int id) throws SQLException {
1968
        if (id == this.getTable().getUndefinedID()) {
1964
        if (id == this.getTable().getUndefinedID()) {
1969
            throw new IllegalArgumentException("can't update undefined with " + this);
1965
            throw new IllegalArgumentException("can't update undefined with " + this);
1970
        }
1966
        }
1971
        // clear primary key, otherwise we might end up with :
1967
        // clear primary key, otherwise we might end up with :
1972
        // UPDATE TABLE SET ID=123,DESIGNATION='aa' WHERE id=456
1968
        // UPDATE TABLE SET ID=123,DESIGNATION='aa' WHERE id=456
1973
        // which will delete ID 456, and possibly cause a conflict with preexisting ID 123
1969
        // which will delete ID 456, and possibly cause a conflict with preexisting ID 123
1974
        final Map<String, Object> updatedValues = this.clearPrimaryKeys(new HashMap<String, Object>(this.values));
1970
        final Map<String, Object> updatedValues = this.clearPrimaryKeys(new HashMap<String, Object>(this.values));
1975
 
1971
 
1976
        final List<String> updatedCols;
1972
        final List<String> updatedCols;
1977
        if (updatedValues.isEmpty()) {
1973
        if (updatedValues.isEmpty()) {
1978
            updatedCols = Collections.emptyList();
1974
            updatedCols = Collections.emptyList();
1979
        } else {
1975
        } else {
1980
            updatedCols = this.getTable().getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<List<String>, SQLException>() {
1976
            updatedCols = this.getTable().getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<List<String>, SQLException>() {
1981
                @Override
1977
                @Override
1982
                public List<String> handle(SQLDataSource ds) throws SQLException {
1978
                public List<String> handle(SQLDataSource ds) throws SQLException {
1983
                    final Tuple2<PreparedStatement, List<String>> pStmt = createUpdateStatement(getTable(), updatedValues, id);
1979
                    final Tuple2<PreparedStatement, List<String>> pStmt = createUpdateStatement(getTable(), updatedValues, id);
1984
                    final long timeMs = System.currentTimeMillis();
1980
                    final long timeMs = System.currentTimeMillis();
1985
                    final long time = System.nanoTime();
1981
                    final long time = System.nanoTime();
1986
                    final int updateCount = pStmt.get0().executeUpdate();
1982
                    final int updateCount = pStmt.get0().executeUpdate();
1987
                    final long afterExecute = System.nanoTime();
1983
                    final long afterExecute = System.nanoTime();
1988
                    // logging after closing fails to get the connection info
1984
                    // logging after closing fails to get the connection info
1989
                    SQLRequestLog.log(pStmt.get0(), "rowValues.update()", timeMs, time, afterExecute, afterExecute, afterExecute, afterExecute, System.nanoTime());
1985
                    SQLRequestLog.log(pStmt.get0(), "rowValues.update()", timeMs, time, afterExecute, afterExecute, afterExecute, afterExecute, System.nanoTime());
1990
                    pStmt.get0().close();
1986
                    pStmt.get0().close();
1991
                    if (updateCount > 1)
1987
                    if (updateCount > 1)
1992
                        throw new IllegalStateException(updateCount + " rows updated with ID " + id);
1988
                        throw new IllegalStateException(updateCount + " rows updated with ID " + id);
1993
                    return updateCount == 0 ? null : pStmt.get1();
1989
                    return updateCount == 0 ? null : pStmt.get1();
1994
                }
1990
                }
1995
            });
1991
            });
1996
        }
1992
        }
1997
 
1993
 
1998
        return updatedCols == null ? null : new SQLTableEvent(getEventRow(id, fetchStoredRow), Mode.ROW_UPDATED, updatedCols);
1994
        return updatedCols == null ? null : new SQLTableEvent(getEventRow(id, fetchStoredRow), Mode.ROW_UPDATED, updatedCols);
1999
    }
1995
    }
2000
 
1996
 
2001
    // * commit
1997
    // * commit
2002
 
1998
 
2003
    /**
1999
    /**
2004
     * S'assure que ces valeurs arrivent dans la base. Si la ligne possède un ID équivaut à update()
2000
     * S'assure que ces valeurs arrivent dans la base. Si la ligne possède un ID équivaut à update()
2005
     * sinon insert().
2001
     * sinon insert().
2006
     * 
2002
     * 
2007
     * @return the affected row.
2003
     * @return the affected row.
2008
     * @throws SQLException
2004
     * @throws SQLException
2009
     */
2005
     */
2010
    public SQLRow commit() throws SQLException {
2006
    public SQLRow commit() throws SQLException {
2011
        return this.store(SQLRowValuesCluster.StoreMode.COMMIT);
2007
        return this.store(SQLRowValuesCluster.StoreMode.COMMIT);
2012
    }
2008
    }
2013
 
2009
 
2014
    SQLTableEvent commitJustThis(boolean fetchStoredRow) throws SQLException {
2010
    SQLTableEvent commitJustThis(boolean fetchStoredRow) throws SQLException {
2015
        if (!hasID()) {
2011
        if (!hasID()) {
2016
            return this.insertJustThis(fetchStoredRow, Collections.<SQLField> emptySet());
2012
            return this.insertJustThis(fetchStoredRow, Collections.<SQLField> emptySet());
2017
        } else
2013
        } else
2018
            return this.updateJustThis(fetchStoredRow, this.getID());
2014
            return this.updateJustThis(fetchStoredRow, this.getID());
2019
    }
2015
    }
2020
 
2016
 
2021
    /**
2017
    /**
2022
     * Returns a string representation of this (excluding any foreign or referent rows).
2018
     * Returns a string representation of this (excluding any foreign or referent rows).
2023
     * 
2019
     * 
2024
     * @return a compact representation of this.
2020
     * @return a compact representation of this.
2025
     * @see #printGraph()
2021
     * @see #printGraph()
2026
     */
2022
     */
2027
    @Override
2023
    @Override
2028
    public String toString() {
2024
    public String toString() {
2029
        return mapToString();
2025
        return mapToString();
2030
    }
2026
    }
2031
 
2027
 
2032
    @Override
2028
    @Override
2033
    public String mapToString() {
2029
    public String mapToString() {
2034
        String result = this.getClass().getSimpleName() + " on " + this.getTable() + " : {";
2030
        String result = this.getClass().getSimpleName() + " on " + this.getTable() + " : {";
2035
        result += CollectionUtils.join(this.values.entrySet(), ", ", new ITransformer<Entry<String, ?>, String>() {
2031
        result += CollectionUtils.join(this.values.entrySet(), ", ", new ITransformer<Entry<String, ?>, String>() {
2036
            public String transformChecked(final Entry<String, ?> e) {
2032
            public String transformChecked(final Entry<String, ?> e) {
2037
                final String className = e.getValue() == null ? "" : "(" + e.getValue().getClass() + ")";
2033
                final String className = e.getValue() == null ? "" : "(" + e.getValue().getClass() + ")";
2038
                final String value;
2034
                final String value;
2039
                // avoid infinite loop (and overly verbose string)
2035
                // avoid infinite loop (and overly verbose string)
2040
                if (e.getValue() instanceof SQLRowValues) {
2036
                if (e.getValue() instanceof SQLRowValues) {
2041
                    final SQLRowValues foreignVals = (SQLRowValues) e.getValue();
2037
                    final SQLRowValues foreignVals = (SQLRowValues) e.getValue();
2042
                    if (foreignVals == SQLRowValues.this) {
2038
                    if (foreignVals == SQLRowValues.this) {
2043
                        value = "this";
2039
                        value = "this";
2044
                    } else if (foreignVals.hasID()) {
2040
                    } else if (foreignVals.hasID()) {
2045
                        value = foreignVals.getIDNumber().toString();
2041
                        value = foreignVals.getIDNumber().toString();
2046
                    } else {
2042
                    } else {
2047
                        // so that if the same vals is referenced multiple times, we can see it
2043
                        // so that if the same vals is referenced multiple times, we can see it
2048
                        value = "@" + System.identityHashCode(foreignVals);
2044
                        value = "@" + System.identityHashCode(foreignVals);
2049
                    }
2045
                    }
2050
                } else
2046
                } else
2051
                    value = String.valueOf(e.getValue());
2047
                    value = String.valueOf(e.getValue());
2052
                return e.getKey() + "=" + value + className;
2048
                return e.getKey() + "=" + value + className;
2053
            }
2049
            }
2054
        });
2050
        });
2055
        result += "}";
2051
        result += "}";
2056
        return result;
2052
        return result;
2057
    }
2053
    }
2058
 
2054
 
2059
    /**
2055
    /**
2060
     * Return a graphical representation (akin to the result of a query) of the tree rooted at
2056
     * Return a graphical representation (akin to the result of a query) of the tree rooted at
2061
     * <code>this</code>.
2057
     * <code>this</code>.
2062
     * 
2058
     * 
2063
     * @return a string representing the rows pointing to this.
2059
     * @return a string representing the rows pointing to this.
2064
     * @see SQLRowValuesCluster#printTree(SQLRowValues, int)
2060
     * @see SQLRowValuesCluster#printTree(SQLRowValues, int)
2065
     */
2061
     */
2066
    public final String printTree() {
2062
    public final String printTree() {
2067
        return this.getGraph().printTree(this, 16);
2063
        return this.getGraph().printTree(this, 16);
2068
    }
2064
    }
2069
 
2065
 
2070
    /**
2066
    /**
2071
     * Return the list of all nodes and their links.
2067
     * Return the list of all nodes and their links.
2072
     * 
2068
     * 
2073
     * @return a string representing the graph of this.
2069
     * @return a string representing the graph of this.
2074
     */
2070
     */
2075
    public final String printGraph() {
2071
    public final String printGraph() {
2076
        return this.getGraph().printNodes();
2072
        return this.getGraph().printNodes();
2077
    }
2073
    }
2078
 
2074
 
2079
    @Override
2075
    @Override
2080
    public boolean equals(Object obj) {
2076
    public boolean equals(Object obj) {
2081
        if (obj == this)
2077
        if (obj == this)
2082
            return true;
2078
            return true;
2083
        if (obj instanceof SQLRowValues) {
2079
        if (obj instanceof SQLRowValues) {
2084
            return this.equalsGraph((SQLRowValues) obj);
2080
            return this.equalsGraph((SQLRowValues) obj);
2085
        } else
2081
        } else
2086
            return false;
2082
            return false;
2087
    }
2083
    }
2088
 
2084
 
2089
    @Override
2085
    @Override
2090
    public int hashCode() {
2086
    public int hashCode() {
2091
        final int prime = 31;
2087
        final int prime = 31;
2092
        int result = 1;
2088
        int result = 1;
2093
        result = prime * result + this.getTable().hashCode();
2089
        result = prime * result + this.getTable().hashCode();
2094
        // don't use SQLRowValues to avoid infinite loop
2090
        // don't use SQLRowValues to avoid infinite loop
2095
        result = prime * result + this.getFields().hashCode();
2091
        result = prime * result + this.getFields().hashCode();
2096
        result = prime * result + this.getGraphSize();
2092
        result = prime * result + this.getGraphSize();
2097
        result = prime * result + this.foreigns.keySet().hashCode();
2093
        result = prime * result + this.foreigns.keySet().hashCode();
2098
        result = prime * result + this.referents.keySet().hashCode();
2094
        result = prime * result + this.referents.keySet().hashCode();
2099
        return result;
2095
        return result;
2100
    }
2096
    }
2101
 
2097
 
2102
    /**
2098
    /**
2103
     * Indicates whether some other graph is "equal to" this one.
2099
     * Indicates whether some other graph is "equal to" this one.
2104
     * 
2100
     * 
2105
     * @param other another rowValues.
2101
     * @param other another rowValues.
2106
     * @return <code>true</code> if both graph are equals.
2102
     * @return <code>true</code> if both graph are equals.
2107
     * @see #getGraphFirstDifference(SQLRowValues)
2103
     * @see #getGraphFirstDifference(SQLRowValues)
2108
     */
2104
     */
2109
    public final boolean equalsGraph(final SQLRowValues other) {
2105
    public final boolean equalsGraph(final SQLRowValues other) {
2110
        return this.getGraphFirstDifference(other) == null;
2106
        return this.getGraphFirstDifference(other) == null;
2111
    }
2107
    }
2112
 
2108
 
2113
    /**
2109
    /**
2114
     * Return the first difference between this graph and another, ignoring order of fields.
2110
     * Return the first difference between this graph and another, ignoring order of fields.
2115
     * 
2111
     * 
2116
     * @param other another instance.
2112
     * @param other another instance.
2117
     * @return the first difference, <code>null</code> if equals.
2113
     * @return the first difference, <code>null</code> if equals.
2118
     */
2114
     */
2119
    public final String getGraphFirstDifference(final SQLRowValues other) {
2115
    public final String getGraphFirstDifference(final SQLRowValues other) {
2120
        return this.getGraphFirstDifference(other, false);
2116
        return this.getGraphFirstDifference(other, false);
2121
    }
2117
    }
2122
 
2118
 
2123
    /**
2119
    /**
2124
     * Return the first difference between this graph and another. Most of the time fields orders
2120
     * Return the first difference between this graph and another. Most of the time fields orders
2125
     * need not to be used, since when inserting they don't matter (which isn't true of the
2121
     * need not to be used, since when inserting they don't matter (which isn't true of the
2126
     * referents). But they can matter if e.g. this is used to construct a query.
2122
     * referents). But they can matter if e.g. this is used to construct a query.
2127
     * 
2123
     * 
2128
     * @param other another instance.
2124
     * @param other another instance.
2129
     * @param useOrder <code>true</code> to also compare the order of fields.
2125
     * @param useOrder <code>true</code> to also compare the order of fields.
2130
     * @return the first difference, <code>null</code> if equals.
2126
     * @return the first difference, <code>null</code> if equals.
2131
     */
2127
     */
2132
    public final String getGraphFirstDifference(final SQLRowValues other, final boolean useOrder) {
2128
    public final String getGraphFirstDifference(final SQLRowValues other, final boolean useOrder) {
2133
        if (this == other)
2129
        if (this == other)
2134
            return null;
2130
            return null;
2135
        return this.getGraph().getFirstDifference(this, other, useOrder, useOrder, true).getFirstDifference();
2131
        return this.getGraph().getFirstDifference(this, other, useOrder, useOrder, true).getFirstDifference();
2136
    }
2132
    }
2137
 
2133
 
2138
    public final boolean equalsJustThis(final SQLRowValues o) {
2134
    public final boolean equalsJustThis(final SQLRowValues o) {
2139
        // don't compare the order of fields, since inserting doesn't change with it
2135
        // don't compare the order of fields, since inserting doesn't change with it
2140
        return this.equalsJustThis(o, false);
2136
        return this.equalsJustThis(o, false);
2141
    }
2137
    }
2142
 
2138
 
2143
    /**
2139
    /**
2144
     * Whether this equals the passed instance without following linked rows. This method use
2140
     * Whether this equals the passed instance without following linked rows. This method use
2145
     * {@link ForeignCopyMode#COPY_ID_OR_RM}, so that a row having a foreign ID and a row having a
2141
     * {@link ForeignCopyMode#COPY_ID_OR_RM}, so that a row having a foreign ID and a row having a
2146
     * foreign row with the same ID are equal.
2142
     * foreign row with the same ID are equal.
2147
     * 
2143
     * 
2148
     * @param o another instance.
2144
     * @param o another instance.
2149
     * @param useFieldsOrder <code>true</code> if the order of {@link #getFields()} is to be
2145
     * @param useFieldsOrder <code>true</code> if the order of {@link #getFields()} is to be
2150
     *        checked.
2146
     *        checked.
2151
     * @return <code>true</code> if both rows have the same {@link #getFields() fields} defined, and
2147
     * @return <code>true</code> if both rows have the same {@link #getFields() fields} defined, and
2152
     *         {@link #getAllValues(ForeignCopyMode) all values} of this are equal to all values of
2148
     *         {@link #getAllValues(ForeignCopyMode) all values} of this are equal to all values of
2153
     *         <code>o</code>.
2149
     *         <code>o</code>.
2154
     * @see #equalsGraph(SQLRowValues)
2150
     * @see #equalsGraph(SQLRowValues)
2155
     */
2151
     */
2156
    public final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder) {
2152
    public final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder) {
2157
        return this.equalsJustThis(o, useFieldsOrder, true);
2153
        return this.equalsJustThis(o, useFieldsOrder, true);
2158
    }
2154
    }
2159
 
2155
 
2160
    final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder, final boolean useForeignID) {
2156
    final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder, final boolean useForeignID) {
2161
        return this.equalsJustThis(o, useFieldsOrder, useForeignID, true);
2157
        return this.equalsJustThis(o, useFieldsOrder, useForeignID, true);
2162
    }
2158
    }
2163
 
2159
 
2164
    final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder, final boolean useForeignID, final boolean usePK) {
2160
    final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder, final boolean useForeignID, final boolean usePK) {
2165
        if (this == o)
2161
        if (this == o)
2166
            return true;
2162
            return true;
2167
        if (!this.getTable().equals(o.getTable()))
2163
        if (!this.getTable().equals(o.getTable()))
2168
            return false;
2164
            return false;
2169
        // first compare keySet as ForeignCopyMode can remove entries
2165
        // first compare keySet as ForeignCopyMode can remove entries
2170
        if (useFieldsOrder) {
2166
        if (useFieldsOrder) {
2171
            if (!CompareUtils.equalsUsingIterator(this.values.keySet(), o.values.keySet()))
2167
            if (!CompareUtils.equalsUsingIterator(this.values.keySet(), o.values.keySet()))
2172
                return false;
2168
                return false;
2173
        } else {
2169
        } else {
2174
            if (!this.values.keySet().equals(o.values.keySet()))
2170
            if (!this.values.keySet().equals(o.values.keySet()))
2175
                return false;
2171
                return false;
2176
        }
2172
        }
2177
        // fields are already checked so if IDs are not wanted, just omit foreign rows
2173
        // fields are already checked so if IDs are not wanted, just omit foreign rows
2178
        final ForeignCopyMode copyMode = useForeignID ? ForeignCopyMode.COPY_ID_OR_RM : ForeignCopyMode.NO_COPY;
2174
        final ForeignCopyMode copyMode = useForeignID ? ForeignCopyMode.COPY_ID_OR_RM : ForeignCopyMode.NO_COPY;
2179
        final Map<String, Object> thisVals = this.getAllValues(copyMode, !usePK);
2175
        final Map<String, Object> thisVals = this.getAllValues(copyMode, !usePK);
2180
        final Map<String, Object> oVals = o.getAllValues(copyMode, !usePK);
2176
        final Map<String, Object> oVals = o.getAllValues(copyMode, !usePK);
2181
        if (!usePK) {
2177
        if (!usePK) {
2182
            final List<String> pk = this.getTable().getPKsNames();
2178
            final List<String> pk = this.getTable().getPKsNames();
2183
            thisVals.keySet().removeAll(pk);
2179
            thisVals.keySet().removeAll(pk);
2184
            oVals.keySet().removeAll(pk);
2180
            oVals.keySet().removeAll(pk);
2185
        }
2181
        }
2186
        // LinkedHashMap.equals() does not compare the order of entries
2182
        // LinkedHashMap.equals() does not compare the order of entries
2187
        return thisVals.equals(oVals);
2183
        return thisVals.equals(oVals);
2188
    }
2184
    }
2189
 
2185
 
2190
    // *** static
2186
    // *** static
2191
 
2187
 
2192
    static private Tuple2<PreparedStatement, List<String>> createInsertStatement(final SQLTable table, Map<String, Object> values) throws SQLException {
2188
    static private Tuple2<PreparedStatement, List<String>> createInsertStatement(final SQLTable table, Map<String, Object> values) throws SQLException {
2193
        final Tuple2<List<String>, List<Object>> l = CollectionUtils.mapToLists(values);
2189
        final Tuple2<List<String>, List<Object>> l = CollectionUtils.mapToLists(values);
2194
        final List<String> fieldsNames = l.get0();
2190
        final List<String> fieldsNames = l.get0();
2195
        final List<Object> vals = l.get1();
2191
        final List<Object> vals = l.get1();
2196
 
2192
 
2197
        addMetadata(fieldsNames, vals, table.getCreationUserField(), getUser());
2193
        addMetadata(fieldsNames, vals, table.getCreationUserField(), getUser());
2198
        addMetadata(fieldsNames, vals, table.getCreationDateField(), new Timestamp(System.currentTimeMillis()));
2194
        addMetadata(fieldsNames, vals, table.getCreationDateField(), new Timestamp(System.currentTimeMillis()));
2199
 
2195
 
2200
        return createStatement(table, fieldsNames, vals, true);
2196
        return createStatement(table, fieldsNames, vals, true);
2201
    }
2197
    }
2202
 
2198
 
2203
    static private Tuple2<PreparedStatement, List<String>> createUpdateStatement(SQLTable table, Map<String, Object> values, int id) throws SQLException {
2199
    static private Tuple2<PreparedStatement, List<String>> createUpdateStatement(SQLTable table, Map<String, Object> values, int id) throws SQLException {
2204
        final Tuple2<List<String>, List<Object>> l = CollectionUtils.mapToLists(values);
2200
        final Tuple2<List<String>, List<Object>> l = CollectionUtils.mapToLists(values);
2205
        final List<String> fieldsNames = l.get0();
2201
        final List<String> fieldsNames = l.get0();
2206
        final List<Object> vals = l.get1();
2202
        final List<Object> vals = l.get1();
2207
 
2203
 
2208
        vals.add(new Integer(id));
2204
        vals.add(new Integer(id));
2209
        return createStatement(table, fieldsNames, vals, false);
2205
        return createStatement(table, fieldsNames, vals, false);
2210
    }
2206
    }
2211
 
2207
 
2212
    static private void addMetadata(List<String> fieldsNames, List<Object> values, SQLField field, Object fieldValue) throws SQLException {
2208
    static private void addMetadata(List<String> fieldsNames, List<Object> values, SQLField field, Object fieldValue) throws SQLException {
2213
        if (field != null) {
2209
        if (field != null) {
2214
            // TODO updateVerbatim to force a value
2210
            // TODO updateVerbatim to force a value
2215
            final int index = fieldsNames.indexOf(field.getName());
2211
            final int index = fieldsNames.indexOf(field.getName());
2216
            if (index < 0) {
2212
            if (index < 0) {
2217
                // ajout au dbt car le where du UPDATE a besoin de l'ID en dernier
2213
                // ajout au dbt car le where du UPDATE a besoin de l'ID en dernier
2218
                fieldsNames.add(0, field.getName());
2214
                fieldsNames.add(0, field.getName());
2219
                values.add(0, fieldValue);
2215
                values.add(0, fieldValue);
2220
            } else {
2216
            } else {
2221
                values.set(index, fieldValue);
2217
                values.set(index, fieldValue);
2222
            }
2218
            }
2223
        }
2219
        }
2224
    }
2220
    }
2225
 
2221
 
2226
    static private Object getUser() {
2222
    static private Object getUser() {
2227
        final int userID = UserManager.getUserID();
2223
        final int userID = UserManager.getUserID();
2228
        return userID < SQLRow.MIN_VALID_ID ? SQL_DEFAULT : userID;
2224
        return userID < SQLRow.MIN_VALID_ID ? SQL_DEFAULT : userID;
2229
    }
2225
    }
2230
 
2226
 
2231
    /**
2227
    /**
2232
     * Create a prepared statement.
2228
     * Create a prepared statement.
2233
     * 
2229
     * 
2234
     * @param table the table to change.
2230
     * @param table the table to change.
2235
     * @param fieldsNames the columns names of <code>table</code>.
2231
     * @param fieldsNames the columns names of <code>table</code>.
2236
     * @param values their values.
2232
     * @param values their values.
2237
     * @param insert whether to insert or update.
2233
     * @param insert whether to insert or update.
2238
     * @return the new statement and its columns.
2234
     * @return the new statement and its columns.
2239
     * @throws SQLException if an error occurs.
2235
     * @throws SQLException if an error occurs.
2240
     */
2236
     */
2241
    static private Tuple2<PreparedStatement, List<String>> createStatement(SQLTable table, List<String> fieldsNames, List<Object> values, boolean insert) throws SQLException {
2237
    static private Tuple2<PreparedStatement, List<String>> createStatement(SQLTable table, List<String> fieldsNames, List<Object> values, boolean insert) throws SQLException {
2242
        addMetadata(fieldsNames, values, table.getModifUserField(), getUser());
2238
        addMetadata(fieldsNames, values, table.getModifUserField(), getUser());
2243
        addMetadata(fieldsNames, values, table.getModifDateField(), new Timestamp(System.currentTimeMillis()));
2239
        addMetadata(fieldsNames, values, table.getModifDateField(), new Timestamp(System.currentTimeMillis()));
2244
 
2240
 
2245
        final PreparedStatement pStmt;
2241
        final PreparedStatement pStmt;
2246
        final String tableQuoted = table.getSQLName().quote();
2242
        final String tableQuoted = table.getSQLName().quote();
2247
        String req = (insert ? "INSERT INTO " : "UPDATE ") + tableQuoted + " ";
2243
        String req = (insert ? "INSERT INTO " : "UPDATE ") + tableQuoted + " ";
2248
        if (insert) {
2244
        if (insert) {
2249
            assert fieldsNames.size() == values.size();
2245
            assert fieldsNames.size() == values.size();
2250
            // remove DEFAULT since they are useless and prevent us from using
2246
            // remove DEFAULT since they are useless and prevent us from using
2251
            // INSERT INTO "TABLEAU_ELECTRIQUE" ("ID_OBSERVATION", ...) select DEFAULT, ?,
2247
            // INSERT INTO "TABLEAU_ELECTRIQUE" ("ID_OBSERVATION", ...) select DEFAULT, ?,
2252
            // MAX("ORDRE") + 1 FROM "TABLEAU_ELECTRIQUE"
2248
            // MAX("ORDRE") + 1 FROM "TABLEAU_ELECTRIQUE"
2253
            for (int i = values.size() - 1; i >= 0; i--) {
2249
            for (int i = values.size() - 1; i >= 0; i--) {
2254
                if (values.get(i) == SQL_DEFAULT) {
2250
                if (values.get(i) == SQL_DEFAULT) {
2255
                    fieldsNames.remove(i);
2251
                    fieldsNames.remove(i);
2256
                    values.remove(i);
2252
                    values.remove(i);
2257
                }
2253
                }
2258
            }
2254
            }
2259
            assert fieldsNames.size() == values.size();
2255
            assert fieldsNames.size() == values.size();
2260
 
2256
 
2261
            // ajout de l'ordre
2257
            // ajout de l'ordre
2262
            final SQLField order = table.getOrderField();
2258
            final SQLField order = table.getOrderField();
2263
            final boolean selectOrder;
2259
            final boolean selectOrder;
2264
            if (order != null && !fieldsNames.contains(order.getName())) {
2260
            if (order != null && !fieldsNames.contains(order.getName())) {
2265
                // si l'ordre n'est pas spécifié, ajout à la fin
2261
                // si l'ordre n'est pas spécifié, ajout à la fin
2266
                fieldsNames.add(order.getName());
2262
                fieldsNames.add(order.getName());
2267
                selectOrder = true;
2263
                selectOrder = true;
2268
            } else {
2264
            } else {
2269
                selectOrder = false;
2265
                selectOrder = false;
2270
            }
2266
            }
2271
 
2267
 
2272
            if (fieldsNames.size() == 0 && table.getServer().getSQLSystem() != SQLSystem.MYSQL) {
2268
            if (fieldsNames.size() == 0 && table.getServer().getSQLSystem() != SQLSystem.MYSQL) {
2273
                // "LOCAL" () VALUES () is a syntax error on PG
2269
                // "LOCAL" () VALUES () is a syntax error on PG
2274
                req += "DEFAULT VALUES";
2270
                req += "DEFAULT VALUES";
2275
            } else {
2271
            } else {
2276
                req += "(" + CollectionUtils.join(fieldsNames, ", ", new ITransformer<String, String>() {
2272
                req += "(" + CollectionUtils.join(fieldsNames, ", ", new ITransformer<String, String>() {
2277
                    public String transformChecked(String input) {
2273
                    public String transformChecked(String input) {
2278
                        return SQLBase.quoteIdentifier(input);
2274
                        return SQLBase.quoteIdentifier(input);
2279
                    }
2275
                    }
2280
                }) + ")";
2276
                }) + ")";
2281
                // no DEFAULT thus only ?
2277
                // no DEFAULT thus only ?
2282
                final String questionMarks = CollectionUtils.join(Collections.nCopies(values.size(), "?"), ", ");
2278
                final String questionMarks = CollectionUtils.join(Collections.nCopies(values.size(), "?"), ", ");
2283
                if (selectOrder) {
2279
                if (selectOrder) {
2284
                    // needed since VALUES ( (select MAX("ORDRE") from "LOCAL") ) on MySQL yield
2280
                    // needed since VALUES ( (select MAX("ORDRE") from "LOCAL") ) on MySQL yield
2285
                    // "You can't specify target table 'LOCAL' for update in FROM clause"
2281
                    // "You can't specify target table 'LOCAL' for update in FROM clause"
2286
                    req += " select ";
2282
                    req += " select ";
2287
                    req += questionMarks;
2283
                    req += questionMarks;
2288
                    if (values.size() > 0)
2284
                    if (values.size() > 0)
2289
                        req += ", ";
2285
                        req += ", ";
2290
                    // COALESCE for empty tables, MIN_ORDER + 1 since MIN_ORDER cannot be moved
2286
                    // COALESCE for empty tables, MIN_ORDER + 1 since MIN_ORDER cannot be moved
2291
                    req += "COALESCE(MAX(" + SQLBase.quoteIdentifier(order.getName()) + "), " + ReOrder.MIN_ORDER + ") + 1 FROM " + tableQuoted;
2287
                    req += "COALESCE(MAX(" + SQLBase.quoteIdentifier(order.getName()) + "), " + ReOrder.MIN_ORDER + ") + 1 FROM " + tableQuoted;
2292
                } else {
2288
                } else {
2293
                    req += " VALUES (";
2289
                    req += " VALUES (";
2294
                    req += questionMarks;
2290
                    req += questionMarks;
2295
                    req += ")";
2291
                    req += ")";
2296
                }
2292
                }
2297
            }
2293
            }
2298
            pStmt = createInsertStatement(req, table);
2294
            pStmt = createInsertStatement(req, table);
2299
        } else {
2295
        } else {
2300
            // ID at the end
2296
            // ID at the end
2301
            assert fieldsNames.size() == values.size() - 1;
2297
            assert fieldsNames.size() == values.size() - 1;
2302
            final List<String> fieldAndValues = new ArrayList<String>(fieldsNames.size());
2298
            final List<String> fieldAndValues = new ArrayList<String>(fieldsNames.size());
2303
            final ListIterator<String> iter = fieldsNames.listIterator();
2299
            final ListIterator<String> iter = fieldsNames.listIterator();
2304
            while (iter.hasNext()) {
2300
            while (iter.hasNext()) {
2305
                final String fieldName = iter.next();
2301
                final String fieldName = iter.next();
2306
                final SQLField field = table.getField(fieldName);
2302
                final SQLField field = table.getField(fieldName);
2307
                final Object value = values.get(iter.previousIndex());
2303
                final Object value = values.get(iter.previousIndex());
2308
                // postgresql doesn't support prefixing fields with their tables in an update
2304
                // postgresql doesn't support prefixing fields with their tables in an update
2309
                fieldAndValues.add(SQLBase.quoteIdentifier(field.getName()) + "= " + getFieldValue(value));
2305
                fieldAndValues.add(SQLBase.quoteIdentifier(field.getName()) + "= " + getFieldValue(value));
2310
            }
2306
            }
2311
 
2307
 
2312
            req += "SET " + CollectionUtils.join(fieldAndValues, ", ");
2308
            req += "SET " + CollectionUtils.join(fieldAndValues, ", ");
2313
            req += " WHERE " + table.getKey().getFieldRef() + "= ?";
2309
            req += " WHERE " + table.getKey().getFieldRef() + "= ?";
2314
            final Connection c = table.getBase().getDataSource().getConnection();
2310
            final Connection c = table.getBase().getDataSource().getConnection();
2315
            pStmt = c.prepareStatement(req);
2311
            pStmt = c.prepareStatement(req);
2316
        }
2312
        }
2317
        // set fields values
2313
        // set fields values
2318
        int i = 0;
2314
        int i = 0;
2319
        for (final Object value : values) {
2315
        for (final Object value : values) {
2320
            // nothing to set if there's no corresponding '?'
2316
            // nothing to set if there's no corresponding '?'
2321
            if (value != SQL_DEFAULT) {
2317
            if (value != SQL_DEFAULT) {
2322
                final Object toIns;
2318
                final Object toIns;
2323
                if (value instanceof SQLRowValues) {
2319
                if (value instanceof SQLRowValues) {
2324
                    // TODO if we already point to some row, archive it
2320
                    // TODO if we already point to some row, archive it
2325
                    toIns = ((SQLRowValues) value).insert().getIDNumber();
2321
                    toIns = ((SQLRowValues) value).insert().getIDNumber();
2326
                } else
2322
                } else
2327
                    toIns = value;
2323
                    toIns = value;
2328
                // sql index start at 1
2324
                // sql index start at 1
2329
                pStmt.setObject(i + 1, toIns);
2325
                pStmt.setObject(i + 1, toIns);
2330
                i++;
2326
                i++;
2331
            }
2327
            }
2332
        }
2328
        }
2333
        return Tuple2.create(pStmt, fieldsNames);
2329
        return Tuple2.create(pStmt, fieldsNames);
2334
    }
2330
    }
2335
 
2331
 
2336
    private static String getFieldValue(final Object value) {
2332
    private static String getFieldValue(final Object value) {
2337
        return value == SQL_DEFAULT ? "DEFAULT" : "?";
2333
        return value == SQL_DEFAULT ? "DEFAULT" : "?";
2338
    }
2334
    }
2339
 
2335
 
2340
    @Override
2336
    @Override
2341
    public SQLTableModifiedListener createTableListener(SQLDataListener l) {
2337
    public SQLTableModifiedListener createTableListener(SQLDataListener l) {
2342
        return new SQLTableListenerData<SQLRowValues>(this, l);
2338
        return new SQLTableListenerData<SQLRowValues>(this, l);
2343
    }
2339
    }
2344
 
2340
 
2345
    // *** static
2341
    // *** static
2346
 
2342
 
2347
    /**
2343
    /**
2348
     * Create an insert statement which can provide the inserted ID.
2344
     * Create an insert statement which can provide the inserted ID.
2349
     * 
2345
     * 
2350
     * @param req the INSERT sql.
2346
     * @param req the INSERT sql.
2351
     * @param table the table where the row will be inserted.
2347
     * @param table the table where the row will be inserted.
2352
     * @return a new <code>PreparedStatement</code> object, containing the pre-compiled SQL
2348
     * @return a new <code>PreparedStatement</code> object, containing the pre-compiled SQL
2353
     *         statement, that will have the capability of returning the primary key.
2349
     *         statement, that will have the capability of returning the primary key.
2354
     * @throws SQLException if a database access error occurs.
2350
     * @throws SQLException if a database access error occurs.
2355
     * @see #insert(PreparedStatement, SQLTable)
2351
     * @see #insert(PreparedStatement, SQLTable)
2356
     */
2352
     */
2357
    static public final PreparedStatement createInsertStatement(String req, final SQLTable table) throws SQLException {
2353
    static public final PreparedStatement createInsertStatement(String req, final SQLTable table) throws SQLException {
2358
        final boolean rowable = table.isRowable();
2354
        final boolean rowable = table.isRowable();
2359
        final boolean isPG = table.getServer().getSQLSystem() == SQLSystem.POSTGRESQL;
2355
        final boolean isPG = table.getServer().getSQLSystem() == SQLSystem.POSTGRESQL;
2360
        if (rowable && isPG)
2356
        if (rowable && isPG)
2361
            req += " RETURNING " + SQLBase.quoteIdentifier(table.getKey().getName());
2357
            req += " RETURNING " + SQLBase.quoteIdentifier(table.getKey().getName());
2362
        final Connection c = table.getDBSystemRoot().getDataSource().getConnection();
2358
        final Connection c = table.getDBSystemRoot().getDataSource().getConnection();
2363
        final int returnGenK = rowable && !isPG && c.getMetaData().supportsGetGeneratedKeys() ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS;
2359
        final int returnGenK = rowable && !isPG && c.getMetaData().supportsGetGeneratedKeys() ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS;
2364
        return c.prepareStatement(req, returnGenK);
2360
        return c.prepareStatement(req, returnGenK);
2365
    }
2361
    }
2366
 
2362
 
2367
    /**
2363
    /**
2368
     * Execute the passed INSERT statement and return the ID of the new row.
2364
     * Execute the passed INSERT statement and return the ID of the new row.
2369
     * 
2365
     * 
2370
     * @param pStmt an INSERT statement (should have been obtained using
2366
     * @param pStmt an INSERT statement (should have been obtained using
2371
     *        {@link #createInsertStatement(String, SQLTable)}).
2367
     *        {@link #createInsertStatement(String, SQLTable)}).
2372
     * @param table the table where the row will be inserted.
2368
     * @param table the table where the row will be inserted.
2373
     * @return the new ID.
2369
     * @return the new ID.
2374
     * @throws SQLException if the insertion fails.
2370
     * @throws SQLException if the insertion fails.
2375
     */
2371
     */
2376
    static public final Number insert(final PreparedStatement pStmt, final SQLTable table) throws SQLException {
2372
    static public final Number insert(final PreparedStatement pStmt, final SQLTable table) throws SQLException {
2377
        final long timeMs = System.currentTimeMillis();
2373
        final long timeMs = System.currentTimeMillis();
2378
 
2374
 
2379
        final long time = System.nanoTime();
2375
        final long time = System.nanoTime();
2380
        pStmt.execute();
2376
        pStmt.execute();
2381
        final long afterExecute = System.nanoTime();
2377
        final long afterExecute = System.nanoTime();
2382
 
2378
 
2383
        final Number newID;
2379
        final Number newID;
2384
        if (table.isRowable()) {
2380
        if (table.isRowable()) {
2385
            final ResultSet rs;
2381
            final ResultSet rs;
2386
            if (table.getServer().getSQLSystem() == SQLSystem.POSTGRESQL) {
2382
            if (table.getServer().getSQLSystem() == SQLSystem.POSTGRESQL) {
2387
                // uses RETURNING
2383
                // uses RETURNING
2388
                rs = pStmt.getResultSet();
2384
                rs = pStmt.getResultSet();
2389
            } else {
2385
            } else {
2390
                rs = pStmt.getGeneratedKeys();
2386
                rs = pStmt.getGeneratedKeys();
2391
            }
2387
            }
2392
            try {
2388
            try {
2393
                if (rs.next()) {
2389
                if (rs.next()) {
2394
                    newID = (Number) rs.getObject(1);
2390
                    newID = (Number) rs.getObject(1);
2395
                } else
2391
                } else
2396
                    throw new IllegalStateException("no keys have been autogenerated for the successfully executed statement :" + pStmt);
2392
                    throw new IllegalStateException("no keys have been autogenerated for the successfully executed statement :" + pStmt);
2397
            } catch (SQLException exn) {
2393
            } catch (SQLException exn) {
2398
                throw new IllegalStateException("can't get autogenerated keys for the successfully executed statement :" + pStmt);
2394
                throw new IllegalStateException("can't get autogenerated keys for the successfully executed statement :" + pStmt);
2399
            }
2395
            }
2400
        } else {
2396
        } else {
2401
            newID = null;
2397
            newID = null;
2402
        }
2398
        }
2403
        final long afterHandle = System.nanoTime();
2399
        final long afterHandle = System.nanoTime();
2404
        SQLRequestLog.log(pStmt, "rowValues.insert()", timeMs, time, afterExecute, afterExecute, afterExecute, afterHandle, System.nanoTime());
2400
        SQLRequestLog.log(pStmt, "rowValues.insert()", timeMs, time, afterExecute, afterExecute, afterExecute, afterHandle, System.nanoTime());
2405
 
2401
 
2406
        return newID;
2402
        return newID;
2407
    }
2403
    }
2408
 
2404
 
2409
    /**
2405
    /**
2410
     * Insert rows in the passed table.
2406
     * Insert rows in the passed table.
2411
     * 
2407
     * 
2412
     * @param t a table, eg /LOCAL/.
2408
     * @param t a table, eg /LOCAL/.
2413
     * @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
2409
     * @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
2414
     * @return the inserted IDs, or <code>null</code> if <code>t</code> is not
2410
     * @return the inserted IDs, or <code>null</code> if <code>t</code> is not
2415
     *         {@link SQLTable#isRowable() rowable}.
2411
     *         {@link SQLTable#isRowable() rowable}.
2416
     * @throws SQLException if an error occurs while inserting.
2412
     * @throws SQLException if an error occurs while inserting.
2417
     */
2413
     */
2418
    @SuppressWarnings("unchecked")
2414
    @SuppressWarnings("unchecked")
2419
    public static final List<Number> insertIDs(final SQLTable t, final String sql) throws SQLException {
2415
    public static final List<Number> insertIDs(final SQLTable t, final String sql) throws SQLException {
2420
        final boolean rowable = t.isRowable();
2416
        final boolean rowable = t.isRowable();
2421
        final Insertion<?> res = insert(t, sql, rowable ? ReturnMode.FIRST_FIELD : ReturnMode.NO_FIELDS);
2417
        final Insertion<?> res = insert(t, sql, rowable ? ReturnMode.FIRST_FIELD : ReturnMode.NO_FIELDS);
2422
        if (rowable)
2418
        if (rowable)
2423
            return ((Insertion<Number>) res).getRows();
2419
            return ((Insertion<Number>) res).getRows();
2424
        else
2420
        else
2425
            return null;
2421
            return null;
2426
    }
2422
    }
2427
 
2423
 
2428
    /**
2424
    /**
2429
     * Insert rows in the passed table.
2425
     * Insert rows in the passed table.
2430
     * 
2426
     * 
2431
     * @param t a table, eg /LOCAL/.
2427
     * @param t a table, eg /LOCAL/.
2432
     * @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
2428
     * @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
2433
     * @return an object to always know the insertion count and possibly the inserted primary keys.
2429
     * @return an object to always know the insertion count and possibly the inserted primary keys.
2434
     * @throws SQLException if an error occurs while inserting.
2430
     * @throws SQLException if an error occurs while inserting.
2435
     */
2431
     */
2436
    @SuppressWarnings("unchecked")
2432
    @SuppressWarnings("unchecked")
2437
    public static final Insertion<List<Object>> insert(final SQLTable t, final String sql) throws SQLException {
2433
    public static final Insertion<List<Object>> insert(final SQLTable t, final String sql) throws SQLException {
2438
        return (Insertion<List<Object>>) insert(t, sql, ReturnMode.ALL_FIELDS);
2434
        return (Insertion<List<Object>>) insert(t, sql, ReturnMode.ALL_FIELDS);
2439
    }
2435
    }
2440
 
2436
 
2441
    /**
2437
    /**
2442
     * Insert rows in the passed table. Should be faster than other insert methods since it doesn't
2438
     * Insert rows in the passed table. Should be faster than other insert methods since it doesn't
2443
     * fetch primary keys.
2439
     * fetch primary keys.
2444
     * 
2440
     * 
2445
     * @param t a table, eg /LOCAL/.
2441
     * @param t a table, eg /LOCAL/.
2446
     * @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
2442
     * @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
2447
     * @return the insertion count.
2443
     * @return the insertion count.
2448
     * @throws SQLException if an error occurs while inserting.
2444
     * @throws SQLException if an error occurs while inserting.
2449
     */
2445
     */
2450
    public static final int insertCount(final SQLTable t, final String sql) throws SQLException {
2446
    public static final int insertCount(final SQLTable t, final String sql) throws SQLException {
2451
        return insert(t, sql, ReturnMode.NO_FIELDS).getCount();
2447
        return insert(t, sql, ReturnMode.NO_FIELDS).getCount();
2452
    }
2448
    }
2453
 
2449
 
2454
    // if scalar is null primary keys aren't fetched
2450
    // if scalar is null primary keys aren't fetched
2455
    private static final Insertion<?> insert(final SQLTable t, final String sql, final ReturnMode mode) throws SQLException {
2451
    private static final Insertion<?> insert(final SQLTable t, final String sql, final ReturnMode mode) throws SQLException {
2456
        return new Inserter(t).insert(sql, mode, true);
2452
        return new Inserter(t).insert(sql, mode, true);
2457
    }
2453
    }
2458
 
2454
 
2459
    /**
2455
    /**
2460
     * Insert rows in the passed table.
2456
     * Insert rows in the passed table.
2461
     * 
2457
     * 
2462
     * @param t a table, eg /LOCAL/.
2458
     * @param t a table, eg /LOCAL/.
2463
     * @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
2459
     * @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
2464
     * @return the inserted rows (with no values, ie a call to a getter will trigger a db access),
2460
     * @return the inserted rows (with no values, ie a call to a getter will trigger a db access),
2465
     *         or <code>null</code> if <code>t</code> is not {@link SQLTable#isRowable() rowable}.
2461
     *         or <code>null</code> if <code>t</code> is not {@link SQLTable#isRowable() rowable}.
2466
     * @throws SQLException if an error occurs while inserting.
2462
     * @throws SQLException if an error occurs while inserting.
2467
     */
2463
     */
2468
    public static final List<SQLRow> insertRows(final SQLTable t, final String sql) throws SQLException {
2464
    public static final List<SQLRow> insertRows(final SQLTable t, final String sql) throws SQLException {
2469
        final List<Number> ids = insertIDs(t, sql);
2465
        final List<Number> ids = insertIDs(t, sql);
2470
        if (ids == null)
2466
        if (ids == null)
2471
            return null;
2467
            return null;
2472
        final List<SQLRow> res = new ArrayList<SQLRow>(ids.size());
2468
        final List<SQLRow> res = new ArrayList<SQLRow>(ids.size());
2473
        for (final Number id : ids)
2469
        for (final Number id : ids)
2474
            res.add(new SQLRow(t, id.intValue()));
2470
            res.add(new SQLRow(t, id.intValue()));
2475
        return res;
2471
        return res;
2476
    }
2472
    }
2477
 
2473
 
2478
    // MAYBE add insertFromSelect(SQLTable, SQLSelect) if aliases are kept in SQLSelect (so that we
2474
    // MAYBE add insertFromSelect(SQLTable, SQLSelect) if aliases are kept in SQLSelect (so that we
2479
    // can map arbitray expressions to fields in the destination table)
2475
    // can map arbitray expressions to fields in the destination table)
2480
    public static final int insertFromTable(final SQLTable dest, final SQLTable src) throws SQLException {
2476
    public static final int insertFromTable(final SQLTable dest, final SQLTable src) throws SQLException {
2481
        return insertFromTable(dest, src, src.getChildrenNames());
2477
        return insertFromTable(dest, src, src.getChildrenNames());
2482
    }
2478
    }
2483
 
2479
 
2484
    /**
2480
    /**
2485
     * Copy all rows from <code>src</code> to <code>dest</code>.
2481
     * Copy all rows from <code>src</code> to <code>dest</code>.
2486
     * 
2482
     * 
2487
     * @param dest the table where rows will be inserted.
2483
     * @param dest the table where rows will be inserted.
2488
     * @param src the table where rows will be selected.
2484
     * @param src the table where rows will be selected.
2489
     * @param fieldsNames the fields to use.
2485
     * @param fieldsNames the fields to use.
2490
     * @return the insertion count.
2486
     * @return the insertion count.
2491
     * @throws SQLException if an error occurs while inserting.
2487
     * @throws SQLException if an error occurs while inserting.
2492
     */
2488
     */
2493
    public static final int insertFromTable(final SQLTable dest, final SQLTable src, final Set<String> fieldsNames) throws SQLException {
2489
    public static final int insertFromTable(final SQLTable dest, final SQLTable src, final Set<String> fieldsNames) throws SQLException {
2494
        if (dest.getDBSystemRoot() != src.getDBSystemRoot())
2490
        if (dest.getDBSystemRoot() != src.getDBSystemRoot())
2495
            throw new IllegalArgumentException("Tables are not on the same system root : " + dest.getSQLName() + " / " + src.getSQLName());
2491
            throw new IllegalArgumentException("Tables are not on the same system root : " + dest.getSQLName() + " / " + src.getSQLName());
2496
        if (!dest.getChildrenNames().containsAll(fieldsNames))
2492
        if (!dest.getChildrenNames().containsAll(fieldsNames))
2497
            throw new IllegalArgumentException("Destination table " + dest.getSQLName() + " doesn't contain all fields of the source " + src + " : " + fieldsNames);
2493
            throw new IllegalArgumentException("Destination table " + dest.getSQLName() + " doesn't contain all fields of the source " + src + " : " + fieldsNames);
2498
 
2494
 
2499
        final List<SQLField> fields = new ArrayList<SQLField>(fieldsNames.size());
2495
        final List<SQLField> fields = new ArrayList<SQLField>(fieldsNames.size());
2500
        for (final String fName : fieldsNames)
2496
        for (final String fName : fieldsNames)
2501
            fields.add(src.getField(fName));
2497
            fields.add(src.getField(fName));
2502
        final SQLSelect sel = new SQLSelect(true);
2498
        final SQLSelect sel = new SQLSelect(true);
2503
        sel.addAllSelect(fields);
2499
        sel.addAllSelect(fields);
2504
        final String colNames = "(" + CollectionUtils.join(fields, ",", new ITransformer<SQLField, String>() {
2500
        final String colNames = "(" + CollectionUtils.join(fields, ",", new ITransformer<SQLField, String>() {
2505
            @Override
2501
            @Override
2506
            public String transformChecked(SQLField input) {
2502
            public String transformChecked(SQLField input) {
2507
                return SQLBase.quoteIdentifier(input.getName());
2503
                return SQLBase.quoteIdentifier(input.getName());
2508
            }
2504
            }
2509
        }) + ") ";
2505
        }) + ") ";
2510
        return insertCount(dest, colNames + sel.asString());
2506
        return insertCount(dest, colNames + sel.asString());
2511
    }
2507
    }
2512
 
2508
 
2513
    /**
2509
    /**
2514
     * Trim a collection of SQLRowValues.
2510
     * Trim a collection of SQLRowValues.
2515
     * 
2511
     * 
2516
     * @param graphs the rowValues to trim.
2512
     * @param graphs the rowValues to trim.
2517
     * @return a copy of <code>graphs</code> without any linked SQLRowValues.
2513
     * @return a copy of <code>graphs</code> without any linked SQLRowValues.
2518
     */
2514
     */
2519
    public static final List<SQLRowValues> trim(final Collection<SQLRowValues> graphs) {
2515
    public static final List<SQLRowValues> trim(final Collection<SQLRowValues> graphs) {
2520
        final List<SQLRowValues> res = new ArrayList<SQLRowValues>(graphs.size());
2516
        final List<SQLRowValues> res = new ArrayList<SQLRowValues>(graphs.size());
2521
        for (final SQLRowValues r : graphs)
2517
        for (final SQLRowValues r : graphs)
2522
            res.add(trim(r));
2518
            res.add(trim(r));
2523
        return res;
2519
        return res;
2524
    }
2520
    }
2525
 
2521
 
2526
    public static final SQLRowValues trim(final SQLRowValues r) {
2522
    public static final SQLRowValues trim(final SQLRowValues r) {
2527
        return new SQLRowValues(r, ForeignCopyMode.COPY_ID_OR_RM);
2523
        return new SQLRowValues(r, ForeignCopyMode.COPY_ID_OR_RM);
2528
    }
2524
    }
2529
}
2525
}