OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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