code: /**
   * <copyright>
   * Copyright (c) 2010-2014 Henshin developers. All rights reserved. 
   * This program and the accompanying materials are made available 
   * under the terms of the Eclipse Public License v1.0 which 
   * accompanies this distribution, and is available at
   * http://www.eclipse.org/legal/epl-v10.html
   * </copyright>
   */
  package org.eclipse.emf.henshin.interpreter.info;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  
  import org.eclipse.emf.ecore.EClass;
  import org.eclipse.emf.ecore.EReference;
  import org.eclipse.emf.henshin.interpreter.impl.EngineImpl;
  import org.eclipse.emf.henshin.interpreter.matching.constraints.AttributeConstraint;
  import org.eclipse.emf.henshin.interpreter.matching.constraints.BinaryConstraint;
  import org.eclipse.emf.henshin.interpreter.matching.constraints.ContainmentConstraint;
  import org.eclipse.emf.henshin.interpreter.matching.constraints.DanglingConstraint;
  import org.eclipse.emf.henshin.interpreter.matching.constraints.PathConstraint;
  import org.eclipse.emf.henshin.interpreter.matching.constraints.ReferenceConstraint;
  import org.eclipse.emf.henshin.interpreter.matching.constraints.UnaryConstraint;
  import org.eclipse.emf.henshin.interpreter.matching.constraints.Variable;
  import org.eclipse.emf.henshin.model.Attribute;
  import org.eclipse.emf.henshin.model.BinaryFormula;
  import org.eclipse.emf.henshin.model.Edge;
  import org.eclipse.emf.henshin.model.Formula;
  import org.eclipse.emf.henshin.model.Graph;
  import org.eclipse.emf.henshin.model.Mapping;
  import org.eclipse.emf.henshin.model.NestedCondition;
  import org.eclipse.emf.henshin.model.Node;
  import org.eclipse.emf.henshin.model.Rule;
  import org.eclipse.emf.henshin.model.UnaryFormula;
  import org.eclipse.emf.henshin.model.staticanalysis.PathFinder;
  
  public class VariableInfo {
  
  	// variables which represent nodes when they are first introduced, e.g. if a
  	// mapped node occurs in the LHS and in one condition, only the variable
  	// representing the LHS node will be in this collection
  	private Collection<Variable> mainVariables;
  
  	// node-variable pair
  	private Map<Node, Variable> node2variable;
  
  	// variable-node pair
  	private Map<Variable, Node> variable2node;
  
  	// map between a graph and all variables corresponding to nodes of that
  	// graph
  	private Map<Graph, List<Variable>> graph2variables;
  
  	// map between a key variable and its main variable, e.g. there is a mapping
  	// chain from the node belonging to the main variable to the key variable
  	private Map<Variable, Variable> variable2mainVariable;
  
  	private Rule rule;
  	private EngineImpl engine;
  
  	public VariableInfo(RuleInfo ruleInfo, EngineImpl engine) {
  		this.rule = ruleInfo.getRule();
  		this.engine = engine;
  
  		this.node2variable = new HashMap<Node, Variable>();
  		this.variable2node = new HashMap<Variable, Node>();
  
  		this.graph2variables = new HashMap<Graph, List<Variable>>();
  		this.variable2mainVariable = new HashMap<Variable, Variable>();
  
  		createVariables(rule.getLhs(), null);
  
  		for (Node node : rule.getLhs().getNodes()) {
  			if (rule.getMappings().getImage(node, rule.getRhs()) == null) {
  				boolean postpone = ruleInfo.getPostponed().contains(node);
  				createDanglingConstraints(node, postpone);
  			}
  		}
  
  		mainVariables = variable2mainVariable.values();
  	}
  
  	private void createVariables(Graph g, Collection<Mapping> mappings) {
  		List<Variable> variables = new ArrayList<Variable>();
  
  		for (Node node : g.getNodes()) {
  			EClass type = node.getType();
  			Variable var = new Variable(type);
  			var.name = node.getName();
  			variables.add(var);
  			node2variable.put(node, var);
  			variable2node.put(var, node);
  
  			Variable mainVariable = var;
  			if (mappings != null) {
  				for (Mapping mapping : mappings) {
  					if (node == mapping.getImage()) {
  						mainVariable = variable2mainVariable.get(node2variable.get(mapping.getOrigin()));
  					}
  				}
  			}
  
  			variable2mainVariable.put(var, mainVariable);
  		}
  
  		for (Node node : g.getNodes()) {
  			createConstraints(node);
  		}
  
  		graph2variables.put(g, variables);
  
  		createVariables(g.getFormula());
  	}
  
  	private void createVariables(Formula formula) {
  		if (formula instanceof BinaryFormula) {
  			createVariables(((BinaryFormula) formula).getLeft());
  			createVariables(((BinaryFormula) formula).getRight());
  		} else if (formula instanceof UnaryFormula) {
  			createVariables(((UnaryFormula) formula).getChild());
  		} else if (formula instanceof NestedCondition) {
  			NestedCondition nc = (NestedCondition) formula;
  			createVariables(nc.getConclusion(), nc.getMappings());
  		}
  	}
  
  	private void createConstraints(Node node) {
  		Variable var = node2variable.get(node);
  
  		// User-defined node constraints:
  		UnaryConstraint userConstraint = engine.createUserConstraints(node);
  		if (userConstraint != null) {
  			var.userConstraints.add(userConstraint);
  		}
  
  		// Outgoing edges:
  		for (Edge edge : node.getOutgoing()) {
  			Variable target = node2variable.get(edge.getTarget());
  			ReferenceConstraint constraint;
  			String index = edge.getIndex();
  			if (index != null && index.length() > 0) {
  				if (rule.getParameter(index) != null) {
  					constraint = new ReferenceConstraint(target, edge.getType(), index, false);
  					var.requiresFinalCheck = true;
  				} else {
  					try {
  						Number constant = (Number) engine.getScriptEngine().eval(index);
  						constraint = new ReferenceConstraint(target, edge.getType(), constant, true);
  					} catch (Exception e) {
  						throw new RuntimeException("Error evaluating index expression: " + index);
  					}
  				}
  			} else {
  				constraint = new ReferenceConstraint(target, edge.getType(), null, true);
  			}
  			var.referenceConstraints.add(constraint);
  			BinaryConstraint binaryUserConstraint = engine.createUserConstraints(edge);
  			if (binaryUserConstraint != null) {
  				var.binaryUserConstraints.put(constraint, binaryUserConstraint);
  			}
  
  		}
  
  		// Incoming edges:
  		for (Edge edge : node.getIncoming()) {
  			if (edge.getType().isContainment()) {
  				Variable target = node2variable.get(edge.getSource());
  				ContainmentConstraint constraint = new ContainmentConstraint(target);
  				var.containmentConstraints.add(constraint);
  			} else if (edge.getType().getEOpposite() != null) {
  				Variable target = node2variable.get(edge.getSource());
  				ReferenceConstraint constraint = new ReferenceConstraint(target, edge.getType().getEOpposite(), null,
  						true);
  				var.referenceConstraints.add(constraint);
  			}
  		}
  
  		// Attributes:
  		createAttributeConstraints(node, var);
  
  		// Path constraints:
  		if (node.getGraph() == rule.getLhs() && !rule.getLhs().getPACs().isEmpty()) {
  			for (Node target : node.getGraph().getNodes()) {
  				if (node == target) {
  					continue;
  				}
  				for (Entry<List<EReference>, Integer> entry : PathFinder.findReferencePaths(node, target, true, true)
  						.entrySet()) {
  					Variable targetVar = node2variable.get(target);
  					PathConstraint constraint = new PathConstraint(targetVar, entry.getKey(), entry.getValue());
  					var.pathConstraints.add(constraint);
  				}
  			}
  		}
  
  	}
  
  	private void createAttributeConstraints(Node node, Variable var) {
  		for (Attribute attribute : node.getAttributes()) {
  			String value = attribute.getValue();
  			AttributeConstraint constraint;
  			if (rule.getParameter(value) != null) {
  				constraint = new AttributeConstraint(attribute.getType(), value, false);
  			} else {
  				Object constant = engine.evalAttributeExpression(attribute, rule);
  				constraint = new AttributeConstraint(attribute.getType(), constant, true);
  			}
  			var.attributeConstraints.add(constraint);
  			UnaryConstraint unaryUserConstraint = engine.createUserConstraints(attribute);
  			if (unaryUserConstraint != null) {
  				var.attributeUserConstraints.put(constraint, unaryUserConstraint);
  			}
  		}
  	}
  
  	private void createDanglingConstraints(Node node, boolean postpone) {
  		Variable var = node2variable.get(node);
  		
  		DanglingConstraint constraint = new DanglingConstraint(getEdgeCounts(node, false), getEdgeCounts(node, true), postpone);
  		
  		var.danglingConstraints.add(constraint);
  	}
  
  	public static Map<EReference, Integer> getEdgeCounts(Node node, boolean incoming) {
  
  		Collection<Edge> edges = incoming ? node.getIncoming() : node.getOutgoing();
  		Collection<Edge> oppositeEdges = incoming ? node.getOutgoing() : node.getIncoming();
  
  		if (edges.size() == 0) {
  			return null;
  		}
  
  		Map<EReference, Integer> edgeCount = new HashMap<EReference, Integer>();
  		for (Edge edge : edges) {
  			EReference type = edge.getType();
  			Integer count = edgeCount.get(type);
  			if (count == null) {
  				count = ONE;
  			} else {
  				count = count + 1;
  			}
  			edgeCount.put(type, count);
  		}
  
  		for (Edge edge : oppositeEdges) {
  			if (edge.getType().getEOpposite() == null)
  				continue;
  
  			EReference oppType = edge.getType().getEOpposite();
  
  			if (incoming) {
  				// opposite edges are outgoing from the PoV of the node
  				Node remoteNode = edge.getTarget();
  				if (remoteNode.getOutgoing(oppType, node) == null) {
  					Integer count = edgeCount.get(oppType);
  					if (count == null) {
  						count = ONE;
  					} else {
  						count = count + 1;
  					}
  					edgeCount.put(oppType, count);
  				}
  			} else {
  				// opposite edges are incoming from the PoV of the node
  				Node remoteNode = edge.getSource();
  				if (node.getOutgoing(oppType, remoteNode) == null) {
  					Integer count = edgeCount.get(oppType);
  					if (count == null) {
  						count = ONE;
  					} else {
  						count = count + 1;
  					}
  					edgeCount.put(oppType, count);
  				}
  			}
  		}
  
  		return edgeCount;
  	}
  
  	public Node getVariableForNode(Variable variable) {
  		return variable2node.get(variable);
  	}
  
  	public Collection<Variable> getDependendVariables(Variable mainVariable) {
  		Collection<Variable> dependendVariables = new HashSet<Variable>();
  		for (Variable var : variable2mainVariable.keySet()) {
  			if (variable2mainVariable.get(var) == mainVariable) {
  				dependendVariables.add(var);
  			}
  		}
  
  		return dependendVariables;
  	}
  
  	public Collection<Variable> getMainVariables() {
  		return mainVariables;
  	}
  
  	/**
  	 * @return the graph2variables
  	 */
  	public Map<Graph, List<Variable>> getGraph2variables() {
  		return graph2variables;
  	}
  
  	/**
  	 * @return the node2variable
  	 */
  	public Map<Node, Variable> getNode2variable() {
  		// TODO: change Solution to get rid of this method
  		return node2variable;
  	}
  
  	private static final Integer ONE = new Integer(1);
  
  	public void updateCached() {
  		for (Entry<Variable, Node> entry : variable2node.entrySet()) {
  			entry.getKey().attributeConstraints.clear();
  			createAttributeConstraints(entry.getValue(), entry.getKey());
  		}
  	}
  
  }
  