OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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

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