OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 151 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.sql.view.list;
15
 
16
import org.openconcerto.sql.model.FieldPath;
73 ilm 17
import org.openconcerto.sql.model.SQLField;
17 ilm 18
import org.openconcerto.sql.model.SQLRowValues;
73 ilm 19
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
17 ilm 20
import org.openconcerto.sql.model.SQLTable;
142 ilm 21
import org.openconcerto.sql.model.SQLTable.VirtualFields;
17 ilm 22
import org.openconcerto.sql.model.graph.Path;
73 ilm 23
import org.openconcerto.sql.view.list.search.SearchQueue;
17 ilm 24
import org.openconcerto.utils.CollectionUtils;
151 ilm 25
import org.openconcerto.utils.NumberUtils;
17 ilm 26
 
27
import java.util.ArrayList;
28
import java.util.Collection;
29
import java.util.Collections;
30
import java.util.HashSet;
31
import java.util.List;
32
import java.util.Set;
33
 
93 ilm 34
import net.jcip.annotations.GuardedBy;
35
import net.jcip.annotations.ThreadSafe;
36
 
17 ilm 37
/**
38
 * A line used by SQLTableModelSource, posessing an order and an id. Compare is done on the order.
39
 *
40
 * @author Sylvain
41
 */
93 ilm 42
@ThreadSafe
17 ilm 43
public final class ListSQLLine implements Comparable<ListSQLLine> {
44
 
45
    public static final int indexFromID(final List<ListSQLLine> l, final int id) {
46
        int foundIndex = -1;
47
        final int size = l.size();
48
        for (int i = 0; i < size; i++) {
49
            final int currentID = l.get(i).getID();
50
            if (currentID == id) {
51
                foundIndex = i;
52
                break;
53
            }
54
        }
55
        return foundIndex;
56
    }
57
 
93 ilm 58
    static final ListSQLLine fromID(final List<ListSQLLine> list, final int id) {
59
        final int size = list.size();
60
        for (int i = 0; i < size; i++) {
61
            final ListSQLLine line = list.get(i);
62
            if (line.getID() == id)
63
                return line;
64
        }
65
        return null;
66
    }
67
 
17 ilm 68
    private final SQLTableModelLinesSource src;
93 ilm 69
    // unmodifiable
70
    @GuardedBy("this")
71
    private SQLRowValues row;
72
    // Immutable
142 ilm 73
    private final SQLTableModelSourceState state;
17 ilm 74
    private final int id;
93 ilm 75
    // allow to order by something not in the row
76
    @GuardedBy("this")
77
    private Number order;
17 ilm 78
    // lists are accessed by Swing (model.getValueAt()) and
79
    // by the search queue (SearchRunnable#matchFilter(ListSQLLine line))
93 ilm 80
    // immutable
81
    @GuardedBy("this")
82
    private List<Object> list;
17 ilm 83
 
142 ilm 84
    ListSQLLine(SQLTableModelLinesSource src, SQLRowValues row, int id, final SQLTableModelSourceState state) {
17 ilm 85
        super();
86
        this.src = src;
93 ilm 87
        this.setRow(row);
17 ilm 88
        this.id = id;
142 ilm 89
        this.state = state;
93 ilm 90
        this.clearCache();
17 ilm 91
    }
92
 
93
    // load at least columnCount values
93 ilm 94
    // (to avoid loading debug columns, which took more time than the regular columns, ie more than
95
    // half the time was passed on almost never displayed values)
96
    private void loadCache(int columnCount) {
97
        updateList(columnCount, Collections.<Integer> emptySet());
17 ilm 98
    }
99
 
142 ilm 100
    public final SQLTableModelSourceState getState() {
101
        return this.state;
102
    }
103
 
104
    public final SQLTableModelColumns getColumns() {
105
        return this.getState().getAllColumns();
106
    }
107
 
17 ilm 108
    public final SQLTableModelLinesSource getSrc() {
109
        return this.src;
110
    }
111
 
93 ilm 112
    private final void setRow(SQLRowValues v) {
113
        if (!v.isFrozen())
114
            throw new IllegalArgumentException("Not frozen : " + v);
115
        synchronized (this) {
116
            this.row = v;
117
        }
118
    }
119
 
120
    public synchronized final SQLRowValues getRow() {
17 ilm 121
        return this.row;
122
    }
123
 
124
    @Override
125
    public int compareTo(ListSQLLine o) {
126
        if (this.src != o.src)
127
            throw new IllegalArgumentException(this.src + " != " + o.src);
128
        return this.src.compare(this, o);
129
    }
130
 
151 ilm 131
    static final int compareLikeRequest(final ListSQLLine l1, final ListSQLLine l2) {
132
        if (l1 == l2)
133
            return 0;
134
        if (l1.getState() != l2.getState())
135
            throw new IllegalArgumentException("Different states");
136
 
137
        final Number order1, order2;
138
        final SQLTableModelLinesSource src = l1.getSrc();
139
        // avoid comparing in the middle of a reordering
140
        synchronized (src.getModel().getUpdateQ().getFullList()) {
141
            order1 = l1.getOrder();
142
            order2 = l2.getOrder();
143
        }
144
        if (order1 != null) {
145
            if (order2 == null)
146
                throw new IllegalStateException("Order mismatch :\n" + order1 + " for " + l1 + " not coherent with\n" + order2 + " for " + l2);
147
            return NumberUtils.compare(order1, order2);
148
        } else {
149
            if (order2 != null)
150
                throw new IllegalStateException("Order mismatch :\n" + order1 + " for " + l1 + " not coherent with\n" + order2 + " for " + l2);
151
            return l1.getState().getReq().order(l1.getRow(), l2.getRow());
152
        }
153
    }
154
 
17 ilm 155
    public int getID() {
156
        return this.id;
157
    }
158
 
93 ilm 159
    public synchronized final void setOrder(Number order) {
160
        this.order = order;
161
    }
162
 
163
    public synchronized final Number getOrder() {
164
        return this.order;
165
    }
166
 
17 ilm 167
    public synchronized List<Object> getList(int columnCount) {
168
        this.loadCache(columnCount);
93 ilm 169
        return this.list;
17 ilm 170
    }
171
 
93 ilm 172
    public Object getValueAt(int column) {
173
        return this.getList(column + 1).get(column);
174
    }
175
 
17 ilm 176
    public final void setValueAt(Object obj, int colIndex) {
142 ilm 177
        this.getColumns().getColumns().get(colIndex).put(this, obj);
17 ilm 178
    }
179
 
93 ilm 180
    // should update this.list at the passed indexes, and then recursively for dependent columns
181
    // return all updated indexes
182
    // for now dependent columns (i.e. getUsedCols()) aren't supported so just return our parameter
183
    private final Set<Integer> updateValueAt(final Set<Integer> colIndexes) {
80 ilm 184
        if (colIndexes.size() == 0)
93 ilm 185
            return colIndexes;
186
        updateList(-1, colIndexes);
187
        return colIndexes;
188
    }
189
 
190
    // @param newSize negative means use current value
191
    private final boolean updateList(int newSize, final Set<Integer> colsToUpdate) {
192
        final int minIndexToUpdate;
193
        if (colsToUpdate.isEmpty()) {
194
            minIndexToUpdate = -1;
195
        } else {
196
            minIndexToUpdate = Collections.min(colsToUpdate).intValue();
197
            if (minIndexToUpdate < 0)
198
                throw new IllegalArgumentException("Negative indexes : " + colsToUpdate);
199
        }
17 ilm 200
        synchronized (this) {
93 ilm 201
            final int alreadyLoaded = this.list.size();
202
            if (newSize < 0)
203
                newSize = alreadyLoaded;
204
            // if there's enough items and either nothing to update or the columns to update aren't
205
            // yet needed, return
206
            if (alreadyLoaded >= newSize && (minIndexToUpdate < 0 || minIndexToUpdate >= alreadyLoaded))
207
                return false;
208
            final List<Object> newList = new ArrayList<Object>(newSize);
209
            for (int i = 0; i < newSize; i++) {
210
                final Object o;
211
                if (i >= alreadyLoaded || colsToUpdate.contains(i))
142 ilm 212
                    o = this.getColumns().getAllColumns().get(i).show(this.getRow());
93 ilm 213
                else
214
                    o = this.list.get(i);
215
                newList.add(o);
17 ilm 216
            }
93 ilm 217
            this.list = Collections.unmodifiableList(newList);
17 ilm 218
        }
93 ilm 219
        return true;
17 ilm 220
    }
221
 
222
    public void clearCache() {
223
        synchronized (this) {
93 ilm 224
            this.list = Collections.emptyList();
17 ilm 225
        }
226
    }
227
 
228
    /**
229
     * Load the passed values into this row at the passed path.
230
     *
73 ilm 231
     * @param id ID of vals, needed when vals is <code>null</code>.
17 ilm 232
     * @param vals values to load, eg CONTACT.NOM = "Dupont".
233
     * @param p where to load the values, eg "SITE.ID_CONTACT_CHEF".
93 ilm 234
     * @return the columns that were affected, <code>null</code> meaning all.
17 ilm 235
     */
93 ilm 236
    synchronized Set<Integer> loadAt(int id, SQLRowValues vals, Path p) {
142 ilm 237
        assert vals == null || vals.getID() == id;
73 ilm 238
        final String lastReferentField = SearchQueue.getLastReferentField(p);
142 ilm 239
        // null vals means id was deleted, the only way we care is if it was pointing to us
240
        // (otherwise the foreign key pointing to it would have changed first)
241
        assert vals != null || lastReferentField != null;
17 ilm 242
        // load() empties vals, so getFields() before
73 ilm 243
        final Set<Integer> indexes = lastReferentField == null ? this.pathToIndex(p, vals.getFields()) : null;
17 ilm 244
        // replace our values with the new ones
93 ilm 245
        final SQLRowValues copy = this.getRow().deepCopy();
73 ilm 246
        if (lastReferentField == null) {
93 ilm 247
            for (final SQLRowValues v : copy.followPath(p, CreateMode.CREATE_NONE, false)) {
142 ilm 248
                // check id, e.g. if p is BATIMENT <- LOCAL -> FAMILLE_LOCAL, then there's multiple
249
                // familles
250
                if (v.getID() == id)
251
                    v.load(vals.deepCopy(), null);
73 ilm 252
            }
253
        } else {
254
            // e.g. if p is SITE <- BATIMENT <- LOCAL, lastField is LOCAL.ID_BATIMENT
255
            // if p is SITE -> CLIENT <- SITE (i.e. siblings of a site), lastField is SITE.ID_CLIENT
256
            final SQLField lastField = p.getStep(-1).getSingleField();
257
            final Collection<SQLRowValues> previous;
258
            if (p.length() > 1 && p.getStep(-2).reverse().equals(p.getStep(-1)))
93 ilm 259
                previous = copy.followPath(p.minusLast(2), CreateMode.CREATE_NONE, false);
73 ilm 260
            else
261
                previous = null;
262
            // the rows that vals should point to, e.g. BATIMENT or CLIENT
93 ilm 263
            final Collection<SQLRowValues> targets = copy.followPath(p.minusLast(), CreateMode.CREATE_NONE, false);
73 ilm 264
            for (final SQLRowValues target : targets) {
265
                // remove existing referent with the updated ID
266
                SQLRowValues toRemove = null;
267
                for (final SQLRowValues toUpdate : target.getReferentRows(lastField)) {
268
                    // don't back track (in the example a given SITE will be at the primary location
269
                    // and a second time along its siblings)
270
                    if ((previous == null || !previous.contains(toUpdate)) && toUpdate.getID() == id) {
271
                        if (toRemove != null)
93 ilm 272
                            throw new IllegalStateException("Duplicate IDs " + id + " : " + System.identityHashCode(toRemove) + " and " + System.identityHashCode(toUpdate) + "\n" + copy.printGraph());
73 ilm 273
                        toRemove = toUpdate;
274
                    }
275
                }
276
                if (toRemove != null)
277
                    toRemove.remove(lastField.getName());
156 ilm 278
                // attach updated values, foreign ID is always present but can be null
279
                if (vals != null && target.getIDNumber().equals(vals.getForeignIDNumberValue(lastField.getName()).getValue()))
73 ilm 280
                    vals.deepCopy().put(lastField.getName(), target);
281
            }
282
        }
93 ilm 283
        copy.getGraph().freeze();
284
        this.setRow(copy);
17 ilm 285
        // update our cache
93 ilm 286
        if (indexes == null) {
17 ilm 287
            this.clearCache();
93 ilm 288
            return null;
289
        } else {
290
            return this.updateValueAt(indexes);
291
        }
17 ilm 292
    }
293
 
294
    /**
295
     * Find the columns that use the modifiedFields for their value.
296
     *
297
     * @param p the path to the modified fields, eg "CPI.ID_LOCAL".
298
     * @param modifiedFields the field modified, eg "DESIGNATION".
299
     * @return the index of columns using "CPI.ID_LOCAL.DESIGNATION", or null for every columns.
300
     */
301
    private Set<Integer> pathToIndex(final Path p, final Collection<String> modifiedFields) {
93 ilm 302
        // TODO check if the column paths start with any of the modified foreign keys
303
        // since it's quite expensive, add a cache in SQLTableModelSource
73 ilm 304
        if (containsFK(p.getLast(), modifiedFields)) {
305
            // e.g. CPI.ID_LOCAL, easier to just refresh the whole line, than to search for each
306
            // column affected (that would mean expanding the FK)
17 ilm 307
            return null;
73 ilm 308
        } else {
17 ilm 309
            final Set<Integer> res = new HashSet<Integer>();
310
            final Set<FieldPath> modifiedPaths = FieldPath.create(p, modifiedFields);
142 ilm 311
            final List<? extends SQLTableModelColumn> cols = this.getColumns().getAllColumns();
17 ilm 312
            for (int i = 0; i < cols.size(); i++) {
313
                final SQLTableModelColumn col = cols.get(i);
314
                if (CollectionUtils.containsAny(col.getPaths(), modifiedPaths))
315
                    res.add(i);
316
            }
317
            return res;
318
        }
319
    }
320
 
321
    private static boolean containsFK(final SQLTable t, Collection<String> fields) {
142 ilm 322
        final Set<SQLField> ffs = t.getFields(VirtualFields.FOREIGN_KEYS);
17 ilm 323
        for (final String f : fields) {
142 ilm 324
            if (ffs.contains(t.getField(f)))
17 ilm 325
                return true;
326
        }
327
        return false;
328
    }
329
 
330
    @Override
331
    public String toString() {
93 ilm 332
        return this.getClass().getSimpleName() + " on " + this.getRow();
17 ilm 333
    }
334
}