Dépôt officiel du code source de l'ERP OpenConcerto
Rev 180 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.element;
import org.openconcerto.sql.Log;
import org.openconcerto.sql.ShowAs;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.DBStructureItemNotFound;
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.SQLName;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.utils.CollectionMap2;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.SetMap;
import org.openconcerto.utils.cc.Cookies;
import org.openconcerto.utils.cc.ITransformer;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.jcip.annotations.GuardedBy;
/**
* Directory of SQLElement by table. <code>final</code> so that {@link SQLElement} can't depend on a
* subclass and can be shared between applications (e.g. using
* {@link #putAll(SQLElementDirectory)}). If some elements needs shared state, use
* {@link #getCookies()}.
*
* @author Sylvain CUAZ
*/
public final class SQLElementDirectory {
private final DBSystemRoot systemRoot;
private final Map<SQLTable, SQLElement> elements;
private final SetMap<String, SQLTable> tableNames;
private final SetMap<String, SQLTable> byCode;
private final SetMap<Class<? extends SQLElement>, SQLTable> byClass;
private final List<DirectoryListener> listeners;
private String phrasesPkgName;
@GuardedBy("this")
private SQLFieldTranslator translator;
private final ShowAs showAs;
private final Cookies cookies;
public SQLElementDirectory(DBSystemRoot systemRoot) {
this.systemRoot = systemRoot;
this.elements = new HashMap<SQLTable, SQLElement>();
// to mimic elements behaviour, if we add twice the same table
// the second one should replace the first one
this.tableNames = new SetMap<String, SQLTable>();
this.byCode = new SetMap<String, SQLTable>();
this.byClass = new SetMap<Class<? extends SQLElement>, SQLTable>();
this.listeners = new ArrayList<DirectoryListener>();
this.phrasesPkgName = null;
this.showAs = new ShowAs((DBRoot) null);
this.cookies = new Cookies();
}
public synchronized final void destroy() {
for (final SQLElement elem : this.elements.values()) {
elem.destroy();
}
}
public final ShowAs getShowAs() {
return this.showAs;
}
public final synchronized void setTranslator(SQLFieldTranslator translator) {
if (translator.getDirectory() != this)
throw new IllegalArgumentException("Not for this : " + translator);
this.translator = translator;
}
public synchronized final SQLFieldTranslator getTranslator() {
return this.translator;
}
public final Cookies getCookies() {
return this.cookies;
}
private static <K> SQLTable getSoleTable(SetMap<K, SQLTable> m, K key) throws IllegalArgumentException {
final Collection<SQLTable> res = m.getNonNull(key);
if (res.size() > 1)
throw new IllegalArgumentException(key + " is not unique: " + CollectionUtils.join(res, ",", new ITransformer<SQLTable, SQLName>() {
@Override
public SQLName transformChecked(SQLTable input) {
return input.getSQLName();
}
}));
return CollectionUtils.getSole(res);
}
public synchronized final void putAll(SQLElementDirectory o) {
// Cookies are needed in addSQLElement() (e.g. by directoryChanged() and getShowAs())
this.cookies.putAll(o.getCookies());
// copy since addSQLElement() removes elements from their previous directory
for (final SQLElement elem : new ArrayList<>(o.getElements())) {
if (!this.contains(elem.getTable()))
this.addSQLElement(elem);
}
this.translator.putAll(o.getTranslator());
}
/**
* Add an element by creating it with the no-arg constructor. If the element cannot find its
* table and thus raise DBStructureItemNotFound, the exception is logged.
*
* @param element the element to add.
*/
public final void addSQLElement(final Class<? extends SQLElement> element) {
this.addSQLElement(element, null);
}
public final void addSQLElement(final Class<? extends SQLElement> element, final DBRoot root) {
final SQLElement newInstance;
try {
if (root == null)
newInstance = element.getConstructor().newInstance();
else
newInstance = element.getConstructor(DBRoot.class).newInstance(root);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof DBStructureItemNotFound) {
Log.get().config("ignore inexistent tables: " + e.getCause().getLocalizedMessage());
return;
}
throw new IllegalArgumentException("Constructor failed", e);
} catch (Exception e) {
throw new IllegalArgumentException("Couldn't use constructor", e);
}
this.addSQLElement(newInstance);
}
/**
* Adds an already instantiated element.
*
* @param elem the SQLElement to add.
* @return the previously added element.
*/
public synchronized final SQLElement addSQLElement(SQLElement elem) {
if (elem.getTable().getDBSystemRoot() != this.systemRoot) {
System.err.println("SQLElementDirectory.addSQLElement() warning : Not in this system root : " + elem + " " + elem.getTable().getDBSystemRoot() + " != " + this.systemRoot);
}
final SQLElement res = this.removeSQLElement(elem.getTable());
this.elements.put(elem.getTable(), elem);
this.tableNames.add(elem.getTable().getName(), elem.getTable());
this.byCode.add(elem.getCode(), elem.getTable());
this.byClass.add(elem.getClass(), elem.getTable());
final CollectionMap2<String, ? extends List<String>, String> sa = elem.getShowAs();
if (sa != null) {
for (final Entry<String, ? extends List<String>> e : sa.entrySet()) {
try {
if (e.getKey() == null)
this.showAs.show(elem.getTable(), e.getValue());
else
this.showAs.show(elem.getTable().getField(e.getKey()), e.getValue());
} catch (RuntimeException exn) {
throw new IllegalStateException("Couldn't add showAs for " + elem + " : " + e, exn);
}
}
}
for (final DirectoryListener dl : this.listeners) {
dl.elementAdded(elem);
}
elem.setDirectory(this);
return res;
}
public synchronized final boolean contains(SQLTable t) {
return this.elements.containsKey(t);
}
public synchronized final SQLElement getElement(SQLTable t) {
return this.elements.get(t);
}
/**
* Search for a table whose name is <code>tableName</code>.
*
* @param tableName a table name, e.g. "ADRESSE".
* @return the corresponding SQLElement, or <code>null</code> if there is no table named
* <code>tableName</code>.
* @throws IllegalArgumentException if more than one table match.
*/
public synchronized final SQLElement getElement(String tableName) {
return this.getElement(getSoleTable(this.tableNames, tableName));
}
/**
* Search for an SQLElement which is an {@link Class#isInstance(Object) instance of}
* <code>clazz</code>.
*
* @param <S> type of SQLElement
* @param clazz the class.
* @return the corresponding SQLElement, or <code>null</code> if none can be found.
* @throws IllegalArgumentException if there's more than one match.
* @see #getElementsOfClass(Class, boolean)
*/
public final <S extends SQLElement> S getElement(Class<S> clazz) {
return this.getElementOfClass(clazz, true);
}
public final <S extends SQLElement> S getElementOfClass(Class<S> clazz, final boolean allowSubclass) {
final List<S> res = getElementsOfClass(clazz, allowSubclass);
if (res.size() > 1)
throw new IllegalArgumentException(clazz + " is not unique: " + res);
return res.isEmpty() ? null : res.get(0);
}
/**
* Search for SQLElement whose class is <code>clazz</code>.
*
* @param clazz the class.
* @param allowSubclass <code>true</code> to include {@link Class#isInstance(Object)
* subclasses}, <code>false</code> to only return exact match.
* @return the corresponding elements.
*/
public final <S extends SQLElement> List<S> getElementsOfClass(final Class<S> clazz, final boolean allowSubclass) {
final List<S> res;
synchronized (this) {
if (allowSubclass) {
res = new ArrayList<>();
for (final SQLElement elem : this.elements.values()) {
if (clazz.isInstance(elem))
res.add(clazz.cast(elem));
}
} else {
final Set<SQLTable> tables = this.byClass.get(clazz);
if (tables != null) {
res = new ArrayList<>(tables.size());
for (final SQLTable t : tables) {
res.add(clazz.cast(this.getElement(t)));
}
} else {
res = Collections.emptyList();
}
}
}
if (res.isEmpty()) {
return Collections.emptyList();
} else if (res.size() == 1) {
return Collections.singletonList(res.get(0));
} else {
return Collections.unmodifiableList(res);
}
}
public synchronized final SQLElement getElementForCode(String code) {
return this.getElement(getSoleTable(this.byCode, code));
}
public synchronized final Set<SQLTable> getTables() {
return this.getElementsMap().keySet();
}
public synchronized final Collection<SQLElement> getElements() {
return this.getElementsMap().values();
}
public final Map<SQLTable, SQLElement> getElementsMap() {
return Collections.unmodifiableMap(this.elements);
}
/**
* Remove the passed instance. NOTE: this method only remove the specific instance passed, so
* it's a conditional <code>removeSQLElement(elem.getTable())</code>.
*
* @param elem the instance to remove.
* @see #removeSQLElement(SQLTable)
*/
public synchronized void removeSQLElement(SQLElement elem) {
if (this.getElement(elem.getTable()) == elem)
this.removeSQLElement(elem.getTable());
}
/**
* Remove the element for the passed table.
*
* @param t the table to remove.
* @return the removed element, can be <code>null</code>.
*/
public synchronized SQLElement removeSQLElement(SQLTable t) {
final SQLElement elem = this.elements.remove(t);
if (elem != null) {
this.tableNames.removeOne(elem.getTable().getName(), elem.getTable());
this.byCode.removeOne(elem.getCode(), elem.getTable());
this.byClass.removeOne(elem.getClass(), elem.getTable());
// MAYBE only reset neighbours.
for (final SQLElement otherElem : this.elements.values())
otherElem.resetRelationships();
elem.setDirectory(null);
this.showAs.removeTable(elem.getTable());
for (final DirectoryListener dl : this.listeners) {
dl.elementRemoved(elem);
}
}
return elem;
}
public synchronized final void initL18nPackageName(final String baseName) {
if (this.phrasesPkgName != null)
throw new IllegalStateException("Already initialized : " + this.getL18nPackageName());
this.phrasesPkgName = baseName;
}
public synchronized final String getL18nPackageName() {
return this.phrasesPkgName;
}
public synchronized final void addListener(DirectoryListener dl) {
this.listeners.add(dl);
}
public synchronized final void removeListener(DirectoryListener dl) {
this.listeners.remove(dl);
}
static public interface DirectoryListener {
void elementAdded(SQLElement elem);
void elementRemoved(SQLElement elem);
}
}