au.csiro.ontology.snomed.refset.rf2.ModuleDependencyRefset Maven / Gradle / Ivy
The newest version!
/**
* Copyright CSIRO Australian e-Health Research Centre (http://aehrc.com).
* All rights reserved. Use is subject to license terms and conditions.
*/
package au.csiro.ontology.snomed.refset.rf2;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import au.csiro.ontology.input.StructuredLog;
/**
* This class represents a module dependency reference set.
*
* @author Alejandro Metke
*
*/
public class ModuleDependencyRefset extends Refset implements IModuleDependencyRefset {
private final static Logger log = LoggerFactory.getLogger(ModuleDependencyRefset.class);
private final static class M implements Comparable {
final private String module;
final private String time;
M(final String module, final String time) {
assert null != module;
assert null != time;
this.module = module;
this.time = time;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + module.hashCode();
result = prime * result + time.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
M other = (M) obj;
return module.equals(other.module) && time.equals(other.time);
}
@Override
public String toString() {
// return "http://snomed.info/module/" + module + "/time/" + time;
return module + "/" + time;
}
@Override
public int compareTo(M other) {
final int mCmp = module.compareTo(other.module);
if (mCmp == 0) {
return parseTime(time).compareTo(parseTime(other.time));
} else {
return mCmp;
}
}
}
protected final Map> dependencies = new HashMap>();
/**
* Creates a new module dependency reference set.
*
* Requirements:
*
* - All MDRS rows are available (actives and inactives)
*
- Result is a map of every valid "Version" defined by the supplied rows
*
- There is a valid Version for every unique combination of moduleId and sourceEffectiveTime
*
- As per the TIG, sourceEffectiveTime and effectiveTime must be equal in every row
*
- The sourceEffectiveTime is used to identify the Version-specific set of modules
*
- targetEffectiveTime is always less-than or equal to (<=) sourceEffectiveTime
*
*
* Note, while module dependencies are not explicitly required to be transitively consistent by the
* IHTSDO Specifications (as documented in the TIG), it is implicitly required (at least for Core) by the
* requirements that the Core is not "changed" by an Extension.
*
* Strategy for loading:
*
* - Gather all (active and inactive) rows
*
- For each unique (active or inactive) moduleId and sourceEffectiveTime pair:
*
* - The set of associated referencedComponentId module and targetEffectiveTime pairs makes up the required modules
*
* - Compute the completion (transitive closure) of the dependency relationship
*
* @param report
*
* @param members
*/
public ModuleDependencyRefset(Set members, boolean validate) throws ValidationException {
id = "900000000000534007";
final List problems = new ArrayList();
// Index the dependency rows
final Map> index = new HashMap>();
for (ModuleDependencyRow member : members) {
final M version = new M(member.getModuleId(), member.getSourceEffectiveTime());
final M requiredModule = new M(member.getReferencedComponentId(), member.getTargetEffectiveTime());
if (!member.isActive()) {
StructuredLog.InactiveDependency.info(log, version, requiredModule);
continue;
}
if (member.isMalformed()) {
problems.add(StructuredLog.MalformedMDRSEntry.warn(member, log, version, requiredModule));
continue;
}
Set vals = index.get(version);
if (vals == null) {
vals = new HashSet();
index.put(version, vals);
}
vals.add(requiredModule);
}
if (validate && !problems.isEmpty()) {
throw new ValidationException("Malformed Module Dependency Reference Set", problems);
}
if (log.isTraceEnabled()) {
// Use a TreeSet to get the output sorted
for (final M version: new TreeSet(index.keySet())) {
log.trace("MDRS entry for version: " + version);
}
}
// Compute the transitive closure of the required modules.
// Note:
// if the MDRS conforms to the spec., then this would not be required
// furthermore, if the dependencies are self-consistent, then this should do nothing
//
tc(index);
for (final M version : index.keySet()) {
final String srcId = version.module;
final String srcVer = version.time;
ModuleDependency md = createDependency(version, index);
Map verDepMap = dependencies.get(srcId);
if (verDepMap == null) {
verDepMap = new HashMap();
dependencies.put(srcId, verDepMap);
}
verDepMap.put(srcVer, md);
}
}
/**
* Compute the transitive closure of the dependencies
*
* @param index
*/
private static void tc(Map> index) {
final Map warnings = new HashMap<>();
for (Entry> entry: index.entrySet()) {
final M src = entry.getKey();
final Set dependents = entry.getValue();
final Queue queue = new LinkedList(dependents);
while (!queue.isEmpty()) {
final M key = queue.poll();
if (!index.containsKey(key)) {
continue;
}
for (M addition: index.get(key)) {
if (!dependents.contains(addition)) {
dependents.add(addition);
queue.add(addition);
warnings.put(src + "|" + addition + "|" + key, new Object[] {src, addition, key});
}
}
}
}
for (final Object[] args: warnings.values()) {
StructuredLog.ImpliedTransitiveDependency.warn(log, args);
}
}
private ModuleDependency createDependency(M version, Map> index) {
return createDependency(new HashSet<>(), version, index);
}
private ModuleDependency createDependency(Collection all, M version, Map> index) {
final ModuleDependency md = new ModuleDependency(version.module, version.time);
if (all.contains(md)) {
StructuredLog.CyclicDependency.warn(md, log);
return null;
}
all.add(md);
Set deps = index.get(version);
if (deps != null) {
for (M dep : deps) {
final ModuleDependency childMd = createDependency(all, dep, index);
if (null != childMd) {
md.getDependencies().add(childMd);
}
}
}
all.remove(md);
return md;
}
@Override
public Map> getModuleDependencies() {
return dependencies;
}
static Date parseTime(String time) {
final SimpleDateFormat ddf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
try {
return ddf.parse(time);
} catch (ParseException e1) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
try {
return sdf.parse(time);
} catch (ParseException e2) {
final String message = StructuredLog.InvalidEffectiveTime.error(log, time, e1.getMessage(), e2.getMessage());
throw new RuntimeException(message, e2);
}
}
}
}