OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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

Rev 142 Rev 156
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 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.view.list;
14
 package org.openconcerto.sql.view.list;
15
 
15
 
16
import org.openconcerto.sql.Log;
16
import org.openconcerto.sql.Log;
17
import org.openconcerto.sql.model.SQLRow;
17
import org.openconcerto.sql.model.SQLRow;
18
import org.openconcerto.sql.model.SQLRowValues;
18
import org.openconcerto.sql.model.SQLRowValues;
19
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
19
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
20
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
20
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
21
import org.openconcerto.sql.model.SQLTable;
21
import org.openconcerto.sql.model.SQLTable;
22
import org.openconcerto.sql.model.SQLTableEvent;
22
import org.openconcerto.sql.model.SQLTableEvent;
23
import org.openconcerto.sql.model.SQLTableModifiedListener;
23
import org.openconcerto.sql.model.SQLTableModifiedListener;
24
import org.openconcerto.sql.model.graph.Link.Direction;
24
import org.openconcerto.sql.model.graph.Link.Direction;
25
import org.openconcerto.sql.model.graph.Path;
25
import org.openconcerto.sql.model.graph.Path;
26
import org.openconcerto.sql.view.list.UpdateRunnable.RmAllRunnable;
26
import org.openconcerto.sql.view.list.UpdateRunnable.RmAllRunnable;
27
import org.openconcerto.sql.view.list.search.SearchOne;
27
import org.openconcerto.sql.view.list.search.SearchOne;
28
import org.openconcerto.sql.view.list.search.SearchOne.Mode;
28
import org.openconcerto.sql.view.list.search.SearchOne.Mode;
29
import org.openconcerto.sql.view.list.search.SearchQueue;
29
import org.openconcerto.sql.view.list.search.SearchQueue;
30
import org.openconcerto.sql.view.list.search.SearchQueue.SetStateRunnable;
30
import org.openconcerto.sql.view.list.search.SearchQueue.SetStateRunnable;
31
import org.openconcerto.utils.CollectionUtils;
31
import org.openconcerto.utils.CollectionUtils;
32
import org.openconcerto.utils.ListMap;
32
import org.openconcerto.utils.ListMap;
33
import org.openconcerto.utils.RecursionType;
33
import org.openconcerto.utils.RecursionType;
34
import org.openconcerto.utils.SleepingQueue;
34
import org.openconcerto.utils.SleepingQueue;
35
import org.openconcerto.utils.Tuple2;
35
import org.openconcerto.utils.Tuple2;
36
import org.openconcerto.utils.cc.IClosure;
36
import org.openconcerto.utils.cc.IClosure;
37
import org.openconcerto.utils.cc.IPredicate;
37
import org.openconcerto.utils.cc.IPredicate;
38
import org.openconcerto.utils.cc.ITransformer;
38
import org.openconcerto.utils.cc.ITransformer;
39
 
39
 
40
import java.beans.PropertyChangeEvent;
40
import java.beans.PropertyChangeEvent;
41
import java.beans.PropertyChangeListener;
41
import java.beans.PropertyChangeListener;
42
import java.util.ArrayList;
42
import java.util.ArrayList;
43
import java.util.Collections;
43
import java.util.Collections;
44
import java.util.Deque;
44
import java.util.Deque;
45
import java.util.HashSet;
45
import java.util.HashSet;
46
import java.util.Iterator;
46
import java.util.Iterator;
47
import java.util.List;
47
import java.util.List;
48
import java.util.Set;
48
import java.util.Set;
49
import java.util.concurrent.FutureTask;
49
import java.util.concurrent.FutureTask;
50
import java.util.logging.Level;
50
import java.util.logging.Level;
51
 
51
 
52
import net.jcip.annotations.GuardedBy;
52
import net.jcip.annotations.GuardedBy;
53
 
53
 
54
public final class UpdateQueue extends SleepingQueue {
54
public final class UpdateQueue extends SleepingQueue {
55
 
55
 
56
    /**
56
    /**
57
     * Whether the passed future performs an update.
57
     * Whether the passed future performs an update.
58
     * 
58
     * 
59
     * @param f a task in this queue, can be <code>null</code>.
59
     * @param f a task in this queue, can be <code>null</code>.
60
     * @return <code>true</code> if <code>f</code> loads from the db.
60
     * @return <code>true</code> if <code>f</code> loads from the db.
61
     */
61
     */
62
    static boolean isUpdate(FutureTask<?> f) {
62
    static boolean isUpdate(FutureTask<?> f) {
63
        return isUpdate(SearchQueue.getRunnable(f));
63
        return isUpdate(SearchQueue.getRunnable(f));
64
    }
64
    }
65
 
65
 
66
    static boolean isUpdate(Runnable r) {
66
    static boolean isUpdate(Runnable r) {
67
        return r instanceof UpdateRunnable;
67
        return r instanceof UpdateRunnable;
68
    }
68
    }
69
 
69
 
70
    private static boolean isCancelableUpdate(Runnable r) {
70
    private static boolean isCancelableUpdate(Runnable r) {
71
        // don't cancel RmAll so we can put an UpdateAll right after it (the UpdateAll won't be
71
        // don't cancel RmAll so we can put an UpdateAll right after it (the UpdateAll won't be
72
        // executed since RmAll put the queue to sleep)
72
        // executed since RmAll put the queue to sleep)
73
        return isUpdate(r) && !(r instanceof RmAllRunnable);
73
        return isUpdate(r) && !(r instanceof RmAllRunnable);
74
    }
74
    }
75
 
75
 
76
    private final class TableListener implements SQLTableModifiedListener, PropertyChangeListener {
76
    private final class TableListener implements SQLTableModifiedListener, PropertyChangeListener {
77
        @Override
77
        @Override
78
        public void tableModified(SQLTableEvent evt) {
78
        public void tableModified(SQLTableEvent evt) {
79
            if (UpdateQueue.this.alwaysUpdateAll)
79
            if (UpdateQueue.this.alwaysUpdateAll)
80
                putUpdateAll();
80
                putUpdateAll();
81
            else if (evt.getMode() == SQLTableEvent.Mode.ROW_UPDATED) {
81
            else if (evt.getMode() == SQLTableEvent.Mode.ROW_UPDATED) {
82
                rowModified(evt);
82
                rowModified(evt);
83
            } else {
83
            } else {
84
                rowAddedOrDeleted(evt);
84
                rowAddedOrDeleted(evt);
85
            }
85
            }
86
        }
86
        }
87
 
87
 
88
        @Override
88
        @Override
89
        public void propertyChange(PropertyChangeEvent evt) {
89
        public void propertyChange(PropertyChangeEvent evt) {
90
            // where changed
90
            // where changed
91
            stateChanged(null, getModel().getReq().createState());
91
            stateChanged(null, getModel().getReq().createState());
92
        }
92
        }
93
    }
93
    }
94
 
94
 
95
    private final ITableModel tableModel;
95
    private final ITableModel tableModel;
96
    // thread-confined
96
    // thread-confined
97
    private SQLTableModelSourceState state;
97
    private SQLTableModelSourceState state;
98
    @GuardedBy("itself")
98
    @GuardedBy("itself")
99
    private final List<ListSQLLine> fullList;
99
    private final List<ListSQLLine> fullList;
100
    @GuardedBy("fullList")
100
    @GuardedBy("fullList")
101
    private SQLTableModelColumns columns;
101
    private SQLTableModelColumns columns;
102
    private final TableListener tableListener;
102
    private final TableListener tableListener;
103
    // TODO rm : needed for now since our optimizations are false if there's a where not on the
103
    // TODO rm : needed for now since our optimizations are false if there's a where not on the
104
    // primary table, see http://192.168.1.10:3000/issues/show/22
104
    // primary table, see http://192.168.1.10:3000/issues/show/22
105
    private boolean alwaysUpdateAll = false;
105
    private boolean alwaysUpdateAll = false;
106
    private final IClosure<Deque<FutureTask<?>>> cancelClosure;
106
    private final IClosure<Deque<FutureTask<?>>> cancelClosure;
107
 
107
 
108
    public UpdateQueue(ITableModel model) {
108
    public UpdateQueue(ITableModel model) {
109
        super(UpdateQueue.class.getSimpleName() + " on " + model);
109
        super(UpdateQueue.class.getSimpleName() + " on " + model);
110
        this.tableModel = model;
110
        this.tableModel = model;
111
        this.fullList = new ArrayList<ListSQLLine>();
111
        this.fullList = new ArrayList<ListSQLLine>();
112
        this.cancelClosure = createCancelClosure(this, new ITransformer<FutureTask<?>, TaskType>() {
112
        this.cancelClosure = createCancelClosure(this, new ITransformer<FutureTask<?>, TaskType>() {
113
            @Override
113
            @Override
114
            public TaskType transformChecked(FutureTask<?> input) {
114
            public TaskType transformChecked(FutureTask<?> input) {
115
                final Runnable r = SearchQueue.getRunnable(input);
115
                final Runnable r = SearchQueue.getRunnable(input);
116
                if (isCancelableUpdate(r))
116
                if (isCancelableUpdate(r))
117
                    return TaskType.COMPUTE;
117
                    return TaskType.COMPUTE;
118
                else if (r instanceof SetStateRunnable)
118
                else if (r instanceof SetStateRunnable)
119
                    return TaskType.SET_STATE;
119
                    return TaskType.SET_STATE;
120
                else
120
                else
121
                    return TaskType.USER;
121
                    return TaskType.USER;
122
            }
122
            }
123
        });
123
        });
124
        this.tableListener = new TableListener();
124
        this.tableListener = new TableListener();
125
    }
125
    }
126
 
126
 
127
    private final ITableModel getModel() {
127
    private final ITableModel getModel() {
128
        return this.tableModel;
128
        return this.tableModel;
129
    }
129
    }
130
 
130
 
131
    @Override
131
    @Override
132
    protected void started() {
132
    protected void started() {
133
        // savoir quand les tables qu'on affiche changent
133
        // savoir quand les tables qu'on affiche changent
134
        addSourceListener();
134
        addSourceListener();
135
        stateChanged(null, this.getModel().getReq().createState());
135
        stateChanged(null, this.getModel().getReq().createState());
136
        // Only starts once there's something to search, that way the runnable passed to
136
        // Only starts once there's something to search, that way the runnable passed to
137
        // ITableModel.search() will be meaningful
137
        // ITableModel.search() will be meaningful
138
        // SetStateRunnable since this must not be cancelled by an updateAll, but it shouldn't
138
        // SetStateRunnable since this must not be cancelled by an updateAll, but it shouldn't
139
        // prevent earlier updates to be cancelled
139
        // prevent earlier updates to be cancelled
140
        this.put(new SetStateRunnable() {
140
        this.put(new SetStateRunnable() {
141
            @Override
141
            @Override
142
            public void run() {
142
            public void run() {
143
                getModel().getSearchQueue().start();
143
                getModel().startSearchQueue();
144
            }
144
            }
145
        });
145
        });
146
    }
146
    }
147
 
147
 
148
    @Override
148
    @Override
149
    protected void dying() throws Exception {
149
    protected void dying() throws Exception {
150
        super.dying();
150
        super.dying();
151
        assert currentlyInQueue();
151
        assert currentlyInQueue();
152
 
152
 
153
        // only kill searchQ once updateQ is really dead, otherwise the currently executing
153
        // only kill searchQ once updateQ is really dead, otherwise the currently executing
154
        // update might finish once the searchQ is already dead.
154
        // update might finish once the searchQ is already dead.
155
 
155
 
156
        final SearchQueue searchQueue = getModel().getSearchQueue();
156
        final SearchQueue searchQueue = getModel().getSearchQueue();
157
        // state cannot change since we only change it in this thread (except for an Error being
157
        // state cannot change since we only change it in this thread (except for an Error being
158
        // thrown and killing the queue)
158
        // thrown and killing the queue)
159
        final RunningState state = searchQueue.getRunningState();
159
        final RunningState state = searchQueue.getRunningState();
160
        // not started or there was an Error
160
        // not started or there was an Error
161
        if (state == RunningState.NEW || state == RunningState.DEAD)
161
        if (state == RunningState.NEW || state == RunningState.DEAD)
162
            return;
162
            return;
163
        if (state == RunningState.WILL_DIE || state == RunningState.DYING)
163
        if (state == RunningState.WILL_DIE || state == RunningState.DYING)
164
            throw new IllegalStateException("Someone else already called die()");
164
            throw new IllegalStateException("Someone else already called die()");
165
        try {
165
        try {
166
            searchQueue.die().get();
166
            searchQueue.die().get();
167
        } catch (Exception e) {
167
        } catch (Exception e) {
168
            if (searchQueue.getRunningState() != RunningState.DEAD)
168
            if (searchQueue.getRunningState() != RunningState.DEAD)
169
                throw e;
169
                throw e;
170
            // there was an Error in the last run task or while in die(), but it's OK we wanted the
170
            // there was an Error in the last run task or while in die(), but it's OK we wanted the
171
            // queue dead
171
            // queue dead
172
            Log.get().log(Level.CONFIG, "Exception while killing search queue", e);
172
            Log.get().log(Level.CONFIG, "Exception while killing search queue", e);
173
        }
173
        }
174
        assert searchQueue.getRunningState().compareTo(RunningState.DYING) >= 0;
174
        assert searchQueue.getRunningState().compareTo(RunningState.DYING) >= 0;
175
        searchQueue.join();
175
        searchQueue.join();
176
        assert searchQueue.getRunningState() == RunningState.DEAD;
176
        assert searchQueue.getRunningState() == RunningState.DEAD;
177
    }
177
    }
178
 
178
 
179
    final List<ListSQLLine> getFullList() {
179
    final List<ListSQLLine> getFullList() {
180
        return this.fullList;
180
        return this.fullList;
181
    }
181
    }
182
 
182
 
183
    public final Tuple2<List<ListSQLLine>, SQLTableModelColumns> copyFullList() {
183
    public final Tuple2<List<ListSQLLine>, SQLTableModelColumns> copyFullList() {
184
        final Tuple2<List<ListSQLLine>, SQLTableModelColumns> res;
184
        final Tuple2<List<ListSQLLine>, SQLTableModelColumns> res;
185
        final List<ListSQLLine> fullList = this.getFullList();
185
        final List<ListSQLLine> fullList = this.getFullList();
186
        synchronized (fullList) {
186
        synchronized (fullList) {
187
            res = Tuple2.<List<ListSQLLine>, SQLTableModelColumns> create(new ArrayList<ListSQLLine>(fullList), this.columns);
187
            res = Tuple2.<List<ListSQLLine>, SQLTableModelColumns> create(new ArrayList<ListSQLLine>(fullList), this.columns);
188
        }
188
        }
189
        return res;
189
        return res;
190
    }
190
    }
191
 
191
 
192
    public final ListSQLLine getLine(final Number id) {
192
    public final ListSQLLine getLine(final Number id) {
193
        final ListSQLLine res;
193
        final ListSQLLine res;
194
        final List<ListSQLLine> fullList = this.getFullList();
194
        final List<ListSQLLine> fullList = this.getFullList();
195
        synchronized (fullList) {
195
        synchronized (fullList) {
196
            res = ListSQLLine.fromID(fullList, id.intValue());
196
            res = ListSQLLine.fromID(fullList, id.intValue());
197
        }
197
        }
198
        return res;
198
        return res;
199
    }
199
    }
200
 
200
 
201
    /**
201
    /**
202
     * The lines and their path affected by a change of the passed row.
202
     * The lines and their path affected by a change of the passed row.
203
     * 
203
     * 
204
     * @param r the row that has changed.
204
     * @param r the row that has changed.
205
     * @return the refreshed lines and their changed paths.
205
     * @return the refreshed lines and their changed paths.
206
     */
206
     */
207
    protected final ListMap<ListSQLLine, Path> getAffectedLines(final SQLRow r) {
207
    protected final ListMap<ListSQLLine, Path> getAffectedLines(final SQLRow r) {
208
        return this.getAffected(r, new ListMap<ListSQLLine, Path>(), true);
208
        return this.getAffected(r, new ListMap<ListSQLLine, Path>(), true);
209
    }
209
    }
210
 
210
 
211
    protected final ListMap<Path, ListSQLLine> getAffectedPaths(final SQLRow r) {
211
    protected final ListMap<Path, ListSQLLine> getAffectedPaths(final SQLRow r) {
212
        return this.getAffected(r, new ListMap<Path, ListSQLLine>(), false);
212
        return this.getAffected(r, new ListMap<Path, ListSQLLine>(), false);
213
    }
213
    }
214
 
214
 
215
    // must be called from within this queue, as this method use fullList
215
    // must be called from within this queue, as this method use fullList
216
    private <K, V> ListMap<K, V> getAffected(final SQLRow r, final ListMap<K, V> res, final boolean byLine) {
216
    private <K, V> ListMap<K, V> getAffected(final SQLRow r, final ListMap<K, V> res, final boolean byLine) {
217
        final List<ListSQLLine> fullList = this.getFullList();
217
        final List<ListSQLLine> fullList = this.getFullList();
218
        synchronized (fullList) {
218
        synchronized (fullList) {
219
            final SQLTable t = r.getTable();
219
            final SQLTable t = r.getTable();
220
            final int id = r.getID();
220
            final int id = r.getID();
221
            if (id < SQLRow.MIN_VALID_ID)
221
            if (id < SQLRow.MIN_VALID_ID)
222
                throw new IllegalArgumentException("invalid ID: " + id);
222
                throw new IllegalArgumentException("invalid ID: " + id);
223
            if (!fullList.isEmpty()) {
223
            if (!fullList.isEmpty()) {
224
                final SQLRowValues proto = this.getState().getReq().getGraphToFetch();
224
                final SQLRowValues proto = this.getState().getReq().getGraphToFetch();
225
                final List<Path> pathsToT = new ArrayList<Path>();
225
                final List<Path> pathsToT = new ArrayList<Path>();
226
                proto.getGraph().walk(proto, pathsToT, new ITransformer<State<List<Path>>, List<Path>>() {
226
                proto.getGraph().walk(proto, pathsToT, new ITransformer<State<List<Path>>, List<Path>>() {
227
                    @Override
227
                    @Override
228
                    public List<Path> transformChecked(State<List<Path>> input) {
228
                    public List<Path> transformChecked(State<List<Path>> input) {
229
                        if (input.getCurrent().getTable() == t) {
229
                        if (input.getCurrent().getTable() == t) {
230
                            input.getAcc().add(input.getPath());
230
                            input.getAcc().add(input.getPath());
231
                        }
231
                        }
232
                        return input.getAcc();
232
                        return input.getAcc();
233
                    }
233
                    }
234
                }, RecursionType.BREADTH_FIRST, Direction.ANY);
234
                }, RecursionType.BREADTH_FIRST, Direction.ANY);
235
                for (final Path p : pathsToT) {
235
                for (final Path p : pathsToT) {
236
                    final String lastReferentField = SearchQueue.getLastReferentField(p);
236
                    final String lastReferentField = SearchQueue.getLastReferentField(p);
237
                    for (final ListSQLLine line : fullList) {
237
                    for (final ListSQLLine line : fullList) {
238
                        boolean put = false;
238
                        boolean put = false;
239
                        for (final SQLRowValues current : line.getRow().followPath(p, CreateMode.CREATE_NONE, false)) {
239
                        for (final SQLRowValues current : line.getRow().followPath(p, CreateMode.CREATE_NONE, false)) {
240
                            // works for rowValues w/o any ID
240
                            // works for rowValues w/o any ID
241
                            if (current != null && current.getID() == id) {
241
                            if (current != null && current.getID() == id) {
242
                                put = true;
242
                                put = true;
243
                            }
243
                            }
244
                        }
244
                        }
245
                        // if the modified row isn't in the existing line, it might still affect it
245
                        // if the modified row isn't in the existing line, it might still affect it
246
                        // if it's a referent row insertion
246
                        // if it's a referent row insertion
247
                        if (!put && lastReferentField != null && r.exists()) {
247
                        if (!put && lastReferentField != null && r.exists() && !r.isForeignEmpty(lastReferentField)) {
-
 
248
                            // no NPE, even without an undefined ID since we tested isForeignEmpty()
248
                            final int foreignID = r.getInt(lastReferentField);
249
                            final int foreignID = r.getInt(lastReferentField);
249
                            for (final SQLRowValues current : line.getRow().followPath(p.minusLast(), CreateMode.CREATE_NONE, false)) {
250
                            for (final SQLRowValues current : line.getRow().followPath(p.minusLast(), CreateMode.CREATE_NONE, false)) {
250
                                if (current.getID() == foreignID) {
251
                                if (current.getID() == foreignID) {
251
                                    put = true;
252
                                    put = true;
252
                                }
253
                                }
253
                            }
254
                            }
254
                        }
255
                        }
255
                        if (put) {
256
                        if (put) {
256
                            // add to the list of paths that have been refreshed
257
                            // add to the list of paths that have been refreshed
257
                            add(byLine, res, p, line);
258
                            add(byLine, res, p, line);
258
                        }
259
                        }
259
                    }
260
                    }
260
                }
261
                }
261
            }
262
            }
262
        }
263
        }
263
        return res;
264
        return res;
264
    }
265
    }
265
 
266
 
266
    @SuppressWarnings("unchecked")
267
    @SuppressWarnings("unchecked")
267
    <V, K> void add(boolean byLine, ListMap<K, V> res, final Path p, final ListSQLLine line) {
268
    <V, K> void add(boolean byLine, ListMap<K, V> res, final Path p, final ListSQLLine line) {
268
        if (byLine)
269
        if (byLine)
269
            res.add((K) line, (V) p);
270
            res.add((K) line, (V) p);
270
        else
271
        else
271
            res.add((K) p, (V) line);
272
            res.add((K) p, (V) line);
272
    }
273
    }
273
 
274
 
274
    final void setFullList(final List<ListSQLLine> tmp, final SQLTableModelColumns cols) {
275
    final void setFullList(final List<ListSQLLine> tmp, final SQLTableModelColumns cols) {
275
        final List<ListSQLLine> fullList = this.getFullList();
276
        final List<ListSQLLine> fullList = this.getFullList();
276
        synchronized (fullList) {
277
        synchronized (fullList) {
277
            fullList.clear();
278
            fullList.clear();
278
            fullList.addAll(tmp);
279
            fullList.addAll(tmp);
279
            // MAYBE only sort() if it can't be done by the SELECT
280
            // MAYBE only sort() if it can't be done by the SELECT
280
            // but comparing ints (field ORDRE) is quite fast : 170ms for 100,000 items
281
            // but comparing ints (field ORDRE) is quite fast : 170ms for 100,000 items
281
            Collections.sort(fullList);
282
            Collections.sort(fullList);
282
            if (cols != null)
283
            if (cols != null)
283
                this.columns = cols;
284
                this.columns = cols;
284
        }
285
        }
285
        this.tableModel.getSearchQueue().fullListChanged();
286
        this.tableModel.getSearchQueue().fullListChanged();
286
    }
287
    }
287
 
288
 
288
    final void reorder(final List<Integer> idsOrder) {
289
    final void reorder(final List<Integer> idsOrder) {
289
        final List<ListSQLLine> fullList = this.getFullList();
290
        final List<ListSQLLine> fullList = this.getFullList();
290
        synchronized (fullList) {
291
        synchronized (fullList) {
291
            for (final ListSQLLine l : fullList) {
292
            for (final ListSQLLine l : fullList) {
292
                final Number newOrder;
293
                final Number newOrder;
293
                if (idsOrder == null) {
294
                if (idsOrder == null) {
294
                    newOrder = null;
295
                    newOrder = null;
295
                } else {
296
                } else {
296
                    final int index = idsOrder.indexOf(l.getID());
297
                    final int index = idsOrder.indexOf(l.getID());
297
                    if (index < 0)
298
                    if (index < 0)
298
                        throw new IllegalArgumentException("Missing id " + l.getID() + " in " + idsOrder);
299
                        throw new IllegalArgumentException("Missing id " + l.getID() + " in " + idsOrder);
299
                    newOrder = index;
300
                    newOrder = index;
300
                }
301
                }
301
                l.setOrder(newOrder);
302
                l.setOrder(newOrder);
302
            }
303
            }
303
            Collections.sort(fullList);
304
            Collections.sort(fullList);
304
        }
305
        }
305
        this.tableModel.getSearchQueue().orderChanged();
306
        this.tableModel.getSearchQueue().orderChanged();
306
    }
307
    }
307
 
308
 
308
    // vals can be null if we're removing a referent row
309
    // vals can be null if we're removing a referent row
309
    final void updateLine(ListSQLLine line, Path p, int valsID, SQLRowValues vals) {
310
    final void updateLine(ListSQLLine line, Path p, int valsID, SQLRowValues vals) {
310
        final Set<Integer> modifiedCols = line.loadAt(valsID, vals, p);
311
        final Set<Integer> modifiedCols = line.loadAt(valsID, vals, p);
311
        this.tableModel.getSearchQueue().changeFullList(line.getID(), line, modifiedCols, SearchOne.Mode.CHANGE);
312
        this.tableModel.getSearchQueue().changeFullList(line.getID(), line, modifiedCols, SearchOne.Mode.CHANGE);
312
    }
313
    }
313
 
314
 
314
    final ListSQLLine replaceLine(final int id, final ListSQLLine newLine) {
315
    final ListSQLLine replaceLine(final int id, final ListSQLLine newLine) {
315
        final Mode mode;
316
        final Mode mode;
316
        final List<ListSQLLine> fullList = this.getFullList();
317
        final List<ListSQLLine> fullList = this.getFullList();
317
        final ListSQLLine oldLine;
318
        final ListSQLLine oldLine;
318
        synchronized (fullList) {
319
        synchronized (fullList) {
319
            final int modifiedIndex = ListSQLLine.indexFromID(fullList, id);
320
            final int modifiedIndex = ListSQLLine.indexFromID(fullList, id);
320
            oldLine = modifiedIndex < 0 ? null : fullList.get(modifiedIndex);
321
            oldLine = modifiedIndex < 0 ? null : fullList.get(modifiedIndex);
321
 
322
 
322
            if (modifiedIndex < 0) {
323
            if (modifiedIndex < 0) {
323
                // la ligne n'était dans notre liste
324
                // la ligne n'était dans notre liste
324
                if (newLine != null) {
325
                if (newLine != null) {
325
                    // mais elle existe : ajout
326
                    // mais elle existe : ajout
326
                    // ATTN on ajoute à la fin, sans se soucier de l'ordre
327
                    // ATTN on ajoute à la fin, sans se soucier de l'ordre
327
                    fullList.add(newLine);
328
                    fullList.add(newLine);
328
                    Collections.sort(fullList);
329
                    Collections.sort(fullList);
329
                    mode = Mode.ADD;
330
                    mode = Mode.ADD;
330
                } else {
331
                } else {
331
                    // et elle n'y est toujours pas
332
                    // et elle n'y est toujours pas
332
                    mode = Mode.NO_CHANGE;
333
                    mode = Mode.NO_CHANGE;
333
                }
334
                }
334
            } else {
335
            } else {
335
                // la ligne était dans notre liste
336
                // la ligne était dans notre liste
336
                if (newLine != null) {
337
                if (newLine != null) {
337
                    // mettre à jour
338
                    // mettre à jour
338
                    fullList.set(modifiedIndex, newLine);
339
                    fullList.set(modifiedIndex, newLine);
339
                    Collections.sort(fullList);
340
                    Collections.sort(fullList);
340
                    mode = Mode.CHANGE;
341
                    mode = Mode.CHANGE;
341
                } else {
342
                } else {
342
                    // elle est effacée ou filtrée
343
                    // elle est effacée ou filtrée
343
                    fullList.remove(modifiedIndex);
344
                    fullList.remove(modifiedIndex);
344
                    mode = Mode.REMOVE;
345
                    mode = Mode.REMOVE;
345
                }
346
                }
346
            }
347
            }
347
        }
348
        }
348
 
349
 
349
        // notify search queue
350
        // notify search queue
350
        this.tableModel.getSearchQueue().changeFullList(id, newLine, null, mode);
351
        this.tableModel.getSearchQueue().changeFullList(id, newLine, null, mode);
351
        return oldLine;
352
        return oldLine;
352
    }
353
    }
353
 
354
 
354
    public final int getFullListSize() {
355
    public final int getFullListSize() {
355
        final List<ListSQLLine> fullList = this.getFullList();
356
        final List<ListSQLLine> fullList = this.getFullList();
356
        synchronized (fullList) {
357
        synchronized (fullList) {
357
            return fullList.size();
358
            return fullList.size();
358
        }
359
        }
359
    }
360
    }
360
 
361
 
361
    void setAlwaysUpdateAll(boolean b) {
362
    void setAlwaysUpdateAll(boolean b) {
362
        this.alwaysUpdateAll = b;
363
        this.alwaysUpdateAll = b;
363
    }
364
    }
364
 
365
 
365
    // *** listeners
366
    // *** listeners
366
 
367
 
367
    void stateChanged(final SQLTableModelSourceState beforeState, final SQLTableModelSourceState afterState) {
368
    void stateChanged(final SQLTableModelSourceState beforeState, final SQLTableModelSourceState afterState) {
368
        if (afterState == null)
369
        if (afterState == null)
369
            throw new NullPointerException("Null state");
370
            throw new NullPointerException("Null state");
370
 
371
 
371
        // As in SearchQueue :
372
        // As in SearchQueue :
372
        // needs to be 2 different runnables, that way if the source is changed and then the table
373
        // needs to be 2 different runnables, that way if the source is changed and then the table
373
        // is updated : the queue would naively contain setState, updateAll, updateAll and thus we
374
        // is updated : the queue would naively contain setState, updateAll, updateAll and thus we
374
        // can cancel one updateAll. Whereas if the setState was contained in updateAll, we
375
        // can cancel one updateAll. Whereas if the setState was contained in updateAll, we
375
        // couldn't cancel it.
376
        // couldn't cancel it.
376
        // use tasksDo() so that no other runnable can come between setState and updateAll.
377
        // use tasksDo() so that no other runnable can come between setState and updateAll.
377
        // Otherwise an updateOne might use new columns and add a line with different columns than
378
        // Otherwise an updateOne might use new columns and add a line with different columns than
378
        // the full list.
379
        // the full list.
379
        this.tasksDo(new IClosure<Deque<FutureTask<?>>>() {
380
        this.tasksDo(new IClosure<Deque<FutureTask<?>>>() {
380
            @Override
381
            @Override
381
            public void executeChecked(Deque<FutureTask<?>> input) {
382
            public void executeChecked(Deque<FutureTask<?>> input) {
382
                put(new SetStateRunnable() {
383
                put(new SetStateRunnable() {
383
                    @Override
384
                    @Override
384
                    public void run() {
385
                    public void run() {
385
                        setState(afterState);
386
                        setState(afterState);
386
                    }
387
                    }
387
                });
388
                });
388
                // TODO if request didn't change and the new graph is smaller, copy and prune the
389
                // TODO if request didn't change and the new graph is smaller, copy and prune the
389
                // rows
390
                // rows
390
                putUpdateAll();
391
                putUpdateAll();
391
            }
392
            }
392
        });
393
        });
393
    }
394
    }
394
 
395
 
395
    protected final void setState(final SQLTableModelSourceState newState) {
396
    protected final void setState(final SQLTableModelSourceState newState) {
396
        if (this.state != null)
397
        if (this.state != null)
397
            this.rmTableListener();
398
            this.rmTableListener();
398
        this.state = newState;
399
        this.state = newState;
399
        if (this.state != null)
400
        if (this.state != null)
400
            this.addTableListener();
401
            this.addTableListener();
401
    }
402
    }
402
 
403
 
403
    protected final SQLTableModelSourceState getState() {
404
    protected final SQLTableModelSourceState getState() {
404
        assert this.currentlyInQueue();
405
        assert this.currentlyInQueue();
405
        if (this.state == null)
406
        if (this.state == null)
406
            throw new IllegalStateException("Not yet started");
407
            throw new IllegalStateException("Not yet started");
407
        return this.state;
408
        return this.state;
408
    }
409
    }
409
 
410
 
410
    @Override
411
    @Override
411
    protected void willDie() {
412
    protected void willDie() {
412
        this.rmTableListener();
413
        this.rmTableListener();
413
        this.removeSourceListener();
414
        this.removeSourceListener();
414
        super.willDie();
415
        super.willDie();
415
    }
416
    }
416
 
417
 
417
    protected final void addTableListener() {
418
    protected final void addTableListener() {
418
        this.getState().getReq().addTableListener(this.tableListener);
419
        this.getState().getReq().addTableListener(this.tableListener);
419
    }
420
    }
420
 
421
 
421
    private void addSourceListener() {
422
    private void addSourceListener() {
422
        this.tableModel.getLinesSource().addListener(this.tableListener);
423
        this.tableModel.getLinesSource().addListener(this.tableListener);
423
    }
424
    }
424
 
425
 
425
    protected final void rmTableListener() {
426
    protected final void rmTableListener() {
426
        this.getState().getReq().removeTableListener(this.tableListener);
427
        this.getState().getReq().removeTableListener(this.tableListener);
427
    }
428
    }
428
 
429
 
429
    private void removeSourceListener() {
430
    private void removeSourceListener() {
430
        this.tableModel.getLinesSource().rmListener(this.tableListener);
431
        this.tableModel.getLinesSource().rmListener(this.tableListener);
431
    }
432
    }
432
 
433
 
433
    // *** une des tables que l'on affiche a changé
434
    // *** une des tables que l'on affiche a changé
434
 
435
 
435
    void rowModified(final SQLTableEvent evt) {
436
    void rowModified(final SQLTableEvent evt) {
436
        final int id = evt.getId();
437
        final int id = evt.getId();
437
        if (id < SQLRow.MIN_VALID_ID) {
438
        if (id < SQLRow.MIN_VALID_ID) {
438
            this.putUpdateAll();
439
            this.putUpdateAll();
439
        } else if (CollectionUtils.containsAny(this.tableModel.getReq().getLineFields(), evt.getFields())) {
440
        } else if (CollectionUtils.containsAny(this.tableModel.getReq().getLineFields(), evt.getFields())) {
440
            this.put(evt);
441
            this.put(evt);
441
        }
442
        }
442
        // si on n'affiche pas le champ ignorer
443
        // si on n'affiche pas le champ ignorer
443
    }
444
    }
444
 
445
 
445
    // takes 1-2ms, perhaps cache
446
    // takes 1-2ms, perhaps cache
446
    final Set<SQLTable> getNotForeignTables() {
447
    final Set<SQLTable> getNotForeignTables() {
447
        final Set<SQLTable> res = new HashSet<SQLTable>();
448
        final Set<SQLTable> res = new HashSet<SQLTable>();
448
        final SQLRowValues maxGraph = this.tableModel.getReq().getMaxGraph();
449
        final SQLRowValues maxGraph = this.tableModel.getReq().getMaxGraph();
449
        maxGraph.getGraph().walk(maxGraph, res, new ITransformer<State<Set<SQLTable>>, Set<SQLTable>>() {
450
        maxGraph.getGraph().walk(maxGraph, res, new ITransformer<State<Set<SQLTable>>, Set<SQLTable>>() {
450
            @Override
451
            @Override
451
            public Set<SQLTable> transformChecked(State<Set<SQLTable>> input) {
452
            public Set<SQLTable> transformChecked(State<Set<SQLTable>> input) {
452
                if (input.getPath().length() == 0 || input.isBackwards())
453
                if (input.getPath().length() == 0 || input.isBackwards())
453
                    input.getAcc().add(input.getCurrent().getTable());
454
                    input.getAcc().add(input.getCurrent().getTable());
454
                return input.getAcc();
455
                return input.getAcc();
455
            }
456
            }
456
        }, RecursionType.BREADTH_FIRST, Direction.ANY);
457
        }, RecursionType.BREADTH_FIRST, Direction.ANY);
457
        return res;
458
        return res;
458
    }
459
    }
459
 
460
 
460
    void rowAddedOrDeleted(final SQLTableEvent evt) {
461
    void rowAddedOrDeleted(final SQLTableEvent evt) {
461
        if (evt.getId() < SQLRow.MIN_VALID_ID)
462
        if (evt.getId() < SQLRow.MIN_VALID_ID)
462
            this.putUpdateAll();
463
            this.putUpdateAll();
463
        // if a row of a table that we point to is added, we will care when the referent table will
464
        // if a row of a table that we point to is added, we will care when the referent table will
464
        // point to it
465
        // point to it
465
        else if (this.getNotForeignTables().contains(evt.getTable()))
466
        else if (this.getNotForeignTables().contains(evt.getTable()))
466
            this.put(evt);
467
            this.put(evt);
467
    }
468
    }
468
 
469
 
469
    // *** puts
470
    // *** puts
470
 
471
 
471
    public final void putExternalUpdated(final String externalID, final IPredicate<ListSQLLine> affectedPredicate) {
472
    public final void putExternalUpdated(final String externalID, final IPredicate<ListSQLLine> affectedPredicate) {
472
        this.put(new Runnable() {
473
        this.put(new Runnable() {
473
            @Override
474
            @Override
474
            public void run() {
475
            public void run() {
475
                externalUpdated(externalID, affectedPredicate);
476
                externalUpdated(externalID, affectedPredicate);
476
            }
477
            }
477
        });
478
        });
478
    }
479
    }
479
 
480
 
480
    protected final void externalUpdated(final String externalID, final IPredicate<ListSQLLine> affectedPredicate) {
481
    protected final void externalUpdated(final String externalID, final IPredicate<ListSQLLine> affectedPredicate) {
481
        final List<ListSQLLine> fullList = this.getFullList();
482
        final List<ListSQLLine> fullList = this.getFullList();
482
        synchronized (fullList) {
483
        synchronized (fullList) {
483
            final Set<Integer> indexes = new HashSet<Integer>();
484
            final Set<Integer> indexes = new HashSet<Integer>();
484
            int i = 0;
485
            int i = 0;
485
            for (final SQLTableModelColumn col : this.columns.getAllColumns()) {
486
            for (final SQLTableModelColumn col : this.columns.getAllColumns()) {
486
                if (col.getUsedExternals().contains(externalID)) {
487
                if (col.getUsedExternals().contains(externalID)) {
487
                    indexes.add(i);
488
                    indexes.add(i);
488
                }
489
                }
489
                i++;
490
                i++;
490
            }
491
            }
491
            if (indexes.isEmpty()) {
492
            if (indexes.isEmpty()) {
492
                Log.get().log(Level.INFO, "No columns use " + externalID + " in " + this);
493
                Log.get().log(Level.INFO, "No columns use " + externalID + " in " + this);
493
                return;
494
                return;
494
            }
495
            }
495
 
496
 
496
            for (final ListSQLLine line : fullList) {
497
            for (final ListSQLLine line : fullList) {
497
                if (affectedPredicate.evaluateChecked(line)) {
498
                if (affectedPredicate.evaluateChecked(line)) {
498
                    this.tableModel.getSearchQueue().changeFullList(line.getID(), line, indexes, SearchOne.Mode.CHANGE);
499
                    this.tableModel.getSearchQueue().changeFullList(line.getID(), line, indexes, SearchOne.Mode.CHANGE);
499
                }
500
                }
500
            }
501
            }
501
        }
502
        }
502
    }
503
    }
503
 
504
 
504
    private void put(SQLTableEvent evt) {
505
    private void put(SQLTableEvent evt) {
505
        this.put(UpdateRunnable.create(this.tableModel, evt));
506
        this.put(UpdateRunnable.create(this.tableModel, evt));
506
    }
507
    }
507
 
508
 
508
    public void putUpdateAll() {
509
    public void putUpdateAll() {
509
        this.put(UpdateRunnable.create(this.tableModel));
510
        this.put(UpdateRunnable.create(this.tableModel));
510
    }
511
    }
511
 
512
 
512
    /**
513
    /**
513
     * If this is sleeping, empty the list and call {@link #putUpdateAll()} so that the list reload
514
     * If this is sleeping, empty the list and call {@link #putUpdateAll()} so that the list reload
514
     * itself when this wakes up.
515
     * itself when this wakes up.
515
     * 
516
     * 
516
     * @throws IllegalStateException if not sleeping.
517
     * @throws IllegalStateException if not sleeping.
517
     */
518
     */
518
    void putRemoveAll() {
519
    void putRemoveAll() {
519
        if (!this.isSleeping())
520
        if (!this.isSleeping())
520
            throw new IllegalStateException("not sleeping");
521
            throw new IllegalStateException("not sleeping");
521
        // no user runnables can come between the RmAll and the UpdateAll since runnableAdded()
522
        // no user runnables can come between the RmAll and the UpdateAll since runnableAdded()
522
        // is blocked by our lock, so there won't be any incoherence for them
523
        // is blocked by our lock, so there won't be any incoherence for them
523
        this.put(UpdateRunnable.createRmAll(this, this.tableModel));
524
        this.put(UpdateRunnable.createRmAll(this, this.tableModel));
524
        this.setSleeping(false);
525
        this.setSleeping(false);
525
        // reload the empty list when waking up
526
        // reload the empty list when waking up
526
        this.putUpdateAll();
527
        this.putUpdateAll();
527
    }
528
    }
528
 
529
 
529
    @Override
530
    @Override
530
    protected void willPut(final FutureTask<?> qr) throws InterruptedException {
531
    protected void willPut(final FutureTask<?> qr) throws InterruptedException {
531
        if (SearchQueue.getRunnable(qr) instanceof ChangeAllRunnable) {
532
        if (SearchQueue.getRunnable(qr) instanceof ChangeAllRunnable) {
532
            // si on met tout à jour, ne sert à rien de garder les maj précédentes.
533
            // si on met tout à jour, ne sert à rien de garder les maj précédentes.
533
            this.tasksDo(this.cancelClosure);
534
            this.tasksDo(this.cancelClosure);
534
        }
535
        }
535
    }
536
    }
536
 
537
 
537
    static public enum TaskType {
538
    static public enum TaskType {
538
        USER(false, true), COMPUTE(true, false), SET_STATE(false, false);
539
        USER(false, true), COMPUTE(true, false), SET_STATE(false, false);
539
 
540
 
540
        private final boolean cancelable, dependsOnPrevious;
541
        private final boolean cancelable, dependsOnPrevious;
541
 
542
 
542
        private TaskType(boolean cancelable, boolean dependsOnPrevious) {
543
        private TaskType(boolean cancelable, boolean dependsOnPrevious) {
543
            this.cancelable = cancelable;
544
            this.cancelable = cancelable;
544
            this.dependsOnPrevious = dependsOnPrevious;
545
            this.dependsOnPrevious = dependsOnPrevious;
545
        }
546
        }
546
    }
547
    }
547
 
548
 
548
    static public final IClosure<Deque<FutureTask<?>>> createCancelClosure(final SleepingQueue q, final ITransformer<? super FutureTask<?>, TaskType> cancelablePred) {
549
    static public final IClosure<Deque<FutureTask<?>>> createCancelClosure(final SleepingQueue q, final ITransformer<? super FutureTask<?>, TaskType> cancelablePred) {
549
        return new IClosure<Deque<FutureTask<?>>>() {
550
        return new IClosure<Deque<FutureTask<?>>>() {
550
            @Override
551
            @Override
551
            public void executeChecked(final Deque<FutureTask<?>> tasks) {
552
            public void executeChecked(final Deque<FutureTask<?>> tasks) {
552
                // on part de la fin et on supprime toutes les maj jusqu'a ce qu'on trouve
553
                // on part de la fin et on supprime toutes les maj jusqu'a ce qu'on trouve
553
                // un runnable qui n'est pas annulable
554
                // un runnable qui n'est pas annulable
554
                final Iterator<FutureTask<?>> iter = tasks.descendingIterator();
555
                final Iterator<FutureTask<?>> iter = tasks.descendingIterator();
555
                boolean needsPrevious = false;
556
                boolean needsPrevious = false;
556
                while (iter.hasNext() && !needsPrevious) {
557
                while (iter.hasNext() && !needsPrevious) {
557
                    final FutureTask<?> current = iter.next();
558
                    final FutureTask<?> current = iter.next();
558
                    final TaskType type = cancelablePred.transformChecked(current);
559
                    final TaskType type = cancelablePred.transformChecked(current);
559
                    needsPrevious = type.dependsOnPrevious;
560
                    needsPrevious = type.dependsOnPrevious;
560
                    if (type.cancelable)
561
                    if (type.cancelable)
561
                        iter.remove();
562
                        iter.remove();
562
                }
563
                }
563
                // if we stop only because we ran out of items, continue with beingRun
564
                // if we stop only because we ran out of items, continue with beingRun
564
                if (!needsPrevious) {
565
                if (!needsPrevious) {
565
                    // before trying to cancel being run we should have been through all the backlog
566
                    // before trying to cancel being run we should have been through all the backlog
566
                    assert !iter.hasNext();
567
                    assert !iter.hasNext();
567
                    final FutureTask<?> br = q.getBeingRun();
568
                    final FutureTask<?> br = q.getBeingRun();
568
                    if (br != null && cancelablePred.transformChecked(br).cancelable) {
569
                    if (br != null && cancelablePred.transformChecked(br).cancelable) {
569
                        // might already be done by now, but it's OK cancel() will just return false
570
                        // might already be done by now, but it's OK cancel() will just return false
570
                        br.cancel(true);
571
                        br.cancel(true);
571
                    }
572
                    }
572
                }
573
                }
573
            }
574
            }
574
        };
575
        };
575
    }
576
    }
576
 
577
 
577
}
578
}