/*
* $Id$
*
* Copyright (c) 1996, 2011, 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.finder;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import com.sun.javatest.TestDescription;
import com.sun.javatest.TestEnvironment;
import com.sun.javatest.TestFinder;
import com.sun.javatest.util.I18NResourceBundle;
import com.sun.javatest.util.StringArray;
/**
* A TestFinder that delegates to different test finders in different
* areas of the test suite, as described by a special "map" file.
*/
public class ChameleonTestFinder extends TestFinder {
/**
* Create an uninitialized ChameleonTestFinder.
*/
public ChameleonTestFinder() {
String ic = System.getProperty("javatest.chameleon.ignoreCase");
if (ic != null)
ignoreCase = ic.equals("true");
else {
String os = System.getProperty("os.name");
ignoreCase = os.startsWith("Windows");
}
exclude(excludeNames);
}
/**
* Exclude all files with a particular name from being scanned.
* This will typically be for directories like SCCS, Codemgr_wsdata, etc
* @param name The name of files to be excluded
*/
public void exclude(String name) {
excludeList.put(name, name);
}
/**
* Exclude all files with particular names from being scanned.
* This will typically be for directories like SCCS, Codemgr_wsdata, etc
* @param names The names of files to be excluded
*/
public void exclude(String[] names) {
for (int i = 0; i < names.length; i++) {
String name = names[i];
excludeList.put(name, name);
}
}
/**
* Check whether or not to ignore case when matching files against entries.
*
* By default, case will be ignored on Windows platforms.
* (System.getProperty("os.name").startsWith("Windows")
)
* This can be overridden by setting the following system property:
*
* -Djavatest.chameleon.ignoreCase=true|false
*
*
* This in turn can be overridden by using -ignoreCase or -dontIgnoreCase
* in the args to {@link #init init}.
*
* @return whether or not to ignore case when matching files against entries.
* @see #setIgnoreCase
*/
public boolean getIgnoreCase() {
return ignoreCase;
}
/**
* Set whether or not to ignore case when matching files against entries.
*
* @param b whether or not to ignore case when matching files against entries.
* @see #getIgnoreCase
*/
public void setIgnoreCase(boolean b) {
ignoreCase = b;
}
/**
* Generic initialization routine. You can also initialize the test finder
* directly, with {@link #exclude}, {@link #readEntries}, etc.
* @param args An array of strings giving initialization data.
* The primary option is "-f file" to specify the name
* of the file describing which test finder to use in which section
* of the test suite.
* @param testSuiteRoot The root file of the test suite.
* @param env This argument is not required by this test finder.
* @throws TestFinder.Fault if an error is found during initialization.
*/
public void init(String[] args, File testSuiteRoot, TestEnvironment env) throws Fault {
super.init(args, testSuiteRoot, env);
if (entryFile == null)
throw new Fault(i18n, "cham.noConfigFile");
if (!entryFile.isAbsolute())
entryFile = new File(getRootDir(), entryFile.getPath());
readEntries(entryFile);
}
/**
* Read the entries in a file which describe which test finder to use
* in which part of the test suite.
* The file is read line by line. If a line is empty or begins with
* a '#' character, the line is ignored. Otherwise the line is broken
* up as follows:
* directory-or-file finder-class-name finder-args ...
* Then, when a file is to be read by the test finder, the entries above are
* checked in the order they were read for an entry whose first word matches
* the beginning of the name of the file to be read. If and when a match
* is found, the test finder delegates the request to the test finder specified
* in the rest of the entry that provided the match.
* @param file The file containing the entries to be read.
* @throws TestFinder.Fault if a problem occurs while reading the file.
*/
public void readEntries(File file) throws Fault {
//System.err.println("reading " + file);
SortedSet s = new TreeSet<>(new Comparator() {
public int compare(Entry o1, Entry o2) {
int n = o1.compareTo(o2);
// this gives us the reverse of the order we want, so ...
return -n;
}
});
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
String line;
int lineNum = 0;
while ((line = in.readLine()) != null) {
line = line.trim();
lineNum++;
if (line.startsWith("#") || line.length() == 0)
continue;
String[] words = StringArray.split(line);
if (words.length < 2) {
throw new Fault(i18n, "cham.missingData",
new Object[] {new Integer(lineNum), line});
}
String pattern = words[0];
String finderClassName = words[1];
String[] finderArgs = new String[words.length - 2];
System.arraycopy(words, 2, finderArgs, 0, finderArgs.length);
Entry e = new Entry(pattern, finderClassName, finderArgs);
s.add(e);
}
}
catch (FileNotFoundException e) {
throw new Fault(i18n, "cham.cantFindFile", file);
}
catch (IOException e) {
throw new Fault(i18n, "cham.ioError",
new Object[] {file, e});
}
entryFile = file;
entries = s.toArray(new Entry[s.size()]);
//for (int i = 0; i < entries.length; i++)
// System.err.println(entries[i].prefix + " " + entries[i].suffix);
}
protected int decodeArg(String[] args, int i) throws Fault {
if (args[i].equals("-f") && i + 1 < args.length) {
entryFile = new File(args[i + 1]);
return 2;
}
else if (args[i].equalsIgnoreCase("-ignoreCase")) {
ignoreCase = true;
return 1;
}
else if (args[i].equalsIgnoreCase("-dontIgnoreCase")) {
ignoreCase = false;
return 1;
}
else
return super.decodeArg(args, i);
}
/**
* Scan a file, looking for test descriptions and other files that might
* need to be scanned. The implementation depends on the type of test
* finder.
* @param file The file to scan
*/
protected void scan(File file) {
//System.err.println("SCAN: " + file);
if (file.isDirectory())
scanDirectory(file);
else
scanFile(file);
}
public File[] getFiles() {
if (currEntry == null)
return super.getFiles();
else if (currEntry.finder == null)
return new File[0];
else
return currEntry.finder.getFiles();
}
public TestDescription[] getTests() {
if (currEntry == null)
return super.getTests();
else if (currEntry.finder == null)
return new TestDescription[0];
else
return currEntry.finder.getTests();
}
//-----internal routines----------------------------------------------------
/**
* Scan a directory, looking for more files to scan
* @param dir The directory to scan
*/
private void scanDirectory(File dir) {
// scan the contents of the directory, checking for
// subdirectories and other files that should be scanned
currEntry = null;
String[] names = dir.list();
for (int i = 0; i < names.length; i++) {
String name = names[i];
// if the file should be ignored, skip it
// This is typically for directories like SCCS etc
if (excludeList.containsKey(name))
continue;
foundFile(new File(dir, name));
}
}
private void scanFile(File file) {
// see if the file matches a registered test finder, and if so
// delegate to that
for (int i = 0; i < entries.length; i++) {
if (entries[i].matches(file)) {
currEntry = entries[i];
currEntry.scanFile(file);
return;
}
}
// no match found
currEntry = null;
}
private Object newInstance(Class> c) throws Fault {
try {
return c.newInstance();
}
catch (InstantiationException e) {
throw new Fault(i18n, "cham.cantCreateClass",
new Object[] {c.getName(), e});
}
catch (IllegalAccessException e) {
throw new Fault(i18n, "cham.cantAccessClass",
new Object[] {c.getName(), e});
}
}
private Class> loadClass(String className) throws Fault {
try {
if (loader == null)
return Class.forName(className);
else
return loader.loadClass(className);
}
catch (ClassNotFoundException e) {
throw new Fault(i18n, "cham.cantFindClass",
new Object[] {className, e});
}
catch (IllegalArgumentException e) {
throw new Fault(i18n, "cham.badClassName", className);
}
}
private TestEnvironment getEnv() {
return env;
}
private File entryFile;
private Entry[] entries;
private boolean ignoreCase;
private Entry currEntry;
private ClassLoader loader;
private Map excludeList = new HashMap<>();
private static final String[] excludeNames = {
"SCCS", "deleted_files"
};
private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(ChameleonTestFinder.class);
private class Entry {
Entry(String pattern, String finderClassName, String[] finderArgs) {
int star = pattern.indexOf('*');
if (star == -1) {
prefix = pattern;
suffix = null;
}
else {
prefix = pattern.substring(0, star);
suffix = pattern.substring(star+1);
}
prefix = new File(getRootDir(), prefix.replace('/', File.separatorChar)).getPath();
if (suffix != null)
suffix = suffix.replace('/', File.separatorChar);
this.finderClassName = finderClassName;
this.finderArgs = finderArgs;
//System.err.println("created entry: prefix: " + prefix);
//System.err.println("created entry: suffix: " + suffix);
//System.err.println("created entry: class: " + finderClassName);
//System.err.println("created entry: args: " + StringArray.join(finderArgs));
}
boolean matches(File file) {
//System.err.println("checking " + file);
//System.err.println(" prefix: " + prefix);
//System.err.println(" suffix: " + suffix);
String p = file.getPath();
int pLen = p.length();
int preLen = prefix.length();
// if file does not match the prefix, return false
if (!p.regionMatches(ignoreCase, 0, prefix, 0, preLen))
return false;
// if there is a suffix, and the suffix is too short or the
// file does not match the prefix, return false
if (suffix != null) {
int sufLen = suffix.length();
if (sufLen > pLen)
return false;
if (!p.regionMatches(ignoreCase, pLen - sufLen, suffix, 0, sufLen))
return false;
}
// if we matched the prefix and possible suffix, we're done
return true;
}
void scanFile(File file) {
if (!initialized)
init();
if (finder != null)
finder.read(file);
}
private void init() {
try {
if (!finderClassName.equals("-")) {
finder = (TestFinder)(newInstance(loadClass(finderClassName)));
finder.init(finderArgs, getRoot(), getEnv());
}
}
catch (Fault e) {
error(i18n, "cham.cantInitClass", e.getMessage());
}
finally {
initialized = true;
}
}
int compareTo(Entry other) {
Entry a = this;
Entry b = other;
int apl = a.prefix.length();
int bpl = b.prefix.length();
if (apl < bpl)
return -1;
else if (apl == bpl) {
int pc = a.prefix.compareTo(b.prefix);
if (pc != 0)
return pc;
else {
// prefixes are the same, check the suffixes
String as = a.suffix;
String bs = b.suffix;
if (as == null && bs == null)
return 0;
if (as == null)
return -1;
if (bs == null)
return +1;
return as.compareTo(bs);
}
}
else
return +1;
}
private String prefix;
private String suffix;
private String finderClassName;
private String[] finderArgs;
private TestFinder finder;
private boolean initialized;
}
}