de.loskutov.anyedit.actions.ConvertAllAction.java Source code

Java tutorial

Introduction

Here is the source code for de.loskutov.anyedit.actions.ConvertAllAction.java

Source

/*******************************************************************************
 * Copyright (c) 2009 Andrey Loskutov.
 * 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
 * Contributor:  Andrey Loskutov - initial API and implementation
 *******************************************************************************/

package de.loskutov.anyedit.actions;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionDelegate;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import org.eclipse.ui.PlatformUI;

import de.loskutov.anyedit.AnyEditToolsPlugin;
import de.loskutov.anyedit.IAnyEditConstants;
import de.loskutov.anyedit.Messages;
import de.loskutov.anyedit.ui.preferences.CombinedPreferences;
import de.loskutov.anyedit.util.EclipseUtils;
import de.loskutov.anyedit.util.LineReplaceResult;
import de.loskutov.anyedit.util.TextReplaceResultSet;

@SuppressWarnings("unused")
public class ConvertAllAction extends Action implements IActionDelegate, IWorkbenchWindowActionDelegate {

    protected List<IFile> selectedFiles;

    protected List<IResource> selectedResources;

    protected static final int MODIFIED = 1 << 0;

    protected static final int SKIPPED = 1 << 1;

    protected static final int ERROR = 1 << 2;

    protected static final IContentType TEXT_TYPE = Platform.getContentTypeManager()
            .getContentType("org.eclipse.core.runtime.text");

    public ConvertAllAction() {
        super();
        selectedFiles = new ArrayList<IFile>();
        selectedResources = new ArrayList<IResource>();
    }

    @Override
    public void run(IAction action) {
        // selectedFiles contains all files for convert.
        WorkspaceJob job = new ConvertJob("Converting 'Tabs<->Spaces'", new ArrayList<IFile>(selectedFiles));
        selectedResources.clear();
        selectedFiles.clear();
        job.schedule();
        PlatformUI.getWorkbench().getProgressService().showInDialog(AnyEditToolsPlugin.getShell(), job);
    }

    protected static final class ConvertJob extends WorkspaceJob {
        private final Shell shell;
        private final Spaces spacesAction;
        private final List<IFile> selectedFiles;
        private final ITextFileBufferManager fbManager;

        public ConvertJob(String name, List<IFile> selectedFiles) {
            super(name);
            this.selectedFiles = selectedFiles;
            spacesAction = new Spaces();
            spacesAction.setUsedOnSave(false);
            shell = AnyEditToolsPlugin.getShell();
            fbManager = FileBuffers.getTextFileBufferManager();
        }

        @Override
        public IStatus runInWorkspace(IProgressMonitor monitor) {
            monitor.beginTask(Messages.ConvertAll_task, selectedFiles.size());
            int filesToConvert = selectedFiles.size();
            IPreferenceStore preferenceStore = AnyEditToolsPlugin.getDefault().getPreferenceStore();

            boolean saveIfDirty = preferenceStore.getBoolean(IAnyEditConstants.SAVE_DIRTY_BUFFER);
            int modified = 0;
            int skipped = 0;
            List<IStatus> errors = new ArrayList<IStatus>();
            long start = System.currentTimeMillis();

            try {

                for (int i = 0; i < filesToConvert && !monitor.isCanceled(); i++) {
                    monitor.internalWorked(1);
                    IFile file = selectedFiles.get(i);
                    try {
                        int result = performAction(file, saveIfDirty, monitor);
                        if (result == ERROR) {
                            errors.add(new Status(IStatus.ERROR, AnyEditToolsPlugin.getId(),
                                    "'Tabs<->Spaces' operation failed for file: " + file, null));
                        } else if (result == MODIFIED) {
                            modified++;
                        } else if (result == SKIPPED) {
                            skipped++;
                        }
                    } catch (CoreException e) {
                        errors.add(new Status(IStatus.ERROR, AnyEditToolsPlugin.getId(),
                                "'Tabs<->Spaces' operation failed for file: " + file, e));
                    }
                }
            } finally {
                monitor.done();
                long stop = System.currentTimeMillis();
                long msec = (stop - start);
                AnyEditToolsPlugin.logInfo("Tabs<->Spaces: modified " + modified + " files from " + filesToConvert
                        + ", ignored " + skipped + ", failed on: " + errors.size() + " (" + msec + " ms)");

            }
            if (errors.size() == 0) {
                if (monitor.isCanceled()) {
                    AnyEditToolsPlugin.logError("'Tabs<->Spaces' operation cancelled by user", null);
                    return Status.CANCEL_STATUS;
                }
                return Status.OK_STATUS;
            }
            MultiStatus error = new MultiStatus(AnyEditToolsPlugin.getId(), IStatus.ERROR,
                    "'Tabs<->Spaces' operation failed for " + errors.size()
                            + " files. Please check log for details.",
                    null);
            for (IStatus status : errors) {
                error.add(status);
            }
            return error;
        }

        private int performAction(IFile file, boolean saveIfDirty, IProgressMonitor monitor) throws CoreException {

            // set current file to action (required to get tab width from)
            spacesAction.setFile(file);
            CombinedPreferences preferences = spacesAction.getCombinedPreferences();

            String filterPerf = preferences.getString(IAnyEditConstants.PREF_ACTIVE_FILTERS_LIST);
            String[] filters = EclipseUtils.parseList(filterPerf);
            String actionId;
            if (spacesAction.isDefaultTabToSpaces()) {
                actionId = AbstractTextAction.ACTION_ID_CONVERT_TABS;
            } else {
                actionId = AbstractTextAction.ACTION_ID_CONVERT_SPACES;
            }

            // 1) get file name. filter all excluded in preferences
            if (matchFilter(file, filters)) {
                return SKIPPED;
            }

            // 2) get content type. filter all non-text files
            if (hasWrongContentType(file, monitor)) {
                return SKIPPED;
            }

            // do the main work
            return convertFile(actionId, file, saveIfDirty, monitor);
        }

        private int convertFile(final String actionId, IFile file, boolean saveIfDirty, IProgressMonitor monitor)
                throws CoreException {
            int result = ERROR;
            IPath fullPath = file.getFullPath();
            try {
                fbManager.connect(fullPath, LocationKind.IFILE, new SubProgressMonitor(monitor, 1));
                monitor.subTask(fullPath.makeRelative().toString());
                final ITextFileBuffer fileBuffer = fbManager.getTextFileBuffer(fullPath, LocationKind.IFILE);

                if (!file.isSynchronized(IResource.DEPTH_ZERO)) {
                    file.refreshLocal(IResource.DEPTH_ZERO, monitor);
                }
                // check if buffer is opened by some editor - save it first, if required
                boolean wasDirty = fileBuffer.isDirty();
                if (saveIfDirty && wasDirty && fileBuffer.isCommitable()) {
                    fileBuffer.commit(new SubProgressMonitor(monitor, 2), false);
                }

                // 4) perform convert in-memory
                result = convertBuffer(actionId, file, fileBuffer, monitor);

                if (result == MODIFIED && (!wasDirty || saveIfDirty)) {
                    if (fileBuffer.isShared()) {
                        // convertBuffer() should checkout the file, but...
                        // because convertBuffer() operation was running in the UI thread,
                        // the checkout file operation task may be still incomplete
                        // second call to check-out file from VCS, if any
                        fileBuffer.validateState(monitor, shell);
                    }
                    fileBuffer.commit(new SubProgressMonitor(monitor, 2), false);
                }

            } finally {
                // clean up - action shouldn't have old file reference
                spacesAction.setFile(null);
                fbManager.disconnect(fullPath, LocationKind.IFILE, new SubProgressMonitor(monitor, 1));
            }
            return result;
        }

        private int convertBuffer(String actionId, final IFile file, ITextFileBuffer fileBuffer,
                IProgressMonitor monitor) throws CoreException {
            final IDocument document = fileBuffer.getDocument();
            final TextReplaceResultSet resultSet = spacesAction.estimateActionRange(document);
            // no lines affected - return immediately
            if (resultSet.getNumberOfLines() == 0) {
                return SKIPPED;
            }

            // perform memory based replace, the result will contain all changed lines
            try {
                spacesAction.doTextOperation(document, actionId, resultSet);
            } catch (Exception ex) {
                AnyEditToolsPlugin.logError("doTextOperation() failed on: " + file, ex);
                return ERROR;
            }

            if (!resultSet.areResultsChanged()) {
                return SKIPPED;
            }

            int result = ERROR;

            // check-out file from VCS, if any
            fileBuffer.validateState(monitor, shell);

            // if buffer is shared, then it means, that this operation could affect
            // changes in the UI thread because of associated editors and we *must*
            // to run it in the UI Thread too...
            if (fileBuffer.isShared()) {
                shell.getDisplay().syncExec(new Runnable() {
                    @Override
                    public void run() {
                        writeDocument(file, document, resultSet);
                    }
                });
            } else {
                writeDocument(file, document, resultSet);
            }
            if (resultSet.getException() != null) {
                result = ERROR;
            } else {
                result = MODIFIED;
            }
            return result;
        }

        static void writeDocument(IFile file, IDocument document, TextReplaceResultSet resultSet) {
            int docLinesNbr = document.getNumberOfLines();
            int changedLinesNbr = resultSet.getNumberOfLines();
            boolean rewriteWholeDoc = changedLinesNbr >= docLinesNbr;

            // some oddities with document??? prevent overflow in changedLinesNbr
            if (rewriteWholeDoc) {
                changedLinesNbr = docLinesNbr;
            }

            // this operation could affect changes in UI thread because of associated editors
            final DocumentRewriteSession rewriteSession = startSequentialRewriteMode(document);
            try {
                for (int i = 0; i < changedLinesNbr; i++) {
                    LineReplaceResult trr = resultSet.get(i);
                    if (trr != null) {
                        IRegion lineInfo = document.getLineInformation(i + resultSet.getStartLine());
                        document.replace(lineInfo.getOffset() + trr.startReplaceIndex, trr.rangeToReplace,
                                trr.textToReplace);
                    }
                }
            } catch (Exception e) {
                resultSet.setException(e);
                AnyEditToolsPlugin.logError("Error during write document for file: " + file, e);
            } finally {
                stopSequentialRewriteMode(document, rewriteSession);
                resultSet.clear();
            }
        }

        @SuppressWarnings("deprecation")
        private static DocumentRewriteSession startSequentialRewriteMode(IDocument document) {
            if (document instanceof IDocumentExtension4) {
                IDocumentExtension4 extension = (IDocumentExtension4) document;
                return extension.startRewriteSession(DocumentRewriteSessionType.SEQUENTIAL);
            }
            if (document instanceof IDocumentExtension) {
                IDocumentExtension extension = (IDocumentExtension) document;
                extension.startSequentialRewrite(false);
            }
            return null;
        }

        @SuppressWarnings("deprecation")
        private static void stopSequentialRewriteMode(IDocument document, DocumentRewriteSession rewriteSession) {
            if (document instanceof IDocumentExtension4) {
                IDocumentExtension4 extension = (IDocumentExtension4) document;
                extension.stopRewriteSession(rewriteSession);
            } else if (document instanceof IDocumentExtension) {
                IDocumentExtension extension = (IDocumentExtension) document;
                extension.stopSequentialRewrite();
            }
        }

        private static boolean hasWrongContentType(IFile file, IProgressMonitor monitor) {
            try {
                IContentDescription contentDescr = file.getContentDescription();
                if (contentDescr == null) {
                    return true;
                }
                IContentType contentType = contentDescr.getContentType();
                if (contentType == null) {
                    return true;
                }
                return !contentType.isKindOf(TEXT_TYPE);
            } catch (CoreException e) {
                AnyEditToolsPlugin.logError("Could not get content type for: " + file, e);
            }
            return false;
        }

        private static boolean matchFilter(IFile file, String[] filters) {
            return EclipseUtils.matchFilter(file.getName(), filters);
        }

    }

    @Override
    public void selectionChanged(IAction action, ISelection selection) {
        selectedFiles.clear();
        selectedResources.clear();
        if (selection instanceof IStructuredSelection) {
            IStructuredSelection ssel = (IStructuredSelection) selection;
            Iterator<?> iterator = ssel.iterator();
            while (iterator.hasNext()) {
                // by definition in plugin.xml, we are called only on IFile's
                selectedFiles.add((IFile) iterator.next());
            }
        }
    }

    @Override
    public void dispose() {
        selectedFiles.clear();
        selectedResources.clear();
    }

    @Override
    public void init(IWorkbenchWindow window) {
        // do nothing
    }

}