content: /*******************************************************************************
   * 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);
  	}
  
  }
  