blob: b5d310a7a72e1d84d6f12632f67c0240696f4837 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2009 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.Collections;
import java.util.List;
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.ltk.core.refactoring.RefactoringStatusContext;
import org.eclipse.photran.internal.core.analysis.binding.Definition;
import org.eclipse.photran.internal.core.analysis.binding.ScopingNode;
import org.eclipse.photran.internal.core.analysis.types.Type;
import org.eclipse.photran.internal.core.analysis.types.TypeChecker;
import org.eclipse.photran.internal.core.lexer.Token;
import org.eclipse.photran.internal.core.parser.ASTAssignmentStmtNode;
import org.eclipse.photran.internal.core.parser.ASTTypeDeclarationStmtNode;
import org.eclipse.photran.internal.core.parser.ASTUseStmtNode;
import org.eclipse.photran.internal.core.parser.IASTListNode;
import org.eclipse.photran.internal.core.parser.IASTNode;
import org.eclipse.photran.internal.core.parser.IActionStmt;
import org.eclipse.photran.internal.core.parser.IBodyConstruct;
import org.eclipse.photran.internal.core.parser.IExpr;
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.vpg.PhotranTokenRef;
/**
* Refactoring to extract an expression into a local (temporary) variable.
*
* INCOMPLETE
*
* @author Jeff Overbey
* @author Ashley Kasza - externalized strings
*/
public class ExtractLocalVariableRefactoring extends FortranEditorRefactoring
{
///////////////////////////////////////////////////////////////////////////
// Fields
///////////////////////////////////////////////////////////////////////////
private IExpr selectedExpr;
private IActionStmt enclosingStmt;
@SuppressWarnings("rawtypes") private IASTListNode enclosingStmtList;
private ScopingNode enclosingScope;
private String decl = null;
private ASTTypeDeclarationStmtNode declToInsert = null;
private String name;
@Override
public String getName()
{
return Messages.ExtractLocalVariableRefactoring_Name;
}
///////////////////////////////////////////////////////////////////////////
// User-Specified Parameters
///////////////////////////////////////////////////////////////////////////
public void setDecl(String decl)
{
assert decl != null;
this.decl = decl;
}
public String getDecl()
{
assert decl != null;
return this.decl;
}
///////////////////////////////////////////////////////////////////////////
// Initial Preconditions
///////////////////////////////////////////////////////////////////////////
@SuppressWarnings("rawtypes")
@Override
protected void doCheckInitialConditions(RefactoringStatus status, IProgressMonitor pm) throws PreconditionFailure
{
ensureProjectHasRefactoringEnabled(status);
IASTNode selection = findEnclosingNode(this.astOfFileInEditor, this.selectedRegionInEditor);
if (selection == null || !(selection instanceof IExpr))
fail(Messages.ExtractLocalVariableRefactoring_SelectExpressionToExtract);
if (!nodeExactlyEnclosesRegion(selection, this.astOfFileInEditor, this.selectedRegionInEditor))
fail(Messages.ExtractLocalVariableRefactoring_ErrorSelectingPartOfExpression);
selectedExpr = (IExpr)selection;
enclosingStmt = selectedExpr.findNearestAncestor(IActionStmt.class);
if (enclosingStmt == null)
fail(Messages.ExtractLocalVariableRefactoring_VarsExtractedOnlyFromActionStmt);
if (!(enclosingStmt.getParent() instanceof IASTListNode)) // Should never happen since <ActionStmt> only under <Body>
fail(Messages.ExtractLocalVariableRefactoring_ExpressionNotInExtractableStmt);
enclosingStmtList = (IASTListNode)enclosingStmt.getParent();
enclosingScope = enclosingStmt.findNearestAncestor(ScopingNode.class);
if (enclosingScope == null) // Should never happen since <ActionStmt> only under <Body>
fail(Messages.ExtractLocalVariableRefactoring_VarsOnlyExtractedFromStmtsIn);
Type exprType = TypeChecker.getTypeOf(selectedExpr);
if (exprType == Type.TYPE_ERROR)
{
status.addWarning(Messages.ExtractLocalVariableRefactoring_ExpressionTypeNotBeAutoDetermined);
decl = "real :: newName"; //$NON-NLS-1$
}
else
{
decl = exprType.toString() + " :: newName"; //$NON-NLS-1$
}
}
///////////////////////////////////////////////////////////////////////////
// Final Preconditions
///////////////////////////////////////////////////////////////////////////
@Override
protected void doCheckFinalConditions(RefactoringStatus status, IProgressMonitor pm) throws PreconditionFailure
{
assert decl != null;
status.addWarning(Messages.ExtractLocalVariableRefactoring_ExtractionMayNotPreserveBehavior);
// Simple checks -- input validation
if (this.decl.trim().equals("")) //$NON-NLS-1$
fail(Messages.ExtractLocalVariableRefactoring_EnterDeclarationForExtractedVar);
IBodyConstruct decl = parseLiteralStatementNoFail(this.decl);
if (decl == null || !(decl instanceof ASTTypeDeclarationStmtNode))
fail(Messages.bind(Messages.ExtractLocalVariableRefactoring_InvalidTypeDeclStmt, this.decl));
declToInsert = (ASTTypeDeclarationStmtNode)decl;
if (declToInsert.getEntityDeclList() == null
|| declToInsert.getEntityDeclList().size() != 1)
fail(Messages.ExtractLocalVariableRefactoring_DeclarationDoesNotDeclareSingleVar);
name = declToInsert.getEntityDeclList().get(0).getObjectName().getObjectName().getText();
if (declToInsert.getEntityDeclList().get(0).getInitialization() != null)
fail(Messages.ExtractLocalVariableRefactoring_DeclarationMustNotContainInitialization);
// Complex checks -- require program analysis
checkForConflictingBindings(pm, status);
}
private void checkForConflictingBindings(IProgressMonitor pm, RefactoringStatus status)
{
Definition def = arbitraryDefinitionInScope();
if (def == null) return; // No declarations in scope, so the new one can't conflict
checkForConflictingBindings(pm,
new ConflictingBindingErrorHandler(status),
def,
Collections.<PhotranTokenRef>emptyList(),
name);
}
private Definition arbitraryDefinitionInScope()
{
List<Definition> allDefs = enclosingScope.getAllDefinitions();
if (allDefs.isEmpty())
return null;
else
return allDefs.get(0);
}
private final class ConflictingBindingErrorHandler implements IConflictingBindingCallback
{
private final RefactoringStatus status;
private ConflictingBindingErrorHandler(RefactoringStatus status) { this.status = status; }
public void addConflictError(List<Conflict> conflictingDef)
{
Conflict conflict = conflictingDef.get(0);
String msg = Messages.bind(Messages.ExtractLocalVariableRefactoring_NameConflictsWith, conflict.name, vpg.getDefinitionFor(conflict.tokenRef));
RefactoringStatusContext context = createContext(conflict.tokenRef); // Highlights problematic definition
status.addError(msg, context);
}
public void addConflictWarning(List<Conflict> conflictingDef)
{
Conflict conflict = conflictingDef.get(0);
String msg = Messages.bind(Messages.ExtractLocalVariableRefactoring_NameMightConflictWithSubprogram, conflict.name);
RefactoringStatusContext context = createContext(conflict.tokenRef); // Highlights problematic definition
status.addWarning(msg, context);
}
public void addReferenceWillChangeError(String newName, Token reference)
{
throw new IllegalStateException();
}
}
///////////////////////////////////////////////////////////////////////////
// Change
///////////////////////////////////////////////////////////////////////////
@Override
protected void doCreateChange(IProgressMonitor pm) throws CoreException, OperationCanceledException
{
assert declToInsert != null;
try
{
insertDeclaration();
insertAssignment();
replaceExpression();
this.addChangeFromModifiedAST(this.fileInEditor, pm);
}
finally
{
vpg.releaseAllASTs();
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void insertDeclaration()
{
IASTListNode body = enclosingScope.getBody();
body.add(findIndexToInsertDeclarationIn(body), declToInsert);
Reindenter.reindent(declToInsert, astOfFileInEditor);
}
private int findIndexToInsertDeclarationIn(IASTListNode<? extends IASTNode> body)
{
int lastTypeDeclStmt = -1;
int lastSpecStmt = -1;
int lastUseStmt = -1;
for (int i = 0; i < body.size(); i++)
{
IASTNode thisStmt = body.get(i);
if (thisStmt instanceof ASTTypeDeclarationStmtNode)
lastTypeDeclStmt = i;
if (thisStmt instanceof ISpecificationPartConstruct
|| thisStmt instanceof ISpecificationStmt)
lastSpecStmt = i;
if (thisStmt instanceof ASTUseStmtNode)
lastUseStmt = i;
}
if (lastTypeDeclStmt >= 0)
return lastTypeDeclStmt + 1;
else if (lastSpecStmt >= 0)
return lastSpecStmt + 1;
else if (lastUseStmt >= 0)
return lastUseStmt + 1;
else
return 0;
}
@SuppressWarnings("unchecked")
private void insertAssignment()
{
IExpr expr = (IExpr)selectedExpr.clone();
expr.findFirstToken().setWhiteBefore(""); //$NON-NLS-1$
ASTAssignmentStmtNode assignmentStmt = (ASTAssignmentStmtNode)parseLiteralStatement(name + " = " + expr); //$NON-NLS-1$
enclosingStmtList.insertBefore(enclosingStmt, assignmentStmt);
Reindenter.reindent(assignmentStmt, astOfFileInEditor);
}
private void replaceExpression()
{
IExpr variable = parseLiteralExpression(name.trim());
variable.findFirstToken().setWhiteBefore(selectedExpr.findFirstToken().getWhiteBefore());
selectedExpr.replaceWith(variable);
}
}