/******************************************************************************* * Copyright (c) 2000, 2016 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 * *******************************************************************************/ package org.eclipse.dltk.internal.core; import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.dltk.core.DLTKContentTypeManager; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.DLTKLanguageManager; import org.eclipse.dltk.core.IDLTKLanguageToolkit; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.IModelElementDelta; import org.eclipse.dltk.core.IModelStatus; import org.eclipse.dltk.core.IModelStatusConstants; import org.eclipse.dltk.core.IProjectFragment; import org.eclipse.dltk.core.IScriptFolder; import org.eclipse.dltk.core.IScriptProject; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.internal.core.util.Messages; import org.eclipse.dltk.internal.core.util.Util; /** * This operation copies/moves/renames a collection of resources from their * current container to a new container, optionally renaming the elements. * <p> * Notes: * <ul> * <li>If there is already an resource with the same name in the new container, * the operation either overwrites or aborts, depending on the collision policy * setting. The default setting is abort. * * <li>When a compilation unit is copied to a new package, the package * declaration in the compilation unit is automatically updated. * * <li>The collection of elements being copied must all share the same type of * container. * * <li>This operation can be used to copy and rename elements within the same * container. * * <li>This operation only copies compilation units and package fragments. It * does not copy package fragment roots - a platform operation must be used for * that. * </ul> * */ public class CopyResourceElementsOperation extends MultiOperation { /** * The list of new resources created during this operation. */ protected ArrayList createdElements; /** * Table specifying deltas for elements being copied/moved/renamed. Keyed by * elements' project(s), and values are the corresponding deltas. */ protected Map deltasPerProject = new HashMap(1); // /** // * The <code>ASTParser</code> used to manipulate the source code of // * <code>ISourceModule</code>. // */ // protected ASTParser parser; /** * When executed, this operation will copy the given resources to the given * container. */ public CopyResourceElementsOperation(IModelElement[] resourcesToCopy, IModelElement destContainer, boolean force) { this(resourcesToCopy, new IModelElement[] { destContainer }, force); } /** * When executed, this operation will copy the given resources to the given * containers. The resources and destination containers must be in the * correct order. If there is > 1 destination, the number of destinations * must be the same as the number of resources being copied/moved. */ public CopyResourceElementsOperation(IModelElement[] resourcesToCopy, IModelElement[] destContainers, boolean force) { super(resourcesToCopy, destContainers, force); // initializeASTParser(); } // private void initializeASTParser() { // this.parser = ASTParser.newParser(AST.JLS3); // } /** * Returns the children of <code>source</code> which are affected by this * operation. If <code>source</code> is a <code>K_SOURCE</code>, these are * the <code>.java</code> files, if it is a <code>K_BINARY</code>, they are * the <code>.class</code> files. */ private IResource[] collectResourcesOfInterest(IScriptFolder source) throws ModelException { IModelElement[] children = source.getChildren(); int childOfInterest = IModelElement.SOURCE_MODULE; // if (source.getKind() == IProjectFragment.K_BINARY) { // childOfInterest = IModelElement.CLASS_FILE; // } ArrayList correctKindChildren = new ArrayList(children.length); for (int i = 0; i < children.length; i++) { IModelElement child = children[i]; if (child.getElementType() == childOfInterest) { correctKindChildren.add(child.getResource()); } } // Gather non-java resources Object[] nonScriptResources = source.getForeignResources(); int actualNonScriptResourceCount = 0; for (int i = 0, max = nonScriptResources.length; i < max; i++) { if (nonScriptResources[i] instanceof IResource) actualNonScriptResourceCount++; } IResource[] actualNonScriptResources = new IResource[actualNonScriptResourceCount]; for (int i = 0, max = nonScriptResources.length, index = 0; i < max; i++) { if (nonScriptResources[i] instanceof IResource) actualNonScriptResources[index++] = (IResource) nonScriptResources[i]; } if (actualNonScriptResourceCount != 0) { int correctKindChildrenSize = correctKindChildren.size(); IResource[] result = new IResource[correctKindChildrenSize + actualNonScriptResourceCount]; correctKindChildren.toArray(result); System.arraycopy(actualNonScriptResources, 0, result, correctKindChildrenSize, actualNonScriptResourceCount); return result; } else { IResource[] result = new IResource[correctKindChildren.size()]; correctKindChildren.toArray(result); return result; } } /** * Creates any destination package fragment(s) which do not exists yet. * Return true if a read-only package fragment has been found among package * fragments, false otherwise */ private boolean createNeededScriptFolders(IContainer sourceFolder, IProjectFragment root, IPath newFragName, boolean moveFolder) throws ModelException { boolean containsReadOnlyScriptFolder = false; IContainer parentFolder = (IContainer) root.getResource(); ModelElementDelta projectDelta = null; IPath sideEffectPackageName = Path.EMPTY; for (int i = 0; i < newFragName.segmentCount(); i++) { String subFolderName = newFragName.segment(i); sideEffectPackageName = sideEffectPackageName.append(subFolderName); IResource subFolder = parentFolder.findMember(subFolderName); if (subFolder == null) { // create deepest folder only if not a move (folder will be // moved in processScriptFolderResource) if (!(moveFolder && i == newFragName.segmentCount() - 1)) { createFolder(parentFolder, subFolderName, force); } parentFolder = parentFolder.getFolder(new Path(subFolderName)); sourceFolder = sourceFolder.getFolder(new Path(subFolderName)); if (Util.isReadOnly(sourceFolder)) { containsReadOnlyScriptFolder = true; } IScriptFolder sideEffectPackage = root .getScriptFolder(sideEffectPackageName); if (i < newFragName.segmentCount() - 1 // all but the last one // are side effect // packages && !Util.isExcluded(parentFolder, root)) { if (projectDelta == null) { projectDelta = getDeltaFor(root.getScriptProject()); } projectDelta.added(sideEffectPackage); } createdElements.add(sideEffectPackage); } else { parentFolder = (IContainer) subFolder; } } return containsReadOnlyScriptFolder; } /** * Returns the <code>ScriptElementDelta</code> for * <code>scriptProject</code>, creating it and putting it in * <code>fDeltasPerProject</code> if it does not exist yet. */ private ModelElementDelta getDeltaFor(IScriptProject scriptProject) { ModelElementDelta delta = (ModelElementDelta) deltasPerProject .get(scriptProject); if (delta == null) { delta = new ModelElementDelta(scriptProject); deltasPerProject.put(scriptProject, delta); } return delta; } /** * @see MultiOperation */ @Override protected String getMainTaskName() { return Messages.operation_copyResourceProgress; } /** * Sets the deltas to register the changes resulting from this operation for * this source element and its destination. If the operation is a cross * project operation * <ul> * <li>On a copy, the delta should be rooted in the dest project * <li>On a move, two deltas are generated * <ul> * <li>one rooted in the source project * <li>one rooted in the destination project * </ul> * </ul> * If the operation is rooted in a single project, the delta is rooted in * that project * */ protected void prepareDeltas(IModelElement sourceElement, IModelElement destinationElement, boolean isMove) { if (Util.isExcluded(sourceElement) || Util.isExcluded(destinationElement)) return; IScriptProject destProject = destinationElement.getScriptProject(); if (isMove) { IScriptProject sourceProject = sourceElement.getScriptProject(); getDeltaFor(sourceProject).movedFrom(sourceElement, destinationElement); getDeltaFor(destProject).movedTo(destinationElement, sourceElement); } else { getDeltaFor(destProject).added(destinationElement); } } /** * Copies/moves a compilation unit with the name <code>newCUName</code> to * the destination package.<br> * The package statement in the compilation unit is updated if necessary. * The main type of the compilation unit is renamed if necessary. * * @exception ScriptModelException * if the operation is unable to complete */ private void processSourceModuleResource(ISourceModule source, IScriptFolder dest) throws ModelException { String newCUName = getNewNameFor(source); String destName = (newCUName != null) ? newCUName : source .getElementName(); // ASTRewrite rewrite = updateContent(source, dest, newCUName); // null // if unchanged // TODO (frederic) remove when bug 67606 will be fixed (bug 67823) // store encoding (fix bug 66898) IFile sourceResource = (IFile) source.getResource(); // String sourceEncoding = null; // try { // if( sourceResource != null ) { // sourceEncoding = sourceResource.getCharset(false); // } // } catch (CoreException ce) { // // no problem, use default encoding // } // end todo // copy resource IContainer destFolder = (IContainer) dest.getResource(); // can be an // IFolder // or an // IProject IFile destFile = destFolder.getFile(new Path(destName)); SourceModule destCU = new SourceModule((ModelElement) dest, destName, DefaultWorkingCopyOwner.PRIMARY); if (sourceResource == null || !destFile.equals(sourceResource)) { try { if (!destCU.isWorkingCopy()) { if (destFile.exists()) { if (this.force) { // we can remove it deleteResource(destFile, IResource.KEEP_HISTORY); destCU.close(); // ensure the in-memory buffer for // the dest CU is closed } else { // abort throw new ModelException(new ModelStatus( IModelStatusConstants.NAME_COLLISION, Messages.bind( Messages.status_nameCollision, destFile.getFullPath().toString()))); } } int flags = this.force ? IResource.FORCE : IResource.NONE; if (this.isMove()) { flags |= IResource.KEEP_HISTORY; if (sourceResource != null) { sourceResource.move(destFile.getFullPath(), flags, getSubProgressMonitor(1)); } else { if (DLTKCore.DEBUG) { System.err .println("TODO: Add correct status message here..."); //$NON-NLS-1$ } throw new ModelException(new ModelStatus( IModelStatusConstants.NAME_COLLISION, Messages.bind( Messages.status_invalidResource, destFile.getFullPath().toString()))); } } else { // if (rewrite != null) flags |= IResource.KEEP_HISTORY; if (sourceResource == null) { ByteArrayInputStream bais = new ByteArrayInputStream( new byte[0]); destFile.create(bais, IResource.FORCE, getSubProgressMonitor(1)); destCU.getBuffer().setContents( source.getSourceAsCharArray()); destCU.save(getSubProgressMonitor(1), true); } else { sourceResource.copy(destFile.getFullPath(), flags, getSubProgressMonitor(1)); } } this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); } else { destCU.getBuffer().setContents( source.getBuffer().getContents()); } } catch (ModelException e) { throw e; } catch (CoreException e) { throw new ModelException(e); } // update new resource content // if (rewrite != null){ // boolean wasReadOnly = destFile.isReadOnly(); // try { // saveContent(dest, destName, rewrite, sourceEncoding, destFile); // } catch (CoreException e) { // if (e instanceof ModelException) throw (ModelException) e; // throw new ModelException(e); // } finally { // Util.setReadOnly(destFile, wasReadOnly); // } // } // register the correct change deltas prepareDeltas(source, destCU, isMove()); if (newCUName != null) { // the main type has been renamed if (DLTKCore.DEBUG) { System.err.println("TODO: Add remove extensions here..."); //$NON-NLS-1$ } String oldName = /* Util.getNameWithoutScriptLikeExtension( */source .getElementName();// ); String newName = /* Util.getNameWithoutScriptLikeExtension( */newCUName;// ) // ; prepareDeltas(source.getType(oldName), destCU.getType(newName), isMove()); } } else { if (!this.force) { throw new ModelException(new ModelStatus( IModelStatusConstants.NAME_COLLISION, Messages.bind( Messages.status_nameCollision, destFile .getFullPath().toString()))); } // update new resource content // in case we do a saveas on the same resource we have to simply // update the contents // see http://dev.eclipse.org/bugs/show_bug.cgi?id=9351 // try { // if (rewrite != null){ // saveContent(dest, destName, rewrite, sourceEncoding, destFile); // } // } catch (CoreException e) { // if (e instanceof ModelException) throw (ModelException) e; // throw new ModelException(e); // } } } /** * Process all of the changed deltas generated by this operation. */ protected void processDeltas() { for (Iterator deltas = this.deltasPerProject.values().iterator(); deltas .hasNext();) { addDelta((IModelElementDelta) deltas.next()); } } /** * @see MultiOperation This method delegates to * <code>processSourceModuleResource</code> or * <code>processScriptFolderResource</code>, depending on the type of * <code>element</code>. */ @Override protected void processElement(IModelElement element) throws ModelException { IModelElement dest = getDestinationParent(element); switch (element.getElementType()) { case IModelElement.SOURCE_MODULE: processSourceModuleResource((ISourceModule) element, (IScriptFolder) dest); createdElements.add(((IScriptFolder) dest).getSourceModule(element .getElementName())); break; case IModelElement.SCRIPT_FOLDER: processScriptFolderResource((IScriptFolder) element, (IProjectFragment) dest, getNewNameFor(element)); break; default: throw new ModelException(new ModelStatus( IModelStatusConstants.INVALID_ELEMENT_TYPES, element)); } } /** * @see MultiOperation Overridden to allow special processing of * <code>ScriptElementDelta</code>s and <code>fResultElements</code>. */ @Override protected void processElements() throws ModelException { createdElements = new ArrayList(elementsToProcess.length); try { super.processElements(); } catch (ModelException jme) { throw jme; } finally { resultElements = new IModelElement[createdElements.size()]; createdElements.toArray(resultElements); processDeltas(); } } /** * Copies/moves a package fragment with the name <code>newName</code> to the * destination package.<br> * * @exception ScriptModelException * if the operation is unable to complete */ private void processScriptFolderResource(IScriptFolder source, IProjectFragment root, String newName) throws ModelException { try { // String[] newFragName = (newName == null) ? source.path.segments() // : Util.getTrimmedSimpleNames(newName); IPath source_path = source.getPath().removeFirstSegments( source.getParent().getPath().segmentCount()); IPath newFragName = (newName == null) ? source_path : new Path( newName); IScriptFolder newFrag = root.getScriptFolder(newFragName); IResource[] resources = collectResourcesOfInterest(source); // if isMove() can we move the folder itself ? (see // http://bugs.eclipse.org/bugs/show_bug.cgi?id=22458) boolean shouldMoveFolder = isMove() && newFrag.getResource() != null && !newFrag.getResource().exists(); // if // new // pkg // fragment // exists, // it // is // an // override IFolder srcFolder = (IFolder) source.getResource(); IPath destPath = newFrag.getPath(); if (shouldMoveFolder) { // check if destination is not included in source if (srcFolder.getFullPath().isPrefixOf(destPath)) { shouldMoveFolder = false; } else { // check if there are no sub-packages IResource[] members = srcFolder.members(); for (int i = 0; i < members.length; i++) { if (members[i] instanceof IFolder) { shouldMoveFolder = false; break; } } } } boolean containsReadOnlySubScriptFolders = createNeededScriptFolders( (IContainer) source.getParent().getResource(), root, newFragName, shouldMoveFolder); boolean sourceIsReadOnly = Util.isReadOnly(srcFolder); // Process resources if (shouldMoveFolder) { // move underlying resource // TODO Revisit once bug 43044 is fixed if (sourceIsReadOnly) { Util.setReadOnly(srcFolder, false); } srcFolder.move(destPath, force, true /* keep history */, getSubProgressMonitor(1)); if (sourceIsReadOnly) { Util.setReadOnly(srcFolder, true); } this.setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); } else { // process the leaf resources if (resources.length > 0) { if (isRename()) { if (!destPath.equals(source.getPath())) { moveResources(resources, destPath); } } else if (isMove()) { // we need to delete this resource if this operation // wants to override existing resources for (int i = 0, max = resources.length; i < max; i++) { IResource destinationResource = ResourcesPlugin .getWorkspace().getRoot().findMember( destPath.append(resources[i] .getName())); if (destinationResource != null) { if (force) { deleteResource(destinationResource, IResource.KEEP_HISTORY); } else { throw new ModelException( new ModelStatus( IModelStatusConstants.NAME_COLLISION, Messages .bind( Messages.status_nameCollision, destinationResource .getFullPath() .toString()))); } } } moveResources(resources, destPath); } else { // we need to delete this resource if this operation // wants to override existing resources for (int i = 0, max = resources.length; i < max; i++) { IResource destinationResource = ResourcesPlugin .getWorkspace().getRoot().findMember( destPath.append(resources[i] .getName())); if (destinationResource != null) { if (force) { // we need to delete this resource if this // operation wants to override existing // resources deleteResource(destinationResource, IResource.KEEP_HISTORY); } else { throw new ModelException( new ModelStatus( IModelStatusConstants.NAME_COLLISION, Messages .bind( Messages.status_nameCollision, destinationResource .getFullPath() .toString()))); } } } copyResources(resources, destPath); } } } // Update package statement in compilation unit if needed if (!Util.equalArraysOrNull(new Object[] { newFragName }, new Object[] { source_path })) { // if package has been // renamed, update the // compilation units for (int i = 0; i < resources.length; i++) { String resourceName = resources[i].getName(); IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(newFrag); if (toolkit != null && DLTKContentTypeManager .isValidResourceForContentType(toolkit, resources[i])) { // we only consider potential compilation units ISourceModule cu = newFrag .getSourceModule(resourceName); if (Util.isExcluded(cu.getPath(), root, false/* * not a * folder */)) continue; if (DLTKCore.DEBUG) { System.err .println("TODO:Add source module modification code here..."); //$NON-NLS-1$ } // this.parser.setSource(cu); // SourceModule astCU = (SourceModule) // this.parser.createAST(this.progressMonitor); // AST ast = astCU.getAST(); // ASTRewrite rewrite = ASTRewrite.create(ast); // updatePackageStatement(astCU, newFragName, rewrite); // IDocument document = getDocument(cu); // TextEdit edits = rewrite.rewriteAST(document, null); // try { // edits.apply(document); // } catch (BadLocationException e) { // throw new ModelException(e, // IModelStatusConstants.INVALID_CONTENTS); // } cu.save(null, false); } } } // Discard empty old package (if still empty after the rename) boolean isEmpty = true; if (isMove() && srcFolder != null) { // delete remaining files in this package (.class file in the // case where Proj=src=bin) // in case of a copy updateReadOnlyScriptFoldersForMove((IContainer) source .getParent().getResource(), root, newFragName, sourceIsReadOnly); if (srcFolder.exists()) { IResource[] remaining = srcFolder.members(); for (int i = 0, length = remaining.length; i < length; i++) { IResource file = remaining[i]; if (file instanceof IFile) { if (Util.isReadOnly(file)) { Util.setReadOnly(file, false); } this.deleteResource(file, IResource.FORCE | IResource.KEEP_HISTORY); } else { isEmpty = false; } } } if (isEmpty) { IResource rootResource; // check if source is included in destination if (destPath.isPrefixOf(srcFolder.getFullPath())) { rootResource = newFrag.getResource(); } else { rootResource = source.getParent().getResource(); } // delete recursively empty folders deleteEmptyScriptFolder(source, false, rootResource); } } else if (containsReadOnlySubScriptFolders) { // in case of a copy updateReadOnlyScriptFoldersForCopy((IContainer) source .getParent().getResource(), root, newFragName); } // workaround for bug // https://bugs.eclipse.org/bugs/show_bug.cgi?id=24505 if (isEmpty && isMove() && !(Util.isExcluded(source) || Util.isExcluded(newFrag))) { IScriptProject sourceProject = source.getScriptProject(); getDeltaFor(sourceProject).movedFrom(source, newFrag); IScriptProject destProject = newFrag.getScriptProject(); getDeltaFor(destProject).movedTo(newFrag, source); } } catch (ModelException e) { throw e; } catch (CoreException ce) { throw new ModelException(ce); } } private void updateReadOnlyScriptFoldersForCopy(IContainer sourceFolder, IProjectFragment root, IPath newFragName) { IContainer parentFolder = (IContainer) root.getResource(); for (int i = 0, length = newFragName.segmentCount(); i < length; i++) { String subFolderName = newFragName.segment(i); parentFolder = parentFolder.getFolder(new Path(subFolderName)); sourceFolder = sourceFolder.getFolder(new Path(subFolderName)); if (sourceFolder.exists() && Util.isReadOnly(sourceFolder)) { Util.setReadOnly(parentFolder, true); } } } private void updateReadOnlyScriptFoldersForMove(IContainer sourceFolder, IProjectFragment root, IPath newFragName, boolean sourceFolderIsReadOnly) { IContainer parentFolder = (IContainer) root.getResource(); for (int i = 0, length = newFragName.segmentCount(); i < length; i++) { String subFolderName = newFragName.segment(i); parentFolder = parentFolder.getFolder(new Path(subFolderName)); sourceFolder = sourceFolder.getFolder(new Path(subFolderName)); if ((sourceFolder.exists() && Util.isReadOnly(sourceFolder)) || (i == length - 1 && sourceFolderIsReadOnly)) { Util.setReadOnly(parentFolder, true); // the source folder will be deleted anyway (move operation) Util.setReadOnly(sourceFolder, false); } } } /** * Possible failures: * <ul> * <li>NO_ELEMENTS_TO_PROCESS - no elements supplied to the operation * <li> * INDEX_OUT_OF_BOUNDS - the number of renamings supplied to the operation * does not match the number of elements that were supplied. * </ul> */ @Override protected IModelStatus verify() { IModelStatus status = super.verify(); if (!status.isOK()) { return status; } if (this.renamingsList != null && this.renamingsList.length != elementsToProcess.length) { return new ModelStatus(IModelStatusConstants.INDEX_OUT_OF_BOUNDS); } return ModelStatus.VERIFIED_OK; } /** * @see MultiOperation */ @Override protected void verify(IModelElement element) throws ModelException { if (element == null || !element.exists()) error(IModelStatusConstants.ELEMENT_DOES_NOT_EXIST, element); if (element.isReadOnly() && (isRename() || isMove())) error(IModelStatusConstants.READ_ONLY, element); IResource resource = element.getResource(); if (resource instanceof IFolder) { if (resource.isLinked()) { error(IModelStatusConstants.INVALID_RESOURCE, element); } } int elementType = element.getElementType(); if (elementType == IModelElement.SOURCE_MODULE && element instanceof SourceModule) { SourceModule compilationUnit = (SourceModule) element; if (isMove() && compilationUnit.isWorkingCopy() && !compilationUnit.isPrimary()) error(IModelStatusConstants.INVALID_ELEMENT_TYPES, element); } else if (elementType == IModelElement.SOURCE_MODULE && element instanceof ExternalSourceModule) { if (isMove()) error(IModelStatusConstants.INVALID_ELEMENT_TYPES, element); } else if (elementType == IModelElement.SOURCE_MODULE && element instanceof BuiltinSourceModule) { if (isMove()) error(IModelStatusConstants.INVALID_ELEMENT_TYPES, element); } else if (elementType != IModelElement.SCRIPT_FOLDER) { error(IModelStatusConstants.INVALID_ELEMENT_TYPES, element); } ModelElement dest = (ModelElement) getDestinationParent(element); verifyDestination(element, dest); if (this.renamings != null) { verifyRenaming(element); } } }