package edu.ucsd.arcum.interpreter.transformation;
import static com.google.common.base.ReferenceType.WEAK;
import java.util.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
import org.eclipse.text.edits.MalformedTreeException;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.ReferenceMap;
import com.google.common.collect.Sets;
import edu.ucsd.arcum.EclipseUtil;
import edu.ucsd.arcum.exceptions.ArcumError;
import edu.ucsd.arcum.exceptions.UserCompilationProblem;
import edu.ucsd.arcum.interpreter.ast.ASTUtil;
import edu.ucsd.arcum.interpreter.ast.Option;
import edu.ucsd.arcum.interpreter.ast.TraitSignature;
import edu.ucsd.arcum.interpreter.query.*;
import edu.ucsd.arcum.interpreter.satisfier.NodesWithLocations;
import edu.ucsd.arcum.ui.UIUtil;
import edu.ucsd.arcum.ui.wizards.ArcumRefactoringWizard;
import edu.ucsd.arcum.util.Graph;
public class TransformationAlgorithm
{
private final ArcumDeclarationTable arcumDeclarationTable;
private final OptionMatchTable oldEntities;
private final OptionMatchTable newEntities;
private final List<Change> externalChanges;
private final String transformationMesg;
private final String srcText;
// Constructs a new transformation from the originalOption to the
// alternativeOption, given that both are declared in the top-level symbol table
public TransformationAlgorithm(ArcumDeclarationTable arcumDeclarationTable,
Option originalOption, Option alternativeOption, String srcText)
throws CoreException
{
this.arcumDeclarationTable = arcumDeclarationTable;
this.oldEntities = arcumDeclarationTable.constructEntityTable(srcText);
this.newEntities = new OptionMatchTable(arcumDeclarationTable, originalOption,
alternativeOption, oldEntities);
this.externalChanges = new ArrayList<Change>();
this.transformationMesg = String.format("Transform %s to %s", originalOption
.getName(), alternativeOption.getName());
this.srcText = srcText;
}
// add an additional change to the Java source code refactoring, call before
// calling transform
public void addChange(Change change) {
externalChanges.add(change);
}
// Returns true if the user clicked OK; false on error or cancel
public boolean transform()
throws IllegalArgumentException, MalformedTreeException, BadLocationException,
CoreException, InterruptedException
{
try {
EntityDataBase.pushCurrentDataBase(arcumDeclarationTable.getEntityDataBase());
CodeRewriter rewriter;
rewriter = new CodeRewriter(externalChanges, "Transform Implementation");
// Transformation algorithm:
// * remove the existing singleton and locals
Collection<ASTNode> doomedNodes = oldEntities.getRemovableLocalNodes();
removeNodes(rewriter, doomedNodes);
// * create the new singletons and other locals
NodesWithLocations newSingletons;
newSingletons = newEntities.generateLocalEntities();
insertSingletons(rewriter, newSingletons);
// * transform all traits
Collection<TraitValue> traits;
traits = oldEntities.getNonSingletons();
transformTraits(rewriter, traits);
// MACNEIL (!!!): Also need to filter the built-in traits asserted to
// be true: Need to take the new root and set that as the binding. Could
// be tricky and may require special recomputation of the predicate
// MACNEIL (!!!): Right here would be the location to add the locals that
// use "before" or "after" constructs that refer to an interface-level trait
/*... e.g., ... = newEntities.generateTraitDependentLocalEntities(); */
// Then, write the changes to the buffers
boolean completed = queryUserAndRefactor(transformationMesg, rewriter);
if (completed) {
arcumDeclarationTable.disposeEntityTable(srcText);
return true;
}
else {
return false;
}
}
catch (UserCompilationProblem e) {
UIUtil.error(String.format(
"Cannot transform, please check your"
+ " code and try again. You may need to select"
+ " \"Project->Clean...\" and then click on \"Refresh.\""
+ " Cause: %s.", e.getMessage()), "Check Failed");
ArcumError.fatalUserError(e.getPosition(), "%s", e.getMessage());
return false;
}
finally {
EntityDataBase.popMostRecentDataBase();
}
}
private void removeNodes(CodeRewriter rewriter, Collection<ASTNode> doomedNodes) {
// If we're deleting the whole parent, no need to separately delete the child
List<ASTNode> doomedRoots = ASTUtil.findTrees(doomedNodes);
for (ASTNode doomed : doomedRoots) {
rewriter.removeNode(doomed);
}
}
private void insertSingletons(CodeRewriter rewriter, NodesWithLocations newSingletons)
{
EntityDataBase edb = arcumDeclarationTable.getEntityDataBase();
List<TraitValue> locations = newSingletons.getLocations();
for (TraitValue location : locations) {
List<EntityTuple> entities = location.getEntities();
for (EntityTuple tuple : entities) {
Object entityToAdd = tuple.lookupEntity(EntityDataBase.CHILD_VAR_REF);
Object parent = tuple.lookupEntity(EntityDataBase.PARENT_VAR_REF);
if (parent instanceof ITypeBinding) {
parent = edb.lookupTypeDeclaration((ITypeBinding)parent);
}
ASTNode parentNode = (ASTNode)parent;
ASTNode entityNode = (ASTNode)entityToAdd;
rewriter.insertNode(parentNode, entityNode);
}
}
// List<EntityTuple> nodes = newSingletons.getNodes();
// nodes = filterOutNonRoots(nodes);
// for (EntityTuple node : nodes) {
// ASTNode rootNode = node.getRootNode();
// ASTNode ownerNode = newSingletons.findParentLocation(rootNode, dataBase);
//
// if (DEBUG) {
// System.out.printf("I'm creating: [%s]%n -and adding it to %s.%n",
// rootNode.toString().trim(), firstLine(ownerNode.toString()));
// }
//
// rewriter.insertNode(ownerNode, rootNode);
// }
}
private List<EntityTuple> filterOutNonRoots(List<EntityTuple> entityTuples) {
ReferenceMap<ASTNode, EntityTuple> map;
map = new ReferenceMap<ASTNode, EntityTuple>(WEAK, WEAK);
Set<ASTNode> astNodes = Sets.newHashSet();
for (EntityTuple entityTuple : entityTuples) {
ASTNode astNode = entityTuple.getRootNode();
if (astNode != null) {
map.put(astNode, entityTuple);
astNodes.add(astNode);
}
}
Iterator<ASTNode> it = astNodes.iterator();
astNode: while (it.hasNext()) {
ASTNode astNode = it.next();
for (;;) {
ASTNode parent = astNode.getParent();
if (parent == null)
break;
if (astNodes.contains(parent)) {
it.remove();
continue astNode;
}
astNode = parent;
}
}
List<EntityTuple> result = Lists.newArrayList();
for (EntityTuple entityTuple : entityTuples) {
ASTNode astNode = entityTuple.getRootNode();
if (astNodes.contains(astNode)) {
result.add(entityTuple);
}
}
return result;
}
// URGENT: Need to check that any option realizes only the traits in the interface,
// except for extra singletons and static traits (the check would not go here,
// however, but is a pre-condition for the getRealizationStatement call below to
// not get a null pointer error)
//
// MACNEIL: Note that it's possible for the destination option to have even
// more matches from code that was around before (meaning that if it were to
// be translated back to the original option, it would have more matches). Thus,
// we will likely need to rematch everything: Building the table again in this
// manner (by indirectly calling newEntities.addTrait(...)) is not complete.
private void transformTraits(CodeRewriter rewriter, Collection<TraitValue> traits)
throws CoreException
{
Map<ASTNode, EntityTuple> nodesMap = extractNonStaticNodes(traits);
List<EntityTuple> originalEntities = topDownOrdering(nodesMap);
try {
List<ASTNode> originalNodes;
originalNodes = Lists.transform(originalEntities, EntityTuple.getRootNode);
rewriter.trackNodes(originalNodes);
for (EntityTuple original : originalEntities) {
EntityTuple replacement;
replacement = newEntities.generateEntityReplacement(original);
rewriter.replaceNode(original.getRootNode(), replacement);
}
}
finally {
rewriter.resetTracking();
}
}
// Extracts from the given collection all ASTNodes that are non-static traits.
// At the same time, the "newEntities" table it updated to have entries for
// these traits
private Map<ASTNode, EntityTuple> extractNonStaticNodes(Collection<TraitValue> traits)
{
Map<ASTNode, EntityTuple> allNodes = Maps.newIdentityHashMap();
traitIteration: for (TraitValue trait : traits) {
if (trait.isStatic()) {
continue traitIteration;
}
TraitSignature traitType = trait.getTraitType();
newEntities.addTrait(traitType, trait.isNested(), trait.isStatic());
for (EntityTuple entity : trait.getEntities()) {
ASTNode rootNode = entity.getRootNode();
allNodes.put(rootNode, entity);
}
}
return allNodes;
}
// Forms a graph that abstract the given "nodes" containment relationships and
// returns a top-down ordering of their associated entity tuples
private List<EntityTuple> topDownOrdering(Map<ASTNode, EntityTuple> nodes) {
Graph<EntityTuple> hasDescendantGraph = Graph.newGraph();
Set<ASTNode> justASTNodes = nodes.keySet();
eachNode: for (ASTNode node : justASTNodes) {
EntityTuple child = nodes.get(node);
hasDescendantGraph.addNode(child);
ASTNode ancestorNode = node.getParent();
while (ancestorNode != null) {
if (nodes.containsKey(ancestorNode)) {
EntityTuple ancestor = nodes.get(ancestorNode);
hasDescendantGraph.addEdge(ancestor, child);
continue eachNode;
}
ancestorNode = ancestorNode.getParent();
}
}
List<EntityTuple> tops = hasDescendantGraph.getTrees();
System.out.printf("%d change(s), with %d root(s):%n", nodes.size(), tops.size());
for (EntityTuple top : tops) {
System.out.printf(" -- %s%n", top);
}
System.out.printf("%n");
List<EntityTuple> result = Lists.newArrayList();
for (EntityTuple top : tops) {
result.addAll(hasDescendantGraph.breadthFirstSearch(top));
}
return result;
}
// returns true if the change actually happened, false if the user canceled
private boolean queryUserAndRefactor(String transformationMesg, CodeRewriter rewriter)
throws InterruptedException
{
ArcumRefactoring refactoring;
refactoring = new ArcumRefactoring(transformationMesg, rewriter);
RefactoringWizard wizard = new ArcumRefactoringWizard(refactoring);
RefactoringWizardOpenOperation operation;
operation = new RefactoringWizardOpenOperation(wizard);
int button = operation.run(EclipseUtil.getShell(), refactoring.getName());
return (button == IDialogConstants.OK_ID);
}
}