OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 17 | Rev 80 | 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
 
32
/**
33
 * A line used by SQLTableModelSource, posessing an order and an id. Compare is done on the order.
34
 *
35
 * @author Sylvain
36
 */
37
public final class ListSQLLine implements Comparable<ListSQLLine> {
38
 
39
    public static final int indexFromID(final List<ListSQLLine> l, final int id) {
40
        int foundIndex = -1;
41
        final int size = l.size();
42
        for (int i = 0; i < size; i++) {
43
            final int currentID = l.get(i).getID();
44
            if (currentID == id) {
45
                foundIndex = i;
46
                break;
47
            }
48
        }
49
        return foundIndex;
50
    }
51
 
52
    private final SQLTableModelLinesSource src;
53
    private final SQLRowValues row;
54
    private final int id;
55
    // lists are accessed by Swing (model.getValueAt()) and
56
    // by the search queue (SearchRunnable#matchFilter(ListSQLLine line))
57
    private final List<Object> list;
58
    // count of column values loaded in this.list
59
    // (to avoid loading debug columns, which took more time than the regular columns, ie more than
60
    // half the time was passed on almost never displayed values)
61
    private int loadedCol;
62
    private final List<Object> pubList;
63
 
64
    public ListSQLLine(SQLTableModelLinesSource src, SQLRowValues row, int id) {
65
        super();
66
        this.src = src;
67
        this.row = row;
68
        this.id = id;
69
        this.list = new ArrayList<Object>();
70
        this.pubList = Collections.unmodifiableList(this.list);
71
        this.loadedCol = 0;
72
    }
73
 
74
    // load at least columnCount values
75
    private synchronized void loadCache(int columnCount) {
76
        if (this.loadedCol >= columnCount)
77
            return;
78
 
79
        try {
80
            final List<SQLTableModelColumn> allCols = this.src.getParent().getAllColumns();
81
            for (int i = this.loadedCol; i < columnCount; i++)
82
                this.list.add(allCols.get(i).show(this.row));
83
            this.loadedCol = columnCount;
84
        } catch (RuntimeException e) {
85
            // the list length must be equal to the column count
86
            // if we're interrupted, come back to a safe state
87
            this.list.clear();
88
            this.loadedCol = 0;
89
            throw e;
90
        }
91
    }
92
 
93
    public final SQLTableModelLinesSource getSrc() {
94
        return this.src;
95
    }
96
 
97
    public final SQLRowValues getRow() {
98
        return this.row;
99
    }
100
 
101
    @Override
102
    public int compareTo(ListSQLLine o) {
103
        if (this.src != o.src)
104
            throw new IllegalArgumentException(this.src + " != " + o.src);
105
        return this.src.compare(this, o);
106
    }
107
 
108
    public int getID() {
109
        return this.id;
110
    }
111
 
112
    public synchronized List<Object> getList(int columnCount) {
113
        this.loadCache(columnCount);
114
        return this.pubList;
115
    }
116
 
117
    public final void setValueAt(Object obj, int colIndex) {
118
        this.src.getParent().getColumn(colIndex).put(this, obj);
119
    }
120
 
121
    public final void updateValueAt(Set<Integer> colIndexes) {
122
        synchronized (this) {
123
            final int max = Collections.max(colIndexes).intValue();
124
            final int alreadyLoaded = this.loadedCol;
125
            this.loadCache(max);
126
            for (final int colIndex : colIndexes) {
127
                // no need to update twice colIndex
128
                if (colIndex < alreadyLoaded)
129
                    // MAYBE first iterate to fetch the new values and then merge them to list,
130
                    // otherwise if there's an exn list will be half updated
131
                    this.list.set(colIndex, this.src.getParent().getColumn(colIndex).show(this.getRow()));
132
            }
133
        }
134
        this.src.fireLineChanged(this.getID(), this, colIndexes);
135
    }
136
 
137
    public void clearCache() {
138
        synchronized (this) {
139
            this.list.clear();
140
            this.loadedCol = 0;
141
        }
142
        this.src.fireLineChanged(this.getID(), this, null);
143
    }
144
 
145
    /**
146
     * Load the passed values into this row at the passed path.
147
     *
73 ilm 148
     * @param id ID of vals, needed when vals is <code>null</code>.
17 ilm 149
     * @param vals values to load, eg CONTACT.NOM = "Dupont".
150
     * @param p where to load the values, eg "SITE.ID_CONTACT_CHEF".
151
     */
73 ilm 152
    void loadAt(int id, SQLRowValues vals, Path p) {
153
        final String lastReferentField = SearchQueue.getLastReferentField(p);
17 ilm 154
        // load() empties vals, so getFields() before
73 ilm 155
        final Set<Integer> indexes = lastReferentField == null ? this.pathToIndex(p, vals.getFields()) : null;
17 ilm 156
        // replace our values with the new ones
73 ilm 157
        if (lastReferentField == null) {
158
            for (final SQLRowValues v : this.getRow().followPath(p, CreateMode.CREATE_NONE, false)) {
159
                v.load(vals.deepCopy(), null);
160
            }
161
        } else {
162
            // e.g. if p is SITE <- BATIMENT <- LOCAL, lastField is LOCAL.ID_BATIMENT
163
            // if p is SITE -> CLIENT <- SITE (i.e. siblings of a site), lastField is SITE.ID_CLIENT
164
            final SQLField lastField = p.getStep(-1).getSingleField();
165
            final Collection<SQLRowValues> previous;
166
            if (p.length() > 1 && p.getStep(-2).reverse().equals(p.getStep(-1)))
167
                previous = this.getRow().followPath(p.minusLast(2), CreateMode.CREATE_NONE, false);
168
            else
169
                previous = null;
170
            // the rows that vals should point to, e.g. BATIMENT or CLIENT
171
            final Collection<SQLRowValues> targets = this.getRow().followPath(p.minusLast(), CreateMode.CREATE_NONE, false);
172
            for (final SQLRowValues target : targets) {
173
                // remove existing referent with the updated ID
174
                SQLRowValues toRemove = null;
175
                for (final SQLRowValues toUpdate : target.getReferentRows(lastField)) {
176
                    // don't back track (in the example a given SITE will be at the primary location
177
                    // and a second time along its siblings)
178
                    if ((previous == null || !previous.contains(toUpdate)) && toUpdate.getID() == id) {
179
                        if (toRemove != null)
180
                            throw new IllegalStateException("Duplicate IDs " + id + " : " + System.identityHashCode(toRemove) + " and " + System.identityHashCode(toUpdate) + "\n"
181
                                    + this.getRow().printGraph());
182
                        toRemove = toUpdate;
183
                    }
184
                }
185
                if (toRemove != null)
186
                    toRemove.remove(lastField.getName());
187
                // attach updated values
188
                if (vals != null && vals.getLong(lastField.getName()) == target.getIDNumber().longValue())
189
                    vals.deepCopy().put(lastField.getName(), target);
190
            }
191
        }
17 ilm 192
        // update our cache
193
        if (indexes == null)
194
            this.clearCache();
195
        else
196
            this.updateValueAt(indexes);
197
    }
198
 
199
    /**
200
     * Find the columns that use the modifiedFields for their value.
201
     *
202
     * @param p the path to the modified fields, eg "CPI.ID_LOCAL".
203
     * @param modifiedFields the field modified, eg "DESIGNATION".
204
     * @return the index of columns using "CPI.ID_LOCAL.DESIGNATION", or null for every columns.
205
     */
206
    private Set<Integer> pathToIndex(final Path p, final Collection<String> modifiedFields) {
73 ilm 207
        if (containsFK(p.getLast(), modifiedFields)) {
208
            // e.g. CPI.ID_LOCAL, easier to just refresh the whole line, than to search for each
209
            // column affected (that would mean expanding the FK)
17 ilm 210
            return null;
73 ilm 211
        } else {
17 ilm 212
            final Set<Integer> res = new HashSet<Integer>();
213
            final Set<FieldPath> modifiedPaths = FieldPath.create(p, modifiedFields);
214
            final List<? extends SQLTableModelColumn> cols = this.src.getParent().getAllColumns();
215
            for (int i = 0; i < cols.size(); i++) {
216
                final SQLTableModelColumn col = cols.get(i);
217
                if (CollectionUtils.containsAny(col.getPaths(), modifiedPaths))
218
                    res.add(i);
219
            }
220
            return res;
221
        }
222
    }
223
 
224
    private static boolean containsFK(final SQLTable t, Collection<String> fields) {
225
        for (final String f : fields) {
226
            if (t.getForeignKeys().contains(t.getField(f)))
227
                return true;
228
        }
229
        return false;
230
    }
231
 
232
    @Override
233
    public String toString() {
234
        return this.getClass().getSimpleName() + " on " + this.row;
235
    }
236
}