OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 93 | Rev 151 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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