content: /*******************************************************************************
   * Copyright (c) 2000, 2009 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.refactoring.structure;
  
  import java.util.Collection;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.Set;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.NullProgressMonitor;
  import org.eclipse.core.runtime.OperationCanceledException;
  import org.eclipse.core.runtime.SubProgressMonitor;
  import org.eclipse.ltk.core.refactoring.Change;
  import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
  import org.eclipse.ltk.core.refactoring.RefactoringStatus;
  import org.eclipse.ltk.core.refactoring.TextChange;
  import org.eclipse.ltk.core.refactoring.TextEditBasedChange;
  import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
  import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
  import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant;
  import org.eclipse.ltk.core.refactoring.participants.SharableParticipants;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.IType;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.dom.AST;
  import org.eclipse.wst.jsdt.core.dom.ASTNode;
  import org.eclipse.wst.jsdt.core.dom.ASTParser;
  import org.eclipse.wst.jsdt.core.dom.ASTRequestor;
  import org.eclipse.wst.jsdt.core.dom.AbstractTypeDeclaration;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.IBinding;
  import org.eclipse.wst.jsdt.core.dom.ITypeBinding;
  import org.eclipse.wst.jsdt.core.refactoring.IJavaScriptRefactorings;
  import org.eclipse.wst.jsdt.core.refactoring.descriptors.JavaScriptRefactoringDescriptor;
  import org.eclipse.wst.jsdt.core.refactoring.descriptors.UseSupertypeDescriptor;
  import org.eclipse.wst.jsdt.internal.corext.dom.NodeFinder;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.Checks;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JDTRefactoringDescriptor;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JavaRefactoringArguments;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringCoreMessages;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.code.ScriptableRefactoring;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.constraints.SuperTypeConstraintsModel;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.constraints.SuperTypeConstraintsSolver;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.constraints.SuperTypeRefactoringProcessor;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints.types.TType;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.ISourceConstraintVariable;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.typeconstraints2.ITypeConstraintVariable;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.RefactoringASTParser;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.ResourceUtil;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.TextEditBasedChangeManager;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.ui.JavaScriptElementLabels;
  
  /**
   * Refactoring processor to replace type occurrences by a super type.
   */
  public final class UseSuperTypeProcessor extends SuperTypeRefactoringProcessor {
  
  	private static final String IDENTIFIER= "org.eclipse.wst.jsdt.ui.useSuperTypeProcessor"; //$NON-NLS-1$
  
  	/**
  	 * Finds the type with the given fully qualified name (generic type
  	 * parameters included) in the hierarchy.
  	 * 
  	 * @param type
  	 *            The hierarchy type to find the super type in
  	 * @param name
  	 *            The fully qualified name of the super type
  	 * @return The found super type, or <code>null</code>
  	 */
  	protected static ITypeBinding findTypeInHierarchy(final ITypeBinding type, final String name) {
  		if (type.isArray() || type.isPrimitive())
  			return null;
  		if (name.equals(type.getTypeDeclaration().getQualifiedName()))
  			return type;
  		final ITypeBinding binding= type.getSuperclass();
  		if (binding != null) {
  			final ITypeBinding result= findTypeInHierarchy(binding, name);
  			if (result != null)
  				return result;
  		}
  		return null;
  	}
  
  	/** The text change manager */
  	private TextEditBasedChangeManager fChangeManager= null;
  
  	/** The number of files affected by the last change generation */
  	private int fChanges= 0;
  
  	/** The subtype to replace */
  	private IType fSubType;
  
  	/** The supertype as replacement */
  	private IType fSuperType= null;
  
  	/**
  	 * Creates a new super type processor.
  	 * 
  	 * @param subType
  	 *            the subtype to replace its occurrences, or <code>null</code>
  	 *            if invoked by scripting
  	 */
  	public UseSuperTypeProcessor(final IType subType) {
  		super(null);
  		fReplace= true;
  		fSubType= subType;
  	}
  
  	/**
  	 * Creates a new super type processor.
  	 * 
  	 * @param subType
  	 *            the subtype to replace its occurrences, or <code>null</code>
  	 *            if invoked by scripting
  	 * @param superType
  	 *            the supertype as replacement, or <code>null</code> if
  	 *            invoked by scripting
  	 */
  	public UseSuperTypeProcessor(final IType subType, final IType superType) {
  		super(null);
  		fReplace= true;
  		fSubType= subType;
  		fSuperType= superType;
  	}
  
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#checkFinalConditions(org.eclipse.core.runtime.IProgressMonitor,org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext)
  	 */
  	public final RefactoringStatus checkFinalConditions(final IProgressMonitor monitor, final CheckConditionsContext context) throws CoreException, OperationCanceledException {
  		Assert.isNotNull(monitor);
  		Assert.isNotNull(context);
  		final RefactoringStatus status= new RefactoringStatus();
  		fChangeManager= new TextEditBasedChangeManager();
  		try {
  			monitor.beginTask("", 200); //$NON-NLS-1$
  			monitor.setTaskName(RefactoringCoreMessages.UseSuperTypeProcessor_checking);
  			fChangeManager= createChangeManager(new SubProgressMonitor(monitor, 200), status);
  			if (!status.hasFatalError()) {
  				final RefactoringStatus validation= Checks.validateModifiesFiles(ResourceUtil.getFiles(fChangeManager.getAllCompilationUnits()), getRefactoring().getValidationContext());
  				if (!validation.isOK())
  					return validation;
  			}
  		} finally {
  			monitor.done();
  		}
  		return status;
  	}
  
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#checkInitialConditions(org.eclipse.core.runtime.IProgressMonitor)
  	 */
  	public final RefactoringStatus checkInitialConditions(final IProgressMonitor monitor) throws CoreException, OperationCanceledException {
  		Assert.isNotNull(monitor);
  		final RefactoringStatus status= new RefactoringStatus();
  		try {
  			monitor.beginTask("", 1); //$NON-NLS-1$
  			monitor.setTaskName(RefactoringCoreMessages.UseSuperTypeProcessor_checking);
  			// No checks
  			monitor.worked(1);
  		} finally {
  			monitor.done();
  		}
  		return status;
  	}
  
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#createChange(org.eclipse.core.runtime.IProgressMonitor)
  	 */
  	public final Change createChange(final IProgressMonitor monitor) throws CoreException, OperationCanceledException {
  		Assert.isNotNull(monitor);
  		try {
  			fChanges= 0;
  			monitor.beginTask("", 1); //$NON-NLS-1$
  			monitor.setTaskName(RefactoringCoreMessages.ExtractInterfaceProcessor_creating);
  			final TextEditBasedChange[] changes= fChangeManager.getAllChanges();
  			if (changes != null && changes.length != 0) {
  				fChanges= changes.length;
  				IJavaScriptProject project= null;
  				if (!fSubType.isBinary())
  					project= fSubType.getJavaScriptProject();
  				int flags= JavaScriptRefactoringDescriptor.JAR_MIGRATION | JavaScriptRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE;
  				try {
  					if (fSubType.isLocal() || fSubType.isAnonymous())
  						flags|= JavaScriptRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
  				} catch (JavaScriptModelException exception) {
  					JavaScriptPlugin.log(exception);
  				}
  				final String name= project != null ? project.getElementName() : null;
  				final String description= Messages.format(RefactoringCoreMessages.UseSuperTypeProcessor_descriptor_description_short, fSuperType.getElementName());
  				final String header= Messages.format(RefactoringCoreMessages.UseSuperTypeProcessor_descriptor_description, new String[] { JavaScriptElementLabels.getElementLabel(fSuperType, JavaScriptElementLabels.ALL_FULLY_QUALIFIED), JavaScriptElementLabels.getElementLabel(fSubType, JavaScriptElementLabels.ALL_FULLY_QUALIFIED) });
  				final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(name, this, header);
  				comment.addSetting(Messages.format(RefactoringCoreMessages.UseSuperTypeProcessor_refactored_element_pattern, JavaScriptElementLabels.getElementLabel(fSuperType, JavaScriptElementLabels.ALL_FULLY_QUALIFIED)));
  				addSuperTypeSettings(comment, false);
  				final UseSupertypeDescriptor descriptor= new UseSupertypeDescriptor();
  				descriptor.setProject(name);
  				descriptor.setDescription(description);
  				descriptor.setComment(comment.asString());
  				descriptor.setFlags(flags);
  				descriptor.setSubtype(getSubType());
  				descriptor.setSupertype(getSuperType());
  				descriptor.setReplaceInstanceof(fInstanceOf);
  				return new DynamicValidationRefactoringChange(descriptor, RefactoringCoreMessages.UseSupertypeWherePossibleRefactoring_name, fChangeManager.getAllChanges());
  			}
  			monitor.worked(1);
  		} finally {
  			monitor.done();
  		}
  		return null;
  	}
  
  	/**
  	 * Creates the text change manager for this processor.
  	 * 
  	 * @param monitor
  	 *            the progress monitor to display progress
  	 * @param status
  	 *            the refactoring status
  	 * @return the created text change manager
  	 * @throws JavaScriptModelException
  	 *             if the method declaration could not be found
  	 * @throws CoreException
  	 *             if the changes could not be generated
  	 */
  	protected final TextEditBasedChangeManager createChangeManager(final IProgressMonitor monitor, final RefactoringStatus status) throws JavaScriptModelException, CoreException {
  		Assert.isNotNull(status);
  		Assert.isNotNull(monitor);
  		try {
  			monitor.beginTask("", 300); //$NON-NLS-1$
  			monitor.setTaskName(RefactoringCoreMessages.UseSuperTypeProcessor_creating);
  			final TextEditBasedChangeManager manager= new TextEditBasedChangeManager();
  			final IJavaScriptProject project= fSubType.getJavaScriptProject();
  			final ASTParser parser= ASTParser.newParser(AST.JLS3);
  			parser.setWorkingCopyOwner(fOwner);
  			parser.setResolveBindings(true);
  			parser.setProject(project);
  			parser.setCompilerOptions(RefactoringASTParser.getCompilerOptions(project));
  			if (fSubType.isBinary() || fSubType.isReadOnly()) {
  				final IBinding[] bindings= parser.createBindings(new IJavaScriptElement[] { fSubType, fSuperType }, new SubProgressMonitor(monitor, 50));
  				if (bindings != null && bindings.length == 2 && bindings[0] instanceof ITypeBinding && bindings[1] instanceof ITypeBinding) {
  					solveSuperTypeConstraints(null, null, fSubType, (ITypeBinding) bindings[0], (ITypeBinding) bindings[1], new SubProgressMonitor(monitor, 100), status);
  					if (!status.hasFatalError())
  						rewriteTypeOccurrences(manager, null, null, null, null, new HashSet(), status, new SubProgressMonitor(monitor, 150));
  				}
  			} else {
  				parser.createASTs(new IJavaScriptUnit[] { fSubType.getJavaScriptUnit() }, new String[0], new ASTRequestor() {
  
  					public final void acceptAST(final IJavaScriptUnit unit, final JavaScriptUnit node) {
  						try {
  							final CompilationUnitRewrite subRewrite= new CompilationUnitRewrite(fOwner, unit, node);
  							final AbstractTypeDeclaration subDeclaration= ASTNodeSearchUtil.getAbstractTypeDeclarationNode(fSubType, subRewrite.getRoot());
  							if (subDeclaration != null) {
  								final ITypeBinding subBinding= subDeclaration.resolveBinding();
  								if (subBinding != null) {
  									final ITypeBinding superBinding= findTypeInHierarchy(subBinding, fSuperType.getFullyQualifiedName('.'));
  									if (superBinding != null) {
  										solveSuperTypeConstraints(subRewrite.getCu(), subRewrite.getRoot(), fSubType, subBinding, superBinding, new SubProgressMonitor(monitor, 100), status);
  										if (!status.hasFatalError()) {
  											rewriteTypeOccurrences(manager, this, subRewrite, subRewrite.getCu(), subRewrite.getRoot(), new HashSet(), status, new SubProgressMonitor(monitor, 200));
  											final TextChange change= subRewrite.createChange();
  											if (change != null)
  												manager.manage(subRewrite.getCu(), change);
  										}
  									}
  								}
  							}
  						} catch (CoreException exception) {
  							JavaScriptPlugin.log(exception);
  							status.merge(RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.UseSuperTypeProcessor_internal_error));
  						}
  					}
  
  					public final void acceptBinding(final String key, final IBinding binding) {
  						// Do nothing
  					}
  				}, new NullProgressMonitor());
  			}
  			return manager;
  		} finally {
  			monitor.done();
  		}
  	}
  
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.corext.refactoring.structure.constraints.SuperTypeRefactoringProcessor#createContraintSolver(org.eclipse.wst.jsdt.internal.corext.refactoring.structure.constraints.SuperTypeConstraintsModel)
  	 */
  	protected final SuperTypeConstraintsSolver createContraintSolver(final SuperTypeConstraintsModel model) {
  		return new SuperTypeConstraintsSolver(model);
  	}
  
  	/**
  	 * Returns the number of files that are affected from the last change
  	 * generation.
  	 * 
  	 * @return The number of files which are affected
  	 */
  	public final int getChanges() {
  		return fChanges;
  	}
  
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#getElements()
  	 */
  	public final Object[] getElements() {
  		return new Object[] { fSubType };
  	}
  
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#getIdentifier()
  	 */
  	public final String getIdentifier() {
  		return IDENTIFIER;
  	}
  
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#getProcessorName()
  	 */
  	public final String getProcessorName() {
  		return RefactoringCoreMessages.UseSuperTypeProcessor_name;
  	}
  
  	/**
  	 * Returns the subtype to be replaced.
  	 * 
  	 * @return The subtype to be replaced
  	 */
  	public final IType getSubType() {
  		return fSubType;
  	}
  
  	/**
  	 * Returns the supertype as replacement.
  	 * 
  	 * @return The supertype as replacement
  	 */
  	public final IType getSuperType() {
  		return fSuperType;
  	}
  
  	/**
  	 * {@inheritDoc}
  	 */
  	public final RefactoringStatus initialize(final RefactoringArguments arguments) {
  		if (arguments instanceof JavaRefactoringArguments) {
  			final JavaRefactoringArguments extended= (JavaRefactoringArguments) arguments;
  			String handle= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_INPUT);
  			if (handle != null) {
  				final IJavaScriptElement element= JDTRefactoringDescriptor.handleToElement(extended.getProject(), handle, false);
  				if (element == null || !element.exists() || element.getElementType() != IJavaScriptElement.TYPE)
  					return ScriptableRefactoring.createInputFatalStatus(element, getRefactoring().getName(), IJavaScriptRefactorings.USE_SUPER_TYPE);
  				else
  					fSubType= (IType) element;
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_INPUT));
  			handle= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_ELEMENT + 1);
  			if (handle != null) {
  				final IJavaScriptElement element= JDTRefactoringDescriptor.handleToElement(extended.getProject(), handle, false);
  				if (element == null || !element.exists() || element.getElementType() != IJavaScriptElement.TYPE)
  					return ScriptableRefactoring.createInputFatalStatus(element, getRefactoring().getName(), IJavaScriptRefactorings.USE_SUPER_TYPE);
  				else
  					fSuperType= (IType) element;
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_ELEMENT + 1));
  			final String instance= extended.getAttribute(ATTRIBUTE_INSTANCEOF);
  			if (instance != null) {
  				fInstanceOf= Boolean.valueOf(instance).booleanValue();
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_INSTANCEOF));
  		} else
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
  		return new RefactoringStatus();
  	}
  
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#isApplicable()
  	 */
  	public final boolean isApplicable() throws CoreException {
  		return Checks.isAvailable(fSubType) && Checks.isAvailable(fSuperType) && !fSubType.isAnonymous() && !fSuperType.isAnonymous();
  	}
  
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#loadParticipants(org.eclipse.ltk.core.refactoring.RefactoringStatus,org.eclipse.ltk.core.refactoring.participants.SharableParticipants)
  	 */
  	public final RefactoringParticipant[] loadParticipants(final RefactoringStatus status, final SharableParticipants sharedParticipants) throws CoreException {
  		return new RefactoringParticipant[0];
  	}
  
  	/**
  	 * {@inheritDoc}
  	 */
  	protected final void rewriteTypeOccurrences(final TextEditBasedChangeManager manager, final ASTRequestor requestor, final CompilationUnitRewrite rewrite, final IJavaScriptUnit unit, final JavaScriptUnit node, final Set replacements, final IProgressMonitor monitor) throws CoreException {
  		try {
  			monitor.beginTask("", 100); //$NON-NLS-1$
  			monitor.setTaskName(RefactoringCoreMessages.ExtractInterfaceProcessor_creating);
  			final Collection collection= (Collection) fTypeOccurrences.get(unit);
  			if (collection != null && !collection.isEmpty()) {
  				final IProgressMonitor subMonitor= new SubProgressMonitor(monitor, 100);
  				try {
  					subMonitor.beginTask("", collection.size() * 10); //$NON-NLS-1$
  					subMonitor.setTaskName(RefactoringCoreMessages.ExtractInterfaceProcessor_creating);
  					TType estimate= null;
  					ISourceConstraintVariable variable= null;
  					CompilationUnitRewrite currentRewrite= null;
  					final IJavaScriptUnit sourceUnit= rewrite.getCu();
  					if (sourceUnit.equals(unit))
  						currentRewrite= rewrite;
  					else
  						currentRewrite= new CompilationUnitRewrite(fOwner, unit, node);
  					for (final Iterator iterator= collection.iterator(); iterator.hasNext();) {
  						variable= (ISourceConstraintVariable) iterator.next();
  						estimate= (TType) variable.getData(SuperTypeConstraintsSolver.DATA_TYPE_ESTIMATE);
  						if (estimate != null && variable instanceof ITypeConstraintVariable) {
  							final ASTNode result= NodeFinder.perform(node, ((ITypeConstraintVariable) variable).getRange().getSourceRange());
  							if (result != null)
  								rewriteTypeOccurrence(estimate, currentRewrite, result, currentRewrite.createCategorizedGroupDescription(RefactoringCoreMessages.SuperTypeRefactoringProcessor_update_type_occurrence, SET_SUPER_TYPE));
  						}
  						subMonitor.worked(10);
  					}
  					if (!sourceUnit.equals(unit)) {
  						final TextChange change= currentRewrite.createChange();
  						if (change != null)
  							manager.manage(unit, change);
  					}
  				} finally {
  					subMonitor.done();
  				}
  			}
  		} finally {
  			monitor.done();
  		}
  	}
  
  	/**
  	 * Sets the supertype as replacement.
  	 * 
  	 * @param type
  	 *            The supertype to set
  	 */
  	public final void setSuperType(final IType type) {
  		Assert.isNotNull(type);
  
  		fSuperType= type;
  	}
  }
  