OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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