1 /* 2 * $Id$ 3 * 4 * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * This code is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 only, as 9 * published by the Free Software Foundation. Oracle designates this 10 * particular file as subject to the "Classpath" exception as provided 11 * by Oracle in the LICENSE file that accompanied this code. 12 * 13 * This code is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 * version 2 for more details (a copy is included in the LICENSE file that 17 * accompanied this code). 18 * 19 * You should have received a copy of the GNU General Public License version 20 * 2 along with this work; if not, write to the Free Software Foundation, 21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 * 23 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 24 * or visit www.oracle.com if you need additional information or have any 25 * questions. 26 */ 27 package com.sun.javatest; 28 29 import com.sun.javatest.util.DynamicArray; 30 31 import java.io.*; 32 import java.nio.charset.StandardCharsets; 33 import java.util.*; 34 35 import com.sun.javatest.util.I18NResourceBundle; 36 37 /** 38 * Support class to read and process a list of tests and test cases which are 39 * known to fail during execution. The intent is to allow better post-run 40 * analysis of repetitive test runs, making is easier to find out what has 41 * "changed" since the list was made. This class is loosely based on the 42 * exclude list, making it easy to interchange the files and tools. 43 * 44 * File format: 45 * Test_URL[Test_Cases] BugID_List 46 * The test URL rules are defined elsewhere, but it is critical that the test 47 * names do not contain spaces and nothing before the BugID_List has any 48 * whitespace. The exact format of the BugID_List must simply conform to being 49 * comma separated values, no whitespace or non-printable characters. 50 * @since 4.4 51 */ 52 public class KnownFailuresList 53 { 54 55 public void addEntry(Entry e) throws Fault { 56 synchronized (table) { 57 Key key = new Key(e.relativeURL); 58 Object o = table.get(key); 59 if (o == null) { 60 // easy case: nothing already exists in the table, so just 61 // add this one 62 table.put(key, e); 63 } 64 else if (o instanceof Entry) { 65 // a single entry exists in the table, so need to check for 66 // invalid combinations of test cases and tests 67 Entry curr = (Entry)o; 68 if (curr.testCase == null) { 69 if (e.testCase == null) 70 // overwrite existing entry for entire test 71 table.put(key, e); 72 else { 73 if (strict) { 74 // can't record test case when entire test already listed 75 throw new Fault(i18n, "kfl.cantListCase", e.relativeURL); 76 } 77 // else ignore new entry since entire test is already listed 78 } 79 } 80 else { 81 if (e.testCase == null) { 82 if (strict) { 83 // can't record entire test when test case already listed 84 throw new Fault(i18n, "kfl.cantListTest", e.relativeURL); 85 } 86 else { 87 // overwrite existing entry for a test case with 88 // new entry for entire test 89 table.put(key, e); 90 } 91 } 92 else if (curr.testCase.equals(e.testCase)) { 93 // overwrite existing entry for the same test case 94 table.put(key, e); 95 } 96 else { 97 // already excluded one test case, now we need to exclude 98 // another; make an array to hold both entries against the 99 // one key 100 table.put(key, new Entry[] {curr, e}); 101 } 102 } 103 } 104 else { 105 // if there is an array, it must be for unique test cases 106 if (e.testCase == null) { 107 if (strict) { 108 // can't exclude entire test when selected test cases already excluded 109 throw new Fault(i18n, "kfl.cantListTest", e.relativeURL); 110 } 111 else { 112 // overwrite existing entry for list of test cases with 113 // new entry for entire test 114 table.put(key, e); 115 } 116 } 117 else { 118 Entry[] curr = (Entry[])o; 119 for (int i = 0; i < curr.length; i++) { 120 if (curr[i].testCase.equals(e.testCase)) { 121 curr[i] = e; 122 return; 123 } 124 } 125 // must be a new test case, add it into the array 126 table.put(key, DynamicArray.append(curr, e)); 127 } 128 } 129 130 } 131 } 132 133 /** 134 * This exception is used to report problems manipulating an exclude list. 135 */ 136 public static class Fault extends Exception 137 { 138 Fault(I18NResourceBundle i18n, String s, Object o) { 139 super(i18n.getString(s, o)); 140 } 141 } 142 143 /** 144 * Test if a file appears to be for an exclude list, by checking the extension. 145 * @param f The file to be tested. 146 * @return <code>true</code> if the file appears to be a known failures list. 147 */ 148 public static boolean isKflFile(File f) { 149 return f.getPath().endsWith(KFLFILE_EXTN); 150 } 151 152 /** 153 * Create a new, empty KFL object. 154 */ 155 public KnownFailuresList() { 156 } 157 158 /** 159 * Create an KnownFailuresList from the data contained in a file. 160 * @param f The file to be read. 161 * @throws FileNotFoundException if the file cannot be found 162 * @throws IOException if any problems occur while reading the file 163 * @throws KnownFailuresList.Fault if the data in the file is inconsistent 164 * @see #KnownFailuresList(File[]) 165 */ 166 public KnownFailuresList(File f) 167 throws FileNotFoundException, IOException, Fault 168 { 169 this(f, false); 170 } 171 172 /** 173 * Create an KnownFailuresList from the data contained in a file. 174 * @param f The file to be read. 175 * @param strict Indicate if strict data checking rules should be used. 176 * @throws FileNotFoundException if the file cannot be found 177 * @throws IOException if any problems occur while reading the file 178 * @throws KnownFailuresList.Fault if the data in the file is inconsistent 179 * @see #KnownFailuresList(File[]) 180 * @see #setStrictModeEnabled(boolean) 181 */ 182 public KnownFailuresList(File f, boolean strict) 183 throws FileNotFoundException, IOException, Fault 184 { 185 setStrictModeEnabled(strict); 186 if (f != null) { 187 BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8)); 188 189 Parser p = new Parser(in); 190 try { 191 Entry e; 192 while ((e = p.readEntry()) != null) 193 addEntry(e); 194 } 195 finally { 196 in.close(); 197 } 198 199 title = p.getTitle(); 200 } 201 } 202 203 204 /** 205 * Create a KnownFailuresList from the data contained in a series of files. 206 * @param files The file to be read. 207 * @throws FileNotFoundException if any of the files cannot be found 208 * @throws IOException if any problems occur while reading the files. 209 * @throws KnownFailuresList.Fault if the data in the files is inconsistent 210 * @see #KnownFailuresList(File) 211 */ 212 public KnownFailuresList(File[] files) 213 throws FileNotFoundException, IOException, Fault 214 { 215 this(files, false); 216 } 217 218 /** 219 * Create a KnownFailuresList from the data contained in a series of files. 220 * @param files The file to be read. 221 * @param strict Indicate if strict data checking rules should be used. 222 * @throws FileNotFoundException if any of the files cannot be found 223 * @throws IOException if any problems occur while reading the files. 224 * @throws KnownFailuresList.Fault if the data in the files is inconsistent 225 * @see #KnownFailuresList(File) 226 * @see #setStrictModeEnabled(boolean) 227 */ 228 public KnownFailuresList(File[] files, boolean strict) 229 throws FileNotFoundException, IOException, Fault 230 { 231 setStrictModeEnabled(strict); 232 for (int i = 0; i < files.length; i++) { 233 KnownFailuresList kfl = new KnownFailuresList(files[i], strict); 234 merge(kfl); 235 } 236 } 237 238 /** 239 * Specify whether strict mode is on or not. In strict mode, calls to addEntry 240 * may generate an exception in the case of conflicts, such as adding an entry 241 * to exclude a specific test case when the entire test is already excluded. 242 * @param on true if strict mode should be enabled, and false otherwise 243 * @see #isStrictModeEnabled 244 */ 245 public void setStrictModeEnabled(boolean on) { 246 //System.err.println("EL.setStrictModeEnabled " + on); 247 strict = on; 248 } 249 250 /** 251 * Check whether strict mode is enabled or not. In strict mode, calls to addEntry 252 * may generate an exception in the case of conflicts, such as adding an entry 253 * to exclude a specific test case when the entire test is already excluded. 254 * @return true if strict mode is enabled, and false otherwise 255 * @see #setStrictModeEnabled 256 */ 257 public boolean isStrictModeEnabled() { 258 return strict; 259 } 260 261 /** 262 * Iterate over the contents of the table. 263 * @param group if <code>true</code>, entries for the same relative 264 * URL are grouped together, and if more than one, returned in an 265 * array; if <code>false</code>, the iterator always returns 266 * separate entries. 267 * @see Entry 268 * @return an iterator for the table: the entries are either 269 * single instances of @link(Entry) or a mixture of @link(Entry) 270 * and @link(Entry)[], depending on the <code>group</code> 271 * parameter. 272 */ 273 public Iterator<Entry> getIterator(boolean group) { 274 if (group) 275 return table.values().iterator(); 276 else { 277 // flatten the enumeration into a vector, then 278 // enumerate that 279 List<Entry> v = new ArrayList<>(table.size()); 280 for (Iterator<Entry> iter = table.values().iterator(); iter.hasNext(); ) { 281 Object o = iter.next(); 282 if (o instanceof Entry) 283 v.add((Entry)o); 284 else { 285 Entry[] entries = (Entry[])o; 286 for (int i = 0; i < entries.length; i++) 287 v.add(entries[i]); 288 } 289 } 290 return v.iterator(); 291 } 292 293 } 294 295 296 /** 297 * Merge the contents of another exclude list into this one. 298 * The individual entries are merged; The title of the exclude list 299 * being merged is ignored. 300 * @param other the exclude list to be merged with this one. 301 * 302 */ 303 public void merge(KnownFailuresList other) { 304 synchronized (table) { 305 for (Iterator iter = other.getIterator(false); iter.hasNext(); ) { 306 Entry otherEntry = (Entry) (iter.next()); 307 Key key = new Key(otherEntry.relativeURL); 308 Object o = table.get(key); 309 if (o == null) { 310 // Easy case: nothing already exists in the table, so just 311 // add this one 312 table.put(key, otherEntry); 313 } 314 else if (o instanceof Entry) { 315 // A single entry exists in the table 316 Entry curr = (Entry)o; 317 if (curr.testCase == null || otherEntry.testCase == null) { 318 table.put(key, new Entry(curr.relativeURL, null, 319 ExcludeList.mergeBugIds(curr.bugIdStrings, otherEntry.bugIdStrings), 320 ExcludeList.mergeSynopsis(curr.notes, otherEntry.notes))); 321 } 322 else 323 table.put(key, new Entry[] {curr, otherEntry}); 324 } 325 else if (otherEntry.testCase == null) { 326 // An array of test cases exist in the table, but we're merging 327 // an entry for the complete test, so flatten down to a single entry 328 // for the whole test 329 String[] bugIdStrings = otherEntry.bugIdStrings; 330 String notes = otherEntry.notes; 331 Entry[] curr = (Entry[])o; 332 for (int i = 0; i < curr.length; i++) { 333 bugIdStrings = ExcludeList.mergeBugIds(bugIdStrings, curr[i].bugIdStrings); 334 notes = ExcludeList.mergeSynopsis(notes, curr[i].notes); 335 } 336 table.put(key, new Entry(otherEntry.relativeURL, null, 337 bugIdStrings, notes)); 338 } 339 else { 340 // An array of test cases exist in the table, and we're merging 341 // an entry with another set of test cases. 342 // For now, concatenate the arrays. 343 // RFE: Replace Entry[] with Set and merge the sets. 344 table.put(key, DynamicArray.append((Entry[]) o, otherEntry)); 345 } 346 } 347 } 348 } 349 350 public Entry[] find(String url) { 351 Object o = table.get(new Key(url)); 352 if (o == null) { 353 return null; 354 } 355 if (o instanceof Entry[]) { 356 return (Entry[])o; 357 } 358 else { 359 return new Entry[] {(Entry)o}; 360 } 361 } 362 363 public Entry find(String url, String tc) { 364 Entry[] entries = find(url); 365 366 if (entries == null || entries.length == 0) 367 return null; 368 369 for(Entry e: entries) { 370 if (e.containsTestCase(tc)) 371 return e; 372 } 373 374 return null; 375 } 376 377 /** 378 * Test if a specific test is completely excluded according to the table. 379 * It is completely excluded if there is an entry, and the test case field is null. 380 * @param td A test description for the test being checked. 381 * @return <code>true</code> if the table contains an entry for this test. 382 */ 383 public boolean listsAllOf(TestDescription td) { 384 return listsAllOf(td.getRootRelativeURL()); 385 } 386 387 /** 388 * Test if a specific test is completely excluded according to the table. 389 * It is completely excluded if there is an entry, and the test case field is null. 390 * @param url The test-suite root-relative URL for the test. 391 * @return <code>true</code> if the table contains an entry for this test. 392 */ 393 public boolean listsAllOf(String url) { 394 Object o = table.get(new Key(url)); 395 return (o != null && o instanceof Entry && ((Entry)o).testCase == null); 396 } 397 398 /** 399 * Test if a specific test is partially or completely excluded according to the table. 400 * It is so excluded if there is any entry in the table for the test. 401 * @param td A test description for the test being checked. 402 * @return <code>true</code> if the table contains an entry for this test. 403 */ 404 public boolean listsAnyOf(TestDescription td) { 405 return listsAnyOf(td.getRootRelativeURL()); 406 } 407 408 /** 409 * Test if a specific test is partially or completely excluded according to the table. 410 * It is so excluded if there is any entry in the table for the test. 411 * @param url The test-suite root-relative URL for the test. 412 * @return <code>true</code> if the table contains an entry for this test. 413 */ 414 public boolean listsAnyOf(String url) { 415 Object o = table.get(new Key(url)); 416 return (o != null); 417 } 418 419 /** 420 * Check whether an exclude list has any entries or not. 421 * @return true if this exclude list has no entries 422 * @see #size 423 */ 424 public boolean isEmpty() { 425 return table.isEmpty(); 426 } 427 428 /** 429 * Get the number of entries in the table. 430 * @return the number of entries in the table 431 * @see #isEmpty 432 */ 433 public int size() { 434 return 0; 435 } 436 437 /** 438 * Get the title for this exclude list. 439 * @return the title for this exclude list 440 * @see #setTitle 441 */ 442 public String getTitle() { 443 return title; 444 } 445 446 /** 447 * Set the title for this exclude list. 448 * @param title the title for this exclude list 449 * @see #getTitle 450 */ 451 public void setTitle(String title) { 452 this.title = title; 453 } 454 455 /** 456 * Write the table out to a file. 457 * @param f The file to which the table should be written. 458 * @throws IOException is thrown if any problems occur while the 459 * file is being written. 460 */ 461 public void write(File f) throws IOException { 462 BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8)); 463 out.write("### KFL/"); 464 out.write(KFL_FILE_VERSION); 465 out.newLine(); 466 out.write("### Known Failures List"); 467 out.newLine(); 468 if (title != null) { 469 out.write("### title " + title); 470 out.newLine(); 471 } 472 473 // write 474 475 out.close(); 476 } 477 478 private void write(Writer out, String s, int width) throws IOException { 479 out.write(s); 480 for (int i = s.length(); i < width; i++) 481 out.write(' '); 482 } 483 484 485 private static boolean equals(String s1, String s2) { 486 return (s1 == null && s2 == null 487 || s1 != null && s2 != null && s1.equals(s2)); 488 } 489 490 491 /** 492 * @param obj - object to compare 493 * @return returns true if two entry tables are equal 494 */ 495 @Override 496 public boolean equals(Object obj) { 497 if (obj == null) { 498 return false; 499 } 500 if (getClass() != obj.getClass()) { 501 return false; 502 } 503 final KnownFailuresList other = (KnownFailuresList) obj; 504 if (this.table != other.table && (this.table == null || 505 !this.table.equals(other.table))) { 506 return false; 507 } 508 return true; 509 } 510 511 @Override 512 public int hashCode() { 513 int hash = 3; 514 hash = 71 * hash + (this.table != null ? this.table.hashCode() : 0); 515 return hash; 516 } 517 518 // --------- Inner classes ----------- 519 private static final class Parser { 520 Parser(Reader in) throws IOException { 521 this.in = in; 522 ch = in.read(); 523 } 524 525 String getTitle() { 526 return title; 527 } 528 529 Entry readEntry() throws IOException, Fault { 530 String url = readURL(); // includes optional test case 531 if (url == null) 532 return null; 533 String testCase = null; // for now 534 if (url.endsWith("]")) { 535 int i = url.lastIndexOf("["); 536 if (i != -1) { 537 testCase = url.substring(i+1, url.length()-1); 538 url = url.substring(0, i); 539 } 540 } 541 String[] bugIdStrings = readBugIds(); 542 String note = readRest(); 543 return new Entry(url, testCase, bugIdStrings, note); 544 } 545 546 private boolean isEndOfLine(int ch) { 547 return (ch == -1 || ch == '\n' || ch == '\r'); 548 } 549 550 private boolean isWhitespace(int ch) { 551 return (ch == ' ' || ch == '\t'); 552 } 553 554 private String readURL() throws IOException, Fault { 555 // skip white space, comments and blank lines until a word is found 556 for (;;) { 557 skipWhite(); 558 switch (ch) { 559 case -1: 560 // end of file 561 return null; 562 case '#': 563 // comment 564 skipComment(); 565 break; 566 case '\r': 567 case '\n': 568 // blank line (or end of comment) 569 ch = in.read(); 570 break; 571 default: 572 return readWord(); 573 } 574 } 575 } 576 577 private String[] readBugIds() throws IOException { 578 // skip white space, then read and sort a list of comma-separated 579 // numbers with no embedded white-space 580 skipWhite(); 581 Set<String> s = new TreeSet<>(); 582 StringBuilder sb = new StringBuilder(); 583 for ( ; !isEndOfLine(ch) && !isWhitespace(ch); ch = in.read()) { 584 if (ch == ',') { 585 if (sb.length() > 0) { 586 s.add(sb.toString()); 587 sb.setLength(0); 588 } 589 } 590 else 591 sb.append((char) ch); 592 } 593 594 if (sb.length() > 0) 595 s.add(sb.toString()); 596 597 if (s.isEmpty()) 598 s.add("0"); // backwards compatibility 599 600 return s.toArray(new String[s.size()]); 601 } 602 603 private String readRest() throws IOException { 604 // skip white space, then read up to the end of the line 605 skipWhite(); 606 StringBuilder word = new StringBuilder(80); 607 for ( ; !isEndOfLine(ch); ch = in.read()) 608 word.append((char)ch); 609 // skip over terminating character 610 ch = in.read(); 611 return word.toString(); 612 } 613 614 private String readWord() throws IOException { 615 // read characters up to the next white space 616 StringBuilder word = new StringBuilder(32); 617 for ( ; !isEndOfLine(ch) && !isWhitespace(ch); ch = in.read()) 618 word.append((char)ch); 619 return word.toString(); 620 } 621 622 private void skipComment() throws IOException, Fault { 623 ch = in.read(); 624 // first # has already been read 625 if (ch == '#') { 626 ch = in.read(); 627 if (ch == '#') { 628 ch = in.read(); 629 skipWhite(); 630 String s = readWord(); 631 if (s.equals("title")) { 632 skipWhite(); 633 title = readRest(); 634 return; 635 } 636 } 637 } 638 while (!isEndOfLine(ch)) 639 ch = in.read(); 640 } 641 642 private void skipWhite() throws IOException { 643 // skip horizontal white space 644 // input is line-oriented, so do not skip over end of line 645 while (ch != -1 && isWhitespace(ch)) 646 ch = in.read(); 647 } 648 649 private Reader in; // source stream being read 650 private int ch; // current character 651 private String title; 652 }; 653 654 private static class Key { 655 Key(String url) { 656 relativeURL = url; 657 } 658 659 @Override 660 public int hashCode() { 661 // the hashCode for a key is the hashcode of the normalized URL. 662 // The normalized URL is url.replace(File.separatorChar, '/').toLowerCase(); 663 int h = hash; 664 if (h == 0) { 665 int len = relativeURL.length(); 666 667 for (int i = 0; i < len; i++) { 668 char c = Character.toLowerCase(relativeURL.charAt(i)); 669 if (c == sep) 670 c = '/'; 671 h = 31*h + c; 672 } 673 hash = h; 674 } 675 return h; 676 } 677 678 @Override 679 public boolean equals(Object o) { 680 // Two keys are equal if their normalized URLs are equal. 681 // The normalized URL is url.replace(File.separatorChar, '/').toLowerCase(); 682 if (o == null || !(o instanceof Key)) 683 return false; 684 String u1 = relativeURL; 685 String u2 = ((Key) o).relativeURL; 686 int len = u1.length(); 687 if (len != u2.length()) 688 return false; 689 for (int i = 0; i < len; i++) { 690 char c1 = Character.toLowerCase(u1.charAt(i)); 691 if (c1 == sep) 692 c1 = '/'; 693 char c2 = Character.toLowerCase(u2.charAt(i)); 694 if (c2 == sep) 695 c2 = '/'; 696 if (c1 != c2) 697 return false; 698 } 699 return true; 700 } 701 702 private static final char sep = File.separatorChar; 703 private String relativeURL; 704 private int hash; 705 } 706 707 /** 708 * An entry in the exclude list. 709 */ 710 public static final class Entry implements Comparable { 711 /** 712 * Create an ExcludeList entry. 713 * @param u The URL for the test, specified relative to the test suite root. 714 * @param tc One or more test cases within the test to be excluded. 715 * @param b An array of bug identifiers, justifying why the test is excluded. 716 717 * @param s A short synopsis of the reasons why the test is excluded. 718 */ 719 public Entry(String u, String tc, String[] b, String s) { 720 if (b == null) 721 throw new NullPointerException(); 722 723 // The file format cannot support platforms but no bugids, 724 // so fault that; other combinations (bugs, no platforms; 725 // no bugs, no platforms etc) are acceptable. 726 if (b.length == 0) 727 throw new IllegalArgumentException(); 728 729 relativeURL = u; 730 testCase = tc; 731 bugIdStrings = b; 732 notes = s; 733 } 734 735 public int compareTo(Object o) { 736 Entry e = (Entry) o; 737 int n = relativeURL.compareTo(e.relativeURL); 738 if (n == 0) { 739 if (testCase == null && e.testCase == null) 740 return 0; 741 else if (testCase == null) 742 return -1; 743 else if (e.testCase == null) 744 return +1; 745 else 746 return testCase.compareTo(e.testCase); 747 } 748 else 749 return n; 750 } 751 752 public boolean containsTestCase(String s) { 753 String[] tcs = getTestCaseList(); 754 755 if (tcs == null || tcs.length == 0) 756 return false; 757 758 for (int i = 0; i < tcs.length; i++) { 759 if (tcs[i].equals(s)) 760 return true; 761 } // for 762 763 return false; 764 } 765 766 /** 767 * Get the relative URL identifying the test referenced by this entry. 768 * @return the relative URL identifying the test referenced by this entry 769 */ 770 public String getRelativeURL() { 771 return relativeURL; 772 } 773 774 /** 775 * Get the (possibly empty) list of test cases for this entry. 776 * An entry can have zero, one, or a comma separated list of TCs. 777 * 778 * @return List, or null if there are no test cases. 779 */ 780 public String getTestCases() { 781 return testCase; 782 } 783 784 /** 785 * Get the same data as getTestCases(), but split into many Strings 786 * This method is costly, so use with care. 787 * 788 * @return The parsed comma list, or null if there are no test cases. 789 */ 790 public String[] getTestCaseList() { 791 // code borrowed from StringArray 792 // it is a little wasteful to recalc everytime but saves space 793 if (testCase == null) 794 return null; 795 796 List<String> v = new ArrayList<>(); 797 int start = -1; 798 for (int i = 0; i < testCase.length(); i++) { 799 if (testCase.charAt(i) == ',') { 800 if (start != -1) 801 v.add(testCase.substring(start, i)); 802 start = -1; 803 } else 804 if (start == -1) 805 start = i; 806 } 807 if (start != -1) 808 v.add(testCase.substring(start)); 809 810 if (v.isEmpty()) 811 return null; 812 813 String[] a = new String[v.size()]; 814 v.toArray(a); 815 return a; 816 } 817 818 /** 819 * Get the set of bug IDs referenced by this entry. 820 * @return the bugs referenced by the entry 821 */ 822 public String[] getBugIdStrings() { 823 return bugIdStrings; 824 } 825 826 /** 827 * Get a short description associated with this entry. 828 * This should normally give details about why the test has been 829 * excluded. 830 * @return a short description associated with this entry 831 */ 832 public String getNotes() { 833 return notes; 834 } 835 836 /** 837 * Create an entry from a string. The string should be formatted 838 * as though it were a line of text in an exclude file. 839 * @param text The text to be read 840 * @return the first entry read from the supplied text 841 * @throws ExcludeList.Fault if there is a problem reading the entry. 842 */ 843 public static Entry read(String text) throws Fault { 844 try { 845 return new Parser(new StringReader(text)).readEntry(); 846 } 847 catch (IOException e) { 848 throw new Fault(i18n, "kfl.badEntry", e); 849 } 850 } 851 852 /** 853 * Compare this entry against another. 854 * @param o the object to compare against 855 * @return true is the objects are bothe ExcludeList.Entries containing 856 * the same details 857 */ 858 @Override 859 public boolean equals(Object o) { 860 if (o instanceof Entry) { 861 Entry e = (Entry)o; 862 return equals(relativeURL, e.relativeURL) 863 && equals(testCase, e.testCase) 864 && equals(bugIdStrings, e.bugIdStrings) 865 && equals(notes, e.notes); 866 } 867 else 868 return false; 869 } 870 871 @Override 872 public int hashCode() { 873 return relativeURL.hashCode(); 874 } 875 876 @Override 877 public String toString() { 878 StringBuffer sb = new StringBuffer(64); 879 sb.append(relativeURL); 880 if (testCase != null) { 881 sb.append('['); 882 sb.append(testCase); 883 sb.append(']'); 884 } 885 if (bugIdStrings != null) { 886 for (int i = 0; i<bugIdStrings.length; i++) { 887 sb.append(i == 0 ? ' ' : ','); 888 sb.append(bugIdStrings[i]); 889 } 890 } 891 if (notes != null) { 892 sb.append(' '); 893 sb.append(notes); 894 } 895 return new String(sb); 896 } 897 898 private static boolean equals(int[] i1, int[] i2) { 899 if (i1 == null || i2 == null) 900 return (i1 == null && i2 == null); 901 902 if (i1.length != i2.length) 903 return false; 904 905 for (int x = 0; x < i1.length; x++) 906 if (i1[x] != i2[x]) 907 return false; 908 909 return true; 910 } 911 912 private static boolean equals(String[] s1, String[] s2) { 913 if (s1 == null || s2 == null) 914 return (s1 == null && s2 == null); 915 916 if (s1.length != s2.length) 917 return false; 918 919 for (int x = 0; x < s1.length; x++) { 920 if (!equals(s1[x], s2[x])) 921 return false; 922 } 923 924 return true; 925 } 926 927 private static boolean equals(String s1, String s2) { 928 return (s1 == null && s2 == null 929 || s1 != null && s2 != null && s1.equals(s2)); 930 } 931 932 933 private String relativeURL; 934 private String testCase; 935 private String[] bugIdStrings; 936 private int[] bugIds; // null, unless required 937 private String notes; 938 } 939 940 private Map table = new HashMap(); 941 private String title; 942 private boolean strict; 943 private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(KnownFailuresList.class); 944 945 /** 946 * The standard extension for KFL files. (".kfl") 947 */ 948 public static final String KFLFILE_EXTN = ".kfl"; 949 public static final String KFL_FILE_VERSION = "1.0"; 950 }