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.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.Block;
  import org.eclipse.wst.jsdt.core.dom.ChildPropertyDescriptor;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.DoStatement;
  import org.eclipse.wst.jsdt.core.dom.EnhancedForStatement;
  import org.eclipse.wst.jsdt.core.dom.ForInStatement;
  import org.eclipse.wst.jsdt.core.dom.ForStatement;
  import org.eclipse.wst.jsdt.core.dom.IfStatement;
  import org.eclipse.wst.jsdt.core.dom.ReturnStatement;
  import org.eclipse.wst.jsdt.core.dom.Statement;
  import org.eclipse.wst.jsdt.core.dom.ThrowStatement;
  import org.eclipse.wst.jsdt.core.dom.WhileStatement;
  import org.eclipse.wst.jsdt.core.dom.WithStatement;
  import org.eclipse.wst.jsdt.core.dom.rewrite.ASTRewrite;
  import org.eclipse.wst.jsdt.internal.corext.dom.GenericVisitor;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
  import org.eclipse.wst.jsdt.internal.ui.text.correction.ASTResolving;
  
  public class ControlStatementsFix extends AbstractFix {
  		
  	private final static class ControlStatementFinder extends GenericVisitor {
  		
  		private final List/*<IFixRewriteOperation>*/ fResult;
  		private final boolean fFindControlStatementsWithoutBlock;
  		private final boolean fRemoveUnnecessaryBlocks;
  		private final boolean fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow;
  		
  		public ControlStatementFinder(boolean findControlStatementsWithoutBlock,
  				boolean removeUnnecessaryBlocks,
  				boolean removeUnnecessaryBlocksOnlyWhenReturnOrThrow,
  				List resultingCollection) {
  			
  			fFindControlStatementsWithoutBlock= findControlStatementsWithoutBlock;
  			fRemoveUnnecessaryBlocks= removeUnnecessaryBlocks;
  			fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow= removeUnnecessaryBlocksOnlyWhenReturnOrThrow;
  			fResult= resultingCollection;
  		}
  		
  		/* (non-Javadoc)
  		 * @see org.eclipse.wst.jsdt.internal.corext.dom.GenericVisitor#visit(org.eclipse.wst.jsdt.core.dom.DoStatement)
  		 */
  		public boolean visit(DoStatement node) {
  			if (fFindControlStatementsWithoutBlock) {
  				Statement doBody= node.getBody();
  				if (!(doBody instanceof Block)) {
  					fResult.add(new AddBlockOperation(DoStatement.BODY_PROPERTY, doBody, node));
  				}
  			} else if (fRemoveUnnecessaryBlocks || fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow) {
  				if (RemoveBlockOperation.satisfiesCleanUpPrecondition(node, DoStatement.BODY_PROPERTY, fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow)) {
  					fResult.add(new RemoveBlockOperation(node, DoStatement.BODY_PROPERTY));
  				}
  			}
  			return super.visit(node);
  		}
  
  		/* (non-Javadoc)
  		 * @see org.eclipse.wst.jsdt.internal.corext.dom.GenericVisitor#visit(org.eclipse.wst.jsdt.core.dom.ForStatement)
  		 */
  		public boolean visit(ForStatement node) {
  			if (fFindControlStatementsWithoutBlock) {
  				Statement forBody= node.getBody();
  				if (!(forBody instanceof Block)) {
  					fResult.add(new AddBlockOperation(ForStatement.BODY_PROPERTY, forBody, node));
  				}
  			} else if (fRemoveUnnecessaryBlocks || fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow) {
  				if (RemoveBlockOperation.satisfiesCleanUpPrecondition(node, ForStatement.BODY_PROPERTY, fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow)) {
  					fResult.add(new RemoveBlockOperation(node, ForStatement.BODY_PROPERTY));
  				}
  			}
  			return super.visit(node);
  		}
  		
  		/**
  		 * {@inheritDoc}
  		 */
  		public boolean visit(EnhancedForStatement node) {
  			if (fFindControlStatementsWithoutBlock) {
  				Statement forBody= node.getBody();
  				if (!(forBody instanceof Block)) {
  					fResult.add(new AddBlockOperation(EnhancedForStatement.BODY_PROPERTY, forBody, node));
  				}
  			} else if (fRemoveUnnecessaryBlocks || fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow) {
  				if (RemoveBlockOperation.satisfiesCleanUpPrecondition(node, EnhancedForStatement.BODY_PROPERTY, fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow)) {
  					fResult.add(new RemoveBlockOperation(node, EnhancedForStatement.BODY_PROPERTY));
  				}
  			}
  			return super.visit(node);
  		}
  		
  		/* (non-Javadoc)
  		 * @see org.eclipse.wst.jsdt.internal.corext.dom.GenericVisitor#visit(org.eclipse.wst.jsdt.core.dom.IfStatement)
  		 */
  		public boolean visit(IfStatement statement) {
  			if (fFindControlStatementsWithoutBlock) {
  				Statement then= statement.getThenStatement();
  				if (!(then instanceof Block)) {
  					fResult.add(new AddBlockOperation(IfStatement.THEN_STATEMENT_PROPERTY, then, statement));
  				}
  				Statement elseStatement= statement.getElseStatement();
  				if (elseStatement != null && !(elseStatement instanceof Block) && !(elseStatement instanceof IfStatement)) {
  					fResult.add(new AddBlockOperation(IfStatement.ELSE_STATEMENT_PROPERTY, elseStatement, statement));
  				}
  			} else if (fRemoveUnnecessaryBlocks || fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow) {
  				if (RemoveBlockOperation.satisfiesCleanUpPrecondition(statement, IfStatement.THEN_STATEMENT_PROPERTY, fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow)) {
  					fResult.add(new RemoveBlockOperation(statement, IfStatement.THEN_STATEMENT_PROPERTY));
  				}
  				if (!(statement.getElseStatement() instanceof IfStatement)) {
  					if (RemoveBlockOperation.satisfiesCleanUpPrecondition(statement, IfStatement.ELSE_STATEMENT_PROPERTY, fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow)) {
  						fResult.add(new RemoveBlockOperation(statement, IfStatement.ELSE_STATEMENT_PROPERTY));
  					}	
  				}
  			}
  			return super.visit(statement);
  		}
  
  		/* (non-Javadoc)
  		 * @see org.eclipse.wst.jsdt.internal.corext.dom.GenericVisitor#visit(org.eclipse.wst.jsdt.core.dom.WhileStatement)
  		 */
  		public boolean visit(WhileStatement node) {
  			if (fFindControlStatementsWithoutBlock) {
  				Statement whileBody= node.getBody();
  				if (!(whileBody instanceof Block)) {
  					fResult.add(new AddBlockOperation(WhileStatement.BODY_PROPERTY, whileBody, node));
  				}
  			} else if (fRemoveUnnecessaryBlocks || fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow) {
  				if (RemoveBlockOperation.satisfiesCleanUpPrecondition(node, WhileStatement.BODY_PROPERTY, fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow))
  					fResult.add(new RemoveBlockOperation(node, WhileStatement.BODY_PROPERTY));
  			}
  			return super.visit(node);
  		}
  
  		public boolean visit(WithStatement node) {
  			if (fFindControlStatementsWithoutBlock) {
  				Statement withBody= node.getBody();
  				if (!(withBody instanceof Block)) {
  					fResult.add(new AddBlockOperation(WithStatement.BODY_PROPERTY, withBody, node));
  				}
  			} else if (fRemoveUnnecessaryBlocks || fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow) {
  				if (RemoveBlockOperation.satisfiesCleanUpPrecondition(node, WithStatement.BODY_PROPERTY, fRemoveUnnecessaryBlocksOnlyWhenReturnOrThrow))
  					fResult.add(new RemoveBlockOperation(node, WithStatement.BODY_PROPERTY));
  			}
  			return super.visit(node);
  		}
  
  	}
  	
  	private static class IfElseIterator {
  		
  		private IfStatement fCursor;
  		
  		public IfElseIterator(IfStatement item) {
  			fCursor= findStart(item);
  		}
  		
  		public IfStatement next() {
  			if (!hasNext())
  				return null;
  			
  			IfStatement result= fCursor;
  			
  			if (fCursor.getElseStatement() instanceof IfStatement) {
  				fCursor= (IfStatement)fCursor.getElseStatement();
  			} else {
  				fCursor= null;
  			}
  			
  			return result;
  		}
  		
  		public boolean hasNext() {
  			return fCursor != null;
  		}
  
  		private IfStatement findStart(IfStatement item) {
              while (item.getLocationInParent() == IfStatement.ELSE_STATEMENT_PROPERTY) {
              	item= (IfStatement)item.getParent();
              }
              return item;
          }
  	}
  	
  	private static final class AddBlockOperation extends AbstractFixRewriteOperation {
  
  		private final ChildPropertyDescriptor fBodyProperty;
  		private final Statement fBody;
  		private final Statement fControlStatement;
  
  		public AddBlockOperation(ChildPropertyDescriptor bodyProperty, Statement body, Statement controlStatement) {
  			fBodyProperty= bodyProperty;
  			fBody= body;
  			fControlStatement= controlStatement;
  		}
  
  		/* (non-Javadoc)
  		 * @see org.eclipse.wst.jsdt.internal.corext.fix.AbstractFix.IFixRewriteOperation#rewriteAST(org.eclipse.wst.jsdt.internal.corext.refactoring.structure.CompilationUnitRewrite, java.util.List)
  		 */
  		public void rewriteAST(CompilationUnitRewrite cuRewrite, List textEditGroups) throws CoreException {
  			ASTRewrite rewrite= cuRewrite.getASTRewrite();
  			String label;
  			if (fBodyProperty == IfStatement.THEN_STATEMENT_PROPERTY) {
  				label = FixMessages.CodeStyleFix_ChangeIfToBlock_desription;
  			} else if (fBodyProperty == IfStatement.ELSE_STATEMENT_PROPERTY) {
  				label = FixMessages.CodeStyleFix_ChangeElseToBlock_description;
  			} else {
  				label = FixMessages.CodeStyleFix_ChangeControlToBlock_description;
  			}
  			
  			TextEditGroup group= createTextEditGroup(label);
  			textEditGroups.add(group);
  			
  			ASTNode moveTarget= rewrite.createMoveTarget(fBody);
  			Block replacingBody= cuRewrite.getRoot().getAST().newBlock();
  			replacingBody.statements().add(moveTarget);
  			rewrite.set(fControlStatement, fBodyProperty, replacingBody, group);
  		}
  
  	}
  	
  	static class RemoveBlockOperation extends AbstractFixRewriteOperation {
  
  		private final Statement fStatement;
  		private final ChildPropertyDescriptor fChild;
  
  		public RemoveBlockOperation(Statement controlStatement, ChildPropertyDescriptor child) {
  			fStatement= controlStatement;
  			fChild= child;
  		}
  
  		/**
  		 * {@inheritDoc}
  		 */
  		public void rewriteAST(CompilationUnitRewrite cuRewrite, List textEditGroups) throws CoreException {
  			ASTRewrite rewrite= cuRewrite.getASTRewrite();
  
  			Block block= (Block)fStatement.getStructuralProperty(fChild);
  			Statement statement= (Statement)block.statements().get(0);
  			Statement moveTarget= (Statement)rewrite.createMoveTarget(statement);
  			
  			TextEditGroup group= createTextEditGroup(FixMessages.ControlStatementsFix_removeBrackets_proposalDescription);
  			textEditGroups.add(group);
  			rewrite.set(fStatement, fChild, moveTarget, group);
  		}
  		
  		public static boolean satisfiesCleanUpPrecondition(Statement controlStatement, ChildPropertyDescriptor childDescriptor, boolean onlyReturnAndThrows) {
  			return satisfiesPrecondition(controlStatement, childDescriptor, onlyReturnAndThrows, true);
  		}
  		
  		public static boolean satisfiesQuickAssistPrecondition(Statement controlStatement, ChildPropertyDescriptor childDescriptor) {
  			return satisfiesPrecondition(controlStatement, childDescriptor, false, false);
  		}
  
  		//Can the block around child with childDescriptor of controlStatement be removed?
          private static boolean satisfiesPrecondition(Statement controlStatement, ChildPropertyDescriptor childDescriptor, boolean onlyReturnAndThrows, boolean cleanUpCheck) {
          	Object child= controlStatement.getStructuralProperty(childDescriptor);
          	
          	if (!(child instanceof Block))
          		return false;
          	
          	Block block= (Block)child;
          	List list= block.statements();
          	if (list.size() != 1)
          		return false;
          	
          	ASTNode singleStatement= (ASTNode)list.get(0);
          	
          	if (onlyReturnAndThrows)
          		if (!(singleStatement instanceof ReturnStatement) && !(singleStatement instanceof ThrowStatement))
          			return false;
          	
          	if (controlStatement instanceof IfStatement) {
          		// if (true) {
          		//  while (true) 
          		// 	 if (false)
          		//    ;
          		// } else
          		//   ;
          		
          		if (((IfStatement)controlStatement).getThenStatement() != child)
          			return true;//can always remove blocks in else part
          		
          		IfStatement ifStatement= (IfStatement)controlStatement;
          		if (ifStatement.getElseStatement() == null)
          			return true;//can always remove if no else part
          		
          		return !hasUnblockedIf((Statement)singleStatement, onlyReturnAndThrows, cleanUpCheck);
          	} else {
          		//if (true)
          		// while (true) {
          		//  if (false)
          		//   ;
          		// }
          		//else
          		// ;
          		if (!hasUnblockedIf((Statement)singleStatement, onlyReturnAndThrows, cleanUpCheck))
          			return true;
          		
          		ASTNode currentChild= controlStatement;
          		ASTNode parent= currentChild.getParent();
          		while (true) {
          			Statement body= null;
          			if (parent instanceof IfStatement) {
          				body= ((IfStatement)parent).getThenStatement();
          				if (body == currentChild && ((IfStatement)parent).getElseStatement() != null)//->currentChild is an unblocked then part
          					return false;
          			} else if (parent instanceof WhileStatement) {
          				body= ((WhileStatement)parent).getBody();
          			} else if (parent instanceof WithStatement) {
          				body= ((WithStatement)parent).getBody();
          			} else if (parent instanceof DoStatement) {
          				body= ((DoStatement)parent).getBody();
          			} else if (parent instanceof ForStatement) {
          				body= ((ForStatement)parent).getBody();
          			} else if (parent instanceof EnhancedForStatement) {
          				body= ((EnhancedForStatement)parent).getBody();
          			} else {
          				return true;
          			}
          			if (body != currentChild)//->parents child is a block
          				return true;
          			
          			currentChild= parent;
          			parent= currentChild.getParent();
          		}
          	}
          }
  
  		private static boolean hasUnblockedIf(Statement p, boolean onlyReturnAndThrows, boolean cleanUpCheck) {
  	        while (true) {
  	        	if (p instanceof IfStatement) {
  	        		return true;
  	        	} else {
  
  	        		ChildPropertyDescriptor childD= null;
  	        		if (p instanceof WhileStatement) {
  	        			childD= WhileStatement.BODY_PROPERTY;
  	        		} else if (p instanceof WithStatement) {
  		        			childD= WithStatement.BODY_PROPERTY;
  	        		} else if (p instanceof ForStatement) {
  	        			childD= ForStatement.BODY_PROPERTY;
  	        		} else if (p instanceof EnhancedForStatement) {
  	        			childD= EnhancedForStatement.BODY_PROPERTY;
  	        		} else if (p instanceof DoStatement) {
  	        			childD= DoStatement.BODY_PROPERTY;
  	        		} else {
  	        			return false;
  	        		}
  	        		Statement body= (Statement)p.getStructuralProperty(childD);
  	        		if (body instanceof Block) {
  	        			if (!cleanUpCheck) {
  	        				return false;
  	        			} else {
  	        				if (!satisfiesPrecondition(p, childD, onlyReturnAndThrows, cleanUpCheck))
  	        					return false;
  	        				
  	        				p= (Statement)((Block)body).statements().get(0);
  	        			}
  	        		} else {
  	        			p= body;
  	        		}
  	        	}
  	        }
          }
  
  	}
  
  	public static IFix[] createRemoveBlockFix(JavaScriptUnit compilationUnit, ASTNode node) {
  		Statement statement= ASTResolving.findParentStatement(node);
  		if (statement == null) {
  			return null;
  		}
  		
  		if (statement instanceof Block) {
  			Block block= (Block)statement;
  			if (block.statements().size() != 1)
  				return null;
  			
  			ASTNode parent= block.getParent();
  			if (!(parent instanceof Statement))
  				return null;
  			
  			statement= (Statement)parent;
  		}
  		
  		if (statement instanceof IfStatement) {
  			List result= new ArrayList();
  			
  			List removeAllList= new ArrayList();
  			
  			IfElseIterator iter= new IfElseIterator((IfStatement)statement);
  			IfStatement item= null;
  			while (iter.hasNext()) {
  				item= iter.next();
  				if (RemoveBlockOperation.satisfiesQuickAssistPrecondition(item, IfStatement.THEN_STATEMENT_PROPERTY)) {
              		RemoveBlockOperation op= new RemoveBlockOperation(item, IfStatement.THEN_STATEMENT_PROPERTY);
  					removeAllList.add(op);
  					if (item == statement)
  						result.add(new ControlStatementsFix(FixMessages.ControlStatementsFix_removeIfBlock_proposalDescription, compilationUnit, new IFixRewriteOperation[] {op}));
              	}
  			}
  			
  			if (RemoveBlockOperation.satisfiesQuickAssistPrecondition(item, IfStatement.ELSE_STATEMENT_PROPERTY)) {
              	RemoveBlockOperation op= new RemoveBlockOperation(item, IfStatement.ELSE_STATEMENT_PROPERTY);
  				removeAllList.add(op);
  				if (item == statement)
  					result.add(new ControlStatementsFix(FixMessages.ControlStatementsFix_removeElseBlock_proposalDescription, compilationUnit, new IFixRewriteOperation[] {op}));
              }
              
  			if (removeAllList.size() > 1) {
  				IFixRewriteOperation[] allConvert= (IFixRewriteOperation[])removeAllList.toArray(new IFixRewriteOperation[removeAllList.size()]);
  				result.add(new ControlStatementsFix(FixMessages.ControlStatementsFix_removeIfElseBlock_proposalDescription, compilationUnit, allConvert));
              }
              
              return (IFix[])result.toArray(new IFix[result.size()]);
  		} else if (statement instanceof WhileStatement) {
  			if (RemoveBlockOperation.satisfiesQuickAssistPrecondition(statement, WhileStatement.BODY_PROPERTY)) {
  				RemoveBlockOperation op= new RemoveBlockOperation(statement, WhileStatement.BODY_PROPERTY);
  				return new IFix[] {new ControlStatementsFix(FixMessages.ControlStatementsFix_removeBrackets_proposalDescription, compilationUnit, new IFixRewriteOperation[] {op})};
  			}
  		} else if (statement instanceof ForStatement) {
  			if (RemoveBlockOperation.satisfiesQuickAssistPrecondition(statement, ForStatement.BODY_PROPERTY)) {
  				RemoveBlockOperation op= new RemoveBlockOperation(statement, ForStatement.BODY_PROPERTY);
  				return new IFix[] {new ControlStatementsFix(FixMessages.ControlStatementsFix_removeBrackets_proposalDescription, compilationUnit, new IFixRewriteOperation[] {op})};
  			}
  		} else if (statement instanceof ForInStatement) {
  			if (RemoveBlockOperation.satisfiesQuickAssistPrecondition(statement, ForInStatement.BODY_PROPERTY)) {
  				RemoveBlockOperation op= new RemoveBlockOperation(statement, ForInStatement.BODY_PROPERTY);
  				return new IFix[] {new ControlStatementsFix(FixMessages.ControlStatementsFix_removeBrackets_proposalDescription, compilationUnit, new IFixRewriteOperation[] {op})};
  			}
  		} else if (statement instanceof EnhancedForStatement) {
  			if (RemoveBlockOperation.satisfiesQuickAssistPrecondition(statement, EnhancedForStatement.BODY_PROPERTY)) {
  				RemoveBlockOperation op= new RemoveBlockOperation(statement, EnhancedForStatement.BODY_PROPERTY);
  				return new IFix[] {new ControlStatementsFix(FixMessages.ControlStatementsFix_removeBrackets_proposalDescription, compilationUnit, new IFixRewriteOperation[] {op})};
  			}
  		} else if (statement instanceof DoStatement) {
  			if (RemoveBlockOperation.satisfiesQuickAssistPrecondition(statement, DoStatement.BODY_PROPERTY)) {
  				RemoveBlockOperation op= new RemoveBlockOperation(statement, DoStatement.BODY_PROPERTY);
  				return new IFix[] {new ControlStatementsFix(FixMessages.ControlStatementsFix_removeBrackets_proposalDescription, compilationUnit, new IFixRewriteOperation[] {op})};
  			}
  		}
  		
  		return null;
  	}
  
  	public static IFix createCleanUp(JavaScriptUnit compilationUnit, 
  			boolean convertSingleStatementToBlock, 
  			boolean removeUnnecessaryBlock,
  			boolean removeUnnecessaryBlockContainingReturnOrThrow) throws CoreException {
  		
  		if (!convertSingleStatementToBlock && !removeUnnecessaryBlock && !removeUnnecessaryBlockContainingReturnOrThrow)
  			return null;
  		
  		List operations= new ArrayList();
  		ControlStatementFinder finder= new ControlStatementFinder(convertSingleStatementToBlock, removeUnnecessaryBlock, removeUnnecessaryBlockContainingReturnOrThrow, operations);
  		compilationUnit.accept(finder);
  		
  		if (operations.isEmpty())
  			return null;
  		
  		IFixRewriteOperation[] ops= (IFixRewriteOperation[])operations.toArray(new IFixRewriteOperation[operations.size()]);
  		return new ControlStatementsFix(FixMessages.ControlStatementsFix_change_name, compilationUnit, ops);
  	}
  
  	protected ControlStatementsFix(String name, JavaScriptUnit compilationUnit, IFixRewriteOperation[] fixRewriteOperations) {
  		super(name, compilationUnit, fixRewriteOperations);
  	}
  
  }
  