package edu.ucsd.arcum.interpreter.query;
import static com.google.common.collect.Lists.newArrayList;
import static edu.ucsd.arcum.ArcumPlugin.DEBUG;
import static edu.ucsd.arcum.interpreter.ast.ASTUtil.PARAMETER_NAME;
import static edu.ucsd.arcum.interpreter.ast.ASTUtil.find;
import static edu.ucsd.arcum.interpreter.ast.RealizationStatement.collectivelyGenerateLocals;
import static edu.ucsd.arcum.interpreter.ast.RealizationStatement.collectivelyRealizeStatements;
import java.io.PrintStream;
import java.util.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.dom.*;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import edu.ucsd.arcum.exceptions.ArcumError;
import edu.ucsd.arcum.exceptions.SourceLocation;
import edu.ucsd.arcum.exceptions.Unreachable;
import edu.ucsd.arcum.interpreter.ast.*;
import edu.ucsd.arcum.interpreter.ast.expressions.ConstraintExpression;
import edu.ucsd.arcum.interpreter.fragments.Union;
import edu.ucsd.arcum.interpreter.parser.FragmentParser;
import edu.ucsd.arcum.interpreter.satisfier.BindingMap;
import edu.ucsd.arcum.interpreter.satisfier.CurrentBindingsLookup;
import edu.ucsd.arcum.interpreter.satisfier.NodesWithLocations;
import edu.ucsd.arcum.interpreter.satisfier.TypeLookupTable;
import edu.ucsd.arcum.interpreter.transformation.ResolvedConceptMapEntry;
import edu.ucsd.arcum.util.SystemUtil;
public class OptionMatchTable implements IEntityLookup
{
private final ArcumDeclarationTable table;
private Option option;
private SourceLocation location;
private final Map<String, TraitValue> singletons = new HashMap<String, TraitValue>();
private final Map<String, TraitValue> traits = new HashMap<String, TraitValue>();
private final Map<String, TraitValue> builtInTraits = new HashMap<String, TraitValue>();
private Set<FormalParameter> resolvedVariables = null;
private boolean needsUpdate = false;
// constructor to use for matchAllEntities -- the location passed in is the
// map entry that causes this entity table to exist in the first place, thus,
// error messages should be associated with it if no other information is
// available (e.g., when a search for a singleton finds no matches)
public OptionMatchTable(ArcumDeclarationTable table, ResolvedConceptMapEntry rcme) {
this.table = table;
this.option = rcme.getOption();
this.location = rcme.getLocation();
TraitValue constructor = constructParameters(table, option, rcme.getArguments());
singletons.put(option.getName(), constructor);
}
// constructor to use for generateSingletonEntities
public OptionMatchTable(ArcumDeclarationTable table, Option originalOption,
Option alternativeOption, OptionMatchTable entities) throws CoreException
{
this.table = table;
this.option = alternativeOption;
OptionInterface optionInterface = option.getOptionInterface();
for (Map.Entry<String, TraitValue> entry : entities.singletons.entrySet()) {
String singletonName = entry.getKey();
TraitValue traitValue = entry.getValue();
if (singletonName.equals(originalOption.getName())) {
singletonName = alternativeOption.getName();
TraitSignature type = constructorType(table, alternativeOption);
TraitValue constructor = new TraitValue(singletonName, type);
EntityTuple singleton = traitValue.getSingleton();
Map<String, Object> values = singleton.getValues();
;
EntityTuple tuple = new EntityTuple(type, values, null);
constructor.addTuple(tuple);
singletons.put(singletonName, constructor);
}
else if (optionInterface.lookupTupleSet(singletonName) != null) {
singletons.put(singletonName, traitValue);
}
}
List<RealizationStatement> stmts = optionInterface.getRealizationStatements();
for (Map.Entry<String, TraitValue> entry : entities.traits.entrySet()) {
String traitName = entry.getKey();
TraitValue traitValue = entry.getValue();
if (traitValue.isStatic()) {
traits.put(traitName, traitValue);
}
}
EntityDataBase entityDataBase = table.getEntityDataBase();
// this.builtInTraits.putAll(entities.builtInTraits);
importBuiltInTraitPredicates(entityDataBase);
matchAllArgumentTraits(entityDataBase);
}
// match all trait arguments, singletons, and non-singleton entities
public void matchAllEntities(EntityDataBase entityDataBase) throws CoreException {
try {
EntityDataBase.pushCurrentDataBase(entityDataBase);
importBuiltInTraitPredicates(entityDataBase);
matchAllArgumentTraits(entityDataBase);
matchAllRealizationStatements(entityDataBase, option.getOptionInterface());
if (true || DEBUG) {
for (PrintStream stream : newArrayList(System.out, SystemUtil
.getOutStream()))
{
stream.println(this.toString());
stream.flush();
}
}
}
finally {
EntityDataBase.popMostRecentDataBase();
}
}
private void importBuiltInTraitPredicates(EntityDataBase entityDataBase) {
entityDataBase.insertBuiltInTraitValues(this);
}
private void matchAllArgumentTraits(EntityDataBase edb) throws CoreException {
TraitValue traitValue = singletons.get(option.getName());
List<EntityTuple> singletonList = traitValue.getEntities();
EntityTuple tuple = singletonList.get(0);
Collection<Object> entities = tuple.getValues().values();
for (@Union("Entity")
Object entity : entities)
{
if (entity instanceof MapTraitArgument) {
MapTraitArgument mta = (MapTraitArgument)entity;
mta.initializeValue(edb, option, this);
}
}
// check parameter requirements first because, if these fail, then any error
// messages that would come later might be misleading
TraitSignature traitType = traitValue.getTraitType();
boolean passed = checkEntityTupleConditions(tuple, traitType, edb, this);
if (!passed) {
ArcumError.stop();
}
}
private void matchAllRealizationStatements(EntityDataBase entityDataBase,
OptionInterface optionInterface) throws CoreException
{
List<RealizationStatement> stmts = Lists.newArrayList();
stmts.addAll(optionInterface.getRealizationStatements());
for (RealizationStatement stmt : stmts) {
if (!stmt.isStatic()) {
ArcumError.fatalUserError(stmt.getPosition(), "Interface level"
+ " realization statements must be declared static");
}
stmt.typeCheckAndValidate(optionInterface);
}
stmts.addAll(option.getRealizationStatements());
collectivelyRealizeStatements(stmts, entityDataBase, this);
}
// TASK -- generate all locals, not just singletons
// generate only local entities
public NodesWithLocations generateLocalEntities() throws CoreException {
List<RealizationStatement> singletonStmts;
singletonStmts = option.getRealizationsOfLocals();
NodesWithLocations result = collectivelyGenerateLocals(singletonStmts, this,
option, table.getEntityDataBase());
List<EntityTuple> nodes = result.getNodes();
for (EntityTuple node : nodes) {
TraitSignature type = node.getType();
String name = type.getName();
if (!traits.containsKey(name)) {
addTrait(type, false, type.isStaticDefinition());
}
addTraitInstance(name, node);
}
return result;
}
public EntityTuple generateEntityReplacement(EntityTuple entity) {
TraitSignature type = entity.getType();
String name = type.getName();
ASTNode rootNode = entity.getRootNode();
AST ast = rootNode.getAST();
RealizationStatement realizationStmt = option.getStatementForReplacement(name);
return realizationStmt.generateEntityReplacement(name, entity, this, ast);
}
public void addSingleton(EntityTuple singleton) {
String name = singleton.getType().getName();
TraitValue set = singletons.get(name);
if (set != null) {
EntityTuple alreadyThere = set.getSingleton();
System.err.printf("Internal error: Adding %s, which already has"
+ " entry %s (parent=%s)%n", name, alreadyThere, alreadyThere
.getRootNode().getParent());
}
set = new TraitValue(name, singleton.getType());
set.addTuple(singleton);
singletons.put(name, set);
this.needsUpdate = true;
}
public void addTrait(TraitSignature type, boolean isNested, boolean isStatic) {
String name = type.getName();
TraitValue set = new TraitValue(name, type, isStatic, isNested);
traits.put(name, set);
}
public void addBuiltInTrait(TraitSignature type) {
String name = type.getName();
TraitValue set = new TraitValue(name, type, false, true);
builtInTraits.put(name, set);
}
// Returns true if this set did not already contain the specified element
public boolean addTraitInstance(String name, EntityTuple tuple) {
TraitValue set = traits.get(name);
if (set == null) {
set = builtInTraits.get(name);
}
return set.addTuple(tuple);
}
private static TraitValue constructParameters(ArcumDeclarationTable table,
Option option, List<MapNameValueBinding> arguments)
{
TraitSignature type = constructorType(table, option);
Map<String, Object> values = new TreeMap<String, Object>();
for (MapNameValueBinding binding : arguments) {
values.put(binding.getName(), binding.getValue());
}
OptionInterface parent = option.getOptionInterface();
TraitSignature parentType = parent.lookupTupleSet(parent.getName());
TraitValue result = new TraitValue(option.getName(), type);
if (parentType != null) {
List<ConstraintExpression> conditions = parentType.getRequireClauses();
List<ErrorMessage> messages = parentType.getErrorMessages();
RealizationStatement.checkPairConsistency(conditions, messages);
for (int i = 0; i < conditions.size(); ++i) {
ConstraintExpression requires = conditions.get(i);
ErrorMessage errorMessage = messages.get(i);
type.addRequiresClause(requires, errorMessage);
}
EntityTuple tuple = new EntityTuple(type, values, null);
result.addTuple(tuple);
}
return result;
}
private static TraitSignature constructorType(ArcumDeclarationTable table,
Option option)
{
List<FormalParameter> params = option.getSingletonParameters(table);
TraitSignature type = TraitSignature.makeSingleton(option.getName(), params);
return type;
}
public Set<FormalParameter> getResolvedVariables() {
if (this.resolvedVariables == null || this.needsUpdate) {
this.resolvedVariables = new HashSet<FormalParameter>();
this.needsUpdate = false;
for (TraitValue trait : singletons.values()) {
EntityTuple singleton = trait.getSingleton();
resolvedVariables.addAll(singleton.getBoundVariables());
}
}
return resolvedVariables;
}
@Override
public String toString() {
StringBuilder buff = new StringBuilder();
buff.append("Matched program entities for ");
buff.append(option.getName());
buff.append(":");
buff.append(String.format("%nSingletons"));
for (TraitValue trait : singletons.values()) {
EntityTuple singleton = trait.getSingleton();
buff.append(String.format("%n --"));
buff.append(singleton.toString());
}
buff.append(String.format("%nTraits"));
for (TraitValue trait : traits.values()) {
List<EntityTuple> entities = trait.getEntities();
for (EntityTuple entity : entities) {
buff.append(String.format("%n --"));
buff.append(entity.toString());
}
}
if (DEBUG) {
buff.append(String.format("%nBuilt-ins"));
for (TraitValue trait : builtInTraits.values()) {
List<EntityTuple> nonSingletonList = trait.getEntities();
for (EntityTuple nonSingleton : nonSingletonList) {
buff.append(String.format("%n --"));
buff.append(nonSingleton.toString());
}
}
buff.append(String.format("%n"));
}
return buff.toString();
}
public boolean resolvesSingleton(String ref) {
return findResolvedSingleton(ref) != null;
}
@Override
public FormalParameter findResolvedSingleton(String variableName) {
return find(variableName, getResolvedVariables(), PARAMETER_NAME);
}
public @Union("Entity")
Object getSingletonEntity(String var) {
@Union("Entity")
Object entity = null;
for (TraitValue trait : singletons.values()) {
EntityTuple singleton = trait.getSingleton();
entity = singleton.lookupEntity(var);
if (entity != null)
break;
}
if (entity == null) {
ArcumError.fatalError("The variable %s doesn't exist, or something%n", var);
}
return entity;
}
@Override
public @Union("Entity")
Object lookupEntity(String reference) {
// look it up in the singletons first
for (TraitValue traitValue : singletons.values()) {
for (EntityTuple tuple : traitValue.getEntities()) {
@Union("Entity")
Object entity = tuple.lookupEntity(reference);
if (entity != null) {
return entity;
}
}
}
// and then return if a trait matches
for (TraitValue traitValue : traits.values()) {
if (reference.equals(traitValue.getTraitName())) {
return traitValue;
}
}
// finally, try the built-in traits
for (TraitValue traitValue : builtInTraits.values()) {
if (reference.equals(traitValue.getTraitName())) {
return traitValue;
}
}
return null;
}
@Override
public String lookupEntitiesID(Object entity) {
// Singletons should not be replaced, so we return null here, somewhat of
// a hack
return null;
}
// TASK -- Return all removable node, like locals, not just singletons
public Collection<ASTNode> getRemovableLocalNodes() {
// With multiple bindings in a realize statement the same ASTNode might
// be roots for multiple things. So, we use a Set.
Set<ASTNode> removable = new HashSet<ASTNode>();
for (TraitValue traitValue : singletons.values()) {
ASTNode astNode = traitValue.getSingleton().getRootNode();
if (astNode != null) {
removable.add(astNode);
}
}
for (TraitValue traitValue : this.traits.values()) {
TraitSignature traitType = traitValue.getTraitType();
String traitName = traitType.getName();
OptionInterface optionInterface = option.getOptionInterface();
if (!optionInterface.declaresAsAbstract(traitName)
&& !traitType.isStaticDefinition())
{
for (EntityTuple entity : traitValue.getEntities()) {
ASTNode astNode = entity.getRootNode();
if (astNode != null) {
removable.add(astNode);
}
}
}
}
return removable;
}
public Option getOption() {
return option;
}
public Collection<TraitValue> getSingletons() {
return singletons.values();
}
public Collection<TraitValue> getInterfaceSingletons() {
Set<String> interfaceSingletonNames = getInterfaceSingletonNames();
ArrayList<TraitValue> result = Lists.newArrayList();
String interfaceName = option.getOptionInterface().getName();
for (String interfaceSingleton : interfaceSingletonNames) {
if (interfaceSingleton.equals(interfaceName)) {
// It still counts as an interface-level singleton, even though
// in the requires map it uses the option's name instead. We need
// to use this name in order for the lookup to work
interfaceSingleton = option.getName();
}
TraitValue traitValue = singletons.get(interfaceSingleton);
result.add(traitValue);
}
return result;
}
public Collection<TraitValue> getOptionSingletons() {
Set<String> interfaceSingletonNames = getInterfaceSingletonNames();
ArrayList<TraitValue> result = Lists.newArrayList();
String optionName = option.getName();
for (TraitValue traitValue : singletons.values()) {
if (traitValue.getTraitName().equals(optionName))
continue;
if (!interfaceSingletonNames.contains(traitValue.getTraitName())) {
result.add(traitValue);
}
}
return result;
}
private Set<String> getInterfaceSingletonNames() {
List<TraitSignature> tscs = option.getOptionInterface().getTraitSignatures();
Set<String> result = Sets.newHashSet();
for (TraitSignature type : tscs) {
if (type.isSingleton()) {
String name = type.getName();
result.add(name);
}
}
return result;
}
public Collection<TraitValue> getNonSingletons() {
return traits.values();
}
// Returns the names of the singletons currently found in the table
// public Set<String> getSingletonNames() {
// Set<String> result = new HashSet<String>();
// for (TraitValue singletonTupleSet : singletons.values()) {
// result.addAll(singletonTupleSet.getNamesOfDeclaredFormals());
// }
// return result;
// }
@Override
public FragmentParser newParser(boolean matchingMode) {
FragmentParser parser = table.newParser(option);
parser.setMatchingMode(matchingMode);
return parser;
}
// Apply the require clause checks. Returns true if the checks pass.
// (If false is returned then there is a user-error and processing should
// stop.)
public boolean checkExtraDefinitionConditions(EntityDataBase edb) {
boolean result = true;
Collection<TraitValue> singletonValues = singletons.values();
for (TraitValue singletonValue : singletonValues) {
TraitSignature type = singletonValue.getTraitType();
EntityTuple tuple = singletonValue.getSingleton();
result &= checkEntityTupleConditions(tuple, type, edb, this);
}
if (result == false)
return false;
Collection<TraitValue> traitValues = traits.values();
for (TraitValue traitValue : traitValues) {
TraitSignature type = traitValue.getTraitType();
for (EntityTuple tuple : traitValue.getEntities()) {
CurrentBindingsLookup lookup = new CurrentBindingsLookup(this, tuple);
result &= checkEntityTupleConditions(tuple, type, edb, lookup);
}
}
if (result == false)
return false;
FreeStandingRequirements requirements = option.getFreeStandingRequirements();
checkFreeStandingConditions(edb, requirements);
if (result == false)
return false;
requirements = option.getOptionInterface().getFreeStandingRequirements();
checkFreeStandingConditions(edb, requirements);
return result;
}
private boolean checkEntityTupleConditions(EntityTuple tuple, TraitSignature type,
EntityDataBase edb, IEntityLookup lookup)
{
boolean result = true;
List<ConstraintExpression> conditions = type.getRequireClauses();
List<ErrorMessage> messages = type.getErrorMessages();
RealizationStatement.checkPairConsistency(conditions, messages);
if (DEBUG) {
System.err.printf("Checking %s%n --against:%s%n", tuple, type);
}
for (int i = 0; i < conditions.size(); ++i) {
ConstraintExpression condition = conditions.get(i);
ErrorMessage message = messages.get(i);
boolean value = condition.evaluate(lookup, edb, this);
if (value == false) {
String errorText;
if (message == ErrorMessage.EMPTY_MESSAGE) {
errorText = String.format("Cannot satisfy constraint: %s", condition);
}
else {
errorText = message.getMessage(lookup);
}
ASTNode rootNode = tuple.getRootNode();
SourceLocation astLocation;
if (rootNode == null) {
if (message.hasLocation()) {
astLocation = message.getLocation(lookup, edb, this);
}
else {
astLocation = location;
}
}
else {
astLocation = new SourceLocation(rootNode);
}
ArcumError.userError(astLocation, errorText);
result = false;
}
}
return result;
}
private void checkFreeStandingConditions(EntityDataBase edb,
FreeStandingRequirements requirements)
{
List<ConstraintExpression> conditions = requirements.getRequireClauses();
List<ErrorMessage> messages = requirements.getErrorMessages();
for (int i = 0; i < conditions.size(); ++i) {
ConstraintExpression condition = conditions.get(i);
ErrorMessage message = messages.get(i);
boolean value = condition.evaluate(this, edb, this);
if (value == false) {
String errorText;
if (message == ErrorMessage.EMPTY_MESSAGE) {
errorText = String.format("Cannot satisfy constraint: %s", condition);
}
else {
errorText = message.getMessage(this);
}
SourceLocation astLocation;
if (message.hasLocation()) {
astLocation = message.getLocation(this, edb, this);
}
else {
astLocation = location;
}
ArcumError.userError(astLocation, errorText);
}
}
}
public SourceLocation getLocation() {
return location;
}
// Extracts only the singleton or static trait entities in the form of a binding
@Override
public BindingMap extractAsBindings() {
BindingMap result = BindingMap.newEmptyMap();
for (TraitValue trait : singletons.values()) {
EntityTuple singleton = trait.getSingleton();
result.addBindings(new BindingMap(singleton.getValues()));
}
for (TraitValue trait : traits.values()) {
if (trait.isStatic()) {
result.bind(trait.getTraitName(), trait, EntityType.TRAIT);
}
}
return result;
}
@Override
public ITypeBinding lookupTypeBinding(ASTNode node) {
EntityDataBase entityDataBase = table.getEntityDataBase();
if (node instanceof AbstractTypeDeclaration) {
AbstractTypeDeclaration atd = (AbstractTypeDeclaration)node;
return entityDataBase.lookupTypeBinding(atd);
}
else if (node instanceof Expression) {
Expression expr = (Expression)node;
return expr.resolveTypeBinding();
}
else {
ArcumError.fatalUserError(SourceLocation.resolveSourceLocation(),
"There is no type associated with %s", Entity.getDisplayString(node));
throw new Unreachable();
}
}
@Override
public AbstractTypeDeclaration lookupTypeDeclaration(ITypeBinding givenBinding) {
return table.getEntityDataBase().lookupTypeDeclaration(givenBinding);
}
public Set<String> getTraitsRealized() {
Set<String> result = Sets.newHashSet();
result.addAll(builtInTraits.keySet());
for (TraitValue singletonTupleSet : singletons.values()) {
result.addAll(singletonTupleSet.getNamesOfDeclaredFormals());
}
return result;
}
@Override
public TypeLookupTable getTypeLookupTable() {
return new TypeLookupTable(getSingletons());
}
}