OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 83 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 83 Rev 93
Line 11... Line 11...
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.search;
14
 package org.openconcerto.sql.view.list.search;
15
 
15
 
16
import org.openconcerto.sql.model.SQLRow;
-
 
17
import org.openconcerto.sql.model.SQLRowValues;
-
 
18
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
-
 
19
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
-
 
20
import org.openconcerto.sql.model.SQLTable;
-
 
21
import org.openconcerto.sql.model.graph.Link.Direction;
16
import org.openconcerto.sql.model.graph.Link.Direction;
22
import org.openconcerto.sql.model.graph.Path;
17
import org.openconcerto.sql.model.graph.Path;
23
import org.openconcerto.sql.model.graph.Step;
18
import org.openconcerto.sql.model.graph.Step;
24
import org.openconcerto.sql.view.list.ITableModel;
19
import org.openconcerto.sql.view.list.ITableModel;
25
import org.openconcerto.sql.view.list.LineListener;
-
 
26
import org.openconcerto.sql.view.list.ListAccess;
20
import org.openconcerto.sql.view.list.ListAccess;
27
import org.openconcerto.sql.view.list.ListSQLLine;
21
import org.openconcerto.sql.view.list.ListSQLLine;
-
 
22
import org.openconcerto.sql.view.list.UpdateQueue;
-
 
23
import org.openconcerto.sql.view.list.UpdateQueue.TaskType;
-
 
24
import org.openconcerto.sql.view.list.search.SearchOne.Mode;
28
import org.openconcerto.sql.view.search.SearchSpec;
25
import org.openconcerto.sql.view.search.SearchSpec;
29
import org.openconcerto.utils.IFutureTask;
26
import org.openconcerto.utils.IFutureTask;
30
import org.openconcerto.utils.ListMap;
-
 
31
import org.openconcerto.utils.RTInterruptedException;
-
 
32
import org.openconcerto.utils.RecursionType;
-
 
33
import org.openconcerto.utils.SleepingQueue;
27
import org.openconcerto.utils.SleepingQueue;
34
import org.openconcerto.utils.cc.IPredicate;
28
import org.openconcerto.utils.cc.IClosure;
35
import org.openconcerto.utils.cc.ITransformer;
29
import org.openconcerto.utils.cc.ITransformer;
36
 
30
 
37
import java.util.ArrayList;
-
 
38
import java.util.Collection;
31
import java.util.Collection;
39
import java.util.List;
-
 
40
import java.util.Set;
32
import java.util.Deque;
41
import java.util.concurrent.Callable;
-
 
42
import java.util.concurrent.ExecutionException;
-
 
43
import java.util.concurrent.FutureTask;
33
import java.util.concurrent.FutureTask;
44
 
34
 
-
 
35
import javax.swing.SwingUtilities;
-
 
36
 
45
public final class SearchQueue extends SleepingQueue {
37
public final class SearchQueue extends SleepingQueue {
46
 
38
 
-
 
39
    static public interface SetStateRunnable extends Runnable {
-
 
40
    }
-
 
41
 
47
    /**
42
    /**
48
     * Whether the passed future performs a search.
43
     * Whether the passed future performs a search.
49
     * 
44
     * 
50
     * @param f a task in this queue, can be <code>null</code>.
45
     * @param f a task in this queue, can be <code>null</code>.
51
     * @return <code>true</code> if <code>f</code> searches.
46
     * @return <code>true</code> if <code>f</code> searches.
52
     */
47
     */
53
    public static boolean isSearch(FutureTask<?> f) {
48
    public static boolean isSearch(final FutureTask<?> f) {
-
 
49
        final Runnable r = getRunnable(f);
54
        return (f instanceof IFutureTask) && ((IFutureTask<?>) f).getRunnable() instanceof SearchRunnable;
50
        return r instanceof SearchRunnable && ((SearchRunnable) r).performsSearch();
-
 
51
    }
-
 
52
 
-
 
53
    public static Runnable getRunnable(final FutureTask<?> f) {
-
 
54
        if (f instanceof IFutureTask)
-
 
55
            return ((IFutureTask<?>) f).getRunnable();
-
 
56
        else
-
 
57
            return null;
55
    }
58
    }
56
 
59
 
57
    /**
60
    /**
58
     * The last referent step of the passed path.
61
     * The last referent step of the passed path.
59
     * 
62
     * 
Line 66... Line 69...
66
        final boolean lastIsForeign = lastStep == null || lastStep.getDirection() == Direction.FOREIGN;
69
        final boolean lastIsForeign = lastStep == null || lastStep.getDirection() == Direction.FOREIGN;
67
        return lastIsForeign ? null : lastStep.getSingleField().getName();
70
        return lastIsForeign ? null : lastStep.getSingleField().getName();
68
    }
71
    }
69
 
72
 
70
    private final ITableModel model;
73
    private final ITableModel model;
-
 
74
    // only accessed within this queue
71
    SearchSpec search;
75
    SearchSpec search;
72
    private final List<ListSQLLine> fullList;
-
 
73
    private final ListAccess listAccess;
76
    private final ListAccess listAccess;
-
 
77
    // thread-safe
74
    private final LineListener lineListener;
78
    private final IClosure<Deque<FutureTask<?>>> cancelClosure;
75
 
79
 
76
    public SearchQueue(final ListAccess la) {
80
    public SearchQueue(final ListAccess la) {
77
        super(SearchQueue.class.getName() + " on " + la.getModel());
81
        super(SearchQueue.class.getName() + " on " + la.getModel());
78
        this.listAccess = la;
82
        this.listAccess = la;
79
        this.model = la.getModel();
83
        this.model = la.getModel();
80
        this.search = null;
84
        this.search = null;
81
        this.fullList = new ArrayList<ListSQLLine>();
-
 
82
 
-
 
83
        this.lineListener = new LineListener() {
85
        this.cancelClosure = UpdateQueue.createCancelClosure(this, new ITransformer<FutureTask<?>, TaskType>() {
84
            @Override
86
            @Override
85
            public void lineChanged(int id, ListSQLLine l, Set<Integer> colIndex) {
87
            public TaskType transformChecked(FutureTask<?> input) {
86
                changeFullList(id, l, colIndex);
88
                final Runnable r = getRunnable(input);
87
            }
89
                if (r instanceof SearchRunnable)
88
        };
90
                    return TaskType.COMPUTE;
89
        this.getModel().getLinesSource().addLineListener(this.lineListener);
91
                else if (r instanceof SetStateRunnable)
90
    }
-
 
91
 
-
 
92
    @Override
92
                    return TaskType.SET_STATE;
93
    protected void willDie() {
93
                else
94
        this.getModel().getLinesSource().rmLineListener(this.lineListener);
-
 
95
        super.willDie();
94
                    return TaskType.USER;
96
    }
95
            }
97
 
-
 
98
    /**
-
 
99
     * The lines and their path affected by a change of the passed row.
-
 
100
     * 
-
 
101
     * @param r the row that has changed.
-
 
102
     * @return the refreshed lines and their changed paths.
-
 
103
     */
96
        });
104
    public ListMap<ListSQLLine, Path> getAffectedLines(final SQLRow r) {
-
 
105
        return this.execGetAffected(r, new ListMap<ListSQLLine, Path>(), true);
-
 
106
    }
97
    }
107
 
98
 
108
    public ListMap<Path, ListSQLLine> getAffectedPaths(final SQLRow r) {
99
    public void orderChanged() {
109
        return this.execGetAffected(r, new ListMap<Path, ListSQLLine>(), false);
100
        // don't search all if only order has changed
110
    }
101
        this.put(new SearchRunnable(this) {
111
 
102
 
112
    private <K, V> ListMap<K, V> execGetAffected(final SQLRow r, final ListMap<K, V> res, final boolean byLine) {
-
 
113
        return this.execute(new Callable<ListMap<K, V>>() {
-
 
114
            @Override
103
            @Override
115
            public ListMap<K, V> call() throws Exception {
104
            protected boolean performsSearch() {
116
                return getAffected(r, res, byLine);
105
                return false;
117
            }
-
 
118
        });
-
 
119
    }
-
 
120
 
-
 
121
    /**
-
 
122
     * Executes <code>c</code> in this queue, blocking the current thread.
-
 
123
     * 
-
 
124
     * @param <R> type of result
-
 
125
     * @param c what to do.
-
 
126
     * @return the result of <code>c</code>.
-
 
127
     */
-
 
128
    private <R> R execute(final Callable<R> c) {
-
 
129
        try {
-
 
130
            return this.execute(new FutureTask<R>(c)).get();
-
 
131
        } catch (InterruptedException e) {
-
 
132
            throw new RTInterruptedException(e);
-
 
133
        } catch (ExecutionException e) {
-
 
134
            throw new IllegalStateException(e);
-
 
135
        }
-
 
136
    }
106
            }
137
 
107
 
138
    // must be called from within this queue, as this method use fullList
-
 
139
    private <K, V> ListMap<K, V> getAffected(final SQLRow r, ListMap<K, V> res, boolean byLine) {
-
 
140
        final SQLTable t = r.getTable();
-
 
141
        final int id = r.getID();
-
 
142
        if (id < SQLRow.MIN_VALID_ID)
-
 
143
            throw new IllegalArgumentException("invalid ID: " + id);
-
 
144
        if (!this.fullList.isEmpty()) {
-
 
145
            final SQLRowValues proto = this.getModel().getLinesSource().getParent().getMaxGraph();
-
 
146
            final List<Path> pathsToT = new ArrayList<Path>();
-
 
147
            proto.getGraph().walk(proto, pathsToT, new ITransformer<State<List<Path>>, List<Path>>() {
-
 
148
                @Override
108
            @Override
149
                public List<Path> transformChecked(State<List<Path>> input) {
-
 
150
                    if (input.getCurrent().getTable() == t) {
-
 
151
                        input.getAcc().add(input.getPath());
-
 
152
                    }
-
 
153
                    return input.getAcc();
-
 
154
                }
-
 
155
            }, RecursionType.BREADTH_FIRST, Direction.ANY);
-
 
156
            for (final Path p : pathsToT) {
-
 
157
                final String lastReferentField = getLastReferentField(p);
-
 
158
                for (final ListSQLLine line : this.fullList) {
-
 
159
                    boolean put = false;
-
 
160
                    for (final SQLRowValues current : line.getRow().followPath(p, CreateMode.CREATE_NONE, false)) {
-
 
161
                        // works for rowValues w/o any ID
-
 
162
                        if (current != null && current.getID() == id) {
-
 
163
                            put = true;
-
 
164
                        }
109
            public void run() {
165
                    }
-
 
166
                    // if the modified row isn't in the existing line, it might still affect it if
-
 
167
                    // it's a referent row insertion
110
                SwingUtilities.invokeLater(new Runnable() {
168
                    if (!put && lastReferentField != null && r.exists()) {
-
 
169
                        final int foreignID = r.getInt(lastReferentField);
-
 
170
                        for (final SQLRowValues current : line.getRow().followPath(p.minusLast(), CreateMode.CREATE_NONE, false)) {
-
 
171
                            if (current.getID() == foreignID) {
-
 
172
                                put = true;
-
 
173
                            }
111
                    @Override
174
                        }
-
 
175
                    }
-
 
176
                    if (put) {
112
                    public void run() {
177
                        // add to the list of paths that have been refreshed
-
 
178
                        add(byLine, res, p, line);
113
                        getAccess().setList(null, null);
179
                    }
-
 
180
                }
-
 
181
            }
114
                    }
-
 
115
                });
182
        }
116
            }
183
        return res;
117
        });
184
    }
118
    }
185
 
119
 
186
    @SuppressWarnings("unchecked")
-
 
187
    <V, K> void add(boolean byLine, ListMap<K, V> res, final Path p, final ListSQLLine line) {
120
    public void changeFullList(final int id, final ListSQLLine modifiedLine, final Collection<Integer> modifiedCols, final Mode mode) {
188
        if (byLine)
-
 
189
            res.add((K) line, (V) p);
121
        final SearchOne oneSearchRunnable = new SearchOne(this, id, modifiedLine, modifiedCols, mode);
190
        else
-
 
191
            res.add((K) p, (V) line);
122
        this.put(oneSearchRunnable);
192
    }
123
    }
193
 
124
 
194
    private synchronized void changeFullList(final int id, final ListSQLLine modifiedLine, final Collection<Integer> modifiedCols) {
-
 
195
        final SearchOne oneSearchRunnable = new SearchOne(this, id, modifiedLine, modifiedCols);
125
    public void fullListChanged() {
196
        this.putTask(new ChangeListOne("changeFullList " + id + " newLine: " + modifiedLine, this, modifiedLine, id, oneSearchRunnable));
-
 
197
        this.putTask(oneSearchRunnable);
126
        fullDataChange();
198
    }
127
    }
199
 
128
 
200
    public synchronized void setFullList(final List<ListSQLLine> l) {
129
    public void setSearch(final SearchSpec s) {
201
        if (l == null)
130
        this.setSearch(s, null);
202
            throw new NullPointerException();
-
 
203
        this.putTask(new ChangeListAll("setFullList", this, l));
-
 
204
        fullDataChange();
-
 
205
    }
131
    }
206
 
132
 
207
    public synchronized void setSearch(final SearchSpec s) {
133
    public void setSearch(final SearchSpec s, final Runnable r) {
-
 
134
        // needs to be 2 different runnables, that way if the search is changed and then the table
-
 
135
        // is updated : the queue would naively contain setSearch, searchAll, searchAll and thus we
-
 
136
        // can cancel one searchAll. Whereas if the setSearch was contained in searchAll, we
-
 
137
        // couldn't cancel it.
-
 
138
        // use tasksDo() so that no other runnable can come between setSearch and searchAll.
-
 
139
        // Otherwise a runnable might the new search query but not the new filtered list.
-
 
140
        this.tasksDo(new IClosure<Deque<FutureTask<?>>>() {
-
 
141
            @Override
-
 
142
            public void executeChecked(Deque<FutureTask<?>> input) {
208
        this.putTask(new Runnable() {
143
                put(new SetStateRunnable() {
-
 
144
                    @Override
209
            public void run() {
145
                    public void run() {
210
                SearchQueue.this.search = s;
146
                        SearchQueue.this.search = s;
211
            }
147
                    }
212
        });
148
                });
213
        fullDataChange();
149
                fullDataChange();
-
 
150
                if (r != null) {
-
 
151
                    put(new Runnable() {
-
 
152
                        @Override
-
 
153
                        public void run() {
-
 
154
                            SwingUtilities.invokeLater(r);
214
    }
155
                        }
215
 
-
 
216
    private synchronized void fullDataChange() {
156
                    });
217
        this.clearCompute();
157
                }
218
        this.putTask(new SearchAll(this));
158
            }
-
 
159
        });
219
    }
160
    }
220
 
161
 
221
    private synchronized void putTask(final Runnable r) {
162
    private void fullDataChange() {
222
        this.execute(new IFutureTask<Object>(r, null));
163
        this.put(new SearchAll(this));
223
    }
164
    }
224
 
165
 
225
    private synchronized void clearCompute() {
-
 
226
        this.cancel(new IPredicate<FutureTask<?>>() {
-
 
227
            @Override
166
    @Override
-
 
167
    protected void willPut(final FutureTask<?> qr) throws InterruptedException {
228
            public boolean evaluateChecked(FutureTask<?> f) {
168
        if (getRunnable(qr) instanceof SearchAll) {
-
 
169
            // si on recherche tout, ne sert à rien de garder les recherches précédentes.
229
                return isSearch(f);
170
            this.tasksDo(this.cancelClosure);
230
            }
171
        }
231
        });
-
 
232
    }
172
    }
233
 
173
 
-
 
174
    @Override
234
    public String toString() {
175
    public String toString() {
235
        return this.getClass().getName() + " for " + this.getModel();
176
        return this.getClass().getName() + " for " + this.getModel();
236
    }
177
    }
237
 
178
 
238
    final SearchSpec getSearch() {
179
    final SearchSpec getSearch() {
239
        return this.search;
180
        return this.search;
240
    }
181
    }
241
 
182
 
242
    final List<ListSQLLine> getFullList() {
-
 
243
        return this.fullList;
-
 
244
    }
-
 
245
 
-
 
246
    final ListAccess getAccess() {
183
    final ListAccess getAccess() {
247
        return this.listAccess;
184
        return this.listAccess;
248
    }
185
    }
249
 
186
 
250
    public final int getFullListSize() {
-
 
251
        return this.fullList.size();
-
 
252
    }
-
 
253
 
-
 
254
    public final ITableModel getModel() {
187
    public final ITableModel getModel() {
255
        return this.model;
188
        return this.model;
256
    }
189
    }
257
}
190
}