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.refactoring.reorg;
  
  import org.eclipse.core.resources.IFile;
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.resources.mapping.ResourceMapping;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IPath;
  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.participants.ReorgExecutionLog;
  import org.eclipse.text.edits.ReplaceEdit;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.IFunction;
  import org.eclipse.wst.jsdt.core.IType;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.search.IJavaScriptSearchConstants;
  import org.eclipse.wst.jsdt.core.search.IJavaScriptSearchScope;
  import org.eclipse.wst.jsdt.core.search.SearchEngine;
  import org.eclipse.wst.jsdt.core.search.SearchMatch;
  import org.eclipse.wst.jsdt.core.search.SearchPattern;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.IRefactoringSearchRequestor;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringCoreMessages;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringSearchEngine;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringSearchEngine2;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.SearchResultGroup;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.TextChangeCompatibility;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.nls.changes.CreateTextFileChange;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.rename.TypeOccurrenceCollector;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.JavaElementUtil;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.TextChangeManager;
  import org.eclipse.wst.jsdt.internal.corext.util.JavaElementResourceMapping;
  import org.eclipse.wst.jsdt.internal.corext.util.JavaModelUtil;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.corext.util.SearchUtils;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  
  public final class CreateCopyOfCompilationUnitChange extends CreateTextFileChange {
  
  	private static TextChangeManager createChangeManager(IProgressMonitor monitor, IJavaScriptUnit copy, String newName) throws CoreException {
  		TextChangeManager manager= new TextChangeManager();
  		SearchResultGroup refs= getReferences(copy, monitor);
  		if (refs == null)
  			return manager;
  		if (refs.getCompilationUnit() == null)
  			return manager;
  
  		String name= RefactoringCoreMessages.CopyRefactoring_update_ref;
  		SearchMatch[] results= refs.getSearchResults();
  		for (int j= 0; j < results.length; j++) {
  			SearchMatch searchResult= results[j];
  			if (searchResult.getAccuracy() == SearchMatch.A_INACCURATE)
  				continue;
  			int offset= searchResult.getOffset();
  			int length= searchResult.getLength();
  			TextChangeCompatibility.addTextEdit(manager.get(copy), name, new ReplaceEdit(offset, length, newName));
  		}
  		return manager;
  	}
  
  	private static SearchPattern createSearchPattern(IType type) throws JavaScriptModelException {
  		SearchPattern pattern= SearchPattern.createPattern(type, IJavaScriptSearchConstants.ALL_OCCURRENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
  		IFunction[] constructors= JavaElementUtil.getAllConstructors(type);
  		if (constructors.length == 0)
  			return pattern;
  		SearchPattern constructorDeclarationPattern= RefactoringSearchEngine.createOrPattern(constructors, IJavaScriptSearchConstants.DECLARATIONS);
  		return SearchPattern.createOrPattern(pattern, constructorDeclarationPattern);
  	}
  
  	private static String getCopiedFileSource(IProgressMonitor monitor, IJavaScriptUnit unit, String newTypeName) throws CoreException {
  		IJavaScriptUnit copy= unit.getPrimary().getWorkingCopy(null);
  		try {
  			TextChangeManager manager= createChangeManager(monitor, copy, newTypeName);
  			String result= manager.get(copy).getPreviewContent(new NullProgressMonitor());
  			return result;
  		} finally {
  			copy.discardWorkingCopy();
  		}
  	}
  
  	private static SearchResultGroup getReferences(final IJavaScriptUnit copy, IProgressMonitor monitor) throws JavaScriptModelException {
  		final IJavaScriptUnit[] copies= new IJavaScriptUnit[] { copy};
  		IJavaScriptSearchScope scope= SearchEngine.createJavaSearchScope(copies);
  		final IType type= copy.findPrimaryType();
  		if (type == null)
  			return null;
  		SearchPattern pattern= createSearchPattern(type);
  		final RefactoringSearchEngine2 engine= new RefactoringSearchEngine2(pattern);
  		engine.setScope(scope);
  		engine.setWorkingCopies(copies);
  		engine.searchPattern(monitor);
  		engine.setRequestor(new IRefactoringSearchRequestor() {
  			TypeOccurrenceCollector fTypeOccurrenceCollector= new TypeOccurrenceCollector(type);
  			public SearchMatch acceptSearchMatch(SearchMatch match) {
  				try {
  					return fTypeOccurrenceCollector.acceptSearchMatch2(copy, match);
  				} catch (CoreException e) {
  					JavaScriptPlugin.log(e);
  					return null;
  				}
  			}
  		});
  		final Object[] results= engine.getResults();
  		// Assert.isTrue(results.length <= 1);
  		// just 1 file or none, but inaccurate matches can play bad here (see
  		// https://bugs.eclipse.org/bugs/show_bug.cgi?id=106127)
  		for (int index= 0; index < results.length; index++) {
  			SearchResultGroup group= (SearchResultGroup) results[index];
  			if (group.getCompilationUnit().equals(copy))
  				return group;
  		}
  		return null;
  	}
  
  	private final INewNameQuery fNameQuery;
  
  	private final IJavaScriptUnit fOldCu;
  
  	public CreateCopyOfCompilationUnitChange(IPath path, String source, IJavaScriptUnit oldCu, INewNameQuery nameQuery) {
  		super(path, source, null, "java"); //$NON-NLS-1$
  		fOldCu= oldCu;
  		fNameQuery= nameQuery;
  		setEncoding(oldCu);
  	}
  
  	public String getName() {
  		return Messages.format(RefactoringCoreMessages.CreateCopyOfCompilationUnitChange_create_copy, new String[] { fOldCu.getElementName(), getPathLabel(fOldCu.getResource())});
  	}
  
  	protected IFile getOldFile(IProgressMonitor monitor) throws OperationCanceledException {
  		try {
  			monitor.beginTask("", 12); //$NON-NLS-1$
  			String oldSource= super.getSource();
  			IPath oldPath= super.getPath();
  			String newTypeName= fNameQuery.getNewName();
  			try {
  				String newSource= getCopiedFileSource(new SubProgressMonitor(monitor, 9), fOldCu, newTypeName);
  				setSource(newSource);
  				setPath(fOldCu.getResource().getParent().getFullPath().append(JavaModelUtil.getRenamedCUName(fOldCu, newTypeName)));
  				return super.getOldFile(new SubProgressMonitor(monitor, 1));
  			} catch (CoreException e) {
  				setSource(oldSource);
  				setPath(oldPath);
  				return super.getOldFile(new SubProgressMonitor(monitor, 2));
  			}
  		} finally {
  			monitor.done();
  		}
  	}
  
  	private String getPathLabel(IResource resource) {
  		final StringBuffer buffer= new StringBuffer(resource.getProject().getName());
  		final String path= resource.getParent().getProjectRelativePath().toString();
  		if (path.length() > 0) {
  			buffer.append('/');
  			buffer.append(path);
  		}
  		return buffer.toString();
  	}
  
  	private void markAsExecuted(IJavaScriptUnit unit, ResourceMapping mapping) {
  		ReorgExecutionLog log= (ReorgExecutionLog) getAdapter(ReorgExecutionLog.class);
  		if (log != null) {
  			log.markAsProcessed(unit);
  			log.markAsProcessed(mapping);
  		}
  	}
  
  	public Change perform(IProgressMonitor monitor) throws CoreException {
  		ResourceMapping mapping= JavaElementResourceMapping.create(fOldCu);
  		final Change result= super.perform(monitor);
  		markAsExecuted(fOldCu, mapping);
  		return result;
  	}
  
  	private void setEncoding(IJavaScriptUnit unit) {
  		IResource resource= unit.getResource();
  		// no file so the encoding is taken from the target
  		if (!(resource instanceof IFile))
  			return;
  		IFile file= (IFile) resource;
  		try {
  			String encoding= file.getCharset(false);
  			if (encoding != null) {
  				setEncoding(encoding, true);
  			} else {
  				encoding= file.getCharset(true);
  				if (encoding != null) {
  					setEncoding(encoding, false);
  				}
  			}
  		} catch (CoreException e) {
  			// Take encoding from target
  		}
  	}
  }
  