/******************************************************************************* * Copyright (c) 2000, 2008 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.wst.jsdt.internal.corext.fix; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.wst.jsdt.core.dom.ASTNode; import org.eclipse.wst.jsdt.core.dom.Assignment; import org.eclipse.wst.jsdt.core.dom.Block; import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit; import org.eclipse.wst.jsdt.core.dom.ConstructorInvocation; import org.eclipse.wst.jsdt.core.dom.ExpressionStatement; import org.eclipse.wst.jsdt.core.dom.FieldDeclaration; import org.eclipse.wst.jsdt.core.dom.IBinding; import org.eclipse.wst.jsdt.core.dom.IFunctionBinding; import org.eclipse.wst.jsdt.core.dom.ITypeBinding; import org.eclipse.wst.jsdt.core.dom.IVariableBinding; import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration; import org.eclipse.wst.jsdt.core.dom.Modifier; import org.eclipse.wst.jsdt.core.dom.SimpleName; import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration; import org.eclipse.wst.jsdt.core.dom.Statement; import org.eclipse.wst.jsdt.core.dom.TypeDeclaration; import org.eclipse.wst.jsdt.core.dom.VariableDeclarationExpression; import org.eclipse.wst.jsdt.core.dom.VariableDeclarationFragment; import org.eclipse.wst.jsdt.core.dom.VariableDeclarationStatement; import org.eclipse.wst.jsdt.core.dom.rewrite.ASTRewrite; import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodes; import org.eclipse.wst.jsdt.internal.corext.dom.GenericVisitor; import org.eclipse.wst.jsdt.internal.corext.dom.VariableDeclarationRewrite; import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.CompilationUnitRewrite; import org.eclipse.wst.jsdt.internal.ui.text.correction.ASTResolving; public class VariableDeclarationFix extends AbstractFix { private static class WrittenNamesFinder extends GenericVisitor { private final HashMap fResult; public WrittenNamesFinder(HashMap result) { fResult= result; } /** * {@inheritDoc} */ public boolean visit(SimpleName node) { if (node.getParent() instanceof VariableDeclarationFragment) return super.visit(node); if (node.getParent() instanceof SingleVariableDeclaration) return super.visit(node); IBinding binding= node.resolveBinding(); if (!(binding instanceof IVariableBinding)) return super.visit(node); binding= ((IVariableBinding)binding).getVariableDeclaration(); if (ASTResolving.isWriteAccess(node)) { List list; if (fResult.containsKey(binding)) { list= (List)fResult.get(binding); } else { list= new ArrayList(); } list.add(node); fResult.put(binding, list); } return super.visit(node); } } private static class VariableDeclarationFinder extends GenericVisitor { private final JavaScriptUnit fCompilationUnit; private final List fResult; private final HashMap fWrittenVariables; private final boolean fAddFinalFields; private final boolean fAddFinalParameters; private final boolean fAddFinalLocals; public VariableDeclarationFinder(boolean addFinalFields, boolean addFinalParameters, boolean addFinalLocals, final JavaScriptUnit compilationUnit, final List result, final HashMap writtenNames) { super(); fAddFinalFields= addFinalFields; fAddFinalParameters= addFinalParameters; fAddFinalLocals= addFinalLocals; fCompilationUnit= compilationUnit; fResult= result; fWrittenVariables= writtenNames; } /** * {@inheritDoc} */ public boolean visit(FieldDeclaration node) { if (fAddFinalFields) return handleFragments(node.fragments(), node); return false; } /** * {@inheritDoc} */ public boolean visit(VariableDeclarationStatement node) { if (fAddFinalLocals) return handleFragments(node.fragments(), node); return false; } /** * {@inheritDoc} */ public boolean visit(VariableDeclarationExpression node) { if (fAddFinalLocals && node.fragments().size() == 1) { SimpleName name= ((VariableDeclarationFragment)node.fragments().get(0)).getName(); IBinding binding= name.resolveBinding(); if (binding == null) return false; if (fWrittenVariables.containsKey(binding)) return false; ModifierChangeOperation op= createAddFinalOperation(name, fCompilationUnit, node); if (op == null) return false; fResult.add(op); return false; } return false; } private boolean handleFragments(List list, ASTNode declaration) { List toChange= new ArrayList(); for (Iterator iter= list.iterator(); iter.hasNext();) { VariableDeclarationFragment fragment= (VariableDeclarationFragment)iter.next(); SimpleName name= fragment.getName(); IBinding resolveBinding= name.resolveBinding(); if (canAddFinal(resolveBinding, name, declaration)) { IVariableBinding varbinding= (IVariableBinding)resolveBinding; if (varbinding.isField()) { if (fieldCanBeFinal(fragment, varbinding)) toChange.add(fragment); } else { if (!fWrittenVariables.containsKey(resolveBinding)) toChange.add(fragment); } } } if (toChange.size() == 0) return false; ModifierChangeOperation op= new ModifierChangeOperation(declaration, toChange, Modifier.FINAL, Modifier.VOLATILE); fResult.add(op); return false; } private boolean fieldCanBeFinal(VariableDeclarationFragment fragment, IVariableBinding binding) { if (Modifier.isStatic(((FieldDeclaration)fragment.getParent()).getModifiers())) return false; if (!fWrittenVariables.containsKey(binding)) { //variable is not written if (fragment.getInitializer() == null) {//variable is not initialized return false; } else { return true; } } if (fragment.getInitializer() != null)//variable is initialized and written return false; ITypeBinding declaringClass= binding.getDeclaringClass(); if (declaringClass == null) return false; ArrayList writes= (ArrayList)fWrittenVariables.get(binding); if (!isWrittenInTypeConstructors(binding, writes, declaringClass)) return false; HashSet writingConstructorBindings= new HashSet(); ArrayList writingConstructors= new ArrayList(); for (int i= 0; i < writes.size(); i++) { SimpleName name= (SimpleName)writes.get(i); FunctionDeclaration constructor= getWritingConstructor(name); if (writingConstructors.contains(constructor))//variable is written twice or more in constructor return false; writingConstructors.add(constructor); IFunctionBinding constructorBinding= constructor.resolveBinding(); if (constructorBinding == null) return false; writingConstructorBindings.add(constructorBinding); } for (int i= 0; i < writingConstructors.size(); i++) { FunctionDeclaration constructor= (FunctionDeclaration)writingConstructors.get(i); if (callsWrittingConstructor(constructor, writingConstructorBindings))//writing constructor calls other writing constructor return false; } FunctionDeclaration constructor= (FunctionDeclaration)writingConstructors.get(0); TypeDeclaration typeDecl= (TypeDeclaration)ASTNodes.getParent(constructor, TypeDeclaration.class); if (typeDecl == null) return false; FunctionDeclaration[] methods= typeDecl.getMethods(); for (int i= 0; i < methods.length; i++) { if (methods[i].isConstructor()) { IFunctionBinding methodBinding= methods[i].resolveBinding(); if (methodBinding == null) return false; if (!writingConstructorBindings.contains(methodBinding)) { if (!callsWrittingConstructor(methods[i], writingConstructorBindings))//non writing constructor does not call a writing constructor return false; } } } return true; } private boolean callsWrittingConstructor(FunctionDeclaration methodDeclaration, HashSet writingConstructorBindings) { Block body= methodDeclaration.getBody(); if (body == null) return false; List statements= body.statements(); if (statements.size() == 0) return false; Statement statement= (Statement)statements.get(0); if (!(statement instanceof ConstructorInvocation)) return false; ConstructorInvocation invocation= (ConstructorInvocation)statement; IFunctionBinding constructorBinding= invocation.resolveConstructorBinding(); if (constructorBinding == null) return false; if (writingConstructorBindings.contains(constructorBinding)) { return true; } else { ASTNode declaration= ASTNodes.findDeclaration(constructorBinding, methodDeclaration.getParent()); if (!(declaration instanceof FunctionDeclaration)) return false; return callsWrittingConstructor((FunctionDeclaration)declaration, writingConstructorBindings); } } private boolean isWrittenInTypeConstructors(IVariableBinding binding, ArrayList writes, ITypeBinding declaringClass) { for (int i= 0; i < writes.size(); i++) { SimpleName name= (SimpleName)writes.get(i); FunctionDeclaration methodDeclaration= getWritingConstructor(name); if (methodDeclaration == null) return false; if (!methodDeclaration.isConstructor()) return false; IFunctionBinding constructor= methodDeclaration.resolveBinding(); if (constructor == null) return false; ITypeBinding declaringClass2= constructor.getDeclaringClass(); if (!declaringClass.equals(declaringClass2)) return false; } return true; } private FunctionDeclaration getWritingConstructor(SimpleName name) { Assignment assignement= (Assignment)ASTNodes.getParent(name, Assignment.class); if (assignement == null) return null; ASTNode expression= assignement.getParent(); if (!(expression instanceof ExpressionStatement)) return null; ASTNode block= expression.getParent(); if (!(block instanceof Block)) return null; ASTNode methodDeclaration= block.getParent(); if (!(methodDeclaration instanceof FunctionDeclaration)) return null; return (FunctionDeclaration)methodDeclaration; } /** * {@inheritDoc} */ public boolean visit(VariableDeclarationFragment node) { SimpleName name= node.getName(); IBinding binding= name.resolveBinding(); if (binding == null) return false; if (fWrittenVariables.containsKey(binding)) return false; ModifierChangeOperation op= createAddFinalOperation(name, fCompilationUnit, node); if (op == null) return false; fResult.add(op); return false; } /** * {@inheritDoc} */ public boolean visit(SingleVariableDeclaration node) { SimpleName name= node.getName(); IBinding binding= name.resolveBinding(); if (!(binding instanceof IVariableBinding)) return false; IVariableBinding varBinding= (IVariableBinding)binding; if (fWrittenVariables.containsKey(varBinding)) return false; if (fAddFinalParameters && fAddFinalLocals) { ModifierChangeOperation op= createAddFinalOperation(name, fCompilationUnit, node); if (op == null) return false; fResult.add(op); return false; } else if (fAddFinalParameters) { if (!varBinding.isParameter()) return false; ModifierChangeOperation op= createAddFinalOperation(name, fCompilationUnit, node); if (op == null) return false; fResult.add(op); return false; } else if (fAddFinalLocals) { if (varBinding.isParameter()) return false; ModifierChangeOperation op= createAddFinalOperation(name, fCompilationUnit, node); if (op == null) return false; fResult.add(op); return false; } return false; } } private static class ModifierChangeOperation extends AbstractFixRewriteOperation { private final ASTNode fDeclaration; private final List fToChange; private final int fIncludedModifiers; private final int fExcludedModifiers; public ModifierChangeOperation(ASTNode declaration, List toChange, int includedModifiers, int excludedModifiers) { fDeclaration= declaration; fToChange= toChange; fIncludedModifiers= includedModifiers; fExcludedModifiers= excludedModifiers; } /** * {@inheritDoc} */ public void rewriteAST(CompilationUnitRewrite cuRewrite, List textEditGroups) throws CoreException { ASTRewrite rewrite= cuRewrite.getASTRewrite(); TextEditGroup group= createTextEditGroup(FixMessages.VariableDeclarationFix_changeModifierOfUnknownToFinal_description); textEditGroups.add(group); if (fDeclaration instanceof VariableDeclarationStatement) { VariableDeclarationFragment[] toChange= (VariableDeclarationFragment[])fToChange.toArray(new VariableDeclarationFragment[fToChange.size()]); VariableDeclarationRewrite.rewriteModifiers((VariableDeclarationStatement)fDeclaration, toChange, fIncludedModifiers, fExcludedModifiers, rewrite, group); } else if (fDeclaration instanceof FieldDeclaration) { VariableDeclarationFragment[] toChange= (VariableDeclarationFragment[])fToChange.toArray(new VariableDeclarationFragment[fToChange.size()]); VariableDeclarationRewrite.rewriteModifiers((FieldDeclaration)fDeclaration, toChange, fIncludedModifiers, fExcludedModifiers, rewrite, group); } else if (fDeclaration instanceof SingleVariableDeclaration) { VariableDeclarationRewrite.rewriteModifiers((SingleVariableDeclaration)fDeclaration, fIncludedModifiers, fExcludedModifiers, rewrite, group); } else if (fDeclaration instanceof VariableDeclarationExpression) { VariableDeclarationRewrite.rewriteModifiers((VariableDeclarationExpression)fDeclaration, fIncludedModifiers, fExcludedModifiers, rewrite, group); } } } public static IFix createChangeModifierToFinalFix(final JavaScriptUnit compilationUnit, ASTNode[] selectedNodes) { HashMap writtenNames= new HashMap(); WrittenNamesFinder finder= new WrittenNamesFinder(writtenNames); compilationUnit.accept(finder); List ops= new ArrayList(); VariableDeclarationFinder visitor= new VariableDeclarationFinder(true, true, true, compilationUnit, ops, writtenNames); if (selectedNodes.length == 1) { if (selectedNodes[0] instanceof SimpleName) { selectedNodes[0]= selectedNodes[0].getParent(); } selectedNodes[0].accept(visitor); } else { for (int i= 0; i < selectedNodes.length; i++) { ASTNode selectedNode= selectedNodes[i]; selectedNode.accept(visitor); } } if (ops.size() == 0) return null; IFixRewriteOperation[] result= (IFixRewriteOperation[])ops.toArray(new IFixRewriteOperation[ops.size()]); String label; if (result.length == 1) { label= FixMessages.VariableDeclarationFix_changeModifierOfUnknownToFinal_description; } else { label= FixMessages.VariableDeclarationFix_ChangeMidifiersToFinalWherPossible_description; } return new VariableDeclarationFix(label, compilationUnit, result); } public static IFix createCleanUp(JavaScriptUnit compilationUnit, boolean addFinalFields, boolean addFinalParameters, boolean addFinalLocals) { if (!addFinalFields && !addFinalParameters && !addFinalLocals) return null; HashMap writtenNames= new HashMap(); WrittenNamesFinder finder= new WrittenNamesFinder(writtenNames); compilationUnit.accept(finder); List operations= new ArrayList(); VariableDeclarationFinder visitor= new VariableDeclarationFinder(addFinalFields, addFinalParameters, addFinalLocals, compilationUnit, operations, writtenNames); compilationUnit.accept(visitor); if (operations.isEmpty()) return null; return new VariableDeclarationFix(FixMessages.VariableDeclarationFix_add_final_change_name, compilationUnit, (IFixRewriteOperation[])operations.toArray(new IFixRewriteOperation[operations.size()])); } private static ModifierChangeOperation createAddFinalOperation(SimpleName name, JavaScriptUnit compilationUnit, ASTNode decl) { if (decl == null) return null; IBinding binding= name.resolveBinding(); if (!canAddFinal(binding, name, decl)) return null; if (decl instanceof SingleVariableDeclaration) { return new ModifierChangeOperation(decl, new ArrayList(), Modifier.FINAL, Modifier.VOLATILE); } else if (decl instanceof VariableDeclarationExpression) { return new ModifierChangeOperation(decl, new ArrayList(), Modifier.FINAL, Modifier.VOLATILE); } else if (decl instanceof VariableDeclarationFragment){ VariableDeclarationFragment frag= (VariableDeclarationFragment)decl; decl= decl.getParent(); if (decl instanceof FieldDeclaration || decl instanceof VariableDeclarationStatement) { List list= new ArrayList(); list.add(frag); return new ModifierChangeOperation(decl, list, Modifier.FINAL, Modifier.VOLATILE); } else if (decl instanceof VariableDeclarationExpression) { return new ModifierChangeOperation(decl, new ArrayList(), Modifier.FINAL, Modifier.VOLATILE); } } return null; } private static boolean canAddFinal(IBinding binding, SimpleName name, ASTNode declNode) { if (!(binding instanceof IVariableBinding)) return false; IVariableBinding varbinding= (IVariableBinding)binding; if (Modifier.isFinal(varbinding.getModifiers())) return false; ASTNode parent= ASTNodes.getParent(declNode, VariableDeclarationExpression.class); if (parent != null && ((VariableDeclarationExpression)parent).fragments().size() > 1) return false; if (varbinding.isField() && !Modifier.isPrivate(varbinding.getModifiers())) return false; if (varbinding.isParameter()) { ASTNode varDecl= declNode.getParent(); if (varDecl instanceof FunctionDeclaration) { FunctionDeclaration declaration= (FunctionDeclaration)varDecl; if (declaration.getBody() == null) return false; } } return true; } protected VariableDeclarationFix(String name, JavaScriptUnit compilationUnit, IFixRewriteOperation[] fixRewriteOperations) { super(name, compilationUnit, fixRewriteOperations); } }