/*
* $Id$
*
* Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javatest;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import com.sun.javatest.util.DynamicArray;
import com.sun.javatest.util.I18NResourceBundle;
/**
* A set of tests to be excluded from a test run.
*/
public class ExcludeList
{
/**
* This exception is used to report problems manipulating an exclude list.
*/
public static class Fault extends Exception
{
Fault(I18NResourceBundle i18n, String s, Object o) {
super(i18n.getString(s, o));
}
}
/**
* Test if a file appears to be for an exclude list, by checking the extension.
* @param f The file to be tested.
* @return true if the file appears to be an exclude list.
*/
public static boolean isExcludeFile(File f) {
return f.getPath().endsWith(EXCLUDEFILE_EXTN);
}
/**
* Create a new exclude list.
*/
public ExcludeList() {
}
/**
* Create an ExcludeList from the data contained in a file.
* @param f The file to be read.
* @throws FileNotFoundException if the file cannot be found
* @throws IOException if any problems occur while reading the file
* @throws ExcludeList.Fault if the data in the file is ionconsistent
* @see #ExcludeList(File[])
*/
public ExcludeList(File f)
throws FileNotFoundException, IOException, Fault
{
this(f, false);
}
/**
* Create an ExcludeList from the data contained in a file.
* @param f The file to be read.
* @param strict Indicate if strict data checking rules should be used.
* @throws FileNotFoundException if the file cannot be found
* @throws IOException if any problems occur while reading the file
* @throws ExcludeList.Fault if the data in the file is inconsistent
* @see #ExcludeList(File[])
* @see #setStrictModeEnabled(boolean)
*/
public ExcludeList(File f, boolean strict)
throws FileNotFoundException, IOException, Fault
{
setStrictModeEnabled(strict);
if (f != null) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8))) {
Parser p = new Parser(in);
Entry e;
while ((e = p.readEntry()) != null)
addEntry(e);
title = p.getTitle();
}
}
}
/**
* Create an ExcludeList from the data contained in a series of files.
* @param files The file to be read.
* @throws FileNotFoundException if any of the files cannot be found
* @throws IOException if any problems occur while reading the files.
* @throws ExcludeList.Fault if the data in the files is inconsistent
* @see #ExcludeList(File)
*/
public ExcludeList(File[] files)
throws FileNotFoundException, IOException, Fault
{
this(files, false);
}
/**
* Create an ExcludeList from the data contained in a series of files.
* @param files The file to be read.
* @param strict Indicate if strict data checking rules should be used.
* @throws FileNotFoundException if any of the files cannot be found
* @throws IOException if any problems occur while reading the files.
* @throws ExcludeList.Fault if the data in the files is inconsistent
* @see #ExcludeList(File)
* @see #setStrictModeEnabled(boolean)
*/
public ExcludeList(File[] files, boolean strict)
throws FileNotFoundException, IOException, Fault
{
setStrictModeEnabled(strict);
for (File file : files) {
ExcludeList et = new ExcludeList(file, strict);
merge(et);
}
}
/**
* Specify whether strict mode is on or not. In strict mode, calls to addEntry
* may generate an exception in the case of conflicts, such as adding an entry
* to exclude a specific test case when the entire test is already excluded.
* @param on true if strict mode should be enabled, and false otherwise
* @see #isStrictModeEnabled
*/
public void setStrictModeEnabled(boolean on) {
//System.err.println("EL.setStrictModeEnabled " + on);
strict = on;
}
/**
* Check whether strict mode is enabled or not. In strict mode, calls to addEntry
* may generate an exception in the case of conflicts, such as adding an entry
* to exclude a specific test case when the entire test is already excluded.
* @return true if strict mode is enabled, and false otherwise
* @see #setStrictModeEnabled
*/
public boolean isStrictModeEnabled() {
return strict;
}
/**
* Test if a specific test is completely excluded according to the table.
* It is completely excluded if there is an entry, and the test case field is null.
* @param td A test description for the test being checked.
* @return true if the table contains an entry for this test.
*/
public boolean excludesAllOf(TestDescription td) {
return excludesAllOf(td.getRootRelativeURL());
}
/**
* Test if a specific test is completely excluded according to the table.
* It is completely excluded if there is an entry, and the test case field is null.
* @param url The test-suite root-relative URL for the test.
* @return true if the table contains an entry for this test.
*/
public boolean excludesAllOf(String url) {
Object o = table.get(new Key(url));
return (o != null && o instanceof Entry && ((Entry)o).testCase == null);
}
/**
* Test if a specific test is partially or completely excluded according to the table.
* It is so excluded if there is any entry in the table for the test.
* @param td A test description for the test being checked.
* @return true if the table contains an entry for this test.
*/
public boolean excludesAnyOf(TestDescription td) {
return excludesAnyOf(td.getRootRelativeURL());
}
/**
* Test if a specific test is partially or completely excluded according to the table.
* It is so excluded if there is any entry in the table for the test.
* @param url The test-suite root-relative URL for the test.
* @return true if the table contains an entry for this test.
*/
public boolean excludesAnyOf(String url) {
Object o = table.get(new Key(url));
return (o != null);
}
/**
* Get the test cases to be excluded for a test.
*
* @param td A test description for the test being checked.
* @return an array of test case names if any test cases are to
* be excluded. The result is null if the test is not found or is
* completely excluded without specifying test cases. This may be
* a mix of single TC strings or a comma separated list of them.
*/
public String[] getTestCases(TestDescription td) {
Key key = new Key(td.getRootRelativeURL());
synchronized (table) {
Object o = table.get(key);
if (o == null)
// not found
return null;
else if (o instanceof Entry) {
Entry e = (Entry)o;
if (e.testCase == null)
// entire test excluded
return null;
else
return (new String[] {e.testCase});
}
else {
Entry[] ee = (Entry[])o;
String[] testCases = new String[ee.length];
for (int i = 0; i < ee.length; i++)
testCases[i] = ee[i].testCase;
return testCases;
}
}
}
/**
* Add an entry to the table.
* @param e The entry to be added; if an entry already exists for this test
* description, it will be replaced.
* @throws ExcludeList.Fault if the entry is for the entire test and
* there is already an entry for a test case for this test, or vice versa.
*/
public void addEntry(Entry e) throws Fault {
synchronized (table) {
Key key = new Key(e.relativeURL);
Object o = table.get(key);
if (o == null) {
// easy case: nothing already exists in the table, so just
// add this one
table.put(key, e);
}
else if (o instanceof Entry) {
// a single entry exists in the table, so need to check for
// invalid combinations of test cases and tests
Entry curr = (Entry)o;
if (curr.testCase == null) {
if (e.testCase == null)
// overwrite existing entry for entire test
table.put(key, e);
else {
if (strict) {
// can't exclude test case when entire test already excluded
throw new Fault(i18n, "excl.cantExcludeCase", e.relativeURL);
}
// else ignore new entry since entire test is already excluded
}
}
else {
if (e.testCase == null) {
if (strict) {
// can't exclude entire test when test case already excluded
throw new Fault(i18n, "excl.cantExcludeTest", e.relativeURL);
}
else {
// overwrite existing entry for a test case with
// new entry for entire test
table.put(key, e);
}
}
else if (curr.testCase.equals(e.testCase)) {
// overwrite existing entry for the same test case
table.put(key, e);
}
else {
// already excluded one test case, now we need to exclude
// another; make an array to hold both entries against the
// one key
table.put(key, new Entry[] {curr, e});
}
}
}
else {
// if there is an array, it must be for unique test cases
if (e.testCase == null) {
if (strict) {
// can't exclude entire test when selected test cases already excluded
throw new Fault(i18n, "excl.cantExcludeTest", e.relativeURL);
}
else {
// overwrite existing entry for list of test cases with
// new entry for entire test
table.put(key, e);
}
}
else {
Entry[] curr = (Entry[])o;
for (int i = 0; i < curr.length; i++) {
if (curr[i].testCase.equals(e.testCase)) {
curr[i] = e;
return;
}
}
// must be a new test case, add it into the array
table.put(key, DynamicArray.append(curr, e));
}
}
}
}
/**
* Locate an entry for a test.
* @param url The root relative URL for the test; the URL may include
* a test case if necessary included in square brackets after the URL proper.
* @return The entry for the test, or null if there is none.
*/
public Entry getEntry(String url) {
String testCase = null;
if (url.endsWith("]")) {
int i = url.lastIndexOf("[");
if (i != -1) {
testCase = url.substring(i+1, url.length()-1);
url = url.substring(0, i);
}
}
return getEntry(url, testCase);
}
/**
* Locate an entry for a test.
*
* @param url The root relative URL for the test.
* @param testCase An optional test case to be taken into account. This cannot
* be a comma separated list. A value of null will match any entry with the given
* url.
* @return The entry for the test, or null if the URL cannot be found.
*/
public Entry getEntry(String url, String testCase) {
// XXX what if multiple entries?
Key key = new Key(url);
Object o = table.get(key);
if (o == null)
return null;
else if (o instanceof Entry) {
Entry e = (Entry)o;
if (testCase == null)
return e;
else
return (isInList(e.testCase, testCase) ? e : null);
}
else {
Entry[] entries = (Entry[])o;
for (Entry e : entries) {
if (isInList(e.testCase, testCase))
return e;
}
return null;
}
}
/**
* Merge the contents of another exclude list into this one.
* The individual entries are merged; The title of the exclude list
* being merged is ignored.
* @param other the exclude list to be merged with this one.
*
*/
public void merge(ExcludeList other) {
synchronized (table) {
for (Iterator> iter = other.getIterator(false); iter.hasNext(); ) {
Entry otherEntry = (Entry) (iter.next());
Key key = new Key(otherEntry.relativeURL);
Object o = table.get(key);
if (o == null) {
// Easy case: nothing already exists in the table, so just
// add this one
table.put(key, otherEntry);
}
else if (o instanceof Entry) {
// A single entry exists in the table
Entry curr = (Entry)o;
if (curr.testCase == null || otherEntry.testCase == null) {
table.put(key, new Entry(curr.relativeURL, null,
mergeBugIds(curr.bugIdStrings, otherEntry.bugIdStrings),
mergePlatforms(curr.platforms, otherEntry.platforms),
mergeSynopsis(curr.synopsis, otherEntry.synopsis)));
}
else
table.put(key, new Entry[] {curr, otherEntry});
}
else if (otherEntry.testCase == null) {
// An array of test cases exist in the table, but we're merging
// an entry for the complete test, so flatten down to a single entry
// for the whole test
String[] bugIdStrings = otherEntry.bugIdStrings;
String[] platforms = otherEntry.platforms;
String synopsis = otherEntry.synopsis;
for (Entry entry : (Entry[])o) {
bugIdStrings = mergeBugIds(bugIdStrings, entry.bugIdStrings);
platforms = mergePlatforms(platforms, entry.platforms);
synopsis = mergeSynopsis(synopsis, entry.synopsis);
}
table.put(key, new Entry(otherEntry.relativeURL, null,
bugIdStrings, platforms, synopsis));
}
else {
// An array of test cases exist in the table, and we're merging
// an entry with another set of test cases.
// For now, concatenate the arrays.
// RFE: Replace Entry[] with Set and merge the sets.
table.put(key, DynamicArray.append((Entry[]) o, otherEntry));
}
}
}
}
static String[] mergeBugIds(String[] a, String[] b) {
return merge(a, b);
}
static String[] mergePlatforms(String[] a, String[] b) {
return merge(a, b);
}
static String[] merge(String[] a, String[] b) {
SortedSet s = new TreeSet<>();
s.addAll(Arrays.asList(a));
s.addAll(Arrays.asList(b));
return s.toArray(new String[s.size()]);
}
static String mergeSynopsis(String a, String b) {
if (a == null || a.trim().length() == 0)
return b;
else if (b == null || b.trim().length() == 0)
return a;
else if (a.indexOf(b) != -1)
return a;
else if (b.indexOf(a) != -1)
return b;
else
return a + "; " + b;
}
/**
* Remove an entry from the table.
* @param e the entry to be removed
*/
public void removeEntry(Entry e) {
synchronized (table) {
Key key = new Key(e.relativeURL);
Object o = table.get(key);
if (o == null)
// no such entry
return;
else if (o instanceof Entry) {
if (o == e)
table.remove(key);
}
else {
Entry[] o2 = DynamicArray.remove((Entry[])o, e);
if (o2 == o)
// not found
return;
else {
if (o2.length == 1)
table.put(key, o2[0]);
else
table.put(key, o2);
}
}
}
}
/**
* Check whether an exclude list has any entries or not.
* @return true if this exclude list has no entries
* @see #size
*/
public boolean isEmpty() {
return table.isEmpty();
}
/**
* Get the number of entries in the table.
* @return the number of entries in the table
* @see #isEmpty
*/
public int size() {
// ouch, this is now expensive to compute
int n = 0;
for (Object o : table.values()) {
if (o instanceof Entry[])
n += ((Entry[]) o).length;
else
n++;
}
return n;
}
/**
* Iterate over the contents of the table.
* @param group if true, entries for the same relative
* URL are grouped together, and if more than one, returned in an
* array; if false, the iterator always returns
* separate entries.
* @see Entry
* @return an iterator for the table: the entries are either
* single instances of @link(Entry) or a mixture of @link(Entry)
* and @link(Entry)[], depending on the group
* parameter.
*/
public Iterator> getIterator(boolean group) {
if (group)
return table.values().iterator();
else {
// flatten the enumeration into a vector, then
// enumerate that
Vector