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.

CompareFiles.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. /* *******************************************************************
  2. * Copyright (c) 1999-2001 Xerox Corporation,
  3. * 2002 Palo Alto Research Center, Incorporated (PARC).
  4. * All rights reserved.
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Common Public License v1.0
  7. * which accompanies this distribution and is available at
  8. * http://www.eclipse.org/legal/cpl-v10.html
  9. *
  10. * Contributors:
  11. * Xerox/PARC initial implementation
  12. * ******************************************************************/
  13. package org.aspectj.testing.compare;
  14. import org.aspectj.util.LangUtil;
  15. import jdiff.util.Diff;
  16. import jdiff.text.FileLine;
  17. import jdiff.util.DiffNormalOutput;
  18. import java.util.*;
  19. import java.util.zip.*;
  20. import java.io.*;
  21. /**
  22. * Compare two files and emit differences.
  23. * Requires the jdiff library in jdiff/jdiff.jar
  24. */
  25. public class CompareFiles {
  26. protected static String[] NO_STRINGS = new String[]{};
  27. /** standard rendering of null references */
  28. public static final String NULL = "null";
  29. /** filter for the both files */
  30. protected RegexpFilter filter;
  31. /** ignore case by converting lines to upper case */
  32. protected boolean ignoreCase = false;
  33. /** collapse internal whitespace by converting to space character */
  34. protected boolean collapseWhitespace = true;
  35. /** trim leading and trailing whitespace from lines before comparison */
  36. protected boolean trimWhitespace = false;
  37. /** output to this File - if not set, System.out */
  38. protected File output = null;
  39. /**
  40. * Compare two files by lines, emitting output to System.out as a series of edits.
  41. * @param args the String[] containing two files to diff plus any number of
  42. * <li>-i "ignore": ignore case</li>
  43. * <li>-t "trim" : ignore leading and trailing white space</li>
  44. * <li>-b "blanks": ignore differences in all white space</li>
  45. * @param lhs the File on the left-hand-side of the comparison
  46. * @param rhs the File on the left-hand-side of the comparison
  47. * @throws IllegalArgumentException if cannot read both files
  48. */
  49. public static void main(String[] args) {
  50. new CompareFiles().comparefiles(args);
  51. }
  52. /**
  53. * Write results of a diff to some output Writer
  54. * @param result the DiffResult containing the results of the diff
  55. * @param output the Writer to output results to - if null,
  56. * defaults to System.out, but will be closed if not null
  57. * @throws IllegalArgumentException if null == result or output
  58. */
  59. public static void writeDiffResult(DiffResult result, File output)
  60. throws IOException {
  61. if (null == result) throw new IllegalArgumentException("null result");
  62. Writer writer = (null != output ? new FileWriter(output)
  63. : new OutputStreamWriter(System.out));
  64. DiffNormalOutput out = new DiffNormalOutput(result.lhsLines, result.rhsLines);
  65. out.setOut(writer);
  66. out.setLineSeparator(LangUtil.EOL);
  67. try {
  68. out.writeScript(result.edits);
  69. } finally {
  70. if (null != output) {
  71. try { writer.close(); }
  72. catch (IOException e) {
  73. e.printStackTrace(System.err);
  74. }
  75. }
  76. }
  77. } // writeDiffResult
  78. /**
  79. * descend filesystem tree, invoking FileRunnerI.accept() on files.
  80. * E.g., To list files from current directory:
  81. * <code><pre>descendFileTree(new File("."), new FileRunnerI() {
  82. * public boolean accept(File f){
  83. * System.out.println(f.getAbsolutePath());
  84. * return true;
  85. * }});</code></pre>
  86. * @param file root/starting point. If a file, the only one visited.
  87. * @param filter supplies accept(File) routine
  88. */
  89. public static void descendFileTree(File file, FileFilter filter) {
  90. descendFileTree(file, filter, false);
  91. }
  92. /**
  93. * Descend filesystem tree, invoking FileFilter.accept() on files
  94. * and, if userRecursion, on dirs. If userRecursion, accept() must
  95. * call descendFileTree() again to recurse down directories.
  96. * This calls fileFilter.accept(File) on all files before doing any dirs.
  97. * E.g., To list only files from Unix root:
  98. * <code><pre>descendFileTree(new File("/"), new FileFilter() {
  99. * public boolean run(File f){
  100. * System.out.println(f.getAbsolutePath());
  101. * return true;
  102. * }}, false);</code></pre>
  103. * To list files/dir from root using user recursion:
  104. * <code><pre>descendFileTree(new File("/"), new FileFilter() {
  105. * public boolean run(File f){
  106. * System.out.println(f.getAbsolutePath());
  107. * if (f.isDirectory() && (-1 == f.getName().indexOf("CVS")))
  108. * return descendFileTree(f, this, true);
  109. * return true;
  110. * }}, true);</code></pre>
  111. * @param file root/starting point. If a file, the only one visited.
  112. * @param filter supplies boolean accept(File) method
  113. * @param userRecursion - if true, do accept() on dirs; else, recurse
  114. * @return false if any fileFilter.accept(File) did.
  115. * @throws IllegalArgumentException if file or fileFilter is null
  116. */
  117. public static boolean descendFileTree(File file, FileFilter fileFilter,
  118. boolean userRecursion) {
  119. if (null == file) {throw new IllegalArgumentException("parm File"); }
  120. if (null == fileFilter){throw new IllegalArgumentException("parm FileFilter");}
  121. if (!file.isDirectory()) {
  122. return fileFilter.accept(file);
  123. } else if (file.canRead()) {
  124. // go through files first
  125. File[] files = file.listFiles(ValidFileFilter.FILE_EXISTS);
  126. if (null != files) {
  127. for (int i = 0; i < files.length; i++) {
  128. if (!fileFilter.accept(files[i])) {
  129. return false;
  130. }
  131. }
  132. }
  133. // now recurse to handle directories
  134. File[] dirs = file.listFiles(ValidFileFilter.DIR_EXISTS);
  135. if (null != dirs) {
  136. for (int i = 0; i < dirs.length; i++) {
  137. if (userRecursion) {
  138. if (!fileFilter.accept(dirs[i])) {
  139. return false;
  140. }
  141. } else {
  142. if (!descendFileTree(dirs[i], fileFilter,userRecursion)) {
  143. return false;
  144. }
  145. }
  146. }
  147. }
  148. } // readable directory (ignore unreadable ones)
  149. return true;
  150. } // descendFiles
  151. /**
  152. * Render a zip/entry combination to String
  153. */
  154. private static String renderZipEntry(File zipfile, ZipEntry entry) {
  155. String filename = (null == zipfile ? "null File" : zipfile.getName());
  156. String entryname = (null == entry ? "null ZipEntry" : entry.getName());
  157. //return filename + "!" + entryname;
  158. return "" + entryname;
  159. }
  160. /**
  161. * Initialise the filter. Users must do this before the filter is
  162. * lazily constructed or must set update to true.
  163. * @param args the String with any args valid for RegexpFilter.init(String, RegexpFilter)
  164. * @param update if true, use existing filter settings unless
  165. * overwritten by the new ones;
  166. * if false, create a new filter using args.
  167. * @throws IllegalArgumentException if cannot read both files
  168. * @see RegexpFilter#init(String, RegexpFilter)
  169. */
  170. public void initFilter(String arg, boolean update) {
  171. filter = RegexpFilter.init(arg, update ? filter : null);
  172. }
  173. /**
  174. * Initialise the filter. Users must do this before the filter is
  175. * lazily constructed or must set update to true.
  176. * @param args the String[] with any args valid for RegexpFilter
  177. * @param update if true, use existing filter settings unless
  178. * overwritten by the new ones;
  179. * if false, create a new filter using args.
  180. * @throws IllegalArgumentException if cannot read both files
  181. * @see RegexpFilter#init(String[], RegexpFilter)
  182. */
  183. public void initFilter(String[] args, boolean update) {
  184. filter = RegexpFilter.init(args, update ? filter : null);
  185. }
  186. /**
  187. * Compare two files by lines, emitting output to System.out as a series of edits.
  188. * @param args the String[] containing two files to diff
  189. * (lhs, rhs) plus any args valid for RegexpFilter
  190. * @throws IllegalArgumentException if cannot read both files
  191. */
  192. public final void comparefiles(String[] args) {
  193. if (errMessage(null == args, "null args", null)) return;
  194. if (errMessage(args.length < 2, "need more args", null)) return;
  195. File lhs = new File(args[0]);
  196. File rhs = new File(args[1]);
  197. if (errMessage(!lhs.canRead(), "!lhs.canRead()", null)) return;
  198. if (errMessage(!rhs.canRead(), "!rhs.canRead()", null)) return;
  199. int filterArgsLength = args.length - 2;
  200. if (0 >= filterArgsLength) {
  201. initFilter(NO_STRINGS, false);
  202. } else {
  203. String[] filterArgs = new String[filterArgsLength];
  204. System.arraycopy(args, 0, filterArgs, 0, filterArgsLength);
  205. initFilter(filterArgs, false);
  206. }
  207. try {
  208. if (errMessage(!diff(lhs, rhs), "diff(lhs,rhs)",null)) return;
  209. } catch (IOException t) {
  210. if (errMessage(false, null, t)) return;
  211. }
  212. } // main
  213. /**
  214. * Compare two files/dirs, emitting output as a series of edits.
  215. * If both files are directories, then this compares their contents
  216. * (including the contents of any zip or jar files) as a series of paths
  217. * (i.e., it will recognize added or removed or changed filenames, but
  218. * not files whose contents have changed).
  219. * Output will go to the File specifies as "output" or System.out if none specified.
  220. * This is costly, creating an in-memory copy, one String per line, of both files.
  221. * @param lhs the File/dir on the left-hand-side of the comparison
  222. * @param rhs the File/dir on the left-hand-side of the comparison
  223. * @throws IllegalArgumentException if either parm is null or not existing,
  224. * or if one is a dir and the other a file
  225. * @throws IOException if getFileLines or diff utilities do
  226. * @return false if there was some error
  227. */
  228. public final boolean diff(File lhs, File rhs) throws IOException {
  229. DiffResult result = null;
  230. String err = null;
  231. if ((null == lhs) || (null == rhs)
  232. || (!lhs.exists()) || (!rhs.exists())
  233. || (!lhs.canRead()) || (!rhs.canRead())
  234. || (lhs.isDirectory() != rhs.isDirectory())) {
  235. err = "need 2 readable files or dirs or zip files - got lhs=" + lhs + " rhs=" + rhs;
  236. } else if (lhs.isDirectory()) {
  237. result = diffDirUtil(lhs, rhs);
  238. } else {
  239. boolean lhsIsZip = isZipFile(lhs);
  240. if (lhsIsZip != isZipFile(rhs)) {
  241. err = "need 2 readable files or dirs or zip files - got lhs=" + lhs + " rhs=" + rhs;
  242. } else if (lhsIsZip) {
  243. result = diffDirUtil(lhs, rhs);
  244. } else {
  245. result = diffUtil(lhs, rhs);
  246. }
  247. }
  248. if (null != err) throw new IllegalArgumentException(err);
  249. if (errMessage(null == result, null, null)) return false;
  250. writeDiffResult(result, output);
  251. return true;
  252. }
  253. /**
  254. * Compare two files, returning results for further evaluation or processing
  255. * @param lhs the File on the left-hand-side of the comparison
  256. * @param rhs the File on the left-hand-side of the comparison
  257. * @throws IllegalArgumentException if either parm is null
  258. * @throws IOException if getFileLines or diff utilities do
  259. * @return false if there was some error
  260. */
  261. public final DiffResult diffUtil(File lhs, File rhs)
  262. throws IOException {
  263. if (errMessage(null == lhs, "null lhs", null)) return null;
  264. if (errMessage(null == rhs, "null rhs", null)) return null;
  265. FileLine[] lhsLines = getFileLines(lhs);
  266. FileLine[] rhsLines = getFileLines(rhs);
  267. Diff.change edits = new Diff(lhsLines, rhsLines).diff_2(false);
  268. return new DiffResult(lhsLines, rhsLines, edits);
  269. }
  270. /**
  271. * Read all lines of a file into a String[] (not very efficient),
  272. * implementing flag policies.
  273. * @param file the File to read
  274. * @return a FileLine[] with elements for each line in the file
  275. */
  276. public FileLine[] getFileLines(File file) throws IOException {
  277. if (file.isDirectory()) return getFileLinesForDir(file);
  278. final Vector results = new Vector();
  279. BufferedReader in = null;
  280. try {
  281. in = getReader(file);
  282. String line;
  283. while (null != (line = in.readLine())) {
  284. results.add(toFileLine(line));
  285. }
  286. } finally {
  287. if (null != in) { in.close(); }
  288. }
  289. final FileLine[] lines = new FileLine[results.size()];
  290. results.copyInto(lines);
  291. return lines;
  292. }
  293. /**
  294. * Compare two directories or zip files by listing contents (including contents of zip/jar files)
  295. * and differencing the results. This does NOT call diff on each file, but only
  296. * recognizes when files or zip entries have been added or removed.
  297. * @param lhsDir the File for an existing, readable directory (as left-hand-side)
  298. * @param rhsDir the File for an existing, readable directory (as right-hand-side)
  299. * @throws IllegalArgumentException if null == lhs or rhsDir
  300. */
  301. public DiffResult diffDirUtil(File lhsDir, File rhsDir) {
  302. FileLine[] lhsLines = getFileLinesForDir(lhsDir);
  303. FileLine[] rhsLines = getFileLinesForDir(rhsDir);
  304. // now do the comparison as if they were two files
  305. Diff.change edits = new Diff(lhsLines, rhsLines).diff_2(false);
  306. return new DiffResult(lhsLines, rhsLines, edits);
  307. }
  308. /**
  309. * Render all sub-elements of a directory as a list of FileLine[], including entries
  310. * in zip and jar files. The directory prefix is not included in the FileLine.
  311. * @param dir the File representing the directory to list
  312. * @throws IllegalArgumentException if null == dir or !dir.isDirectory()
  313. */
  314. public FileLine[] getFileLinesForDir(File dir) {
  315. if (null == dir) throw new IllegalArgumentException("null dir");
  316. if (!dir.isDirectory() && ! isZipFile(dir)) throw new IllegalArgumentException("not a dir: " + dir);
  317. Collection items = directoryToString(dir, null);
  318. return toFileLine(items, dir.getPath(), true);
  319. }
  320. /** @return true if test or if null != error */
  321. protected boolean errMessage(boolean test, String message, Throwable error) {
  322. if (test && (null != message)) { System.err.println(message); }
  323. if (null != error) { error.printStackTrace(System.err); }
  324. return (test || (null != error));
  325. }
  326. /**
  327. * Convert current setting into an initialization list for filter
  328. */
  329. protected String[] getFilterArgs() {
  330. return new String[]
  331. { (trimWhitespace ? "-t" : "-T")
  332. , (collapseWhitespace ? "-b" : "-B")
  333. , (ignoreCase ? "-i" : "-I")
  334. };
  335. }
  336. /**
  337. * Lazy construction of filter
  338. */
  339. protected RegexpFilter getFilter() {
  340. if (null == filter) {
  341. filter = RegexpFilter.init(getFilterArgs(), null);
  342. }
  343. return filter;
  344. }
  345. /**
  346. * Factory for reader used by getFileLines(File).
  347. * Default implementation creates a BufferedReader.
  348. * Subclasses may implement pre-processing filters here.
  349. */
  350. protected BufferedReader getReader(File file) throws IOException {
  351. return new BufferedReader(new FileReader(file));
  352. }
  353. /**
  354. * Create a FileLine from the input string,
  355. * applying policies for whitespace, etc.
  356. * @param string the String to wrap as a FileLine
  357. * @return FileLine with string as text and
  358. * canonical as string modified by any canonicalizing policies.
  359. */
  360. protected FileLine toFileLine(String string) {
  361. String canonical = getFilter().process(string);
  362. return new FileLine(string, canonical);
  363. }
  364. protected boolean isZipFile(File f) {
  365. String s = null;
  366. if ((null == f) || (null == (s = f.getPath()))) {
  367. return false;
  368. } else {
  369. return (f.exists() && !f.isDirectory() && (s.endsWith(".jar")));
  370. }
  371. }
  372. /**
  373. * Convert to an array of FileLine by optionally removing prefix and/or sorting
  374. * @param collection the Collection of String to process
  375. */
  376. protected FileLine[] toFileLine(Collection collection, String ignorePrefix, boolean sort) {
  377. if (null == collection) throw new IllegalArgumentException("null collection");
  378. List list = new ArrayList();
  379. list.addAll(collection);
  380. if (null != ignorePrefix) {
  381. for (int i = 0; i < list.size(); i++) {
  382. String next = list.get(i).toString();
  383. if (next.startsWith(ignorePrefix)) {
  384. list.set(i, next.substring(ignorePrefix.length()));
  385. }
  386. }
  387. }
  388. if (sort) {
  389. Collections.sort(list);
  390. }
  391. FileLine[] result = new FileLine[list.size()];
  392. int i = 0;
  393. for (Iterator it = list.iterator(); it.hasNext();) {
  394. result[i++] = toFileLine(it.next().toString());
  395. }
  396. if (i < result.length) {
  397. throw new Error("list lost elements? " + (result.length-i));
  398. }
  399. return result;
  400. }
  401. /**
  402. * Return the names of all files below a directory.
  403. * If file is a directory, then all files under the directory
  404. * are returned. If file is absolute or relative, all the files are.
  405. * If file is a zip or jar file, then all entries in the zip or jar
  406. * are listed. Entries inside those jarfiles/zipfiles are not listed.
  407. * There are no guarantees about ordering.
  408. * @param dir the File to list for
  409. * @param results the Collection to use for the results (may be null)
  410. * @throws IllegalArgumentException if null == dir
  411. * @return a Collection of String of paths, including paths inside jars
  412. */
  413. protected Collection directoryToString(File dir, Collection results) {
  414. if (null == dir) throw new IllegalArgumentException("null dir");
  415. final Collection result = (results != null? results : new Vector());
  416. if (isZipFile(dir)) {
  417. zipFileToString(dir, result);
  418. } else if (!dir.isDirectory()) {
  419. throw new IllegalArgumentException("not a dir: " + dir);
  420. } else {
  421. AccumulatingFileFilter acFilter = new AccumulatingFileFilter() {
  422. public boolean accumulate(File file) {
  423. String name = file.getPath();
  424. result.add(name);
  425. if (isZipFile(file)) {
  426. zipFileToString(file, result);
  427. }
  428. return true;
  429. }
  430. };
  431. descendFileTree(dir, acFilter, false);
  432. }
  433. return result;
  434. } // directoryToString
  435. /**
  436. * Render as String the entries in a zip or jar file,
  437. * converting each to String beforehand (as jarpath!jarentry)
  438. * applying policies for whitespace, etc.
  439. * @param file the File to enumerate ZipEntry for
  440. * @param results the Colection to use to return the FileLine - may be null
  441. * @return FileLines with string as text and
  442. * canonical as string modified by any canonicalizing policies.
  443. */
  444. protected Collection zipFileToString(final File zipfile, Collection results) {
  445. Collection result = (results != null ? results : new Vector());
  446. ZipFile zip = null;
  447. try {
  448. //ZipFile.OPEN_READ | ZipFile.OPEN_DELETE); delete is 1.3 only
  449. zip = new ZipFile(zipfile);
  450. Enumeration enum = zip.entries();
  451. // now emitting filename only once, so entries match even if filename does not
  452. results.add("ZipFileName: " + zipfile);
  453. while (enum.hasMoreElements()) {
  454. results.add(renderZipEntry(zipfile, (ZipEntry) enum.nextElement()));
  455. }
  456. zip.close();
  457. zip = null;
  458. } catch (Throwable t) {
  459. String err = "Error opening " + zipfile + " attempting to continue...";
  460. System.err.println(err);
  461. t.printStackTrace(System.err);
  462. } finally {
  463. if (null != zip) {
  464. try { zip.close(); }
  465. catch (IOException e) {
  466. e.printStackTrace(System.err);
  467. }
  468. }
  469. }
  470. return result;
  471. }
  472. /**
  473. * return structure for diffUtil
  474. */
  475. public final class DiffResult {
  476. public final FileLine[] lhsLines;
  477. public final FileLine[] rhsLines;
  478. public final Diff.change edits;
  479. public DiffResult (FileLine[] lhsLines,
  480. FileLine[] rhsLines,
  481. Diff.change edits) {
  482. this.lhsLines = lhsLines;
  483. this.rhsLines = rhsLines;
  484. this.edits = edits;
  485. }
  486. }
  487. } // class CompareFiles
  488. class ValidFileFilter implements FileFilter {
  489. public static final FileFilter EXIST = new ValidFileFilter();
  490. public static final FileFilter FILE_EXISTS = new FilesOnlyFilter();
  491. public static final FileFilter DIR_EXISTS = new DirsOnlyFilter();
  492. protected ValidFileFilter(){}
  493. public boolean accept(File f) {
  494. return ((null != f) && (f.exists()));
  495. }
  496. static class FilesOnlyFilter extends ValidFileFilter {
  497. public final boolean accept(File f) {
  498. return (super.accept(f) && (!f.isDirectory()));
  499. }
  500. }
  501. static class DirsOnlyFilter extends ValidFileFilter {
  502. public final boolean accept(File f) {
  503. return (super.accept(f) && (f.isDirectory()));
  504. }
  505. }
  506. }
  507. /**
  508. * A FileFilter that accumulates the results when called if they exist.
  509. * Subclasses override accumulate to determine whether it should be
  510. * accumulated.
  511. */
  512. class AccumulatingFileFilter extends ValidFileFilter {
  513. Vector files = new Vector();
  514. public final boolean accept(File f) {
  515. if (super.accept(f) && (accumulate(f))) {
  516. files.add(f);
  517. }
  518. return true;
  519. }
  520. /**
  521. * This implementation accumulates everything.
  522. * Subclasses should override to implement filter
  523. * @param file a File guaranteed to exist
  524. * @return true if file should be accumulated.
  525. */
  526. public boolean accumulate(File f) {
  527. return true;
  528. }
  529. /**
  530. * @return list of files currently accumulated
  531. */
  532. public File[] getFiles() {
  533. return (File[]) files.toArray(new File[0]);
  534. }
  535. }