org.eclipse.team.internal.ccvs.ui.operations.DiffOperation.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.team.internal.ccvs.ui.operations.DiffOperation.java

Source

/*******************************************************************************
 * Copyright (c) 2005, 2012 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.team.internal.ccvs.ui.operations;

import java.io.*;
import java.util.*;

import org.eclipse.compare.patch.WorkspacePatcherUI;
import org.eclipse.core.resources.*;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.*;
import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption;
import org.eclipse.team.internal.ccvs.core.client.listeners.DiffListener;
import org.eclipse.team.internal.ccvs.core.connection.CVSCommunicationException;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.ui.CVSUIMessages;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.statushandlers.*;

public abstract class DiffOperation extends SingleCommandOperation {

    private static final int UNIFIED_FORMAT = 0;
    private static final int CONTEXT_FORMAT = 1;
    private static final int STANDARD_FORMAT = 2;

    protected boolean isMultiPatch;
    protected boolean includeFullPathInformation;
    protected PrintStream stream;
    protected IPath patchRoot;
    protected boolean patchHasContents;
    protected boolean patchHasNewFiles;

    /* see bug 116427 */
    private Object destination = null;

    /* see bug 159894 */
    private class CustomizableEOLPrintStream extends PrintStream {

        private boolean error = false;

        private String defaultLineEnding = "\n"; //$NON-NLS-1$

        public CustomizableEOLPrintStream(PrintStream openStream) {
            super(openStream);
            if (CVSProviderPlugin.getPlugin().isUsePlatformLineend()) {
                defaultLineEnding = System.getProperty("line.separator"); //$NON-NLS-1$
            }
        }

        public boolean checkError() {
            return error || super.checkError();
        }

        public void println() {
            try {
                write(defaultLineEnding.getBytes());
            } catch (IOException e) {
                error = true;
            }
        }

        public void println(boolean x) {
            print(x);
            println();
        }

        public void println(char x) {
            print(x);
            println();
        }

        public void println(char[] x) {
            print(x);
            println();
        }

        public void println(double x) {
            print(x);
            println();
        }

        public void println(float x) {
            print(x);
            println();
        }

        public void println(int x) {
            print(x);
            println();
        }

        public void println(long x) {
            print(x);
            println();
        }

        public void println(Object x) {
            print(x);
            println();
        }

        public void println(String x) {
            print(x);
            println();
        }
    }

    public DiffOperation(IWorkbenchPart part, ResourceMapping[] mappings, LocalOption[] options,
            boolean isMultiPatch, boolean includeFullPathInformation, IPath patchRoot, Object destination) {
        super(part, mappings, options);
        this.isMultiPatch = isMultiPatch;
        this.includeFullPathInformation = includeFullPathInformation;
        this.patchRoot = patchRoot;
        this.patchHasContents = false;
        this.patchHasNewFiles = false;
        this.destination = destination;
    }

    protected boolean shouldRun() {
        if (super.shouldRun() == false) {
            return false;
        }
        Job[] jobs = Job.getJobManager().find(destination);
        if (jobs.length != 0) {
            MessageDialog question = new MessageDialog(getShell(),
                    CVSUIMessages.DiffOperation_CreatePatchConflictTitle, null,
                    NLS.bind(CVSUIMessages.DiffOperation_CreatePatchConflictMessage, destination.toString()),
                    MessageDialog.QUESTION, new String[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL },
                    1);
            if (question.open() == 0) {
                Job.getJobManager().cancel(destination);
            } else {
                return false;
            }
        }
        return true;
    }

    public void execute(IProgressMonitor monitor) throws CVSException, InterruptedException {
        try {
            stream = new CustomizableEOLPrintStream(openStream());
            if (isMultiPatch) {
                stream.println(WorkspacePatcherUI.getWorkspacePatchHeader());
            }
            super.execute(monitor);
        } finally {
            if (stream != null) {
                stream.close();
            }
        }
    }

    /**
     * Open and return a stream for the diff output.
     * @return a stream for the diff output
     */
    protected abstract PrintStream openStream() throws CVSException;

    private static Comparator COMPARATOR = new Comparator() {
        private int compare(IResource r1, IResource r2) {
            return r1.getFullPath().toString().compareTo(r2.getFullPath().toString());
        }

        public int compare(Object o1, Object o2) {
            IResource r1 = null;
            IResource r2 = null;
            if (o1 instanceof ICVSResource) {
                r1 = ((ICVSResource) o1).getIResource();
            } else {
                r1 = (IResource) o1;
            }
            if (o2 instanceof ICVSResource) {
                r2 = ((ICVSResource) o2).getIResource();
            } else {
                r2 = (IResource) o2;
            }
            return compare(r1, r2);
        }
    };

    protected void execute(CVSTeamProvider provider, IResource[] resources, boolean recurse,
            IProgressMonitor monitor) throws CVSException, InterruptedException {

        //add this project to the total projects encountered
        final HashSet newFiles = new HashSet(); //array of ICVSResource - need HashSet to guard for duplicate entries
        final HashSet existingFiles = new HashSet(); //array of IResource - need HashSet to guard for duplicate entries

        monitor.beginTask(null, 100);
        final IProgressMonitor subMonitor = Policy.subMonitorFor(monitor, 10);
        for (int i = 0; i < resources.length; i++) {
            IResource resource = resources[i];
            ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
            cvsResource.accept(new ICVSResourceVisitor() {
                public void visitFile(ICVSFile file) throws CVSException {
                    if (!(file.isIgnored())) {
                        if (!file.isManaged() || file.getSyncInfo().isAdded()) {
                            //this is a new file
                            if (file.exists())
                                newFiles.add(file);
                        } else if (file.isModified(subMonitor)) {
                            existingFiles.add(file.getIResource());
                        }
                    }
                }

                public void visitFolder(ICVSFolder folder) throws CVSException {
                    // Even if we are not supposed to recurse we still need to go into
                    // the root directory.
                    if (!folder.exists() || folder.isIgnored()) {
                        return;
                    }

                    folder.acceptChildren(this);

                }
            }, recurse);
        }

        final SortedSet allFiles = new TreeSet(COMPARATOR);
        allFiles.addAll(existingFiles);
        allFiles.addAll(newFiles);

        subMonitor.done();

        //Check options 
        //Append our diff output to the server diff output.
        // Our diff output includes new files and new files in new directories.
        int format = STANDARD_FORMAT;

        LocalOption[] localoptions = getLocalOptions(recurse);
        for (int i = 0; i < localoptions.length; i++) {
            LocalOption option = localoptions[i];
            if (option.equals(Diff.UNIFIED_FORMAT) || isMultiPatch) {
                format = UNIFIED_FORMAT;
            } else if (option.equals(Diff.CONTEXT_FORMAT)) {
                format = CONTEXT_FORMAT;
            }
        }

        boolean haveAddedProjectHeader = false;

        if (!existingFiles.isEmpty()) {
            if (isMultiPatch && !haveAddedProjectHeader) {
                haveAddedProjectHeader = true;
                IProject project = resources[0].getProject();
                stream.println(WorkspacePatcherUI.getWorkspacePatchProjectHeader(project));
            }
        }

        if (!newFiles.isEmpty() && Diff.INCLUDE_NEWFILES.isElementOf(localoptions)) {
            //Set new file to flag to let us know that we have added something to the current patch
            patchHasNewFiles = true;

            if (isMultiPatch && !haveAddedProjectHeader) {
                haveAddedProjectHeader = true;
                IProject project = resources[0].getProject();
                stream.println(WorkspacePatcherUI.getWorkspacePatchProjectHeader(project));
            }
        }

        List existingFilesSubList = new ArrayList();
        for (Iterator iter = allFiles.iterator(); iter.hasNext();) {
            Object file = iter.next();
            if (existingFiles.contains(file)) {
                existingFilesSubList.add(file);
            } else if (newFiles.contains(file)) {
                addExistingFilesSubListToDiff(provider, existingFilesSubList, recurse, monitor,
                        existingFiles.size());
                ICVSFile cvsFile = (ICVSFile) file;
                addFileToDiff(getNewFileRoot(cvsFile), cvsFile, stream, format);
            }
        }
        addExistingFilesSubListToDiff(provider, existingFilesSubList, recurse, monitor, existingFiles.size());

        monitor.done();
    }

    private void addExistingFilesSubListToDiff(CVSTeamProvider provider, Collection subList, boolean recurse,
            IProgressMonitor monitor, int existingFilesTotal) throws InterruptedException {
        if (!subList.isEmpty()) {
            int ticks = 90 * subList.size() / existingFilesTotal;
            try {
                super.execute(provider, (IResource[]) subList.toArray(new IResource[subList.size()]), recurse,
                        Policy.subMonitorFor(monitor, ticks));
            } catch (CVSCommunicationException ex) { // see bug 123430
                StatusAdapter adapter = new StatusAdapter(ex.getStatus());
                adapter.setProperty(IStatusAdapterConstants.TITLE_PROPERTY,
                        CVSUIMessages.DiffOperation_ErrorsOccurredWhileCreatingThePatch);
                StatusManager.getManager().handle(adapter, StatusManager.SHOW | StatusManager.NONE);
            } catch (CVSException ex) {
                handleCVSException(ex);
            }
            subList.clear();
        }
    }

    /**
     * Checks if the exception contain a status that has to be shown to the
     * user. If yes, the method shows the dialog.
     * 
     * @param ex exception to handle
     */
    private void handleCVSException(CVSException ex) {
        IStatus status = ex.getStatus();
        List toShow = new ArrayList();
        IStatus children[] = status.getChildren();
        boolean may = true;
        for (int i = 0; i < children.length; i++) {
            // ignore all errors except those found by DiffListener
            if (children[i].getCode() == CVSStatus.BINARY_FILES_DIFFER
                    || children[i].getCode() == CVSStatus.PROTOCOL_ERROR
                    || children[i].getCode() == CVSStatus.ERROR_LINE) {
                toShow.add(children[i]);
                if (children[i].getCode() == CVSStatus.BINARY_FILES_DIFFER)
                    // the patch does not contain some changes for sure
                    may = false;
            }
        }
        if (toShow.size() > 0) {
            String msg = may ? CVSUIMessages.DiffOperation_ThePatchMayNotContainAllTheChanges
                    : CVSUIMessages.DiffOperation_ThePatchDoesNotContainAllTheChanges;
            StatusAdapter adapter = new StatusAdapter(new MultiStatus(CVSProviderPlugin.ID, CVSStatus.SERVER_ERROR,
                    (IStatus[]) toShow.toArray(new IStatus[toShow.size()]),
                    CVSUIMessages.DiffOperation_ErrorsOccurredWhileCreatingThePatch, null));
            adapter.setProperty(IStatusAdapterConstants.TITLE_PROPERTY, msg);
            StatusManager.getManager().handle(adapter, StatusManager.SHOW | StatusManager.LOG);
        }
    }

    private ICVSFolder getNewFileRoot(ICVSFile cvsFile) {
        ICVSFolder patchRootFolder = getPatchRootFolder();
        if (patchRootFolder != null)
            return patchRootFolder;
        return CVSWorkspaceRoot.getCVSFolderFor(cvsFile.getIResource().getProject());
    }

    protected IStatus executeCommand(Session session, CVSTeamProvider provider, ICVSResource[] resources,
            boolean recurse, IProgressMonitor monitor) throws CVSException, InterruptedException {

        DiffListener diffListener = new DiffListener(stream);

        IStatus status = Command.DIFF.execute(session, Command.NO_GLOBAL_OPTIONS, getLocalOptions(recurse),
                resources, diffListener, monitor);

        //Once any run of the Diff commands reports that it has written something to the stream, the patch 
        //in its entirety is considered non-empty - until then keep trying to set the flag.
        if (!patchHasContents)
            patchHasContents = diffListener.wroteToStream();

        return status;
    }

    protected String getTaskName(CVSTeamProvider provider) {
        return NLS.bind(CVSUIMessages.DiffOperation_0, new String[] { provider.getProject().getName() });
    }

    protected String getTaskName() {
        return CVSUIMessages.DiffOperation_1;
    }

    Map getProviderTraversalMapping(IProgressMonitor monitor) throws CoreException {
        Map providerTraversal = super.getProviderTraversalMapping(monitor);
        SortedMap result = new TreeMap(new Comparator() {
            public int compare(Object o1, Object o2) {
                CVSTeamProvider p1 = (CVSTeamProvider) o1;
                CVSTeamProvider p2 = (CVSTeamProvider) o2;
                return COMPARATOR.compare(p1.getProject(), p2.getProject());
            }
        });
        result.putAll(providerTraversal);
        return result;
    }

    private void addFileToDiff(ICVSFolder patchRoot, ICVSFile file, PrintStream printStream, int format)
            throws CVSException {

        String nullFilePrefix = ""; //$NON-NLS-1$
        String newFilePrefix = ""; //$NON-NLS-1$
        String positionInfo = ""; //$NON-NLS-1$
        String linePrefix = ""; //$NON-NLS-1$

        String pathString = ""; //$NON-NLS-1$

        //get the path string for this file
        pathString = file.getRelativePath(patchRoot);

        int lines = 0;
        BufferedReader fileReader = new BufferedReader(new InputStreamReader(file.getContents()));
        try {
            while (fileReader.readLine() != null) {
                lines++;
            }
        } catch (IOException e) {
            throw CVSException.wrapException(file.getIResource(),
                    NLS.bind(CVSUIMessages.DiffOperation_ErrorAddingFileToDiff, new String[] { pathString }), e);
        } finally {
            try {
                fileReader.close();
            } catch (IOException e1) {
                //ignore
            }
        }

        // Ignore empty files
        if (lines == 0)
            return;

        switch (format) {
        case UNIFIED_FORMAT:
            nullFilePrefix = "--- "; //$NON-NLS-1$
            newFilePrefix = "+++ "; //$NON-NLS-1$
            positionInfo = "@@ -0,0 +1," + lines + " @@"; //$NON-NLS-1$ //$NON-NLS-2$
            linePrefix = "+"; //$NON-NLS-1$
            break;

        case CONTEXT_FORMAT:
            nullFilePrefix = "*** "; //$NON-NLS-1$
            newFilePrefix = "--- "; //$NON-NLS-1$
            positionInfo = "--- 1," + lines + " ----"; //$NON-NLS-1$ //$NON-NLS-2$
            linePrefix = "+ "; //$NON-NLS-1$
            break;

        default:
            positionInfo = "0a1," + lines; //$NON-NLS-1$
            linePrefix = "> "; //$NON-NLS-1$
            break;
        }

        fileReader = new BufferedReader(new InputStreamReader(file.getContents()));
        try {

            printStream.println("Index: " + pathString); //$NON-NLS-1$
            printStream.println("==================================================================="); //$NON-NLS-1$
            printStream.println("RCS file: " + pathString); //$NON-NLS-1$
            printStream.println("diff -N " + pathString); //$NON-NLS-1$

            if (format != STANDARD_FORMAT) {
                printStream.println(nullFilePrefix + "/dev/null   1 Jan 1970 00:00:00 -0000"); //$NON-NLS-1$
                // Technically this date should be the local file date but nobody really cares.
                printStream.println(newFilePrefix + pathString + "   1 Jan 1970 00:00:00 -0000"); //$NON-NLS-1$
            }

            if (format == CONTEXT_FORMAT) {
                printStream.println("***************"); //$NON-NLS-1$
                printStream.println("*** 0 ****"); //$NON-NLS-1$
            }

            printStream.println(positionInfo);

            for (int i = 0; i < lines - 1; i++) {
                printStream.print(linePrefix);
                printStream.println(fileReader.readLine());
            }

            printStream.print(linePrefix);
            readLastLine(fileReader, printStream);
        } catch (IOException e) {
            throw CVSException.wrapException(file.getIResource(),
                    NLS.bind(CVSUIMessages.DiffOperation_ErrorAddingFileToDiff, new String[] { pathString }), e);
        } finally {
            try {
                fileReader.close();
            } catch (IOException e1) {
            }
        }
    }

    // based on org.eclipse.compare.internal.core.patch.LineReader.readLine()
    private void readLastLine(BufferedReader reader, PrintStream printStream) throws IOException {
        boolean sawCRorLF = false;
        boolean sawEOF = false;
        // TODO: hardcoded, set to the same value as initially in LineReader
        boolean ignoreSingleCR = false;
        while (!sawEOF) {
            int c = reader.read();
            if (c == -1) {
                sawEOF = true;
                break;
            }
            printStream.print((char) c);
            if (c == '\n') {
                sawCRorLF = true;
                break;
            }
            if (c == '\r') {
                sawCRorLF = true;
                c = reader.read();
                if (c == -1) {
                    sawEOF = true;
                    break; // EOF
                }
                if (c != '\n') {
                    if (ignoreSingleCR) {
                        sawCRorLF = false;
                        printStream.print((char) c);
                        continue;
                    }
                } else { // '\n'
                    printStream.print((char) c);
                }
                break;
            }
        }
        if (!sawCRorLF) {
            printStream.println();
            printStream.println("\\ No newline at end of file"); //$NON-NLS-1$
        }
    }

    public void setStream(PrintStream stream) {
        this.stream = new CustomizableEOLPrintStream(stream);
    }

    protected void reportEmptyDiff() {
        StatusAdapter adapter = new StatusAdapter(
                new Status(IStatus.INFO, CVSProviderPlugin.ID, CVSUIMessages.GenerateCVSDiff_noDiffsFoundMsg));
        adapter.setProperty(IStatusAdapterConstants.TITLE_PROPERTY,
                CVSUIMessages.GenerateCVSDiff_noDiffsFoundTitle);
        StatusManager.getManager().handle(adapter, StatusManager.SHOW);
    }

    protected ICVSFolder getLocalRoot(CVSTeamProvider provider) throws CVSException {
        ICVSFolder root = getPatchRootFolder();
        if (root != null)
            return root;
        return super.getLocalRoot(provider);
    }

    private ICVSFolder getPatchRootFolder() {
        if (!isMultiPatch && !includeFullPathInformation) {
            //Check to see if the selected patchRoot has enough segments to consider it a folder/resource
            //if not just get the project

            IResource patchFolder = null;
            IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
            if (patchRoot.segmentCount() > 1) {
                patchFolder = root.getFolder(patchRoot);
            } else {
                patchFolder = root.getProject(patchRoot.toString());
            }

            ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(patchFolder);
            if (!cvsResource.isFolder()) {
                cvsResource = cvsResource.getParent();
            }
            return (ICVSFolder) cvsResource;
        }
        return null;
    }

    /* (non-Javadoc)
     * @see org.eclipse.team.internal.ccvs.ui.operations.RepositoryProviderOperation#consultModelsForMappings()
     */
    public boolean consultModelsForMappings() {
        return false;
    }

    public boolean belongsTo(Object family) {
        if (family != null && family.equals(destination))
            return true;
        return super.belongsTo(family);
    }

}