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 }