1 /* 2 * $Id$ 3 * 4 * Copyright (c) 2001, 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 java.io.*; 30 import java.nio.charset.StandardCharsets; 31 import java.util.*; 32 33 import com.sun.javatest.util.DynamicArray; 34 import com.sun.javatest.util.I18NResourceBundle; 35 36 /** 37 * A set of tests to be excluded from a test run. 38 */ 39 40 public class ExcludeList 41 { 42 /** 43 * This exception is used to report problems manipulating an exclude list. 44 */ 45 public static class Fault extends Exception 46 { 47 Fault(I18NResourceBundle i18n, String s, Object o) { 48 super(i18n.getString(s, o)); 49 } 50 } 51 52 /** 53 * Test if a file appears to be for an exclude list, by checking the extension. 54 * @param f The file to be tested. 55 * @return <code>true</code> if the file appears to be an exclude list. 56 */ 57 public static boolean isExcludeFile(File f) { 58 return f.getPath().endsWith(EXCLUDEFILE_EXTN); 59 } 60 61 /** 62 * Create a new exclude list. 63 */ 64 public ExcludeList() { 65 } 66 67 /** 68 * Create an ExcludeList from the data contained in a file. 69 * @param f The file to be read. 70 * @throws FileNotFoundException if the file cannot be found 71 * @throws IOException if any problems occur while reading the file 72 * @throws ExcludeList.Fault if the data in the file is ionconsistent 73 * @see #ExcludeList(File[]) 74 */ 75 public ExcludeList(File f) 76 throws FileNotFoundException, IOException, Fault 77 { 78 this(f, false); 79 } 80 81 /** 82 * Create an ExcludeList from the data contained in a file. 83 * @param f The file to be read. 84 * @param strict Indicate if strict data checking rules should be used. 85 * @throws FileNotFoundException if the file cannot be found 86 * @throws IOException if any problems occur while reading the file 87 * @throws ExcludeList.Fault if the data in the file is inconsistent 88 * @see #ExcludeList(File[]) 89 * @see #setStrictModeEnabled(boolean) 90 */ 91 public ExcludeList(File f, boolean strict) 92 throws FileNotFoundException, IOException, Fault 93 { 94 setStrictModeEnabled(strict); 95 if (f != null) { 96 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8))) { 97 Parser p = new Parser(in); 98 Entry e; 99 while ((e = p.readEntry()) != null) 100 addEntry(e); 101 102 title = p.getTitle(); 103 } 104 } 105 } 106 107 108 /** 109 * Create an ExcludeList from the data contained in a series of files. 110 * @param files The file to be read. 111 * @throws FileNotFoundException if any of the files cannot be found 112 * @throws IOException if any problems occur while reading the files. 113 * @throws ExcludeList.Fault if the data in the files is inconsistent 114 * @see #ExcludeList(File) 115 */ 116 public ExcludeList(File[] files) 117 throws FileNotFoundException, IOException, Fault 118 { 119 this(files, false); 120 } 121 122 /** 123 * Create an ExcludeList from the data contained in a series of files. 124 * @param files The file to be read. 125 * @param strict Indicate if strict data checking rules should be used. 126 * @throws FileNotFoundException if any of the files cannot be found 127 * @throws IOException if any problems occur while reading the files. 128 * @throws ExcludeList.Fault if the data in the files is inconsistent 129 * @see #ExcludeList(File) 130 * @see #setStrictModeEnabled(boolean) 131 */ 132 public ExcludeList(File[] files, boolean strict) 133 throws FileNotFoundException, IOException, Fault 134 { 135 setStrictModeEnabled(strict); 136 for (File file : files) { 137 ExcludeList et = new ExcludeList(file, strict); 138 merge(et); 139 } 140 } 141 142 /** 143 * Specify whether strict mode is on or not. In strict mode, calls to addEntry 144 * may generate an exception in the case of conflicts, such as adding an entry 145 * to exclude a specific test case when the entire test is already excluded. 146 * @param on true if strict mode should be enabled, and false otherwise 147 * @see #isStrictModeEnabled 148 */ 149 public void setStrictModeEnabled(boolean on) { 150 //System.err.println("EL.setStrictModeEnabled " + on); 151 strict = on; 152 } 153 154 /** 155 * Check whether strict mode is enabled or not. In strict mode, calls to addEntry 156 * may generate an exception in the case of conflicts, such as adding an entry 157 * to exclude a specific test case when the entire test is already excluded. 158 * @return true if strict mode is enabled, and false otherwise 159 * @see #setStrictModeEnabled 160 */ 161 public boolean isStrictModeEnabled() { 162 return strict; 163 } 164 165 /** 166 * Test if a specific test is completely excluded according to the table. 167 * It is completely excluded if there is an entry, and the test case field is null. 168 * @param td A test description for the test being checked. 169 * @return <code>true</code> if the table contains an entry for this test. 170 */ 171 public boolean excludesAllOf(TestDescription td) { 172 return excludesAllOf(td.getRootRelativeURL()); 173 } 174 175 /** 176 * Test if a specific test is completely excluded according to the table. 177 * It is completely excluded if there is an entry, and the test case field is null. 178 * @param url The test-suite root-relative URL for the test. 179 * @return <code>true</code> if the table contains an entry for this test. 180 */ 181 public boolean excludesAllOf(String url) { 182 Object o = table.get(new Key(url)); 183 return (o != null && o instanceof Entry && ((Entry)o).testCase == null); 184 } 185 186 /** 187 * Test if a specific test is partially or completely excluded according to the table. 188 * It is so excluded if there is any entry in the table for the test. 189 * @param td A test description for the test being checked. 190 * @return <code>true</code> if the table contains an entry for this test. 191 */ 192 public boolean excludesAnyOf(TestDescription td) { 193 return excludesAnyOf(td.getRootRelativeURL()); 194 } 195 196 /** 197 * Test if a specific test is partially or completely excluded according to the table. 198 * It is so excluded if there is any entry in the table for the test. 199 * @param url The test-suite root-relative URL for the test. 200 * @return <code>true</code> if the table contains an entry for this test. 201 */ 202 public boolean excludesAnyOf(String url) { 203 Object o = table.get(new Key(url)); 204 return (o != null); 205 } 206 207 /** 208 * Get the test cases to be excluded for a test. 209 * 210 * @param td A test description for the test being checked. 211 * @return an array of test case names if any test cases are to 212 * be excluded. The result is null if the test is not found or is 213 * completely excluded without specifying test cases. This may be 214 * a mix of single TC strings or a comma separated list of them. 215 */ 216 public String[] getTestCases(TestDescription td) { 217 Key key = new Key(td.getRootRelativeURL()); 218 synchronized (table) { 219 Object o = table.get(key); 220 if (o == null) 221 // not found 222 return null; 223 else if (o instanceof Entry) { 224 Entry e = (Entry)o; 225 if (e.testCase == null) 226 // entire test excluded 227 return null; 228 else 229 return (new String[] {e.testCase}); 230 } 231 else { 232 Entry[] ee = (Entry[])o; 233 String[] testCases = new String[ee.length]; 234 for (int i = 0; i < ee.length; i++) 235 testCases[i] = ee[i].testCase; 236 return testCases; 237 } 238 } 239 } 240 241 /** 242 * Add an entry to the table. 243 * @param e The entry to be added; if an entry already exists for this test 244 * description, it will be replaced. 245 * @throws ExcludeList.Fault if the entry is for the entire test and 246 * there is already an entry for a test case for this test, or vice versa. 247 */ 248 public void addEntry(Entry e) throws Fault { 249 synchronized (table) { 250 Key key = new Key(e.relativeURL); 251 Object o = table.get(key); 252 253 if (o == null) { 254 // easy case: nothing already exists in the table, so just 255 // add this one 256 table.put(key, e); 257 } 258 else if (o instanceof Entry) { 259 // a single entry exists in the table, so need to check for 260 // invalid combinations of test cases and tests 261 Entry curr = (Entry)o; 262 if (curr.testCase == null) { 263 if (e.testCase == null) 264 // overwrite existing entry for entire test 265 table.put(key, e); 266 else { 267 if (strict) { 268 // can't exclude test case when entire test already excluded 269 throw new Fault(i18n, "excl.cantExcludeCase", e.relativeURL); 270 } 271 // else ignore new entry since entire test is already excluded 272 } 273 } 274 else { 275 if (e.testCase == null) { 276 if (strict) { 277 // can't exclude entire test when test case already excluded 278 throw new Fault(i18n, "excl.cantExcludeTest", e.relativeURL); 279 } 280 else { 281 // overwrite existing entry for a test case with 282 // new entry for entire test 283 table.put(key, e); 284 } 285 } 286 else if (curr.testCase.equals(e.testCase)) { 287 // overwrite existing entry for the same test case 288 table.put(key, e); 289 } 290 else { 291 // already excluded one test case, now we need to exclude 292 // another; make an array to hold both entries against the 293 // one key 294 table.put(key, new Entry[] {curr, e}); 295 } 296 } 297 } 298 else { 299 // if there is an array, it must be for unique test cases 300 if (e.testCase == null) { 301 if (strict) { 302 // can't exclude entire test when selected test cases already excluded 303 throw new Fault(i18n, "excl.cantExcludeTest", e.relativeURL); 304 } 305 else { 306 // overwrite existing entry for list of test cases with 307 // new entry for entire test 308 table.put(key, e); 309 } 310 } 311 else { 312 Entry[] curr = (Entry[])o; 313 for (int i = 0; i < curr.length; i++) { 314 if (curr[i].testCase.equals(e.testCase)) { 315 curr[i] = e; 316 return; 317 } 318 } 319 // must be a new test case, add it into the array 320 table.put(key, DynamicArray.append(curr, e)); 321 } 322 } 323 324 } 325 } 326 327 /** 328 * Locate an entry for a test. 329 * @param url The root relative URL for the test; the URL may include 330 * a test case if necessary included in square brackets after the URL proper. 331 * @return The entry for the test, or null if there is none. 332 */ 333 public Entry getEntry(String url) { 334 String testCase = null; 335 if (url.endsWith("]")) { 336 int i = url.lastIndexOf("["); 337 if (i != -1) { 338 testCase = url.substring(i+1, url.length()-1); 339 url = url.substring(0, i); 340 } 341 } 342 return getEntry(url, testCase); 343 } 344 345 /** 346 * Locate an entry for a test. 347 * 348 * @param url The root relative URL for the test. 349 * @param testCase An optional test case to be taken into account. This cannot 350 * be a comma separated list. A value of null will match any entry with the given 351 * url. 352 * @return The entry for the test, or null if the URL cannot be found. 353 */ 354 public Entry getEntry(String url, String testCase) { 355 // XXX what if multiple entries? 356 Key key = new Key(url); 357 Object o = table.get(key); 358 if (o == null) 359 return null; 360 else if (o instanceof Entry) { 361 Entry e = (Entry)o; 362 if (testCase == null) 363 return e; 364 else 365 return (isInList(e.testCase, testCase) ? e : null); 366 } 367 else { 368 Entry[] entries = (Entry[])o; 369 for (Entry e : entries) { 370 if (isInList(e.testCase, testCase)) 371 return e; 372 } 373 return null; 374 } 375 } 376 377 /** 378 * Merge the contents of another exclude list into this one. 379 * The individual entries are merged; The title of the exclude list 380 * being merged is ignored. 381 * @param other the exclude list to be merged with this one. 382 * 383 */ 384 public void merge(ExcludeList other) { 385 synchronized (table) { 386 for (Iterator<?> iter = other.getIterator(false); iter.hasNext(); ) { 387 Entry otherEntry = (Entry) (iter.next()); 388 Key key = new Key(otherEntry.relativeURL); 389 Object o = table.get(key); 390 if (o == null) { 391 // Easy case: nothing already exists in the table, so just 392 // add this one 393 table.put(key, otherEntry); 394 } 395 else if (o instanceof Entry) { 396 // A single entry exists in the table 397 Entry curr = (Entry)o; 398 if (curr.testCase == null || otherEntry.testCase == null) { 399 table.put(key, new Entry(curr.relativeURL, null, 400 mergeBugIds(curr.bugIdStrings, otherEntry.bugIdStrings), 401 mergePlatforms(curr.platforms, otherEntry.platforms), 402 mergeSynopsis(curr.synopsis, otherEntry.synopsis))); 403 } 404 else 405 table.put(key, new Entry[] {curr, otherEntry}); 406 } 407 else if (otherEntry.testCase == null) { 408 // An array of test cases exist in the table, but we're merging 409 // an entry for the complete test, so flatten down to a single entry 410 // for the whole test 411 String[] bugIdStrings = otherEntry.bugIdStrings; 412 String[] platforms = otherEntry.platforms; 413 String synopsis = otherEntry.synopsis; 414 for (Entry entry : (Entry[])o) { 415 bugIdStrings = mergeBugIds(bugIdStrings, entry.bugIdStrings); 416 platforms = mergePlatforms(platforms, entry.platforms); 417 synopsis = mergeSynopsis(synopsis, entry.synopsis); 418 } 419 table.put(key, new Entry(otherEntry.relativeURL, null, 420 bugIdStrings, platforms, synopsis)); 421 } 422 else { 423 // An array of test cases exist in the table, and we're merging 424 // an entry with another set of test cases. 425 // For now, concatenate the arrays. 426 // RFE: Replace Entry[] with Set and merge the sets. 427 table.put(key, DynamicArray.append((Entry[]) o, otherEntry)); 428 } 429 } 430 } 431 } 432 433 static String[] mergeBugIds(String[] a, String[] b) { 434 return merge(a, b); 435 } 436 437 static String[] mergePlatforms(String[] a, String[] b) { 438 return merge(a, b); 439 } 440 441 static String[] merge(String[] a, String[] b) { 442 SortedSet<String> s = new TreeSet<>(); 443 s.addAll(Arrays.asList(a)); 444 s.addAll(Arrays.asList(b)); 445 return s.toArray(new String[s.size()]); 446 } 447 448 static String mergeSynopsis(String a, String b) { 449 if (a == null || a.trim().length() == 0) 450 return b; 451 else if (b == null || b.trim().length() == 0) 452 return a; 453 else if (a.indexOf(b) != -1) 454 return a; 455 else if (b.indexOf(a) != -1) 456 return b; 457 else 458 return a + "; " + b; 459 } 460 461 462 /** 463 * Remove an entry from the table. 464 * @param e the entry to be removed 465 */ 466 public void removeEntry(Entry e) { 467 synchronized (table) { 468 Key key = new Key(e.relativeURL); 469 Object o = table.get(key); 470 if (o == null) 471 // no such entry 472 return; 473 else if (o instanceof Entry) { 474 if (o == e) 475 table.remove(key); 476 } 477 else { 478 Entry[] o2 = DynamicArray.remove((Entry[])o, e); 479 if (o2 == o) 480 // not found 481 return; 482 else { 483 if (o2.length == 1) 484 table.put(key, o2[0]); 485 else 486 table.put(key, o2); 487 } 488 } 489 } 490 } 491 492 /** 493 * Check whether an exclude list has any entries or not. 494 * @return true if this exclude list has no entries 495 * @see #size 496 */ 497 public boolean isEmpty() { 498 return table.isEmpty(); 499 } 500 501 /** 502 * Get the number of entries in the table. 503 * @return the number of entries in the table 504 * @see #isEmpty 505 */ 506 public int size() { 507 // ouch, this is now expensive to compute 508 int n = 0; 509 for (Object o : table.values()) { 510 if (o instanceof Entry[]) 511 n += ((Entry[]) o).length; 512 else 513 n++; 514 } 515 return n; 516 } 517 518 /** 519 * Iterate over the contents of the table. 520 * @param group if <code>true</code>, entries for the same relative 521 * URL are grouped together, and if more than one, returned in an 522 * array; if <code>false</code>, the iterator always returns 523 * separate entries. 524 * @see Entry 525 * @return an iterator for the table: the entries are either 526 * single instances of @link(Entry) or a mixture of @link(Entry) 527 * and @link(Entry)[], depending on the <code>group</code> 528 * parameter. 529 */ 530 public Iterator<?> getIterator(boolean group) { 531 if (group) 532 return table.values().iterator(); 533 else { 534 // flatten the enumeration into a vector, then 535 // enumerate that 536 Vector<Object> v = new Vector<>(table.size()); 537 for (Object o : table.values()) { 538 if (o instanceof Entry) 539 v.addElement(o); 540 else { 541 for (Entry entry : (Entry[]) o) v.addElement(entry); 542 } 543 } 544 return v.iterator(); 545 } 546 547 } 548 549 /** 550 * Get the title for this exclude list. 551 * @return the title for this exclude list 552 * @see #setTitle 553 */ 554 public String getTitle() { 555 return title; 556 } 557 558 /** 559 * Set the title for this exclude list. 560 * @param title the title for this exclude list 561 * @see #getTitle 562 */ 563 public void setTitle(String title) { 564 this.title = title; 565 } 566 567 /** 568 * Write the table out to a file. 569 * @param f The file to which the table should be written. 570 * @throws IOException is thrown if any problems occur while the 571 * file is being written. 572 */ 573 public void write(File f) throws IOException { 574 // sort the entries for convenience, and measure col widths 575 int maxURLWidth = 0; 576 int maxBugIdWidth = 0; 577 int maxPlatformWidth = 0; 578 SortedSet<Entry> entries = new TreeSet<>(); 579 for (Iterator<?> iter = getIterator(false); iter.hasNext(); ) { 580 Entry entry = (Entry) (iter.next()); 581 entries.add(entry); 582 if (entry.testCase == null) 583 maxURLWidth = Math.max(entry.relativeURL.length(), maxURLWidth); 584 else 585 maxURLWidth = Math.max(entry.relativeURL.length() + entry.testCase.length() + 2, maxURLWidth); 586 maxBugIdWidth = Math.max(bugIdsToString(entry).length(), maxBugIdWidth); 587 maxPlatformWidth = Math.max(platformsToString(entry).length(), maxPlatformWidth); 588 } 589 590 BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8)); 591 out.write("# Exclude List"); 592 out.newLine(); 593 out.write("# SCCS %" + 'W' + "% %" + 'E' + "%"); // TAKE CARE WITH SCCS HEADERS 594 out.newLine(); 595 if (title != null) { 596 out.write("### title " + title); 597 out.newLine(); 598 } 599 for (Entry e : entries) { 600 if (e.testCase == null) 601 write(out, e.relativeURL, maxURLWidth + 2); 602 else 603 write(out, e.relativeURL + "[" + e.testCase + "]", maxURLWidth + 2); 604 write(out, bugIdsToString(e), maxBugIdWidth + 2); 605 write(out, platformsToString(e), maxPlatformWidth + 2); 606 out.write(e.synopsis); 607 out.newLine(); 608 } 609 out.close(); 610 } 611 612 private void write(Writer out, String s, int width) throws IOException { 613 out.write(s); 614 for (int i = s.length(); i < width; i++) 615 out.write(' '); 616 } 617 618 private String bugIdsToString(Entry e) { 619 StringBuffer sb = new StringBuffer(e.bugIdStrings.length*10); 620 sb.append(e.bugIdStrings[0]); 621 for (int i = 1; i < e.bugIdStrings.length; i++) { 622 sb.append(','); 623 sb.append(e.bugIdStrings[i]); 624 } 625 return sb.toString(); 626 } 627 628 private String platformsToString(Entry e) { 629 StringBuffer sb = new StringBuffer(e.platforms.length*10); 630 sb.append(e.platforms[0]); 631 for (int i = 1; i < e.platforms.length; i++) { 632 sb.append(','); 633 sb.append(e.platforms[i]); 634 } 635 return sb.toString(); 636 } 637 638 private static boolean equals(String s1, String s2) { 639 return (s1 == null && s2 == null 640 || s1 != null && s2 != null && s1.equals(s2)); 641 } 642 643 /** 644 * Is val in the comma separated list. 645 * 646 * @param list Comma separated list or a single value. 647 * @return Null if either parameter is null. 648 */ 649 private static boolean isInList(String list, String val) { 650 // check for invalid args 651 if (list == null || val == null) 652 return false; 653 654 // loop through possible matches 655 for (int pos = list.indexOf(val); pos != -1; pos = list.indexOf(val, pos + 1) ) { 656 // check beginning of string 657 if (!(pos == 0 || list.charAt(pos -1) == ',')) 658 continue; 659 660 // check end of string 661 if (!(pos + val.length() == list.length() || list.charAt(pos + val.length()) == ',')) 662 continue; 663 664 // beginning and end are OK; got a match 665 return true; 666 } 667 668 // no good matches found 669 return false; 670 } 671 672 /** 673 * @param obj - object to compare 674 * @return returns true if two entry tables are equal 675 */ 676 @Override 677 public boolean equals(Object obj) { 678 if (obj == null) { 679 return false; 680 } 681 if (getClass() != obj.getClass()) { 682 return false; 683 } 684 final ExcludeList other = (ExcludeList) obj; 685 if (this.table != other.table && (this.table == null || 686 !this.table.equals(other.table))) { 687 return false; 688 } 689 return true; 690 } 691 692 @Override 693 public int hashCode() { 694 int hash = 3; 695 hash = 71 * hash + (this.table != null ? this.table.hashCode() : 0); 696 return hash; 697 } 698 699 private Map<Key, Object> table = new HashMap<>(); 700 private String title; 701 private boolean strict; 702 703 private static final class Parser { 704 Parser(Reader in) throws IOException { 705 this.in = in; 706 ch = in.read(); 707 } 708 709 String getTitle() { 710 return title; 711 } 712 713 Entry readEntry() throws IOException, Fault { 714 String url = readURL(); // includes optional test case 715 if (url == null) 716 return null; 717 String testCase = null; // for now 718 719 if (url.endsWith("]")) { 720 int i = url.lastIndexOf("["); 721 if (i != -1) { 722 testCase = url.substring(i+1, url.length()-1); 723 url = url.substring(0, i); 724 } 725 } 726 String[] bugIdStrings = readBugIds(); 727 String[] platforms = readPlatforms(); 728 String synopsis = readRest(); 729 return new Entry(url, testCase, bugIdStrings, platforms, synopsis); 730 } 731 732 private boolean isEndOfLine(int ch) { 733 return (ch == -1 || ch == '\n' || ch == '\r'); 734 } 735 736 private boolean isWhitespace(int ch) { 737 return (ch == ' ' || ch == '\t'); 738 } 739 740 private String readURL() throws IOException, Fault { 741 // skip white space, comments and blank lines until a word is found 742 for (;;) { 743 skipWhite(); 744 switch (ch) { 745 case -1: 746 // end of file 747 return null; 748 case '#': 749 // comment 750 skipComment(); 751 break; 752 case '\r': 753 case '\n': 754 // blank line (or end of comment) 755 ch = in.read(); 756 break; 757 default: 758 return readWord(); 759 } 760 } 761 } 762 763 private String[] readBugIds() throws IOException { 764 // skip white space, then read and sort a list of comma-separated 765 // numbers with no embedded white-space 766 skipWhite(); 767 TreeSet<String> s = new TreeSet<>(); 768 StringBuffer sb = new StringBuffer(); 769 for ( ; !isEndOfLine(ch) && !isWhitespace(ch); ch = in.read()) { 770 if (ch == ',') { 771 if (sb.length() > 0) { 772 s.add(sb.toString()); 773 sb.setLength(0); 774 } 775 } 776 else 777 sb.append((char) ch); 778 } 779 780 if (sb.length() > 0) 781 s.add(sb.toString()); 782 783 if (s.size() == 0) 784 s.add("0"); // backwards compatibility 785 786 return s.toArray(new String[s.size()]); 787 } 788 789 private String[] readPlatforms() throws IOException { 790 // skip white space, then read and sort a list of comma-separated 791 // platform names with no embedded white space. Since the 792 // set of platforms (and their combinations) is small, 793 // share the result amongst all equivalent entries. 794 skipWhite(); 795 String s = readWord(); 796 String[] platforms = platformCache.get(s); 797 if (platforms == null) { 798 // split string into sorted comma separated pieces 799 int n = 0; 800 for (int i = 0; i < s.length(); i++) { 801 if (s.charAt(i) == ',') 802 n++; 803 } 804 Set<String> ts = new TreeSet<>(); 805 int start = 0; 806 int end = s.indexOf(','); 807 while (end != -1) { 808 ts.add(s.substring(start, end)); 809 start = end + 1; 810 end = s.indexOf(',', start); 811 } 812 ts.add(s.substring(start)); 813 platforms = ts.toArray(new String[ts.size()]); 814 platformCache.put(s, platforms); 815 } 816 return platforms; 817 } 818 819 private String readRest() throws IOException { 820 // skip white space, then read up to the end of the line 821 skipWhite(); 822 StringBuffer word = new StringBuffer(80); 823 for ( ; !isEndOfLine(ch); ch = in.read()) 824 word.append((char)ch); 825 // skip over terminating character 826 ch = in.read(); 827 return word.toString(); 828 } 829 830 private String readWord() throws IOException { 831 // read characters up to the next white space 832 StringBuffer word = new StringBuffer(32); 833 for ( ; !isEndOfLine(ch) && !isWhitespace(ch); ch = in.read()) 834 word.append((char)ch); 835 return word.toString(); 836 } 837 838 private void skipComment() throws IOException, Fault { 839 ch = in.read(); 840 // first # has already been read 841 if (ch == '#') { 842 ch = in.read(); 843 if (ch == '#') { 844 ch = in.read(); 845 skipWhite(); 846 String s = readWord(); 847 if (s.equals("title")) { 848 skipWhite(); 849 title = readRest(); 850 return; 851 } 852 } 853 } 854 while (!isEndOfLine(ch)) 855 ch = in.read(); 856 } 857 858 private void skipWhite() throws IOException { 859 // skip horizontal white space 860 // input is line-oriented, so do not skip over end of line 861 while (ch != -1 && isWhitespace(ch)) 862 ch = in.read(); 863 } 864 865 private Reader in; // source stream being read 866 private int ch; // current character 867 private Map<String, String[]> platformCache = new HashMap<>(); 868 // cache of results for readPlatforms 869 private String title; 870 }; 871 872 private static class Key { 873 Key(String url) { 874 relativeURL = url; 875 } 876 877 @Override 878 public int hashCode() { 879 // the hashCode for a key is the hashcode of the normalized URL. 880 // The normalized URL is url.replace(File.separatorChar, '/').toLowerCase(); 881 int h = hash; 882 if (h == 0) { 883 int len = relativeURL.length(); 884 885 for (int i = 0; i < len; i++) { 886 char c = relativeURL.charAt(i); 887 if (!caseSensitive) 888 c = Character.toLowerCase(c); 889 890 if (c == sep) 891 c = '/'; 892 h = 31*h + c; 893 } 894 hash = h; 895 } 896 return h; 897 } 898 899 @Override 900 public boolean equals(Object o) { 901 // Two keys are equal if their normalized URLs are equal. 902 // The normalized URL is url.replace(File.separatorChar, '/').toLowerCase(); 903 if (o == null || !(o instanceof Key)) 904 return false; 905 String u1 = relativeURL; 906 String u2 = ((Key) o).relativeURL; 907 int len = u1.length(); 908 if (len != u2.length()) 909 return false; 910 for (int i = 0; i < len; i++) { 911 char c1 = u1.charAt(i); 912 913 if (c1 == sep) 914 c1 = '/'; 915 else if(!caseSensitive) 916 c1 = Character.toLowerCase(c1); 917 918 char c2 = u2.charAt(i); 919 920 if (c2 == sep) 921 c2 = '/'; 922 else if(!caseSensitive) 923 c2 = Character.toLowerCase(c2); 924 925 if (c1 != c2) 926 return false; 927 } 928 return true; 929 } 930 931 private static final char sep = File.separatorChar; 932 private String relativeURL; 933 private int hash; 934 } 935 936 /** 937 * An entry in the exclude list. 938 */ 939 public static final class Entry implements Comparable<Entry> { 940 /** 941 * Create an ExcludeList entry. 942 * @param u The URL for the test, specified relative to the test suite root. 943 * @param tc One or more test cases within the test to be excluded. 944 * @param b An array of bug identifiers, justifying why the test is excluded. 945 * @param p An array of platform identifiers, on which the faults are 946 * known to occur 947 * @param s A short synopsis of the reasons why the test is excluded. 948 */ 949 public Entry(String u, String tc, String[] b, String[] p, String s) { 950 if (b == null || p == null) 951 throw new NullPointerException(); 952 953 // The file format cannot support platforms but no bugids, 954 // so fault that; other combinations (bugs, no platforms; 955 // no bugs, no platforms etc) are acceptable. 956 if (b.length == 0 && p.length > 0) 957 throw new IllegalArgumentException(); 958 959 relativeURL = u; 960 testCase = tc; 961 bugIdStrings = b; 962 platforms = p; 963 synopsis = s; 964 } 965 /** 966 * Create an ExcludeList entry. 967 * @param u The URL for the test, specified relative to the test suite root. 968 * @param tc One or more test cases within the test to be excluded. 969 * @param b An array of bug numbers, justifying why the test is excluded. 970 * @param p An array of platform identifiers, on which the faults are 971 * known to occur 972 * @param s A short synopsis of the reasons why the test is excluded. 973 * @deprecated use constructor with String[] bugIDs instead 974 */ 975 public Entry(String u, String tc, int[] b, String[] p, String s) { 976 if (b == null || p == null) 977 throw new NullPointerException(); 978 979 // The file format cannot support platforms but no bugids, 980 // so fault that; other combinations (bugs, no platforms; 981 // no bugs, no platforms etc) are acceptable. 982 if (b.length == 0 && p.length > 0) 983 throw new IllegalArgumentException(); 984 985 relativeURL = u; 986 testCase = tc; 987 988 bugIdStrings = new String[b.length]; 989 for (int i = 0; i < b.length; i++) 990 bugIdStrings[i] = String.valueOf(b[i]); 991 bugIds = b; 992 993 platforms = p; 994 synopsis = s; 995 } 996 997 public int compareTo(Entry e) { 998 int n = relativeURL.compareTo(e.relativeURL); 999 if (n == 0) { 1000 if (testCase == null && e.testCase == null) 1001 return 0; 1002 else if (testCase == null) 1003 return -1; 1004 else if (e.testCase == null) 1005 return +1; 1006 else 1007 return testCase.compareTo(e.testCase); 1008 } 1009 else 1010 return n; 1011 } 1012 1013 /** 1014 * Get the relative URL identifying the test referenced by this entry. 1015 * @return the relative URL identifying the test referenced by this entry 1016 */ 1017 public String getRelativeURL() { 1018 return relativeURL; 1019 } 1020 1021 /** 1022 * Get the (possibly empty) list of test cases for this entry. 1023 * An entry can have zero, one, or a comma separated list of TCs. 1024 * 1025 * @return List, or null if there are no test cases. 1026 */ 1027 public String getTestCases() { 1028 return testCase; 1029 } 1030 1031 /** 1032 * Get the same data as getTestCases(), but split into many Strings 1033 * This method is costly, so use with care. 1034 * 1035 * @return The parsed comma list, or null if there are no test cases. 1036 */ 1037 public String[] getTestCaseList() { 1038 // code borrowed from StringArray 1039 // it is a little wasteful to recalc everytime but saves space 1040 if (testCase == null) 1041 return null; 1042 1043 Vector<String> v = new Vector<String>(); 1044 int start = -1; 1045 for (int i = 0; i < testCase.length(); i++) { 1046 if (testCase.charAt(i) == ',') { 1047 if (start != -1) 1048 v.addElement(testCase.substring(start, i)); 1049 start = -1; 1050 } else 1051 if (start == -1) 1052 start = i; 1053 } 1054 if (start != -1) 1055 v.addElement(testCase.substring(start)); 1056 1057 if (v.size() == 0) 1058 return null; 1059 1060 String[] a = new String[v.size()]; 1061 v.copyInto(a); 1062 return a; 1063 } 1064 1065 /** 1066 * Get the set of bug IDs referenced by this entry. 1067 * @return the bugs referenced by the entry 1068 * @deprecated use getBugIdStrings() instead 1069 */ 1070 public int[] getBugIds() { 1071 if (bugIds == null) { 1072 bugIds = new int[bugIdStrings.length]; 1073 for (int i = 0; i < bugIds.length; i++) { 1074 try { 1075 1076 StringBuilder sb = new StringBuilder(); 1077 String str = bugIdStrings[i]; 1078 for (int j = 0; j < str.length(); j++) { 1079 if (Character.isDigit(str.charAt(j))) { 1080 sb.append(str.charAt(j)); 1081 } 1082 } 1083 1084 bugIds[i] = Integer.parseInt(sb.toString()); 1085 } catch (NumberFormatException e) { 1086 bugIds[i] = -1; 1087 } 1088 } 1089 } 1090 1091 return bugIds; 1092 } 1093 1094 /** 1095 * Get the set of bug IDs referenced by this entry. 1096 * @return the bugs referenced by the entry 1097 */ 1098 public String[] getBugIdStrings() { 1099 return bugIdStrings; 1100 } 1101 1102 /** 1103 * Get the set of platforms or keywords associated with this entry. 1104 * These should normally give details about why the test has been 1105 * excluded. 1106 * @return the set of platforms or keywords associated with this entry 1107 */ 1108 public String[] getPlatforms() { 1109 return platforms; 1110 } 1111 1112 /** 1113 * Get a short description associated with this entry. 1114 * This should normally give details about why the test has been 1115 * excluded. 1116 * @return a short description associated with this entry 1117 */ 1118 public String getSynopsis() { 1119 return synopsis; 1120 } 1121 1122 /** 1123 * Create an entry from a string. The string should be formatted 1124 * as though it were a line of text in an exclude file. 1125 * @param text The text to be read 1126 * @return the first entry read from the supplied text 1127 * @throws ExcludeList.Fault if there is a problem reading the entry. 1128 */ 1129 public static Entry read(String text) throws Fault { 1130 try { 1131 return new Parser(new StringReader(text)).readEntry(); 1132 } 1133 catch (IOException e) { 1134 throw new Fault(i18n, "excl.badEntry", e); 1135 } 1136 } 1137 1138 /** 1139 * Compare this entry against another. 1140 * @param o the object to compare against 1141 * @return true is the objects are bothe ExcludeList.Entries containing 1142 * the same details 1143 */ 1144 public boolean equals(Object o) { 1145 if (o instanceof Entry) { 1146 Entry e = (Entry)o; 1147 return equals(relativeURL, e.relativeURL) 1148 && equals(testCase, e.testCase) 1149 && equals(bugIdStrings, e.bugIdStrings) 1150 && equals(platforms, e.platforms) 1151 && equals(synopsis, e.synopsis); 1152 } 1153 else 1154 return false; 1155 } 1156 1157 @Override 1158 public int hashCode() { 1159 return relativeURL.hashCode(); 1160 } 1161 1162 @Override 1163 public String toString() { 1164 StringBuffer sb = new StringBuffer(64); 1165 sb.append(relativeURL); 1166 if (testCase != null) { 1167 sb.append('['); 1168 sb.append(testCase); 1169 sb.append(']'); 1170 } 1171 if (bugIdStrings != null) { 1172 for (int i = 0; i<bugIdStrings.length; i++) { 1173 sb.append(i == 0 ? ' ' : ','); 1174 sb.append(bugIdStrings[i]); 1175 } 1176 } 1177 if (platforms != null) { 1178 for (int i = 0; i<platforms.length; i++) { 1179 sb.append(i == 0 ? ' ' : ','); 1180 sb.append(platforms[i]); 1181 } 1182 } 1183 if (synopsis != null) { 1184 sb.append(' '); 1185 sb.append(synopsis); 1186 } 1187 return new String(sb); 1188 } 1189 1190 private static boolean equals(int[] i1, int[] i2) { 1191 if (i1 == null || i2 == null) 1192 return (i1 == null && i2 == null); 1193 1194 if (i1.length != i2.length) 1195 return false; 1196 1197 for (int x = 0; x < i1.length; x++) 1198 if (i1[x] != i2[x]) 1199 return false; 1200 1201 return true; 1202 } 1203 1204 private static boolean equals(String[] s1, String[] s2) { 1205 if (s1 == null || s2 == null) 1206 return (s1 == null && s2 == null); 1207 1208 if (s1.length != s2.length) 1209 return false; 1210 1211 for (int x = 0; x < s1.length; x++) { 1212 if (!equals(s1[x], s2[x])) 1213 return false; 1214 } 1215 1216 return true; 1217 } 1218 1219 private static boolean equals(String s1, String s2) { 1220 return (s1 == null && s2 == null 1221 || s1 != null && s2 != null && s1.equals(s2)); 1222 } 1223 1224 1225 private String relativeURL; 1226 private String testCase; 1227 private String[] bugIdStrings; 1228 private int[] bugIds; // null, unless required 1229 private String[] platforms; 1230 private String synopsis; 1231 } 1232 1233 private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(ExcludeList.class); 1234 /** 1235 * The standard extension for exclude-list files. (".jtx") 1236 */ 1237 public static final String EXCLUDEFILE_EXTN = ".jtx"; 1238 private static boolean caseSensitive = Boolean.getBoolean("javatest.caseSensitiveJtx"); 1239 }