| /* |
| * Copyright (c) 2013 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.emf.cdo.releng.gitbash.repository; |
| |
| import org.eclipse.emf.cdo.releng.gitbash.AbstractAction; |
| import org.eclipse.emf.cdo.releng.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.emf.cdo.releng.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); |
| } |
| } |
| } |
| } |