comparison graal/com.oracle.max.base/src/com/sun/max/tools/CheckCopyright.java @ 3733:e233f5660da4

Added Java files from Maxine project.
author Thomas Wuerthinger <thomas.wuerthinger@oracle.com>
date Sat, 17 Dec 2011 19:59:18 +0100
parents
children bc8527f3071c
comparison
equal deleted inserted replaced
3732:3e2e8b8abdaf 3733:e233f5660da4
1 /*
2 * Copyright (c) 2011, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23 package com.sun.max.tools;
24
25 import java.io.*;
26 import java.util.*;
27 import java.util.regex.*;
28
29 import com.sun.max.ide.*;
30 import com.sun.max.program.*;
31 import com.sun.max.program.option.*;
32
33 /**
34 * A program to check the existence and correctness of the copyright notice on a given set of Maxine sources.
35 * Sources are defined to be those under management by Mercurial and various options are available
36 * to limit the set of sources scanned.
37 */
38
39 public class CheckCopyright {
40
41 static class YearInfo {
42
43 final int firstYear;
44 final int lastYear;
45
46 YearInfo(int firstYear, int lastYear) {
47 this.firstYear = firstYear;
48 this.lastYear = lastYear;
49 }
50
51 @Override
52 public boolean equals(Object other) {
53 final YearInfo yearInfo = (YearInfo) other;
54 return yearInfo.firstYear == firstYear && yearInfo.lastYear == lastYear;
55 }
56
57 @Override
58 public int hashCode() {
59 return firstYear ^ lastYear;
60 }
61 }
62
63 static class Info extends YearInfo {
64
65 final String fileName;
66
67 Info(String fileName, int firstYear, int lastYear) {
68 super(firstYear, lastYear);
69 this.fileName = fileName;
70 }
71
72 @Override
73 public String toString() {
74 return fileName + " " + firstYear + ", " + lastYear;
75 }
76 }
77
78 enum CopyrightKind {
79 STAR("star"),
80 HASH("hash");
81
82 private static Map<String, CopyrightKind> copyrightMap;
83 private static final String COPYRIGHT_REGEX = "com.oracle.max.base/.copyright.regex";
84 private static String copyrightFiles = "bin/max|.*/makefile|.*/Makefile|.*\\.sh|.*\\.bash|.*\\.mk|.*\\.java|.*\\.c|.*\\.h";
85 private static Pattern copyrightFilePattern;
86 private final String suffix;
87 private String copyright;
88 Pattern copyrightPattern;
89
90 CopyrightKind(String suffix) {
91 this.suffix = suffix;
92 }
93
94 static void addCopyrightFilesPattern(String pattern) {
95 copyrightFiles += "|" + pattern;
96 }
97
98 void readCopyright() throws IOException {
99 final File file = new File(workSpaceDirectory, COPYRIGHT_REGEX + "." + suffix);
100 assert file.exists();
101 byte[] b = new byte[(int) file.length()];
102 FileInputStream is = new FileInputStream(file);
103 is.read(b);
104 is.close();
105 copyright = new String(b);
106 copyrightPattern = Pattern.compile(copyright, Pattern.DOTALL);
107 }
108
109 /**
110 * Returns a matcher for the modification year from copyright.
111 *
112 * @param fileContent
113 * @return modification year matcher or null if copyright not expected
114 */
115 static Matcher getCopyrightMatcher(String fileName, String fileContent) {
116 if (copyrightMap == null) {
117 copyrightFilePattern = Pattern.compile(copyrightFiles);
118 copyrightMap = new HashMap<String, CopyrightKind>();
119 copyrightMap.put("java", CopyrightKind.STAR);
120 copyrightMap.put("c", CopyrightKind.STAR);
121 copyrightMap.put("h", CopyrightKind.STAR);
122 copyrightMap.put("mk", CopyrightKind.HASH);
123 copyrightMap.put("sh", CopyrightKind.HASH);
124 copyrightMap.put("bash", CopyrightKind.HASH);
125 copyrightMap.put("", CopyrightKind.HASH);
126 }
127 if (!copyrightFilePattern.matcher(fileName).matches()) {
128 return null;
129 }
130 final String extension = getExtension(fileName);
131 CopyrightKind ck = copyrightMap.get(extension);
132 assert ck != null : fileName;
133 return ck.copyrightPattern.matcher(fileContent);
134 }
135
136 private static String getExtension(String fileName) {
137 int index = fileName.lastIndexOf(File.separatorChar);
138 if (index > 0) {
139 fileName = fileName.substring(index + 1);
140 }
141 index = fileName.lastIndexOf('.');
142 if (index > 0) {
143 return fileName.substring(index + 1);
144 }
145 if (fileName.equals("makefile")) {
146 return "mk";
147 }
148 return "";
149 }
150 }
151
152 private static List<YearInfo> infoList = new ArrayList<YearInfo>();
153 private static int currentYear = Calendar.getInstance().get(Calendar.YEAR);
154 private static final OptionSet options = new OptionSet(true);
155 private static final Option<Boolean> help = options.newBooleanOption("help", false, "Show help message and exit.");
156 private static final Option<List<String>> FILES_TO_CHECK = options.newStringListOption("files",
157 null, ',', "list of files to check");
158 private static final Option<String> FILE_LIST = options.newStringOption("filelist",
159 null, "file containing list of files to check");
160 private static final Option<Boolean> HG_ALL = options.newBooleanOption("all", false, "check all hg managed files requiring a copyright (hg status --all)");
161 private static final Option<Boolean> HG_MODIFIED = options.newBooleanOption("modified", false, "check all modified hg managed files requiring a copyright (hg status)");
162 private static final Option<Boolean> HG_OUTGOING = options.newBooleanOption("outgoing", false, "check outgoing hg managed files requiring a copyright (hg outgoing)");
163 private static final Option<Integer> HG_LOG = options.newIntegerOption("last", 0, "check hg managed files requiring a copyright in last N changesets (hg log -l N)");
164 private static final Option<List<String>> PROJECT = options.newStringListOption("projects", null, ',', "filter files to specific projects");
165 private static final Option<String> OUTGOING_REPO = options.newStringOption("repo", null, "override outgoing repository");
166 private static final Option<Boolean> EXHAUSTIVE = options.newBooleanOption("exhaustive", false, "check all hg managed files");
167 private static final Option<Boolean> FIX = options.newBooleanOption("fix", false, "fix copyright errors");
168 private static final Option<String> FILE_PATTERN = options.newStringOption("filepattern", null, "append additiional file patterns for copyright checks");
169 private static final Option<Boolean> REPORT_ERRORS = options.newBooleanOption("reporterrors", true, "report non-fatal errors");
170 private static final Option<Boolean> CONTINUE_ON_ERROR = options.newBooleanOption("continueonerror", false, "continue after normally fatal error");
171 private static final Option<String> HG_PATH = options.newStringOption("hgpath", "hg", "path to hg executable");
172 private static final String NON_EXISTENT_FILE = "abort: cannot follow nonexistent file:";
173 private static String hgPath;
174 private static boolean error;
175 private static File workSpaceDirectory;
176
177
178 public static void main(String[] args) {
179 Trace.addTo(options);
180 // parse the arguments
181 options.parseArguments(args).getArguments();
182 if (help.getValue()) {
183 options.printHelp(System.out, 100);
184 return;
185 }
186
187 hgPath = HG_PATH.getValue();
188
189 workSpaceDirectory = JavaProject.findWorkspaceDirectory();
190
191 if (FILE_PATTERN.getValue() != null) {
192 CopyrightKind.addCopyrightFilesPattern(FILE_PATTERN.getValue());
193 }
194
195 try {
196 CopyrightKind.STAR.readCopyright();
197 CopyrightKind.HASH.readCopyright();
198 List<String> filesToCheck = null;
199 if (HG_ALL.getValue()) {
200 filesToCheck = getAllFiles(true);
201 } else if (HG_OUTGOING.getValue()) {
202 filesToCheck = getOutgoingFiles();
203 } else if (HG_MODIFIED.getValue()) {
204 filesToCheck = getAllFiles(false);
205 } else if (HG_LOG.getValue() > 0) {
206 filesToCheck = getLastNFiles(HG_LOG.getValue());
207 } else if (FILE_LIST.getValue() != null) {
208 filesToCheck = readFileList(FILE_LIST.getValue());
209 } else {
210 filesToCheck = FILES_TO_CHECK.getValue();
211 }
212 if (filesToCheck != null && filesToCheck.size() > 0) {
213 processFiles(filesToCheck);
214 } else {
215 System.out.println("nothing to check");
216 }
217 System.exit(error ? 1 : 0);
218 } catch (Exception ex) {
219 throw ProgramError.unexpected("processing failed", ex);
220 }
221 }
222
223 private static void processFiles(List<String> fileNames) throws Exception {
224 final List<String> projects = PROJECT.getValue();
225 for (String fileName : fileNames) {
226 if (projects == null || isInProjects(fileName, projects)) {
227 Trace.line(1, "checking " + fileName);
228 try {
229 final List<String> logInfo = hglog(fileName);
230 final Info info = getInfo(fileName, true, logInfo);
231 checkFile(fileName, info);
232 } catch (ProgramError e) {
233 System.err.println("COPYRIGHT CHECK WARNING: error while processing " + fileName);
234 }
235 }
236 }
237 }
238
239 private static boolean isInProjects(String fileName, List<String> projects) {
240 final int ix = fileName.indexOf(File.separatorChar);
241 if (ix < 0) {
242 return false;
243 }
244 final String fileProject = fileName.substring(0, ix);
245 for (String project : projects) {
246 if (fileProject.equals(project)) {
247 return true;
248 }
249 }
250 return false;
251 }
252
253 private static List<String> readFileList(String fileListName) throws IOException {
254 final List<String> result = new ArrayList<String>();
255 BufferedReader b = null;
256 try {
257 b = new BufferedReader(new FileReader(fileListName));
258 while (true) {
259 final String fileName = b.readLine();
260 if (fileName == null) {
261 break;
262 }
263 if (fileName.length() == 0) {
264 continue;
265 }
266 result.add(fileName);
267 }
268 } finally {
269 if (b != null) {
270 b.close();
271 }
272 }
273 return result;
274 }
275
276 private static Info getInfo(String fileName, boolean lastOnly, List<String> logInfo) {
277 // process sequence of changesets
278 int lastYear = 0;
279 int firstYear = 0;
280 String summary = null;
281 int ix = 0;
282
283 while (ix < logInfo.size()) {
284 String s = logInfo.get(ix++);
285 assert s.startsWith("changeset");
286 s = logInfo.get(ix++);
287 // process every entry in a given change set
288 if (s.startsWith("tag")) {
289 s = logInfo.get(ix++);
290 }
291 if (s.startsWith("branch")) {
292 s = logInfo.get(ix++);
293 }
294 while (s.startsWith("parent")) {
295 s = logInfo.get(ix++);
296 }
297 assert s.startsWith("user");
298 s = logInfo.get(ix++);
299 assert s.startsWith("date");
300 final int csYear = getYear(s);
301 summary = logInfo.get(ix++);
302 assert summary.startsWith("summary");
303 s = logInfo.get(ix++); // blank
304 assert s.length() == 0;
305 if (lastYear == 0 && summary.contains("change all copyright notices from Sun to Oracle")) {
306 // special case of last change being the copyright change, which didn't
307 // count as a change of last modification date!
308 continue;
309 }
310 if (lastYear == 0) {
311 lastYear = csYear;
312 firstYear = lastYear;
313 } else {
314 firstYear = csYear;
315 }
316 // if we only want the last modified year, quit now
317 if (lastOnly) {
318 break;
319 }
320
321 }
322
323 // Special case
324 if (summary != null && summary.contains("Initial commit of VM sources")) {
325 firstYear = 2007;
326 }
327 if (HG_MODIFIED.getValue()) {
328 // We are only looking at modified and, therefore, uncommitted files.
329 // This means that the lastYear value will be the current year once the
330 // file is committed, so that is what we want to check against.
331 lastYear = currentYear;
332 }
333 return new Info(fileName, firstYear, lastYear);
334 }
335
336 private static int getYear(String dateLine) {
337 final String[] parts = dateLine.split(" ");
338 assert parts[parts.length - 2].startsWith("20");
339 return Integer.parseInt(parts[parts.length - 2]);
340 }
341
342 private static void checkFile(String c, Info info) throws IOException {
343 String fileName = info.fileName;
344 File file = new File(fileName);
345 if (!file.exists()) {
346 System.err.println("COPYRIGHT CHECK WARNING: file " + file + " doesn't exist");
347 return;
348 }
349 int fileLength = (int) file.length();
350 byte[] b = new byte[fileLength];
351 FileInputStream is = new FileInputStream(file);
352 is.read(b);
353 is.close();
354 final String fileContent = new String(b);
355 Matcher copyrightMatcher = CopyrightKind.getCopyrightMatcher(fileName, fileContent);
356 if (copyrightMatcher != null) {
357 if (copyrightMatcher.matches()) {
358 int yearInCopyright;
359 int yearInCopyrightIndex;
360 yearInCopyright = Integer.parseInt(copyrightMatcher.group(2));
361 yearInCopyrightIndex = copyrightMatcher.start(2);
362 if (yearInCopyright != info.lastYear) {
363 System.out.println(fileName + " copyright last modified year " + yearInCopyright + ", hg last modified year " + info.lastYear);
364 if (FIX.getValue()) {
365 // Use currentYear as that is what it will be when it's checked in!
366 System.out.println("updating last modified year of " + fileName + " to " + currentYear);
367 final int lx = yearInCopyrightIndex;
368 final String newContent = fileContent.substring(0, lx) + info.lastYear + fileContent.substring(lx + 4);
369 final FileOutputStream os = new FileOutputStream(file);
370 os.write(newContent.getBytes());
371 os.close();
372 } else {
373 error = true;
374 }
375 }
376 } else {
377 System.out.println("ERROR: file " + fileName + " has no copyright");
378 error = true;
379 }
380 } else if (EXHAUSTIVE.getValue()) {
381 System.out.println("ERROR: file " + fileName + " has no copyright");
382 error = true;
383 }
384 }
385
386
387 private static List<String> hglog(String fileName) throws Exception {
388 final String[] cmd = new String[] {hgPath, "log", "-f", fileName};
389 return exec(null, cmd, true);
390 }
391
392 private static List<String> getLastNFiles(int n) throws Exception {
393 final String[] cmd = new String[] {hgPath, "log", "-v", "-l", Integer.toString(n)};
394 return getFilesFiles(exec(null, cmd, false));
395 }
396
397 private static List<String> getAllFiles(boolean all) throws Exception {
398 final String[] cmd;
399 if (HG_MODIFIED.getValue()) {
400 cmd = new String[] {hgPath, "status"};
401 } else {
402 cmd = new String[] {hgPath, "status", "--all"};
403 }
404 List<String> output = exec(null, cmd, true);
405 final List<String> result = new ArrayList<String>(output.size());
406 for (String s : output) {
407 final char ch = s.charAt(0);
408 if (!(ch == 'R' || ch == 'I' || ch == '?' || ch == '!')) {
409 result.add(s.substring(2));
410 }
411 }
412 return result;
413 }
414
415 private static List<String> getOutgoingFiles() throws Exception {
416 final String[] cmd;
417 if (OUTGOING_REPO.getValue() == null) {
418 cmd = new String[] {hgPath, "-v", "outgoing"};
419 } else {
420 cmd = new String[] {hgPath, "-v", "outgoing", OUTGOING_REPO.getValue()};
421 }
422
423 final List<String> output = exec(null, cmd, false); // no outgoing exits with result 1
424 return getFilesFiles(output);
425 }
426
427 private static List<String> getFilesFiles(List<String> output) {
428 // there may be multiple changesets so merge the "files:"
429 final Map<String, String> outSet = new TreeMap<String, String>();
430 for (String s : output) {
431 if (s.startsWith("files:")) {
432 int ix = s.indexOf(' ');
433 while (ix < s.length() && s.charAt(ix) == ' ') {
434 ix++;
435 }
436 final String[] files = s.substring(ix).split(" ");
437 for (String file : files) {
438 outSet.put(file, file);
439 }
440 }
441 }
442 return new ArrayList<String>(outSet.values());
443 }
444
445 private static List<String> exec(File workingDir, String[] command, boolean failOnError) throws IOException, InterruptedException {
446 List<String> result = new ArrayList<String>();
447 if (Trace.hasLevel(2)) {
448 Trace.line(2, "Executing process in directory: " + workingDir);
449 for (String c : command) {
450 Trace.line(2, " " + c);
451 }
452 }
453 final Process process = Runtime.getRuntime().exec(command, null, workingDir);
454 try {
455 result = readOutput(process.getInputStream());
456 final int exitValue = process.waitFor();
457 if (exitValue != 0) {
458 final List<String> errorResult = readOutput(process.getErrorStream());
459 if (REPORT_ERRORS.getValue()) {
460 System.err.print("execution of command: ");
461 for (String c : command) {
462 System.err.print(c);
463 System.err.print(' ');
464 }
465 System.err.println("failed with result " + exitValue);
466 for (String e : errorResult) {
467 System.err.println(e);
468 }
469 }
470 if (failOnError && !(CONTINUE_ON_ERROR.getValue() || cannotFollowNonExistentFile(errorResult))) {
471 throw ProgramError.unexpected("terminating");
472 }
473 }
474 } finally {
475 process.destroy();
476 }
477 return result;
478 }
479
480 private static boolean cannotFollowNonExistentFile(List<String> errorResult) {
481 return errorResult.size() == 1 && errorResult.get(0).startsWith(NON_EXISTENT_FILE);
482 }
483
484 private static List<String> readOutput(InputStream is) throws IOException {
485 final List<String> result = new ArrayList<String>();
486 BufferedReader bs = null;
487 try {
488 bs = new BufferedReader(new InputStreamReader(is));
489 while (true) {
490 final String line = bs.readLine();
491 if (line == null) {
492 break;
493 }
494 result.add(line);
495 }
496 } finally {
497 if (bs != null) {
498 bs.close();
499 }
500 }
501 return result;
502 }
503
504 }