PandaLib/pandalib-db/src/main/java/fr/pandacube/lib/db/SQLElementList.java

210 lines
9.3 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package fr.pandacube.lib.db;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* An {@link ArrayList} that provides special operations for the table entries it contains.
* @param <E> the table type.
*/
public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
/**
* Stores all the values modified by {@link #setCommon(SQLField, Object)}.
*/
private final Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
@Override
public synchronized boolean add(E e) {
if (e == null || !e.isStored()) return false;
return super.add(e);
}
/**
* Sets the value of a field for all the entries.
* The changed value is stored in this {@link SQLElementList} itself, and does not modify the content of the entries.
* To apply the modification into the database and into the entries themselves, call {@link #saveCommon()}.
* @param field the field to set.
* @param value the new value for this field.
* @param <T> the Java type of the field.
*/
public synchronized <T> void setCommon(SQLField<E, T> field, T value) {
if (field == null)
throw new IllegalArgumentException("field can't be null");
if (Objects.equals(field.getName(), "id"))
throw new IllegalArgumentException("Can't modify id field in a SQLElementList");
Class<E> elemClass = field.getSQLElementType();
try {
E emptyElement = elemClass.getConstructor().newInstance();
emptyElement.set(field, value, false);
} catch (Exception e) {
throw new IllegalArgumentException("Illegal field or value or can't instantiate an empty instance of "
+ elemClass.getName() + ". (the instance is only created to test validity of field and value)", e);
}
// ici, la valeur est bonne
modifiedValues.put(field, value);
}
/**
* Apply all the changes made with {@link #setCommon(SQLField, Object)} to the entries currently present in this
* list.
* The change is applied in the database and into the entries in this list (except the fields that has already been
* modified in an entry (checked using {@link SQLElement#isModified(SQLField)})).
* The entries of this list that are not stored in database (using {@link SQLElement#isStored()}) are ignored.
* @return the value returned by {@link PreparedStatement#executeUpdate()}.
* @throws DBException if an error occurs when interacting with the database.
*/
public synchronized int saveCommon() throws DBException {
List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return 0;
@SuppressWarnings("unchecked")
Class<E> classEl = (Class<E>)storedEl.get(0).getClass();
int ret = DB.update(classEl,
storedEl.get(0).getIdField().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList())
),
modifiedValues);
applyNewValuesToElements(storedEl);
return ret;
}
@SuppressWarnings("unchecked")
private void applyNewValuesToElements(List<E> storedEl) {
// applique les valeurs dans chaque objet de la liste
for (E el : storedEl) {
for (@SuppressWarnings("rawtypes") SQLField entry : modifiedValues.keySet()) {
if (!el.isModified(entry)) {
el.set(entry, modifiedValues.get(entry), false);
}
}
}
}
private List<E> getStoredEl() {
return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(ArrayList::new));
}
/**
* Removes all the entries of this list from the database.
* This method has the same effect as calling the {@link SQLElement#delete()} method individually on each element,
* but with only one SQL query to delete all the entries.
* <p>
* If you intend to remove the entries from the database just after fetching them, call directly the
* {@link DB#delete(Class, SQLWhere)} method instead.
* @throws DBException if an error occurs when interacting with the database.
*/
public synchronized void deleteFromDB() throws DBException {
List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return;
@SuppressWarnings("unchecked")
Class<E> classEl = (Class<E>)storedEl.get(0).getClass();
DB.delete(classEl,
storedEl.get(0).getIdField().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList()))
);
for (E el : storedEl)
el.markAsNotStored();
}
/**
* Get all the entries targeted by the foreign key of all the entries in this list.
* @param foreignKey a foreign key of this table.
* @param orderBy the {@code ORDER BY} clause of the query.
* @return a list of foreign table entries targeted by the provided foreign key of this table.
* @param <T> the fields Java type.
* @param <P> the target table type.
* @throws DBException if an error occurs when interacting with the database.
*/
public <T, P extends SQLElement<P>> SQLElementList<P> getReferencedEntries(SQLFKField<E, T, P> foreignKey, SQLOrderBy<P> orderBy) throws DBException {
Set<T> values = stream()
.map(v -> v.get(foreignKey))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
return values.isEmpty()
? new SQLElementList<>()
: DB.getAll(foreignKey.getForeignElementClass(), foreignKey.getPrimaryField().in(values), orderBy, null, null);
}
/**
* Get all the entries targeted by the foreign key of all the entries in this list, mapped from the foreign key value.
* @param foreignKey a foreign key of this table.
* @return a map of the foreign key values, mapped to the foreign tables entries.
* @param <T> the fields Java type.
* @param <P> the target table type.
* @throws DBException if an error occurs when interacting with the database.
*/
public <T, P extends SQLElement<P>> Map<T, P> getReferencedEntriesInGroups(SQLFKField<E, T, P> foreignKey) throws DBException {
return getReferencedEntries(foreignKey, null).stream()
.collect(Collectors.toMap(
foreignVal -> foreignVal.get(foreignKey.getPrimaryField()),
Function.identity(),
(a, b) -> b)
);
}
/**
* Gets all the original tables entries which the provided foreign key is targeting the entries of this list, and
* following the provided {@code ORDER BY}, {@code LIMIT} and {@code OFFSET} clauses.
* @param foreignKey a foreign key in the original table.
* @param orderBy the {@code ORDER BY} clause of the query.
* @param limit the {@code LIMIT} clause of the query.
* @param offset the {@code OFFSET} clause of the query.
* @param <T> the type of the foreign key field.
* @param <F> the table class of the foreign key that reference a field of this entry.
* @return the original tables entries which the provided foreign key is targeting the entries of this list.
* @throws DBException if an error occurs when interacting with the database.
*/
public <T, F extends SQLElement<F>> SQLElementList<F> getReferencingForeignEntries(SQLFKField<F, T, E> foreignKey, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws DBException {
Set<T> values = stream()
.map(v -> v.get(foreignKey.getPrimaryField()))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
return values.isEmpty()
? new SQLElementList<>()
: DB.getAll(foreignKey.getSQLElementType(), foreignKey.in(values), orderBy, limit, offset);
}
/**
* Gets all the original tables entries which the provided foreign key is targeting the entries of this list,
* following the provided {@code ORDER BY}, {@code LIMIT} and {@code OFFSET} clauses, and mapped from the foreign
* key value.
* @param foreignKey a foreign key in the original table.
* @param orderBy the {@code ORDER BY} clause of the query.
* @param limit the {@code LIMIT} clause of the query.
* @param offset the {@code OFFSET} clause of the query.
* @param <T> the type of the foreign key field.
* @param <F> the table class of the foreign key that reference a field of this entry.
* @return a map of the foreign key values, mapped to the orignal tables entries.
* @throws DBException if an error occurs when interacting with the database.
*/
public <T, F extends SQLElement<F>> Map<T, SQLElementList<F>> getReferencingForeignEntriesInGroups(SQLFKField<F, T, E> foreignKey, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws DBException {
return getReferencingForeignEntries(foreignKey, orderBy, limit, offset).stream()
.collect(Collectors.groupingBy(
e -> e.get(foreignKey),
Collectors.toCollection(SQLElementList::new)
));
}
}