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.

Diffs.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  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 Eclipse Public License v1.0
  7. * which accompanies this distribution and is available at
  8. * http://www.eclipse.org/legal/epl-v10.html
  9. *
  10. * Contributors:
  11. * Xerox/PARC initial implementation
  12. * ******************************************************************/
  13. package org.aspectj.testing.util;
  14. import java.io.File;
  15. import java.util.ArrayList;
  16. import java.util.Arrays;
  17. import java.util.Collections;
  18. import java.util.Comparator;
  19. import java.util.Iterator;
  20. import java.util.List;
  21. import java.util.ListIterator;
  22. import org.aspectj.bridge.IMessage;
  23. import org.aspectj.bridge.IMessageHandler;
  24. import org.aspectj.bridge.ISourceLocation;
  25. import org.aspectj.bridge.MessageUtil;
  26. import org.aspectj.util.FileUtil;
  27. import org.aspectj.util.LangUtil;
  28. /**
  29. * Result struct for expected/actual diffs for Collection
  30. */
  31. public class Diffs {
  32. /**
  33. * Compare IMessage.Kind based on kind priority.
  34. */
  35. public static final Comparator KIND_PRIORITY = new Comparator() {
  36. /**
  37. * Compare IMessage.Kind based on kind priority.
  38. * @throws NullPointerException if anything is null
  39. */
  40. public int compare(Object lhs, Object rhs) {
  41. return ((IMessage.Kind) lhs).compareTo((IMessage.Kind) rhs);
  42. }
  43. };
  44. /**
  45. * Sort ISourceLocation based on line, file path.
  46. */
  47. public static final Comparator SORT_SOURCELOC = new Comparator() {
  48. /**
  49. * Compare ISourceLocation based on line, file path.
  50. * @throws NullPointerException if anything is null
  51. */
  52. public int compare(Object lhs, Object rhs) {
  53. ISourceLocation l = (ISourceLocation) lhs;
  54. ISourceLocation r = (ISourceLocation) rhs;
  55. int result = getLine(l) - getLine(r);
  56. if (0 != result) {
  57. return result;
  58. }
  59. String lp = getSourceFile(l).getPath();
  60. String rp = getSourceFile(r).getPath();
  61. return lp.compareTo(rp);
  62. }
  63. };
  64. /**
  65. * Compare IMessages based on kind and source location line (only).
  66. */
  67. public static final Comparator<IMessage> MESSAGE_LINEKIND = new Comparator<IMessage>() {
  68. /**
  69. * Compare IMessages based on kind and source location line (only).
  70. * @throws NullPointerException if anything is null
  71. */
  72. public int compare(IMessage lhs, IMessage rhs) {
  73. IMessage lm = (IMessage) lhs;
  74. IMessage rm = (IMessage) rhs;
  75. ISourceLocation ls = (lm == null ? null : lm.getSourceLocation());
  76. ISourceLocation rs = (rm == null ? null : rm.getSourceLocation());
  77. int left = (ls == null ? -1 : ls.getLine());
  78. int right = (rs == null ? -1 : rs.getLine());
  79. int result = left - right;
  80. if (0 == result) {
  81. result = lm.getKind().compareTo(rm.getKind());
  82. }
  83. return result;
  84. }
  85. };
  86. public static final Filter ACCEPT_ALL = new Filter() {
  87. public boolean accept(Object o) {
  88. return true;
  89. }
  90. };
  91. // // XXX List -> Collection b/c comparator orders
  92. // public static final Diffs NONE
  93. // = new Diffs("NONE", Collections.EMPTY_LIST, Collections.EMPTY_LIST);
  94. public static Diffs makeDiffs(
  95. String label,
  96. List expected,
  97. List actual,
  98. Comparator comparator) {
  99. return makeDiffs(
  100. label,
  101. expected,
  102. actual,
  103. comparator,
  104. ACCEPT_ALL,
  105. ACCEPT_ALL);
  106. }
  107. public static Diffs makeDiffs(
  108. String label,
  109. IMessage[] expected,
  110. IMessage[] actual) {
  111. return makeDiffs(label, expected, actual, null, null);
  112. }
  113. private static int getLine(ISourceLocation loc) {
  114. int result = -1;
  115. if (null != loc) {
  116. result = loc.getLine();
  117. }
  118. return result;
  119. }
  120. private static int getLine(IMessage message) {
  121. int result = -1;
  122. if ((null != message)) {
  123. result = getLine(message.getSourceLocation());
  124. }
  125. return result;
  126. }
  127. private static File getSourceFile(ISourceLocation loc) {
  128. File result = ISourceLocation.NO_FILE;
  129. if (null != loc) {
  130. result = loc.getSourceFile();
  131. }
  132. return result;
  133. }
  134. public static Diffs makeDiffs(
  135. String label,
  136. IMessage[] expected,
  137. IMessage[] actual,
  138. IMessage.Kind[] ignoreExpectedKinds,
  139. IMessage.Kind[] ignoreActualKinds) {
  140. ArrayList exp = getExcept(expected, ignoreExpectedKinds);
  141. ArrayList act = getExcept(actual, ignoreActualKinds);
  142. ArrayList missing = new ArrayList();
  143. List unexpected = new ArrayList();
  144. if (LangUtil.isEmpty(expected)) {
  145. unexpected.addAll(act);
  146. } else if (LangUtil.isEmpty(actual)) {
  147. missing.addAll(exp);
  148. } else {
  149. ListIterator expectedIterator = exp.listIterator();
  150. int lastLine = Integer.MIN_VALUE + 1;
  151. ArrayList expectedFound = new ArrayList();
  152. ArrayList expectedForLine = new ArrayList();
  153. for (ListIterator iter = act.listIterator(); iter.hasNext();) {
  154. IMessage actualMessage = (IMessage) iter.next();
  155. int actualLine = getLine(actualMessage);
  156. if (actualLine != lastLine) {
  157. // new line - get all messages expected for it
  158. if (lastLine > actualLine) {
  159. throw new Error("sort error");
  160. }
  161. lastLine = actualLine;
  162. expectedForLine.clear();
  163. while (expectedIterator.hasNext()) {
  164. IMessage curExpected =
  165. (IMessage) expectedIterator.next();
  166. int curExpectedLine = getLine(curExpected);
  167. if (actualLine == curExpectedLine) {
  168. expectedForLine.add(curExpected);
  169. } else {
  170. expectedIterator.previous();
  171. break;
  172. }
  173. }
  174. }
  175. // now check actual against all expected on that line
  176. boolean found = false;
  177. IMessage expectedMessage = null;
  178. for (Iterator iterator = expectedForLine.iterator();
  179. !found && iterator.hasNext();
  180. ) {
  181. expectedMessage = (IMessage) iterator.next();
  182. found = expectingMessage(expectedMessage, actualMessage);
  183. }
  184. if (found) {
  185. iter.remove();
  186. if (expectedFound.contains(expectedMessage)) {
  187. // XXX warn: expected message matched two actual
  188. } else {
  189. expectedFound.add(expectedMessage);
  190. }
  191. } else {
  192. // unexpected: any actual result not found
  193. unexpected.add(actualMessage);
  194. }
  195. }
  196. // missing: all expected results not found
  197. exp.removeAll(expectedFound);
  198. missing.addAll(exp);
  199. }
  200. return new Diffs(label, missing, unexpected);
  201. }
  202. public static Diffs makeDiffs(
  203. String label,
  204. List expected,
  205. List actual,
  206. Comparator comparator,
  207. Filter missingFilter,
  208. Filter unexpectedFilter) {
  209. label = label.trim();
  210. if (null == label) {
  211. label = ": ";
  212. } else if (!label.endsWith(":")) {
  213. label += ": ";
  214. }
  215. final String thisLabel = " " + label;
  216. ArrayList miss = new ArrayList();
  217. ArrayList unexpect = new ArrayList();
  218. org.aspectj.testing.util.LangUtil.makeSoftDiffs(
  219. expected,
  220. actual,
  221. miss,
  222. unexpect,
  223. comparator);
  224. if (null != missingFilter) {
  225. for (ListIterator iter = miss.listIterator(); iter.hasNext();) {
  226. if (!missingFilter.accept(iter.next())) {
  227. iter.remove();
  228. }
  229. }
  230. }
  231. if (null != unexpectedFilter) {
  232. for (ListIterator iter = unexpect.listIterator();
  233. iter.hasNext();
  234. ) {
  235. if (!unexpectedFilter.accept(iter.next())) {
  236. iter.remove();
  237. }
  238. }
  239. }
  240. return new Diffs(thisLabel, miss, unexpect);
  241. }
  242. // /**
  243. // * Shift over elements in sink if they are of one of the specified kinds.
  244. // * @param sink the IMessage[] to shift elements from
  245. // * @param kinds
  246. // * @return length of sink after shifting
  247. // * (same as input length if nothing shifted)
  248. // */
  249. // public static int removeKinds(IMessage[] sink, IMessage.Kind[] kinds) {
  250. // if (LangUtil.isEmpty(kinds)) {
  251. // return sink.length;
  252. // } else if (LangUtil.isEmpty(sink)) {
  253. // return 0;
  254. // }
  255. // int from = -1;
  256. // int to = -1;
  257. // for (int j = 0; j < sink.length; j++) {
  258. // from++;
  259. // if (null == sink[j]) {
  260. // continue;
  261. // }
  262. // boolean remove = false;
  263. // for (int i = 0; !remove && (i < kinds.length); i++) {
  264. // IMessage.Kind kind = kinds[i];
  265. // if (null == kind) {
  266. // continue;
  267. // }
  268. // if (0 == kind.compareTo(sink[j].getKind())) {
  269. // remove = true;
  270. // }
  271. // }
  272. // if (!remove) {
  273. // to++;
  274. // if (to != from) {
  275. // sink[to] = sink[from];
  276. // }
  277. // }
  278. // }
  279. // return to+1;
  280. // }
  281. /**
  282. * @param expected the File from the expected source location
  283. * @param actual the File from the actual source location
  284. * @return true if exp is ISourceLocation.NO_FILE
  285. * or exp path is a suffix of the actual path
  286. * (after using FileUtil.weakNormalize(..) on both)
  287. */
  288. static boolean expectingFile(File expected, File actual) {
  289. if (null == expected) {
  290. return (null == actual);
  291. } else if (null == actual) {
  292. return false;
  293. }
  294. if (expected != ISourceLocation.NO_FILE) {
  295. String expPath = FileUtil.weakNormalize(expected.getPath());
  296. String actPath = FileUtil.weakNormalize(actual.getPath());
  297. if (!actPath.endsWith(expPath)) {
  298. return false;
  299. }
  300. }
  301. return true;
  302. }
  303. /**
  304. * Soft comparison for expected message will not check a corresponding
  305. * element in the actual message unless defined in the expected message.
  306. * <pre>
  307. * message
  308. * kind must match (constant/priority)
  309. * message only requires substring
  310. * thrown ignored
  311. * column ignored
  312. * endline ignored
  313. * details only requires substring
  314. * sourceLocation
  315. * line must match, unless expected < 0
  316. * file ignored if ISourceLocation.NOFILE
  317. * matches if expected is a suffix of actual
  318. * after changing any \ to /
  319. * extraSourceLocation[]
  320. * if any are defined in expected, then there
  321. * must be exactly the actual elements as are
  322. * defined in expected (so it is an error to
  323. * not define all if you define any)
  324. * <pre>
  325. * @param expected
  326. * @param actual
  327. * @return true if we are expecting the line, kind, file, message,
  328. * details, and any extra source locations.
  329. * (ignores column/endline, thrown) XXX
  330. */
  331. static boolean expectingMessage(IMessage expected, IMessage actual) {
  332. if (null == expected) {
  333. return (null == actual);
  334. } else if (null == actual) {
  335. return false;
  336. }
  337. if (0 != expected.getKind().compareTo(actual.getKind())) {
  338. return false;
  339. }
  340. if (!expectingSourceLocation(expected.getSourceLocation(),
  341. actual.getSourceLocation())) {
  342. return false;
  343. }
  344. if (!expectingText(expected.getMessage(), actual.getMessage())) {
  345. return false;
  346. }
  347. if (!expectingText(expected.getDetails(), actual.getDetails())) {
  348. return false;
  349. }
  350. ISourceLocation[] esl =
  351. (ISourceLocation[]) expected.getExtraSourceLocations().toArray(
  352. new ISourceLocation[0]);
  353. ISourceLocation[] asl =
  354. (ISourceLocation[]) actual.getExtraSourceLocations().toArray(
  355. new ISourceLocation[0]);
  356. Arrays.sort(esl, SORT_SOURCELOC);
  357. Arrays.sort(asl, SORT_SOURCELOC);
  358. if (!expectingSourceLocations(esl, asl)) {
  359. return false;
  360. }
  361. return true;
  362. }
  363. /**
  364. * This returns true if no ISourceLocation are specified
  365. * (i.e., it ignored any extra source locations if no expectations stated).
  366. * XXX need const like NO_FILE.
  367. * @param expected the sorted ISourceLocation[] expected
  368. * @param expected the actual sorted ISourceLocation[]
  369. * @return true if any expected element is expected by the corresponding actual element.
  370. */
  371. static boolean expectingSourceLocations(
  372. ISourceLocation[] expected,
  373. ISourceLocation[] actual) {
  374. if (LangUtil.isEmpty(expected)) {
  375. return true;
  376. } else if (LangUtil.isEmpty(actual)) {
  377. return false;
  378. } else if (actual.length != expected.length) {
  379. return false;
  380. }
  381. for (int i = 0; i < actual.length; i++) {
  382. if (!expectingSourceLocation(expected[i], actual[i])) {
  383. return false;
  384. }
  385. }
  386. return true;
  387. }
  388. /**
  389. * @param expected
  390. * @param actual
  391. * @return true if any expected line/file matches the actual line/file,
  392. * accepting a substring as a file match
  393. */
  394. static boolean expectingSourceLocation(
  395. ISourceLocation expected,
  396. ISourceLocation actual) {
  397. int eline = getLine(expected);
  398. int aline = getLine(actual);
  399. if ((-1 < eline) && (eline != aline)) {
  400. return false;
  401. }
  402. if (!expectingFile(getSourceFile(expected), getSourceFile(actual))) {
  403. return false;
  404. }
  405. return true;
  406. }
  407. /**
  408. * @param expected the String in the expected message
  409. * @param actual the String in the actual message
  410. * @return true if both are null or actual contains expected
  411. */
  412. static boolean expectingText(String expected, String actual) {
  413. if (null == expected) {
  414. return true; // no expectations
  415. } else if (null == actual) {
  416. return false; // expected something
  417. } else {
  418. return (-1 != actual.indexOf(expected));
  419. }
  420. }
  421. private static ArrayList getExcept(
  422. IMessage[] source,
  423. IMessage.Kind[] skip) {
  424. ArrayList<IMessage> sink = new ArrayList<IMessage>();
  425. if (LangUtil.isEmpty(source)) {
  426. return sink;
  427. }
  428. if (LangUtil.isEmpty(skip)) {
  429. sink.addAll(Arrays.asList(source));
  430. Collections.sort(sink, MESSAGE_LINEKIND);
  431. return sink;
  432. }
  433. for (int i = 0; i < source.length; i++) {
  434. IMessage message = source[i];
  435. IMessage.Kind mkind = message.getKind();
  436. boolean skipping = false;
  437. for (int j = 0; !skipping && (j < skip.length); j++) {
  438. if (0 == mkind.compareTo(skip[j])) {
  439. skipping = true;
  440. }
  441. }
  442. if (!skipping) {
  443. sink.add(message);
  444. }
  445. }
  446. Collections.sort(sink, MESSAGE_LINEKIND);
  447. return sink;
  448. }
  449. private static List harden(List list) {
  450. return (
  451. LangUtil.isEmpty(list)
  452. ? Collections.EMPTY_LIST
  453. : Collections.unmodifiableList(list));
  454. }
  455. /** name of the thing being diffed - used only for reporting */
  456. public final String label;
  457. /** immutable List */
  458. public final List missing;
  459. /** immutable List */
  460. public final List unexpected;
  461. /** true if there are any missing or unexpected */
  462. public final boolean different;
  463. /**
  464. * Struct-constructor stores these values,
  465. * wrapping the lists as unmodifiable.
  466. * @param label the String label for these diffs
  467. * @param missing the List of missing elements
  468. * @param unexpected the List of unexpected elements
  469. */
  470. public Diffs(String label, List missing, List unexpected) {
  471. this.label = label;
  472. this.missing = harden(missing);
  473. this.unexpected = harden(unexpected);
  474. different =
  475. ((0 != this.missing.size()) || (0 != this.unexpected.size()));
  476. }
  477. /**
  478. * Report missing and extra items to handler.
  479. * For each item in missing or unexpected, this creates a {kind} IMessage with
  480. * the text "{missing|unexpected} {label}: {message}"
  481. * where {message} is the result of
  482. * <code>MessageUtil.renderMessage(IMessage)</code>.
  483. * @param handler where the messages go - not null
  484. * @param kind the kind of message to construct - not null
  485. * @param label the prefix for the message text - if null, "" used
  486. * @see MessageUtil#renderMessage(IMessage)
  487. */
  488. public void report(IMessageHandler handler, IMessage.Kind kind) {
  489. LangUtil.throwIaxIfNull(handler, "handler");
  490. LangUtil.throwIaxIfNull(kind, "kind");
  491. if (different) {
  492. for (Iterator iter = missing.iterator(); iter.hasNext();) {
  493. String s = MessageUtil.renderMessage((IMessage) iter.next());
  494. MessageUtil.fail(handler, "missing " + label + ": " + s);
  495. }
  496. for (Iterator iter = unexpected.iterator(); iter.hasNext();) {
  497. String s = MessageUtil.renderMessage((IMessage) iter.next());
  498. MessageUtil.fail(handler, "unexpected " + label + ": " + s);
  499. }
  500. }
  501. }
  502. /** @return "{label}: (unexpected={#}, missing={#})" */
  503. public String toString() {
  504. return label
  505. + "(unexpected="
  506. + unexpected.size()
  507. + ", missing="
  508. + missing.size()
  509. + ")";
  510. }
  511. public static interface Filter {
  512. /** @return true to keep input in list of messages */
  513. boolean accept(Object input);
  514. }
  515. }