/*
* #%L
* gitools-core
* %%
* Copyright (C) 2013 Universitat Pompeu Fabra - Biomedical Genomics group
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
package org.gitools.datasources.idmapper;
import org.gitools.api.analysis.IProgressMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class MappingEngine {
private static final Logger logger = LoggerFactory.getLogger(MappingEngine.class);
private static class Edge {
private final MappingNode src;
private final MappingNode dst;
private final Mapper proc;
public Edge(MappingNode src, MappingNode dst, Mapper proc) {
this.src = src;
this.dst = dst;
this.proc = proc;
}
public MappingNode getSrc() {
return src;
}
public MappingNode getDst() {
return dst;
}
public Mapper getMapper() {
return proc;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Edge)) {
return false;
}
Edge other = (Edge) obj;
return src.equals(other.getSrc()) && dst.equals(other.getDst());
}
@Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + (this.src != null ? this.src.hashCode() : 0);
hash = 67 * hash + (this.dst != null ? this.dst.hashCode() : 0);
return hash;
}
@Override
public String toString() {
return src.toString() + " --> " + dst.toString();
}
}
private static class Path {
private final LinkedList<Step> steps = new LinkedList<>();
private final Set<MappingNode> visited = new HashSet<>();
public Path() {
}
public Path(MappingNode node) {
steps.add(new Step(node));
}
private Path(Path path, Step step) {
steps.addAll(path.getSteps());
steps.add(step);
}
public MappingNode getLastNode() {
return steps.getLast().getNode();
}
public void addStep(Step step) {
steps.add(step);
visited.add(step.getNode());
}
public int getLength() {
return steps.size() - 1;
}
public LinkedList<Step> getSteps() {
return steps;
}
private boolean visited(MappingNode node) {
return visited.contains(node);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Iterator<Step> it = steps.iterator();
if (it.hasNext()) {
Step step = it.next();
sb.append(step.getNode());
while (it.hasNext()) {
step = it.next();
sb.append(" --[").append(step.getMapper());
sb.append("]--> ").append(step.getNode());
}
}
return sb.toString();
}
}
private static class Step {
private final MappingNode node;
private final Mapper mapper;
public Step(MappingNode node) {
this(node, null);
}
public Step(MappingNode node, Mapper mapper) {
this.node = node;
this.mapper = mapper;
}
public MappingNode getNode() {
return node;
}
public Mapper getMapper() {
return mapper;
}
@Override
public String toString() {
return node.getId() + " {" + mapper.getName() + "}";
}
}
private final MappingContext context;
private final List<Edge> edges;
public MappingEngine() {
this.context = new MappingContext();
this.edges = new ArrayList<>();
}
public void addMapper(String src, String dst, Mapper proc) {
addMapper(new StringMappingNode(src), new StringMappingNode(dst), proc);
}
void addMapper(MappingNode src, MappingNode dst, Mapper proc) {
edges.add(new Edge(src, dst, proc));
}
public MappingData run(String[] ids, String src, String dst, IProgressMonitor monitor) throws MappingException {
monitor.begin("Mapping from " + src + " to " + dst + " ...", 4);
MappingData data = new MappingData(src, src);
if (ids != null) {
data.identity(new HashSet<>(Arrays.asList(ids)));
}
monitor.info("Searching mapping path ...");
Path path = findPath(src, dst, monitor);
if (path == null) {
throw new MappingException("Unable to find a mapping path from " + src + " to " + dst);
}
monitor.debug("Mapping path: " + path);
monitor.worked(1);
LinkedList<Step> steps = path.getSteps();
Iterator<Step> it = steps.iterator();
Set<Mapper> initializedMappers = new HashSet<>();
it.next();
while (it.hasNext()) {
Step step = it.next();
Mapper mapper = step.getMapper();
if (!initializedMappers.contains(mapper)) {
mapper.initialize(context, monitor);
initializedMappers.add(mapper);
}
}
monitor.worked(1);
it = steps.iterator();
MappingNode lastNode = it.next().getNode();
while (it.hasNext()) {
Step step = it.next();
IProgressMonitor m = monitor.subtask();
m.begin("Mapping from " + lastNode + " to " + step.getNode() + " ...", 1);
data = step.getMapper().map(context, data, lastNode, step.getNode(), m.subtask());
lastNode = step.getNode();
data.setDstNode(lastNode);
data.removeEmptyKeys();
}
monitor.worked(1);
for (Mapper m : initializedMappers)
m.finalize(context, monitor);
return data;
}
public MappingData run(String src, String dst, IProgressMonitor monitor) throws MappingException {
return run(null, src, dst, monitor);
}
private Path findPath(String src, String dst, IProgressMonitor monitor) {
StringMappingNode dstNode = new StringMappingNode(dst);
Path bestPath = null;
int bestLength = Integer.MAX_VALUE;
LinkedList<Path> paths = new LinkedList<>();
paths.offer(new Path(new StringMappingNode(src)));
while (paths.size() > 0) {
Path path = paths.poll();
if (path.getLength() >= bestLength) {
continue;
}
MappingNode snode = path.getLastNode();
boolean generatorRequired = path.getLength() == 0;
List<Step> steps = getSteps(snode, generatorRequired);
for (Step step : steps) {
if (step.getNode().equals(dstNode)) {
if (bestPath == null) {
bestPath = new Path(path, step);
bestLength = bestPath.getLength();
break;
}
} else if (!path.visited(step.getNode())) {
paths.add(new Path(path, step));
}
}
}
return bestPath;
}
private List<Step> getSteps(MappingNode snode, boolean generatorRequired) {
List<Step> steps = new ArrayList<>();
for (Edge edge : edges) {
MappingNode src = edge.getSrc();
MappingNode dst = edge.getDst();
Mapper mapper = edge.getMapper();
if (generatorRequired && !mapper.isGenerator()) {
continue;
}
if (src.equals(snode)) {
steps.add(new Step(dst, mapper));
} else if (mapper.isBidirectional() && dst.equals(snode)) {
steps.add(new Step(src, mapper));
}
}
return steps;
}
}