1 /* 2 * $Id$ 3 * 4 * Copyright (c) 1996, 2011, 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.finder; 28 29 import java.io.*; 30 import java.nio.charset.StandardCharsets; 31 import java.util.*; 32 33 import com.sun.javatest.TestDescription; 34 import com.sun.javatest.TestEnvironment; 35 import com.sun.javatest.TestFinder; 36 import com.sun.javatest.util.I18NResourceBundle; 37 import com.sun.javatest.util.StringArray; 38 39 /** 40 * A TestFinder that delegates to different test finders in different 41 * areas of the test suite, as described by a special "map" file. 42 */ 43 public class ChameleonTestFinder extends TestFinder { 44 /** 45 * Create an uninitialized ChameleonTestFinder. 46 */ 47 public ChameleonTestFinder() { 48 String ic = System.getProperty("javatest.chameleon.ignoreCase"); 49 if (ic != null) 50 ignoreCase = ic.equals("true"); 51 else { 52 String os = System.getProperty("os.name"); 53 ignoreCase = os.startsWith("Windows"); 54 } 55 exclude(excludeNames); 56 } 57 58 /** 59 * Exclude all files with a particular name from being scanned. 60 * This will typically be for directories like SCCS, Codemgr_wsdata, etc 61 * @param name The name of files to be excluded 62 */ 63 public void exclude(String name) { 64 excludeList.put(name, name); 65 } 66 67 /** 68 * Exclude all files with particular names from being scanned. 69 * This will typically be for directories like SCCS, Codemgr_wsdata, etc 70 * @param names The names of files to be excluded 71 */ 72 public void exclude(String[] names) { 73 for (int i = 0; i < names.length; i++) { 74 String name = names[i]; 75 excludeList.put(name, name); 76 } 77 } 78 79 /** 80 * Check whether or not to ignore case when matching files against entries. 81 * 82 * By default, case will be ignored on Windows platforms. 83 * (<code><font size=-1>System.getProperty("os.name").startsWith("Windows")</font></code>) 84 * This can be overridden by setting the following system property: 85 * <pre> 86 * -Djavatest.chameleon.ignoreCase=true|false 87 * </pre> 88 * 89 * This in turn can be overridden by using -ignoreCase or -dontIgnoreCase 90 * in the args to {@link #init init}. 91 * 92 * @return whether or not to ignore case when matching files against entries. 93 * @see #setIgnoreCase 94 */ 95 public boolean getIgnoreCase() { 96 return ignoreCase; 97 } 98 99 /** 100 * Set whether or not to ignore case when matching files against entries. 101 * 102 * @param b whether or not to ignore case when matching files against entries. 103 * @see #getIgnoreCase 104 */ 105 public void setIgnoreCase(boolean b) { 106 ignoreCase = b; 107 } 108 /** 109 * Generic initialization routine. You can also initialize the test finder 110 * directly, with {@link #exclude}, {@link #readEntries}, etc. 111 * @param args An array of strings giving initialization data. 112 * The primary option is "-f <em>file</em>" to specify the name 113 * of the file describing which test finder to use in which section 114 * of the test suite. 115 * @param testSuiteRoot The root file of the test suite. 116 * @param env This argument is not required by this test finder. 117 * @throws TestFinder.Fault if an error is found during initialization. 118 */ 119 public void init(String[] args, File testSuiteRoot, TestEnvironment env) throws Fault { 120 super.init(args, testSuiteRoot, env); 121 122 if (entryFile == null) 123 throw new Fault(i18n, "cham.noConfigFile"); 124 125 if (!entryFile.isAbsolute()) 126 entryFile = new File(getRootDir(), entryFile.getPath()); 127 128 readEntries(entryFile); 129 } 130 131 /** 132 * Read the entries in a file which describe which test finder to use 133 * in which part of the test suite. 134 * The file is read line by line. If a line is empty or begins with 135 * a '#' character, the line is ignored. Otherwise the line is broken 136 * up as follows:<br> 137 * <em>directory-or-file</em> <em>finder-class-name</em> <em>finder-args</em> <em>...</em><br> 138 * Then, when a file is to be read by the test finder, the entries above are 139 * checked in the order they were read for an entry whose first word matches 140 * the beginning of the name of the file to be read. If and when a match 141 * is found, the test finder delegates the request to the test finder specified 142 * in the rest of the entry that provided the match. 143 * @param file The file containing the entries to be read. 144 * @throws TestFinder.Fault if a problem occurs while reading the file. 145 */ 146 public void readEntries(File file) throws Fault { 147 //System.err.println("reading " + file); 148 SortedSet<Entry> s = new TreeSet<>(new Comparator<Entry>() { 149 public int compare(Entry o1, Entry o2) { 150 int n = o1.compareTo(o2); 151 // this gives us the reverse of the order we want, so ... 152 return -n; 153 } 154 }); 155 156 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { 157 String line; 158 int lineNum = 0; 159 while ((line = in.readLine()) != null) { 160 line = line.trim(); 161 lineNum++; 162 if (line.startsWith("#") || line.length() == 0) 163 continue; 164 165 String[] words = StringArray.split(line); 166 if (words.length < 2) { 167 throw new Fault(i18n, "cham.missingData", 168 new Object[] {new Integer(lineNum), line}); 169 } 170 171 String pattern = words[0]; 172 String finderClassName = words[1]; 173 String[] finderArgs = new String[words.length - 2]; 174 System.arraycopy(words, 2, finderArgs, 0, finderArgs.length); 175 Entry e = new Entry(pattern, finderClassName, finderArgs); 176 s.add(e); 177 } 178 } 179 catch (FileNotFoundException e) { 180 throw new Fault(i18n, "cham.cantFindFile", file); 181 } 182 catch (IOException e) { 183 throw new Fault(i18n, "cham.ioError", 184 new Object[] {file, e}); 185 } 186 187 entryFile = file; 188 entries = s.toArray(new Entry[s.size()]); 189 190 //for (int i = 0; i < entries.length; i++) 191 // System.err.println(entries[i].prefix + " " + entries[i].suffix); 192 } 193 194 protected int decodeArg(String[] args, int i) throws Fault { 195 if (args[i].equals("-f") && i + 1 < args.length) { 196 entryFile = new File(args[i + 1]); 197 return 2; 198 } 199 else if (args[i].equalsIgnoreCase("-ignoreCase")) { 200 ignoreCase = true; 201 return 1; 202 } 203 else if (args[i].equalsIgnoreCase("-dontIgnoreCase")) { 204 ignoreCase = false; 205 return 1; 206 } 207 else 208 return super.decodeArg(args, i); 209 } 210 211 212 /** 213 * Scan a file, looking for test descriptions and other files that might 214 * need to be scanned. The implementation depends on the type of test 215 * finder. 216 * @param file The file to scan 217 */ 218 protected void scan(File file) { 219 //System.err.println("SCAN: " + file); 220 if (file.isDirectory()) 221 scanDirectory(file); 222 else 223 scanFile(file); 224 } 225 226 public File[] getFiles() { 227 if (currEntry == null) 228 return super.getFiles(); 229 else if (currEntry.finder == null) 230 return new File[0]; 231 else 232 return currEntry.finder.getFiles(); 233 } 234 235 public TestDescription[] getTests() { 236 if (currEntry == null) 237 return super.getTests(); 238 else if (currEntry.finder == null) 239 return new TestDescription[0]; 240 else 241 return currEntry.finder.getTests(); 242 } 243 244 245 //-----internal routines---------------------------------------------------- 246 247 /** 248 * Scan a directory, looking for more files to scan 249 * @param dir The directory to scan 250 */ 251 private void scanDirectory(File dir) { 252 // scan the contents of the directory, checking for 253 // subdirectories and other files that should be scanned 254 currEntry = null; 255 String[] names = dir.list(); 256 for (int i = 0; i < names.length; i++) { 257 String name = names[i]; 258 // if the file should be ignored, skip it 259 // This is typically for directories like SCCS etc 260 if (excludeList.containsKey(name)) 261 continue; 262 263 foundFile(new File(dir, name)); 264 } 265 } 266 267 private void scanFile(File file) { 268 // see if the file matches a registered test finder, and if so 269 // delegate to that 270 for (int i = 0; i < entries.length; i++) { 271 if (entries[i].matches(file)) { 272 currEntry = entries[i]; 273 currEntry.scanFile(file); 274 return; 275 } 276 } 277 // no match found 278 currEntry = null; 279 } 280 281 private Object newInstance(Class<?> c) throws Fault { 282 try { 283 return c.newInstance(); 284 } 285 catch (InstantiationException e) { 286 throw new Fault(i18n, "cham.cantCreateClass", 287 new Object[] {c.getName(), e}); 288 } 289 catch (IllegalAccessException e) { 290 throw new Fault(i18n, "cham.cantAccessClass", 291 new Object[] {c.getName(), e}); 292 } 293 } 294 295 private Class<?> loadClass(String className) throws Fault { 296 try { 297 if (loader == null) 298 return Class.forName(className); 299 else 300 return loader.loadClass(className); 301 } 302 catch (ClassNotFoundException e) { 303 throw new Fault(i18n, "cham.cantFindClass", 304 new Object[] {className, e}); 305 } 306 catch (IllegalArgumentException e) { 307 throw new Fault(i18n, "cham.badClassName", className); 308 } 309 310 } 311 312 private TestEnvironment getEnv() { 313 return env; 314 } 315 316 private File entryFile; 317 private Entry[] entries; 318 private boolean ignoreCase; 319 private Entry currEntry; 320 private ClassLoader loader; 321 private Map<String, String> excludeList = new HashMap<>(); 322 323 private static final String[] excludeNames = { 324 "SCCS", "deleted_files" 325 }; 326 327 private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(ChameleonTestFinder.class); 328 329 private class Entry { 330 Entry(String pattern, String finderClassName, String[] finderArgs) { 331 int star = pattern.indexOf('*'); 332 if (star == -1) { 333 prefix = pattern; 334 suffix = null; 335 } 336 else { 337 prefix = pattern.substring(0, star); 338 suffix = pattern.substring(star+1); 339 } 340 prefix = new File(getRootDir(), prefix.replace('/', File.separatorChar)).getPath(); 341 if (suffix != null) 342 suffix = suffix.replace('/', File.separatorChar); 343 344 this.finderClassName = finderClassName; 345 this.finderArgs = finderArgs; 346 347 //System.err.println("created entry: prefix: " + prefix); 348 //System.err.println("created entry: suffix: " + suffix); 349 //System.err.println("created entry: class: " + finderClassName); 350 //System.err.println("created entry: args: " + StringArray.join(finderArgs)); 351 } 352 353 boolean matches(File file) { 354 //System.err.println("checking " + file); 355 //System.err.println(" prefix: " + prefix); 356 //System.err.println(" suffix: " + suffix); 357 358 String p = file.getPath(); 359 int pLen = p.length(); 360 int preLen = prefix.length(); 361 362 // if file does not match the prefix, return false 363 if (!p.regionMatches(ignoreCase, 0, prefix, 0, preLen)) 364 return false; 365 366 // if there is a suffix, and the suffix is too short or the 367 // file does not match the prefix, return false 368 if (suffix != null) { 369 int sufLen = suffix.length(); 370 371 if (sufLen > pLen) 372 return false; 373 374 if (!p.regionMatches(ignoreCase, pLen - sufLen, suffix, 0, sufLen)) 375 return false; 376 } 377 378 // if we matched the prefix and possible suffix, we're done 379 return true; 380 } 381 382 void scanFile(File file) { 383 if (!initialized) 384 init(); 385 386 if (finder != null) 387 finder.read(file); 388 } 389 390 private void init() { 391 try { 392 if (!finderClassName.equals("-")) { 393 finder = (TestFinder)(newInstance(loadClass(finderClassName))); 394 finder.init(finderArgs, getRoot(), getEnv()); 395 } 396 } 397 catch (Fault e) { 398 error(i18n, "cham.cantInitClass", e.getMessage()); 399 } 400 finally { 401 initialized = true; 402 } 403 } 404 405 int compareTo(Entry other) { 406 Entry a = this; 407 Entry b = other; 408 int apl = a.prefix.length(); 409 int bpl = b.prefix.length(); 410 if (apl < bpl) 411 return -1; 412 else if (apl == bpl) { 413 int pc = a.prefix.compareTo(b.prefix); 414 if (pc != 0) 415 return pc; 416 else { 417 // prefixes are the same, check the suffixes 418 String as = a.suffix; 419 String bs = b.suffix; 420 if (as == null && bs == null) 421 return 0; 422 423 if (as == null) 424 return -1; 425 426 if (bs == null) 427 return +1; 428 429 return as.compareTo(bs); 430 } 431 } 432 else 433 return +1; 434 } 435 436 private String prefix; 437 private String suffix; 438 private String finderClassName; 439 private String[] finderArgs; 440 private TestFinder finder; 441 private boolean initialized; 442 } 443 }