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; 28 29 import java.io.File; 30 import java.io.IOException; 31 import java.io.Serializable; 32 import java.util.Arrays; 33 import java.util.Iterator; 34 import java.util.Map; 35 import java.util.Set; 36 import java.util.TreeSet; 37 import java.util.Vector; 38 import com.sun.javatest.util.PropertyArray; 39 import com.sun.javatest.util.StringArray; 40 import java.net.MalformedURLException; 41 import java.net.URL; 42 import java.util.ArrayList; 43 44 /** 45 * TestDescription objects embody the parameters of a test and provide the 46 * ability to run a test. The parameters are normally found by parsing 47 * HTML files and looking for distinguished constructions whose parameters 48 * provide the necessary description of a test. 49 */ 50 51 public class TestDescription implements Serializable 52 { 53 /** 54 * Construct a test description from the parameters of a recognized descriptions. 55 * 56 * @param root The root file of the test suite 57 * @param file The file containing the test description 58 * @param params The collected parameters of the test description 59 * @throws IllegalArgumentException if the file argument is an absolute 60 * filename and does not begin with the root filename. 61 * 62 */ 63 public TestDescription(File root, File file, Map<?, ?> params) 64 throws IllegalArgumentException { 65 66 synchronized (this.getClass()) { 67 if (root.equals(cachedRoot)) 68 rootDir = cachedRootDir; 69 else { 70 if (root.exists() ? root.isFile() : root.getName().endsWith(".html")) 71 rootDir = root.getParent(); 72 else 73 rootDir = root.getPath(); 74 75 // cache root->rootDir map to avoid making extra files 76 cachedRoot = root; 77 cachedRootDir = rootDir; 78 } 79 } 80 81 String fp = file.getPath(); 82 String rootRelativeFile; 83 if (file.isAbsolute()) { 84 String rp = rootDir; 85 if (! (fp.startsWith(rp) && fp.charAt(rp.length()) == File.separatorChar)) 86 throw new IllegalArgumentException("file must be relative to root: " + file); 87 rootRelativeFile = fp.substring(rp.length() + 1); 88 } 89 else 90 rootRelativeFile = fp; 91 rootRelativePath = rootRelativeFile.replace(File.separatorChar, '/'); 92 93 Vector<String> v = new Vector<>(0, params.size() * 2); 94 for (Map.Entry<?, ?> entry : params.entrySet()) { 95 insert(v, (String) entry.getKey(), (String) entry.getValue()); 96 } 97 fields = new String[v.size()]; 98 v.copyInto(fields); 99 } 100 101 /** 102 * Internal constructor used by load() 103 */ 104 private TestDescription(String root, String file, String[] params) { 105 rootDir = root; 106 // skip over the root part of the filename. 107 char sep = file.charAt(root.length()); 108 rootRelativePath = file.substring(root.length() + 1).replace(sep, '/'); 109 110 Vector<String> v = new Vector<>(0, params.length); 111 for (int i = 0; i < params.length; i += 2) { 112 String key = params[i]; 113 if (!(key.startsWith("$") || key.equals("testsuite") || key.equals("file"))) { 114 // don't keep synthetic values from save; 115 String value = params[i+1]; 116 insert(v, key, value); 117 } 118 } 119 fields = new String[v.size()]; 120 v.copyInto(fields); 121 } 122 123 @Override 124 public int hashCode() { 125 int hash = 3; 126 hash = 43 * hash + Arrays.deepHashCode(this.fields); 127 return hash; 128 } 129 130 @Override 131 public boolean equals(Object td) { 132 if (!(td instanceof TestDescription)) 133 return false; 134 135 TestDescription otherTd = (TestDescription)td; 136 137 // a quick and simple check 138 if (otherTd.getParameterCount() != getParameterCount()) { 139 return false; 140 } 141 142 // raw compare 143 int pos = 0; 144 while (pos < fields.length) { 145 String otherVal = otherTd.getParameter(fields[pos]); 146 147 if (otherVal == null || 148 !otherVal.equals(fields[pos+1])) { 149 return false; 150 } 151 152 pos += 2; 153 } 154 155 return true; 156 } 157 158 /** 159 * Get the directory for this test description. 160 * @return the directory containing this test description 161 */ 162 public File getDir() { 163 return new File(getFile().getParent()); 164 } 165 166 /** 167 * Get the file for this test description. 168 * WARNING: If this description has been read in from a .jtr file, the rootDir 169 * may be inappropriate for this system. 170 * @return the file containing this test description 171 */ 172 public File getFile() { 173 return new File(rootDir, rootRelativePath.replace('/', File.separatorChar)); 174 } 175 176 /** 177 * Get the id within the file for this test description. 178 * @return the id within the file of this test description 179 */ 180 public String getId() { 181 return getParameter("id"); 182 } 183 184 /** 185 * Get the title of this test description. This title is determined from 186 * the "title" parameter, if present, defaulting to the value returned 187 * by <code>getName()</code>. 188 * @return the title of this test description 189 */ 190 public String getTitle() { 191 // default title to name 192 String title = getParameter("title"); 193 if (title == null) 194 title = getName(); 195 196 return title; 197 } 198 199 /** 200 * Get the name of this test description; if not given explicitly, 201 * it defaults to the filename root of the first source file. 202 * @return the name of this test description 203 */ 204 public String getName() { 205 206 // the name used to be a parameter you could set explicitly 207 // now, it is based on the file and id 208 int lastSep = rootRelativePath.lastIndexOf('/'); 209 String name = (lastSep == -1 ? 210 rootRelativePath : 211 rootRelativePath.substring(lastSep + 1)); 212 213 // strip off extension 214 int dot = name.indexOf('.'); 215 if (dot != -1) 216 name = name.substring(0, dot); 217 218 String id = getParameter("id"); 219 if (id != null) 220 name = name + "_" + id; 221 222 return name; 223 } 224 225 /** 226 * Get the set of keywords for this test description, 227 * as specified by the "keywords" parameter. 228 * @return the set of keywords 229 */ 230 public String[] getKeywords() { 231 return StringArray.split(getParameter("keywords")); 232 } 233 234 /** 235 * Get the set of keywords for this test description, 236 * as specified by the "keywords" parameter. 237 * They are returned in canonical form (lower-case). 238 * @return the set of keywords 239 */ 240 public Set<String> getKeywordTable() { 241 String[] keys = StringArray.split(getParameter("keywords")); 242 Set<String> s = new TreeSet<>(); 243 for (int i = 0; i < keys.length; i++) { 244 String k = keys[i].toLowerCase(); 245 s.add(k); 246 } 247 return s; 248 } 249 250 /** 251 * Get the set of source files for this test description, 252 * as specified by the "source" parameter. 253 * @return The sources as specified in the HTML test description 254 * @see #getSourceFiles 255 */ 256 public String[] getSources() { 257 return StringArray.split(getParameter("source")); 258 } 259 260 /** 261 * Get the set of source files for this test description, 262 * as specified by the "source" parameter. The files in the 263 * "source" parameter should normally be relative, in which 264 * case, they will be evaluated relative to the directory 265 * containing this test description. Then, if any of the 266 * files are under the user's current directory, they will 267 * be returned relative to that directory; otherwise, they 268 * will be returned as absolute filenames. 269 * @return filenames specified by the source parameter. 270 * @see #getSources 271 */ 272 public File[] getSourceFiles() { 273 String dir = getFile().getParent(); 274 String[] srcs = getSources(); 275 File[] sourceFiles = new File[srcs.length]; 276 String userCurrDir = System.getProperty("user.dir") + File.separator; 277 for (int i = 0; i < srcs.length; i++) { 278 File f = new File(dir, srcs[i].replace('/', File.separatorChar)); 279 280 // normalize name 281 try { 282 f = f.getCanonicalFile(); 283 } catch (IOException ex) { 284 ex.printStackTrace(); 285 } 286 287 String s = f.getPath(); 288 if (s.startsWith(userCurrDir)) { 289 s = s.substring(userCurrDir.length()); 290 sourceFiles[i] = new File(s); 291 } else 292 sourceFiles[i] = f; 293 } 294 return sourceFiles; 295 } 296 297 298 /** 299 * Get a list of associated files for a specified test description. 300 * Normally, this will include the file containing the test description, 301 * and any source files used by the test. By default, the source files 302 * are determined from the test description's "source" entry. 303 * @return a list of associated files for this test description 304 */ 305 public URL[] getSourceURLs() { 306 ArrayList<File> res = new ArrayList<File>(); 307 308 // always include the file containing the test description 309 res.add(getFile()); 310 // add in files given in source parameter 311 res.addAll(Arrays.asList(getSourceFiles())); 312 313 URL[] urls = new URL[res.size()]; 314 for (int i = 0; i < res.size(); i++) { 315 try { 316 urls[i] = res.get(i).toURI().toURL(); 317 } catch (MalformedURLException ex) { 318 ex.printStackTrace(); 319 urls[i] = null; 320 } 321 } 322 323 return urls; 324 } 325 326 327 /** 328 * Get the optional class directory for this test description, 329 * as specified by the "classDir" parameter. 330 * @return the class directory, or null if not specified 331 * @deprecated use <code>getParameter("classDir")</code> instead 332 */ 333 public String getClassDir() { 334 return getParameter("classDir"); 335 } 336 337 /** 338 * Get the execution class for this test description, 339 * as specified by the "executeClass" parameter. 340 * @return the execute class name, or null if not specified 341 * @deprecated use <code>getParameter("executeClass")</code> instead 342 */ 343 public String getExecuteClass() { 344 return getParameter("executeClass"); 345 } 346 347 /** 348 * Get the execution args for this test description, 349 * as specified by the "executeArgs" parameter. 350 * @return the execute args, or null if not specified 351 * @deprecated use <code>getParameter("executeArgs")</code> instead 352 */ 353 public String getExecuteArgs() { 354 return getParameter("executeArgs"); 355 } 356 357 /** 358 * Get the requested timeout value for this test description, 359 * as specified by the "timeout" parameter. 360 * @return the timeout value, or 0 if not specified 361 * @deprecated use <code>getParameter("timeout")</code> instead 362 */ 363 public int getTimeout() { 364 String t = getParameter("timeout"); 365 if (t == null) 366 return 0; 367 else 368 return Integer.parseInt(t); 369 } 370 371 /** 372 * Get the root file for this test suite; 373 * THIS IS PROVIDED FOR BACKWARDS COMPATIBILTY FOR JCK ONLY. 374 * It returns the name of testsuite.html within the root directory 375 * of the test suite. 376 * WARNING: If this description has been read in from a .jtr file, the rootDir 377 * may be inappropriate for this system. 378 * @return the root file for this test suite 379 * @see #getRootDir 380 * @deprecated No longer relevant for some test suites, so will not be supported 381 * in the future. If needed the value can be determined by asking the test 382 * suite's <code>TestFinder</code>. 383 */ 384 public File getRoot() { 385 return new File(rootDir, "testsuite.html"); 386 } 387 388 /** 389 * Get the root directory for this test suite 390 * WARNING: If this description has been read in from a .jtr file, the rootDir 391 * may be inappropriate for this system. 392 * @return the root directory for this test suite 393 */ 394 public String getRootDir() { 395 return rootDir; 396 } 397 398 /** 399 * Get the path of the test, relative to the root dir for the test suite. 400 * This is the path to the source file for this description. 401 * The internal separator is always '/'. 402 * @return the path for this test description within the test suite 403 */ 404 public String getRootRelativePath() { 405 return rootRelativePath; 406 } 407 408 /** 409 * Get the file of the test, relative to the root dir for the test suite. 410 * @return A platform specific path to the source file. 411 */ 412 public File getRootRelativeFile() { 413 return new File(rootRelativePath.replace('/', File.separatorChar)); 414 } 415 416 /** 417 * Get the url of the test, relative to the root dir for the test suite. 418 * This is the path to the source file for this description, plus the 419 * test id if necessary. Again, the path separator is always '/'. 420 * @return a relative URL for this test within the test suite 421 */ 422 public String getRootRelativeURL() { 423 if (rrurl == null) { 424 String id = getParameter("id"); 425 rrurl = (id == null ? rootRelativePath : rootRelativePath + "#" + id); 426 rrurl = rrurl.intern(); 427 } 428 429 return rrurl; 430 } 431 432 /** 433 * Get the path of the test directory, relative to the root directory for the test suite. 434 * @return the a relative path to the directory containing this test description 435 * 436 * @deprecated Use getRootRelativeFile().getParent() 437 */ 438 public File getRootRelativeDir() { 439 String p = getRootRelativeFile().getParent(); 440 return (p == null ? new File(".") : new File(p)); 441 } 442 443 /** 444 * Get the number of parameters contained in this test description. 445 * @return the number of parameters 446 */ 447 public int getParameterCount() { 448 return fields.length / 2; 449 } 450 451 /** 452 * Get an iterator for the names of the parameters contained in 453 * this test description. 454 * @return an iterator for the names of the parameters 455 */ 456 public Iterator<String> getParameterKeys() { 457 return new Iterator<String>() { 458 int pos = 0; 459 460 public boolean hasNext() { 461 if(fields == null || fields.length == 0 || 462 pos >= fields.length) { 463 return false; 464 } else { 465 return true; 466 } 467 } 468 469 public String next() { 470 if(fields == null || fields.length == 0 || 471 pos == fields.length) { 472 return null; 473 } else { 474 String current = fields[pos]; 475 pos += 2; 476 return current; 477 } 478 } 479 480 public void remove() { 481 throw new UnsupportedOperationException(); 482 } 483 }; 484 } 485 486 /** 487 * Get a parameter of the test description by name. 488 * @param key the name of the parameter value to be returned 489 * @return the value of the specified parameter, or null if not found 490 */ 491 public String getParameter(String key) { 492 int lower = 0; 493 int upper = fields.length - 2; 494 int mid; 495 496 if (upper < 0) 497 return null; 498 499 String last = fields[upper]; 500 int cmp = key.compareTo(last); 501 if (cmp > 0) 502 return null; 503 504 while (lower <= upper) { 505 // in next line, take care to ensure that mid is always even 506 mid = lower + ((upper - lower) / 4) * 2; 507 String e = fields[mid]; 508 cmp = key.compareTo(e); 509 if (cmp < 0) { 510 upper = mid - 2; 511 } 512 else if (cmp > 0) { 513 lower = mid + 2; 514 } 515 else 516 return fields[mid+1]; 517 } 518 519 // did not find an exact match 520 return null; 521 } 522 523 /** 524 * Simple standard debugging output. 525 */ 526 public String toString() { 527 return ("TestDescription[" + getTitle() + "]"); 528 } 529 530 /** 531 * Save TestDescription to a dictionary 532 * WARNING: If this description has been read in from a .jtr file, the rootDir 533 * may be inappropriate for this system. 534 */ 535 void save(Map<String, String> p) { 536 saveField(p, "$root", rootDir); 537 saveField(p, "$file", getFile().getPath()); 538 for (int i = 0; i < fields.length; i+=2) { 539 saveField(p, fields[i], fields[i+1]); 540 } 541 } 542 543 private void saveField(Map<String, String> p, String key, String value) { 544 if (value != null) 545 p.put(key, value); 546 } 547 548 /** 549 * Recover TestDescription from saved dictionary 550 */ 551 static TestDescription load(String[] params) { 552 //File r = new File((String)d.get("testsuite")); 553 //if (!r.isDirectory()) 554 // r = new File(r.getParent()); 555 //File f = new File((String)d.get("file")); 556 String r = PropertyArray.get(params, "$root"); 557 if (r == null) 558 r = PropertyArray.get(params, "testsuite"); 559 String f = PropertyArray.get(params, "$file"); 560 if (f == null) 561 f = PropertyArray.get(params, "file"); 562 return new TestDescription(r, f, params); 563 } 564 565 private static void insert(Vector<String> v, String key, String value) { 566 int lower = 0; 567 int upper = v.size() - 2; 568 int mid = 0; 569 570 if (upper < 0) { 571 v.addElement(key); 572 v.addElement(value); 573 return; 574 } 575 576 String last = v.elementAt(upper); 577 int cmp = key.compareTo(last); 578 if (cmp > 0) { 579 v.addElement(key); 580 v.addElement(value); 581 return; 582 } 583 584 while (lower <= upper) { 585 // in next line, take care to ensure that mid is always even 586 mid = lower + ((upper - lower) / 4) * 2; 587 String e = v.elementAt(mid); 588 cmp = key.compareTo(e); 589 if (cmp < 0) { 590 upper = mid - 2; 591 } 592 else if (cmp > 0) { 593 lower = mid + 2; 594 } 595 else 596 throw new Error("should not happen"); 597 } 598 599 // did not find an exact match (we did not expect to) 600 // adjust the insert point 601 if (cmp > 0) 602 mid += 2; 603 604 v.insertElementAt(key, mid); 605 v.insertElementAt(value, mid+1); 606 } 607 608 //-----member variables------------------------------------------------------- 609 610 /** 611 * Root directory for the test suite 612 * WARNING: If this description has been read in from a .jtr file, the rootDir 613 * may be inappropriate for this system. 614 * @serial 615 */ 616 private String rootDir; 617 618 /** 619 * Root relative path for this test description within the test suite 620 * @serial 621 */ 622 private String rootRelativePath; 623 624 /** 625 * The data for this test description, organized as a sequence of 626 * name-value pairs. 627 * @serial 628 */ 629 private String[] fields; 630 631 /** 632 * Cached version of the root relative path. 633 * @see #getRootRelativeURL 634 */ 635 private String rrurl; 636 637 private static File cachedRoot; 638 private static String cachedRootDir; 639 } 640