OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 142 | 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
 
142 ilm 16
import org.openconcerto.sql.element.SQLElement;
17 ilm 17
import org.openconcerto.sql.model.FieldPath;
142 ilm 18
import org.openconcerto.sql.model.IFieldPath;
17 ilm 19
import org.openconcerto.sql.model.SQLField;
20
import org.openconcerto.sql.model.SQLFieldsSet;
21
import org.openconcerto.sql.model.SQLRow;
22
import org.openconcerto.sql.model.SQLRowAccessor;
23
import org.openconcerto.sql.model.SQLRowValues;
24
import org.openconcerto.sql.model.SQLRowValues.ForeignCopyMode;
25
import org.openconcerto.sql.model.SQLTable;
26
import org.openconcerto.sql.model.graph.Path;
142 ilm 27
import org.openconcerto.sql.request.BaseFillSQLRequest;
182 ilm 28
import org.openconcerto.sql.request.ComboSQLRequest.KeepMode;
142 ilm 29
import org.openconcerto.sql.request.ListSQLRequest;
17 ilm 30
import org.openconcerto.utils.cc.IClosure;
31
import org.openconcerto.utils.change.ListChangeIndex;
32
import org.openconcerto.utils.change.ListChangeRecorder;
33
 
34
import java.beans.PropertyChangeListener;
35
import java.beans.PropertyChangeSupport;
36
import java.lang.ref.WeakReference;
37
import java.util.ArrayList;
38
import java.util.Collections;
39
import java.util.HashSet;
40
import java.util.List;
41
import java.util.Set;
142 ilm 42
import java.util.concurrent.atomic.AtomicBoolean;
17 ilm 43
 
93 ilm 44
import javax.swing.SwingUtilities;
45
 
46
import net.jcip.annotations.GuardedBy;
47
 
17 ilm 48
/**
49
 * Define the columns and lines for ITableModel.
50
 *
51
 * @author Sylvain
52
 */
53
public abstract class SQLTableModelSource {
54
 
93 ilm 55
    protected static final class DebugRow extends BaseSQLTableModelColumn {
56
        private final SQLTable t;
57
 
58
        protected DebugRow(final SQLTable t) {
59
            // don't put SQLRowAccessor: it's an interface and thus JTable.getDefaultRenderer()
60
            // returns null
61
            super("Fields", Object.class);
62
            this.t = t;
63
        }
64
 
65
        @Override
66
        protected Object show_(SQLRowAccessor r) {
67
            if (r instanceof SQLRow)
68
                return r;
69
            else
70
                return new SQLRowValues((SQLRowValues) r, ForeignCopyMode.COPY_ID_OR_RM);
71
        }
72
 
73
        @Override
74
        public Set<SQLField> getFields() {
75
            return this.t.getFields();
76
        }
77
 
78
        @Override
79
        public Set<FieldPath> getPaths() {
80
            return FieldPath.create(Path.get(this.t), this.t.getFieldsName());
81
        }
82
    }
83
 
142 ilm 84
    private final ListSQLRequest req;
85
    private final SQLElement elem;
93 ilm 86
    // only from EDT
17 ilm 87
    private SQLRowValues inited;
93 ilm 88
    // this.cols + debugCols, unmodifiable
89
    @GuardedBy("this")
90
    private SQLTableModelColumns allCols;
91
    // only from EDT
17 ilm 92
    private final ListChangeRecorder<SQLTableModelColumn> cols;
93 ilm 93
    // only from EDT
94
    private final List<SQLTableModelColumn> debugCols;
17 ilm 95
    // to notify of columns change, better than having one listener per line
96
    private final List<WeakReference<SQLTableModelLinesSource>> lines;
97
 
98
    private final PropertyChangeSupport supp;
99
 
100
    {
101
        this.supp = new PropertyChangeSupport(this);
102
        this.lines = new ArrayList<WeakReference<SQLTableModelLinesSource>>();
103
    }
104
 
142 ilm 105
    public SQLTableModelSource(final ListSQLRequest req, final SQLElement elem) {
106
        if (elem == null)
107
            throw new IllegalArgumentException("Missing element");
108
        this.req = req;
109
        this.elem = elem;
110
        if (!this.getPrimaryTable().equals(this.elem.getTable()))
111
            throw new IllegalArgumentException("not the same table: " + this.getPrimaryTable() + " != " + this.elem);
93 ilm 112
        this.setAllCols(SQLTableModelColumns.empty());
17 ilm 113
        this.cols = new ListChangeRecorder<SQLTableModelColumn>(new ArrayList<SQLTableModelColumn>());
93 ilm 114
        this.debugCols = new ArrayList<SQLTableModelColumn>();
142 ilm 115
        this.inited = req.getGraph();
17 ilm 116
    }
117
 
142 ilm 118
    public SQLElement getElem() {
119
        return this.elem;
120
    }
121
 
182 ilm 122
    protected abstract KeepMode getKeepMode();
123
 
17 ilm 124
    // lazy initialization since this method calls colsChanged() which subclasses overload and
125
    // they need their own attribute that aren't set yet since super() must be the first statement.
93 ilm 126
    public void init() {
127
        assert SwingUtilities.isEventDispatchThread();
17 ilm 128
        if (this.inited == null)
129
            return;
130
 
131
        final SQLRowValues graph = this.inited;
132
 
182 ilm 133
        final boolean moreDebugCols = this.getKeepMode() == KeepMode.GRAPH;
17 ilm 134
        graph.walkFields(new IClosure<FieldPath>() {
135
            @Override
136
            public void executeChecked(final FieldPath input) {
137
                final SQLField f = input.getField();
138
                if (f.getTable().getLocalContentFields().contains(f)) {
142 ilm 139
                    final SQLTableModelColumnPath col = new SQLTableModelColumnPath(input, null, getElem().getDirectory());
17 ilm 140
                    SQLTableModelSource.this.cols.add(col);
182 ilm 141
                } else if (moreDebugCols) {
93 ilm 142
                    SQLTableModelSource.this.debugCols.add(new SQLTableModelColumnPath(input.getPath(), f.getName(), f.toString()) {
17 ilm 143
                        // don't show the rowValues since it's very verbose (and all content fields
144
                        // are already displayed as normal columns) and unsortable
145
                        @Override
146
                        protected Object show_(SQLRowAccessor r) {
147
                            final Object res = super.show_(r);
148
                            return res instanceof SQLRowValues ? ((SQLRowValues) res).getID() : res;
149
                        }
150
                    });
182 ilm 151
                }
17 ilm 152
            }
153
        }, true);
154
 
182 ilm 155
        if (moreDebugCols)
156
            this.debugCols.add(new DebugRow(getPrimaryTable()));
93 ilm 157
        final SQLField orderField = getPrimaryTable().getOrderField();
158
        if (orderField != null)
159
            this.debugCols.add(new SQLTableModelColumnPath(Path.get(getPrimaryTable()), orderField.getName(), "Order"));
160
        this.debugCols.add(new SQLTableModelColumnPath(Path.get(getPrimaryTable()), getPrimaryTable().getKey().getName(), "PrimaryKey"));
17 ilm 161
 
162
        // at the end so that fireColsChanged() can use it
163
        this.inited = null;
93 ilm 164
        listenToCols();
165
        updateCols(null);
17 ilm 166
    }
167
 
168
    public SQLTableModelSource(SQLTableModelSource src) {
142 ilm 169
        this.req = src.req;
170
        this.elem = src.elem;
93 ilm 171
        this.setAllCols(src.getAllColumns());
17 ilm 172
        this.cols = new ListChangeRecorder<SQLTableModelColumn>(new ArrayList<SQLTableModelColumn>(src.cols));
93 ilm 173
        this.debugCols = new ArrayList<SQLTableModelColumn>(src.debugCols);
17 ilm 174
        this.inited = null;
175
        listenToCols();
176
    }
177
 
178
    private void listenToCols() {
93 ilm 179
        assert SwingUtilities.isEventDispatchThread();
17 ilm 180
        // keep allCols in sync with cols, and listen to any change
19 ilm 181
        this.cols.getRecipe().addListener(new IClosure<ListChangeIndex<SQLTableModelColumn>>() {
17 ilm 182
            @Override
19 ilm 183
            public void executeChecked(ListChangeIndex<SQLTableModelColumn> change) {
93 ilm 184
                updateCols(change);
17 ilm 185
            }
186
        });
187
    }
188
 
93 ilm 189
    protected final void updateCols(ListChangeIndex<SQLTableModelColumn> change) {
190
        assert SwingUtilities.isEventDispatchThread();
191
        // do not fire while initializing
192
        assert this.inited == null;
193
 
194
        if (change != null && change.getItemsAdded().isEmpty() && change.getItemsRemoved().isEmpty())
195
            return;
196
        final SQLTableModelSourceState beforeState = this.createState();
197
        this.setAllCols(new SQLTableModelColumns(this.cols, this.debugCols));
198
        colsChanged(change == null ? new ListChangeIndex.Add<SQLTableModelColumn>(0, this.getAllColumns().getAllColumns()) : change);
199
        final SQLTableModelSourceState afterState = this.createState();
200
        fireColsChanged(beforeState, afterState);
201
    }
202
 
142 ilm 203
    protected final SQLTableModelSourceState createState() {
204
        return new SQLTableModelSourceState(this.getAllColumns(), this.getReq());
205
    }
93 ilm 206
 
142 ilm 207
    private final void colsChanged(final ListChangeIndex<SQLTableModelColumn> change) {
208
        final AtomicBoolean biggerGraph = new AtomicBoolean(false);
209
        // add needed fields for each new column
210
        this.getReq().changeGraphToFetch(new IClosure<SQLRowValues>() {
211
            @Override
212
            public void executeChecked(SQLRowValues g) {
213
                for (final SQLTableModelColumn col : change.getItemsAdded()) {
214
                    // DebugRow should uses all *fetched* fields, but since it cannot know them
215
                    // getPaths() return all fields in the table. So don't fetch all fields just for
216
                    // this debug column
217
                    if (!(col instanceof DebugRow)) {
218
                        for (final IFieldPath p : col.getPaths()) {
219
                            if (BaseFillSQLRequest.addToFetch(g, p.getPath(), Collections.singleton(p.getFieldName())))
220
                                biggerGraph.set(true);
221
                        }
222
                    }
223
                }
224
            }
225
        });
226
        if (biggerGraph.get() && !allowBiggerGraph())
227
            throw new IllegalStateException("Bigger graph not allowed");
17 ilm 228
    }
229
 
142 ilm 230
    protected abstract boolean allowBiggerGraph();
231
 
93 ilm 232
    private void fireColsChanged(final SQLTableModelSourceState beforeState, final SQLTableModelSourceState afterState) {
17 ilm 233
        // let know each of our LinesSource that the columns have changed
142 ilm 234
        for (final SQLTableModelLinesSource line : getLines()) {
235
            line.colsChanged(beforeState, afterState);
236
        }
237
        // before notifying our regular listeners
238
        this.supp.firePropertyChange("cols", null, this.cols);
239
    }
240
 
241
    private final List<SQLTableModelLinesSource> getLines() {
242
        final List<SQLTableModelLinesSource> res = new ArrayList<SQLTableModelLinesSource>();
17 ilm 243
        int i = 0;
244
        while (i < this.lines.size()) {
245
            final WeakReference<SQLTableModelLinesSource> l = this.lines.get(i);
246
            final SQLTableModelLinesSource line = l.get();
247
            if (line == null)
248
                this.lines.remove(i);
249
            else {
142 ilm 250
                res.add(line);
17 ilm 251
                i++;
252
            }
253
        }
142 ilm 254
        return res;
17 ilm 255
    }
256
 
142 ilm 257
    protected final int getLinesCount() {
258
        return this.getLines().size();
259
    }
260
 
17 ilm 261
    public final SQLTableModelLinesSource createLinesSource(ITableModel model) {
93 ilm 262
        this.init();
17 ilm 263
        final SQLTableModelLinesSource res = this._createLinesSource(model);
264
        this.lines.add(new WeakReference<SQLTableModelLinesSource>(res));
265
        return res;
266
    }
267
 
268
    protected abstract SQLTableModelLinesSource _createLinesSource(ITableModel model);
269
 
20 ilm 270
    /**
271
     * The maximum graph of the lines returned by {@link #createLinesSource(ITableModel)}.
272
     *
273
     * @return the maximum graph of our lines.
274
     */
142 ilm 275
    public final SQLRowValues getMaxGraph() {
276
        return this.getReq().getGraphToFetch();
277
    }
20 ilm 278
 
17 ilm 279
    // * columns
280
 
281
    /**
282
     * The normal columns.
283
     *
284
     * @return the normal columns.
285
     */
286
    public final List<SQLTableModelColumn> getColumns() {
287
        this.init();
288
        return this.cols;
289
    }
290
 
93 ilm 291
    private synchronized void setAllCols(SQLTableModelColumns allCols) {
292
        this.allCols = allCols;
293
    }
294
 
17 ilm 295
    /**
296
     * The normal columns plus some debug columns. Usually primary and foreign keys.
297
     *
298
     * @return the debub columns.
299
     */
93 ilm 300
    public synchronized final SQLTableModelColumns getAllColumns() {
301
        return this.allCols;
17 ilm 302
    }
303
 
80 ilm 304
    public final void addDebugColumn(final SQLTableModelColumn col) {
305
        this.init();
93 ilm 306
        this.debugCols.add(col);
307
        updateCols(new ListChangeIndex.Add<SQLTableModelColumn>(this.getAllColumns().size(), Collections.singleton(col)));
80 ilm 308
    }
309
 
17 ilm 310
    public final SQLTableModelColumn getColumn(int index) {
93 ilm 311
        return this.getAllColumns().getAllColumns().get(index);
17 ilm 312
    }
313
 
314
    /**
315
     * All the columns that depends on the passed field.
316
     *
317
     * @param f the field.
318
     * @return all columns needing <code>f</code>.
319
     */
320
    public final List<SQLTableModelColumn> getColumns(SQLField f) {
93 ilm 321
        return this.getAllColumns().getColumns(f);
17 ilm 322
    }
323
 
324
    /**
325
     * The column depending solely on the passed field.
326
     *
327
     * @param f the field.
328
     * @return the column needing only <code>f</code>.
329
     * @throws IllegalArgumentException if more than one column matches.
330
     */
331
    public final SQLTableModelColumn getColumn(SQLField f) {
93 ilm 332
        return this.getAllColumns().getColumn(f);
17 ilm 333
    }
334
 
80 ilm 335
    /**
336
     * The column depending solely on the passed path.
337
     *
338
     * @param fp the field path.
339
     * @return the column needing only <code>fp</code>.
340
     * @throws IllegalArgumentException if more than one column matches.
341
     */
342
    public final SQLTableModelColumn getColumn(FieldPath fp) {
93 ilm 343
        return this.getAllColumns().getColumn(fp);
80 ilm 344
    }
345
 
17 ilm 346
    public final void addColumnListener(PropertyChangeListener l) {
347
        this.supp.addPropertyChangeListener("cols", l);
348
    }
349
 
350
    public final void rmColumnListener(PropertyChangeListener l) {
351
        this.supp.removePropertyChangeListener("cols", l);
352
    }
353
 
354
    // * SQLIdentifier
355
 
142 ilm 356
    public final ListSQLRequest getReq() {
357
        return this.req;
358
    }
359
 
17 ilm 360
    public final SQLTable getPrimaryTable() {
142 ilm 361
        return this.getReq().getPrimaryTable();
17 ilm 362
    }
363
 
364
    /**
73 ilm 365
     * All the displayed tables, i.e. tables of {@link #getLineFields()}.
17 ilm 366
     *
367
     * @return the displayed tables.
368
     */
369
    public final Set<SQLTable> getTables() {
370
        return new SQLFieldsSet(this.getLineFields()).getTables();
371
    }
372
 
373
    /**
73 ilm 374
     * All fields that affects a line of this source. I.e. not just the displayed fields, but also
375
     * the foreign keys, including intermediate ones (e.g. if this displays [BATIMENT.DES, CPI.DES]
376
     * LOCAL.ID_BATIMENT matters).
17 ilm 377
     *
378
     * @return the fields affecting this.
379
     */
380
    public final Set<SQLField> getLineFields() {
381
        final Set<SQLField> res = new HashSet<SQLField>();
73 ilm 382
        for (final SQLRowValues v : getMaxGraph().getGraph().getItems()) {
383
            for (final String f : v.getFields())
384
                res.add(v.getTable().getField(f));
385
            if (v.getTable().isArchivable())
386
                res.add(v.getTable().getArchiveField());
387
        }
17 ilm 388
        return res;
389
    }
390
 
391
}