blob: 04db84846debfb0ffef0f0c13fba2b4cf2b5ab62 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 UFSM - Universidade Federal de Santa Maria (www.ufsm.br).
* 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
*******************************************************************************/
package org.eclipse.photran.internal.core.refactoring;
import java.util.LinkedList;
import java.util.List;
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.ScopingNode;
import org.eclipse.photran.internal.core.parser.ASTAssignmentStmtNode;
import org.eclipse.photran.internal.core.parser.ASTDataStmtNode;
import org.eclipse.photran.internal.core.parser.ASTDataStmtValueNode;
import org.eclipse.photran.internal.core.parser.ASTDatalistNode;
import org.eclipse.photran.internal.core.parser.ASTDerivedTypeDefNode;
import org.eclipse.photran.internal.core.parser.ASTExecutableProgramNode;
import org.eclipse.photran.internal.core.parser.IASTListNode;
import org.eclipse.photran.internal.core.parser.IASTNode;
import org.eclipse.photran.internal.core.parser.IDataStmtObject;
import org.eclipse.photran.internal.core.refactoring.infrastructure.FortranResourceRefactoring;
import org.eclipse.photran.internal.core.refactoring.infrastructure.Reindenter;
import org.eclipse.photran.internal.core.refactoring.infrastructure.SourcePrinter;
/**
* Data To Parameter: refactoring to transform variables declared as data in variables declared
* with parameter attribute, when these are intended to be constants in source code.
* Often, developers who want to use constants can confuse the data statement with the
* attribute parameter, which is the most suitable in these cases. Making the substitution
* can generate performance gains, because it decreases the access to variables.
*
* @author Gustavo Rissetti
* @author Timofey Yuvashev
* @author Jeff Overbey
* @author Ashley Kasza - externalized strings
**/
public class DataToParameterRefactoring extends FortranResourceRefactoring
{
boolean changesWereMade = false;
@Override
public String getName()
{
return Messages.DataToParameterRefactoring_Name;
}
@Override
protected void doCheckInitialConditions(RefactoringStatus status, IProgressMonitor pm) throws PreconditionFailure
{
ensureProjectHasRefactoringEnabled(status);
removeFixedFormFilesFrom(this.selectedFiles, status);
removeCpreprocessedFilesFrom(this.selectedFiles, status);
}
@Override
protected void doCheckFinalConditions(RefactoringStatus status, IProgressMonitor pm) throws PreconditionFailure
{
try
{
for (IFile file : selectedFiles)
{
IFortranAST ast = vpg.acquirePermanentAST(file);
if (ast == null)
{
status.addError(Messages.bind(Messages.DataToParameterRefactoring_SelectedFileCannotBeParsed, file.getName()));
}
else
{
makeChangesTo(file, ast, status, pm);
vpg.releaseAST(file);
}
}
}
finally
{
vpg.releaseAllASTs();
}
}
private void makeChangesTo(IFile file, IFortranAST ast, RefactoringStatus status, IProgressMonitor pm) throws PreconditionFailure
{
for (ScopingNode scope : ast.getRoot().getAllContainedScopes())
new ScopeConverter().convert(scope, ast);
if (changesWereMade)
{
status.addWarning(Messages.DataToParameterRefactoring_RefactorNotConsideringVarAssignment);
addChangeFromModifiedAST(file, pm);
}
}
private class ScopeConverter
{
private IASTListNode<IASTNode> scopeBody;
private IFortranAST ast;
private List<IASTNode> dataAndParameterStmts = new LinkedList<IASTNode>();
private List<IASTNode> nodesToDelete = new LinkedList<IASTNode>();
@SuppressWarnings("unchecked")
public void convert(ScopingNode scope, IFortranAST ast) throws PreconditionFailure
{
if (scope instanceof ASTExecutableProgramNode || scope instanceof ASTDerivedTypeDefNode)
return;
this.ast = ast;
this.scopeBody = (IASTListNode<IASTNode>)scope.getBody();
convert();
}
private void convert() throws PreconditionFailure
{
List<String> assignedVars = determineAssignedVariables();
for (IASTNode node : scopeBody)
if (node instanceof ASTDataStmtNode)
convertDataStmt((ASTDataStmtNode)node, assignedVars);
insertAndDeleteStmts();
removeLeadingComma();
}
/**
* In order to convert <pre>data name / value /</pre>
* into <pre>parameter ( name = value )</pre>, the variable
* <i>name</i> cannot be assigned in the program, only read.
*
* @return a list of variables that appear on the left-hand side of an assignment statement
*/
private List<String> determineAssignedVariables()
{
List<String> assignedVars = new LinkedList<String>();
for (IASTNode node : scopeBody)
if (node instanceof ASTAssignmentStmtNode)
assignedVars.add(((ASTAssignmentStmtNode)node).getLhsVariable().getName().getText());
return assignedVars;
}
private void convertDataStmt(ASTDataStmtNode node, List<String> assignedVars) throws PreconditionFailure
{
if (node.getDatalist() == null)
throw new PreconditionFailure(Messages.DataToParameterRefactoring_EmptyDataListInNode);
int size = node.getDatalist().size();
for (ASTDatalistNode dataList : node.getDatalist())
size = new DataListConverter().convert(dataList, size, assignedVars, this);
}
private void insertAndDeleteStmts()
{
// Inserts all Parameter nodes created.
for (int i = 0; i<dataAndParameterStmts.size(); i+=2)
{
scopeBody.insertAfter(dataAndParameterStmts.get(i), dataAndParameterStmts.get(i+1));
Reindenter.reindent(dataAndParameterStmts.get(i+1), ast);
}
// Delete Data nodes which were empty.
for (int i = 0; i<nodesToDelete.size(); i++)
{
ASTDataStmtNode delete = (ASTDataStmtNode)nodesToDelete.get(i);
if(scopeBody.contains(delete)){
delete.removeFromTree();
}
}
}
/**
* If any statement has been changed to
* <pre>data ,val /value/</pre>
* this removes the comma after the DATA keyword.
*/
private void removeLeadingComma()
{
for (IASTNode node : scopeBody)
{
if (node instanceof ASTDataStmtNode)
{
IASTNode comma = node;
String source_comma = SourcePrinter.getSourceCodeFromASTNode(comma);
String[] source_comma_split = source_comma.split("\n"); //$NON-NLS-1$
// Find the Data statement.
String statement = source_comma_split[source_comma_split.length-1].trim();
String data = statement.substring(0, 4);
String list_data = statement.substring(4);
list_data = list_data.trim();
if(list_data.startsWith(",")) //$NON-NLS-1$
{
// Remove the comma that is left.
list_data = list_data.substring(1);
list_data = list_data.trim();
String new_source = new String(""); //$NON-NLS-1$
for(int i=0; i<source_comma_split.length-1; i++)
{
new_source += source_comma_split[i] + "\n"; //$NON-NLS-1$
}
new_source += data + " " + list_data; //$NON-NLS-1$
// Create the new node and replaces the old.
IASTNode without_comma = parseLiteralStatement(new_source);
comma.replaceWith(without_comma);
Reindenter.reindent(without_comma, ast);
}
}
}
}
// These are used by DataListConverter, below
public void prependCommentsToLastParameterStmt(String comments)
{
IASTNode lastParameterStmt = dataAndParameterStmts.get(lastParameterStmtIndex());
IASTNode parameterStmtWithComments = parseLiteralStatement(comments + SourcePrinter.getSourceCodeFromASTNode(lastParameterStmt));
replaceLastParameterStmtWith(parameterStmtWithComments);
}
private int lastParameterStmtIndex()
{
return dataAndParameterStmts.size()-1;
}
private void replaceLastParameterStmtWith(IASTNode newParameterStmt)
{
dataAndParameterStmts.remove(lastParameterStmtIndex());
dataAndParameterStmts.add(newParameterStmt);
}
public void addDataStmtAndParameterStmt(IASTNode dataStmt, IASTNode parameter)
{
// Reference to where the new node should be inserted in the AST.
dataAndParameterStmts.add(dataStmt);
// New node to be inserted in the AST.
dataAndParameterStmts.add(parameter);
}
/** Adds the given node to the list of nodes to delete */
public void addNodeToDelete(ASTDataStmtNode dataStmt)
{
nodesToDelete.add(dataStmt);
}
}
private class DataListConverter
{
private ScopeConverter scopeConverter;
private ASTDatalistNode dataList;
private IASTListNode<IDataStmtObject> objectList;
private IASTListNode<ASTDataStmtValueNode> valueList;
private ASTDataStmtNode dataStmt;
private List<IDataStmtObject> objectsToDelete;
private List<ASTDataStmtValueNode> valuesToDelete;
public int convert(ASTDatalistNode dataList, int numDataLists, List<String> assignedVars, ScopeConverter scopeConverter)
{
this.dataList = dataList;
this.objectList = dataList.getDataStmtSet().getDataStmtObjectList();
this.valueList = dataList.getDataStmtSet().getDataStmtValueList();
this.dataStmt = (ASTDataStmtNode)dataList.getParent().getParent();
this.scopeConverter = scopeConverter;
this.objectsToDelete = new LinkedList<IDataStmtObject>();
this.valuesToDelete = new LinkedList<ASTDataStmtValueNode>();
for (int i = 0; i < dataList.getDataStmtSet().getDataStmtObjectList().size(); i++)
transformToParameter(i, assignedVars);
return removeASTEntries(numDataLists);
}
private void transformToParameter(int index, List<String> assignedVars)
{
String parameterName = objectList.get(index).toString().trim();
if (!assignedVars.contains(parameterName))
{
changesWereMade = true;
IASTNode parameterStmt = createParameterStmt(index, parameterName);
scopeConverter.addDataStmtAndParameterStmt(dataStmt, parameterStmt);
valuesToDelete.add(valueList.get(index));
objectsToDelete.add(objectList.get(index));
}
}
private IASTNode createParameterStmt(int index, String parameterName)
{
StringBuffer parameterStmt = new StringBuffer("parameter ( "); //$NON-NLS-1$
parameterStmt.append(parameterName + " = "); //$NON-NLS-1$
String value = valueList.get(index).getConstant().toString().trim();
parameterStmt.append(value);
parameterStmt.append(" )"); //$NON-NLS-1$
parameterStmt.append(trailingComments());
return parseLiteralStatement(parameterStmt.toString());
}
private String trailingComments()
{
// TODO: Use dataStmt.findLastToken().getWhiteBefore()?
String source = SourcePrinter.getSourceCodeFromASTNode(dataStmt);
String[] sourceSplit = source.split("\n"); //$NON-NLS-1$
String lastLine = sourceSplit[sourceSplit.length-1];
for (int index_comment = 0; index_comment < lastLine.length(); index_comment++)
if (lastLine.charAt(index_comment) == '!')
return " " + lastLine.substring(index_comment); //$NON-NLS-1$
return ""; //$NON-NLS-1$
}
private int removeASTEntries(int numDataLists)
{
if (objectList.size() == objectsToDelete.size())
return removeEntireDataList(numDataLists);
else
return removeSpecifiedObjectsOnly(numDataLists);
}
private int removeEntireDataList(int numDataLists)
{
dataList.removeFromTree();
numDataLists--;
// If a node has all its data removed, it is necessary to recover the
// comments that were before it and put them in place.
if (numDataLists == 0)
{
scopeConverter.prependCommentsToLastParameterStmt(leadingComments());
scopeConverter.addNodeToDelete(dataStmt);
}
return numDataLists;
}
private String leadingComments()
{
String source = SourcePrinter.getSourceCodeFromASTNode(dataStmt);
String[] sourceSplit = source.split("\n"); //$NON-NLS-1$
String commentsBeforeLine = ""; //$NON-NLS-1$
for (int i = 0; i < sourceSplit.length - 1; i++)
commentsBeforeLine += sourceSplit[i]+"\n"; //$NON-NLS-1$
return commentsBeforeLine;
}
private int removeSpecifiedObjectsOnly(int numDataLists)
{
objectList.removeAll(objectsToDelete);
//A bit of a hack... This adds a white space before each remaining element in the data section.
//It is needed to prevent "clumping together" of the key-word "data" and the following variables
for (IDataStmtObject n : objectList)
n.findFirstToken().setWhiteBefore(" "); //$NON-NLS-1$
valueList.removeAll(valuesToDelete);
return numDataLists;
}
}
@Override
protected void doCreateChange(IProgressMonitor pm) throws CoreException, OperationCanceledException
{
// The change is made in method makeChangesTo(...).
}
}