/******************************************************************************* * 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.compare.internal.patch; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.compare.internal.core.Messages; import org.eclipse.compare.internal.core.patch.DiffProject; import org.eclipse.compare.internal.core.patch.FilePatch2; import org.eclipse.compare.internal.core.patch.Hunk; import org.eclipse.compare.internal.core.patch.PatchReader; import org.eclipse.compare.patch.IHunk; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceRuleFactory; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.MultiRule; /** * A Patcher * - knows how to parse various patch file formats into some in-memory structure, * - holds onto the parsed data and the options to use when applying the patches, * - knows how to apply the patches to files and folders. */ public class WorkspacePatcher extends Patcher { private DiffProject[] fDiffProjects; private boolean fIsWorkspacePatch= false; private final Map retargetedDiffs = new HashMap(); public WorkspacePatcher() { // nothing to do } public WorkspacePatcher(IResource target) { setTarget(target); } protected void patchParsed(PatchReader patchReader) { super.patchParsed(patchReader); fDiffProjects = patchReader.getDiffProjects(); fIsWorkspacePatch = patchReader.isWorkspacePatch(); } public DiffProject[] getDiffProjects() { return fDiffProjects; } public boolean isWorkspacePatch() { return fIsWorkspacePatch; } //---- parsing patch files public void applyAll(IProgressMonitor pm, IFileValidator validator) throws CoreException { if (!fIsWorkspacePatch) { super.applyAll(pm, validator); } else { final int WORK_UNIT= 10; // get all files to be modified in order to call validateEdit List list= new ArrayList(); for (int j= 0; j < fDiffProjects.length; j++) { DiffProject diffProject= fDiffProjects[j]; if (Utilities.getProject(diffProject).isAccessible()) list.addAll(Arrays.asList(getTargetFiles(diffProject))); } // validate the files for editing if (!validator.validateResources((IFile[])list.toArray(new IFile[list.size()]))) { return; } FilePatch2[] diffs = getDiffs(); if (pm != null) { String message= Messages.WorkspacePatcher_0; pm.beginTask(message, diffs.length * WORK_UNIT); } for (int i= 0; i < diffs.length; i++) { int workTicks= WORK_UNIT; FilePatch2 diff= diffs[i]; if (isAccessible(diff)) { IFile file= getTargetFile(diff); IPath path= file.getProjectRelativePath(); if (pm != null) pm.subTask(path.toString()); createPath(file.getProject(), path); List failed= new ArrayList(); int type= diff.getDiffType(isReversed()); switch (type) { case FilePatch2.ADDITION : // patch it and collect rejected hunks List result= apply(diff, file, true, failed); if (result != null) store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); workTicks -= WORK_UNIT; break; case FilePatch2.DELETION : file.delete(true, true, new SubProgressMonitor(pm, workTicks)); workTicks -= WORK_UNIT; break; case FilePatch2.CHANGE : // patch it and collect rejected hunks result= apply(diff, file, false, failed); if (result != null) store(LineReader.createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); workTicks -= WORK_UNIT; break; } if (isGenerateRejectFile() && failed.size() > 0) { IPath pp= null; if (path.segmentCount() > 1) { pp= path.removeLastSegments(1); pp= pp.append(path.lastSegment() + REJECT_FILE_EXTENSION); } else pp= new Path(path.lastSegment() + REJECT_FILE_EXTENSION); file= createPath(file.getProject(), pp); if (file != null) { store(getRejected(failed), file, pm); try { IMarker marker= file.createMarker(MARKER_TYPE); marker.setAttribute(IMarker.MESSAGE, Messages.WorkspacePatcher_1); marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); } catch (CoreException ex) { // NeedWork } } } } if (pm != null) { if (pm.isCanceled()) break; if (workTicks > 0) pm.worked(workTicks); } } } } private boolean isAccessible(FilePatch2 diff) { return isEnabled(diff) && Utilities.getProject(diff.getProject()).isAccessible(); } /** * Returns the target files of all the Diffs contained by this * DiffProject. * @param project * @return An array of IFiles that are targeted by the Diffs */ public IFile[] getTargetFiles(DiffProject project) { List files= new ArrayList(); FilePatch2[] diffs = project.getFileDiffs(); for (int i = 0; i < diffs.length; i++) { FilePatch2 diff = diffs[i]; if (isEnabled(diff)) { files.add(getTargetFile(diff)); } } return (IFile[]) files.toArray(new IFile[files.size()]); } public IFile getTargetFile(FilePatch2 diff) { IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed()); DiffProject project = getProject(diff); if (project != null) return Utilities.getProject(project).getFile(path); return super.getTargetFile(diff); } private IPath getFullPath(FilePatch2 diff) { IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed()); DiffProject project = getProject(diff); if (project != null) return Utilities.getProject(project).getFile(path).getFullPath(); return getTarget().getFullPath().append(path); } public ISchedulingRule[] getTargetProjects() { List projects= new ArrayList(); IResourceRuleFactory ruleFactory= ResourcesPlugin.getWorkspace().getRuleFactory(); // Determine the appropriate scheduling rules for (int i= 0; i < fDiffProjects.length; i++) { IProject tempProject= Utilities.getProject(fDiffProjects[i]); // The goal here is to lock as little of the workspace as necessary // but still allow the patcher to obtain the locks it needs. // As such, we need to get the modify rules from the rule factory for the .project file. A pessimistic // rule factory will return the root, while others might return just the project. Combining // this rule with the project will result in the smallest possible locking set. ISchedulingRule scheduleRule= ruleFactory.modifyRule(tempProject.getFile(IProjectDescription.DESCRIPTION_FILE_NAME)); MultiRule multiRule= new MultiRule(new ISchedulingRule[] { scheduleRule, tempProject } ); projects.add(multiRule); } return (ISchedulingRule[]) projects.toArray(new ISchedulingRule[projects.size()]); } public void setDiffProjects(DiffProject[] newProjectArray) { fDiffProjects = new DiffProject[newProjectArray.length]; System.arraycopy(newProjectArray,0, fDiffProjects, 0, newProjectArray.length); } public void removeProject(DiffProject project) { DiffProject[] temp = new DiffProject[fDiffProjects.length - 1]; int counter = 0; for (int i = 0; i < fDiffProjects.length; i++) { if (fDiffProjects[i] != project){ temp[counter++] = fDiffProjects[i]; } } fDiffProjects = temp; } protected Object getElementParent(Object element) { if (element instanceof FilePatch2 && fDiffProjects != null) { FilePatch2 diff = (FilePatch2) element; for (int i = 0; i < fDiffProjects.length; i++) { DiffProject project = fDiffProjects[i]; if (project.contains(diff)) return project; } } return null; } public boolean isRetargeted(Object object) { return retargetedDiffs.containsKey(object); } public IPath getOriginalPath(Object object) { return (IPath)retargetedDiffs.get(object); } public void retargetDiff(FilePatch2 diff, IFile file) { retargetedDiffs.put(diff, diff.getPath(false)); IHunk[] hunks = diff.getHunks(); if (isWorkspacePatch()){ //since the diff has no more hunks to apply, remove it from the parent and the patcher diff.getProject().remove(diff); } removeDiff(diff); FilePatch2 newDiff = getDiffForFile(file); for (int i = 0; i < hunks.length; i++) { Hunk hunk = (Hunk) hunks[i]; newDiff.add(hunk); } } private FilePatch2 getDiffForFile(IFile file) { DiffProject diffProject = null; FilePatch2[] diffsToCheck; if (isWorkspacePatch()){ // Check if the diff project already exists for the file IProject project = file.getProject(); DiffProject[] diffProjects = getDiffProjects(); for (int i = 0; i < diffProjects.length; i++) { if (Utilities.getProject(diffProjects[i]).equals(project)){ diffProject = diffProjects[i]; break; } } // If the project doesn't exist yet, create it and add it to the project list if (diffProject == null){ diffProject = addDiffProjectForProject(project); } diffsToCheck = diffProject.getFileDiffs(); } else { diffsToCheck = getDiffs(); } // Check to see if a diff already exists for the file for (int i = 0; i < diffsToCheck.length; i++) { FilePatch2 fileDiff = diffsToCheck[i]; if (isDiffForFile(fileDiff, file)) { return fileDiff; } } // Create a new diff for the file IPath path = getDiffPath(file); FilePatch2 newDiff = new FilePatch2(path, 0, path, 0); if (diffProject != null){ diffProject.add(newDiff); } addDiff(newDiff); return newDiff; } private IPath getDiffPath(IFile file) { DiffProject project = getDiffProject(file.getProject()); if (project != null) { return file.getProjectRelativePath(); } return file.getFullPath().removeFirstSegments(getTarget().getFullPath().segmentCount()); } private boolean isDiffForFile(FilePatch2 fileDiff, IFile file) { return getFullPath(fileDiff).equals(file.getFullPath()); } private DiffProject addDiffProjectForProject(IProject project) { DiffProject[] diffProjects = getDiffProjects(); DiffProject diffProject = new DiffProject(project.getName()); DiffProject[] newProjectArray = new DiffProject[diffProjects.length + 1]; System.arraycopy(diffProjects, 0, newProjectArray, 0, diffProjects.length); newProjectArray[diffProjects.length] = diffProject; setDiffProjects(newProjectArray); return diffProject; } public void retargetHunk(Hunk hunk, IFile file) { FilePatch2 newDiff = getDiffForFile(file); newDiff.add(hunk); } public void retargetProject(DiffProject project, IProject targetProject) { retargetedDiffs.put(project, Utilities.getProject(project).getFullPath()); FilePatch2[] diffs = project.getFileDiffs(); DiffProject selectedProject = getDiffProject(targetProject); if (selectedProject == null) selectedProject = addDiffProjectForProject(targetProject); // Copy over the diffs to the new project for (int i = 0; i < diffs.length; i++) { selectedProject.add(diffs[i]); } // Since the project has been retargeted, remove it from the patcher removeProject(project); } /** * Return the diff project for the given project * or <code>null</code> if the diff project doesn't exist * or if the patch is not a workspace patch. * @param project the project * @return the diff project for the given project * or <code>null</code> */ private DiffProject getDiffProject(IProject project) { if (!isWorkspacePatch()) return null; DiffProject[] projects = getDiffProjects(); for (int i = 0; i < projects.length; i++) { if (Utilities.getProject(projects[i]).equals(project)) return projects[i]; } return null; } public int getStripPrefixSegments() { // Segments are never stripped from a workspace patch if (isWorkspacePatch()) return 0; return super.getStripPrefixSegments(); } }