/*=============================================================================# # Copyright (c) 2008-2016 Stephan Wahlbrink (WalWare.de) 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: # Stephan Wahlbrink - initial API and implementation #=============================================================================*/ package de.walware.ecommons.ltk.core.refactoring; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.filebuffers.LocationKind; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourceAttributes; import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.jface.text.AbstractDocument; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPartitioningException; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.CompositeChange; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.ltk.core.refactoring.participants.CopyArguments; import org.eclipse.ltk.core.refactoring.participants.CopyParticipant; import org.eclipse.ltk.core.refactoring.participants.DeleteArguments; import org.eclipse.ltk.core.refactoring.participants.DeleteParticipant; import org.eclipse.ltk.core.refactoring.participants.MoveArguments; import org.eclipse.ltk.core.refactoring.participants.MoveParticipant; import org.eclipse.ltk.core.refactoring.participants.ParticipantManager; import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant; import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor; import org.eclipse.ltk.core.refactoring.participants.ReorgExecutionLog; import org.eclipse.ltk.core.refactoring.participants.SharableParticipants; import org.eclipse.ltk.core.refactoring.resource.DeleteResourceChange; import org.eclipse.ltk.internal.core.refactoring.Resources; import org.eclipse.osgi.util.NLS; import org.eclipse.text.edits.DeleteEdit; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MultiTextEdit; import de.walware.jcommons.collections.ImCollections; import de.walware.ecommons.io.FileUtil; import de.walware.ecommons.text.BasicHeuristicTokenScanner; import de.walware.ecommons.text.TextUtil; import de.walware.ecommons.ltk.LTK; import de.walware.ecommons.ltk.LTKUtil; import de.walware.ecommons.ltk.core.ElementSet; import de.walware.ecommons.ltk.core.model.IModelElement; import de.walware.ecommons.ltk.core.model.ISourceElement; import de.walware.ecommons.ltk.core.model.ISourceStructElement; import de.walware.ecommons.ltk.core.model.ISourceUnit; import de.walware.ecommons.ltk.core.model.IWorkspaceSourceUnit; import de.walware.ecommons.ltk.core.util.ElementComparator; import de.walware.ecommons.ltk.internal.core.refactoring.Messages; /** * Provides common functions for refacotring. * Can be extended to adapt to language specific peculiarity. */ public abstract class RefactoringAdapter { private static final Comparator<IModelElement> MODELELEMENT_SORTER = new ElementComparator(); private final String fModelTypeId; public RefactoringAdapter(final String modelTypeId) { fModelTypeId = modelTypeId; } public Comparator<IModelElement> getModelElementComparator() { return MODELELEMENT_SORTER; } public String getModelTypeId() { return fModelTypeId; } public boolean isSupportedModelType(final String typeId) { return (fModelTypeId == typeId); } public abstract String getPluginIdentifier(); public abstract boolean isCommentContent(final ITypedRegion partition); public abstract BasicHeuristicTokenScanner getScanner(final ISourceUnit su); /** * - Sort elements * - Removes nested children. * * @param elements must be sorted by unit and order * @return */ public ISourceStructElement[] checkElements(final ISourceStructElement[] elements) { if (elements.length <= 1) { return elements; } Arrays.sort(elements, getModelElementComparator()); ISourceStructElement last = elements[0]; ISourceUnit unitOfLast = last.getSourceUnit(); int endOfLast = last.getSourceRange().getOffset()+last.getSourceRange().getLength(); final List<ISourceStructElement> checked = new ArrayList<>(elements.length); for (final ISourceStructElement element : elements) { final ISourceUnit unit = element.getSourceUnit(); final int end = last.getSourceRange().getOffset()+last.getSourceRange().getLength(); if (unit != unitOfLast) { checked.add(element); last = element; unitOfLast = unit; endOfLast = end; continue; } if (end > endOfLast) { checked.add(element); last = element; endOfLast = end; continue; } // is child, ignore continue; } return checked.toArray(new ISourceStructElement[checked.size()]); } public IRegion getContinuousSourceRange(final ISourceStructElement[] elements) { if (elements == null || elements.length == 0) { return null; } final ISourceUnit su = elements[0].getSourceUnit(); if (su == null) { return null; } final AbstractDocument doc = su.getDocument(null); if (doc == null) { return null; } // check if no other code is between the elements // and create one single range including comments at line end try { final BasicHeuristicTokenScanner scanner = getScanner(su); scanner.configure(doc); final int start = elements[0].getSourceRange().getOffset(); int end = elements[0].getSourceRange().getOffset() + elements[0].getSourceRange().getLength(); for (int i = 1; i < elements.length; i++) { if (elements[i].getSourceUnit() != su) { return null; } final int elementStart = elements[i].getSourceRange().getOffset(); final int elementEnd = elementStart + elements[i].getSourceRange().getLength(); if (elementEnd <= end) { continue; } int match; while (end < elementStart && (match = scanner.findAnyNonBlankForward(end, elementStart, true)) >= 0) { final ITypedRegion partition = doc.getPartition(scanner.getDocumentPartitioning(), match, false); if (isCommentContent(partition)) { end = partition.getOffset() + partition.getLength(); } else { return null; } } end = elementEnd; } final IRegion lastLine = doc.getLineInformationOfOffset(end); final int match = scanner.findAnyNonBlankForward(end, lastLine.getOffset()+lastLine.getLength(), true); if (match >= 0) { final ITypedRegion partition = doc.getPartition(scanner.getDocumentPartitioning(), match, false); if (isCommentContent(partition)) { end = partition.getOffset() + partition.getLength(); } } return new Region(start, end-start); } catch (final BadPartitioningException e) { } catch (final BadLocationException e) { } return null; } public String getSourceCodeStringedTogether(final ISourceStructElement[] sourceElements, final IProgressMonitor monitor) throws CoreException { return getSourceCodeStringedTogether(new ElementSet((Object[]) sourceElements), monitor); } public String getSourceCodeStringedTogether(final ElementSet sourceElements, final IProgressMonitor monitor) throws CoreException { final SubMonitor progress = SubMonitor.convert(monitor, sourceElements.getElementCount() * 2); ISourceUnit lastUnit = null; BasicHeuristicTokenScanner scanner = null; try { sourceElements.removeElementsWithAncestorsOnList(); Collections.sort(sourceElements.getModelElements(), getModelElementComparator()); final String lineDelimiter = TextUtil.getPlatformLineDelimiter(); AbstractDocument doc = null; final List<IModelElement> modelElements = sourceElements.getModelElements(); int todo = modelElements.size(); final StringBuilder sb = new StringBuilder(todo*100); final List<String> codeFragments = new ArrayList<>(); for (final IModelElement element : modelElements) { final ISourceUnit su = LTKUtil.getSourceUnit(element); if (su != lastUnit) { if (lastUnit != null) { progress.setWorkRemaining(todo*2); lastUnit.disconnect(progress.newChild(1)); lastUnit = null; } su.connect(progress.newChild(1)); lastUnit = su; scanner = getScanner(su); doc = su.getDocument(monitor); } getSourceCode((ISourceElement) element, doc, scanner, codeFragments); for (final String s : codeFragments) { sb.append(s); sb.append(lineDelimiter); } codeFragments.clear(); todo--; } return sb.toString(); } catch (final BadLocationException e) { throw new CoreException(failDocAnalyzation(e)); } catch (final BadPartitioningException e) { throw new CoreException(failDocAnalyzation(e)); } finally { if (lastUnit != null) { progress.setWorkRemaining(1); lastUnit.disconnect(progress.newChild(1)); lastUnit = null; } } } protected void getSourceCode(final ISourceElement element, final AbstractDocument doc, final BasicHeuristicTokenScanner scanner, final List<String> codeFragments) throws BadLocationException, BadPartitioningException { final IRegion range = expandElementRange(element, doc, scanner); if (range != null && range.getLength() > 0) { codeFragments.add(doc.get(range.getOffset(), range.getLength())); } } public IRegion expandElementRange(final ISourceElement element, final AbstractDocument document, final BasicHeuristicTokenScanner scanner) throws BadLocationException, BadPartitioningException { final IRegion sourceRange = element.getSourceRange(); int start = sourceRange.getOffset(); int end = start + sourceRange.getLength(); final IRegion docRange = element.getDocumentationRange(); if (docRange != null) { if (docRange.getOffset() < start) { start = docRange.getOffset(); } if (docRange.getOffset()+docRange.getLength() > end) { end = docRange.getOffset()+docRange.getLength(); } } return expandSourceRange(start, end, document, scanner); } protected IRegion expandSourceRange(final int start, int end, final AbstractDocument doc, final BasicHeuristicTokenScanner scanner) throws BadLocationException, BadPartitioningException { scanner.configure(doc); IRegion lastLineInfo; int match; lastLineInfo = doc.getLineInformationOfOffset(end); match = scanner.findAnyNonBlankForward(end, lastLineInfo.getOffset()+lastLineInfo.getLength(), true); if (match >= 0) { final ITypedRegion partition = doc.getPartition(scanner.getDocumentPartitioning(), match, false ); if (isCommentContent(partition)) { end = partition.getOffset() + partition.getLength(); } } final int checkLine = doc.getLineOfOffset(end)+1; if (checkLine < doc.getNumberOfLines()) { final IRegion checkLineInfo = doc.getLineInformation(checkLine); match = scanner.findAnyNonBlankForward( end, checkLineInfo.getOffset()+checkLineInfo.getLength(), true); if (match < 0) { end = checkLineInfo.getOffset()+checkLineInfo.getLength(); } } return new Region(start, end-start); } public IRegion expandWhitespaceBlock(final AbstractDocument document, final IRegion region, final BasicHeuristicTokenScanner scanner) throws BadLocationException { scanner.configure(document); final int firstLine = document.getLineOfOffset(region.getOffset()); int lastLine = document.getLineOfOffset(region.getOffset()+region.getLength()); if (lastLine > firstLine && document.getLineOffset(lastLine) == region.getOffset()+region.getLength()) { lastLine--; } int result; final int min = document.getLineOffset(firstLine); final int max = document.getLineOffset(lastLine)+document.getLineLength(lastLine); result = scanner.findAnyNonBlankForward(region.getOffset()+region.getLength(), max, true); final int end = (result >= 0) ? result : max; result = scanner.findAnyNonBlankBackward(region.getOffset(), min, true); if (result >= 0) { return new Region(result+1, end-(result+1)); } else { return new Region(min, end-min); } } public boolean canDelete(final ElementSet elements) { if (elements.getInitialObjects().size() == 0) { return false; } if (!elements.isOK()) { return false; } for (final IModelElement element : elements.getModelElements()) { if (!canDelete(element)) { return false; } } for (final IResource element : elements.getResources()) { if (!canDelete(element)) { return false; } } return true; } public boolean canDelete(final IModelElement element) { if (!element.exists()) { return false; } // if ((element.getElementType() & IModelElement.MASK_C1) == IModelElement.PROJECT) { // return false; // } if (!isSupportedModelType(element.getModelTypeId())) { return false; } if (element.isReadOnly()) { return false; } return true; } public boolean canDelete(final IResource resource) { if (!resource.exists() || resource.isPhantom()) { return false; } if (resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT) { return false; } if (resource.getParent() != null) { final ResourceAttributes attributes = resource.getParent().getResourceAttributes(); if (attributes != null && attributes.isReadOnly()) { return false; } } return true; } public boolean canInsert(final ElementSet elements, final RefactoringDestination to) { if (to.getInitialObjects().get(0) instanceof ISourceElement) { return canInsert(elements, (ISourceElement) to.getInitialObjects().get(0), to.getPosition()); } return false; } protected boolean canInsert(final ElementSet elements, final ISourceElement to, final RefactoringDestination.Position pos) { if (elements.getInitialObjects().size() == 0) { return false; } if (!elements.isOK()) { return false; } if (!canInsertTo(to)) { return false; } for (final IModelElement element : elements.getModelElements()) { if (!canInsert(element)) { return false; } } for (final IResource element : elements.getResources()) { // if (!canInsert(element, parent) { return false; // } } return !elements.includes(to); } protected boolean canInsert(final IModelElement element) { if (!element.exists()) { return false; } if (!isSupportedModelType(element.getModelTypeId())) { return false; } return true; } public boolean canInsertTo(final RefactoringDestination destination) { if (destination.getModelElements().size() != 1) { return false; } return canInsertTo(destination.getModelElements().get(0)); } protected boolean canInsertTo(final IModelElement element) { if (!element.exists()) { return false; } if (!isSupportedModelType(element.getModelTypeId())) { return false; } if (element.isReadOnly()) { return false; } return true; } public void checkInitialToModify(final RefactoringStatus result, final ElementSet elements) { final Set<IResource> resources = new HashSet<>(); resources.addAll(elements.getResources()); for(final IModelElement element : elements.getModelElements()) { final ISourceUnit su = LTKUtil.getSourceUnit(element); if (su instanceof IWorkspaceSourceUnit) { resources.add(((IWorkspaceSourceUnit) su).getResource()); continue; } result.addFatalError(Messages.Check_ElementNotInWS_message); return; } result.merge(RefactoringStatus.create( Resources.checkInSync(resources.toArray(new IResource[resources.size()])) )); } public void checkFinalToModify(final RefactoringStatus result, final ElementSet elements, final IProgressMonitor monitor) { final Set<IResource> resources = new HashSet<>(); resources.addAll(elements.getResources()); for(final IModelElement element : elements.getModelElements()) { final ISourceUnit su = LTKUtil.getSourceUnit(element); if (su instanceof IWorkspaceSourceUnit) { resources.add(((IWorkspaceSourceUnit) su).getResource()); continue; } result.addFatalError(Messages.Check_ElementNotInWS_message); return; } final IResource[] array = resources.toArray(new IResource[resources.size()]); result.merge(RefactoringStatus.create(Resources.checkInSync(array))); result.merge(RefactoringStatus.create(Resources.makeCommittable(array, IWorkspace.VALIDATE_PROMPT))); } public void checkFinalToDelete(final RefactoringStatus result, final ElementSet elements) throws CoreException { for (final IModelElement element : elements.getModelElements()) { checkFinalToDelete(result, element); } for (final IResource element : elements.getResources()) { checkFinalToDelete(result, element); } } public void checkFinalToDelete(final RefactoringStatus result, final IResource element) throws CoreException { if (element.getType() == IResource.FILE) { warnIfDirty(result, (IFile) element); return; } else { element.accept(new IResourceVisitor() { @Override public boolean visit(final IResource visitedResource) throws CoreException { if (visitedResource instanceof IFile) { warnIfDirty(result, (IFile) visitedResource); } return true; } }, IResource.DEPTH_INFINITE, false); } } public void checkFinalToDelete(final RefactoringStatus result, final IModelElement element) throws CoreException { if ((element.getElementType() & IModelElement.MASK_C2) == IModelElement.C2_SOURCE_FILE) { if (element instanceof IWorkspaceSourceUnit) { checkFinalToDelete(result, ((IWorkspaceSourceUnit) element).getResource()); } } else if ((element.getElementType() & IModelElement.MASK_C1) == IModelElement.C1_BUNDLE && element instanceof ISourceStructElement) { final List<? extends IModelElement> children = ((ISourceStructElement) element).getSourceChildren(null); for (final IModelElement child : children) { checkFinalToDelete(result, child); } } } public void warnIfDirty(final RefactoringStatus result, final IFile file) { if (file == null || !file.exists()) { return; } final ITextFileBuffer buffer = FileBuffers.getTextFileBufferManager().getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); if (buffer != null && buffer.isDirty()) { if (buffer.isStateValidated() && buffer.isSynchronized()) { result.addWarning(NLS.bind( Messages.Check_FileUnsavedChanges_message, FileUtil.getFileUtil(file).getLabel()) ); } else { result.addFatalError(NLS.bind( Messages.Check_FileUnsavedChanges_message, FileUtil.getFileUtil(file).getLabel()) ); } } } public boolean confirmDeleteOfReadOnlyElements(final ElementSet elements, final Object queries) throws CoreException { // TODO add query support return hasReadOnlyElements(elements); } public boolean hasReadOnlyElements(final ElementSet elements) throws CoreException { for (final IResource element : elements.getResources()) { if (hasReadOnlyElements(element)) { return true; } } for (final IModelElement element : elements.getModelElements()) { if (hasReadOnlyElements(element)) { return true; } } return false; } public boolean hasReadOnlyElements(final IResource element) throws CoreException { if (isReadOnly(element)) { return true; } if (element instanceof IContainer) { final IResource[] members = ((IContainer) element).members(false); for (final IResource member : members) { if (hasReadOnlyElements(member)) { return true; } } } return false; } public boolean hasReadOnlyElements(final IModelElement element) throws CoreException { final ISourceUnit su = LTKUtil.getSourceUnit(element); IResource resource = null; if (su instanceof IWorkspaceSourceUnit) { resource = ((IWorkspaceSourceUnit) su).getResource(); } if (resource == null) { resource = (IResource) element.getAdapter(IResource.class); } if (resource != null) { return hasReadOnlyElements(resource); } return false; } public boolean isReadOnly(final IResource element) { final ResourceAttributes attributes = element.getResourceAttributes(); if (attributes != null) { return attributes.isReadOnly(); } return false; } public void addParticipantsToDelete(final ElementSet elementsToDelete, final List<RefactoringParticipant> list, final RefactoringStatus status, final RefactoringProcessor processor, final SharableParticipants shared) throws CoreException { final String[] natures = ElementSet.getAffectedProjectNatures(elementsToDelete); final DeleteArguments arguments = new DeleteArguments(); for (final IResource resource : elementsToDelete.getResources()) { final DeleteParticipant[] deletes = ParticipantManager.loadDeleteParticipants(status, processor, resource, arguments, natures, shared); list.addAll(Arrays.asList(deletes)); } for (final IResource resource : elementsToDelete.getResourcesOwnedByElements()) { final DeleteParticipant[] deletes = ParticipantManager.loadDeleteParticipants(status, processor, resource, arguments, natures, shared); list.addAll(Arrays.asList(deletes)); } for (final IModelElement element : elementsToDelete.getModelElements()) { final DeleteParticipant[] deletes = ParticipantManager.loadDeleteParticipants(status, processor, element, arguments, natures, shared); list.addAll(Arrays.asList(deletes)); } } public void addParticipantsToMove(final ElementSet elementsToMove, final RefactoringDestination destination, final List<RefactoringParticipant> list, final RefactoringStatus status, final RefactoringProcessor processor, final SharableParticipants shared, final ReorgExecutionLog executionLog) throws CoreException { final String[] natures = ElementSet.getAffectedProjectNatures( ImCollections.newList(elementsToMove, destination) ); final MoveArguments mArguments = new MoveArguments(destination.getModelElements().get(0), false ); // for (final IResource resource : elementsToCopy.getResources()) { // final MoveParticipant[] deletes = ParticipantManager.loadMoveParticipants(status, // processor, resource, arguments, natures, shared ); // list.addAll(Arrays.asList(deletes)); // } // for (final IResource resource : elementsToCopy.getResourcesOwnedByElements()) { // final MoveParticipant[] deletes = ParticipantManager.loadMoveParticipants(status, // processor, resource, arguments, natures, shared ); // list.addAll(Arrays.asList(deletes)); // } for (final IModelElement element : elementsToMove.getModelElements()) { final MoveParticipant[] deletes = ParticipantManager.loadMoveParticipants(status, processor, element, mArguments, natures, shared ); list.addAll(Arrays.asList(deletes)); } } public void addParticipantsToCopy(final ElementSet elementsToCopy, final RefactoringDestination destination, final List<RefactoringParticipant> list, final RefactoringStatus status, final RefactoringProcessor processor, final SharableParticipants shared, final ReorgExecutionLog executionLog) throws CoreException { final String[] natures = ElementSet.getAffectedProjectNatures( ImCollections.newList(elementsToCopy, destination) ); final CopyArguments mArguments = new CopyArguments(destination.getModelElements().get(0), executionLog ); // for (final IResource resource : elementsToCopy.getResources()) { // final CopyParticipant[] deletes = ParticipantManager.loadCopyParticipants(status, // processor, resource, arguments, natures, shared ); // list.addAll(Arrays.asList(deletes)); // } // for (final IResource resource : elementsToCopy.getResourcesOwnedByElements()) { // final CopyParticipant[] deletes = ParticipantManager.loadCopyParticipants(status, // processor, resource, arguments, natures, shared); // list.addAll(Arrays.asList(deletes)); // } for (final IModelElement element : elementsToCopy.getModelElements()) { final CopyParticipant[] deletes = ParticipantManager.loadCopyParticipants(status, processor, element, mArguments, natures, shared); list.addAll(Arrays.asList(deletes)); } } public void buildDeltaToDelete(final ElementSet elements, final IResourceChangeDescriptionFactory resourceDelta) { for (final IResource resource : elements.getResources()) { resourceDelta.delete(resource); } for (final IResource resource : elements.getResourcesOwnedByElements()) { resourceDelta.delete(resource); } for (final IFile file : elements.getFilesContainingElements()) { resourceDelta.change(file); } } public void buildDeltaToModify(final ElementSet elements, final IResourceChangeDescriptionFactory resourceDelta) { for (final IResource resource : elements.getResources()) { if (resource instanceof IFile) { resourceDelta.change((IFile) resource); } } for (final IResource resource : elements.getResourcesOwnedByElements()) { if (resource instanceof IFile) { resourceDelta.change((IFile) resource); } } for (final IFile file : elements.getFilesContainingElements()) { resourceDelta.change(file); } } /** * @param changeName the name of the change * @param resources the resources to delete * @param manager the text change manager * @return the created change * @throws CoreException */ public Change createChangeToDelete(final String changeName, final ElementSet elementsToDelete, final TextChangeManager manager, final IProgressMonitor monitor) throws CoreException { final SubMonitor progress = SubMonitor.convert(monitor, 1); final CompositeChange result = new CompositeChange(changeName); addChangesToDelete(result, elementsToDelete, manager, progress.newChild(1)); result.addAll(manager.getAllChanges()); return result; } public Change createChangeToMove(final String changeName, final ElementSet elementsToMove, final RefactoringDestination destination, final TextChangeManager manager, final IProgressMonitor monitor) throws CoreException { final SubMonitor progress = SubMonitor.convert(monitor, 3); final CompositeChange result = new CompositeChange(changeName); final String code = getSourceCodeStringedTogether(elementsToMove, progress.newChild(1)); addChangesToDelete(result, elementsToMove, manager, progress.newChild(1)); addChangesToInsert(result, code, destination, manager, progress.newChild(1)); result.addAll(manager.getAllChanges()); return result; } public Change createChangeToCopy(final String changeName, final ElementSet elementsToMove, final RefactoringDestination destination, final TextChangeManager manager, final IProgressMonitor monitor) throws CoreException { final SubMonitor progress = SubMonitor.convert(monitor, 2); final CompositeChange result = new CompositeChange(changeName); final String code = getSourceCodeStringedTogether(elementsToMove, progress.newChild(1)); addChangesToInsert(result, code, destination, manager, progress.newChild(1)); result.addAll(manager.getAllChanges()); return result; } public Change createChangeToInsert(final String changeName, final String code, final RefactoringDestination destination, final TextChangeManager manager, final IProgressMonitor monitor) throws CoreException { assert (changeName != null && code != null && destination != null && manager != null); final SubMonitor progress = SubMonitor.convert(monitor); final CompositeChange result = new CompositeChange(changeName); addChangesToInsert(result, code, destination, manager, progress); result.addAll(manager.getAllChanges()); return result; } protected void addChangesToDelete(final CompositeChange result, final ElementSet elements, final TextChangeManager manager, final SubMonitor progress) throws CoreException { for (final IResource resource : elements.getResources()) { result.add(createChangeToDelete(elements, resource)); } final Map<ISourceUnit, List<IModelElement>> suSubChanges = new HashMap<>(); for (final IModelElement element : elements.getModelElements()) { final IResource resource = elements.getOwningResource(element); if (resource != null) { result.add(createChangeToDelete(elements, resource)); } else { final ISourceUnit su = LTKUtil.getSourceUnit(element); List<IModelElement> list = suSubChanges.get(su); if (list == null) { list = new ArrayList<>(1); suSubChanges.put(su, list); } list.add(element); } } if (!suSubChanges.isEmpty()) { progress.setWorkRemaining(suSubChanges.size()*3); for (final Map.Entry<ISourceUnit, List<IModelElement>> suChanges : suSubChanges.entrySet()) { createChangeToDelete(elements, suChanges.getKey(), suChanges.getValue(), manager, progress); } } } private void createChangeToDelete(final ElementSet elements, final ISourceUnit su, final List<IModelElement> elementsInUnit, final TextChangeManager manager, final SubMonitor progress) throws CoreException { if (!(su instanceof IWorkspaceSourceUnit) || ((IWorkspaceSourceUnit) su).getResource().getType() != IResource.FILE ) { throw new IllegalArgumentException(); } su.connect(progress.newChild(1)); try { final MultiTextEdit rootEdit = getRootEdit(manager, su); final BasicHeuristicTokenScanner scanner = getScanner(su); final AbstractDocument document = su.getDocument(null); for (final IModelElement element : elementsInUnit) { final ISourceElement member = (ISourceElement) element; final IRegion sourceRange = expandElementRange(member, document, scanner); final DeleteEdit edit = new DeleteEdit(sourceRange.getOffset(), sourceRange.getLength()); rootEdit.addChild(edit); } progress.worked(1); } catch (final BadLocationException e) { throw new CoreException(failCreation(e)); } catch (final BadPartitioningException e) { throw new CoreException(failCreation(e)); } finally { su.disconnect(progress.newChild(1)); } } protected Change createChangeToDelete(final ElementSet elements, final IModelElement element) throws CoreException { final IResource resource = elements.getOwningResource(element); if (resource != null) { return createChangeToDelete(elements, resource); } throw new IllegalStateException(); } protected Change createChangeToDelete(final ElementSet elements, final IResource resource) { if (resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT) { throw new IllegalStateException(); } return new DeleteResourceChange(resource.getFullPath(), true); } protected Change createChangeToDelete(final ElementSet elements, final ISourceUnit su) throws CoreException { if (su instanceof IWorkspaceSourceUnit) { return createChangeToDelete(elements, ((IWorkspaceSourceUnit) su).getResource()); } throw new IllegalStateException(); } protected void addChangesToInsert(final CompositeChange result, final String code, final RefactoringDestination destination, final TextChangeManager manager, final SubMonitor progress) throws CoreException { final ISourceElement element = (ISourceElement) destination.getModelElements().get(0); final ISourceUnit su = LTKUtil.getSourceUnit(element); progress.setWorkRemaining(3); createChangeToInsert(su, code, element, destination, manager, progress); } private void createChangeToInsert(final ISourceUnit su, final String code, final ISourceElement desElement, final RefactoringDestination destination, final TextChangeManager manager, final SubMonitor progress) throws CoreException { if (!(su instanceof IWorkspaceSourceUnit) || ((IWorkspaceSourceUnit) su).getResource().getType() != IResource.FILE ) { throw new IllegalArgumentException(); } su.connect(progress.newChild(1)); try { final AbstractDocument document = su.getDocument(progress.newChild(1)); final BasicHeuristicTokenScanner scanner = getScanner(su); final int offset; if (destination.getPosition() == RefactoringDestination.Position.AT) { offset = destination.getOffset(); } else { offset = getInsertionOffset(document, desElement, destination.getPosition(), scanner); } final MultiTextEdit rootEdit = getRootEdit(manager, su); final InsertEdit edit = new InsertEdit(offset, code); rootEdit.addChild(edit); ((SourceUnitChange) manager.get(su)).setInsertPosition(new Position(edit.getOffset())); progress.worked(1); } catch (final BadLocationException e) { throw new CoreException(failCreation(e)); } catch (final BadPartitioningException e) { throw new CoreException(failCreation(e)); } finally { su.disconnect(progress.newChild(1)); } } protected int getInsertionOffset(final AbstractDocument document, final ISourceElement element, final RefactoringDestination.Position pos, final BasicHeuristicTokenScanner scanner) throws BadLocationException, BadPartitioningException { final IRegion range = expandElementRange(element, document, scanner); if (pos == RefactoringDestination.Position.ABOVE) { final int offset = range.getOffset(); return offset; } else { int offset = range.getOffset()+range.getLength(); final int line = document.getLineOfOffset(offset); final IRegion lineInformation = document.getLineInformation(line); if (offset == lineInformation.getOffset() + lineInformation.getLength()) { offset += document.getLineDelimiter(line).length(); } return offset; } } private MultiTextEdit getRootEdit(final TextChangeManager manager, final ISourceUnit su) { final TextFileChange textFileChange = manager.get(su); if (su.getWorkingContext() == LTK.EDITOR_CONTEXT) { textFileChange.setSaveMode(TextFileChange.LEAVE_DIRTY); } if (textFileChange.getEdit() == null) { textFileChange.setEdit(new MultiTextEdit()); } return (MultiTextEdit) textFileChange.getEdit(); } protected IStatus failDocAnalyzation(final Throwable e) { return new Status(IStatus.ERROR, LTK.PLUGIN_ID, Messages.Common_error_AnalyzingSourceDocument_message, e); } protected IStatus failCreation(final Throwable e) { return new Status(IStatus.ERROR, LTK.PLUGIN_ID, Messages.Common_error_CreatingElementChange_message, e); } }