blob: 0d30f432d62df6676db01fae176f5941c00ef4e7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 University of Illinois at Urbana-Champaign and others.
* 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
*
* Contributors:
* UIUC - Initial API and implementation
*******************************************************************************/
package org.eclipse.photran.internal.core.refactoring;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.photran.core.IFortranAST;
import org.eclipse.photran.internal.core.analysis.binding.Definition;
import org.eclipse.photran.internal.core.analysis.binding.ScopingNode;
import org.eclipse.photran.internal.core.lexer.Token;
import org.eclipse.photran.internal.core.parser.ASTCommonBlockNameNode;
import org.eclipse.photran.internal.core.parser.ASTCommonBlockNode;
import org.eclipse.photran.internal.core.parser.ASTCommonBlockObjectNode;
import org.eclipse.photran.internal.core.parser.ASTCommonStmtNode;
import org.eclipse.photran.internal.core.parser.ASTModuleNode;
import org.eclipse.photran.internal.core.parser.ASTTypeDeclarationStmtNode;
import org.eclipse.photran.internal.core.parser.ASTUseStmtNode;
import org.eclipse.photran.internal.core.parser.GenericASTVisitor;
import org.eclipse.photran.internal.core.parser.IASTListNode;
import org.eclipse.photran.internal.core.parser.IASTNode;
import org.eclipse.photran.internal.core.parser.ISpecificationPartConstruct;
import org.eclipse.photran.internal.core.parser.ISpecificationStmt;
import org.eclipse.photran.internal.core.refactoring.infrastructure.FortranEditorRefactoring;
import org.eclipse.photran.internal.core.reindenter.Reindenter;
import org.eclipse.photran.internal.core.reindenter.Reindenter.Strategy;
import org.eclipse.photran.internal.core.vpg.PhotranTokenRef;
import org.eclipse.photran.internal.core.vpg.PhotranVPG;
import org.eclipse.rephraserengine.core.refactorings.UserInputString;
/**
* Refactoring to move a COMMON block into a module.
* <p>
* THIS REFACTORING IS INCOMPLETE. IT *DOES NOT* WORK CORRECTLY YET.
*
* @author Jeff Overbey
* @author Ashley Kasza - externalized strings
*/
public class MoveCommonToModuleRefactoring extends FortranEditorRefactoring
{
private static final String CRLF = System.getProperty("line.separator"); //$NON-NLS-1$
private ASTCommonBlockNode commonBlockToMove = null;
private String nameOfCommonBlockToMove = null;
private String newModuleName = null;
private Set<IFile> affectedFiles = null;
@Override
public String getName()
{
return Messages.MoveCommonToModuleRefactoring_Name;
}
///////////////////////////////////////////////////////////////////////////
// User-Accessible Parameters
///////////////////////////////////////////////////////////////////////////
public String getSuggestedNewModuleName()
{
assert commonBlockToMove != null && nameOfCommonBlockToMove != null;
return nameOfCommonBlockToMove.equals("") ? "common" : nameOfCommonBlockToMove; //$NON-NLS-1$ //$NON-NLS-2$
}
@UserInputString(label="Create a module named ", defaultValueMethod="getSuggestedNewModuleName")
public void setNewModuleName(String name)
{
assert name != null;
this.newModuleName = name;
}
///////////////////////////////////////////////////////////////////////////
// Initial Preconditions
///////////////////////////////////////////////////////////////////////////
@Override
protected void doCheckInitialConditions(RefactoringStatus status, IProgressMonitor pm) throws PreconditionFailure
{
ensureProjectHasRefactoringEnabled(status);
findEnclosingCommonBlock();
determineEnclosingCommonBlockName();
determineAffectedFiles();
// TODO: Require implicit none?
// TODO: Make sure all uses of this common block are identical (names, types)
// so that USE-ing the module will not cause naming conflicts
}
private void findEnclosingCommonBlock() throws PreconditionFailure
{
Token enclosingToken = findEnclosingToken(this.astOfFileInEditor, this.selectedRegionInEditor);
if (enclosingToken == null)
fail(Messages.MoveCommonToModuleRefactoring_SelectVarOrBlockInCommonStmt);
commonBlockToMove = enclosingToken.findNearestAncestor(ASTCommonBlockNode.class);
if (commonBlockToMove == null)
fail(Messages.MoveCommonToModuleRefactoring_SelectVarOrBlockInCommonStmt);
}
private void determineEnclosingCommonBlockName()
{
nameOfCommonBlockToMove = getCommonBlockName(commonBlockToMove);
}
private String getCommonBlockName(ASTCommonBlockNode commonBlockToMove)
{
ASTCommonBlockNameNode commonBlockNameToken = commonBlockToMove.getName();
if (commonBlockNameToken != null)
return commonBlockNameToken.getCommonBlockName().getText();
else
return ""; //$NON-NLS-1$
}
private void determineAffectedFiles() throws PreconditionFailure
{
affectedFiles = new HashSet<IFile>();
affectedFiles.add(fileInEditor);
affectedFiles.addAll(vpg.findFilesThatUseCommonBlock(nameOfCommonBlockToMove));
}
///////////////////////////////////////////////////////////////////////////
// Final Preconditions
///////////////////////////////////////////////////////////////////////////
@Override
protected void doCheckFinalConditions(RefactoringStatus status, IProgressMonitor pm) throws PreconditionFailure
{
assert commonBlockToMove != null && nameOfCommonBlockToMove != null && affectedFiles != null;
assert newModuleName != null;
if (!isValidIdentifier(newModuleName)) fail(Messages.bind(Messages.MoveCommonToModuleRefactoring_InvalidIdentifier, newModuleName));
}
///////////////////////////////////////////////////////////////////////////
// Change
///////////////////////////////////////////////////////////////////////////
@Override
protected void doCreateChange(IProgressMonitor pm) throws CoreException, OperationCanceledException
{
assert commonBlockToMove != null && nameOfCommonBlockToMove != null && affectedFiles != null;
assert newModuleName != null;
try
{
for (IFile file : affectedFiles)
{
if (file.equals(fileInEditor))
createModule();
replaceCommonBlockWithModuleUseIn(file);
addChangeFromModifiedAST(file, pm);
}
}
finally
{
vpg.releaseAllASTs();
}
}
private void createModule()
{
ASTModuleNode module = createEmptyModule();
for (ASTCommonBlockObjectNode obj : commonBlockToMove.getCommonBlockObjectList())
populateModuleWithDeclarations(module, obj.getVariableName());
addModuleAtBeginningOfFile(module);
}
private ASTModuleNode createEmptyModule()
{
String moduleSource =
"module " + newModuleName + CRLF + //$NON-NLS-1$
Reindenter.defaultIndentation() + "implicit none" + CRLF + //$NON-NLS-1$
"end module " + newModuleName + CRLF; //$NON-NLS-1$
return (ASTModuleNode)parseLiteralProgramUnit(moduleSource);
}
private void populateModuleWithDeclarations(ASTModuleNode module, Token variable)
{
List<ISpecificationPartConstruct> specificationStmts = findSpecificationStmtsFor(variable);
module.getModuleBody().addAll(specificationStmts);
}
private List<ISpecificationPartConstruct> findSpecificationStmtsFor(Token variable)
{
List<Definition> defs = variable.resolveBinding();
if (defs.size() == 1)
return findSpecificationStmtsFor(defs.get(0));
else
throw new Error(); // TODO
}
private List<ISpecificationPartConstruct> findSpecificationStmtsFor(Definition def)
{
List<ISpecificationPartConstruct> result = new LinkedList<ISpecificationPartConstruct>();
ASTTypeDeclarationStmtNode typeDecl = def.getTokenRef().findToken().findNearestAncestor(ASTTypeDeclarationStmtNode.class);
if (typeDecl != null)
result.add((ISpecificationPartConstruct)typeDecl.clone());
for (PhotranTokenRef tokRef : def.findAllReferences(false))
{
ISpecificationStmt enclosingSpecStmt = tokRef.findToken().findNearestAncestor(ISpecificationStmt.class);
if (enclosingSpecStmt != null && !(enclosingSpecStmt instanceof ASTCommonStmtNode))
result.add((ISpecificationPartConstruct)enclosingSpecStmt.clone());
}
return result;
}
private void addModuleAtBeginningOfFile(ASTModuleNode module)
{
astOfFileInEditor.getRoot().getProgramUnitList().add(0, module);
Reindenter.reindent(module, astOfFileInEditor, Strategy.REINDENT_EACH_LINE);
}
private void replaceCommonBlockWithModuleUseIn(IFile file)
{
IFortranAST ast = vpg.acquireTransientAST(file);
for (ASTCommonBlockNode commonBlock : findCommonBlocksWithCorrectNameIn(ast))
{
removeSpecificationStmtsForCommonBlockVars(commonBlock);
ASTCommonStmtNode enclosingCommonStmt = commonBlock.findNearestAncestor(ASTCommonStmtNode.class);
addUseStmtAtBeginningOfScopeContaining(enclosingCommonStmt, ast);
if (commonStmtContainsOnlyOneCommonBlock(enclosingCommonStmt))
enclosingCommonStmt.removeFromTree();
else
commonBlock.removeFromTree();
}
}
private List<ASTCommonBlockNode> findCommonBlocksWithCorrectNameIn(IFortranAST ast)
{
// Note that we must traverse the tree *before* we start changing it
// to prevent ConcurrentModificationExceptions on list nodes
final List<ASTCommonBlockNode> result = new LinkedList<ASTCommonBlockNode>();
ast.accept(new GenericASTVisitor()
{
@Override
public void visitASTCommonBlockNode(ASTCommonBlockNode commonBlock)
{
if (commonBlockHasSameName(commonBlock))
result.add(commonBlock);
}
});
return result;
}
private void removeSpecificationStmtsForCommonBlockVars(ASTCommonBlockNode commonBlock)
{
// TODO: Should only remove this variable, not the entire spec statement
// common a, b
// integer :: a, b
// will cause an IllegalStateException since the decl statement will be removed twice
for (ASTCommonBlockObjectNode obj : commonBlock.getCommonBlockObjectList())
for (ISpecificationPartConstruct specStmt : findSpecificationStmtsFor(obj.getVariableName()))
try
{
specStmt.removeFromTree();
}
catch (IllegalStateException e)
{
// FIXME HACK -- Ignore
}
}
@SuppressWarnings("unchecked")
private void addUseStmtAtBeginningOfScopeContaining(ASTCommonStmtNode enclosingCommonStmt, IFortranAST ast)
{
ASTUseStmtNode useStmt = (ASTUseStmtNode)parseLiteralStatement("use " + newModuleName); //$NON-NLS-1$
ScopingNode enclosingScope = enclosingCommonStmt.findNearestAncestor(ScopingNode.class);
((IASTListNode<IASTNode>)enclosingScope.getBody()).add(0, useStmt);
Reindenter.reindent(useStmt, ast);
}
private boolean commonBlockHasSameName(ASTCommonBlockNode commonBlock)
{
String cnameOfThisCommonBlock = PhotranVPG.canonicalizeIdentifier(getCommonBlockName(commonBlock));
String cnameOfCommonBlockToMove = PhotranVPG.canonicalizeIdentifier(nameOfCommonBlockToMove);
return cnameOfThisCommonBlock.equals(cnameOfCommonBlockToMove);
}
private boolean commonStmtContainsOnlyOneCommonBlock(ASTCommonStmtNode enclosingCommonStmt)
{
return enclosingCommonStmt.getCommonBlockList().size() == 1;
}
}