OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | 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.Log;
132 ilm 17
import org.openconcerto.sql.ShowAs;
18
import org.openconcerto.sql.model.DBRoot;
17 ilm 19
import org.openconcerto.sql.model.DBStructureItemNotFound;
182 ilm 20
import org.openconcerto.sql.model.DBSystemRoot;
17 ilm 21
import org.openconcerto.sql.model.SQLName;
22
import org.openconcerto.sql.model.SQLTable;
142 ilm 23
import org.openconcerto.sql.request.SQLFieldTranslator;
132 ilm 24
import org.openconcerto.utils.CollectionMap2;
17 ilm 25
import org.openconcerto.utils.CollectionUtils;
83 ilm 26
import org.openconcerto.utils.SetMap;
182 ilm 27
import org.openconcerto.utils.cc.Cookies;
17 ilm 28
import org.openconcerto.utils.cc.ITransformer;
29
 
30
import java.lang.reflect.InvocationTargetException;
19 ilm 31
import java.util.ArrayList;
17 ilm 32
import java.util.Collection;
19 ilm 33
import java.util.Collections;
17 ilm 34
import java.util.HashMap;
19 ilm 35
import java.util.List;
17 ilm 36
import java.util.Map;
132 ilm 37
import java.util.Map.Entry;
17 ilm 38
import java.util.Set;
39
 
142 ilm 40
import net.jcip.annotations.GuardedBy;
41
 
17 ilm 42
/**
182 ilm 43
 * Directory of SQLElement by table. <code>final</code> so that {@link SQLElement} can't depend on a
44
 * subclass and can be shared between applications (e.g. using
45
 * {@link #putAll(SQLElementDirectory)}). If some elements needs shared state, use
46
 * {@link #getCookies()}.
17 ilm 47
 *
48
 * @author Sylvain CUAZ
49
 */
50
public final class SQLElementDirectory {
51
 
182 ilm 52
    private final DBSystemRoot systemRoot;
17 ilm 53
    private final Map<SQLTable, SQLElement> elements;
83 ilm 54
    private final SetMap<String, SQLTable> tableNames;
55
    private final SetMap<String, SQLTable> byCode;
56
    private final SetMap<Class<? extends SQLElement>, SQLTable> byClass;
19 ilm 57
    private final List<DirectoryListener> listeners;
17 ilm 58
 
73 ilm 59
    private String phrasesPkgName;
60
 
142 ilm 61
    @GuardedBy("this")
62
    private SQLFieldTranslator translator;
132 ilm 63
    private final ShowAs showAs;
64
 
182 ilm 65
    private final Cookies cookies;
66
 
67
    public SQLElementDirectory(DBSystemRoot systemRoot) {
68
        this.systemRoot = systemRoot;
17 ilm 69
        this.elements = new HashMap<SQLTable, SQLElement>();
70
        // to mimic elements behaviour, if we add twice the same table
71
        // the second one should replace the first one
83 ilm 72
        this.tableNames = new SetMap<String, SQLTable>();
73
        this.byCode = new SetMap<String, SQLTable>();
74
        this.byClass = new SetMap<Class<? extends SQLElement>, SQLTable>();
19 ilm 75
 
76
        this.listeners = new ArrayList<DirectoryListener>();
73 ilm 77
 
78
        this.phrasesPkgName = null;
132 ilm 79
 
80
        this.showAs = new ShowAs((DBRoot) null);
182 ilm 81
        this.cookies = new Cookies();
17 ilm 82
    }
83
 
144 ilm 84
    public synchronized final void destroy() {
85
        for (final SQLElement elem : this.elements.values()) {
86
            elem.destroy();
87
        }
88
    }
89
 
132 ilm 90
    public final ShowAs getShowAs() {
91
        return this.showAs;
92
    }
93
 
142 ilm 94
    public final synchronized void setTranslator(SQLFieldTranslator translator) {
95
        if (translator.getDirectory() != this)
96
            throw new IllegalArgumentException("Not for this : " + translator);
97
        this.translator = translator;
98
    }
99
 
100
    public synchronized final SQLFieldTranslator getTranslator() {
101
        return this.translator;
102
    }
103
 
182 ilm 104
    public final Cookies getCookies() {
105
        return this.cookies;
106
    }
107
 
83 ilm 108
    private static <K> SQLTable getSoleTable(SetMap<K, SQLTable> m, K key) throws IllegalArgumentException {
17 ilm 109
        final Collection<SQLTable> res = m.getNonNull(key);
110
        if (res.size() > 1)
111
            throw new IllegalArgumentException(key + " is not unique: " + CollectionUtils.join(res, ",", new ITransformer<SQLTable, SQLName>() {
112
                @Override
113
                public SQLName transformChecked(SQLTable input) {
114
                    return input.getSQLName();
115
                }
116
            }));
117
        return CollectionUtils.getSole(res);
118
    }
119
 
120
    public synchronized final void putAll(SQLElementDirectory o) {
182 ilm 121
        // Cookies are needed in addSQLElement() (e.g. by directoryChanged() and getShowAs())
122
        this.cookies.putAll(o.getCookies());
123
        // copy since addSQLElement() removes elements from their previous directory
124
        for (final SQLElement elem : new ArrayList<>(o.getElements())) {
17 ilm 125
            if (!this.contains(elem.getTable()))
126
                this.addSQLElement(elem);
127
        }
142 ilm 128
        this.translator.putAll(o.getTranslator());
17 ilm 129
    }
130
 
131
    /**
132
     * Add an element by creating it with the no-arg constructor. If the element cannot find its
133
     * table and thus raise DBStructureItemNotFound, the exception is logged.
134
     *
135
     * @param element the element to add.
136
     */
137
    public final void addSQLElement(final Class<? extends SQLElement> element) {
156 ilm 138
        this.addSQLElement(element, null);
139
    }
140
 
141
    public final void addSQLElement(final Class<? extends SQLElement> element, final DBRoot root) {
142
        final SQLElement newInstance;
17 ilm 143
        try {
156 ilm 144
            if (root == null)
145
                newInstance = element.getConstructor().newInstance();
146
            else
147
                newInstance = element.getConstructor(DBRoot.class).newInstance(root);
17 ilm 148
        } catch (InvocationTargetException e) {
149
            if (e.getCause() instanceof DBStructureItemNotFound) {
150
                Log.get().config("ignore inexistent tables: " + e.getCause().getLocalizedMessage());
151
                return;
152
            }
156 ilm 153
            throw new IllegalArgumentException("Constructor failed", e);
17 ilm 154
        } catch (Exception e) {
156 ilm 155
            throw new IllegalArgumentException("Couldn't use constructor", e);
17 ilm 156
        }
156 ilm 157
        this.addSQLElement(newInstance);
17 ilm 158
    }
159
 
160
    /**
161
     * Adds an already instantiated element.
162
     *
163
     * @param elem the SQLElement to add.
80 ilm 164
     * @return the previously added element.
17 ilm 165
     */
80 ilm 166
    public synchronized final SQLElement addSQLElement(SQLElement elem) {
182 ilm 167
        if (elem.getTable().getDBSystemRoot() != this.systemRoot) {
168
            System.err.println("SQLElementDirectory.addSQLElement() warning : Not in this system root : " + elem + " " + elem.getTable().getDBSystemRoot() + "  != " + this.systemRoot);
169
        }
80 ilm 170
        final SQLElement res = this.removeSQLElement(elem.getTable());
17 ilm 171
        this.elements.put(elem.getTable(), elem);
83 ilm 172
        this.tableNames.add(elem.getTable().getName(), elem.getTable());
173
        this.byCode.add(elem.getCode(), elem.getTable());
174
        this.byClass.add(elem.getClass(), elem.getTable());
132 ilm 175
 
176
        final CollectionMap2<String, ? extends List<String>, String> sa = elem.getShowAs();
177
        if (sa != null) {
178
            for (final Entry<String, ? extends List<String>> e : sa.entrySet()) {
179
                try {
180
                    if (e.getKey() == null)
181
                        this.showAs.show(elem.getTable(), e.getValue());
182
                    else
183
                        this.showAs.show(elem.getTable().getField(e.getKey()), e.getValue());
184
                } catch (RuntimeException exn) {
185
                    throw new IllegalStateException("Couldn't add showAs for " + elem + " : " + e, exn);
186
                }
187
            }
188
        }
189
 
19 ilm 190
        for (final DirectoryListener dl : this.listeners) {
191
            dl.elementAdded(elem);
192
        }
73 ilm 193
        elem.setDirectory(this);
80 ilm 194
        return res;
17 ilm 195
    }
196
 
197
    public synchronized final boolean contains(SQLTable t) {
198
        return this.elements.containsKey(t);
199
    }
200
 
201
    public synchronized final SQLElement getElement(SQLTable t) {
202
        return this.elements.get(t);
203
    }
204
 
205
    /**
206
     * Search for a table whose name is <code>tableName</code>.
207
     *
208
     * @param tableName a table name, e.g. "ADRESSE".
209
     * @return the corresponding SQLElement, or <code>null</code> if there is no table named
210
     *         <code>tableName</code>.
211
     * @throws IllegalArgumentException if more than one table match.
212
     */
19 ilm 213
    public synchronized final SQLElement getElement(String tableName) {
17 ilm 214
        return this.getElement(getSoleTable(this.tableNames, tableName));
215
    }
216
 
217
    /**
180 ilm 218
     * Search for an SQLElement which is an {@link Class#isInstance(Object) instance of}
219
     * <code>clazz</code>.
17 ilm 220
     *
221
     * @param <S> type of SQLElement
222
     * @param clazz the class.
223
     * @return the corresponding SQLElement, or <code>null</code> if none can be found.
224
     * @throws IllegalArgumentException if there's more than one match.
180 ilm 225
     * @see #getElementsOfClass(Class, boolean)
17 ilm 226
     */
174 ilm 227
    public final <S extends SQLElement> S getElement(Class<S> clazz) {
180 ilm 228
        return this.getElementOfClass(clazz, true);
17 ilm 229
    }
230
 
174 ilm 231
    public final <S extends SQLElement> S getElementOfClass(Class<S> clazz, final boolean allowSubclass) {
232
        final List<S> res = getElementsOfClass(clazz, allowSubclass);
233
        if (res.size() > 1)
234
            throw new IllegalArgumentException(clazz + " is not unique: " + res);
235
        return res.isEmpty() ? null : res.get(0);
236
    }
237
 
238
    /**
239
     * Search for SQLElement whose class is <code>clazz</code>.
240
     *
241
     * @param clazz the class.
242
     * @param allowSubclass <code>true</code> to include {@link Class#isInstance(Object)
243
     *        subclasses}, <code>false</code> to only return exact match.
244
     * @return the corresponding elements.
245
     */
246
    public final <S extends SQLElement> List<S> getElementsOfClass(final Class<S> clazz, final boolean allowSubclass) {
247
        final List<S> res;
248
        synchronized (this) {
249
            if (allowSubclass) {
250
                res = new ArrayList<>();
251
                for (final SQLElement elem : this.elements.values()) {
252
                    if (clazz.isInstance(elem))
253
                        res.add(clazz.cast(elem));
254
                }
255
            } else {
256
                final Set<SQLTable> tables = this.byClass.get(clazz);
257
                if (tables != null) {
258
                    res = new ArrayList<>(tables.size());
259
                    for (final SQLTable t : tables) {
260
                        res.add(clazz.cast(this.getElement(t)));
261
                    }
262
                } else {
263
                    res = Collections.emptyList();
264
                }
265
            }
266
        }
267
        if (res.isEmpty()) {
268
            return Collections.emptyList();
269
        } else if (res.size() == 1) {
270
            return Collections.singletonList(res.get(0));
271
        } else {
272
            return Collections.unmodifiableList(res);
273
        }
274
    }
275
 
27 ilm 276
    public synchronized final SQLElement getElementForCode(String code) {
277
        return this.getElement(getSoleTable(this.byCode, code));
278
    }
279
 
17 ilm 280
    public synchronized final Set<SQLTable> getTables() {
19 ilm 281
        return this.getElementsMap().keySet();
17 ilm 282
    }
283
 
284
    public synchronized final Collection<SQLElement> getElements() {
19 ilm 285
        return this.getElementsMap().values();
17 ilm 286
    }
19 ilm 287
 
288
    public final Map<SQLTable, SQLElement> getElementsMap() {
289
        return Collections.unmodifiableMap(this.elements);
290
    }
291
 
292
    /**
293
     * Remove the passed instance. NOTE: this method only remove the specific instance passed, so
294
     * it's a conditional <code>removeSQLElement(elem.getTable())</code>.
295
     *
296
     * @param elem the instance to remove.
297
     * @see #removeSQLElement(SQLTable)
298
     */
299
    public synchronized void removeSQLElement(SQLElement elem) {
300
        if (this.getElement(elem.getTable()) == elem)
301
            this.removeSQLElement(elem.getTable());
302
    }
303
 
304
    /**
305
     * Remove the element for the passed table.
306
     *
307
     * @param t the table to remove.
308
     * @return the removed element, can be <code>null</code>.
309
     */
310
    public synchronized SQLElement removeSQLElement(SQLTable t) {
311
        final SQLElement elem = this.elements.remove(t);
312
        if (elem != null) {
144 ilm 313
            this.tableNames.removeOne(elem.getTable().getName(), elem.getTable());
314
            this.byCode.removeOne(elem.getCode(), elem.getTable());
315
            this.byClass.removeOne(elem.getClass(), elem.getTable());
19 ilm 316
            // MAYBE only reset neighbours.
317
            for (final SQLElement otherElem : this.elements.values())
318
                otherElem.resetRelationships();
80 ilm 319
            elem.setDirectory(null);
132 ilm 320
            this.showAs.removeTable(elem.getTable());
19 ilm 321
            for (final DirectoryListener dl : this.listeners) {
322
                dl.elementRemoved(elem);
323
            }
324
        }
325
        return elem;
326
    }
327
 
73 ilm 328
    public synchronized final void initL18nPackageName(final String baseName) {
329
        if (this.phrasesPkgName != null)
330
            throw new IllegalStateException("Already initialized : " + this.getL18nPackageName());
331
        this.phrasesPkgName = baseName;
332
    }
333
 
334
    public synchronized final String getL18nPackageName() {
335
        return this.phrasesPkgName;
336
    }
337
 
19 ilm 338
    public synchronized final void addListener(DirectoryListener dl) {
339
        this.listeners.add(dl);
340
    }
341
 
342
    public synchronized final void removeListener(DirectoryListener dl) {
343
        this.listeners.remove(dl);
344
    }
345
 
346
    static public interface DirectoryListener {
347
        void elementAdded(SQLElement elem);
348
 
349
        void elementRemoved(SQLElement elem);
350
    }
17 ilm 351
}