package edu.ucsd.arcum.interpreter.satisfier; import static com.google.common.collect.Lists.transform; import static com.google.common.collect.Maps.newHashMapWithExpectedSize; import static edu.ucsd.arcum.ArcumPlugin.DEBUG; import java.util.*; import java.util.Map.Entry; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import edu.ucsd.arcum.exceptions.ArcumError; import edu.ucsd.arcum.interpreter.ast.FormalParameter; import edu.ucsd.arcum.interpreter.ast.TraitSignature; import edu.ucsd.arcum.interpreter.fragments.Union; import edu.ucsd.arcum.interpreter.fragments.VariableNode; import edu.ucsd.arcum.interpreter.query.*; import edu.ucsd.arcum.util.Pure; import edu.ucsd.arcum.util.StringUtil; public class BindingMap implements Comparable<BindingMap> { private static final String SPECIAL_RESULT_LABEL = "<RESULT>"; // EXAMPLE: Issue: we can't label the Object as an Entity in the generic arg // could type inference come to the aid? Or labeling type args? private final @Union("Entity") SortedMap<String, Object> bindings; private final SortedMap<String, EntityType> types; public static BindingMap newEmptyMap() { return new BindingMap(); } private BindingMap() { this.bindings = new TreeMap<String, Object>(); this.types = new TreeMap<String, EntityType>(); } public BindingMap(Map<String, Object> values, Map<String, EntityType> types) { this(); for (Entry<String, Object> entry : values.entrySet()) { String var = entry.getKey(); Object entity = entry.getValue(); bind(var, entity, types.get(var)); } } public BindingMap(Object specialResult) { this(); bind(SPECIAL_RESULT_LABEL, specialResult, EntityType.ANY); } public void bind(String id, @Union("Entity") Object entity, EntityType type) { if (!id.equals(VariableNode.DONT_CARE)) { bindings.put(id, entity); types.put(id, type); } } public void bindResultAs(String name) { @Union("Entity") Object entity = bindings.get(SPECIAL_RESULT_LABEL); // bindings.remove(SPECIAL_RESULT_LABEL); bindings.put(name, entity); } public Object getResult() { return bindings.get(SPECIAL_RESULT_LABEL); } // MACNEIL: Should some calls to this actually be calls to consistentMerge? public void addBindings(BindingMap theta) { for (Entry<String, Object> entry : theta.bindings.entrySet()) { String newKey = entry.getKey(); Object newValue = entry.getValue(); if (isTempName(newKey)) { continue; } if (this.bindings.containsKey(newKey)) { ArcumError.fatalError("Variable \"%s\" is multiple-bound", newKey); } bindings.put(newKey, newValue); } } private boolean isTempName(String key) { return key.equals(SPECIAL_RESULT_LABEL); } @Pure public BindingMap consistentMerge(BindingMap... maps) { BindingMap result = this; for (BindingMap map : maps) { result = result.consistentMerge(map); } return result; } // Returns null if a consistent merge cannot be accomplished (i.e., they share // variables that do not have equal values). When the values of a specific // variable in the bindings have the same name and the same value the value of // the result will be what was found in "that". @Pure public BindingMap consistentMerge(BindingMap that) { BindingMap result = new BindingMap(); for (Entry<String, Object> thisEntry : this.bindings.entrySet()) { String thisID = thisEntry.getKey(); @Union("Entity") Object thisEntity = thisEntry.getValue(); if (!isTempName(thisID) && that.bindings.containsKey(thisID)) { @Union("Entity") Object thatEntity = that.bindings.get(thisID); if (Entity.compareTo(thisEntity, thatEntity) == 0) { // Even though thatEntity is equivalent to thisEntity we keep // the newer one from that result.bind(thisID, thatEntity, that.types.get(thisID)); } else { // the two bindings can never be consistent return null; } } else { result.bind(thisID, thisEntity, this.types.get(thisID)); } } for (Entry<String, Object> thatEntry : that.bindings.entrySet()) { String thatID = thatEntry.getKey(); if (isTempName(thatID)) continue; @Union("Entity") Object thatEntity = thatEntry.getValue(); if (!result.bindings.containsKey(thatID)) { // add it only if we haven't already result.bind(thatID, thatEntity, that.types.get(thatID)); } } return result; } // Returns a copy of the binding map, but with all bindings associated with // the given variables removed @Pure public BindingMap withVarsRemoved(Set<String> varNames) { BindingMap result = BindingMap.newEmptyMap(); for (Entry<String, Object> entry : this.bindings.entrySet()) { String id = entry.getKey(); Object entity = entry.getValue(); if (!varNames.contains(id)) { result.bind(id, entity, this.types.get(id)); } } return result; } public boolean isEmpty() { return bindings.isEmpty(); } @Override public String toString() { StringBuilder buff = new StringBuilder(); buff.append("("); Iterator<Entry<String, Object>> i = bindings.entrySet().iterator(); while (i.hasNext()) { Entry<String, Object> entry = i.next(); String id = entry.getKey(); @Union("Entity") Object entity = entry.getValue(); buff.append(id); buff.append(String.format(" [%x]: ", System.identityHashCode(entity))); buff.append(Entity.valueAsString(entity)); if (i.hasNext()) { buff.append(String.format(",%n ")); } } buff.append("): "); buff.append(this.bindings.size()); return buff.toString(); } @Override public int compareTo(BindingMap that) { // TUESDAY: (!!!) This maybe should not be used at all. Trace where this is // called. Maybe only trait values should be comparable like this. As a result, // we could have binding maps share their representations by being a linked // structure: The links won't be too deep because it's bound by the number // of variables used in the expression, with globals serving as the base // binding map. It'd be nice for BindingMap instances to be immutable as well. if (this == that) { return 0; } else { int thisSize = this.bindings.size(); int thatSize = that.bindings.size(); if (thisSize != thatSize) { return thisSize - thatSize; } Iterator<String> i = this.bindings.keySet().iterator(); Iterator<String> j = that.bindings.keySet().iterator(); while (i.hasNext()) { String thisID = i.next(); String thatID = j.next(); if (!thisID.equals(thatID)) { return thisID.compareTo(thatID); } else { @Union("Entity") Object thisEntity = this.bindings.get(thisID); @Union("Entity") Object thatEntity = that.bindings.get(thisID); int k = Entity.compareToWithLocations(thisEntity, thatEntity); if (k != 0) { return k; } } } return 0; } } public List<EntityTuple> asEntityTuple(List<TraitSignature> types) { List<EntityTuple> result = new ArrayList<EntityTuple>(); for (TraitSignature type : types) { // if (nodeValue == null) { // // XXX (!!!) -- Could also apply this check to all entities in // // this.bindings and update them to a different Entity type (though // // that may get weird under generics, so we could punt on all uses // // of parameterized types in a non-raw form) // ITypeBinding typeBinding = Entity.getTypeBindingValue(root); // AbstractTypeDeclaration decl = edb.lookupTypeDeclaration(typeBinding); // // nodeValue may still be null // nodeValue = decl; // } List<FormalParameter> formals = type.getFormals(); List<String> vars = transform(type.getFormals(), FormalParameter.getIdentifier); int numMembers = formals.size(); List<ASTNode> entities = Lists.newArrayListWithExpectedSize(numMembers); Map<String, Object> tupleSubValues = newHashMapWithExpectedSize(numMembers); for (String var : vars) { Object entity = bindings.get(var); tupleSubValues.put(var, entity); if (entity instanceof ASTNode) { entities.add((ASTNode)entity); } // TASK !!!! // else if (entity instanceof ITypeBinding) { // AbstractTypeDeclaration atd; // atd = EntityDataBase.findTypeDeclaration((ITypeBinding)entity); // if (atd != null) { // tupleSubValues.put(var, atd); // overwrite previous entry // entities.add(atd); // } // } else { // MACNEIL: Might want to ASTNodeify typebindings, like // in the commented out code above // MACNEIL: Could maybe do something about pseudo-parents here // too } } ASTNode root = findRoot(entities); EntityTuple tuple = new EntityTuple(type, tupleSubValues, root); result.add(tuple); } return result; } // Finds the common parent of all of the astNodes, or returns null if no such // parent exists. NOTE: Similar but not quite the same as findTrees in ASTUtil. private ASTNode findRoot(List<ASTNode> astNodes) { if (astNodes.isEmpty()) { return null; } if (astNodes.size() == 1) { return astNodes.get(0); } Iterator<ASTNode> it = astNodes.iterator(); ASTNode node = it.next(); AST ast = node.getAST(); while (it.hasNext()) { node = it.next(); if (!ast.equals(node.getAST())) { return null; } } List<ASTNode> candidates = Lists.newArrayList(astNodes); it = candidates.iterator(); nextElement: while (it.hasNext()) { ASTNode curElement = it.next(); ASTNode expecting = curElement; ASTNode previous = curElement; while (expecting != null) { previous = expecting; expecting = expecting.getParent(); if (astNodes.contains(expecting)) { // it can't be us, because another node is our parent it.remove(); continue nextElement; } } if (expecting == null && previous instanceof CompilationUnit) { // we made it to the top without finding our parent, so we must // be the root. The check for CompilationUnit avoids desugared nodes // that are implicit (like the "this." that gets inserted) from // acting like the top, just because their parents are null too return curElement; } } if (candidates.size() == 1) { return candidates.get(0); } else { return null; } } public Object lookupEntity(String name) { return bindings.get(name); } public String lookupEntitiesID(Object entity) { entrySearch: for (Entry<String, Object> entry : bindings.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if (key.equals(SPECIAL_RESULT_LABEL)) continue entrySearch; if (value == entity) { return key; } } return null; } public List<TraitValue> extractBuiltInTraits() { List<TraitValue> result = Lists.newArrayList(); for (Entry<String, Object> entry : bindings.entrySet()) { String traitName = entry.getKey(); if (EntityDataBase.isBuiltInTrait(traitName)) { Object value = entry.getValue(); result.add((TraitValue)value); } } return result; } public void boundValueUpdated(Object originalValue, Object newValue) { Set<String> keys = Sets.newHashSet(bindings.keySet()); for (String key : keys) { Object value = bindings.get(key); if (value == originalValue) { if (DEBUG) { System.out.printf("updated %s%n ----to %s%n", StringUtil .debugDisplay(originalValue), StringUtil.debugDisplay(newValue)); System.out.flush(); } bindings.put(key, newValue); } } } public Map<String,EntityType> getTypes() { return types; } }