package org.openlca.core.matrix;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.openlca.core.database.IDatabase;
import org.openlca.core.database.NativeSql;
import org.openlca.core.database.NativeSql.BatchInsertHandler;
import org.openlca.core.matrix.cache.MatrixCache;
import org.openlca.core.matrix.product.index.IProductIndexBuilder;
import org.openlca.core.matrix.product.index.ProductIndexBuilder;
import org.openlca.core.matrix.product.index.ProductIndexCutoffBuilder;
import org.openlca.core.model.Flow;
import org.openlca.core.model.Process;
import org.openlca.core.model.ProcessLink;
import org.openlca.core.model.ProcessType;
import org.openlca.core.model.ProductSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import gnu.trove.impl.Constants;
import gnu.trove.set.hash.TLongHashSet;
public class ProductSystemBuilder {
private Logger log = LoggerFactory.getLogger(this.getClass());
private MatrixCache matrixCache;
private IDatabase database;
private boolean preferSystemProcesses;
private Double cutoff;
public ProductSystemBuilder(MatrixCache matrixCache,
boolean preferSystemProcesses) {
this.matrixCache = matrixCache;
this.database = matrixCache.getDatabase();
this.preferSystemProcesses = preferSystemProcesses;
}
public void setCutoff(Double cutoff) {
this.cutoff = cutoff;
}
public ProductSystem autoComplete(ProductSystem system) {
if (system == null || system.getReferenceExchange() == null
|| system.getReferenceProcess() == null)
return system;
Process refProcess = system.getReferenceProcess();
Flow refProduct = system.getReferenceExchange().getFlow();
if (refProduct == null)
return system;
LongPair ref = new LongPair(refProcess.getId(), refProduct.getId());
return autoComplete(system, ref);
}
public ProductSystem autoComplete(ProductSystem system,
LongPair processProduct) {
try (Connection con = database.createConnection()) {
log.trace("auto complete product system {}", system);
run(system, processProduct);
log.trace("reload system");
database.getEntityFactory().getCache().evict(ProductSystem.class);
return database.createDao(ProductSystem.class).getForId(
system.getId());
} catch (Exception e) {
log.error("Failed to auto complete product system " + system, e);
return null;
}
}
private void run(ProductSystem system, LongPair processProduct) {
log.trace("build product index");
IProductIndexBuilder builder = getProductIndexBuilder(system);
builder.setPreferredType(preferSystemProcesses ? ProcessType.LCI_RESULT
: ProcessType.UNIT_PROCESS);
TechIndex index = builder.build(processProduct);
log.trace("create new process links");
addLinksAndProcesses(system, index);
}
private IProductIndexBuilder getProductIndexBuilder(ProductSystem system) {
if (cutoff == null || cutoff == 0)
return new ProductIndexBuilder(matrixCache, system);
return new ProductIndexCutoffBuilder(matrixCache, system, cutoff);
}
private void addLinksAndProcesses(ProductSystem system, TechIndex index) {
List<ProcessLink> links = new ArrayList<>();
TLongHashSet linkIds = new TLongHashSet(Constants.DEFAULT_CAPACITY,
Constants.DEFAULT_LOAD_FACTOR, -1);
TLongHashSet processes = new TLongHashSet(Constants.DEFAULT_CAPACITY,
Constants.DEFAULT_LOAD_FACTOR, -1);
// links and processes from system
for (ProcessLink link : system.getProcessLinks()) {
if (linkIds.add(link.exchangeId)) {
links.add(link);
}
}
processes.addAll(system.getProcesses());
// links and processes from tech-index
for (LongPair exchange : index.getLinkedExchanges()) {
LongPair provider = index.getLinkedProvider(exchange);
if (provider == null)
continue;
processes.add(provider.getFirst());
processes.add(exchange.getFirst());
long exchangeId = exchange.getSecond();
if (linkIds.add(exchangeId)) {
ProcessLink link = new ProcessLink();
link.exchangeId = exchangeId;
link.flowId = provider.getSecond();
link.processId = exchange.getFirst();
link.providerId = provider.getFirst();
links.add(link);
}
}
updateDatabase(system, links, processes);
}
private void updateDatabase(ProductSystem system, List<ProcessLink> links,
TLongHashSet processes) {
try {
log.trace("update product system tables");
cleanTables(system.getId());
insertLinks(system.getId(), links);
insertProcesses(system.getId(), processes);
} catch (Exception e) {
log.error("faile to update database in process builder", e);
}
}
private void cleanTables(long systemId) throws Exception {
log.trace("clean system tables for {}", systemId);
String sql = "delete from tbl_process_links where "
+ "f_product_system = " + systemId;
NativeSql.on(database).runUpdate(sql);
sql = "delete from tbl_product_system_processes where "
+ "f_product_system = " + systemId;
NativeSql.on(database).runUpdate(sql);
}
private void insertLinks(long systemId, List<ProcessLink> links)
throws Exception {
log.trace("insert {} process links", links.size());
String stmt = "insert into tbl_process_links(f_product_system, "
+ "f_provider, f_process, f_flow, f_exchange) "
+ "values (?, ?, ?, ?, ?)";
NativeSql.on(database).batchInsert(stmt, links.size(),
(int i, PreparedStatement ps) -> {
ProcessLink link = links.get(i);
ps.setLong(1, systemId);
ps.setLong(2, link.providerId);
ps.setLong(3, link.processId);
ps.setLong(4, link.flowId);
ps.setLong(5, link.exchangeId);
return true;
});
}
private void insertProcesses(final long systemId, TLongHashSet processes)
throws Exception {
log.trace("insert {} system processes", processes.size());
final long[] processIds = processes.toArray();
String stmt = "insert into tbl_product_system_processes("
+ "f_product_system, f_process) values (?, ?)";
NativeSql.on(database).batchInsert(stmt, processIds.length,
new BatchInsertHandler() {
@Override
public boolean addBatch(int i, PreparedStatement ps)
throws SQLException {
ps.setLong(1, systemId);
ps.setLong(2, processIds[i]);
return true;
}
});
}
}