org.eclipse.oomph.gitbash.repository.UpdateCopyrightsAction.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.oomph.gitbash.repository.UpdateCopyrightsAction.java

Source

/*
 * Copyright (c) 2014 Eike Stepper (Berlin, Germany) 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:
 *    Eike Stepper - initial API and implementation
 */
package org.eclipse.oomph.gitbash.repository;

import org.eclipse.oomph.gitbash.AbstractAction;
import org.eclipse.oomph.gitbash.Activator;

import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Eike Stepper
 */
public class UpdateCopyrightsAction extends AbstractAction<Repository> {
    private static final String[] IGNORED_PATHS = { "resourcemanager.java", "menucardtemplate.java",
            "org.eclipse.oomph.version.tests/tests", "org.eclipse.net4j.jms.api/src",
            "org/eclipse/net4j/util/ui/proposals/", "org.eclipse.emf.cdo.examples.installer/examples",
            "org.eclipse.net4j.examples.installer/examples" };

    private static final String[] REQUIRED_EXTENSIONS = { ".java", ".ant", "build.xml", "plugin.xml",
            "fragment.xml", "feature.xml", "plugin.properties", "fragment.properties", "feature.properties",
            "about.properties", "build.properties", "messages.properties", "copyright.txt", ".exsd",
            "org.eclipse.jdt.ui.prefs" };

    private static final String[] OPTIONAL_EXTENSIONS = { ".properties", ".xml", ".css", ".ecore", ".genmodel",
            ".mwe", ".xpt", ".ext" };

    private static final String[] IGNORED_MESSAGE_VERBS = { "update", "adjust", "fix" };

    private static final String[] IGNORED_MESSAGE_NOUNS = { "copyright", "legal header" };

    private static final String[] IGNORED_MESSAGES = combineWords(IGNORED_MESSAGE_VERBS, IGNORED_MESSAGE_NOUNS);

    private static final Pattern COPYRIGHT_PATTERN = Pattern
            .compile("(.*?)Copyright \\(c\\) ([0-9 ,-]+) (.*?) and others\\.(.*)");

    private static final Calendar CALENDAR = GregorianCalendar.getInstance();

    private static final int CURRENT_YEAR = CALENDAR.get(Calendar.YEAR);

    private static final String CURRENT_YEAR_STRING = Integer.toString(CURRENT_YEAR);

    private static final IWorkbench WORKBENCH = PlatformUI.getWorkbench();

    private static final String NL = System.getProperty("line.separator");

    private Git git;

    private File workTree;

    private int workTreeLength;

    private List<String> missingCopyrights = new ArrayList<String>();

    private int rewriteCount;

    private int fileCount;

    public UpdateCopyrightsAction() {
        super(Repository.class);
    }

    protected boolean isCheckOnly() {
        return false;
    }

    @Override
    protected void run(final Shell shell, final Repository repository) throws Exception {
        IRunnableWithProgress runnable = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                try {
                    git = new Git(repository);
                    workTree = repository.getWorkTree();
                    workTreeLength = workTree.getAbsolutePath().length() + 1;

                    fileCount = countFiles(workTree);
                    monitor.beginTask(getTitle(), fileCount);

                    final long start = System.currentTimeMillis();
                    checkFolder(monitor, workTree);

                    shell.getDisplay().syncExec(new Runnable() {
                        public void run() {
                            try {
                                handleResult(shell, workTree, formatDuration(start));
                            } catch (Exception ex) {
                                Activator.log(ex);
                            }
                        }
                    });
                } catch (OperationCanceledException ex) {
                    // Do nothing
                } catch (Exception ex) {
                    throw new InvocationTargetException(ex);
                } finally {
                    missingCopyrights.clear();
                    rewriteCount = 0;
                    fileCount = 0;
                    workTreeLength = 0;
                    workTree = null;
                    git = null;
                    monitor.done();
                }
            }
        };

        WORKBENCH.getProgressService().run(true, true, runnable);
    }

    private int countFiles(File folder) throws Exception {
        int count = 0;
        for (File file : folder.listFiles()) {
            String fileName = file.getName();
            if (file.isDirectory() && !fileName.equals(".git")) {
                count += countFiles(file);
            } else {
                String path = getPath(file);
                if (hasString(path, IGNORED_PATHS)) {
                    continue;
                }

                ++count;
            }
        }

        return count;
    }

    private void checkFolder(IProgressMonitor monitor, File folder) throws Exception {
        for (File file : folder.listFiles()) {
            if (monitor.isCanceled()) {
                throw new OperationCanceledException();
            }

            String fileName = file.getName();
            if (file.isDirectory() && !fileName.equals(".git")) {
                checkFolder(monitor, file);
            } else {
                if (checkFile(monitor, file)) {
                    monitor.worked(1);
                }
            }
        }
    }

    private boolean checkFile(IProgressMonitor monitor, File file) throws Exception {
        String path = getPath(file);
        if (!hasString(path, IGNORED_PATHS)) {
            boolean required = hasExtension(file, REQUIRED_EXTENSIONS);
            boolean optional = required || hasExtension(file, OPTIONAL_EXTENSIONS);

            boolean checkOnly = isCheckOnly();
            if (checkOnly ? required : optional) {
                monitor.subTask(path);

                List<String> lines = new ArrayList<String>();
                boolean copyrightFound = false;
                boolean copyrightChanged = false;

                FileReader fileReader = new FileReader(file);
                BufferedReader bufferedReader = new BufferedReader(fileReader);

                try {
                    String line;
                    while ((line = bufferedReader.readLine()) != null) {
                        Matcher matcher = COPYRIGHT_PATTERN.matcher(line);
                        if (matcher.matches()) {
                            copyrightFound = true;
                            if (checkOnly) {
                                break;
                            }

                            String prefix = matcher.group(1);
                            String dates = matcher.group(2);
                            String owner = matcher.group(3);
                            String suffix = matcher.group(4);

                            if (dates.endsWith(CURRENT_YEAR_STRING)) {
                                // This file must have been rewritten already in the current year
                                break;
                            }

                            String newLine = rewriteCopyright(path, line, prefix, dates, owner, suffix);
                            if (newLine != line) {
                                line = newLine;
                                copyrightChanged = true;
                            }
                        }

                        lines.add(line);
                    }
                } finally {
                    close(bufferedReader);
                    close(fileReader);
                }

                if (required && !copyrightFound) {
                    missingCopyrights.add(path);
                }

                if (copyrightChanged) {
                    writeLines(file, lines);
                    ++rewriteCount;
                }
            }

            return true;
        }

        return false;
    }

    private String rewriteCopyright(String path, String line, String prefix, String dates, String owner,
            String suffix) throws Exception {
        String newDates;

        if (path.endsWith("org.eclipse.jdt.ui.prefs") || path.endsWith("copyright.txt")
                || path.endsWith("org.eclipse.emf.cdo.license-feature/feature.properties")
                || suffix.equals(" All rights reserved.\\n\\")) {
            // Special handling of occurrences with a more global (then file-scoped) meaning.
            // Special handling of second occurrence in about.properties (which is visible in the UI).
            newDates = "2004-" + CURRENT_YEAR;
        } else {
            Set<Integer> years = new HashSet<Integer>();

            for (RevCommit commit : git.log().addPath(path).call()) {
                String message = commit.getFullMessage();
                if (!hasString(message, IGNORED_MESSAGES)) {
                    CALENDAR.setTimeInMillis(1000L * commit.getCommitTime());
                    int year = CALENDAR.get(Calendar.YEAR);
                    years.add(year);
                }
            }

            if (years.isEmpty()) {
                // The file doesn't have a history, may be derived.
                return line;
            }

            newDates = formatYears(years);
        }

        if (newDates.equals(dates)) {
            return line;
        }

        return prefix + "Copyright (c) " + newDates + " " + owner + " and" + " others." + suffix;
    }

    private String formatYears(Collection<Integer> years) {
        class YearRange {
            private int begin;

            private int end;

            public YearRange(int begin) {
                this.begin = begin;
                end = begin;
            }

            public boolean add(int year) {
                if (year == end + 1) {
                    end = year;
                    return true;
                }

                return false;
            }

            @Override
            public String toString() {
                if (begin == end) {
                    return "" + begin;
                }

                if (begin == end - 1) {
                    return "" + begin + ", " + end;
                }

                return "" + begin + "-" + end;
            }
        }

        List<Integer> list = new ArrayList<Integer>(years);
        Collections.sort(list);

        List<YearRange> ranges = new ArrayList<YearRange>();
        YearRange lastRange = null;
        for (Integer year : list) {
            if (lastRange == null || !lastRange.add(year)) {
                lastRange = new YearRange(year);
                ranges.add(lastRange);
            }
        }

        StringBuilder builder = new StringBuilder();
        for (YearRange range : ranges) {
            if (builder.length() != 0) {
                builder.append(", ");
            }

            builder.append(range);
        }

        return builder.toString();
    }

    private String getTitle() {
        return (isCheckOnly() ? "Check" : "Update") + " Copyrights";
    }

    private String getPath(File file) {
        String path = file.getAbsolutePath().replace('\\', '/');
        return path.substring(workTreeLength);
    }

    private boolean hasString(String string, String[] strings) {
        string = string.toLowerCase();
        for (int i = 0; i < strings.length; i++) {
            if (string.indexOf(strings[i]) != -1) {
                return true;
            }
        }

        return false;
    }

    private boolean hasExtension(File file, String[] extensions) {
        String fileName = file.getName().toLowerCase();
        for (int i = 0; i < extensions.length; i++) {
            if (fileName.endsWith(extensions[i])) {
                return true;
            }
        }

        return false;
    }

    private void handleResult(Shell shell, File workTree, String duration) throws PartInitException {
        String message = "Copyrights missing: " + missingCopyrights.size();
        message += "\nCopyrights rewritten: " + rewriteCount;
        message += "\nFiles visited: " + fileCount;
        message += "\nTime needed: " + duration;

        int size = missingCopyrights.size();
        if (size != 0) {
            StringBuilder builder = new StringBuilder(message);
            message += "\n\nDo you want to open the files with missing copyrights in editors?";
            if (MessageDialog.openQuestion(shell, getTitle(), message)) {
                IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
                IWorkbenchPage page = WORKBENCH.getActiveWorkbenchWindow().getActivePage();

                builder.append("\n");
                for (String missingCopyright : missingCopyrights) {
                    builder.append("\nMissing: ");
                    builder.append(missingCopyright);

                    File externalFile = new File(workTree, missingCopyright);
                    IFile file = root.getFileForLocation(new Path(externalFile.getAbsolutePath()));
                    if (file != null && file.isAccessible()) {
                        IDE.openEditor(page, file);
                    } else {
                        IFileStore fileStore = EFS.getLocalFileSystem().getStore(externalFile.toURI());
                        if (fileStore != null) {
                            IDE.openEditorOnFileStore(page, fileStore);
                        }
                    }
                }

                Activator.log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, builder.toString()));
            }
        } else {
            MessageDialog.openInformation(shell, getTitle(), message);
            Activator.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, message));
        }
    }

    private static String[] combineWords(String[] verbs, String[] nouns) {
        List<String> result = new ArrayList<String>();
        for (String noun : nouns) {
            for (String verb : verbs) {
                result.add(verb + " " + noun);

                if (verb.endsWith("e")) {
                    verb += "d";
                } else {
                    verb += "ed";
                }

                result.add(verb + " " + noun);
            }
        }

        return result.toArray(new String[result.size()]);
    }

    private static String formatDuration(long start) {
        double duration = System.currentTimeMillis() - start;

        String unit = "milliseconds";
        if (duration > 1000d) {
            duration = duration / 1000d;
            unit = "seconds";

            if (duration > 60d) {
                duration = duration / 60d;
                unit = "minutes";

                if (duration > 60d) {
                    duration = duration / 60d;
                    unit = "hours";
                }
            }
        }

        duration = Math.round(duration * 100d) / 100d;
        return duration + " " + unit;
    }

    private static void writeLines(File file, List<String> lines) throws IOException {
        Writer fileWriter = null;
        BufferedWriter bufferedWriter = null;

        try {
            fileWriter = new FileWriter(file);
            bufferedWriter = new BufferedWriter(fileWriter);

            for (String line : lines) {
                bufferedWriter.write(line);
                bufferedWriter.write(NL);
            }
        } finally {
            close(bufferedWriter);
            close(fileWriter);
        }
    }

    private static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException ex) {
                Activator.log(ex);
            }
        }
    }
}