package org.openlca.core.database.references;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openlca.core.database.IDatabase;
import org.openlca.core.database.ParameterDao;
import org.openlca.core.model.AbstractEntity;
import org.openlca.core.model.CategorizedEntity;
import org.openlca.core.model.Parameter;
import org.openlca.core.model.ParameterScope;
import org.openlca.core.model.descriptors.BaseDescriptor;
import org.openlca.core.model.descriptors.CategorizedDescriptor;
import org.openlca.core.model.descriptors.ParameterDescriptor;
import org.openlca.util.Formula;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
abstract class BaseReferenceSearch<T extends CategorizedDescriptor> implements
IReferenceSearch<T> {
private final static Logger log = LoggerFactory.getLogger(BaseReferenceSearch.class);
protected final IDatabase database;
private final Class<? extends CategorizedEntity> type;
private final boolean includeOptional;
BaseReferenceSearch(IDatabase database,
Class<? extends CategorizedEntity> type) {
this(database, type, false);
}
BaseReferenceSearch(IDatabase database,
Class<? extends CategorizedEntity> type, boolean includeOptional) {
this.database = database;
this.type = type;
this.includeOptional = includeOptional;
}
@Override
public List<Reference> findReferences() {
return findReferences(new HashSet<Long>());
}
@Override
public List<Reference> findReferences(T descriptor) {
if (descriptor == null || descriptor.getId() == 0l)
return Collections.emptyList();
return findReferences(Collections.singletonList(descriptor));
}
@Override
public List<Reference> findReferences(List<T> descriptors) {
if (descriptors == null || descriptors.isEmpty())
return Collections.emptyList();
return findReferences(toIdSet(descriptors));
}
@Override
public List<Reference> findReferences(long id) {
if (id == 0l)
return Collections.emptyList();
return findReferences(Collections.singleton(id));
}
protected List<Reference> findReferences(String table, String idField, Set<Long> ids, Ref[] references) {
return findReferences(table, idField, ids, null, references);
}
protected List<Reference> findReferences(String table, String idField,
Set<Long> ids, Map<Long, Long> idToOwnerId, Ref[] references) {
return Search.on(database, type).findReferences(table, idField, ids, idToOwnerId, references, includeOptional);
}
protected List<Reference> findGlobalParameters(Set<Long> ids,
Map<Long, Set<String>> idToFormulas) {
Map<Long, Set<String>> idToNames = new HashMap<>();
List<String> queries = Search.createQueries(
"SELECT f_owner, lower(name), is_input_param, lower(formula) FROM tbl_parameters",
"WHERE f_owner IN", ids);
for (String paramQuery : queries) {
Search.on(database, type).query(paramQuery, (result) -> {
long ownerId = result.getLong(1);
Set<String> names = idToNames.get(ownerId);
if (names == null)
idToNames.put(ownerId, names = new HashSet<>());
names.add(result.getString(2));
if (!result.getBoolean(3)) {
Set<String> formulas = idToFormulas.get(ownerId);
if (formulas == null)
idToFormulas.put(ownerId, formulas = new HashSet<>());
formulas.add(result.getString(4));
}
});
}
Map<Long, Set<String>> undeclared = findUndeclaredParameters(idToNames,
idToFormulas);
Set<String> names = new HashSet<>();
for (Set<String> n : undeclared.values())
names.addAll(n);
List<ParameterDescriptor> descriptors = new ParameterDao(database)
.getDescriptors(names.toArray(new String[names.size()]), ParameterScope.GLOBAL);
List<Reference> results = toReferences(descriptors, false, undeclared, null);
Set<String> found = new HashSet<>();
for (ParameterDescriptor d : descriptors)
found.add(d.getName().toLowerCase());
for (String name : names)
if (!found.contains(name))
results.addAll(createMissingReferences(name, undeclared));
return results;
}
private List<Reference> createMissingReferences(String name,
Map<Long, Set<String>> ownerToNames) {
List<Reference> missing = new ArrayList<>();
for (long ownerId : ownerToNames.keySet())
if (ownerToNames.get(ownerId).contains(name))
missing.add(new Reference(name, Parameter.class, 0, type, ownerId));
return missing;
}
protected Map<Long, Set<String>> findUndeclaredParameters(
Map<Long, Set<String>> idToNames,
Map<Long, Set<String>> idToFormulas) {
Map<Long, Set<String>> undeclared = new HashMap<>();
for (long id : idToFormulas.keySet()) {
Set<String> formulas = idToFormulas.get(id);
for (String formula : formulas) {
try {
for (String var : Formula.getVariables(formula)) {
Set<String> set = idToNames.get(id);
if (set != null && set.contains(var))
continue;
Set<String> names = undeclared.get(id);
if (names == null)
undeclared.put(id, names = new HashSet<>());
names.add(var);
}
} catch (Throwable e) {
log.warn("Failed parsing formula " + formula + " in model " + id, e);
}
}
}
return undeclared;
}
protected List<Reference> findGlobalParameterRedefs(Set<Long> ids) {
return findGlobalParameterRedefs(ids, null);
}
protected List<Reference> findGlobalParameterRedefs(Set<Long> ids,
Map<Long, Long> idToOwnerId) {
Map<Long, Set<String>> idToNames = new HashMap<>();
List<String> queries = Search.createQueries(
"SELECT f_owner, name FROM tbl_parameter_redefs WHERE (f_context is null OR f_context = 0)",
"AND f_owner IN"
, ids);
for (String query : queries) {
Search.on(database, type).query(query, (result) -> {
long ownerId = result.getLong(1);
if (idToOwnerId != null)
ownerId = idToOwnerId.get(ownerId);
Set<String> names = idToNames.get(ownerId);
if (names == null)
idToNames.put(ownerId, names = new HashSet<>());
names.add(result.getString(2));
});
}
Set<String> names = new HashSet<>();
for (Set<String> n : idToNames.values())
names.addAll(n);
String[] nameArray = names.toArray(new String[names.size()]);
List<ParameterDescriptor> descriptors = new ParameterDao(database)
.getDescriptors(nameArray, ParameterScope.GLOBAL);
return toReferences(descriptors, false, idToNames, "parameterRedefs");
}
protected <F extends AbstractEntity> List<Reference> filter(Class<F> clazz,
List<Reference> references) {
List<Reference> filtered = new ArrayList<>();
for (Reference reference : references)
if (clazz.isAssignableFrom(reference.getType()))
filtered.add(reference);
return filtered;
}
protected Set<Long> toIdSet(List<?> objects) {
Set<Long> ids = new HashSet<>();
for (Object o : objects)
if (o instanceof Reference)
ids.add(((Reference) o).id);
else if (o instanceof BaseDescriptor)
ids.add(((BaseDescriptor) o).getId());
return ids;
}
protected Map<Long, Long> toIdMap(List<Reference> references) {
Map<Long, Long> ids = new HashMap<>();
for (Reference r : references)
ids.put(r.id, r.ownerId);
return ids;
}
protected List<Reference> toReferences(
List<ParameterDescriptor> descriptors, boolean optional,
Map<Long, Set<String>> idToNames, String property) {
Map<Long, Set<Long>> descriptorToOwnerIds = new HashMap<>();
for (ParameterDescriptor d : descriptors) {
for (long ownerId : idToNames.keySet()) {
if (!idToNames.get(ownerId).contains(d.getName()))
continue;
Set<Long> set = descriptorToOwnerIds.get(d.getId());
if (set == null)
descriptorToOwnerIds.put(d.getId(), set = new HashSet<>());
set.add(ownerId);
}
}
List<Reference> references = new ArrayList<>();
for (BaseDescriptor descriptor : descriptors) {
Set<Long> set = descriptorToOwnerIds.get(descriptor.getId());
if (set == null)
continue;
for (long ownerId : set) {
Class<? extends AbstractEntity> type = (Class<? extends AbstractEntity>) descriptor
.getModelType().getModelClass();
long id = descriptor.getId();
Reference reference = new Reference(property, type, id, this.type, ownerId, optional);
references.add(reference);
}
}
return references;
}
}