package urban.shapes;
import static urban.util.Pair.pairOf;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import urban.model.Rule;
import urban.transformers.RuleGraphToRuleTransformer;
import urban.transformers.RuleToRuleGraphTransformer;
import urban.util.Pair;
/**
* Merges compatible Rules or RuleGraphs together.
*
* If parameters cannot be merged then null will be returned.
*/
public class RuleMerger {
/**
* Converts rules to rulegraphs, merges them and converts the result back to a rule.
* Used by test code.
*
* @param r first rule
* @param r2 second rule
* @return merged rule
*/
public static Rule merge(Rule r, Rule r2) {
try {
RuleGraph rg1 = new RuleToRuleGraphTransformer().transform(r);
RuleGraph rg2 = new RuleToRuleGraphTransformer().transform(r2);
return new RuleGraphToRuleTransformer().transform(new RuleMerger(rg1,rg2).merge());
} catch (IllegalArgumentException ex){
return null;
}
}
private final RuleGraph rg1;
private final RuleGraph rg2;
private Map<RuleGraph, RuleGraph> reverse = new HashMap<RuleGraph, RuleGraph>();
private Map<RuleGraph, List<Pair<RuleGraph,Link>>> reverseLinks = new HashMap<RuleGraph, List<Pair<RuleGraph,Link>>>();
/**
* Constructor
* @param rg1 first RuleGraph
* @param rg2 second RuleGraph
*/
public RuleMerger(RuleGraph rg1, RuleGraph rg2) {
this.rg1 = rg1;
this.rg2 = rg2;
}
/**
* Merges the two RuleGraphs provided in the constructor.
*
* @return a merged rulegraph or null if they cannot be merged.
*/
public RuleGraph merge() {
try {
final RuleGraph merge = merge(rg1, rg2);
testForInvalidPinch();
return merge;
} catch (IllegalArgumentException ex){
return null;
}
}
private RuleGraph merge(RuleGraph r1, RuleGraph r2) {
if (r1 == null && r2 == null)
return null;
if (r2 == null){
return r1;
}
if (r1 == null){
return r2;
}
if (r1 == r2){
return r1;
}
RuleGraph graph = new RuleGraph(merge(r1.getRoot(), r2.getRoot()));
if (r1.getRoot() instanceof BondNode && (r1.getChildren().size() == 1 || r2.getChildren().size() == 1))
{
try {
doMerge(r1, r2, graph);
} catch(IllegalArgumentException e){
if (r1.hasSymmetricalBond() || r2.hasSymmetricalBond())
throw e;
graph = new RuleGraph(merge(r1.getRoot(), r2.getRoot()));
final TreeMap<Link, RuleGraph> list = new TreeMap<Link, RuleGraph>(r2.getChildrenMap());
for(Entry<Link, RuleGraph> entry : list.entrySet()){
r2.removeChild(entry.getKey());
}
for(Entry<Link, RuleGraph> entry : list.entrySet()){
r2.addChild(swap(entry.getKey()), entry.getValue());
}
doMerge(r1,r2,graph);
}
} else {
doMerge(r1, r2, graph);
}
return graph;
}
private Link swap(Link key) {
return new Link(key.getSrc().equals("1") ? "2" : "1", key.getDst());
}
private void doMerge(RuleGraph r1, RuleGraph r2, RuleGraph graph) {
List<String> used = new LinkedList<String>();
mergeShared(r1, r2, graph, used);
mergeOnlyInFirst(r1, r2, graph, used);
mergeOnlyInFirst(r2, r1, graph, used);
}
/*
* Simple merges for graphs in r2 that r1 does not contain.
*/
private void mergeOnlyInFirst(RuleGraph r2, RuleGraph r1, RuleGraph graph, List<String> used) {
for(Entry<Link, RuleGraph> e : r2.getChildren()){
if(r1.getChild(e.getKey()) == null){
String site = e.getKey().getSrc();
if (used.contains(site))
throw new IllegalArgumentException("Conflicting sites - rules do not merge");
if (graph.getRoot().contains(site))
throw new IllegalArgumentException("Conflicting sites - rules do not merge");
used.add(site);
RuleGraph rg2 = e.getValue();
RuleGraph pinch2 = reverse.get(rg2);
RuleGraph value = pinch2 == null ? rg2 : pinch2;
maintainPinchRecords(null, pinch2, value);
maintainPinchRecords(r1, graph, e, rg2, value);
recordInfoForPinches(value);// value is not merged with anything so need to add the contents to the pinch data
graph.addChild(e.getKey(), value);
}
}
}
private void recordInfoForPinches(RuleGraph r2) {
if (r2 != null && r2.getChildren() != null){
for(Entry<Link, RuleGraph> e : r2.getChildren()){
RuleGraph rg2 = e.getValue();
RuleGraph pinch2 = reverse.get(rg2);
RuleGraph value = pinch2 == null ? rg2 : pinch2;
maintainPinchRecords(null, pinch2, value);
maintainPinchRecords(r2, r2, e, rg2, value);
recordInfoForPinches(value);
}
}
}
private void testForInvalidPinch() {
ArrayList<RuleGraph> list = new ArrayList<RuleGraph>(reverse.values());
while(!list.isEmpty()){
int c = 1;
RuleGraph tmp = list.get(0);
list.remove(tmp);
while(list.remove(tmp))
c++;
if (c > 2)
throw new IllegalArgumentException("Pinch shape conflicts with non-pinched shape");
}
}
private void mergeShared(RuleGraph r1, RuleGraph r2, RuleGraph graph, List<String> used) {
for(Entry<Link, RuleGraph> e : r1.getChildren()){
RuleGraph rg2 = r2.getChild(e.getKey());
if (rg2 == null)
continue;
String site = e.getKey().getSrc();
used.add(site);
if (graph.getRoot().contains(site))
throw new IllegalArgumentException("Conflicting sites - rules do not merge");
RuleGraph rg1 = e.getValue();
RuleGraph pinch1 = reverse.get(rg1);
RuleGraph pinch2 = reverse.get(rg2);
RuleGraph value = merge(pinch1 == null ? rg1 : pinch1, pinch2 == null ? rg2 : pinch2);
maintainPinchRecords(pinch1, pinch2, value);
maintainPinchRecords(r2, graph, e, rg1, value);
maintainPinchRecords(r1, graph, e, rg2, value);
graph.addChild(e.getKey(), value);
}
}
private void maintainPinchRecords(RuleGraph pinch1, RuleGraph pinch2, RuleGraph value) {
if (pinch1 == null && pinch2 == null)
return;
for(Entry<RuleGraph, RuleGraph> e : new ArrayList<Entry<RuleGraph, RuleGraph>>(reverse.entrySet())){
if (e.getValue() == pinch1 || e.getValue() == pinch2)
reverse.put(e.getKey(), value);
}
}
private void maintainPinchRecords(RuleGraph oldParent, RuleGraph newParent, Entry<Link, RuleGraph> e, RuleGraph oldChild, RuleGraph newChild) {
List<Pair<RuleGraph, Link>> list = reverseLinks.get(oldChild);
if (list != null){
for(Pair<RuleGraph,Link> l : list){
l.fst.addChild(l.snd, newChild);
}
} else {
list = new ArrayList<Pair<RuleGraph,Link>>();
reverseLinks.put(oldChild, list);
}
reverse.put(oldChild, newChild);
list.add(pairOf(newParent,e.getKey()));
}
private static Node merge(Node root, Node root2) {
if (!root.getName().equals(root2.getName()))
throw new IllegalArgumentException();
if (root instanceof BondNode)
return new BondNode();
if (root instanceof SiteNode)
return new SiteNode((SiteNode)root, (SiteNode)root2);
return new Node(root, root2);
}
}