Mercurial > hg > truffle
comparison mxtool/CheckCopyright.java @ 21472:c190ed6b84bf
added checkcopyrights command (from mxtool2)
author | Doug Simon <doug.simon@oracle.com> |
---|---|
date | Fri, 22 May 2015 23:11:17 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
21471:a64d09dc4590 | 21472:c190ed6b84bf |
---|---|
1 /* | |
2 * Copyright (c) 2011, 2015, 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 import java.io.*; | |
24 import java.net.URISyntaxException; | |
25 import java.net.URL; | |
26 import java.util.*; | |
27 import java.util.regex.*; | |
28 | |
29 | |
30 /** | |
31 * A program to check the existence and correctness of the copyright notice on a given set of sources. | |
32 * Sources are defined to be those under management by Mercurial and various options are available | |
33 * to limit the set of sources scanned. | |
34 */ | |
35 public class CheckCopyright { | |
36 | |
37 static class YearInfo { | |
38 | |
39 final int firstYear; | |
40 final int lastYear; | |
41 | |
42 YearInfo(int firstYear, int lastYear) { | |
43 this.firstYear = firstYear; | |
44 this.lastYear = lastYear; | |
45 } | |
46 | |
47 @Override | |
48 public boolean equals(Object other) { | |
49 final YearInfo yearInfo = (YearInfo) other; | |
50 return yearInfo.firstYear == firstYear && yearInfo.lastYear == lastYear; | |
51 } | |
52 | |
53 @Override | |
54 public int hashCode() { | |
55 return firstYear ^ lastYear; | |
56 } | |
57 } | |
58 | |
59 static class Info extends YearInfo { | |
60 | |
61 final String fileName; | |
62 | |
63 Info(String fileName, int firstYear, int lastYear) { | |
64 super(firstYear, lastYear); | |
65 this.fileName = fileName; | |
66 } | |
67 | |
68 @Override | |
69 public String toString() { | |
70 return fileName + " " + firstYear + ", " + lastYear; | |
71 } | |
72 } | |
73 | |
74 private static abstract class CopyrightHandler { | |
75 enum CommentType{ | |
76 STAR, HASH | |
77 } | |
78 | |
79 private static Map<String, CopyrightHandler> copyrightMap; | |
80 private static String copyrightFiles = ".*/makefile|.*/Makefile|.*\\.sh|.*\\.bash|.*\\.mk|.*\\.java|.*\\.c|.*\\.h|.*\\.py|.*\\.g|.*\\.r"; | |
81 private static Pattern copyrightFilePattern; | |
82 | |
83 protected final String suffix; | |
84 private CopyrightHandler customHandler; | |
85 | |
86 CopyrightHandler(CommentType commentType) { | |
87 this.suffix = commentType.name().toLowerCase(); | |
88 initCopyrightMap(); | |
89 } | |
90 | |
91 void addCustomhandler(CopyrightHandler copyrightHandler) { | |
92 this.customHandler = copyrightHandler; | |
93 } | |
94 | |
95 /** | |
96 * Add @code extension to files handled by this {@code CopyrightKind} | |
97 */ | |
98 protected void updateMap(String extension) { | |
99 copyrightMap.put(extension, this); | |
100 } | |
101 | |
102 static void addCopyrightFilesPattern(String pattern) { | |
103 copyrightFiles += "|" + pattern; | |
104 } | |
105 | |
106 protected abstract void readCopyrights() throws IOException; | |
107 | |
108 protected abstract Matcher getMatcher(String fileName, String fileContent) throws IOException; | |
109 | |
110 protected abstract String getText(String fileName) throws IOException ; | |
111 | |
112 protected abstract boolean handlesFile(String fileName); | |
113 | |
114 /** | |
115 * Checks that the Oracle copyright year info was correct. | |
116 * @return {@code false} if the year info was incorrect and was not fixed otherwise return {@code true} | |
117 * @throws IOException | |
118 */ | |
119 protected abstract boolean checkYearInfo(String fileName, String fileContent, Matcher matcher, Info info) throws IOException; | |
120 | |
121 static String getCopyrightText(String fileName) throws IOException { | |
122 return getCopyrightHandler(fileName).getText(fileName); | |
123 } | |
124 | |
125 private static CopyrightHandler getCopyrightHandler(String fileName) { | |
126 initCopyrightMap(); | |
127 if (!copyrightFilePattern.matcher(fileName).matches()) { | |
128 return null; | |
129 } | |
130 CopyrightHandler ck = getDefaultHandler(fileName); | |
131 if (ck.customHandler != null && ck.customHandler.handlesFile(fileName)) { | |
132 return ck.customHandler; | |
133 } else { | |
134 return ck; | |
135 } | |
136 } | |
137 | |
138 private static void initCopyrightMap() { | |
139 if (copyrightMap == null) { | |
140 copyrightMap = new HashMap<String, CopyrightHandler>(); | |
141 copyrightFilePattern = Pattern.compile(copyrightFiles); | |
142 } | |
143 } | |
144 | |
145 static CopyrightHandler getDefaultHandler(String fileName) { | |
146 int index = fileName.lastIndexOf(File.separatorChar); | |
147 if (index > 0) { | |
148 fileName = fileName.substring(index + 1); | |
149 } | |
150 String ext = ""; | |
151 index = fileName.lastIndexOf('.'); | |
152 if (index > 0) { | |
153 ext = fileName.substring(index + 1); | |
154 } | |
155 if (fileName.equals("makefile")) { | |
156 ext = "mk"; | |
157 } | |
158 CopyrightHandler ck = copyrightMap.get(ext); | |
159 assert ck != null : fileName; | |
160 return ck; | |
161 } | |
162 | |
163 protected String readCopyright(InputStream is) throws IOException { | |
164 byte[] b = new byte[16384]; | |
165 int n = is.read(b); | |
166 is.close(); | |
167 return new String(b, 0, n); | |
168 } | |
169 | |
170 } | |
171 | |
172 private static class DefaultCopyrightHandler extends CopyrightHandler { | |
173 private static String ORACLE_COPYRIGHT = "oracle.copyright"; | |
174 private static String ORACLE_COPYRIGHT_REGEX = "oracle.copyright.regex"; | |
175 | |
176 private String copyrightRegex; | |
177 private String copyright; | |
178 Pattern copyrightPattern; | |
179 | |
180 DefaultCopyrightHandler(CopyrightHandler.CommentType commentType) throws IOException { | |
181 super(commentType); | |
182 if (commentType == CopyrightHandler.CommentType.STAR) { | |
183 updateMap("java"); | |
184 updateMap("c"); | |
185 updateMap("h"); | |
186 updateMap("g"); | |
187 } else { | |
188 updateMap("r"); | |
189 updateMap("R"); | |
190 updateMap("py"); | |
191 updateMap("sh"); | |
192 updateMap("mk"); | |
193 updateMap("bash"); | |
194 updateMap(""); | |
195 } | |
196 readCopyrights(); | |
197 } | |
198 | |
199 private String readCopyright(String name) throws IOException { | |
200 String copyRightDir = COPYRIGHT_DIR.getValue(); | |
201 String fileName = "copyrights/" + name + "." + suffix; | |
202 String copyrightPath; | |
203 if (copyRightDir != null) { | |
204 copyrightPath = new File(new File(copyRightDir), fileName).getAbsolutePath(); | |
205 } else { | |
206 URL url = CheckCopyright.class.getResource(fileName); | |
207 try { | |
208 copyrightPath = url.toURI().getPath(); | |
209 } catch (URISyntaxException ex) { | |
210 throw new IOException(ex); | |
211 } | |
212 } | |
213 InputStream is = new FileInputStream(copyrightPath); | |
214 return readCopyright(is); | |
215 } | |
216 | |
217 @Override | |
218 protected void readCopyrights() throws IOException { | |
219 copyright = readCopyright(ORACLE_COPYRIGHT); | |
220 copyrightRegex = readCopyright(ORACLE_COPYRIGHT_REGEX); | |
221 copyrightPattern = Pattern.compile(copyrightRegex, Pattern.DOTALL); | |
222 } | |
223 | |
224 @Override | |
225 protected Matcher getMatcher(String fileName, String fileContent) { | |
226 return copyrightPattern.matcher(fileContent); | |
227 } | |
228 | |
229 @Override | |
230 protected String getText(String fileName) { | |
231 return copyright; | |
232 } | |
233 | |
234 @Override | |
235 protected boolean handlesFile(String fileName) { | |
236 return true; | |
237 } | |
238 | |
239 /** | |
240 * Check the year info against the copyright header. | |
241 * N.B. In the case of multiple matching groups, only the last group is checked. | |
242 * I.e., only the last lines containing year info is checked/updated. | |
243 */ | |
244 @Override | |
245 protected boolean checkYearInfo(String fileName, String fileContent, Matcher matcher, Info info) throws IOException { | |
246 int yearInCopyright; | |
247 int yearInCopyrightIndex; | |
248 int groupCount = matcher.groupCount(); | |
249 String yearInCopyrightString = matcher.group(groupCount); | |
250 yearInCopyright = Integer.parseInt(yearInCopyrightString); | |
251 yearInCopyrightIndex = matcher.start(groupCount); | |
252 if (yearInCopyright != info.lastYear) { | |
253 System.out.println(fileName + " copyright last modified year " + yearInCopyright + ", hg last modified year " + info.lastYear); | |
254 if (FIX.getValue()) { | |
255 // Use currentYear as that is what it will be when it's checked in! | |
256 System.out.println("updating last modified year of " + fileName + " to " + info.lastYear); | |
257 // If the previous copyright only specified a single (initial) year, we convert it to the pair form | |
258 String newContent = fileContent.substring(0, yearInCopyrightIndex); | |
259 if (matcher.group(groupCount - 1) == null) { | |
260 // single year form | |
261 newContent += yearInCopyrightString + ", "; | |
262 } | |
263 newContent += info.lastYear + fileContent.substring(yearInCopyrightIndex + 4); | |
264 final FileOutputStream os = new FileOutputStream(fileName); | |
265 os.write(newContent.getBytes()); | |
266 os.close(); | |
267 return true; | |
268 } else { | |
269 return false; | |
270 } | |
271 } | |
272 return true; | |
273 } | |
274 | |
275 } | |
276 | |
277 private static class CustomCopyrightHandler extends CopyrightHandler { | |
278 private Map<String, String> overrides = new HashMap<String, String>(); | |
279 private CopyrightHandler defaultHandler; | |
280 | |
281 CustomCopyrightHandler(CopyrightHandler.CommentType commentType, CopyrightHandler defaultHandler) { | |
282 super(commentType); | |
283 this.defaultHandler = defaultHandler; | |
284 } | |
285 | |
286 void addFile(String fileName, String copyright) { | |
287 overrides.put(fileName, copyright); | |
288 } | |
289 | |
290 @Override | |
291 protected void readCopyrights() throws IOException { | |
292 } | |
293 | |
294 @Override | |
295 protected Matcher getMatcher(String fileName, String fileContent) throws IOException { | |
296 String copyright = overrides.get(fileName); | |
297 assert copyright != null : fileName; | |
298 try (InputStream fs = new FileInputStream(copyright + "." + suffix + ".regex")) { | |
299 return Pattern.compile(readCopyright(fs), Pattern.DOTALL).matcher(fileContent); | |
300 } | |
301 } | |
302 | |
303 @Override | |
304 protected String getText(String fileName) throws IOException { | |
305 String copyright = overrides.get(fileName); | |
306 assert copyright != null : fileName; | |
307 try (InputStream fs = new FileInputStream(copyright + "." + suffix)) { | |
308 return readCopyright(fs); | |
309 } | |
310 } | |
311 | |
312 @Override | |
313 protected boolean handlesFile(String fileName) { | |
314 return overrides.get(fileName) != null; | |
315 } | |
316 | |
317 @Override | |
318 protected boolean checkYearInfo(String fileName, String fileContent, Matcher matcher, Info info) throws IOException { | |
319 // This is a bit tacky | |
320 String copyright = overrides.get(fileName); | |
321 if (copyright.endsWith("no.copyright")) { | |
322 return true; | |
323 } | |
324 return defaultHandler.checkYearInfo(fileName, fileContent, matcher, info); | |
325 } | |
326 } | |
327 | |
328 private static void initCopyrightKinds() throws IOException { | |
329 CopyrightHandler starHandler = new DefaultCopyrightHandler(CopyrightHandler.CommentType.STAR); | |
330 CopyrightHandler hashHandler = new DefaultCopyrightHandler(CopyrightHandler.CommentType.HASH); | |
331 | |
332 String customCopyrightDir = CUSTOM_COPYRIGHT_DIR.getValue(); | |
333 if (customCopyrightDir != null) { | |
334 CustomCopyrightHandler customStarHandler = new CustomCopyrightHandler(CopyrightHandler.CommentType.STAR, starHandler); | |
335 CustomCopyrightHandler customHashHandler = new CustomCopyrightHandler(CopyrightHandler.CommentType.HASH, hashHandler); | |
336 starHandler.addCustomhandler(customStarHandler); | |
337 hashHandler.addCustomhandler(customHashHandler); | |
338 | |
339 File overrides = new File(new File(customCopyrightDir), "overrides"); | |
340 if (overrides.exists()) { | |
341 ArrayList<String> lines = new ArrayList<>(); | |
342 boolean changed = false; | |
343 try (BufferedReader br = new BufferedReader(new FileReader( | |
344 overrides))) { | |
345 while (true) { | |
346 String line = br.readLine(); | |
347 if (line == null) { | |
348 break; | |
349 } | |
350 if (line.length() == 0 || line.startsWith("#")) { | |
351 lines.add(line); | |
352 continue; | |
353 } | |
354 String[] parts = line.split(","); | |
355 // filename,copyright-file | |
356 CopyrightHandler defaultHandler = CopyrightHandler.getDefaultHandler(parts[0]); | |
357 if (defaultHandler == null) { | |
358 System.err.println("no default copyright handler for: " + parts[0]); | |
359 System.exit(1); | |
360 } | |
361 if (!new File(parts[0]).exists()) { | |
362 System.err.printf("file %s in overrides file does not exist", parts[0]); | |
363 if (FIX.getValue()) { | |
364 System.err.print(" - removing"); | |
365 line = null; | |
366 changed = true; | |
367 } | |
368 System.err.println(); | |
369 } | |
370 if (line != null) { | |
371 lines.add(line); | |
372 } | |
373 CustomCopyrightHandler customhandler = (CustomCopyrightHandler) defaultHandler.customHandler; | |
374 customhandler.addFile(parts[0], new File(new File(customCopyrightDir), parts[1]).getAbsolutePath()); | |
375 } | |
376 } | |
377 if (changed) { | |
378 try (BufferedWriter bw = new BufferedWriter(new FileWriter( | |
379 overrides))) { | |
380 for (String line : lines) { | |
381 bw.write(line); | |
382 bw.write('\n'); | |
383 } | |
384 } | |
385 } | |
386 } | |
387 } | |
388 } | |
389 | |
390 private static int currentYear = Calendar.getInstance().get(Calendar.YEAR); | |
391 private static Options options = new Options(); | |
392 private static Option<Boolean> help = options.newBooleanOption("help", false, "Show help message and exit."); | |
393 private static Option<String> COPYRIGHT_DIR = options.newStringOption("copyright-dir", null, "override default location of copyright files"); | |
394 private static Option<List<String>> FILES_TO_CHECK = options.newStringListOption("files", null, "list of files to check"); | |
395 private static Option<String> FILE_LIST = options.newStringOption("file-list", null, "file containing list of files to check"); | |
396 private static Option<Boolean> DIR_WALK = options.newBooleanOption("list-dir", false, "check all files in directory tree requiring a copyright (ls -R)"); | |
397 private static Option<Boolean> HG_ALL = options.newBooleanOption("hg-all", false, "check all hg managed files requiring a copyright (hg status --all)"); | |
398 private static Option<Boolean> HG_MODIFIED = options.newBooleanOption("hg-modified", false, "check all modified hg managed files requiring a copyright (hg status)"); | |
399 private static Option<Boolean> HG_OUTGOING = options.newBooleanOption("hg-outgoing", false, "check outgoing hg managed files requiring a copyright (hg outgoing)"); | |
400 private static Option<String> HG_LOG = options.newStringOption("hg-last", "0", "check hg managed files requiring a copyright in last N changesets (hg log -l N)"); | |
401 private static Option<List<String>> PROJECT = options.newStringListOption("projects", null, "filter files to specific projects"); | |
402 private static Option<String> OUTGOING_REPO = options.newStringOption("hg-repo", null, "override outgoing repository"); | |
403 private static Option<Boolean> EXHAUSTIVE = options.newBooleanOption("hg-exhaustive", false, "check all hg managed files"); | |
404 private static Option<Boolean> FIX = options.newBooleanOption("fix", false, "fix all copyright errors"); | |
405 private static Option<String> FILE_PATTERN = options.newStringOption("file-pattern", null, "append additional file patterns for copyright checks"); | |
406 private static Option<Boolean> REPORT_ERRORS = options.newBooleanOption("report-errors", false, "report non-fatal errors"); | |
407 private static Option<Boolean> HALT_ON_ERROR = options.newBooleanOption("halt-on-error", false, "continue after normally fatal error"); | |
408 private static Option<String> HG_PATH = options.newStringOption("hg-path", "hg", "path to hg executable"); | |
409 private static Option<Boolean> VERBOSE = options.newBooleanOption("verbose", false, "verbose output"); | |
410 private static Option<Boolean> VERY_VERBOSE = options.newBooleanOption("very-verbose", false, "very verbose output"); | |
411 private static Option<String> CUSTOM_COPYRIGHT_DIR = options.newStringOption("custom-copyright-dir", null, "file containing filenames with custom copyrights"); | |
412 | |
413 private static String CANNOT_FOLLOW_FILE = "abort: cannot follow"; | |
414 private static String hgPath; | |
415 private static boolean error; | |
416 // private static File workSpaceDirectory; | |
417 private static boolean verbose; | |
418 private static boolean veryVerbose; | |
419 | |
420 public static void main(String[] args) { | |
421 // parse the arguments | |
422 options.parseArguments(args); | |
423 if (help.getValue()) { | |
424 options.printHelp(); | |
425 return; | |
426 } | |
427 | |
428 verbose = VERBOSE.getValue(); | |
429 veryVerbose = VERY_VERBOSE.getValue(); | |
430 | |
431 hgPath = HG_PATH.getValue(); | |
432 | |
433 if (FILE_PATTERN.getValue() != null) { | |
434 CopyrightHandler.addCopyrightFilesPattern(FILE_PATTERN.getValue()); | |
435 } | |
436 | |
437 try { | |
438 initCopyrightKinds(); | |
439 List<String> filesToCheck = null; | |
440 if (HG_ALL.getValue()) { | |
441 filesToCheck = getAllFiles(true); | |
442 } else if (HG_OUTGOING.getValue()) { | |
443 filesToCheck = getOutgoingFiles(); | |
444 } else if (HG_MODIFIED.getValue()) { | |
445 filesToCheck = getAllFiles(false); | |
446 } else if (Integer.parseInt(HG_LOG.getValue()) > 0) { | |
447 filesToCheck = getLastNFiles(Integer.parseInt(HG_LOG.getValue())); | |
448 } else if (FILE_LIST.getValue() != null) { | |
449 filesToCheck = readFileList(FILE_LIST.getValue()); | |
450 } else if (DIR_WALK.getValue()) { | |
451 filesToCheck = getDirWalkFiles(); | |
452 } else if (FILES_TO_CHECK.getValue() != null) { | |
453 filesToCheck = FILES_TO_CHECK.getValue(); | |
454 } else { | |
455 // no option set, default to HG_ALL | |
456 filesToCheck = getAllFiles(true); | |
457 } | |
458 if (filesToCheck != null && filesToCheck.size() > 0) { | |
459 processFiles(filesToCheck); | |
460 } else { | |
461 System.out.println("nothing to check"); | |
462 } | |
463 System.exit(error ? 1 : 0); | |
464 } catch (Exception ex) { | |
465 System.err.println("processing failed: " + ex); | |
466 ex.printStackTrace(); | |
467 } | |
468 } | |
469 | |
470 private static void processFiles(List<String> fileNames) throws Exception { | |
471 final List<String> projects = PROJECT.getValue(); | |
472 Calendar cal = Calendar.getInstance(); | |
473 for (String fileName : fileNames) { | |
474 if (projects == null || isInProjects(fileName, projects)) { | |
475 File file = new File(fileName); | |
476 if (file.isDirectory()) { | |
477 continue; | |
478 } | |
479 if (verbose) { | |
480 System.out.println("checking " + fileName); | |
481 } | |
482 try { | |
483 Info info = null; | |
484 if (DIR_WALK.getValue()) { | |
485 info = getFromLastModified(cal, fileName); | |
486 } else { | |
487 final List<String> logInfo = hglog(fileName); | |
488 if (logInfo.size() == 0) { | |
489 // an added file, so go with last modified | |
490 info = getFromLastModified(cal, fileName); | |
491 } else { | |
492 info = getInfo(fileName, true, logInfo); | |
493 } | |
494 } | |
495 checkFile(fileName, info); | |
496 } catch (Exception e) { | |
497 System.err.format("COPYRIGHT CHECK WARNING: error while processing %s: %s%n", fileName, e.getMessage()); | |
498 } | |
499 } | |
500 } | |
501 } | |
502 | |
503 private static Info getFromLastModified(Calendar cal, String fileName) { | |
504 File file = new File(fileName); | |
505 cal.setTimeInMillis(file.lastModified()); | |
506 int year = cal.get(Calendar.YEAR); | |
507 return new Info(fileName, year, year); | |
508 } | |
509 | |
510 private static boolean isInProjects(String fileName, List<String> projects) { | |
511 final int ix = fileName.indexOf(File.separatorChar); | |
512 if (ix < 0) { | |
513 return false; | |
514 } | |
515 final String fileProject = fileName.substring(0, ix); | |
516 for (String project : projects) { | |
517 if (fileProject.equals(project)) { | |
518 return true; | |
519 } | |
520 } | |
521 return false; | |
522 } | |
523 | |
524 private static List<String> readFileList(String fileListName) throws IOException { | |
525 final List<String> result = new ArrayList<String>(); | |
526 BufferedReader b = null; | |
527 try { | |
528 b = new BufferedReader(new FileReader(fileListName)); | |
529 while (true) { | |
530 final String fileName = b.readLine(); | |
531 if (fileName == null) { | |
532 break; | |
533 } | |
534 if (fileName.length() == 0) { | |
535 continue; | |
536 } | |
537 result.add(fileName); | |
538 } | |
539 } finally { | |
540 if (b != null) { | |
541 b.close(); | |
542 } | |
543 } | |
544 return result; | |
545 } | |
546 | |
547 private static Info getInfo(String fileName, boolean lastOnly, List<String> logInfo) { | |
548 // process sequence of changesets | |
549 int lastYear = 0; | |
550 int firstYear = 0; | |
551 int ix = 0; | |
552 | |
553 while (ix < logInfo.size()) { | |
554 Map<String, String> tagMap = new HashMap<>(); | |
555 ix = getChangeset(logInfo, ix, tagMap); | |
556 String date = tagMap.get("date"); | |
557 assert date != null; | |
558 final int csYear = getYear(date); | |
559 if (lastYear == 0) { | |
560 lastYear = csYear; | |
561 firstYear = lastYear; | |
562 } else { | |
563 firstYear = csYear; | |
564 } | |
565 // if we only want the last modified year, quit now | |
566 if (lastOnly) { | |
567 break; | |
568 } | |
569 | |
570 } | |
571 | |
572 if (HG_MODIFIED.getValue()) { | |
573 // We are only looking at modified and, therefore, uncommitted files. | |
574 // This means that the lastYear value will be the current year once the | |
575 // file is committed, so that is what we want to check against. | |
576 lastYear = currentYear; | |
577 } | |
578 return new Info(fileName, firstYear, lastYear); | |
579 } | |
580 | |
581 /** | |
582 * Process all the changeset data, storing in {@outMap}. | |
583 * Return updated value of {@code ix}. | |
584 */ | |
585 private static int getChangeset(List<String> logInfo, int ixx, Map<String, String> outMap) { | |
586 int ix = ixx; | |
587 String s = logInfo.get(ix++); | |
588 while (s.length() > 0) { | |
589 int cx = s.indexOf(':'); | |
590 String tag = s.substring(0, cx); | |
591 String value = s.substring(cx + 1); | |
592 outMap.put(tag, value); | |
593 s = logInfo.get(ix++); | |
594 } | |
595 return ix; | |
596 } | |
597 | |
598 private static int getYear(String dateLine) { | |
599 final String[] parts = dateLine.split(" "); | |
600 assert parts[parts.length - 2].startsWith("20"); | |
601 return Integer.parseInt(parts[parts.length - 2]); | |
602 } | |
603 | |
604 private static void checkFile(String c, Info info) throws IOException { | |
605 String fileName = info.fileName; | |
606 File file = new File(fileName); | |
607 if (!file.exists()) { | |
608 System.err.println("COPYRIGHT CHECK WARNING: file " + file + " doesn't exist"); | |
609 return; | |
610 } | |
611 int fileLength = (int) file.length(); | |
612 byte[] fileContentBytes = new byte[fileLength]; | |
613 FileInputStream is = new FileInputStream(file); | |
614 is.read(fileContentBytes); | |
615 is.close(); | |
616 final String fileContent = new String(fileContentBytes); | |
617 CopyrightHandler copyrightHandler = CopyrightHandler.getCopyrightHandler(fileName); | |
618 if (file.getName().equals("Makefile")) { | |
619 System.console(); | |
620 } | |
621 if (copyrightHandler != null) { | |
622 Matcher copyrightMatcher = copyrightHandler.getMatcher(fileName, fileContent); | |
623 if (copyrightMatcher.matches()) { | |
624 error = error | !copyrightHandler.checkYearInfo(fileName, fileContent, copyrightMatcher, info); | |
625 } else { | |
626 // If copyright is missing, insert it, otherwise user has to manually fix existing copyright. | |
627 if (!fileContent.contains("Copyright")) { | |
628 System.out.print("file " + fileName + " has missing copyright"); | |
629 if (FIX.getValue()) { | |
630 final FileOutputStream os = new FileOutputStream(file); | |
631 os.write(CopyrightHandler.getCopyrightText(fileName) | |
632 .getBytes()); | |
633 os.write(fileContentBytes); | |
634 os.close(); | |
635 System.out.println("...fixed"); | |
636 } else { | |
637 System.out.println(); | |
638 error = true; | |
639 } | |
640 } else { | |
641 System.out.println("file " + fileName + " has malformed copyright" + (FIX.getValue() ? " not fixing" : "")); | |
642 error = true; | |
643 } | |
644 } | |
645 } else if (EXHAUSTIVE.getValue()) { | |
646 System.out.println("ERROR: file " + fileName + " has no copyright"); | |
647 error = true; | |
648 } | |
649 } | |
650 | |
651 | |
652 private static List<String> hglog(String fileName) throws Exception { | |
653 final String[] cmd = new String[] {hgPath, "log", "-f", fileName}; | |
654 return exec(null, cmd, true); | |
655 } | |
656 | |
657 private static List<String> getLastNFiles(int n) throws Exception { | |
658 final String[] cmd = new String[] {hgPath, "log", "-v", "-l", Integer.toString(n)}; | |
659 return getFilesFiles(exec(null, cmd, false)); | |
660 } | |
661 | |
662 private static List<String> getAllFiles(boolean all) throws Exception { | |
663 final String[] cmd; | |
664 if (HG_MODIFIED.getValue()) { | |
665 cmd = new String[] {hgPath, "status"}; | |
666 } else { | |
667 cmd = new String[] {hgPath, "status", "--all"}; | |
668 } | |
669 List<String> output = exec(null, cmd, true); | |
670 final List<String> result = new ArrayList<String>(output.size()); | |
671 for (String s : output) { | |
672 final char ch = s.charAt(0); | |
673 if (!(ch == 'R' || ch == 'I' || ch == '?' || ch == '!')) { | |
674 result.add(s.substring(2)); | |
675 } | |
676 } | |
677 return result; | |
678 } | |
679 | |
680 private static List<String> getOutgoingFiles() throws Exception { | |
681 final String[] cmd; | |
682 if (OUTGOING_REPO.getValue() == null) { | |
683 cmd = new String[] {hgPath, "-v", "outgoing"}; | |
684 } else { | |
685 cmd = new String[] {hgPath, "-v", "outgoing", OUTGOING_REPO.getValue()}; | |
686 } | |
687 | |
688 final List<String> output = exec(null, cmd, false); // no outgoing exits with result 1 | |
689 return getFilesFiles(output); | |
690 } | |
691 | |
692 private static List<String> getFilesFiles(List<String> output) { | |
693 // there may be multiple changesets so merge the "files:" | |
694 final Map<String, String> outSet = new TreeMap<String, String>(); | |
695 for (String s : output) { | |
696 if (s.startsWith("files:")) { | |
697 int ix = s.indexOf(' '); | |
698 while (ix < s.length() && s.charAt(ix) == ' ') { | |
699 ix++; | |
700 } | |
701 final String[] files = s.substring(ix).split(" "); | |
702 for (String file : files) { | |
703 outSet.put(file, file); | |
704 } | |
705 } | |
706 } | |
707 return new ArrayList<String>(outSet.values()); | |
708 } | |
709 | |
710 private static List<String> getDirWalkFiles() { | |
711 File cwd = new File(System.getProperty("user.dir")); | |
712 ArrayList<String> result = new ArrayList<String>(); | |
713 getDirWalkFiles(cwd, result); | |
714 // remove "user.dir" prefix to make files relative as per hg | |
715 String cwdPath = cwd.getAbsolutePath() + '/'; | |
716 for (int i = 0; i < result.size(); i++) { | |
717 String path = result.get(i); | |
718 result.set(i, path.replace(cwdPath, "")); | |
719 } | |
720 return result; | |
721 } | |
722 | |
723 private static void getDirWalkFiles(File dir, ArrayList<String> list) { | |
724 File[] files = dir.listFiles(); | |
725 for (File file : files) { | |
726 if (ignoreFile(file.getName())) { | |
727 continue; | |
728 } | |
729 if (file.isDirectory()) { | |
730 getDirWalkFiles(file, list); | |
731 } else { | |
732 list.add(file.getAbsolutePath()); | |
733 } | |
734 } | |
735 } | |
736 | |
737 private static final String IGNORE_LIST = "\\.hg|.*\\.class|bin|src_gen"; | |
738 private static final Pattern ignorePattern = Pattern.compile(IGNORE_LIST); | |
739 | |
740 private static boolean ignoreFile(String name) { | |
741 return ignorePattern.matcher(name).matches(); | |
742 } | |
743 | |
744 private static List<String> exec(File workingDir, String[] command, boolean failOnError) throws IOException, InterruptedException { | |
745 List<String> result = new ArrayList<String>(); | |
746 if (veryVerbose) { | |
747 System.out.println("Executing process in directory: " + workingDir); | |
748 for (String c : command) { | |
749 System.out.println(" " + c); | |
750 } | |
751 } | |
752 final Process process = Runtime.getRuntime().exec(command, null, workingDir); | |
753 try { | |
754 result = readOutput(process.getInputStream()); | |
755 final int exitValue = process.waitFor(); | |
756 if (exitValue != 0) { | |
757 final List<String> errorResult = readOutput(process.getErrorStream()); | |
758 if (REPORT_ERRORS.getValue()) { | |
759 System.err.print("execution of command: "); | |
760 for (String c : command) { | |
761 System.err.print(c); | |
762 System.err.print(' '); | |
763 } | |
764 System.err.println("failed with result " + exitValue); | |
765 for (String e : errorResult) { | |
766 System.err.println(e); | |
767 } | |
768 } | |
769 if (failOnError && HALT_ON_ERROR.getValue()) { | |
770 if (!cannotFollowNonExistentFile(errorResult)) { | |
771 throw new Error("terminating"); | |
772 } | |
773 } | |
774 } | |
775 } finally { | |
776 process.destroy(); | |
777 } | |
778 return result; | |
779 } | |
780 | |
781 private static boolean cannotFollowNonExistentFile(List<String> errorResult) { | |
782 return errorResult.size() == 1 && errorResult.get(0).startsWith(CANNOT_FOLLOW_FILE); | |
783 } | |
784 | |
785 private static List<String> readOutput(InputStream is) throws IOException { | |
786 final List<String> result = new ArrayList<String>(); | |
787 BufferedReader bs = null; | |
788 try { | |
789 bs = new BufferedReader(new InputStreamReader(is)); | |
790 while (true) { | |
791 final String line = bs.readLine(); | |
792 if (line == null) { | |
793 break; | |
794 } | |
795 result.add(line); | |
796 } | |
797 } finally { | |
798 if (bs != null) { | |
799 bs.close(); | |
800 } | |
801 } | |
802 return result; | |
803 } | |
804 | |
805 private static class Options { | |
806 private static Map<String, Option<?>> optionMap = new TreeMap<>(); | |
807 | |
808 private Option<Boolean> newBooleanOption(String name, boolean defaultValue, String help) { | |
809 Option<Boolean> option = new Option<Boolean>(name, help, defaultValue, false, false); | |
810 optionMap.put(key(name), option); | |
811 return option; | |
812 } | |
813 | |
814 private Option<String> newStringOption(String name, String defaultValue, String help) { | |
815 Option<String> option = new Option<String>(name, help, defaultValue); | |
816 optionMap.put(key(name), option); | |
817 return option; | |
818 } | |
819 | |
820 private Option<List<String>> newStringListOption(String name, List<String> defaultValue, String help) { | |
821 Option<List<String>> option = new Option<List<String>>(name, help, defaultValue, true, true); | |
822 optionMap.put(key(name), option); | |
823 return option; | |
824 } | |
825 | |
826 private static String key(String name) { | |
827 return "--" + name; | |
828 } | |
829 | |
830 void parseArguments(String[] args) { | |
831 for (int i = 0; i < args.length; i++) { | |
832 final String arg = args[i]; | |
833 if (arg.startsWith("--")) { | |
834 Option<?> option = optionMap.get(arg); | |
835 if (option == null || (option.consumesNext() && i == args.length - 1)) { | |
836 System.out.println("usage:"); | |
837 printHelp(); | |
838 System.exit(1); | |
839 } | |
840 if (option.consumesNext()) { | |
841 i++; | |
842 option.setValue(args[i]); | |
843 } else { | |
844 option.setValue(true); | |
845 } | |
846 } | |
847 } | |
848 } | |
849 | |
850 void printHelp() { | |
851 int maxKeyLen = 0; | |
852 for (Map.Entry<String, Option<?>> entrySet : optionMap.entrySet()) { | |
853 int l = entrySet.getKey().length(); | |
854 if (l > maxKeyLen) { | |
855 maxKeyLen = l; | |
856 } | |
857 } | |
858 for (Map.Entry<String, Option<?>> entrySet : optionMap.entrySet()) { | |
859 String key = entrySet.getKey(); | |
860 System.out.printf(" %s", key); | |
861 for (int i = 0; i < maxKeyLen - key.length(); i++) { | |
862 System.out.print(' '); | |
863 } | |
864 System.out.printf(" %s%n", entrySet.getValue().help); | |
865 } | |
866 } | |
867 } | |
868 | |
869 private static class Option<T> { | |
870 private final String name; | |
871 private final String help; | |
872 private final boolean consumesNext; | |
873 private final boolean isList; | |
874 private T value; | |
875 | |
876 Option(String name, String help, T defaultValue, boolean consumesNext, boolean isList) { | |
877 this.name = name; | |
878 this.help = help; | |
879 this.value = defaultValue; | |
880 this.consumesNext = consumesNext; | |
881 this.isList = isList; | |
882 | |
883 } | |
884 | |
885 Option(String name, String help, T defaultValue) { | |
886 this(name, help, defaultValue, true, false); | |
887 } | |
888 | |
889 T getValue() { | |
890 return value; | |
891 } | |
892 | |
893 boolean consumesNext() { | |
894 return consumesNext; | |
895 } | |
896 | |
897 @SuppressWarnings("unchecked") | |
898 void setValue(boolean value) { | |
899 this.value = (T) new Boolean(value); | |
900 } | |
901 | |
902 @SuppressWarnings("unchecked") | |
903 void setValue(String value) { | |
904 if (isList) { | |
905 String[] parts = value.split(","); | |
906 this.value = (T) Arrays.asList(parts); | |
907 } else { | |
908 this.value = (T) value; | |
909 } | |
910 } | |
911 | |
912 @SuppressWarnings("unused") | |
913 String getName() { | |
914 return name; | |
915 } | |
916 } | |
917 | |
918 } |