diff options
Diffstat (limited to 'bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src')
19 files changed, 4135 insertions, 0 deletions
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Activator.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Activator.java new file mode 100644 index 000000000..e8f86548f --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Activator.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 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.compare.internal.core; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Plugin; +import org.eclipse.core.runtime.Status; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends Plugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.eclipse.compare.core"; //$NON-NLS-1$ + + // The shared instance + private static Activator plugin; + + /** + * The constructor + */ + public Activator() { + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.Plugins#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + + public static void log(Throwable e) { + log(new Status(IStatus.ERROR, PLUGIN_ID, 0, Messages.Activator_1, e)); + } + + public static void log(IStatus status) { + getDefault().getLog().log(status); + } + +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java new file mode 100644 index 000000000..b43efaaee --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2008. All Rights Reserved. + * + * Note to U.S. Government Users Restricted Rights: Use, + * duplication or disclosure restricted by GSA ADP Schedule + * Contract with IBM Corp. + *******************************************************************************/ +package org.eclipse.compare.internal.core; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.compare.internal.core.messages"; //$NON-NLS-1$ + public static String Activator_1; + public static String FileDiffResult_0; + public static String FileDiffResult_1; + public static String FileDiffResult_2; + public static String FileDiffResult_3; + public static String Patcher_0; + public static String Patcher_1; + public static String Patcher_2; + public static String WorkspacePatcher_0; + public static String WorkspacePatcher_1; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties new file mode 100644 index 000000000..8db9bece1 --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties @@ -0,0 +1,10 @@ +Activator_1=Internal error +FileDiffResult_0=(file already exists) +FileDiffResult_1=(file does not exist) +FileDiffResult_2={0} {1} +FileDiffResult_3={0} (hunk \#{1}) +Patcher_0=Patching +Patcher_1=Rejected patch +Patcher_2=Guessing Fuzz Factor... +WorkspacePatcher_0=Patching +WorkspacePatcher_1=Rejected patch diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/DiffProject.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/DiffProject.java new file mode 100644 index 000000000..ddb461292 --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/DiffProject.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2005, 2006 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.HashSet; +import java.util.Set; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IPath; + +/** + * A diff project represents a project that was read from a workspace patch. + * It contains the set of file diffs that were associated with the project + * in the patch file. + */ +public class DiffProject { + + private IProject fProject; + private Set fDiffs= new HashSet(); + + /** + * Create a diff project for the given workspace project. + * @param project a workspace project + */ + public DiffProject(IProject project) { + this.fProject= project; + } + + /** + * Add the file diff to this project. + * @param diff the file diff. + */ + void add(FileDiff diff) { + fDiffs.add(diff); + if (diff.getProject() != this) + diff.setProject(this); + } + + + /** + * Return the workspace project associated with this diff project. + * @return the workspace project associated with this project + */ + public IProject getProject() { + return this.fProject; + } + + /** + * Return the name of this project. + * @return the name of this project + */ + public String getName() { + return fProject.getName(); + } + + /** + * Return the file at the given path relative to this project. + * @param path the relative path + * @return the file at the given path relative to this project + */ + public IFile getFile(IPath path) { + return fProject.getFile(path); + } + + /** + * Remove the file diff from this project. + * @param diff the diff to be removed + */ + public void remove(FileDiff diff) { + fDiffs.remove(diff); + } + + /** + * Return whether this project contains the given diff. + * @param diff a file diff + * @return whether this project contains the given diff + */ + public boolean contains(FileDiff diff) { + return fDiffs.contains(diff); + } + + /** + * Return the file diffs associated with this project. + * @return the file diffs associated with this project + */ + public FileDiff[] getFileDiffs() { + return (FileDiff[]) fDiffs.toArray(new FileDiff[fDiffs.size()]); + } +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiff.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiff.java new file mode 100644 index 000000000..be69fb4f2 --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiff.java @@ -0,0 +1,260 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.compare.internal.patch; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.compare.patch.IFilePatch; +import org.eclipse.compare.patch.IFilePatchResult; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Path; + +/** + * A file diff represents a set of hunks that were associated with the + * same path in a patch file. + */ +public class FileDiff implements IFilePatch { + + /** + * Difference constant (value 1) indicating one side was added. + */ + public static final int ADDITION= 1; + /** + * Difference constant (value 2) indicating one side was removed. + */ + public static final int DELETION= 2; + /** + * Difference constant (value 3) indicating side changed. + */ + public static final int CHANGE= 3; + + private IPath fOldPath, fNewPath; + private long oldDate, newDate; + private List fHunks= new ArrayList(); + private DiffProject fProject; //the project that contains this diff + private String header; + private int addedLines, removedLines; + + /** + * Create a file diff for the given path and date information. + * @param oldPath the path of the before state of the file + * @param oldDate the timestamp of the before state + * @param newPath the path of the after state + * @param newDate the timestamp of the after state + */ + protected FileDiff(IPath oldPath, long oldDate, IPath newPath, long newDate) { + fOldPath= oldPath; + this.oldDate = oldDate; + fNewPath= newPath; + this.newDate = newDate; + } + + /** + * Return the parent project or <code>null</code> if there isn't one. + * @return the parent project or <code>null</code> + */ + public DiffProject getProject() { + return fProject; + } + + /** + * Set the project of this diff to the given project. + * This method should only be called from + * {@link DiffProject#add(FileDiff)} + * @param diffProject the parent project + */ + void setProject(DiffProject diffProject) { + if (fProject == diffProject) + return; + if (fProject != null) + fProject.remove(this); + this.fProject= diffProject; + } + + /** + * Get the path of the file diff. + * @param reverse whether the path of the before state or after state + * should be used + * @return the path of the file diff + */ + public IPath getPath(boolean reverse) { + if (getDiffType(reverse) == ADDITION) { + if (reverse) + return fOldPath; + return fNewPath; + } + if (reverse && fNewPath != null) + return fNewPath; + if (fOldPath != null) + return fOldPath; + return fNewPath; + } + + /** + * Add the hunk to this file diff. + * @param hunk the hunk + */ + protected void add(Hunk hunk) { + fHunks.add(hunk); + hunk.setParent(this); + } + + /** + * Remove the hunk from this file diff + * @param hunk the hunk + */ + protected void remove(Hunk hunk) { + fHunks.remove(hunk); + } + + /** + * Return the hunks associated with this file diff. + * @return the hunks associated with this file diff + */ + public Hunk[] getHunks() { + return (Hunk[]) fHunks.toArray(new Hunk[fHunks.size()]); + } + + /** + * Return the number of hunks associated with this file diff. + * @return the number of hunks associated with this file diff + */ + public int getHunkCount() { + return fHunks.size(); + } + + /** + * Return the difference type of this file diff. + * @param reverse whether the patch is being reversed + * @return the type of this file diff + */ + public int getDiffType(boolean reverse) { + if (fHunks.size() == 1) { + boolean add = false; + boolean delete = false; + Iterator iter = fHunks.iterator(); + while (iter.hasNext()){ + Hunk hunk = (Hunk) iter.next(); + int type =hunk.getHunkType(reverse); + if (type == ADDITION){ + add = true; + } else if (type == DELETION ){ + delete = true; + } + } + if (add && !delete){ + return ADDITION; + } else if (!add && delete){ + return DELETION; + } + } + return CHANGE; + } + + /** + * Return the path of this file diff with the specified number + * of leading segments striped. + * @param strip the number of leading segments to strip from the path + * @param reverse whether the patch is being reversed + * @return the path of this file diff with the specified number + * of leading segments striped + */ + protected IPath getStrippedPath(int strip, boolean reverse) { + IPath path= getPath(reverse); + if (strip > 0 && strip < path.segmentCount()) + path= path.removeFirstSegments(strip); + return path; + } + + /** + * Return the segment count of the path of this file diff. + * @return the segment count of the path of this file diff + */ + public int segmentCount() { + //Update prefix count - go through all of the diffs and find the smallest + //path segment contained in all diffs. + int length= 99; + if (fOldPath != null) + length= Math.min(length, fOldPath.segmentCount()); + if (fNewPath != null) + length= Math.min(length, fNewPath.segmentCount()); + return length; + } + + public IFilePatchResult apply(IStorage contents, + PatchConfiguration configuration, IProgressMonitor monitor) { + FileDiffResult result = new FileDiffResult(this, configuration); + result.refresh(contents, monitor); + return result; + } + + public IPath getTargetPath(PatchConfiguration configuration) { + return getStrippedPath(configuration.getPrefixSegmentStripCount(), configuration.isReversed()); + } + + public FileDiff asRelativeDiff() { + if (fProject == null) + return this; + IPath adjustedOldPath = null; + if (fOldPath != null) { + adjustedOldPath = new Path(null, fProject.getName()).append(fOldPath); + } + IPath adjustedNewPath = null; + if (fNewPath != null) { + adjustedNewPath = new Path(null, fProject.getName()).append(fNewPath); + } + FileDiff diff = new FileDiff(adjustedOldPath, 0, adjustedNewPath, 0); + for (Iterator iterator = fHunks.iterator(); iterator.hasNext();) { + Hunk hunk = (Hunk) iterator.next(); + // Creating the hunk adds it to the parent diff + new Hunk(diff, hunk); + } + return diff; + } + + public void setHeader(String header) { + this.header = header; + } + + public String getHeader() { + return header; + } + + public long getBeforeDate() { + return oldDate; + } + + public long getAfterDate() { + return newDate; + } + + public void setAddedLines(int addedLines) { + this.addedLines = addedLines; + } + + public void setRemovedLines(int removedLines) { + this.removedLines = removedLines; + } + + public int getAddedLines() { + return addedLines; + } + + public int getRemovedLines() { + return removedLines; + } + +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiffResult.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiffResult.java new file mode 100644 index 000000000..1d3d74d4b --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiffResult.java @@ -0,0 +1,335 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.compare.internal.core.Activator; +import org.eclipse.compare.internal.core.Messages; +import org.eclipse.compare.patch.IFilePatchResult; +import org.eclipse.compare.patch.IHunk; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.osgi.util.NLS; + +public class FileDiffResult implements IFilePatchResult { + + private FileDiff fDiff; + private boolean fMatches= false; + private boolean fDiffProblem; + private String fErrorMessage; + private Map fHunkResults = new HashMap(); + private List fBeforeLines, fAfterLines; + private final PatchConfiguration configuration; + private String charset; + + public FileDiffResult(FileDiff diff, PatchConfiguration configuration) { + super(); + fDiff = diff; + this.configuration = configuration; + } + + public PatchConfiguration getConfiguration() { + return configuration; + } + + public boolean canApplyHunk(Hunk hunk) { + HunkResult result = getHunkResult(hunk); + return result.isOK() && !fDiffProblem; + } + + /** + * Refreshes the state of the diff to {no matches, no problems} and checks to see what hunks contained + * by this Diff can actually be applied. + * + * Checks to see: + * 1) if the target file specified in fNewPath exists and is patchable + * 2) which hunks contained by this diff can actually be applied to the file + * @param storage the contents being patched or <code>null</code> for an addition + * @param monitor a progress monitor or <code>null</code> if no progress monitoring is desired + */ + public void refresh(IStorage storage, IProgressMonitor monitor) { + fMatches= false; + fDiffProblem= false; + boolean create= false; + charset = Utilities.getCharset(storage); + //If this diff is an addition, make sure that it doesn't already exist + boolean exists = targetExists(storage); + if (fDiff.getDiffType(getConfiguration().isReversed()) == FileDiff.ADDITION) { + if ((!exists || isEmpty(storage)) && canCreateTarget(storage)) { + fMatches= true; + } else { + // file already exists + fDiffProblem= true; + fErrorMessage= Messages.FileDiffResult_0; + } + create= true; + } else { //This diff is not an addition, try to find a match for it + //Ensure that the file described by the path exists and is modifiable + if (exists) { + fMatches= true; + } else { + // file doesn't exist + fDiffProblem= true; + fErrorMessage= Messages.FileDiffResult_1; + } + } + + if (fDiffProblem) { + // We couldn't find the target file or the patch is trying to add a + // file that already exists but we need to initialize the hunk + // results for display + fBeforeLines = new ArrayList(getLines(storage, false)); + fAfterLines = fMatches ? new ArrayList() : fBeforeLines; + Hunk[] hunks = fDiff.getHunks(); + for (int i = 0; i < hunks.length; i++) { + Hunk hunk = hunks[i]; + HunkResult result = getHunkResult(hunk); + result.setMatches(false); + } + } else { + // If this diff has no problems discovered so far, try applying the patch + patch(getLines(storage, create), monitor); + } + + if (containsProblems()) { + if (fMatches) { + // Check to see if we have at least one hunk that matches + fMatches = false; + Hunk[] hunks = fDiff.getHunks(); + for (int i = 0; i < hunks.length; i++) { + Hunk hunk = hunks[i]; + HunkResult result = getHunkResult(hunk); + if (result.isOK()) { + fMatches = true; + break; + } + } + } + } + } + + protected boolean canCreateTarget(IStorage storage) { + return true; + } + + protected boolean targetExists(IStorage storage) { + return storage != null; + } + + protected List getLines(IStorage storage, boolean create) { + List lines = Patcher.load(storage, create); + return lines; + } + + protected boolean isEmpty(IStorage storage) { + if (storage == null) + return true; + return Patcher.load(storage, false).isEmpty(); + } + + /* + * Tries to patch the given lines with the specified Diff. + * Any hunk that couldn't be applied is returned in the list failedHunks. + */ + public void patch(List lines, IProgressMonitor monitor) { + fBeforeLines = new ArrayList(); + fBeforeLines.addAll(lines); + if (getConfiguration().getFuzz() != 0) { + calculateFuzz(fBeforeLines, monitor); + } + int shift= 0; + Hunk[] hunks = fDiff.getHunks(); + for (int i = 0; i < hunks.length; i++) { + Hunk hunk = hunks[i]; + HunkResult result = getHunkResult(hunk); + result.setShift(shift); + if (result.patch(lines)) { + shift = result.getShift(); + } + } + fAfterLines = lines; + } + + public boolean getDiffProblem() { + return fDiffProblem; + } + + /** + * Returns whether this Diff has any problems + * @return true if this Diff or any of its children Hunks have a problem, false if it doesn't + */ + public boolean containsProblems() { + if (fDiffProblem) + return true; + for (Iterator iterator = fHunkResults.values().iterator(); iterator.hasNext();) { + HunkResult result = (HunkResult) iterator.next(); + if (!result.isOK()) + return true; + } + return false; + } + + public String getLabel() { + String label= getTargetPath().toString(); + if (this.fDiffProblem) + return NLS.bind(Messages.FileDiffResult_2, new String[] {label, fErrorMessage}); + return label; + } + + public boolean hasMatches() { + return fMatches; + } + + /** + * Return the lines of the target file with all matched hunks applied. + * @return the lines of the target file with all matched hunks applied + */ + public List getLines() { + return fAfterLines; + } + + /** + * Calculate the fuzz factor that will allow the most hunks to be matched. + * @param lines the lines of the target file + * @param monitor a progress monitor + * @return the fuzz factor or <code>-1</code> if no hunks could be matched + */ + public int calculateFuzz(List lines, IProgressMonitor monitor) { + if (monitor == null) + monitor = new NullProgressMonitor(); + fBeforeLines = new ArrayList(lines); + // TODO: What about deletions? + if (fDiff.getDiffType(getConfiguration().isReversed()) == FileDiff.ADDITION) { + // Additions don't need to adjust the fuzz factor + // TODO: What about the after lines? + return -1; + } + int shift= 0; + int highestFuzz = -1; // the maximum fuzz factor for all hunks + String name = getTargetPath() != null ? getTargetPath().lastSegment() : ""; //$NON-NLS-1$ + Hunk[] hunks = fDiff.getHunks(); + for (int j = 0; j < hunks.length; j++) { + Hunk h = hunks[j]; + monitor.subTask(NLS.bind(Messages.FileDiffResult_3, new String[] {name, Integer.toString(j + 1)})); + HunkResult result = getHunkResult(h); + result.setShift(shift); + int fuzz = result.calculateFuzz(lines, monitor); + shift = result.getShift(); + if (fuzz > highestFuzz) + highestFuzz = fuzz; + monitor.worked(1); + } + fAfterLines = lines; + return highestFuzz; + } + + public IPath getTargetPath() { + return fDiff.getStrippedPath(getConfiguration().getPrefixSegmentStripCount(), getConfiguration().isReversed()); + } + + private HunkResult getHunkResult(Hunk hunk) { + HunkResult result = (HunkResult)fHunkResults.get(hunk); + if (result == null) { + result = new HunkResult(this, hunk); + fHunkResults .put(hunk, result); + } + return result; + } + + List getFailedHunks() { + List failedHunks = new ArrayList(); + for (Iterator iterator = fHunkResults.values().iterator(); iterator.hasNext();) { + HunkResult result = (HunkResult) iterator.next(); + if (!result.isOK()) + failedHunks.add(result.getHunk()); + } + return failedHunks; + } + + private HunkResult[] getFailedHunkResults() { + List failedHunks = new ArrayList(); + for (Iterator iterator = fHunkResults.values().iterator(); iterator.hasNext();) { + HunkResult result = (HunkResult) iterator.next(); + if (!result.isOK()) + failedHunks.add(result); + } + return (HunkResult[]) failedHunks.toArray(new HunkResult[failedHunks.size()]); + } + + public FileDiff getDiff() { + return fDiff; + } + + public List getBeforeLines() { + return fBeforeLines; + } + + public List getAfterLines() { + return fAfterLines; + } + + public HunkResult[] getHunkResults() { + return (HunkResult[]) fHunkResults.values().toArray(new HunkResult[fHunkResults.size()]); + } + + public InputStream getOriginalContents() { + String contents = Patcher.createString(isPreserveLineDelimeters(), getBeforeLines()); + return asInputStream(contents, getCharset()); + } + + public InputStream getPatchedContents() { + String contents = Patcher.createString(isPreserveLineDelimeters(), getLines()); + return asInputStream(contents, getCharset()); + } + + public String getCharset() { + return charset; + } + + public boolean isPreserveLineDelimeters() { + return false; + } + + public IHunk[] getRejects() { + return getFailedHunkResults(); + } + + public boolean hasRejects() { + return getFailedHunkResults().length > 0; + } + + public static InputStream asInputStream(String contents, String charSet) { + byte[] bytes = null; + if (charSet != null) { + try { + bytes = contents.getBytes(charSet); + } catch (UnsupportedEncodingException e) { + Activator.log(e); + } + } + if (bytes == null) { + bytes = contents.getBytes(); + } + return new ByteArrayInputStream(bytes); + } + +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Hunk.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Hunk.java new file mode 100644 index 000000000..bd3844030 --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Hunk.java @@ -0,0 +1,445 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.List; + +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.runtime.Assert; + +/** + * A Hunk describes a range of changed lines and some context lines. + */ +public class Hunk { + + private FileDiff fParent; + private int fOldStart, fOldLength; + private int fNewStart, fNewLength; + private String[] fLines; + private int hunkType; + + public Hunk(FileDiff parent, Hunk toCopy) { + fParent = parent; + if (fParent != null) { + fParent.add(this); + } + + fOldStart = toCopy.fOldStart; + fOldLength = toCopy.fOldLength; + fNewStart = toCopy.fNewStart; + fNewLength = toCopy.fOldLength; + fLines = toCopy.fLines; + hunkType = toCopy.hunkType; + } + + public Hunk(FileDiff parent, int[] oldRange, int[] newRange, List lines, boolean hasLineAdditions, boolean hasLineDeletions, boolean hasContextLines) { + + fParent= parent; + if (fParent != null) + fParent.add(this); + + if (oldRange[0] > 0) + fOldStart= oldRange[0]-1; // line number start at 0! + else + fOldStart= 0; + fOldLength= oldRange[1]; + if (newRange[0] > 0) + fNewStart= newRange[0]-1; // line number start at 0! + else + fNewStart= 0; + fNewLength= newRange[1]; + + fLines= (String[]) lines.toArray(new String[lines.size()]); + + hunkType = FileDiff.CHANGE; + if (!hasContextLines) { + if (hasLineAdditions && !hasLineDeletions) { + hunkType = FileDiff.ADDITION; + } else if (!hasLineAdditions && hasLineDeletions) { + hunkType = FileDiff.DELETION; + } + } + } + + /* + * Returns the contents of this hunk. + * Each line starts with a control character. Their meaning is as follows: + * <ul> + * <li> + * '+': add the line + * <li> + * '-': delete the line + * <li> + * ' ': no change, context line + * </ul> + */ + String getContent() { + StringBuffer sb= new StringBuffer(); + for (int i= 0; i < fLines.length; i++) { + String line= fLines[i]; + sb.append(line.substring(0, Patcher.length(line))); + sb.append('\n'); + } + return sb.toString(); + } + + /* + * Returns a descriptive String for this hunk. + * It is in the form old_start,old_length -> new_start,new_length. + */ + String getDescription() { + StringBuffer sb= new StringBuffer(); + sb.append(Integer.toString(fOldStart)); + sb.append(','); + sb.append(Integer.toString(fOldLength)); + sb.append(" -> "); //$NON-NLS-1$ + sb.append(Integer.toString(fNewStart)); + sb.append(','); + sb.append(Integer.toString(fNewLength)); + return sb.toString(); + } + + String getRejectedDescription() { + StringBuffer sb= new StringBuffer(); + sb.append("@@ -"); //$NON-NLS-1$ + sb.append(Integer.toString(fOldStart)); + sb.append(','); + sb.append(Integer.toString(fOldLength)); + sb.append(" +"); //$NON-NLS-1$ + sb.append(Integer.toString(fNewStart)); + sb.append(','); + sb.append(Integer.toString(fNewLength)); + sb.append(" @@"); //$NON-NLS-1$ + return sb.toString(); + } + + int getHunkType(boolean reverse) { + if (reverse) { + if (hunkType == FileDiff.ADDITION) + return FileDiff.DELETION; + if (hunkType == FileDiff.DELETION) + return FileDiff.ADDITION; + } + return hunkType; + } + + void setHunkType(int hunkType) { + this.hunkType = hunkType; + } + + public String[] getLines() { + return fLines; + } + + /** + * Set the parent of this hunk. This method + * should only be invoked from {@link FileDiff#add(Hunk)} + * @param diff the parent of this hunk + */ + void setParent(FileDiff diff) { + if (fParent == diff) + return; + if (fParent != null) + fParent.remove(this); + fParent = diff; + } + + public FileDiff getParent() { + return fParent; + } + + /* + * Tries to apply the given hunk on the specified lines. + * The parameter shift is added to the line numbers given + * in the hunk. + */ + public boolean tryPatch(PatchConfiguration configuration, List lines, int shift, int fuzz) { + boolean reverse = configuration.isReversed(); + int pos = getStart(reverse) + shift; + int deleteMatches = 0; + List contextLines = new ArrayList(); + boolean contextLinesMatched = true; + boolean precedingLinesChecked = false; + for (int i= 0; i < fLines.length; i++) { + String s = fLines[i]; + Assert.isTrue(s.length() > 0); + String line = s.substring(1); + char controlChar = s.charAt(0); + + if (controlChar == ' ') { // context lines + + if (pos < 0 || pos >= lines.size()) + return false; + contextLines.add(line); + if (linesMatch(configuration, line, (String) lines.get(pos))) { + pos++; + continue; + } else if (fuzz > 0) { + // doesn't match, use the fuzz factor + contextLinesMatched = false; + pos++; + continue; + } + return false; + } else if (isDeletedDelimeter(controlChar, reverse)) { + // deleted lines + + if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0) + // context lines inside hunk don't match + return false; + + // check following context lines if exist + // use the fuzz factor if needed + if (!precedingLinesChecked + && !contextLinesMatched + && contextLines.size() >= fuzz + && !checkPrecedingContextLines(configuration, lines, + fuzz, pos, contextLines)) + return false; + // else if there is less or equal context line to the fuzz + // factor we ignore them all and treat as matching + + precedingLinesChecked = true; + contextLines.clear(); + contextLinesMatched = true; + + if (pos < 0 || pos >= lines.size()) // out of the file + return false; + if (linesMatch(configuration, line, (String) lines.get(pos))) { + deleteMatches++; + pos++; + continue; // line matched, continue with the next one + } + + // We must remove all lines at once, return false if this + // fails. In other words, all lines considered for deletion + // must be found one by one. + + // if (deleteMatches <= 0) + return false; + // pos++; + } else if (isAddedDelimeter(controlChar, reverse)) { + + if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0) + return false; + + if (!precedingLinesChecked + && !contextLinesMatched + && contextLines.size() >= fuzz + && !checkPrecedingContextLines(configuration, lines, + fuzz, pos, contextLines)) + return false; + + precedingLinesChecked = true; + contextLines.clear(); + contextLinesMatched = true; + + // we don't have to do anything more for a 'try' + } else + Assert.isTrue(false, "tryPatch: unknown control character: " + controlChar); //$NON-NLS-1$ + } + + // check following context lines if exist + if (!contextLinesMatched + && fuzz > 0 + && contextLines.size() > fuzz + && !checkFollowingContextLines(configuration, lines, fuzz, pos, + contextLines)) + return false; + + return true; + } + + private boolean checkPrecedingContextLines( + PatchConfiguration configuration, List lines, int fuzz, int pos, + List contextLines) { + + // ignore from the beginning + for (int j = fuzz; j < contextLines.size(); j++) { + if (!linesMatch(configuration, (String) contextLines.get(j), + (String) lines.get(pos - contextLines.size() + j))) + return false; + } + return true; + } + + private boolean checkFollowingContextLines( + PatchConfiguration configuration, List lines, int fuzz, int pos, + List contextLines) { + if (!contextLines.isEmpty()) { + // ignore from the end + for (int j = 0; j < contextLines.size() - fuzz; j++) { + if (!linesMatch(configuration, (String) contextLines.get(j), + (String) lines.get(pos - contextLines.size() + j))) + return false; + } + } + return true; + } + + int getStart(boolean reverse) { + if (reverse) { + return fNewStart; + } + return fOldStart; + } + + private int getLength(boolean reverse) { + if (reverse) { + return fNewLength; + } + return fOldLength; + } + + private int getShift(boolean reverse) { + if (reverse) { + return fOldLength - fNewLength; + } + return fNewLength - fOldLength; + } + + int doPatch(PatchConfiguration configuration, List lines, int shift, int fuzz) { + boolean reverse = configuration.isReversed(); + int pos = getStart(reverse) + shift; + List contextLines = new ArrayList(); + boolean contextLinesMatched = true; + boolean precedingLinesChecked = false; + for (int i= 0; i < fLines.length; i++) { + String s= fLines[i]; + Assert.isTrue(s.length() > 0); + String line= s.substring(1); + char controlChar= s.charAt(0); + if (controlChar == ' ') { + // context lines + Assert.isTrue(pos < lines.size(), "doPatch: inconsistency in context"); //$NON-NLS-1$ + contextLines.add(line); + if (linesMatch(configuration, line, (String) lines.get(pos))) { + pos++; + continue; + } else if (fuzz > 0) { + // doesn't match, use the fuzz factor + contextLinesMatched = false; + pos++; + continue; + } + Assert.isTrue(false, "doPatch: context doesn't match"); //$NON-NLS-1$ +// pos++; + } else if (isDeletedDelimeter(controlChar, reverse)) { + // deleted lines + if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0) + // context lines inside hunk don't match + Assert.isTrue(false, "doPatch: context lines inside hunk don't match"); //$NON-NLS-1$ + + // check following context lines if exist + // use the fuzz factor if needed + if (!precedingLinesChecked + && !contextLinesMatched + && contextLines.size() >= fuzz + && !checkPrecedingContextLines(configuration, lines, + fuzz, pos, contextLines)) + Assert.isTrue(false, "doPatch: preceding context lines don't match, even though fuzz factor has been used"); //$NON-NLS-1$; + // else if there is less or equal context line to the fuzz + // factor we ignore them all and treat as matching + + precedingLinesChecked = true; + contextLines.clear(); + contextLinesMatched = true; + + lines.remove(pos); + } else if (isAddedDelimeter(controlChar, reverse)) { + // added lines + if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0) + Assert.isTrue(false, "doPatch: context lines inside hunk don't match"); //$NON-NLS-1$ + + if (!precedingLinesChecked + && !contextLinesMatched + && contextLines.size() >= fuzz + && !checkPrecedingContextLines(configuration, lines, + fuzz, pos, contextLines)) + Assert.isTrue(false, "doPatch: preceding context lines don't match, even though fuzz factor has been used"); //$NON-NLS-1$; + + precedingLinesChecked = true; + contextLines.clear(); + contextLinesMatched = true; + + if (getLength(reverse) == 0 && pos+1 < lines.size()) + lines.add(pos+1, line); + else + lines.add(pos, line); + pos++; + } else + Assert.isTrue(false, "doPatch: unknown control character: " + controlChar); //$NON-NLS-1$ + } + return getShift(reverse); + } + + private boolean isDeletedDelimeter(char controlChar, boolean reverse) { + return (!reverse && controlChar == '-') || (reverse && controlChar == '+'); + } + + private boolean isAddedDelimeter(char controlChar, boolean reverse) { + return (reverse && controlChar == '-') || (!reverse && controlChar == '+'); + } + + /* + * Compares two strings. + * If fIgnoreWhitespace is true whitespace is ignored. + */ + private boolean linesMatch(PatchConfiguration configuration, String line1, String line2) { + if (configuration.isIgnoreWhitespace()) + return stripWhiteSpace(line1).equals(stripWhiteSpace(line2)); + if (isIgnoreLineDelimiter()) { + int l1= Patcher.length(line1); + int l2= Patcher.length(line2); + if (l1 != l2) + return false; + return line1.regionMatches(0, line2, 0, l1); + } + return line1.equals(line2); + } + + private boolean isIgnoreLineDelimiter() { + return true; + } + + /* + * Returns the given string with all whitespace characters removed. + * Whitespace is defined by <code>Character.isWhitespace(...)</code>. + */ + private String stripWhiteSpace(String s) { + StringBuffer sb= new StringBuffer(); + int l= s.length(); + for (int i= 0; i < l; i++) { + char c= s.charAt(i); + if (!Character.isWhitespace(c)) + sb.append(c); + } + return sb.toString(); + } + + public String getContents(boolean isAfterState, boolean reverse) { + StringBuffer result= new StringBuffer(); + for (int i= 0; i<fLines.length; i++) { + String line= fLines[i]; + String rest= line.substring(1); + char c = line.charAt(0); + if (c == ' ') { + result.append(rest); + } else if (isDeletedDelimeter(c, reverse) && !isAfterState) { + result.append(rest); + } else if (isAddedDelimeter(c, reverse) && isAfterState) { + result.append(rest); + } + } + return result.toString(); + } +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/HunkResult.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/HunkResult.java new file mode 100644 index 000000000..3077bd8eb --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/HunkResult.java @@ -0,0 +1,294 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.compare.internal.patch; + +import java.io.InputStream; +import java.util.List; + +import org.eclipse.compare.patch.IHunk; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +public class HunkResult implements IHunk { + + private static final boolean DEBUG= false; + + /** + * Default maximum fuzz factor equals 2. This is related to the default + * number of context lines, which is 3. + */ + private static final int MAXIMUM_FUZZ_FACTOR = 2; + + private Hunk fHunk; + private boolean fMatches; + private int fShift; + private int fFuzz = -1; // not set or couldn't be found + + private final FileDiffResult fDiffResult; + + /** + * Create a hunk result for the given hunk + * @param diffResult the parent diff result + * @param hunk the hunk + */ + public HunkResult(FileDiffResult diffResult, Hunk hunk) { + fDiffResult = diffResult; + fHunk = hunk; + } + + /** + * Try to apply the specified hunk to the given lines. + * If the hunk cannot be applied at the original position + * the method tries shift lines up and down. + * @param lines the lines to be patched + * @return whether the hunk could be applied + */ + public boolean patch(List lines) { + fMatches = false; + PatchConfiguration configuration = getConfiguration(); + // if the fuzz is not set for the current hunk use the one from fDiffResult + int fuzz = fFuzz != -1 ? fFuzz : configuration.getFuzz(); + if (isEnabled(configuration)) { + if (fHunk.tryPatch(configuration, lines, fShift, fuzz)) { + // it's a perfect match, no shifting is needed + fShift += fHunk.doPatch(configuration, lines, fShift, fuzz); + fMatches = true; + } else { + boolean found= false; + int oldShift= fShift; + + int hugeShift = lines.size(); + for (int i = 1; i <= hugeShift; i++) { + if (fHunk.tryPatch(configuration, lines, fShift - i, fuzz)) { + if (isAdjustShift()) + fShift -= i; + found = true; + break; + } + } + + if (!found) { + for (int i = 1; i <= hugeShift; i++) { + if (fHunk.tryPatch(configuration, lines, fShift + i, fuzz)) { + if (isAdjustShift()) + fShift += i; + found = true; + break; + } + } + } + + if (found) { + if (DEBUG) System.out.println("patched hunk at offset: " + (fShift-oldShift)); //$NON-NLS-1$ + fShift+= fHunk.doPatch(configuration, lines, fShift, fuzz); + fMatches = true; + } + } + } + return fMatches; + } + + private boolean isAdjustShift() { + return true; + } + + private PatchConfiguration getConfiguration() { + return getDiffResult().getConfiguration(); + } + + /** + * Calculate the fuzz that will allow the most hunks to be matched. Even + * though we're interested only in the value of the fuzz, the shifting is + * done anyway. + * + * @param lines + * the lines of the target file + * @param monitor + * a progress monitor + * @return the fuzz factor or -1 if the hunk could not be matched + */ + public int calculateFuzz(List lines, IProgressMonitor monitor) { + fMatches = false; + PatchConfiguration configuration = getConfiguration(); + int fuzz = 0; + int maxFuzz = configuration.getFuzz() == -1 ? MAXIMUM_FUZZ_FACTOR + : configuration.getFuzz(); + for (; fuzz <= maxFuzz; fuzz++) { + // try to apply using lines coordinates from the patch + if (fHunk.tryPatch(configuration, lines, fShift, fuzz)) { + // it's a perfect match, no adjustment is needed + fShift += fHunk.doPatch(configuration, lines, fShift, fuzz); + fMatches = true; + break; + } + + // TODO (tzarna): hugeShift=lines.size() is more than we need. + // Lines to the beg/end of a file would be enough but this can still + // in matching hunks out of order. Try to shift using only lines + // available "between" hunks. + int hugeShift = lines.size(); + + // shift up + for (int i = 1; i <= hugeShift; i++) { + if (monitor.isCanceled()) { + throw new OperationCanceledException(); + } + if (fHunk.tryPatch(configuration, lines, fShift - i, fuzz)) { + if (isAdjustShift()) + fShift -= i; + fMatches = true; + break; + } + } + + // shift down + if (!fMatches) { + for (int i = 1; i <= hugeShift; i++) { + if (monitor.isCanceled()) { + throw new OperationCanceledException(); + } + if (fHunk.tryPatch(configuration, lines, fShift + i, fuzz)) { + if (isAdjustShift()) + fShift += i; + fMatches = true; + break; + } + } + } + + if (fMatches) { + fShift += fHunk.doPatch(configuration, lines, fShift, fuzz); + break; + } + } + // set fuzz for the current hunk + fFuzz = fMatches ? fuzz : -1; + return fFuzz; + } + + /** + * Return the amount that this hunk should be shifted when a match with the file + * is attempted. The shift is needed to compensate for previous hunks that have + * been applied. + * @return the amount that this hunk should be shifted when applied + */ + public int getShift() { + return fShift; + } + + /** + * Set the amount that this hunk should be shifted when a match with the file + * is attempted. The shift is needed to compensate for previous hunks that have + * been applied. + * @param shift the amount to shift this hunk + */ + public void setShift(int shift) { + fShift = shift; + } + + /** + * Return the hunk to which this result applies. + * @return the hunk to which this result applies + */ + public Hunk getHunk() { + return fHunk; + } + + /** + * Return the parent diff result. + * @return the parent diff result + */ + public FileDiffResult getDiffResult() { + return fDiffResult; + } + + /** + * Return whether the hunk was matched with the target file. + * @return whether the hunk was matched with the target file + */ + public boolean isOK() { + return fMatches; + } + + /** + * Return the contents that should be displayed for the hunk result. + * @param afterState whether the after state or before state of the hunk is desired + * @param fullContext whether the hunk should be displayed with the entire file or + * only the lines in the hunk itself + * @return the contents to be display + */ + public String getContents(boolean afterState, boolean fullContext) { + if (fullContext) { + boolean problemFound = false; + List lines = getDiffResult().getBeforeLines(); + if (afterState) { + if (isOK()) { + int oldShift = fShift; + try { + fShift = 0; + problemFound = !patch(lines); + } finally { + fShift = oldShift; + } + } else { + problemFound = true; + } + } + // Only return the full context if we could apply the hunk + if (!problemFound) + return Patcher.createString(fDiffResult.isPreserveLineDelimeters(), lines); + } + return getHunk().getContents(afterState, getConfiguration().isReversed()); + } + + private boolean isEnabled(PatchConfiguration configuration) { + Patcher patcher = Patcher.getPatcher(configuration); + if (patcher != null) + return patcher.isEnabled(fHunk); + return true; + } + + public void setMatches(boolean matches) { + fMatches = matches; + } + + public int getStartPosition() { + return fHunk.getStart(getConfiguration().isReversed()) + fShift; + } + + public String getLabel() { + return getHunk().getDescription(); + } + + public InputStream getOriginalContents() { + String contents = getContents(false, false); + return asInputStream(contents); + } + + public InputStream asInputStream(String contents) { + String charSet = getCharset(); + return FileDiffResult.asInputStream(contents, charSet); + } + + public InputStream getPatchedContents() { + String contents = getContents(true, false); + return asInputStream(contents); + } + + public String getCharset() { + return fDiffResult.getCharset(); + } + + public int getFuzz() { + return fFuzz; + } +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/LineReader.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/LineReader.java new file mode 100644 index 000000000..ecf8d7725 --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/LineReader.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * 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 + * Brock Janiczak <brockj@tpg.com.au> - Bug 181919 LineReader creating unneeded garbage + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.Assert; + +public class LineReader { + + private boolean fHaveChar= false; + private int fLastChar; + private boolean fSawEOF= false; + private BufferedReader fReader; + private boolean fIgnoreSingleCR= false; + private StringBuffer fBuffer= new StringBuffer(); + + public LineReader(BufferedReader reader) { + fReader= reader; + Assert.isNotNull(reader); + } + + void ignoreSingleCR() { + fIgnoreSingleCR= true; + } + + /** + * Reads a line of text. A line is considered to be terminated by any one + * of a line feed ('\n'), a carriage return ('\r'), or a carriage return + * followed immediately by a line-feed. + * @return A string containing the contents of the line including + * the line-termination characters, or <code>null</code> if the end of the + * stream has been reached + * @exception IOException If an I/O error occurs + */ + /* package */ String readLine() throws IOException { + try { + while (!fSawEOF) { + int c= readChar(); + if (c == -1) { + fSawEOF= true; + break; + } + fBuffer.append((char)c); + if (c == '\n') + break; + if (c == '\r') { + c= readChar(); + if (c == -1) { + fSawEOF= true; + break; // EOF + } + if (c != '\n') { + if (fIgnoreSingleCR) { + fBuffer.append((char)c); + continue; + } + fHaveChar= true; + fLastChar= c; + } else + fBuffer.append((char)c); + break; + } + } + + if (fBuffer.length() != 0) { + return fBuffer.toString(); + } + return null; + } finally { + fBuffer.setLength(0); + } + } + + /* package */ void close() { + try { + fReader.close(); + } catch (IOException ex) { + // silently ignored + } + } + + public List readLines() { + try { + List lines= new ArrayList(); + String line; + while ((line= readLine()) != null) + lines.add(line); + return lines; + } catch (IOException ex) { + // NeedWork + //System.out.println("error while reading file: " + fileName + "(" + ex + ")"); + } finally { + close(); + } + return null; + } + + /* + * Returns the number of characters in the given string without + * counting a trailing line separator. + */ + /* package */ int lineContentLength(String line) { + if (line == null) + return 0; + int length= line.length(); + for (int i= length-1; i >= 0; i--) { + char c= line.charAt(i); + if (c =='\n' || c == '\r') + length--; + else + break; + } + return length; + } + + //---- private + + private int readChar() throws IOException { + if (fHaveChar) { + fHaveChar= false; + return fLastChar; + } + return fReader.read(); + } +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/PatchReader.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/PatchReader.java new file mode 100644 index 000000000..da93ef7fc --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/PatchReader.java @@ -0,0 +1,669 @@ +/******************************************************************************* + * Copyright (c) 2006, 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.compare.internal.patch; + +import java.io.BufferedReader; +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.StringTokenizer; + +import org.eclipse.compare.patch.IFilePatch; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; + +import com.ibm.icu.text.DateFormat; +import com.ibm.icu.text.SimpleDateFormat; + +public class PatchReader { + + private static final boolean DEBUG= false; + + private static final String DEV_NULL= "/dev/null"; //$NON-NLS-1$ + + protected static final String MARKER_TYPE= "org.eclipse.compare.rejectedPatchMarker"; //$NON-NLS-1$ + + // diff formats + // private static final int CONTEXT= 0; + // private static final int ED= 1; + // private static final int NORMAL= 2; + // private static final int UNIFIED= 3; + + // we recognize the following date/time formats + private DateFormat[] fDateFormats= new DateFormat[] { + new SimpleDateFormat("EEE MMM dd kk:mm:ss yyyy"), //$NON-NLS-1$ + new SimpleDateFormat("yyyy/MM/dd kk:mm:ss"), //$NON-NLS-1$ + new SimpleDateFormat("EEE MMM dd kk:mm:ss yyyy", Locale.US) //$NON-NLS-1$ + }; + + private boolean fIsWorkspacePatch; + private DiffProject[] fDiffProjects; + private FileDiff[] fDiffs; + + // API for writing new multi-project patch format + public static final String MULTIPROJECTPATCH_HEADER= "### Eclipse Workspace Patch"; //$NON-NLS-1$ + + public static final String MULTIPROJECTPATCH_VERSION= "1.0"; //$NON-NLS-1$ + + public static final String MULTIPROJECTPATCH_PROJECT= "#P"; //$NON-NLS-1$ + + /** + * Create a patch reader for the default date formats. + */ + public PatchReader() { + // nothing here + } + + /** + * Create a patch reader for the given date formats. + * + * @param dateFormats + * Array of <code>DateFormat</code>s to be used when + * extracting dates from the patch. + */ + public PatchReader(DateFormat[] dateFormats) { + this(); + fDateFormats = dateFormats; + } + + public void parse(BufferedReader reader) throws IOException { + List diffs= new ArrayList(); + HashMap diffProjects= new HashMap(4); + String line= null; + boolean reread= false; + String diffArgs= null; + String fileName= null; + // no project means this is a single patch,create a placeholder project for now + // which will be replaced by the target selected by the user in the preview pane + String project= ""; //$NON-NLS-1$ + fIsWorkspacePatch= false; + + LineReader lr= new LineReader(reader); + lr.ignoreSingleCR(); // Don't treat single CRs as line feeds to be consistent with command line patch + + // Test for our format + line= lr.readLine(); + if (line != null && line.startsWith(PatchReader.MULTIPROJECTPATCH_HEADER)) { + fIsWorkspacePatch= true; + } else { + parse(lr, line); + return; + } + + // read leading garbage + while (true) { + if (!reread) + line= lr.readLine(); + reread= false; + if (line == null) + break; + if (line.length() < 4) + continue; // too short + + if (line.startsWith(PatchReader.MULTIPROJECTPATCH_PROJECT)) { + project= line.substring(2).trim(); + continue; + } + + if (line.startsWith("Index: ")) { //$NON-NLS-1$ + fileName= line.substring(7).trim(); + continue; + } + if (line.startsWith("diff")) { //$NON-NLS-1$ + diffArgs= line.substring(4).trim(); + continue; + } + + if (line.startsWith("--- ")) { //$NON-NLS-1$ + // if there is no current project or + // the current project doesn't equal the newly parsed project + // reset the current project to the newly parsed one, create a new DiffProject + // and add it to the array + DiffProject diffProject; + if (!diffProjects.containsKey(project)) { + IProject iproject= ResourcesPlugin.getWorkspace().getRoot().getProject(project); + diffProject= new DiffProject(iproject); + diffProjects.put(project, diffProject); + } else { + diffProject= (DiffProject) diffProjects.get(project); + } + + line= readUnifiedDiff(diffs, lr, line, diffArgs, fileName, diffProject); + diffArgs= fileName= null; + reread= true; + } + } + + lr.close(); + + fDiffProjects= (DiffProject[]) diffProjects.values().toArray(new DiffProject[diffProjects.size()]); + fDiffs = (FileDiff[]) diffs.toArray(new FileDiff[diffs.size()]); + } + + private String readUnifiedDiff(List diffs, LineReader lr, String line, String diffArgs, String fileName, DiffProject diffProject) throws IOException { + List newDiffs= new ArrayList(); + String nextLine= readUnifiedDiff(newDiffs, lr, line, diffArgs, fileName); + for (Iterator iter= newDiffs.iterator(); iter.hasNext();) { + FileDiff diff= (FileDiff) iter.next(); + diffProject.add(diff); + diffs.add(diff); + } + return nextLine; + } + + public void parse(LineReader lr, String line) throws IOException { + List diffs= new ArrayList(); + boolean reread= false; + String diffArgs= null; + String fileName= null; + List headerLines = new ArrayList(); + + // read leading garbage + reread= line!=null; + while (true) { + if (!reread) + line= lr.readLine(); + reread= false; + if (line == null) + break; + + // remember some infos + if (line.startsWith("Index: ")) { //$NON-NLS-1$ + fileName= line.substring(7).trim(); + } else if (line.startsWith("diff")) { //$NON-NLS-1$ + diffArgs= line.substring(4).trim(); + } else if (line.startsWith("--- ")) { //$NON-NLS-1$ + line= readUnifiedDiff(diffs, lr, line, diffArgs, fileName); + if (!headerLines.isEmpty()) + setHeader((FileDiff)diffs.get(diffs.size() - 1), headerLines); + diffArgs= fileName= null; + reread= true; + } else if (line.startsWith("*** ")) { //$NON-NLS-1$ + line= readContextDiff(diffs, lr, line, diffArgs, fileName); + if (!headerLines.isEmpty()) + setHeader((FileDiff)diffs.get(diffs.size() - 1), headerLines); + diffArgs= fileName= null; + reread= true; + } + + // Any lines we read here are header lines. + // However, if reread is set, we will add them to the header on the next pass through + if (!reread) { + headerLines.add(line); + } + } + + lr.close(); + + fDiffs = (FileDiff[]) diffs.toArray(new FileDiff[diffs.size()]); + } + + private void setHeader(FileDiff diff, List headerLines) { + String header = Patcher.createString(false, headerLines); + diff.setHeader(header); + headerLines.clear(); + } + + /* + * Returns the next line that does not belong to this diff + */ + protected String readUnifiedDiff(List diffs, LineReader reader, String line, String args, String fileName) throws IOException { + + String[] oldArgs= split(line.substring(4)); + + // read info about new file + line= reader.readLine(); + if (line == null || !line.startsWith("+++ ")) //$NON-NLS-1$ + return line; + + String[] newArgs= split(line.substring(4)); + + FileDiff diff= new FileDiff(extractPath(oldArgs, 0, fileName), extractDate(oldArgs, 1), + extractPath(newArgs, 0, fileName), extractDate(newArgs, 1)); + diffs.add(diff); + + int[] oldRange= new int[2]; + int[] newRange= new int[2]; + List lines= new ArrayList(); + + boolean encounteredPlus = false; + boolean encounteredMinus = false; + boolean encounteredSpace = false; + + try { + // read lines of hunk + while (true) { + + line= reader.readLine(); + if (line == null) + return null; + + if (reader.lineContentLength(line) == 0) { + //System.out.println("Warning: found empty line in hunk; ignored"); + //lines.add(' ' + line); + continue; + } + + char c= line.charAt(0); + switch (c) { + case '@': + if (line.startsWith("@@ ")) { //$NON-NLS-1$ + // flush old hunk + if (lines.size() > 0) { + new Hunk(diff, oldRange, newRange, lines,encounteredPlus, encounteredMinus, encounteredSpace); + lines.clear(); + } + + // format: @@ -oldStart,oldLength +newStart,newLength @@ + extractPair(line, '-', oldRange); + extractPair(line, '+', newRange); + continue; + } + break; + case ' ': + encounteredSpace = true; + lines.add(line); + continue; + case '+': + encounteredPlus = true; + lines.add(line); + continue; + case '-': + encounteredMinus = true; + lines.add(line); + continue; + case '\\': + if (line.indexOf("newline at end") > 0) { //$NON-NLS-1$ + int lastIndex= lines.size(); + if (lastIndex > 0) { + line= (String) lines.get(lastIndex-1); + int end= line.length()-1; + char lc= line.charAt(end); + if (lc == '\n') { + end--; + if (end > 0 && line.charAt(end) == '\r') + end--; + } else if (lc == '\r') { + end--; + } + line= line.substring(0, end+1); + lines.set(lastIndex-1, line); + } + continue; + } + break; + default: + if (DEBUG) { + int a1= c, a2= 0; + if (line.length() > 1) + a2= line.charAt(1); + System.out.println("char: " + a1 + " " + a2); //$NON-NLS-1$ //$NON-NLS-2$ + } + break; + } + return line; + } + } finally { + if (lines.size() > 0) + new Hunk(diff, oldRange, newRange, lines, encounteredPlus, encounteredMinus, encounteredSpace); + } + } + + /* + * Returns the next line that does not belong to this diff + */ + private String readContextDiff(List diffs, LineReader reader, String line, String args, String fileName) throws IOException { + + String[] oldArgs= split(line.substring(4)); + + // read info about new file + line= reader.readLine(); + if (line == null || !line.startsWith("--- ")) //$NON-NLS-1$ + return line; + + String[] newArgs= split(line.substring(4)); + + FileDiff diff= new FileDiff(extractPath(oldArgs, 0, fileName), extractDate(oldArgs, 1), + extractPath(newArgs, 0, fileName), extractDate(newArgs, 1)); + diffs.add(diff); + + int[] oldRange= new int[2]; + int[] newRange= new int[2]; + List oldLines= new ArrayList(); + List newLines= new ArrayList(); + List lines= oldLines; + + + boolean encounteredPlus = false; + boolean encounteredMinus = false; + boolean encounteredSpace = false; + + try { + // read lines of hunk + while (true) { + + line= reader.readLine(); + if (line == null) + return line; + + int l= line.length(); + if (l == 0) + continue; + if (l > 1) { + switch (line.charAt(0)) { + case '*': + if (line.startsWith("***************")) { // new hunk //$NON-NLS-1$ + // flush old hunk + if (oldLines.size() > 0 || newLines.size() > 0) { + new Hunk(diff, oldRange, newRange, unifyLines(oldLines, newLines), encounteredPlus, encounteredMinus, encounteredSpace); + oldLines.clear(); + newLines.clear(); + } + continue; + } + if (line.startsWith("*** ")) { // old range //$NON-NLS-1$ + // format: *** oldStart,oldEnd *** + extractPair(line, ' ', oldRange); + oldRange[1]= oldRange[1]-oldRange[0]+1; + lines= oldLines; + continue; + } + break; + case ' ': // context line + if (line.charAt(1) == ' ') { + lines.add(line); + continue; + } + break; + case '+': // addition + if (line.charAt(1) == ' ') { + encounteredPlus = true; + lines.add(line); + continue; + } + break; + case '!': // change + if (line.charAt(1) == ' ') { + encounteredSpace = true; + lines.add(line); + continue; + } + break; + case '-': + if (line.charAt(1) == ' ') { // deletion + encounteredMinus = true; + lines.add(line); + continue; + } + if (line.startsWith("--- ")) { // new range //$NON-NLS-1$ + // format: *** newStart,newEnd *** + extractPair(line, ' ', newRange); + newRange[1]= newRange[1]-newRange[0]+1; + lines= newLines; + continue; + } + break; + default: + break; + } + } + return line; + } + } finally { + // flush last hunk + if (oldLines.size() > 0 || newLines.size() > 0) + new Hunk(diff, oldRange, newRange, unifyLines(oldLines, newLines), encounteredPlus, encounteredMinus, encounteredSpace); + } + } + + /* + * Creates a List of lines in the unified format from + * two Lists of lines in the 'classic' format. + */ + private List unifyLines(List oldLines, List newLines) { + List result= new ArrayList(); + + String[] ol= (String[]) oldLines.toArray(new String[oldLines.size()]); + String[] nl= (String[]) newLines.toArray(new String[newLines.size()]); + + int oi= 0, ni= 0; + + while (true) { + + char oc= 0; + String o= null; + if (oi < ol.length) { + o= ol[oi]; + oc= o.charAt(0); + } + + char nc= 0; + String n= null; + if (ni < nl.length) { + n= nl[ni]; + nc= n.charAt(0); + } + + // EOF + if (oc == 0 && nc == 0) + break; + + // deletion in old + if (oc == '-') { + do { + result.add('-' + o.substring(2)); + oi++; + if (oi >= ol.length) + break; + o= ol[oi]; + } while (o.charAt(0) == '-'); + continue; + } + + // addition in new + if (nc == '+') { + do { + result.add('+' + n.substring(2)); + ni++; + if (ni >= nl.length) + break; + n= nl[ni]; + } while (n.charAt(0) == '+'); + continue; + } + + // differing lines on both sides + if (oc == '!' && nc == '!') { + // remove old + do { + result.add('-' + o.substring(2)); + oi++; + if (oi >= ol.length) + break; + o= ol[oi]; + } while (o.charAt(0) == '!'); + + // add new + do { + result.add('+' + n.substring(2)); + ni++; + if (ni >= nl.length) + break; + n= nl[ni]; + } while (n.charAt(0) == '!'); + + continue; + } + + // context lines + if (oc == ' ' && nc == ' ') { + do { + Assert.isTrue(o.equals(n), "non matching context lines"); //$NON-NLS-1$ + result.add(' ' + o.substring(2)); + oi++; + ni++; + if (oi >= ol.length || ni >= nl.length) + break; + o= ol[oi]; + n= nl[ni]; + } while (o.charAt(0) == ' ' && n.charAt(0) == ' '); + continue; + } + + if (oc == ' ') { + do { + result.add(' ' + o.substring(2)); + oi++; + if (oi >= ol.length) + break; + o= ol[oi]; + } while (o.charAt(0) == ' '); + continue; + } + + if (nc == ' ') { + do { + result.add(' ' + n.substring(2)); + ni++; + if (ni >= nl.length) + break; + n= nl[ni]; + } while (n.charAt(0) == ' '); + continue; + } + + Assert.isTrue(false, "unexpected char <" + oc + "> <" + nc + ">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + return result; + } + + /* + * @return the parsed time/date in milliseconds or IFilePatch.DATE_UNKNOWN + * (0) on error + */ + private long extractDate(String[] args, int n) { + if (n < args.length) { + String line= args[n]; + for (int i= 0; i < fDateFormats.length; i++) { + fDateFormats[i].setLenient(true); + try { + Date date= fDateFormats[i].parse(line); + return date.getTime(); + } catch (ParseException ex) { + // silently ignored + } + } + // System.err.println("can't parse date: <" + line + ">"); + } + return IFilePatch.DATE_UNKNOWN; + } + + /* + * Returns null if file name is "/dev/null". + */ + private IPath extractPath(String[] args, int n, String path2) { + if (n < args.length) { + String path= args[n]; + if (DEV_NULL.equals(path)) + return null; + int pos= path.lastIndexOf(':'); + if (pos >= 0) + path= path.substring(0, pos); + if (path2 != null && !path2.equals(path)) { + if (DEBUG) System.out.println("path mismatch: " + path2); //$NON-NLS-1$ + path= path2; + } + return new Path(path); + } + return null; + } + + /* + * Tries to extract two integers separated by a comma. + * The parsing of the line starts at the position after + * the first occurrence of the given character start an ends + * at the first blank (or the end of the line). + * If only a single number is found this is assumed to be the start of a one line range. + * If an error occurs the range -1,-1 is returned. + */ + private void extractPair(String line, char start, int[] pair) { + pair[0]= pair[1]= -1; + int startPos= line.indexOf(start); + if (startPos < 0) { + if (DEBUG) System.out.println("parsing error in extractPair: couldn't find \'" + start + "\'"); //$NON-NLS-1$ //$NON-NLS-2$ + return; + } + line= line.substring(startPos+1); + int endPos= line.indexOf(' '); + if (endPos < 0) { + if (DEBUG) System.out.println("parsing error in extractPair: couldn't find end blank"); //$NON-NLS-1$ + return; + } + line= line.substring(0, endPos); + int comma= line.indexOf(','); + if (comma >= 0) { + pair[0]= Integer.parseInt(line.substring(0, comma)); + pair[1]= Integer.parseInt(line.substring(comma+1)); + } else { // abbreviated form for one line patch + pair[0]= Integer.parseInt(line); + pair[1]= 1; + } + } + + + /* + * Breaks the given string into tab separated substrings. + * Leading and trailing whitespace is removed from each token. + */ + private String[] split(String line) { + List l= new ArrayList(); + StringTokenizer st= new StringTokenizer(line, "\t"); //$NON-NLS-1$ + while (st.hasMoreElements()) { + String token= st.nextToken().trim(); + if (token.length() > 0) + l.add(token); + } + return (String[]) l.toArray(new String[l.size()]); + } + + public boolean isWorkspacePatch() { + return fIsWorkspacePatch; + } + + public DiffProject[] getDiffProjects() { + return fDiffProjects; + } + + public FileDiff[] getDiffs() { + return fDiffs; + } + + public FileDiff[] getAdjustedDiffs() { + if (!isWorkspacePatch() || fDiffs.length == 0) + return fDiffs; + List result = new ArrayList(); + for (int i = 0; i < fDiffs.length; i++) { + FileDiff diff = fDiffs[i]; + result.add(diff.asRelativeDiff()); + } + return (FileDiff[]) result.toArray(new FileDiff[result.size()]); + } + +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Patcher.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Patcher.java new file mode 100644 index 000000000..caf2dcafc --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Patcher.java @@ -0,0 +1,877 @@ +/******************************************************************************* + * 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 + * Martin Burger <m@rtin-burger.de> patch for #93810 and #93901 + *******************************************************************************/ +package org.eclipse.compare.internal.patch; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.compare.internal.core.Activator; +import org.eclipse.compare.internal.core.Messages; +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IEncodedStorage; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.runtime.Assert; +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; + +/** + * 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 Patcher { + + static protected final String REJECT_FILE_EXTENSION= ".rej"; //$NON-NLS-1$ + + static protected final String MARKER_TYPE= "org.eclipse.compare.rejectedPatchMarker"; //$NON-NLS-1$ + + /** + * Property used to associate a patcher with a {@link PatchConfiguration} + */ + public static final String PROP_PATCHER = "org.eclipse.compare.patcher"; //$NON-NLS-1$ + + public interface IFileValidator { + boolean validateResources(IFile[] array); + } + + // diff formats + // private static final int CONTEXT= 0; + // private static final int ED= 1; + // private static final int NORMAL= 2; + // private static final int UNIFIED= 3; + + private FileDiff[] fDiffs; + private IResource fTarget; + // patch options + private Set disabledElements = new HashSet(); + private Map diffResults = new HashMap(); + private final Map contentCache = new HashMap(); + private Set mergedHunks = new HashSet(); + + private final PatchConfiguration configuration; + private boolean fGenerateRejectFile = false; + + public Patcher() { + configuration = new PatchConfiguration(); + configuration.setProperty(PROP_PATCHER, this); + } + + /* + * Returns an array of Diffs after a sucessfull call to <code>parse</code>. + * If <code>parse</code> hasn't been called returns <code>null</code>. + */ + public FileDiff[] getDiffs() { + if (fDiffs == null) + return new FileDiff[0]; + return fDiffs; + } + + public IPath getPath(FileDiff diff) { + return diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setStripPrefixSegments(int strip) { + if (strip != getConfiguration().getPrefixSegmentStripCount()) { + getConfiguration().setPrefixSegmentStripCount(strip); + return true; + } + return false; + } + + int getStripPrefixSegments() { + return getConfiguration().getPrefixSegmentStripCount(); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setFuzz(int fuzz) { + if (fuzz != getConfiguration().getFuzz()) { + getConfiguration().setFuzz(fuzz); + return true; + } + return false; + } + + public int getFuzz(){ + return getConfiguration().getFuzz(); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setIgnoreWhitespace(boolean ignoreWhitespace) { + if (ignoreWhitespace != getConfiguration().isIgnoreWhitespace()) { + getConfiguration().setIgnoreWhitespace(ignoreWhitespace); + return true; + } + return false; + } + + public boolean isIgnoreWhitespace() { + return getConfiguration().isIgnoreWhitespace(); + } + + public boolean isGenerateRejectFile() { + return fGenerateRejectFile; + } + + public void setGenerateRejectFile(boolean generateRejectFile) { + fGenerateRejectFile = generateRejectFile; + } + + //---- parsing patch files + + public void parse(IStorage storage) throws IOException, CoreException { + BufferedReader reader = createReader(storage); + try { + parse(reader); + } finally { + try { + reader.close(); + } catch (IOException e) { //ignored + } + } + } + + public static BufferedReader createReader(IStorage storage) throws CoreException { + String charset = null; + if (storage instanceof IEncodedStorage) { + IEncodedStorage es = (IEncodedStorage) storage; + charset = es.getCharset(); + } + InputStreamReader in = null; + if (charset != null) { + InputStream contents = storage.getContents(); + try { + in = new InputStreamReader(contents, charset); + } catch (UnsupportedEncodingException e) { + Activator.log(e); + try { + contents.close(); + } catch (IOException e1) { + // Ignore + } + } + } + if (in == null) { + in = new InputStreamReader(storage.getContents()); + } + return new BufferedReader(in); + } + + public void parse(BufferedReader reader) throws IOException { + PatchReader patchReader= new PatchReader(); + patchReader.parse(reader); + patchParsed(patchReader); + } + + protected void patchParsed(PatchReader patchReader) { + fDiffs = patchReader.getDiffs(); + } + + public void countLines() { + FileDiff[] fileDiffs = getDiffs(); + for (int i = 0; i < fileDiffs.length; i++) { + int addedLines = 0; + int removedLines = 0; + FileDiff fileDiff = fileDiffs[i]; + for (int j = 0; j < fileDiff.getHunkCount(); j++) { + Hunk hunk = fileDiff.getHunks()[j]; + String[] lines = hunk.getLines(); + for (int k = 0; k < lines.length; k++) { + char c = lines[k].charAt(0); + switch (c) { + case '+': + addedLines++; + continue; + case '-': + removedLines++; + continue; + } + } + } + fileDiff.setAddedLines(addedLines); + fileDiff.setRemovedLines(removedLines); + } + } + + //---- applying a patch file + + public void applyAll(IProgressMonitor pm, IFileValidator validator) throws CoreException { + + int i; + + IFile singleFile= null; // file to be patched + IContainer container= null; + if (fTarget instanceof IContainer) + container= (IContainer) fTarget; + else if (fTarget instanceof IFile) { + singleFile= (IFile) fTarget; + container= singleFile.getParent(); + } else { + Assert.isTrue(false); + } + + // get all files to be modified in order to call validateEdit + List list= new ArrayList(); + if (singleFile != null) + list.add(singleFile); + else { + for (i= 0; i < fDiffs.length; i++) { + FileDiff diff= fDiffs[i]; + if (isEnabled(diff)) { + switch (diff.getDiffType(isReversed())) { + case FileDiff.CHANGE: + list.add(createPath(container, getPath(diff))); + break; + } + } + } + } + if (! validator.validateResources((IFile[])list.toArray(new IFile[list.size()]))) { + return; + } + + final int WORK_UNIT= 10; + if (pm != null) { + String message= Messages.Patcher_0; + pm.beginTask(message, fDiffs.length*WORK_UNIT); + } + + for (i= 0; i < fDiffs.length; i++) { + + int workTicks= WORK_UNIT; + + FileDiff diff= fDiffs[i]; + if (isEnabled(diff)) { + + IPath path= getPath(diff); + if (pm != null) + pm.subTask(path.toString()); + + IFile file= singleFile != null + ? singleFile + : createPath(container, path); + + List failed= new ArrayList(); + + int type= diff.getDiffType(isReversed()); + switch (type) { + case FileDiff.ADDITION: + // patch it and collect rejected hunks + List result= apply(diff, file, true, failed); + if (result != null) + store(createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); + workTicks-= WORK_UNIT; + break; + case FileDiff.DELETION: + file.delete(true, true, new SubProgressMonitor(pm, workTicks)); + workTicks-= WORK_UNIT; + break; + case FileDiff.CHANGE: + // patch it and collect rejected hunks + result= apply(diff, file, false, failed); + if (result != null) + store(createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); + workTicks-= WORK_UNIT; + break; + } + + if (isGenerateRejectFile() && failed.size() > 0) { + IPath pp = getRejectFilePath(path); + file= createPath(container, pp); + if (file != null) { + store(getRejected(failed), file, pm); + try { + IMarker marker= file.createMarker(MARKER_TYPE); + marker.setAttribute(IMarker.MESSAGE, Messages.Patcher_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 IPath getRejectFilePath(IPath path) { + 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); + return pp; + } + + /* + * Reads the contents from the given file and returns them as + * a List of lines. + */ + public static List load(IStorage file, boolean create) { + List lines= null; + if (!create && file != null && exists(file)) { + // read current contents + String charset = Utilities.getCharset(file); + InputStream is= null; + try { + is= file.getContents(); + + Reader streamReader= null; + try { + streamReader= new InputStreamReader(is, charset); + } catch (UnsupportedEncodingException x) { + // use default encoding + streamReader= new InputStreamReader(is); + } + + BufferedReader reader= new BufferedReader(streamReader); + lines = readLines(reader); + } catch(CoreException ex) { + // TODO + Activator.log(ex); + } finally { + if (is != null) + try { + is.close(); + } catch(IOException ex) { + // silently ignored + } + } + } + + if (lines == null) + lines= new ArrayList(); + return lines; + } + + private static boolean exists(IStorage file) { + if (file instanceof IFile) { + return ((IFile) file).exists(); + } + return true; + } + + private static List readLines(BufferedReader reader) { + List lines; + LineReader lr= new LineReader(reader); + lr.ignoreSingleCR(); // Don't treat single CRs as line feeds to be consistent with command line patch + lines= lr.readLines(); + return lines; + } + + List apply(FileDiff diff, IFile file, boolean create, List failedHunks) { + FileDiffResult result = getDiffResult(diff); + List lines = Patcher.load(file, create); + result.patch(lines, null); + failedHunks.addAll(result.getFailedHunks()); + if (hasCachedContents(diff)) { + // Used the cached contents since they would have been provided by the user + return getCachedLines(diff); + } else if (!result.hasMatches()) { + // Return null if there were no matches + return null; + } + return result.getLines(); + } + + /* + * Converts the string into bytes and stores them in the given file. + */ + protected void store(String contents, IFile file, IProgressMonitor pm) throws CoreException { + + byte[] bytes; + try { + bytes= contents.getBytes(Utilities.getCharset(file)); + } catch (UnsupportedEncodingException x) { + // uses default encoding + bytes= contents.getBytes(); + } + + store(bytes,file, pm); + } + + protected void store(byte[] bytes, IFile file, IProgressMonitor pm) throws CoreException { + InputStream is= new ByteArrayInputStream(bytes); + try { + if (file.exists()) { + file.setContents(is, false, true, pm); + } else { + file.create(is, false, pm); + } + } finally { + if (is != null) + try { + is.close(); + } catch(IOException ex) { + // silently ignored + } + } + } + + + + /* + * Concatenates all strings found in the given List. + */ + public static String createString(boolean preserveLineDelimeters, List lines) { + StringBuffer sb= new StringBuffer(); + Iterator iter= lines.iterator(); + if (preserveLineDelimeters) { + while (iter.hasNext()) + sb.append((String)iter.next()); + } else { + String lineSeparator= System.getProperty("line.separator"); //$NON-NLS-1$ + while (iter.hasNext()) { + String line= (String)iter.next(); + int l= length(line); + if (l < line.length()) { // line has delimiter + sb.append(line.substring(0, l)); + sb.append(lineSeparator); + } else { + sb.append(line); + } + } + } + return sb.toString(); + } + + public boolean isPreserveLineDelimeters() { + return false; + } + + public static String getRejected(List failedHunks) { + if (failedHunks.size() <= 0) + return null; + + String lineSeparator= System.getProperty("line.separator"); //$NON-NLS-1$ + StringBuffer sb= new StringBuffer(); + Iterator iter= failedHunks.iterator(); + while (iter.hasNext()) { + Hunk hunk= (Hunk) iter.next(); + sb.append(hunk.getRejectedDescription()); + sb.append(lineSeparator); + sb.append(hunk.getContent()); + } + return sb.toString(); + } + + /* + * Ensures that a file with the given path exists in + * the given container. Folder are created as necessary. + */ + protected IFile createPath(IContainer container, IPath path) throws CoreException { + if (path.segmentCount() > 1) { + IContainer childContainer; + if (container instanceof IWorkspaceRoot) { + IProject project = ((IWorkspaceRoot)container).getProject(path.segment(0)); + if (!project.exists()) + project.create(null); + if (!project.isOpen()) + project.open(null); + childContainer = project; + } else { + IFolder f= container.getFolder(path.uptoSegment(1)); + if (!f.exists()) + f.create(false, true, null); + childContainer = f; + } + return createPath(childContainer, path.removeFirstSegments(1)); + } + // a leaf + return container.getFile(path); + } + + /* + * Returns the length (excluding a line delimiter CR, LF, CR/LF) + * of the given string. + */ + /* package */ static int length(String s) { + int l= s.length(); + if (l > 0) { + char c= s.charAt(l-1); + if (c == '\r') + return l-1; + if (c == '\n') { + if (l > 1 && s.charAt(l-2) == '\r') + return l-2; + return l-1; + } + } + return l; + } + + public IResource getTarget() { + return fTarget; + } + + public void setTarget(IResource target) { + fTarget= target; + } + + + public IFile getTargetFile(FileDiff diff) { + IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + return existsInTarget(path); + } + + /** + * Iterates through all of the resources contained in the Patch Wizard target + * and looks to for a match to the passed in file + * @param path + * @return IFile which matches the passed in path or null if none found + */ + public IFile existsInTarget(IPath path) { + if (fTarget instanceof IFile) { // special case + IFile file= (IFile) fTarget; + if (matches(file.getFullPath(), path)) + return file; + } else if (fTarget instanceof IContainer) { + IContainer c= (IContainer) fTarget; + if (c.exists(path)) + return c.getFile(path); + } + return null; + } + + /** + * Returns true if path completely matches the end of fullpath + * @param fullpath + * @param path + * @return true if path matches, false otherwise + */ + private boolean matches(IPath fullpath, IPath path) { + for (IPath p= fullpath; path.segmentCount()<=p.segmentCount(); p= p.removeFirstSegments(1)) { + if (p.equals(path)) + return true; + } + return false; + } + + public int calculatePrefixSegmentCount() { + //Update prefix count - go through all of the diffs and find the smallest + //path segment contained in all diffs. + int length= 99; + if (fDiffs!=null) + for (int i= 0; i<fDiffs.length; i++) { + FileDiff diff= fDiffs[i]; + length= Math.min(length, diff.segmentCount()); + } + return length; + } + + public void addDiff(FileDiff newDiff){ + FileDiff[] temp = new FileDiff[fDiffs.length + 1]; + System.arraycopy(fDiffs,0, temp, 0, fDiffs.length); + temp[fDiffs.length] = newDiff; + fDiffs = temp; + } + + public void removeDiff(FileDiff diffToRemove){ + FileDiff[] temp = new FileDiff[fDiffs.length - 1]; + int counter = 0; + for (int i = 0; i < fDiffs.length; i++) { + if (fDiffs[i] != diffToRemove){ + temp[counter++] = fDiffs[i]; + } + } + fDiffs = temp; + } + + public void setEnabled(Object element, boolean enabled) { + if (element instanceof DiffProject) + setEnabledProject((DiffProject) element, enabled); + if (element instanceof FileDiff) + setEnabledFile((FileDiff)element, enabled); + if (element instanceof Hunk) + setEnabledHunk((Hunk) element, enabled); + } + + private void setEnabledProject(DiffProject projectDiff, boolean enabled) { + FileDiff[] diffFiles = projectDiff.getFileDiffs(); + for (int i = 0; i < diffFiles.length; i++) { + setEnabledFile(diffFiles[i], enabled); + } + } + + private void setEnabledFile(FileDiff fileDiff, boolean enabled) { + Hunk[] hunks = fileDiff.getHunks(); + for (int i = 0; i < hunks.length; i++) { + setEnabledHunk(hunks[i], enabled); + } + } + + private void setEnabledHunk(Hunk hunk, boolean enabled) { + if (enabled) { + disabledElements.remove(hunk); + FileDiff file = hunk.getParent(); + disabledElements.remove(file); + DiffProject project = file.getProject(); + if (project != null) + disabledElements.remove(project); + } else { + disabledElements.add(hunk); + FileDiff file = hunk.getParent(); + if (disabledElements.containsAll(Arrays.asList(file.getHunks()))) { + disabledElements.add(file); + DiffProject project = file.getProject(); + if (project != null + && disabledElements.containsAll(Arrays.asList(project + .getFileDiffs()))) + disabledElements.add(project); + } + } + } + + public boolean isEnabled(Object element) { + if (disabledElements.contains(element)) + return false; + Object parent = getElementParent(element); + if (parent == null) + return true; + return isEnabled(parent); + } + + protected Object getElementParent(Object element) { + if (element instanceof Hunk) { + Hunk hunk = (Hunk) element; + return hunk.getParent(); + } + return null; + } + + /** + * Calculate the fuzz factor that will allow the most hunks to be matched. + * @param monitor a progress monitor + * @return the fuzz factor or <code>-1</code> if no hunks could be matched + */ + public int guessFuzzFactor(IProgressMonitor monitor) { + try { + monitor.beginTask(Messages.Patcher_2, IProgressMonitor.UNKNOWN); + FileDiff[] diffs= getDiffs(); + if (diffs==null||diffs.length<=0) + return -1; + int fuzz= -1; + for (int i= 0; i<diffs.length; i++) { + FileDiff d= diffs[i]; + IFile file= getTargetFile(d); + if (file != null && file.exists()) { + List lines= load(file, false); + FileDiffResult result = getDiffResult(d); + int f = result.calculateFuzz(lines, monitor); + if (f > fuzz) + fuzz = f; + } + } + return fuzz; + } finally { + monitor.done(); + } + } + + public void refresh() { + diffResults.clear(); + refresh(getDiffs()); + } + + protected void refresh(FileDiff[] diffs) { + for (int i = 0; i < diffs.length; i++) { + FileDiff diff = diffs[i]; + FileDiffResult result = getDiffResult(diff); + ((WorkspaceFileDiffResult)result).refresh(); + } + } + + public FileDiffResult getDiffResult(FileDiff diff) { + FileDiffResult result = (FileDiffResult)diffResults.get(diff); + if (result == null) { + result = new WorkspaceFileDiffResult(diff, getConfiguration()); + diffResults.put(diff, result); + } + return result; + } + + public PatchConfiguration getConfiguration() { + return configuration; + } + + /** + * Return the project that contains this diff or <code>null</code> + * if the patch is not a workspace patch. + * @param diff the diff + * @return the project that contains the diff + */ + public DiffProject getProject(FileDiff diff) { + return diff.getProject(); + } + + /* + * Returns <code>true</code> if new value differs from old. + */ + public boolean setReversed(boolean reverse) { + if (getConfiguration().isReversed() != reverse) { + getConfiguration().setReversed(reverse); + refresh(); + return true; + } + return false; + } + + public boolean isReversed() { + return getConfiguration().isReversed(); + } + + /** + * Cache the contents for the given file diff. These contents + * will be used for the diff when the patch is applied. When the + * patch is applied, it is assumed that the provided contents + * already have all relevant hunks applied. + * @param diff the file diff + * @param contents the contents for the file diff + */ + public void cacheContents(FileDiff diff, byte[] contents) { + contentCache.put(diff, contents); + } + + /** + * Return whether contents have been cached for the + * given file diff. + * @param diff the file diff + * @return whether contents have been cached for the file diff + * @see #cacheContents(FileDiff, byte[]) + */ + public boolean hasCachedContents(FileDiff diff) { + return contentCache.containsKey(diff); + } + + /** + * Return the content lines that are cached for the given + * file diff. + * @param diff the file diff + * @return the content lines that are cached for the file diff + */ + public List getCachedLines(FileDiff diff) { + byte[] contents = (byte[])contentCache.get(diff); + if (contents != null) { + BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(contents))); + return readLines(reader); + } + return null; + } + + /** + * Return the contents that are cached for the given diff or + * <code>null</code> if there is no contents cached. + * @param diff the diff + * @return the contents that are cached for the given diff or + * <code>null</code> + */ + public byte[] getCachedContents(FileDiff diff) { + return (byte[])contentCache.get(diff); + } + + /** + * Return whether the patcher has any cached contents. + * @return whether the patcher has any cached contents + */ + public boolean hasCachedContents() { + return !contentCache.isEmpty(); + } + + /** + * Clear any cached contents. + */ + public void clearCachedContents() { + contentCache.clear(); + mergedHunks.clear(); + } + + public void setProperty(String key, Object value) { + getConfiguration().setProperty(key, value); + } + + public Object getProperty(String key) { + return getConfiguration().getProperty(key); + } + + public boolean isManuallyMerged(Hunk hunk) { + return mergedHunks.contains(hunk); + } + + public void setManuallyMerged(Hunk hunk, boolean merged) { + if (merged) + mergedHunks.add(hunk); + else + mergedHunks.remove(hunk); + } + + public IProject getTargetProject(FileDiff diff) { + DiffProject dp = getProject(diff); + if (dp != null) + return dp.getProject(); + IResource tr = getTarget(); + if (tr instanceof IWorkspaceRoot) { + IWorkspaceRoot root = (IWorkspaceRoot) tr; + return root.getProject(diff.getPath(isReversed()).segment(0)); + } + return tr.getProject(); + } + + public static Patcher getPatcher(PatchConfiguration configuration) { + return (Patcher)configuration.getProperty(PROP_PATCHER); + } + + public boolean hasRejects() { + for (Iterator iterator = diffResults.values().iterator(); iterator.hasNext();) { + FileDiffResult result = (FileDiffResult) iterator.next(); + if (result.hasRejects()) + return true; + } + return false; + } +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Utilities.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Utilities.java new file mode 100644 index 000000000..291fcfea9 --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Utilities.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 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.compare.internal.patch; + +import org.eclipse.compare.internal.core.Activator; +import org.eclipse.core.resources.IEncodedStorage; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; + +public class Utilities { + + public static String getCharset(Object resource) { + if (resource instanceof IEncodedStorage) { + try { + return ((IEncodedStorage)resource).getCharset(); + } catch (CoreException ex) { + Activator.log(ex); + } + } + return ResourcesPlugin.getEncoding(); + } + +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java new file mode 100644 index 000000000..c0690416a --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2007 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.List; + +import org.eclipse.compare.patch.PatchConfiguration; +import org.eclipse.core.resources.*; + +public class WorkspaceFileDiffResult extends FileDiffResult { + + public WorkspaceFileDiffResult(FileDiff diff, + PatchConfiguration configuration) { + super(diff, configuration); + } + + protected boolean canCreateTarget(IStorage storage) { + IProject project = getPatcher().getTargetProject(getDiff()); + return project != null && project.isAccessible(); + } + + protected boolean targetExists(IStorage storage) { + IFile file= (IFile)storage; + return file != null && file.isAccessible(); + } + + protected List getLines(IStorage storage, boolean create) { + IFile file= getTargetFile(); + List lines = Patcher.load(file, create); + return lines; + } + + protected Patcher getPatcher() { + return Patcher.getPatcher(getConfiguration()); + } + + public IFile getTargetFile() { + return getPatcher().getTargetFile(getDiff()); + } + + public void refresh() { + refresh(getTargetFile(), null); + } + +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspacePatcher.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspacePatcher.java new file mode 100644 index 000000000..331e30023 --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspacePatcher.java @@ -0,0 +1,377 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.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 (diffProject.getProject().isAccessible()) + list.addAll(Arrays.asList(getTargetFiles(diffProject))); + } + // validate the files for editing + if (!validator.validateResources((IFile[])list.toArray(new IFile[list.size()]))) { + return; + } + + FileDiff[] 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; + + FileDiff 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 FileDiff.ADDITION : + // patch it and collect rejected hunks + List result= apply(diff, file, true, failed); + if (result != null) + store(createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks)); + workTicks -= WORK_UNIT; + break; + case FileDiff.DELETION : + file.delete(true, true, new SubProgressMonitor(pm, workTicks)); + workTicks -= WORK_UNIT; + break; + case FileDiff.CHANGE : + // patch it and collect rejected hunks + result= apply(diff, file, false, failed); + if (result != null) + store(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(FileDiff diff) { + return isEnabled(diff) && diff.getProject().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(); + FileDiff[] diffs = project.getFileDiffs(); + for (int i = 0; i < diffs.length; i++) { + FileDiff diff = diffs[i]; + if (isEnabled(diff)) { + files.add(getTargetFile(diff)); + } + } + return (IFile[]) files.toArray(new IFile[files.size()]); + } + + public IFile getTargetFile(FileDiff diff) { + IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + DiffProject project = getProject(diff); + if (project != null) + return project.getFile(path); + return super.getTargetFile(diff); + } + + private IPath getFullPath(FileDiff diff) { + IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed()); + DiffProject project = getProject(diff); + if (project != null) + return 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= fDiffProjects[i].getProject(); + // 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 FileDiff && fDiffProjects != null) { + FileDiff diff = (FileDiff) 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(FileDiff diff, IFile file) { + retargetedDiffs.put(diff, diff.getPath(false)); + Hunk[] 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); + FileDiff newDiff = getDiffForFile(file); + for (int i = 0; i < hunks.length; i++) { + Hunk hunk = hunks[i]; + newDiff.add(hunk); + } + } + + private FileDiff getDiffForFile(IFile file) { + DiffProject diffProject = null; + FileDiff[] 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 (diffProjects[i].getProject().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++) { + FileDiff fileDiff = diffsToCheck[i]; + if (isDiffForFile(fileDiff, file)) { + return fileDiff; + } + } + + // Create a new diff for the file + IPath path = getDiffPath(file); + FileDiff newDiff = new FileDiff(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(FileDiff fileDiff, IFile file) { + return getFullPath(fileDiff).equals(file.getFullPath()); + } + + private DiffProject addDiffProjectForProject(IProject project) { + DiffProject[] diffProjects = getDiffProjects(); + DiffProject diffProject = new DiffProject(project); + 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) { + FileDiff newDiff = getDiffForFile(file); + newDiff.add(hunk); + } + + public void retargetProject(DiffProject project, IProject targetProject) { + retargetedDiffs.put(project, project.getProject().getFullPath()); + FileDiff[] 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 (projects[i].getProject().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(); + } + +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch.java new file mode 100644 index 000000000..21646d916 --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.compare.patch; + +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * A representation of a file patch that can be applied to an input stream. + * <p> + * This interface is not intended to be implemented by clients. Clients can + * obtain file patches by calling + * {@link ApplyPatchOperation#parsePatch(org.eclipse.core.resources.IStorage)}. + * </p> + * + * @see ApplyPatchOperation#parsePatch(org.eclipse.core.resources.IStorage) + * @since 3.3 + * @noimplement + */ +public interface IFilePatch { + + /** + * Special constant that will be returned from get getBeforeDate() or + * getAfterDate() if the date is unknown. Equal to Midnight, Jan 1, 1970 + * GMT. + * + * @since 3.4 + */ + public static long DATE_UNKNOWN = 0; + + /** + * Return the target path for this patch. The target path may differ + * depending on whether the patch is being reversed or not. + * + * @param configuration the patch configuration + * @return the target path for this patch + * @see PatchConfiguration#isReversed() + */ + public IPath getTargetPath(PatchConfiguration configuration); + + /** + * Apply this patch to the given file contents. The result provides the + * original and patch contents and also indicates whether some portions of + * the patch (called hunks) failed to apply. + * + * @param contents the file contents + * @param configuration the patch configuration + * @param monitor a progress monitor + * @return the result of the patch application + */ + public IFilePatchResult apply(IStorage contents, + PatchConfiguration configuration, IProgressMonitor monitor); + + /** + * Return the header information of the patch or + * <code>null</code> if there was no header text. + * The header may be multi-line. + * @return the header information of the patch or + * <code>null</code> + */ + public String getHeader(); + + /** + * Returns the milliseconds time value of the before date from the patch, or + * DATE_UNKNOWN if the date is unknown. + * + * @return milliseconds time value of the before date from the patch + * @since 3.4 + */ + public long getBeforeDate(); + + /** + * Returns the milliseconds time value of the after date from the patch, or + * DATE_UNKNOWN if the date is unknown. + * + * @return milliseconds time value of the after date from the patch + * @since 3.4 + */ + public long getAfterDate(); +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java new file mode 100644 index 000000000..4f229c3aa --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2007 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.patch; + +import java.io.InputStream; + +import org.eclipse.core.resources.IEncodedStorage; +import org.eclipse.core.runtime.CoreException; + +/** + * A file patch result provides the results of an attempt to apply an + * {@link IFilePatch} to the contents of a file. * + * <p> + * This interface is not intended to be implemented by clients. Clients can + * obtain patch results from an {@link IFilePatch}. + * </p> + * + * @see IFilePatch + * @since 3.3 + */ +public interface IFilePatchResult { + + /** + * Return a stream the contains the original contents of the file before + * any portions of the patch have been applied. + * @return a stream to the original contents of the file before + * any portions of the patch have been applied + * @see #getPatchedContents() + */ + public InputStream getOriginalContents(); + + /** + * Return a stream that contains the file with as much of the patch + * applied as possible. if {@link #hasMatches()} returns <code>false</code> + * then the patched contents will match the original contents. Otherwise, + * at least a portion of the patch could be successfully applied. if + * {@link #hasRejects()} returns <code>false</code>, then the entire patch was + * applied. Otherwise, portions could not be applied. The portions that could + * not be applied can be obtained by calling {@link #getRejects()}. + * + * @return a stream that contains the file with as much of the patch + * applied as possible. + */ + public InputStream getPatchedContents(); + + /** + * Return whether the patch has portions that were successfully applied. + * @return whether the patch has portions that were successfully applied + * @see #getPatchedContents() + */ + public boolean hasMatches(); + + /** + * Return whether the patch has portions that were not successfully applied. + * @return whether the patch has portions that were not successfully applied + * @see #getPatchedContents() + */ + public boolean hasRejects(); + + /** + * Return the portions of the patch (referred to a hunks) that could not + * be applied. + * @return the portions of the patch (referred to a hunks) that could not + * be applied + * @see #getPatchedContents() + */ + public IHunk[] getRejects(); + + /** + * Returns the name of a charset encoding to be used when decoding the contents + * of this result into characters. Returns <code>null</code> if a proper + * encoding cannot be determined. + * <p> + * Note that this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * <code>UnsupportedEncodingException</code> where this charset is used. + * </p> + * + * @return the name of a charset, or <code>null</code> + * @exception CoreException if an error happens while determining + * the charset. See any refinements for more information. + * @see IEncodedStorage + */ + public String getCharset() throws CoreException; + +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java new file mode 100644 index 000000000..126150c0d --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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.patch; + +import java.io.InputStream; + +import org.eclipse.core.resources.IEncodedStorage; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; + +/** + * Interface that represents a hunk. A hunk is a portion of a patch. It + * identifies where the hunk is to be located in the target file. One use of + * this interface is a means to communicate to content merge viewers that one of + * the sides of a compare input is a patch hunk. Clients can determine which + * side it is by adapting the side to this interface (see {@link IAdaptable}. + * <p> + * This interface is not intended to be implemented by clients but can be + * obtained from an {@link IFilePatchResult} + * </p> + * + * @since 3.3 + * + */ +public interface IHunk { + + /** + * Return a label that can be used to describe the hunk. + * @return a label that can be used to describe the hunk + */ + public String getLabel(); + + /** + * Return the start position of the hunk in the target file. + * + * @return the start position of the hunk in the target file. + */ + public int getStartPosition(); + + /** + * Return the original contents from which the hunk was generated. + * The returned contents usually only represent a portion of the + * file from which the hunk was generated. + * @return the original contents from which the hunk was generated + */ + public InputStream getOriginalContents(); + + /** + * Return the contents that contain the modifications for this hunk. + * The returned contents usually only represent a portion of the + * file that was modified. + * @return the contents that contain the modifications for this hunk + */ + public InputStream getPatchedContents(); + + /** + * Returns the name of a charset encoding to be used when decoding the contents + * of this hunk into characters. Returns <code>null</code> if a proper + * encoding cannot be determined. + * <p> + * Note that this method does not check whether the result is a supported + * charset name. Callers should be prepared to handle + * <code>UnsupportedEncodingException</code> where this charset is used. + * </p> + * + * @return the name of a charset, or <code>null</code> + * @exception CoreException if an error happens while determining + * the charset. See any refinements for more information. + * @see IEncodedStorage + */ + public String getCharset() throws CoreException; + + +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java new file mode 100644 index 000000000..3287a4585 --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2007 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.patch; + +import java.util.HashMap; + +/** + * A patch configuration allows clients to set parameters that control how a patch + * is applied. + * <p> + * This class may be instantiated by clients but is not intended to be subclassed. + * </p> + * @see IFilePatch + * @since 3.3 + */ +public class PatchConfiguration { + + private int fStripPrefixSegments; + private int fFuzz; + private boolean fIgnoreWhitespace= false; + private boolean fReverse= false; + private HashMap properties = new HashMap(); + + /** + * Return whether the patch should be reversed when applied. + * @return whether the patch should be reversed when applied + */ + public boolean isReversed() { + return fReverse; + } + + /** + * Set whether the patch should be reversed when applied. + * @param reversed whether the patch should be reversed when applied + */ + public void setReversed(boolean reversed) { + this.fReverse = reversed; + } + + /** + * Return the number of prefix segments to be stripped when attempting + * to apply a patch. + * @return the number of prefix segments to be stripped when attempting + * to apply a patch + */ + public int getPrefixSegmentStripCount() { + return fStripPrefixSegments; + } + + /** + * Set the number of prefix segments to be stripped when attempting + * to apply a patch. + * @param stripCount the number of prefix segments to be stripped when attempting + * to apply a patch. + */ + public void setPrefixSegmentStripCount(int stripCount) { + this.fStripPrefixSegments = stripCount; + } + + /** + * Return the fuzz factor to be used when applying a patch. + * If the fuzz factor is set to -1, then the patcher is to make a best + * effort to apply the patch by adjusting the fuzz factor + * accordingly. + * @return the fuzz factor to be used when applying a patch. + */ + public int getFuzz() { + return fFuzz; + } + + /** + * Set the fuzz factor to be used when applying a patch. + * @param fuzz the fuzz factor to be used when applying a patch. + */ + public void setFuzz(int fuzz) { + fFuzz = fuzz; + } + + /** + * Return whether whitespace should be ignored. + * @return whether whitespace should be ignored + */ + public boolean isIgnoreWhitespace() { + return fIgnoreWhitespace; + } + + /** + * Set whether whitespace should be ignored + * @param ignoreWhitespace whether whitespace should be ignored + */ + public void setIgnoreWhitespace(boolean ignoreWhitespace) { + fIgnoreWhitespace = ignoreWhitespace; + } + + /** + * Return the property associated with the given key or + * <code>null</code> if there is no property for the key. + * @param key the key + * @return the property associated with the given key or + * <code>null</code> + */ + public Object getProperty(String key) { + return properties.get(key); + } + + /** + * Set the property associated with the given key + * @param key the key + * @param value the value to be associated with the key + */ + public void setProperty(String key, Object value) { + properties.put(key, value); + } +} diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java new file mode 100644 index 000000000..f734a957a --- /dev/null +++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 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.compare.patch; + +import java.io.BufferedReader; +import java.io.IOException; + +import org.eclipse.compare.internal.core.Activator; +import org.eclipse.compare.internal.patch.PatchReader; +import org.eclipse.compare.internal.patch.Patcher; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +/** + * Helper class for parsing patches. + */ +public class PatchParser { + + /** + * Return whether the given storage contains a patch. + * @param storage the storage + * @return whether the given storage contains a patch + * @throws CoreException if an error occurs reading the contents from the storage + */ + public static boolean isPatch(IStorage storage) throws CoreException { + return parsePatch(storage).length > 0; + } + + /** + * Parse the given patch and return the set of file patches that it contains. + * @param storage the storage that contains the patch + * @return the set of file patches that the storage contains + * @throws CoreException if an error occurs reading the contents from the storage + */ + public static IFilePatch[] parsePatch(IStorage storage) throws CoreException { + BufferedReader reader = Patcher.createReader(storage); + try { + PatchReader patchReader= new PatchReader(); + patchReader.parse(reader); + return patchReader.getAdjustedDiffs(); + } catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, e.getMessage(), e)); + } finally { + try { + reader.close(); + } catch (IOException e) { //ignored + } + } + } +} |