You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Checklics.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. /* *******************************************************************
  2. * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
  3. * All rights reserved.
  4. * This program and the accompanying materials are made available
  5. * under the terms of the Eclipse Public License v 2.0
  6. * which accompanies this distribution and is available at
  7. * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
  8. *
  9. * Contributors:
  10. * Xerox/PARC initial implementation
  11. * ******************************************************************/
  12. package org.aspectj.internal.tools.ant.taskdefs;
  13. import org.apache.tools.ant.BuildException;
  14. import org.apache.tools.ant.Project;
  15. import org.apache.tools.ant.taskdefs.MatchingTask;
  16. import org.apache.tools.ant.types.Path;
  17. import org.apache.tools.ant.types.Reference;
  18. import java.io.*;
  19. import java.util.*;
  20. /**
  21. * Check that included .java files contain license and copyright strings for MPL 1.0 (default), Apache, or CPL. Use list="true" to
  22. * get a list of known license variants {license}-{copyrightHolder} todo reimplement with regexp and jdiff FileLine utilities
  23. */
  24. @SuppressWarnings("deprecation")
  25. public class Checklics extends MatchingTask {
  26. /*
  27. * This does not enforce that copyrights are correct/current, only that they exist. E.g., the default behavior requires MPL but
  28. * permits either Xerox or PARC copyright holders and any valid year.
  29. */
  30. public static final String MPL_TAG = "mpl";
  31. public static final String APACHE_TAG = "apache";
  32. public static final String CPL_IBM_PARC_TAG = "cpl-ibm|parc";
  33. public static final String CPL_IBM_TAG = "cpl-ibm";
  34. public static final String MPL_XEROX_PARC_TAG = "mpl-parc|xerox";
  35. public static final String MPL_ONLY_TAG = "mpl-only";
  36. public static final String MPL_PARC_TAG = "mpl-parc";
  37. public static final String PARC_COPYRIGHT_TAG = "parc-copy";
  38. public static final String CPL_IBM_PARC_XEROX_TAG = "cpl-ibm|parc|xerox";
  39. public static final String CPL_IBM_PARC_XEROX_OTHERS_TAG = "cpl-ibm|parc|xerox|others";
  40. public static final String EPL_CPL_IBM_PARC_XEROX_OTHERS_TAG = "epl-cpl-ibm|parc|xerox|vmware|others";
  41. public static final String DEFAULT = EPL_CPL_IBM_PARC_XEROX_OTHERS_TAG;
  42. static final Map<String,License> LICENSES; // unmodifiable Map
  43. static {
  44. final String CONTRIBUTORS = "Contributors";
  45. final String XEROX = "Xerox";
  46. final String PARC = "Palo Alto Research Center";
  47. final String APACHE = "The Apache Software Foundation";
  48. final String IBM = "IBM";
  49. final String VMWARE = "VMware";
  50. final String IBM_LONG = "International Business Machines";
  51. final String LIC_APL = "Apache Software Foundation (http://www.apache.org/)";
  52. final String LIC_MPL = "http://aspectj.org/MPL/";
  53. final String LIC_CPL = "Eclipse Public License";
  54. final String LIC_ECPL = " Public License";
  55. License APL = new License(APACHE_TAG, LIC_APL, APACHE);
  56. License MPL = new License(MPL_TAG, LIC_MPL, XEROX);
  57. License MPL_XEROX_PARC = new License(DEFAULT, LIC_MPL, XEROX, PARC);
  58. License CPL_IBM_PARC = new License(CPL_IBM_PARC_TAG, LIC_CPL, new String[] { IBM_LONG, IBM, PARC });
  59. License CPL_IBM_PARC_XEROX = new License(CPL_IBM_PARC_XEROX_TAG, LIC_CPL, new String[] { IBM_LONG, IBM, PARC, XEROX });
  60. License CPL_IBM_PARC_XEROX_OTHERS = new License(CPL_IBM_PARC_XEROX_OTHERS_TAG, LIC_CPL, new String[] { IBM_LONG, IBM, PARC,
  61. XEROX, CONTRIBUTORS });
  62. License EPL_CPL_IBM_PARC_XEROX_OTHERS = new License(EPL_CPL_IBM_PARC_XEROX_OTHERS_TAG, LIC_ECPL, new String[] { IBM_LONG,
  63. IBM, PARC, XEROX, VMWARE, CONTRIBUTORS });
  64. License CPL_IBM = new License(CPL_IBM_TAG, LIC_CPL, IBM, IBM_LONG);
  65. License MPL_ONLY = new License(MPL_ONLY_TAG, LIC_MPL);
  66. License MPL_PARC = new License(MPL_PARC_TAG, LIC_MPL, PARC);
  67. License PARC_COPYRIGHT = new License(PARC_COPYRIGHT_TAG, null, PARC);
  68. LICENSES = new Hashtable<>();
  69. LICENSES.put(APL.tag, APL);
  70. LICENSES.put(MPL.tag, MPL);
  71. LICENSES.put(MPL_PARC.tag, MPL_PARC);
  72. LICENSES.put(MPL_XEROX_PARC.tag, MPL_XEROX_PARC);
  73. LICENSES.put(CPL_IBM_PARC.tag, CPL_IBM_PARC);
  74. LICENSES.put(MPL_ONLY.tag, MPL_ONLY);
  75. LICENSES.put(CPL_IBM.tag, CPL_IBM);
  76. LICENSES.put(PARC_COPYRIGHT.tag, PARC_COPYRIGHT);
  77. LICENSES.put(CPL_IBM_PARC_XEROX.tag, CPL_IBM_PARC_XEROX);
  78. LICENSES.put(CPL_IBM_PARC_XEROX_OTHERS.tag, CPL_IBM_PARC_XEROX_OTHERS);
  79. LICENSES.put(EPL_CPL_IBM_PARC_XEROX_OTHERS.tag, EPL_CPL_IBM_PARC_XEROX_OTHERS);
  80. }
  81. /** @param args String[] { &lt; sourcepath &gt; {, &lt; licenseTag &gt; } } */
  82. public static void main(String[] args) {
  83. switch (args.length) {
  84. case 1:
  85. runDirect(args[0], null, false);
  86. break;
  87. case 2:
  88. runDirect(args[0], args[1], false);
  89. break;
  90. default:
  91. String options = "{replace-headers|get-years|list|{licenseTag}}";
  92. System.err.println("java {me} sourcepath " + options);
  93. break;
  94. }
  95. }
  96. /**
  97. * Run the license check directly
  98. *
  99. * @param sourcepaths String[] of paths to source directories
  100. * @param license the String tag for the license, if any
  101. * @param failonerror boolean flag to pass to Checklics
  102. * @throws IllegalArgumentException if sourcepaths is empty
  103. * @return total number of failed licenses
  104. */
  105. public static int runDirect(String sourcepath, String license, boolean failonerror) {
  106. if ((null == sourcepath) || (1 > sourcepath.length())) {
  107. throw new IllegalArgumentException("bad sourcepath: " + sourcepath);
  108. }
  109. Checklics me = new Checklics();
  110. Project p = new Project();
  111. p.setName("direct interface to Checklics");
  112. p.setBasedir(".");
  113. me.setProject(p);
  114. me.setFailOnError(failonerror);
  115. me.setSourcepath(new Path(p, sourcepath));
  116. if (null != license) {
  117. if ("replace-headers".equals(license)) {
  118. me.setReplaceheaders(true);
  119. } else if ("get-years".equals(license)) {
  120. me.setGetYears(true);
  121. } else if ("list".equals(license)) {
  122. me.setList(true);
  123. } else {
  124. me.setLicense(license);
  125. }
  126. }
  127. me.execute();
  128. return me.failed;
  129. }
  130. private Path sourcepath;
  131. private License license;
  132. private boolean list;
  133. private String streamTag;
  134. private boolean failOnError;
  135. private boolean getYears;
  136. private boolean replaceHeaders;
  137. private int failed;
  138. private int passed;
  139. private boolean printDirectories;
  140. /** @param list if true, don't run but list known license tags */
  141. public void setList(boolean list) {
  142. this.list = list;
  143. }
  144. public void setPrintDirectories(boolean print) {
  145. printDirectories = print;
  146. }
  147. /**
  148. * When failOnError is true, if any file failed, throw BuildException listing number of files that file failed to pass license
  149. * check
  150. *
  151. * @param fail if true, report errors by throwing BuildException
  152. */
  153. public void setFailOnError(boolean fail) {
  154. this.failOnError = fail;
  155. }
  156. /** @param tl mpl | apache | cpl */
  157. public void setLicense(String tl) {
  158. License input = LICENSES.get(tl);
  159. if (null == input) {
  160. throw new BuildException("no license known for " + tl);
  161. }
  162. license = input;
  163. }
  164. public void setSourcepath(Path path) {
  165. if (sourcepath == null) {
  166. sourcepath = path;
  167. } else {
  168. sourcepath.append(path);
  169. }
  170. }
  171. public Path createSourcepath() {
  172. return sourcepath == null ? (sourcepath = new Path(project)) : sourcepath.createPath();
  173. }
  174. public void setSourcepathRef(Reference id) {
  175. createSourcepath().setRefid(id);
  176. }
  177. /** @param out "out" or "err" */
  178. public void setOutputStream(String out) {
  179. this.streamTag = out;
  180. }
  181. public void setReplaceheaders(boolean replaceHeaders) {
  182. this.replaceHeaders = replaceHeaders;
  183. }
  184. public void setGetYears(boolean getYears) {
  185. this.getYears = getYears;
  186. }
  187. /** list known licenses or check source tree */
  188. @Override
  189. public void execute() throws BuildException {
  190. if (list) {
  191. list();
  192. } else if (replaceHeaders) {
  193. replaceHeaders();
  194. } else if (getYears) {
  195. getYears();
  196. } else {
  197. checkLicenses();
  198. }
  199. }
  200. private PrintStream getOut() {
  201. return ("err".equals(streamTag) ? System.err : System.out);
  202. }
  203. interface FileVisitor {
  204. void visit(File file);
  205. }
  206. /** visit all .java files in all directories... */
  207. private void visitAll(FileVisitor visitor) {
  208. // List filelist = new ArrayList();
  209. String[] dirs = sourcepath.list();
  210. for (String dir2 : dirs) {
  211. File dir = project.resolveFile(dir2);
  212. String[] files = getDirectoryScanner(dir).getIncludedFiles();
  213. for (String file2 : files) {
  214. File file = new File(dir, file2);
  215. String path = file.getPath();
  216. if (path.endsWith(".java")) {
  217. visitor.visit(file);
  218. }
  219. }
  220. }
  221. }
  222. private void replaceHeaders() {
  223. class YearVisitor implements FileVisitor {
  224. @Override
  225. public void visit(File file) {
  226. HeaderInfo info = Header.checkFile(file);
  227. if (!Header.replaceHeader(file, info)) {
  228. throw new BuildException("failed to replace header for " + file + " using " + info);
  229. }
  230. }
  231. }
  232. visitAll(new YearVisitor());
  233. }
  234. private void getYears() {
  235. final PrintStream out = getOut();
  236. class YearVisitor implements FileVisitor {
  237. @Override
  238. public void visit(File file) {
  239. HeaderInfo info = Header.checkFile(file);
  240. out.println(info.toString());
  241. }
  242. }
  243. visitAll(new YearVisitor());
  244. }
  245. private void checkLicenses() throws BuildException {
  246. if (null == license) {
  247. setLicense(DEFAULT);
  248. }
  249. final License license = this.license; // being paranoid...
  250. if (null == license) {
  251. throw new BuildException("no license");
  252. }
  253. final PrintStream out = getOut();
  254. class Visitor implements FileVisitor {
  255. int failed = 0;
  256. int passed = 0;
  257. @Override
  258. public void visit(File file) {
  259. if (license.checkFile(file)) {
  260. passed++;
  261. } else {
  262. failed++;
  263. String path = file.getPath();
  264. if (!license.foundLicense()) {
  265. out.println(license.tag + " LICENSE FAIL: " + path);
  266. }
  267. if (!license.foundCopyright()) {
  268. out.println(license.tag + " COPYRIGHT FAIL: " + path);
  269. }
  270. }
  271. }
  272. }
  273. Visitor visitor = new Visitor();
  274. visitAll(visitor);
  275. this.failed = visitor.failed;
  276. this.passed = visitor.passed;
  277. if (0 < visitor.failed) {
  278. getOut().println("Total passed: " + visitor.passed + (visitor.failed == 0 ? "" : " failed: " + visitor.failed));
  279. if (failOnError) {
  280. throw new BuildException(failed + " files failed license check");
  281. }
  282. }
  283. }
  284. private void list() {
  285. Iterator enu = LICENSES.keySet().iterator();
  286. StringBuilder sb = new StringBuilder();
  287. sb.append("known license keys:");
  288. boolean first = true;
  289. while (enu.hasNext()) {
  290. sb.append((first ? " " : ", ") + enu.next());
  291. if (first) {
  292. first = false;
  293. }
  294. }
  295. getOut().println(sb.toString());
  296. }
  297. /**
  298. * Encapsulate license and copyright specifications to check files use hokey string matching.
  299. */
  300. public static class License {
  301. /** acceptable years for copyright prefix to company - append " " */
  302. static final String[] YEARS = // remove older after license xfer?
  303. new String[] {
  304. "2002 ", "2003 ", "2004 ", "2005", "2006", "2007", "2008", "2009", "2010", "2011",
  305. "2012", "2013", "2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021",
  306. "2001 ", "2000 ", "1999 " };
  307. public final String tag;
  308. public final String license;
  309. private final String[] copyright;
  310. private boolean gotLicense;
  311. private boolean gotCopyright;
  312. License(String tag, String license) {
  313. this(tag, license, (String[]) null);
  314. }
  315. License(String tag, String license, String copyright) {
  316. this(tag, license, new String[] { copyright });
  317. }
  318. License(String tag, String license, String copyright, String altCopyright) {
  319. this(tag, license, new String[] { copyright, altCopyright });
  320. }
  321. License(String tag, String license, String[] copyright) {
  322. this.tag = tag;
  323. if ((null == tag) || (0 == tag.length())) {
  324. throw new IllegalArgumentException("null tag");
  325. }
  326. this.license = license;
  327. this.copyright = copyright;
  328. }
  329. public final boolean gotValidFile() {
  330. return foundLicense() && foundCopyright();
  331. }
  332. /** @return true if no license sought or if some license found */
  333. public final boolean foundLicense() {
  334. return ((null == license) || gotLicense);
  335. }
  336. /** @return true if no copyright sought or if some copyright found */
  337. public final boolean foundCopyright() {
  338. return ((null == copyright) || gotCopyright);
  339. }
  340. public boolean checkFile(final File file) {
  341. clear();
  342. // boolean result = false;
  343. BufferedReader input = null;
  344. int lineNum = 0;
  345. try {
  346. input = new BufferedReader(new FileReader(file));
  347. String line;
  348. while (!gotValidFile() && (line = input.readLine()) != null) {
  349. lineNum++;
  350. checkLine(line);
  351. }
  352. } catch (IOException e) {
  353. System.err.println("reading line " + lineNum + " of " + file);
  354. e.printStackTrace(System.err);
  355. } finally {
  356. if (null != input) {
  357. try {
  358. input.close();
  359. } catch (IOException e) {
  360. } // ignore
  361. }
  362. }
  363. return gotValidFile();
  364. }
  365. @Override
  366. public String toString() {
  367. return tag;
  368. }
  369. private void checkLine(String line) {
  370. if ((null == line) || (0 == line.length())) {
  371. return;
  372. }
  373. if (!gotLicense && (null != license) && (line.contains(license))) {
  374. gotLicense = true;
  375. }
  376. if (!gotCopyright && (null != copyright)) {
  377. int loc;
  378. for (int j = 0; !gotCopyright && (j < YEARS.length); j++) {
  379. if (-1 != (loc = line.indexOf(YEARS[j]))) {
  380. loc += YEARS[j].length();
  381. String afterLoc = line.substring(loc).trim();
  382. for (int i = 0; !gotCopyright && (i < copyright.length); i++) {
  383. if (0 == afterLoc.indexOf(copyright[i])) {
  384. gotCopyright = true;
  385. }
  386. }
  387. }
  388. }
  389. }
  390. }
  391. private void clear() {
  392. if (gotLicense) {
  393. gotLicense = false;
  394. }
  395. if (gotCopyright) {
  396. gotCopyright = false;
  397. }
  398. }
  399. } // class License
  400. }
  401. class HeaderInfo {
  402. /** File for which this is the info */
  403. public final File file;
  404. /** unmodifiable List of String years */
  405. public final List years;
  406. /** last line of license */
  407. public final int lastLine;
  408. /** last line of license */
  409. public final boolean hasLicense;
  410. public HeaderInfo(File file, int lastLine, List<String> years, boolean hasLicense) {
  411. this.lastLine = lastLine;
  412. this.file = file;
  413. this.hasLicense = hasLicense;
  414. List<String> newYears = new ArrayList<>(years);
  415. Collections.sort(newYears);
  416. this.years = Collections.unmodifiableList(newYears);
  417. if ((null == file) || !file.canWrite()) {
  418. throw new IllegalArgumentException("bad file: " + this);
  419. }
  420. if (!hasLicense) {
  421. if ((0 > lastLine) || (65 < lastLine)) {
  422. throw new IllegalArgumentException("bad last line: " + this);
  423. }
  424. } else {
  425. if ((null == years) || (1 > years.size())) {
  426. throw new IllegalArgumentException("no years: " + this);
  427. }
  428. if ((20 > lastLine) || (65 < lastLine)) {
  429. throw new IllegalArgumentException("bad last line: " + this);
  430. }
  431. }
  432. }
  433. @Override
  434. public String toString() {
  435. return file.getPath() + ":" + lastLine + " " + years;
  436. }
  437. public void writeHeader(PrintWriter writer) {
  438. if (!hasLicense) {
  439. writer.println(TOP);
  440. writer.println(PARC_ONLY);
  441. writeRest(writer);
  442. } else {
  443. final int size = years.size();
  444. if (1 > size) {
  445. throw new Error("no years found in " + toString());
  446. }
  447. String first = (String) years.get(0);
  448. String last = (String) years.get(size - 1);
  449. boolean lastIs2002 = "2002".equals(last);
  450. String xlast = last;
  451. if (lastIs2002) { // 2002 was PARC
  452. xlast = (String) (size > 1 ? years.get(size - 2) : null);
  453. // 1999-2002 Xerox implies 1999-2001 Xerox
  454. if (first.equals(xlast) && !"2001".equals(xlast)) {
  455. xlast = "2001";
  456. }
  457. }
  458. String xyears = first + "-" + xlast;
  459. if (first.equals(last)) {
  460. xyears = first;
  461. }
  462. writer.println(TOP);
  463. if (!lastIs2002) { // Xerox only
  464. writer.println(XEROX_PREFIX + xyears + XEROX_SUFFIX + ". ");
  465. } else if (size == 1) { // PARC only
  466. writer.println(PARC_ONLY);
  467. } else { // XEROX plus PARC
  468. writer.println(XEROX_PREFIX + xyears + XEROX_SUFFIX + ", ");
  469. writer.println(PARC);
  470. }
  471. writeRest(writer);
  472. }
  473. }
  474. void writeRest(PrintWriter writer) {
  475. writer.println(" * All rights reserved.");
  476. writer.println(" * This program and the accompanying materials are made available");
  477. writer.println(" * under the terms of the Eclipse Public License v 2.0");
  478. writer.println(" * which accompanies this distribution and is available at");
  479. writer.println(" * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt");
  480. writer.println(" * ");
  481. writer.println(" * Contributors:");
  482. writer.println(" * Xerox/PARC initial implementation");
  483. writer.println(" * ******************************************************************/");
  484. writer.println("");
  485. }
  486. public static final String TOP = "/* *******************************************************************";
  487. public static final String PARC = " * 2002 Palo Alto Research Center, Incorporated (PARC).";
  488. public static final String PARC_ONLY = " * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).";
  489. public static final String XEROX_PREFIX = " * Copyright (c) ";
  490. public static final String XEROX_SUFFIX = " Xerox Corporation";
  491. }
  492. /**
  493. * header search/replace using hokey string matching
  494. */
  495. class Header {
  496. /** replace the header in file */
  497. public static boolean replaceHeader(File file, HeaderInfo info) {
  498. // ArrayList years = new ArrayList();
  499. // int endLine = 0;
  500. BufferedReader input = null;
  501. PrintWriter output = null;
  502. FileWriter outWriter = null;
  503. int lineNum = 0;
  504. boolean result = false;
  505. final File inFile = new File(file.getPath() + ".tmp");
  506. try {
  507. File outFile = new File(file.getPath());
  508. if (!file.renameTo(inFile) || !inFile.canRead()) {
  509. throw new Error("unable to rename " + file + " to " + inFile);
  510. }
  511. outWriter = new FileWriter(outFile);
  512. input = new BufferedReader(new FileReader(inFile));
  513. output = new PrintWriter(outWriter, true);
  514. info.writeHeader(output);
  515. String line;
  516. while (null != (line = input.readLine())) {
  517. lineNum++;
  518. if (lineNum > info.lastLine) {
  519. output.println(line);
  520. }
  521. }
  522. } catch (IOException e) {
  523. System.err.println("writing line " + lineNum + " of " + file);
  524. e.printStackTrace(System.err);
  525. result = false;
  526. } finally {
  527. if (null != input) {
  528. try {
  529. input.close();
  530. } catch (IOException e) {
  531. result = false;
  532. }
  533. }
  534. if (null != outWriter) {
  535. try {
  536. outWriter.close();
  537. } catch (IOException e) {
  538. result = false;
  539. }
  540. }
  541. result = inFile.delete();
  542. }
  543. return result;
  544. }
  545. public static HeaderInfo checkFile(final File file) {
  546. ArrayList<String> years = new ArrayList<>();
  547. int endLine = 0;
  548. BufferedReader input = null;
  549. int lineNum = 0;
  550. try {
  551. input = new BufferedReader(new FileReader(file));
  552. String line;
  553. while (null != (line = input.readLine())) {
  554. lineNum++;
  555. String ll = line.trim();
  556. if (ll.startsWith("package ") || ll.startsWith("import ")) {
  557. break; // ignore default package w/o imports
  558. }
  559. if (checkLine(line, years)) {
  560. endLine = lineNum;
  561. break;
  562. }
  563. }
  564. } catch (IOException e) {
  565. System.err.println("reading line " + lineNum + " of " + file);
  566. e.printStackTrace(System.err);
  567. } finally {
  568. if (null != input) {
  569. try {
  570. input.close();
  571. } catch (IOException e) {
  572. } // ignore
  573. }
  574. }
  575. return new HeaderInfo(file, endLine, years, endLine > 0);
  576. }
  577. /**
  578. * Add any years found (as String) to years, and return true at the first end-of-comment
  579. *
  580. * @return true if this line has end-of-comment
  581. */
  582. private static boolean checkLine(String line, List<String> years) {
  583. if ((null == line) || (0 == line.length())) {
  584. return false;
  585. }
  586. int loc;
  587. int start = 0;
  588. while ((-1 != (loc = line.indexOf("199", start)) || (-1 != (loc = line.indexOf("200", start))))) {
  589. char c = line.charAt(loc + 3);
  590. if ((c <= '9') && (c >= '0')) {
  591. years.add(line.substring(loc, loc + 4));
  592. }
  593. start = loc + 4;
  594. }
  595. return (line.contains("*/"));
  596. }
  597. } // class Header