package edu.ucsd.arcum.interpreter.fragments;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.Modifier;
import com.google.common.collect.Lists;
import edu.ucsd.arcum.exceptions.ArcumError;
import edu.ucsd.arcum.exceptions.SourceLocation;
import edu.ucsd.arcum.interpreter.query.EntityType;
import edu.ucsd.arcum.interpreter.query.IEntityLookup;
import edu.ucsd.arcum.interpreter.satisfier.BindingMap;
import edu.ucsd.arcum.util.StringUtil;
public class SubtreeList extends ProgramFragment
{
public enum Kind
{
ORDER_MATTERS, UNORDERED
};
private final List<ProgramFragment> nodes;
private final Kind kind;
public SubtreeList(List<? extends ProgramFragment> nodes, Kind kind) {
this.nodes = Lists.newArrayList(nodes);
this.kind = kind;
}
public ProgramFragment get(int index) {
return nodes.get(index);
}
// Creates a new independent list, but it will point to the same program
// fragments and not copies of them
public SubtreeList(SubtreeList that) {
this.nodes = Lists.newArrayList(that.nodes);
this.kind = that.kind;
}
// Adds the given ProgramFragment to each element in the list. If the
// given list is empty a new SubtreeList will be created with the given type and
// kind and added to the list.
public static void addToAll(List<SubtreeList> list, ProgramFragment toAdd,
Kind kind)
{
if (list.isEmpty()) {
SubtreeList subtreeList;
subtreeList = new SubtreeList(Lists.newArrayList(toAdd), kind);
list.add(subtreeList);
}
else {
for (SubtreeList subtreeList : list) {
subtreeList.nodes.add(toAdd);
}
}
}
// Creates copies of all of the subtree lists in "list" and adds the given
// ProgramFragment to each. If the given list is empty two new SubtreeLists
// will be created with the given type and kind and returned in the collection, but
// only one of the lists will have the element to be added.
public static List<SubtreeList> copyAllAndAdd(Collection<SubtreeList> list,
ProgramFragment toAdd, SubtreeList.Kind kind)
{
List<SubtreeList> result = new ArrayList<SubtreeList>();
if (list.isEmpty()) {
SubtreeList subtreeList;
subtreeList = new SubtreeList(new ArrayList<ProgramFragment>(), kind);
result.add(subtreeList);
subtreeList = new SubtreeList(Lists.newArrayList(toAdd), kind);
result.add(subtreeList);
}
else {
for (SubtreeList original : list) {
SubtreeList copyOfOriginal = new SubtreeList(original);
result.add(copyOfOriginal);
}
for (SubtreeList original : list) {
SubtreeList originalWithToAdd = new SubtreeList(original);
originalWithToAdd.nodes.add(toAdd);
result.add(originalWithToAdd);
}
}
return result;
}
@Override protected void buildString(StringBuilder buff) {
buff.append(getIndenter());
buff.append("(SubtreeList");
if (!nodes.isEmpty()) {
buff.append(String.format("%n"));
getIndenter().indent();
Iterator<ProgramFragment> it = nodes.iterator();
while (it.hasNext()) {
ProgramFragment node = it.next();
node.buildString(buff);
if (it.hasNext()) {
buff.append(String.format("%n"));
}
}
}
buff.append(")");
if (!nodes.isEmpty()) {
getIndenter().unindent();
}
}
// @Override
// public Object instantiateNew(AST ast, IEntityLookup entities,
// Map<String, String> importCouplings) {
// List result = new ArrayList<Object>();
// for (ProgramFragment node: nodes) {
// Object instance = node.instantiateNew(ast, entities, importCouplings);
// result.add(instance);
// }
// return result;
// }
@Override public BindingMap generateNode(IEntityLookup lookup, AST ast) {
EntityList list = new EntityList(null);
BindingMap result = bindRoot(list);
for (ProgramFragment node: nodes) {
BindingMap child = node.generateNode(lookup, ast);
Object entity = child.getResult();
list.addEntity(entity, node);
result = result.consistentMerge(child);
}
return result;
}
@Override protected BindingMap matchesEntityList(EntityList value) {
List astNodes = value.getList();
if (kind == Kind.ORDER_MATTERS) {
return orderedComparison(nodes, astNodes, value);
}
else if (kind == Kind.UNORDERED) {
return unorderedComparison(nodes, astNodes, value);
}
else {
ArcumError.fatalError("Impossible case");
return null;
}
}
private BindingMap orderedComparison(List<ProgramFragment> fragments,
List astNodes, EntityList originalRoot)
{
if (astNodes.size() != fragments.size()) {
return null;
}
BindingMap result = bindRoot(originalRoot);
Iterator<ProgramFragment> i = fragments.iterator();
Iterator<?> j = astNodes.iterator();
while (i.hasNext()) {
ProgramFragment childPattern = i.next();
Object child = j.next();
BindingMap theta = childPattern.matches(child);
if (theta == null) {
return null;
}
result.addBindings(theta);
}
return result;
}
private BindingMap unorderedComparison(List<ProgramFragment> fragments,
List astNodes, EntityList originalRoot)
{
astNodes = Lists.newArrayList(astNodes);
fragments = Lists.newArrayList(fragments);
//DELETEME if (EntityList.isModifiersEdge(originalRoot.getLocationInParent())) {
if (originalRoot.isModifiersList()) {
return compareModifiers(fragments, astNodes, originalRoot);
}
else {
if (astNodes.size() != fragments.size()) {
return null;
}
BindingMap result = bindRoot(originalRoot);
fragmentSearch: for (ProgramFragment fragment : fragments) {
Iterator<Object> astIt = astNodes.iterator();
while (astIt.hasNext()) {
Object astNode = astIt.next();
BindingMap theta = fragment.matches(astNode);
if (theta != null) {
result.addBindings(theta);
astIt.remove();
continue fragmentSearch;
}
}
// if we get to here the fragment has no match, the lists aren't the same
return null;
}
return result;
}
}
private BindingMap compareModifiers(List<ProgramFragment> fragments,
List astNodes, EntityList originalRoot)
{
BindingMap result = bindRoot(originalRoot);
ModifierElement accessSpecifier = removeAccessSpecifier(astNodes);
BindingMap theta = matchAndRemoveAccessSpecifier(accessSpecifier, fragments);
if (theta != null) {
result.addBindings(theta);
}
else {
// no chance for a match
return null;
}
if (fragments.size() != astNodes.size()) {
return null;
}
ArrayList<ProgramFragment> fragmentsNoVariables;
fragmentsNoVariables = Lists.newArrayList(fragments);
ArrayList<ProgramFragment> onlyVariables;
onlyVariables = Lists.newArrayList();
{
Iterator<ProgramFragment> it = fragmentsNoVariables.iterator();
while (it.hasNext()) {
ProgramFragment fragment = it.next();
if (fragment instanceof VariableNode) {
it.remove();
onlyVariables.add(fragment);
}
}
}
fragmentSearch: for (ProgramFragment fragment : fragmentsNoVariables) {
Iterator<Object> astIt = astNodes.iterator();
while (astIt.hasNext()) {
Object astNode = astIt.next();
theta = fragment.matches(astNode);
if (theta != null) {
result.addBindings(theta);
astIt.remove();
continue fragmentSearch;
}
}
// if we get to here the fragment has no match, the lists aren't the same
return null;
}
if (onlyVariables.size() > 1) {
ArcumError.fatalUserError(SourceLocation.GENERATED,
"Unsupported: Multiple variables matching non-access specifier: %s",
StringUtil.separate(onlyVariables));
}
if (onlyVariables.size() != astNodes.size()) {
return null;
}
if (onlyVariables.size() == 1) {
ProgramFragment fragment = onlyVariables.get(0);
theta = fragment.matches(astNodes.get(0));
if (theta != null) {
result.addBindings(theta);
}
else {
return null;
}
}
return result;
}
private boolean isListOfModifiers(List astNodes) {
return true;
}
// Removes the optional access specifier from the given list of astNodes (it is
// assumed that at most one access specifier is present in the list), returning
// the ModifierElement translated value of the specifier. Returns the special
// package specifier if neither public/private/protected are present.
private ModifierElement removeAccessSpecifier(List astNodes) {
ModifierElement result = ModifierElement.MOD_PACKAGE;
for (Iterator it = astNodes.iterator(); it.hasNext();) {
Object astNode = it.next();
if (astNode instanceof Modifier) {
Modifier modifier = (Modifier)astNode;
ModifierElement modifierElement = ModifierElement.lookup(modifier);
if (modifierElement != null && modifierElement.isAccessSpecifier()) {
result = modifierElement;
it.remove();
}
}
else {
ArcumError.fatalError("Unimplemented: An unordered list that"
+ " isn't for modifiers");
}
}
return result;
}
private BindingMap matchAndRemoveAccessSpecifier(ModifierElement accessSpecifier,
List<ProgramFragment> fragments)
{
BindingMap theta = BindingMap.newEmptyMap();
for (Iterator<ProgramFragment> it = fragments.iterator(); it.hasNext();) {
ProgramFragment fragment = it.next();
if (fragment instanceof VariableNode) {
VariableNode variableNode = (VariableNode)fragment;
EntityType type = variableNode.getNodeType();
if (type.isAssignableFrom(EntityType.ACCESS_SPECIFIER)) {
it.remove();
theta = variableNode.matches(accessSpecifier);
}
}
else if (fragment instanceof ResolvedEntity) {
ResolvedEntity resolvedEntity = (ResolvedEntity)fragment;
Object value = resolvedEntity.getValue();
if (value instanceof ModifierElement) {
ModifierElement modifierElement = (ModifierElement)value;
if (accessSpecifier.equals(modifierElement)) {
it.remove();
theta = resolvedEntity.matches(accessSpecifier);
}
else if (modifierElement.isAccessSpecifier()) {
return null;
}
}
}
}
return theta;
}
public int size() {
return nodes.size();
}
}