package org.openlca.io.simapro.csv.input;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import org.openlca.core.database.FlowPropertyDao;
import org.openlca.core.database.IDatabase;
import org.openlca.core.database.UnitGroupDao;
import org.openlca.core.model.FlowProperty;
import org.openlca.core.model.FlowPropertyType;
import org.openlca.core.model.RootEntity;
import org.openlca.core.model.Unit;
import org.openlca.core.model.UnitGroup;
import org.openlca.io.UnitMapping;
import org.openlca.io.UnitMappingEntry;
import org.openlca.simapro.csv.model.refdata.QuantityRow;
import org.openlca.simapro.csv.model.refdata.UnitRow;
import org.openlca.util.KeyGen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Synchronizes the used unit names found in a SimaPro CSV file with the units
* in a database. Normally all units should be already exist in the database.
* Otherwise the corresponding unit group, flow property and, unit entries are
* created.
*/
class UnitSync {
private final Logger log = LoggerFactory.getLogger(getClass());
private final SpRefDataIndex index;
private final IDatabase database;
public UnitSync(SpRefDataIndex index, IDatabase database) {
this.index = index;
this.database = database;
}
public void run(RefData refData) {
log.trace("synchronize units with database");
try {
UnitMapping mapping = UnitMapping.createDefault(database);
List<String> unknownUnits = new ArrayList<>();
for (String usedUnit : index.getUsedUnits()) {
UnitMappingEntry entry = mapping.getEntry(usedUnit);
if (entry == null)
unknownUnits.add(usedUnit);
else
log.trace("{} is a known unit", usedUnit);
}
if (!unknownUnits.isEmpty())
syncUnits(mapping, unknownUnits);
refData.setUnitMapping(mapping);
} catch (Exception e) {
log.error("failed to synchronize units with database", e);
refData.setUnitMapping(new UnitMapping());
}
}
private void syncUnits(UnitMapping mapping, List<String> unknownUnits) {
while (!unknownUnits.isEmpty()) {
String unit = unknownUnits.remove(0);
UnitRow row = index.getUnitRow(unit);
if (row != null
&& mapping.getEntry(row.getReferenceUnit()) != null) {
addUnit(row, mapping);
continue;
}
QuantityRow quantity = getQuantity(unit);
if (quantity == null) {
log.warn("unit {} found but with no quantity; create default "
+ "unit, unit group, and flow property", unit);
createDefaultMapping(unit, mapping);
} else {
log.warn(
"unknown unit {}, import complete SimaPro quantity {}",
unit, quantity);
UnitGroup group = importQuantity(quantity, mapping);
for (Unit u : group.getUnits())
unknownUnits.remove(u.getName());
}
}
}
/** Add a new unit to an existing unit group. */
private void addUnit(UnitRow row, UnitMapping mapping) {
String name = row.getName();
UnitMappingEntry refEntry = mapping.getEntry(row.getReferenceUnit());
double factor = row.getConversionFactor()
* refEntry.unit.getConversionFactor();
Unit unit = new Unit();
unit.setConversionFactor(factor);
unit.setName(name);
unit.setRefId(KeyGen.get(name));
UnitGroup group = refEntry.unitGroup;
group.getUnits().add(unit);
UnitGroupDao groupDao = new UnitGroupDao(database);
group = groupDao.update(group);
log.info("added new unit {} to group {}", unit, group);
FlowPropertyDao propDao = new FlowPropertyDao(database);
FlowProperty property = propDao
.getForId(refEntry.flowProperty.getId());
updateRefs(mapping, group, property);
UnitMappingEntry newEntry = new UnitMappingEntry();
newEntry.factor = factor;
newEntry.flowProperty = property;
newEntry.unit = group.getUnit(name);
newEntry.unitGroup = group;
newEntry.unitName = name;
mapping.put(name, newEntry);
}
private void updateRefs(UnitMapping mapping, UnitGroup group,
FlowProperty property) {
for (String name : mapping.getUnits()) {
UnitMappingEntry entry = mapping.getEntry(name);
if (!entry.isValid())
continue;
if (!Objects.equals(group, entry.unitGroup)
|| !Objects.equals(property, entry.flowProperty))
continue;
Unit u = group.getUnit(entry.unit.getName());
if (u == null) {
log.error("Could not find {} in {}", u, group);
continue;
}
entry.flowProperty = property;
entry.unitGroup = group;
entry.unit = u;
}
}
private UnitGroup importQuantity(QuantityRow quantity,
UnitMapping mapping) {
UnitGroup group = create(UnitGroup.class,
"Units of " + quantity.getName());
addUnits(group, quantity);
group = insertLinkProperty(group, quantity.getName());
for (Unit unit : group.getUnits()) {
UnitMappingEntry entry = new UnitMappingEntry();
entry.flowProperty = group.getDefaultFlowProperty();
entry.unitName = unit.getName();
entry.unit = unit;
entry.factor = unit.getConversionFactor();
entry.unitGroup = group;
mapping.put(unit.getName(), entry);
}
return group;
}
private UnitGroup insertLinkProperty(UnitGroup group, String propertyName) {
UnitGroupDao groupDao = new UnitGroupDao(database);
group = groupDao.insert(group);
FlowProperty property = create(FlowProperty.class, propertyName);
property.setFlowPropertyType(FlowPropertyType.PHYSICAL);
property.setUnitGroup(group);
FlowPropertyDao propertyDao = new FlowPropertyDao(database);
property = propertyDao.insert(property);
group.setDefaultFlowProperty(property);
groupDao.update(group);
return group;
}
private void addUnits(UnitGroup unitGroup, QuantityRow quantity) {
for (UnitRow row : index.getUnitRows()) {
if (!Objects.equals(row.getQuantity(), quantity.getName()))
continue;
Unit unit = create(Unit.class, row.getName());
unit.setConversionFactor(row.getConversionFactor());
unitGroup.getUnits().add(unit);
if (Objects.equals(row.getName(), row.getReferenceUnit()))
unitGroup.setReferenceUnit(unit);
}
}
private void createDefaultMapping(String unitName, UnitMapping mapping) {
Unit unit = create(Unit.class, unitName);
unit.setConversionFactor(1);
UnitGroup group = create(UnitGroup.class, "Unit group for " + unitName);
group.getUnits().add(unit);
group.setReferenceUnit(unit);
group = insertLinkProperty(group, "Property for " + unitName);
UnitMappingEntry e = new UnitMappingEntry();
e.unitGroup = group;
e.unit = group.getReferenceUnit();
e.factor = 1d;
e.flowProperty = group.getDefaultFlowProperty();
e.unitName = unitName;
mapping.put(unitName, e);
}
private <T extends RootEntity> T create(Class<T> clazz, String name) {
try {
T t = clazz.newInstance();
t.setName(name);
t.setRefId(UUID.randomUUID().toString());
return t;
} catch (Exception e) {
log.error("failed to create " + clazz, e);
return null;
}
}
private QuantityRow getQuantity(String unitName) {
UnitRow row = index.getUnitRow(unitName);
if (row == null)
return null;
return index.getQuantity(row.getQuantity());
}
}