OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 144 | 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.element;
15
 
16
import org.openconcerto.sql.Configuration;
132 ilm 17
import org.openconcerto.sql.Log;
83 ilm 18
import org.openconcerto.sql.TM;
144 ilm 19
import org.openconcerto.sql.element.SQLElement.PrivateMode;
17 ilm 20
import org.openconcerto.sql.element.SQLElement.ReferenceAction;
132 ilm 21
import org.openconcerto.sql.element.SQLElementLink.LinkType;
22
import org.openconcerto.sql.model.FieldRef;
17 ilm 23
import org.openconcerto.sql.model.SQLField;
24
import org.openconcerto.sql.model.SQLRow;
25
import org.openconcerto.sql.model.SQLRowAccessor;
26
import org.openconcerto.sql.model.SQLRowValues;
132 ilm 27
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
83 ilm 28
import org.openconcerto.sql.model.SQLRowValuesCluster;
17 ilm 29
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
30
import org.openconcerto.sql.model.SQLSelect;
132 ilm 31
import org.openconcerto.sql.model.SQLSelect.LockStrength;
17 ilm 32
import org.openconcerto.sql.model.SQLTable;
33
import org.openconcerto.sql.model.Where;
34
import org.openconcerto.sql.model.graph.Link;
132 ilm 35
import org.openconcerto.sql.model.graph.Link.Direction;
36
import org.openconcerto.sql.model.graph.Path;
37
import org.openconcerto.sql.request.SQLFieldTranslator;
38
import org.openconcerto.utils.CollectionMap2.Mode;
39
import org.openconcerto.utils.CollectionMap2Itf.ListMapItf;
83 ilm 40
import org.openconcerto.utils.CollectionUtils;
132 ilm 41
import org.openconcerto.utils.CompareUtils;
83 ilm 42
import org.openconcerto.utils.ListMap;
90 ilm 43
import org.openconcerto.utils.Tuple3;
132 ilm 44
import org.openconcerto.utils.cc.CustomEquals;
45
import org.openconcerto.utils.cc.CustomEquals.ProxyItf;
46
import org.openconcerto.utils.cc.HashingStrategy;
17 ilm 47
import org.openconcerto.utils.cc.ITransformer;
48
 
49
import java.sql.SQLException;
50
import java.util.ArrayList;
51
import java.util.Collection;
52
import java.util.Collections;
53
import java.util.HashMap;
54
import java.util.HashSet;
83 ilm 55
import java.util.IdentityHashMap;
17 ilm 56
import java.util.Iterator;
57
import java.util.List;
58
import java.util.Map;
83 ilm 59
import java.util.Map.Entry;
17 ilm 60
import java.util.Set;
61
import java.util.SortedMap;
62
import java.util.TreeMap;
63
 
64
/**
65
 * Cache several trees of rows (a row and its descendants).
66
 *
67
 * @author Sylvain
68
 */
69
public final class TreesOfSQLRows {
70
 
71
    public static final TreesOfSQLRows createFromIDs(final SQLElement elem, final Collection<? extends Number> ids) {
72
        final List<SQLRow> rows = new ArrayList<SQLRow>(ids.size());
132 ilm 73
        for (final Number id : ids) {
74
            // don't access the DB here, expand() will do it once for all rows
75
            rows.add(new SQLRow(elem.getTable(), id.intValue()));
76
        }
17 ilm 77
        return new TreesOfSQLRows(elem, rows);
78
    }
79
 
132 ilm 80
    private static String createRestrictDesc(SQLElement refElem, SQLRowAccessor refVals, SQLElementLink elemLink) {
17 ilm 81
        final String rowDesc = refElem != null ? refElem.getDescription(refVals.asRow()) : refVals.asRow().toString();
132 ilm 82
        final String fieldS = getLabel(elemLink, null);
17 ilm 83
        // la tâche du 26/05 ne peut perdre son champ UTILISATEUR
83 ilm 84
        return TM.getTM().trM("sqlElement.linkCantBeCut", CollectionUtils.createMap("row", refVals.asRow(), "rowDesc", rowDesc, "fieldLabel", fieldS));
17 ilm 85
    }
86
 
132 ilm 87
    private static String getLabel(SQLElementLink elemLink, Path p) {
88
        final SQLFieldTranslator translator = Configuration.getInstance().getTranslator();
89
        final SQLTable table;
90
        final String itemName;
91
        if (elemLink != null) {
92
            assert p == null || elemLink.getPath().equals(p);
93
            table = elemLink.getPath().getFirst();
94
            itemName = elemLink.getName();
95
        } else {
96
            assert p.length() == 1 : "Joins should have an Element : " + p;
97
            assert p.getDirection() == Direction.FOREIGN;
98
            final SQLField singleField = p.getStep(0).getSingleField();
99
            table = singleField.getTable();
100
            itemName = singleField.getName();
101
        }
102
        final String fieldLabel = translator.getDescFor(table, itemName).getLabel();
103
        return fieldLabel != null ? fieldLabel : itemName;
104
    }
105
 
17 ilm 106
    private final SQLElement elem;
132 ilm 107
    private final Set<SQLRow> originalRoots;
108
    private Map<SQLRow, SQLRowValues> trees;
109
    private Set<SQLRow> mainRows;
110
    private Map<SQLRow, SQLRowValues> allRows;
111
    private LinksToCut externReferences;
17 ilm 112
 
113
    public TreesOfSQLRows(final SQLElement elem, SQLRow row) {
114
        this(elem, Collections.singleton(row));
115
    }
116
 
83 ilm 117
    public TreesOfSQLRows(final SQLElement elem, final Collection<? extends SQLRowAccessor> rows) {
17 ilm 118
        super();
119
        this.elem = elem;
132 ilm 120
        this.originalRoots = new HashSet<SQLRow>();
121
        this.trees = null;
122
        // check each row and remove duplicates (i.e. this.originalRoots might be smaller than rows)
83 ilm 123
        for (final SQLRowAccessor r : rows) {
17 ilm 124
            this.elem.check(r);
132 ilm 125
            this.originalRoots.add(r.asRow());
17 ilm 126
        }
127
        this.externReferences = null;
128
    }
129
 
130
    public final SQLElement getElem() {
131
        return this.elem;
132
    }
133
 
132 ilm 134
    /**
135
     * The unique rows that were passed to the constructor. NOTE : the rows that do not exist or are
136
     * archived won't be in {@link #getTrees()}.
137
     *
138
     * @return a set of rows.
139
     */
17 ilm 140
    public final Set<SQLRow> getRows() {
132 ilm 141
        return this.originalRoots;
17 ilm 142
    }
143
 
132 ilm 144
    public final Set<SQLRow> getMainRows() {
145
        return this.mainRows;
17 ilm 146
    }
147
 
132 ilm 148
    public final Set<SQLRow> getAllRows() {
149
        return this.allRows.keySet();
150
    }
151
 
152
    /**
153
     * Whether the passed trees are contained in this. NOTE : only mains rows are considered, not
154
     * private ones.
155
     *
156
     * @param o other trees.
157
     * @return <code>true</code> if all {@link #getMainRows() main rows} of <code>o</code> are in
158
     *         this and all {@link #getExternReferences() links to cut} as well.
159
     */
160
    public final boolean containsAll(final TreesOfSQLRows o) {
161
        if (this == o)
162
            return true;
163
        return this.getMainRows().containsAll(o.getMainRows()) && this.getExternReferences().containsAll(o.getExternReferences());
164
    }
165
 
166
    public final boolean isFetched() {
167
        return this.externReferences != null;
168
    }
169
 
170
    private final void checkFetched() {
171
        if (!this.isFetched())
172
            throw new IllegalStateException("Not yet fetched");
173
    }
174
 
175
    /**
176
     * Fetch the rows.
177
     *
178
     * @param ls how to lock rows.
179
     * @return the rows to archive indexed by their roots (a subset of {@link #getRows()}).
180
     * @throws SQLException if an error occurs.
181
     */
182
    public final Map<SQLRow, SQLRowValues> fetch(final LockStrength ls) throws SQLException {
183
        if (this.isFetched())
184
            throw new IllegalStateException("Already fetched");
185
 
186
        final Tuple3<Map<SQLRow, SQLRowValues>, Rows, LinksToCut> expand = this.expand(ls);
187
        this.trees = Collections.unmodifiableMap(expand.get0());
188
        this.mainRows = Collections.unmodifiableSet(expand.get1().mainRows);
189
        this.allRows = Collections.unmodifiableMap(expand.get1().vals);
190
        this.externReferences = expand.get2();
191
 
192
        if (hasFetchedLess())
193
            Log.get().fine("Some rows are missing : " + this.trees.keySet() + "\n" + this.getRows());
194
        return this.getTrees();
195
    }
196
 
197
    public final boolean hasFetchedLess() {
198
        final Set<SQLRow> rowsFetched = this.trees.keySet();
199
        assert this.getRows().containsAll(rowsFetched);
200
        // archived or deleted (or never existed)
201
        return !rowsFetched.equals(this.getRows());
202
    }
203
 
204
    /**
205
     * The trees of rows to archive.
206
     *
207
     * @return the rows to archive indexed by their roots (a subset of {@link #getRows()}).
208
     */
209
    public final Map<SQLRow, SQLRowValues> getTrees() {
210
        checkFetched();
211
        return this.trees;
212
    }
213
 
214
    public final Set<SQLRowValuesCluster> getClusters() {
83 ilm 215
        final Set<SQLRowValuesCluster> res = Collections.newSetFromMap(new IdentityHashMap<SQLRowValuesCluster, Boolean>());
216
        for (final SQLRowValues r : this.getTrees().values()) {
217
            // trees can be linked together
218
            res.add(r.getGraph());
17 ilm 219
        }
220
        return res;
221
    }
222
 
132 ilm 223
    // the root rows linked to their privates and descendant, all visited main rows, the rows
224
    // pointing to visited rows
225
    private final Tuple3<Map<SQLRow, SQLRowValues>, Rows, LinksToCut> expand(final LockStrength ls) throws SQLException {
226
        // root rows (linked to their privates if any) indexed by ID
83 ilm 227
        final Map<Integer, SQLRowValues> valsMap = new HashMap<Integer, SQLRowValues>();
90 ilm 228
        final Rows hasBeen = new Rows();
132 ilm 229
        final LinksToCutMutable toCut = new LinksToCutMutable();
230
        final Map<SQLRow, SQLRowValues> res = new HashMap<SQLRow, SQLRowValues>();
83 ilm 231
 
232
        // fetch privates of root rows
144 ilm 233
        final SQLRowValues privateGraph = this.getElem().createGraph(ArchivedGraph.ARCHIVE_AND_FOREIGNS, PrivateMode.ALL_PRIVATES, true);
132 ilm 234
        final NextRows privates = new NextRows(hasBeen, toCut);
235
        // always fetch to have up to date values, and behave the same way whether the element has
236
        // privates or not
237
        final Set<Number> ids = new HashSet<Number>();
238
        for (final SQLRow r : this.getRows()) {
239
            ids.add(r.getIDNumber());
240
        }
241
        final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(privateGraph);
242
        fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
243
            @Override
244
            public SQLSelect transformChecked(SQLSelect input) {
245
                input.setLockStrength(ls);
246
                input.addLockedTable(privateGraph.getTable().getName());
247
                return input.andWhere(new Where(privateGraph.getTable().getKey(), ids));
83 ilm 248
            }
132 ilm 249
        });
250
        for (final SQLRowValues newVals : fetcher.fetch()) {
251
            final SQLRow r = newVals.asRow();
252
            valsMap.put(newVals.getID(), newVals);
253
            privates.collect(newVals);
254
            res.put(r, newVals);
83 ilm 255
        }
256
 
132 ilm 257
        privates.expand(ls);
258
        return Tuple3.create(res, hasBeen, new LinksToCut(toCut.getMap()));
17 ilm 259
    }
260
 
261
    // NOTE using a collection of vals changed the time it took to archive a site (736 01) from 225s
262
    // to 5s.
263
    /**
264
     * Expand the passed values by going through referent keys.
265
     *
266
     * @param t the table, eg /LOCAL/.
267
     * @param valsMap the values to expand, eg {3=>LOCAL(3)->BAT(4), 12=>LOCAL(12)->BAT(4)}.
90 ilm 268
     * @param hasBeen the rows already expanded, eg {BAT[4], LOCAL[3], LOCAL[12]}.
17 ilm 269
     * @param toCut the links to cut, eg {|BAT.ID_PRECEDENT|=> [BAT[2]]}.
132 ilm 270
     * @param ignorePrivateParentRF <code>true</code> if {@link LinkType#COMPOSITION private links}
271
     *        to <code>t</code> should be ignored.
272
     * @param ls how to lock rows.
17 ilm 273
     * @throws SQLException if a link is {@link ReferenceAction#RESTRICT}.
274
     */
132 ilm 275
    private final void expand(final SQLTable t, final Map<Integer, SQLRowValues> valsMap, final Rows hasBeen, final LinksToCutMutable toCut, final boolean ignorePrivateParentRF, final LockStrength ls)
90 ilm 276
            throws SQLException {
17 ilm 277
        if (valsMap.size() == 0)
278
            return;
279
 
132 ilm 280
        final SQLElement elem = getElem().getElement(t);
281
 
282
        final Set<Link> ownedLinks = new HashSet<Link>();
283
        for (final SQLElementLink elemLink : elem.getOwnedLinks().getByPath().values()) {
284
            if (elemLink.isJoin())
285
                ownedLinks.add(elemLink.getPath().getStep(0).getSingleLink());
286
        }
287
        final Map<Link, SQLElementLink> links = new HashMap<Link, SQLElementLink>();
288
        for (final SQLElementLink elemLink : elem.getLinksOwnedByOthers().getByPath().values()) {
289
            links.put(elemLink.getPath().getStep(-1).getSingleLink(), elemLink);
290
        }
291
 
292
        final NextRows privates = new NextRows(hasBeen, toCut);
17 ilm 293
        for (final Link link : t.getDBSystemRoot().getGraph().getReferentLinks(t)) {
132 ilm 294
            // all owned links are fetched alongside the main row
295
            if (ownedLinks.contains(link))
296
                continue;
297
            final SQLElementLink elemLink = links.get(link);
298
            if (elemLink == null)
299
                throw new IllegalStateException("Referent link " + link + " missing from " + links);
300
            final Path elemPath = elemLink.getPath();
301
            assert elemLink.getOwned() == elem;
302
            assert elemPath.getLast() == t;
303
            final Link foreignLink = elemPath.getStep(-1).getSingleLink();
304
 
305
            if (ignorePrivateParentRF && elemLink.getLinkType().equals(LinkType.COMPOSITION)) {
83 ilm 306
                // if we did fetch the referents rows, they would be contained in hasBeen
307
                continue;
308
            }
17 ilm 309
            // eg "ID_LOCAL"
132 ilm 310
            final String ffName = foreignLink.getSingleField().getName();
311
            final SQLElement refElem = elemLink.getOwner();
312
            final Path pathToTableWithFK = elemPath.minusLast();
313
            final ReferenceAction action = elemLink.getAction();
19 ilm 314
            if (action == null) {
132 ilm 315
                throw new IllegalStateException("Null action for " + refElem + " " + elemPath);
19 ilm 316
            }
83 ilm 317
            final SQLRowValues graphToFetch;
318
            if (action == ReferenceAction.CASCADE) {
319
                // otherwise we would need to find and expand the parent rows of referents
132 ilm 320
                if (refElem.isPrivate())
321
                    throw new UnsupportedOperationException("Cannot cascade to private element " + refElem + " from " + elemPath);
144 ilm 322
                graphToFetch = refElem.createGraph(ArchivedGraph.ARCHIVE_AND_FOREIGNS, PrivateMode.ALL_PRIVATES, true);
83 ilm 323
            } else {
132 ilm 324
                graphToFetch = new SQLRowValues(pathToTableWithFK.getFirst());
83 ilm 325
            }
132 ilm 326
            // add the foreign fields pointing to the rows to expand
327
            graphToFetch.assurePath(pathToTableWithFK).putNulls(foreignLink.getCols());
83 ilm 328
            final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(graphToFetch);
17 ilm 329
            fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
330
                @Override
331
                public SQLSelect transformChecked(SQLSelect input) {
132 ilm 332
                    input.setLockStrength(ls);
333
                    input.addLockedTable(graphToFetch.getTable().getName());
334
                    return input;
335
                }
336
            });
337
            final ListMap<Path, SQLRowValuesListFetcher> fetchers = fetcher.getFetchers(pathToTableWithFK);
338
            if (fetchers.allValues().size() != 1)
339
                throw new IllegalStateException("Fetcher which references " + t + " not found : " + fetchers);
340
            fetchers.allValues().iterator().next().appendSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
341
                @Override
342
                public SQLSelect transformChecked(SQLSelect input) {
343
                    final FieldRef refField = input.getAlias(pathToTableWithFK.getLast()).getField(ffName);
17 ilm 344
                    // eg where RECEPTEUR.ID_LOCAL in (3,12)
182 ilm 345
                    return input.andWhere(Where.inValues(refField, valsMap.keySet()));
17 ilm 346
                }
347
            });
348
            for (final SQLRowValues newVals : fetcher.fetch()) {
349
                final SQLRow r = newVals.asRow();
90 ilm 350
                final boolean already = hasBeen.contains(r);
132 ilm 351
                // a row might reference the same linked row multiple times
352
                final Collection<SQLRowValues> rowsWithFK = newVals.followPath(pathToTableWithFK, CreateMode.CREATE_NONE, false);
17 ilm 353
                switch (action) {
354
                case RESTRICT:
132 ilm 355
                    throw new SQLException(createRestrictDesc(refElem, newVals, elemLink));
17 ilm 356
                case CASCADE:
83 ilm 357
                    if (!already) {
358
                        // walk before linking to existing graph
359
                        privates.collect(newVals);
17 ilm 360
                        // link with existing graph, eg RECEPTEUR(235)->LOCAL(3)
132 ilm 361
                        for (final SQLRowValues rowWithFK : rowsWithFK) {
362
                            rowWithFK.putForeign(foreignLink, hasBeen.getValues(rowWithFK.getForeign(foreignLink).asRow()));
363
                        }
17 ilm 364
                    }
365
                    break;
366
                case SET_EMPTY:
367
                    // if the row should be archived no need to cut any of its links
83 ilm 368
                    if (!already)
132 ilm 369
                        toCut.add(elemLink, rowsWithFK);
17 ilm 370
                    break;
371
                }
83 ilm 372
                // if already expanded just link and do not add to next
373
                if (already) {
132 ilm 374
                    // now link the new join row or the existing row
375
                    for (final SQLRowValues joinRow : rowsWithFK) {
376
                        final boolean linked = hasBeen.tryToLink(joinRow, foreignLink);
377
                        if (!linked)
378
                            throw new IllegalStateException("Join row not found : " + joinRow);
379
                    }
83 ilm 380
                }
17 ilm 381
            }
382
        }
132 ilm 383
        privates.expand(ls);
17 ilm 384
    }
385
 
132 ilm 386
    private final class NextRows {
90 ilm 387
        private final Rows hasBeen;
132 ilm 388
        private final LinksToCutMutable toCut;
389
        private final Map<SQLTable, Map<Integer, SQLRowValues>> mainRows;
390
        // only contains private rows (no main or join rows)
83 ilm 391
        private final Map<SQLTable, Map<Integer, SQLRowValues>> privateRows;
17 ilm 392
 
132 ilm 393
        public NextRows(final Rows hasBeen, final LinksToCutMutable toCut) {
83 ilm 394
            this.hasBeen = hasBeen;
395
            this.toCut = toCut;
132 ilm 396
            this.mainRows = new HashMap<SQLTable, Map<Integer, SQLRowValues>>();
83 ilm 397
            this.privateRows = new HashMap<SQLTable, Map<Integer, SQLRowValues>>();
398
        }
399
 
132 ilm 400
        /**
401
         * Record all private rows of the passed graph.
402
         *
403
         * @param mainRow main row linked to its private graph. NOTE : the main row can itself be a
404
         *        private.
405
         */
83 ilm 406
        private void collect(final SQLRowValues mainRow) {
407
            for (final SQLRowValues privateVals : mainRow.getGraph().getItems()) {
132 ilm 408
                // since newVals isn't in, its privates can't
409
                assert !this.hasBeen.contains(privateVals.asRow());
410
                final SQLElement rowElem = getElem().getElement(privateVals.getTable());
411
                final Map<SQLTable, Map<Integer, SQLRowValues>> m;
412
                final boolean isMainRow = privateVals == mainRow;
413
                if (isMainRow) {
414
                    assert !(rowElem instanceof JoinSQLElement);
415
                    m = this.mainRows;
416
                } else {
417
                    if (rowElem.isPrivate()) {
418
                        m = this.privateRows;
419
                    } else {
420
                        assert rowElem instanceof JoinSQLElement;
421
                        m = null;
422
                    }
423
                }
424
                this.hasBeen.put(privateVals.asRow(), privateVals, isMainRow);
425
                if (m != null) {
426
                    Map<Integer, SQLRowValues> map = m.get(privateVals.getTable());
83 ilm 427
                    if (map == null) {
428
                        map = new HashMap<Integer, SQLRowValues>();
132 ilm 429
                        m.put(privateVals.getTable(), map);
83 ilm 430
                    }
431
                    map.put(privateVals.getID(), privateVals);
432
                }
17 ilm 433
            }
434
        }
83 ilm 435
 
132 ilm 436
        private void expand(final LockStrength ls) throws SQLException {
437
            this.expand(this.mainRows, false, ls);
438
            this.expand(this.privateRows, true, ls);
439
            // if the row has been added to the graph (by another link) no need to cut any of its
440
            // links
441
            this.toCut.restoreLinks(this.hasBeen);
442
        }
443
 
444
        private void expand(final Map<SQLTable, Map<Integer, SQLRowValues>> m, final boolean privateRows, final LockStrength ls) throws SQLException {
445
            for (final Entry<SQLTable, Map<Integer, SQLRowValues>> e : m.entrySet()) {
446
                TreesOfSQLRows.this.expand(e.getKey(), e.getValue(), this.hasBeen, this.toCut, privateRows, ls);
83 ilm 447
            }
448
        }
17 ilm 449
    }
450
 
90 ilm 451
    // unique SQLRowValues indexed by SQLRow
452
    static private final class Rows {
453
 
454
        private final Map<SQLRow, SQLRowValues> vals;
132 ilm 455
        private final Set<SQLRow> mainRows;
90 ilm 456
 
457
        private Rows() {
458
            this.vals = new HashMap<SQLRow, SQLRowValues>();
132 ilm 459
            this.mainRows = new HashSet<SQLRow>();
90 ilm 460
        }
461
 
462
        private boolean contains(final SQLRow r) {
463
            return this.vals.containsKey(r);
464
        }
465
 
466
        private SQLRowValues getValues(final SQLRow r) {
467
            return this.vals.get(r);
468
        }
469
 
132 ilm 470
        private void put(final SQLRow r, final SQLRowValues newVals, final boolean isMainRow) {
90 ilm 471
            assert newVals.asRow().equals(r);
472
            if (this.vals.put(r, newVals) != null)
473
                throw new IllegalStateException("Row already in : " + newVals);
132 ilm 474
            if (isMainRow)
475
                this.mainRows.add(r);
90 ilm 476
        }
132 ilm 477
 
478
        // link rowWithFK if it is already contained in this
479
        private boolean tryToLink(final SQLRowValues rowWithFK, final Link l) {
480
            final SQLRowValues inGraphRow = this.getValues(rowWithFK.asRow());
481
            final boolean linked = inGraphRow != null;
482
            if (linked) {
483
                // add link
484
                final SQLRowValues dest = this.getValues(rowWithFK.getForeign(l).asRow());
485
                if (dest == null)
486
                    throw new IllegalStateException("destination of " + l + " not found for " + rowWithFK);
487
                inGraphRow.putForeign(l, dest);
488
            }
489
            return linked;
490
        }
491
 
90 ilm 492
    }
493
 
132 ilm 494
    static private final class LinksToCutMutable {
495
 
496
        // use List to avoid comparing SQLRowValues instances
497
        private final ListMap<SQLElementLink, SQLRowValues> toCut;
498
 
499
        private LinksToCutMutable() {
500
            this.toCut = new ListMap<SQLElementLink, SQLRowValues>(32, Mode.NULL_FORBIDDEN, false);
501
        }
502
 
503
        private void add(SQLElementLink link, Collection<SQLRowValues> rowsWithFK) {
504
            assert !this.toCut.containsKey(link) || !CollectionUtils.containsAny(this.toCut.get(link), rowsWithFK) : "some rows (and their optional joins) already added : " + link + " " + rowsWithFK;
505
            // a row might reference the same row multiple times
506
            this.toCut.addAll(link, rowsWithFK);
507
        }
508
 
509
        private final ListMap<SQLElementLink, SQLRowValues> getMap() {
510
            return this.toCut;
511
        }
512
 
513
        private final void restoreLinks(final Rows hasBeen) {
514
            final Iterator<Entry<SQLElementLink, List<SQLRowValues>>> iter = this.getMap().entrySet().iterator();
515
            while (iter.hasNext()) {
516
                final Entry<SQLElementLink, List<SQLRowValues>> e = iter.next();
517
                final SQLElementLink elemLink = e.getKey();
518
                final Link linkToCut = elemLink.getPath().getStep(-1).getSingleLink();
519
 
520
                final Iterator<SQLRowValues> iter2 = e.getValue().iterator();
521
                while (iter2.hasNext()) {
522
                    final SQLRowValues rowWithFK = iter2.next();
523
                    if (hasBeen.tryToLink(rowWithFK, linkToCut)) {
524
                        // remove from toCut
525
                        iter2.remove();
526
                    }
527
                }
528
                if (e.getValue().isEmpty())
529
                    iter.remove();
530
            }
531
        }
532
    }
533
 
83 ilm 534
    // ***
535
 
17 ilm 536
    /**
90 ilm 537
     * Put all the main (i.e. non private) rows of the trees (except the roots) in a map by table.
17 ilm 538
     *
539
     * @return the descendants by table.
540
     */
132 ilm 541
    public final Map<SQLTable, List<SQLRowAccessor>> getDescendantsByTable() {
83 ilm 542
        final ListMap<SQLTable, SQLRowAccessor> res = new ListMap<SQLTable, SQLRowAccessor>();
543
        final Set<SQLRow> roots = this.getRows();
544
        for (final SQLRowValuesCluster c : this.getClusters()) {
545
            for (final SQLRowValues v : c.getItems()) {
90 ilm 546
                final SQLRow r = v.asRow();
132 ilm 547
                if (!roots.contains(r) && this.getMainRows().contains(r))
83 ilm 548
                    res.add(v.getTable(), v);
549
            }
17 ilm 550
        }
551
        return res;
552
    }
553
 
554
    // * extern
555
 
132 ilm 556
    static final class LinkToCut implements Comparable<LinkToCut> {
557
        private final SQLElementLink link;
558
        private final String label;
559
 
560
        protected LinkToCut(final SQLElementLink link) {
561
            super();
562
            if (link == null)
563
                throw new NullPointerException("Null link");
564
            this.link = link;
565
            this.label = TreesOfSQLRows.getLabel(this.link, null);
566
        }
567
 
568
        public final Path getPath() {
569
            return this.link.getPath();
570
        }
571
 
572
        public final SQLTable getTable() {
573
            return this.getPath().getFirst();
574
        }
575
 
576
        public final String getItem() {
577
            return this.link.getName();
578
        }
579
 
580
        public final String getLabel() {
581
            return this.label;
582
        }
583
 
584
        @Override
585
        public int hashCode() {
586
            final int prime = 31;
587
            return prime + this.link.hashCode();
588
        }
589
 
590
        @Override
591
        public boolean equals(Object obj) {
592
            if (this == obj)
593
                return true;
594
            if (obj == null)
595
                return false;
596
            if (getClass() != obj.getClass())
597
                return false;
598
            final LinkToCut other = (LinkToCut) obj;
599
            return this.link.equals(other.link);
600
        }
601
 
602
        @Override
603
        public int compareTo(LinkToCut o) {
604
            final int compareTable = CompareUtils.compareList(this.getTable().getSQLName().asList(), o.getTable().getSQLName().asList());
605
            if (compareTable != 0)
606
                return compareTable;
607
            else
608
                return this.getItem().compareTo(o.getItem());
609
        }
610
    }
611
 
612
    static public final class LinksToCut {
613
 
614
        private final ListMapItf<SQLElementLink, SQLRowValues> toCut;
615
 
616
        private LinksToCut(final ListMapItf<SQLElementLink, SQLRowValues> map) {
617
            map.removeAllEmptyCollections();
618
            this.toCut = ListMap.unmodifiableMap(map);
619
        }
620
 
621
        public final ListMapItf<SQLElementLink, SQLRowValues> getMap() {
622
            return this.toCut;
623
        }
624
 
625
        public final SortedMap<LinkToCut, Integer> countByLink() {
626
            final SortedMap<LinkToCut, Integer> res = new TreeMap<LinkToCut, Integer>();
627
            for (final Entry<SQLElementLink, List<SQLRowValues>> e : this.getMap().entrySet()) {
628
                final SQLElementLink elemLink = e.getKey();
629
                res.put(new LinkToCut(elemLink), e.getValue().size());
630
            }
631
            return res;
632
        }
633
 
634
        boolean containsAll(final LinksToCut o) {
635
            if (this == o)
636
                return true;
637
            if (!this.getMap().keySet().containsAll(o.getMap().keySet()))
638
                return false;
639
            final HashingStrategy<SQLRowAccessor> strategy = SQLRowAccessor.getRowStrategy();
640
            for (final Entry<SQLElementLink, ? extends Collection<SQLRowValues>> e : this.getMap().entrySet()) {
641
                final List<SQLRowValues> otherRows = o.getMap().get(e.getKey());
642
                // cannot be empty, see constructor
643
                if (otherRows != null) {
644
                    /*
645
                     * each row can be a graph : it's always the row with the foreign key, so in
646
                     * case of a join there's also the main row attached. Don't bother comparing the
647
                     * all values, just use the IDs.
648
                     */
649
                    final Set<ProxyItf<SQLRowValues>> thisVals = CustomEquals.createSet(strategy, e.getValue());
650
                    final Set<ProxyItf<SQLRowValues>> otherVals = CustomEquals.createSet(strategy, otherRows);
651
                    assert thisVals.size() == e.getValue().size() && otherVals.size() == o.getMap().get(e.getKey()).size() : "There were duplicates";
652
                    if (!thisVals.containsAll(otherVals))
653
                        return false;
654
                }
655
            }
656
            return true;
657
        }
658
    }
659
 
17 ilm 660
    /**
661
     * Return the rows that point to these trees.
662
     *
663
     * @return the rows by referent field.
664
     */
132 ilm 665
    public final LinksToCut getExternReferences() {
666
        checkFetched();
17 ilm 667
        return this.externReferences;
668
    }
144 ilm 669
 
670
    @Override
671
    public String toString() {
672
        return this.getClass().getSimpleName() + " for " + this.getElem() + " " + this.getRows();
673
    }
17 ilm 674
}