1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 /* 19 * $Id$ 20 */ 21 22 /* 23 * 24 * XMLFileLogger.java 25 * 26 */ 27 package org.apache.qetest; 28 29 import java.io.BufferedWriter; 30 import java.io.File; 31 import java.io.FileWriter; 32 import java.io.IOException; 33 import java.io.PrintWriter; 34 import java.io.StringWriter; 35 import java.util.Date; 36 import java.util.Enumeration; 37 import java.util.Hashtable; 38 import java.util.Properties; 39 40 /** 41 * Logger that saves output to a simple XML-format file. 42 * @todo improve escapeString so it's more rigorous about escaping 43 * @author Shane_Curcuru@lotus.com 44 * @version $Id$ 45 */ 46 public class XMLFileLogger implements Logger 47 { 48 49 //----------------------------------------------------- 50 //-------- Constants for results file structure -------- 51 //----------------------------------------------------- 52 53 /** XML root element tag: root of our output document. */ 54 public static final String ELEM_RESULTSFILE = "resultsfile"; 55 56 /** XML element tag: testFileInit() parent element. */ 57 public static final String ELEM_TESTFILE = "testfile"; 58 59 /** XML element tag: testFileClose() leaf element 60 * emitted immediately before closing ELEM_TESTFILE. 61 */ 62 public static final String ELEM_FILERESULT = "fileresult"; 63 64 /** XML element tag: testCaseInit() parent element. */ 65 public static final String ELEM_TESTCASE = "testcase"; 66 67 /** XML element tag: testCaseClose() leaf element 68 * emitted immediately before closing ELEM_TESTCASE. 69 */ 70 public static final String ELEM_CASERESULT = "caseresult"; 71 72 /** XML element tag: check*() element. */ 73 public static final String ELEM_CHECKRESULT = "checkresult"; 74 75 /** XML element tag: logStatistic() element. */ 76 public static final String ELEM_STATISTIC = "statistic"; 77 78 /** XML element tag: logStatistic() child element. */ 79 public static final String ELEM_LONGVAL = "longval"; 80 81 /** XML element tag: logStatistic() child element. */ 82 public static final String ELEM_DOUBLEVAL = "doubleval"; 83 84 /** XML element tag: log*Msg() element. */ 85 public static final String ELEM_MESSAGE = "message"; 86 87 /** XML element tag: logArbitrary() element. */ 88 public static final String ELEM_ARBITRARY = "arbitrary"; 89 90 /** XML element tag: logHashtable() parent element. */ 91 public static final String ELEM_HASHTABLE = "hashtable"; 92 93 /** XML element tag: logHashtable() child element. */ 94 public static final String ELEM_HASHITEM = "hashitem"; 95 96 /** XML attribute tag: log*Msg(), etc. attribute denoting 97 * loggingLevel for that element. 98 */ 99 public static final String ATTR_LEVEL = "level"; 100 101 /** XML attribute tag: comment value for various methods. */ 102 public static final String ATTR_DESC = "desc"; 103 104 /** XML attribute tag: Date.toString() attribute on 105 * ELEM_TESTFILE and ELEM_FILERESULT. */ 106 public static final String ATTR_TIME = "time"; 107 108 /** XML attribute tag: PASS|FAIL|etc. attribute on 109 * ELEM_CHECKRESULT, ELEM_CASERESULT, ELEM_FILERESULT. 110 */ 111 public static final String ATTR_RESULT = "result"; 112 113 /** XML attribute tag: key name for ELEM_HASHITEM. */ 114 public static final String ATTR_KEY = "key"; 115 116 /** XML attribute tag: actual output filename on ELEM_RESULTSFILE. */ 117 public static final String ATTR_FILENAME = OPT_LOGFILE; 118 119 /** XML attribute tag: test filename on ELEM_TESTFILE. */ 120 public static final String ATTR_TESTFILENAME = "filename"; 121 122 /** Parameter: if we should flush on every testCaseClose(). */ 123 public static final String OPT_FLUSHONCASECLOSE = "flushOnCaseClose"; 124 125 //----------------------------------------------------- 126 //-------- Class members and accessors -------- 127 //----------------------------------------------------- 128 129 /** If we're ready to start outputting yet. */ 130 protected boolean ready = false; 131 132 /** If an error has occoured in this Logger. */ 133 protected boolean error = false; 134 135 /** If we should flush on every testCaseClose(). */ 136 protected boolean flushOnCaseClose = true; 137 138 /** 139 * Accessor for flushing; is set from properties. 140 * 141 * @return current flushing status 142 */ getFlushOnCaseClose()143 public boolean getFlushOnCaseClose() 144 { 145 return (flushOnCaseClose); 146 } 147 148 /** 149 * Accessor for flushing; is set from properties. 150 * 151 * @param b If we should flush on every testCaseClose() 152 */ setFlushOnCaseClose(boolean b)153 public void setFlushOnCaseClose(boolean b) 154 { 155 flushOnCaseClose = b; 156 } 157 158 /** If we have output anything yet. */ 159 protected boolean anyOutput = false; 160 161 /** Name of the file we're outputing to. */ 162 protected String fileName = null; 163 164 /** File reference and other internal convenience variables. */ 165 protected File reportFile; 166 167 /** File reference and other internal convenience variables. */ 168 protected FileWriter reportWriter; 169 170 /** File reference and other internal convenience variables. */ 171 protected PrintWriter reportPrinter; 172 173 /** Generic properties for this logger; sort-of replaces instance variables. */ 174 protected Properties loggerProps = null; 175 176 //----------------------------------------------------- 177 //-------- Control and utility routines -------- 178 //----------------------------------------------------- 179 180 /** Simple constructor, does not perform initialization. */ XMLFileLogger()181 public XMLFileLogger() 182 { /* no-op */ 183 } 184 185 /** 186 * Constructor calls initialize(p). 187 * @param p Properties block to initialize us with. 188 */ XMLFileLogger(Properties p)189 public XMLFileLogger(Properties p) 190 { 191 ready = initialize(p); 192 } 193 194 /** 195 * Return a description of what this Logger does. 196 * @return "reports results in XML to specified fileName". 197 */ getDescription()198 public String getDescription() 199 { 200 return ("org.apache.qetest.XMLFileLogger - reports results in XML to specified fileName."); 201 } 202 203 /** 204 * Returns information about the Property name=value pairs 205 * that are understood by this Logger: fileName=filename. 206 * @return same as {@link java.applet.Applet.getParameterInfo}. 207 */ getParameterInfo()208 public String[][] getParameterInfo() 209 { 210 211 String pinfo[][] = 212 { 213 { OPT_LOGFILE, "String", 214 "Name of file to use for output; required" }, 215 { OPT_FLUSHONCASECLOSE, "boolean", 216 "if we should flush on every testCaseClose(); optional; default:true" } 217 }; 218 219 return pinfo; 220 } 221 222 /** 223 * Accessor methods for our properties block. 224 * 225 * @return our current properties; may be null 226 */ getProperties()227 public Properties getProperties() 228 { 229 return loggerProps; 230 } 231 232 /** 233 * Accessor methods for our properties block. 234 * @param p Properties to set (is cloned). 235 */ setProperties(Properties p)236 public void setProperties(Properties p) 237 { 238 239 if (p != null) 240 { 241 loggerProps = (Properties) p.clone(); 242 } 243 } 244 245 /** 246 * Initialize this XMLFileLogger. 247 * Must be called before attempting to log anything. 248 * Opens a FileWriter for our output, and logs Record format: 249 * <pre><resultfile fileName="<i>name of result file</i>"></pre> 250 * 251 * If no name provided, supplies a default one in current dir. 252 * 253 * @param p Properties block to initialize from 254 * @return true if we think we initialized OK 255 */ initialize(Properties p)256 public boolean initialize(Properties p) 257 { 258 259 setProperties(p); 260 261 fileName = loggerProps.getProperty(OPT_LOGFILE, fileName); 262 263 if ((fileName == null) || fileName.equals("")) 264 { 265 // Make up a file name 266 fileName = "XMLFileLogger-default-results.xml"; 267 loggerProps.put(OPT_LOGFILE, fileName); 268 } 269 270 // Create a file and ensure it has a place to live; be sure 271 // to insist on an absolute path for later parent path creation 272 reportFile = new File(fileName); 273 try 274 { 275 reportFile = new File(reportFile.getCanonicalPath()); 276 } 277 catch (IOException ioe1) 278 { 279 reportFile = new File(reportFile.getAbsolutePath()); 280 } 281 282 // Note: bare filenames may not have parents, so catch and ignore exceptions 283 try 284 { 285 File parent = new File(reportFile.getParent()); 286 if ((!parent.mkdirs()) && (!parent.exists())) 287 { 288 289 // Couldn't create or find the directory for the file to live in, so bail 290 error = true; 291 ready = false; 292 293 System.err.println( 294 "XMLFileLogger.initialize() WARNING: cannot create directories: " 295 + fileName); 296 297 // Don't return yet: see if the reportWriter can still create the file later 298 // return(false); 299 } 300 } 301 catch (Exception e) 302 { 303 304 // No-op: ignore if the parent's not there; trust that the file will get created later 305 } 306 307 try 308 { 309 reportWriter = new FileWriter(reportFile); 310 } 311 catch (IOException e) 312 { 313 System.err.println("XMLFileLogger.initialize() EXCEPTION: " 314 + e.toString()); 315 e.printStackTrace(); 316 317 error = true; 318 ready = false; 319 320 return false; 321 } 322 323 String tmp = loggerProps.getProperty(OPT_FLUSHONCASECLOSE); 324 if (null != tmp) 325 { 326 setFlushOnCaseClose((Boolean.valueOf(tmp)).booleanValue()); 327 } 328 329 // Use BufferedWriter for better general performance 330 reportPrinter = new PrintWriter(new BufferedWriter(reportWriter)); 331 ready = true; 332 333 return startResultsFile(); 334 } 335 336 /** 337 * Is this Logger ready to log results? 338 * @return status - true if it's ready to report, false otherwise 339 */ isReady()340 public boolean isReady() 341 { 342 343 // Ensure our underlying logger, if one, is still OK 344 if ((reportPrinter != null) && reportPrinter.checkError()) 345 { 346 347 // NEEDSWORK: should we set ready = false in this case? 348 // errors in the PrintStream are not necessarily fatal 349 error = true; 350 ready = false; 351 } 352 353 return ready; 354 } 355 356 /** Flush this logger - ensure our File is flushed. */ flush()357 public void flush() 358 { 359 360 if (isReady()) 361 { 362 reportPrinter.flush(); 363 } 364 } 365 366 /** 367 * Close this logger - ensure our File, etc. are closed. 368 * Record format: 369 * <pre></resultfile></pre> 370 */ close()371 public void close() 372 { 373 374 flush(); 375 376 if (isReady()) 377 { 378 closeResultsFile(); 379 reportPrinter.close(); 380 } 381 382 ready = false; 383 } 384 385 /** 386 * worker method to dump the xml header and open the resultsfile element. 387 * 388 * @return true if ready/OK, false if not ready (meaning we may 389 * not have output anything!) 390 */ startResultsFile()391 protected boolean startResultsFile() 392 { 393 394 if (isReady()) 395 { 396 397 // Write out XML header and root test result element 398 reportPrinter.println("<?xml version=\"1.0\"?>"); 399 400 // Note: this tag is closed in our .close() method, which the caller had better call! 401 reportPrinter.println("<" + ELEM_RESULTSFILE + " " 402 + ATTR_FILENAME + "=\"" + fileName + "\">"); 403 404 return true; 405 } 406 else 407 return false; 408 } 409 410 /** 411 * worker method to close the resultsfile element. 412 * 413 * @return true if ready/OK, false if not ready (meaning we may 414 * not have output a closing tag!) 415 */ closeResultsFile()416 protected boolean closeResultsFile() 417 { 418 419 if (isReady()) 420 { 421 reportPrinter.println("</" + ELEM_RESULTSFILE + ">"); 422 423 return true; 424 } 425 else 426 return false; 427 } 428 429 //----------------------------------------------------- 430 //-------- Testfile / Testcase start and stop routines -------- 431 //----------------------------------------------------- 432 433 /** 434 * Report that a testfile has started. 435 * Begins a testfile element. Record format: 436 * <pre><testfile desc="<i>test description</i>" time="<i>timestamp</i>"></pre> 437 * @param name file name or tag specifying the test. 438 * @param comment comment about the test. 439 */ testFileInit(String name, String comment)440 public void testFileInit(String name, String comment) 441 { 442 443 if (isReady()) 444 { 445 reportPrinter.println("<" + ELEM_TESTFILE + " " 446 + ATTR_TESTFILENAME + "=\"" 447 + escapeString(name) + "\" " 448 + ATTR_DESC + "=\"" 449 + escapeString(comment) + "\" " 450 + ATTR_TIME + "=\"" 451 + (new Date()).toString() + "\">"); 452 } 453 } 454 455 /** 456 * Report that a testfile has finished, and report it's result; flushes output. 457 * Ends a testfile element. Record format: 458 * <pre><fileresult desc="<i>test description</i>" result="<i>pass/fail status</i>" time="<i>timestamp</i>"> 459 * </testfile></pre> 460 * @param msg message to log out 461 * @param result result of testfile 462 */ testFileClose(String msg, String result)463 public void testFileClose(String msg, String result) 464 { 465 466 if (isReady()) 467 { 468 reportPrinter.println("<" + ELEM_FILERESULT + " " + ATTR_DESC 469 + "=\"" + escapeString(msg) + "\" " 470 + ATTR_RESULT + "=\"" + result + "\" " 471 + ATTR_TIME + "=\"" 472 + (new Date()).toString() + "\"/>"); 473 reportPrinter.println("</" + ELEM_TESTFILE + ">"); 474 } 475 476 flush(); 477 } 478 479 /** Optimization: for heavy use methods, form pre-defined constants to save on string concatenation. */ 480 private static final String TESTCASEINIT_HDR = "<" + ELEM_TESTCASE + " " 481 + ATTR_DESC + "=\""; 482 483 /** 484 * Report that a testcase has begun. 485 * Begins a testcase element. Record format: 486 * <pre><testcase desc="<i>case description</i>"></pre> 487 * 488 * @param comment message to log out 489 */ testCaseInit(String comment)490 public void testCaseInit(String comment) 491 { 492 493 if (isReady()) 494 { 495 reportPrinter.println(TESTCASEINIT_HDR + escapeString(comment) 496 + "\">"); 497 } 498 } 499 500 /** NEEDSDOC Field TESTCASECLOSE_HDR */ 501 private static final String TESTCASECLOSE_HDR = "<" + ELEM_CASERESULT 502 + " " + ATTR_DESC 503 + "=\""; 504 505 /** 506 * Report that a testcase has finished, and report it's result. 507 * Optionally flushes output. Ends a testcase element. Record format: 508 * <pre><caseresult desc="<i>case description</i>" result="<i>pass/fail status</i>"> 509 * </testcase></pre> 510 * @param msg message of name of test case to log out 511 * @param result result of testfile 512 */ testCaseClose(String msg, String result)513 public void testCaseClose(String msg, String result) 514 { 515 516 if (isReady()) 517 { 518 reportPrinter.println(TESTCASECLOSE_HDR + escapeString(msg) 519 + "\" " + ATTR_RESULT + "=\"" + result 520 + "\"/>"); 521 reportPrinter.println("</" + ELEM_TESTCASE + ">"); 522 } 523 524 if (getFlushOnCaseClose()) 525 flush(); 526 } 527 528 //----------------------------------------------------- 529 //-------- Test results logging routines -------- 530 //----------------------------------------------------- 531 532 /** NEEDSDOC Field MESSAGE_HDR */ 533 private static final String MESSAGE_HDR = "<" + ELEM_MESSAGE + " " 534 + ATTR_LEVEL + "=\""; 535 536 /** 537 * Report a comment to result file with specified severity. 538 * Record format: <pre><message level="##">msg</message></pre> 539 * @param level severity or class of message. 540 * @param msg comment to log out. 541 */ logMsg(int level, String msg)542 public void logMsg(int level, String msg) 543 { 544 545 if (isReady()) 546 { 547 reportPrinter.print(MESSAGE_HDR + level + "\">"); 548 reportPrinter.print(escapeString(msg)); 549 reportPrinter.println("</" + ELEM_MESSAGE + ">"); 550 } 551 } 552 553 /** NEEDSDOC Field ARBITRARY_HDR */ 554 private static final String ARBITRARY_HDR = "<" + ELEM_ARBITRARY + " " 555 + ATTR_LEVEL + "=\""; 556 557 /** 558 * Report an arbitrary String to result file with specified severity. 559 * Appends and prepends \\n newline characters at the start and 560 * end of the message to separate it from the tags. 561 * Record format: <pre><arbitrary level="##"><![CDATA[ 562 * msg 563 * ]]></arbitrary></pre> 564 * 565 * Note that arbitrary messages are always wrapped in CDATA 566 * sections to ensure that any non-valid XML is wrapped. This needs 567 * to be investigated for other elements as well (i.e. we should set a 568 * standard for what Logger calls must be well-formed or not). 569 * @param level severity or class of message. 570 * @param msg arbitrary String to log out. 571 * @todo investigate <b>not</b> fully escaping this string, since 572 * it does get wrappered in CDATA 573 */ logArbitrary(int level, String msg)574 public void logArbitrary(int level, String msg) 575 { 576 577 if (isReady()) 578 { 579 reportPrinter.println(ARBITRARY_HDR + level + "\"><![CDATA["); 580 reportPrinter.println(escapeString(msg)); 581 reportPrinter.println("]]></" + ELEM_ARBITRARY + ">"); 582 } 583 } 584 585 /** NEEDSDOC Field STATISTIC_HDR */ 586 private static final String STATISTIC_HDR = "<" + ELEM_STATISTIC + " " 587 + ATTR_LEVEL + "=\""; 588 589 /** 590 * Logs out statistics to result file with specified severity. 591 * Record format: <pre><statistic level="##" desc="msg"><longval>1234</longval><doubleval>1.234</doubleval></statistic></pre> 592 * @param level severity of message. 593 * @param lVal statistic in long format. 594 * @param dVal statistic in double format. 595 * @param msg comment to log out. 596 */ logStatistic(int level, long lVal, double dVal, String msg)597 public void logStatistic(int level, long lVal, double dVal, String msg) 598 { 599 600 if (isReady()) 601 { 602 reportPrinter.print(STATISTIC_HDR + level + "\" " + ATTR_DESC 603 + "=\"" + escapeString(msg) + "\">"); 604 reportPrinter.print("<" + ELEM_LONGVAL + ">" + lVal + "</" 605 + ELEM_LONGVAL + ">"); 606 reportPrinter.print("<" + ELEM_DOUBLEVAL + ">" + dVal + "</" 607 + ELEM_DOUBLEVAL + ">"); 608 reportPrinter.println("</" + ELEM_STATISTIC + ">"); 609 } 610 } 611 612 /** 613 * Logs out Throwable.toString() and a stack trace of the 614 * Throwable with the specified severity. 615 * <p>This uses logArbitrary to log out your msg - message, 616 * a newline, throwable.toString(), a newline, 617 * and then throwable.printStackTrace().</p> 618 * //@todo future work to use logElement() to output 619 * a specific <throwable> element instead. 620 * @author Shane_Curcuru@lotus.com 621 * @param level severity of message. 622 * @param throwable throwable/exception to log out. 623 * @param msg description of the throwable. 624 */ logThrowable(int level, Throwable throwable, String msg)625 public void logThrowable(int level, Throwable throwable, String msg) 626 { 627 if (isReady()) 628 { 629 StringWriter sWriter = new StringWriter(); 630 631 sWriter.write(msg + "\n"); 632 sWriter.write(throwable.toString() + "\n"); 633 634 PrintWriter pWriter = new PrintWriter(sWriter); 635 636 throwable.printStackTrace(pWriter); 637 logArbitrary(level, sWriter.toString()); 638 } 639 } 640 641 /** 642 * Logs out a element to results with specified severity. 643 * Uses user-supplied element name and attribute list. Currently 644 * attribute values and msg are forced .toString(). Also, 645 * 'level' is forced to be the first attribute of the element. 646 * Record format: 647 * <pre><<i>element_text</i> level="##" 648 * attribute1="value1" 649 * attribute2="value2" 650 * attribute<i>n</i>="value<i>n</i>"> 651 * msg 652 * </<i>element_text</i>></pre> 653 * @author Shane_Curcuru@lotus.com 654 * @param level severity of message. 655 * @param element name of enclosing element 656 * @param attrs hash of name=value attributes; note that the 657 * caller must ensure they're legal XML 658 * @param msg Object to log out .toString(); caller should 659 * ensure it's legal XML (no CDATA is supplied) 660 */ logElement(int level, String element, Hashtable attrs, Object msg)661 public void logElement(int level, String element, Hashtable attrs, 662 Object msg) 663 { 664 665 if (isReady() 666 && (element != null) 667 && (attrs != null) 668 ) 669 { 670 reportPrinter.println("<" + element + " " + ATTR_LEVEL + "=\"" 671 + level + "\""); 672 673 for (Enumeration keys = attrs.keys(); 674 keys.hasMoreElements(); /* no increment portion */ ) 675 { 676 Object key = keys.nextElement(); 677 678 reportPrinter.println(key.toString() + "=\"" 679 + escapeString(attrs.get(key).toString()) + "\""); 680 } 681 682 reportPrinter.println(">"); 683 if (msg != null) 684 reportPrinter.println(msg.toString()); 685 reportPrinter.println("</" + element + ">"); 686 } 687 } 688 689 /** NEEDSDOC Field HASHTABLE_HDR */ 690 private static final String HASHTABLE_HDR = "<" + ELEM_HASHTABLE + " " 691 + ATTR_LEVEL + "=\""; 692 693 // Note the HASHITEM_HDR indent; must be updated if we ever switch to another indenting method. 694 695 /** NEEDSDOC Field HASHITEM_HDR */ 696 private static final String HASHITEM_HDR = " <" + ELEM_HASHITEM + " " 697 + ATTR_KEY + "=\""; 698 699 /** 700 * Logs out contents of a Hashtable with specified severity. 701 * Indents each hashitem within the table. 702 * Record format: <pre><hashtable level="##" desc="msg"/> 703 * <hashitem key="key1">value1</hashitem> 704 * <hashitem key="key2">value2</hashitem> 705 * </hashtable></pre> 706 * 707 * @param level severity or class of message. 708 * @param hash Hashtable to log the contents of. 709 * @param msg decription of the Hashtable. 710 */ logHashtable(int level, Hashtable hash, String msg)711 public void logHashtable(int level, Hashtable hash, String msg) 712 { 713 714 if (isReady()) 715 { 716 reportPrinter.println(HASHTABLE_HDR + level + "\" " + ATTR_DESC 717 + "=\"" + escapeString(msg) + "\">"); 718 719 if (hash == null) 720 { 721 reportPrinter.print("<" + ELEM_HASHITEM + " " + ATTR_KEY 722 + "=\"null\">"); 723 reportPrinter.println("</" + ELEM_HASHITEM + ">"); 724 } 725 726 try 727 { 728 for (Enumeration keys = hash.keys(); 729 keys.hasMoreElements(); /* no increment portion */ ) 730 { 731 Object key = keys.nextElement(); 732 733 // Ensure we'll have clean output by pre-fetching value before outputting anything 734 String value = escapeString(hash.get(key).toString()); 735 736 reportPrinter.print(HASHITEM_HDR + escapeString(key.toString()) 737 + "\">"); 738 reportPrinter.print(value); 739 reportPrinter.println("</" + ELEM_HASHITEM + ">"); 740 } 741 } 742 catch (Exception e) 743 { 744 745 // No-op: should ensure we have clean output 746 } 747 748 reportPrinter.println("</" + ELEM_HASHTABLE + ">"); 749 } 750 } 751 752 //----------------------------------------------------- 753 //-------- Test results reporting check* routines -------- 754 //----------------------------------------------------- 755 756 /** NEEDSDOC Field CHECKPASS_HDR */ 757 private static final String CHECKPASS_HDR = "<" + ELEM_CHECKRESULT + " " 758 + ATTR_RESULT + "=\"" 759 + Reporter.PASS + "\" " 760 + ATTR_DESC + "=\""; 761 762 /** 763 * Writes out a Pass record with comment. 764 * Record format: <pre><checkresult result="PASS" desc="comment"/></pre> 765 * @param comment comment to log with the pass record. 766 */ checkPass(String comment)767 public void checkPass(String comment) 768 { 769 770 if (isReady()) 771 { 772 reportPrinter.println(CHECKPASS_HDR + escapeString(comment) 773 + "\"/>"); 774 } 775 } 776 777 /** NEEDSDOC Field CHECKAMBG_HDR */ 778 private static final String CHECKAMBG_HDR = "<" + ELEM_CHECKRESULT + " " 779 + ATTR_RESULT + "=\"" 780 + Reporter.AMBG + "\" " 781 + ATTR_DESC + "=\""; 782 783 /** 784 * Writes out an ambiguous record with comment. 785 * Record format: <pre><checkresult result="AMBG" desc="comment"/></pre> 786 * @param comment comment to log with the ambg record. 787 */ checkAmbiguous(String comment)788 public void checkAmbiguous(String comment) 789 { 790 791 if (isReady()) 792 { 793 reportPrinter.println(CHECKAMBG_HDR + escapeString(comment) 794 + "\"/>"); 795 } 796 } 797 798 /** NEEDSDOC Field CHECKFAIL_HDR */ 799 private static final String CHECKFAIL_HDR = "<" + ELEM_CHECKRESULT + " " 800 + ATTR_RESULT + "=\"" 801 + Reporter.FAIL + "\" " 802 + ATTR_DESC + "=\""; 803 804 /** 805 * Writes out a Fail record with comment. 806 * Record format: <pre><checkresult result="FAIL" desc="comment"/></pre> 807 * @param comment comment to log with the fail record. 808 */ checkFail(String comment)809 public void checkFail(String comment) 810 { 811 812 if (isReady()) 813 { 814 reportPrinter.println(CHECKFAIL_HDR + escapeString(comment) 815 + "\"/>"); 816 } 817 } 818 819 /** NEEDSDOC Field CHECKERRR_HDR */ 820 private static final String CHECKERRR_HDR = "<" + ELEM_CHECKRESULT + " " 821 + ATTR_RESULT + "=\"" 822 + Reporter.ERRR + "\" " 823 + ATTR_DESC + "=\""; 824 825 /** 826 * Writes out a Error record with comment. 827 * Record format: <pre><checkresult result="ERRR" desc="comment"/></pre> 828 * @param comment comment to log with the error record. 829 */ checkErr(String comment)830 public void checkErr(String comment) 831 { 832 833 if (isReady()) 834 { 835 reportPrinter.println(CHECKERRR_HDR + escapeString(comment) 836 + "\"/>"); 837 } 838 } 839 840 /* EXPERIMENTAL: have duplicate set of check*() methods 841 that all output some form of ID as well as comment. 842 Leave the non-ID taking forms for both simplicity to the 843 end user who doesn't care about IDs as well as for 844 backwards compatibility. 845 */ 846 847 /** ID_ATTR optimization for extra ID attribute. */ 848 private static final String ATTR_ID = "\" id=\""; 849 /** 850 * Writes out a Pass record with comment. 851 * Record format: <pre><checkresult result="PASS" desc="comment"/></pre> 852 * @param comment comment to log with the pass record. 853 * @param ID token to log with the pass record. 854 */ checkPass(String comment, String id)855 public void checkPass(String comment, String id) 856 { 857 858 if (isReady()) 859 { 860 StringBuffer tmp = new StringBuffer(CHECKPASS_HDR + escapeString(comment)); 861 if (id != null) 862 tmp.append(ATTR_ID + escapeString(id)); 863 864 tmp.append("\"/>"); 865 reportPrinter.println(tmp); 866 } 867 } 868 869 /** 870 * Writes out an ambiguous record with comment. 871 * Record format: <pre><checkresult result="AMBG" desc="comment"/></pre> 872 * @param comment comment to log with the ambg record. 873 * @param ID token to log with the pass record. 874 */ checkAmbiguous(String comment, String id)875 public void checkAmbiguous(String comment, String id) 876 { 877 878 if (isReady()) 879 { 880 StringBuffer tmp = new StringBuffer(CHECKAMBG_HDR + escapeString(comment)); 881 if (id != null) 882 tmp.append(ATTR_ID + escapeString(id)); 883 884 tmp.append("\"/>"); 885 reportPrinter.println(tmp); 886 } 887 } 888 889 /** 890 * Writes out a Fail record with comment. 891 * Record format: <pre><checkresult result="FAIL" desc="comment"/></pre> 892 * @param comment comment to log with the fail record. 893 * @param ID token to log with the pass record. 894 */ checkFail(String comment, String id)895 public void checkFail(String comment, String id) 896 { 897 898 if (isReady()) 899 { 900 StringBuffer tmp = new StringBuffer(CHECKFAIL_HDR + escapeString(comment)); 901 if (id != null) 902 tmp.append(ATTR_ID + escapeString(id)); 903 904 tmp.append("\"/>"); 905 reportPrinter.println(tmp); 906 } 907 } 908 909 /** 910 * Writes out a Error record with comment. 911 * Record format: <pre><checkresult result="ERRR" desc="comment"/></pre> 912 * @param comment comment to log with the error record. 913 * @param ID token to log with the pass record. 914 */ checkErr(String comment, String id)915 public void checkErr(String comment, String id) 916 { 917 918 if (isReady()) 919 { 920 StringBuffer tmp = new StringBuffer(CHECKERRR_HDR + escapeString(comment)); 921 if (id != null) 922 tmp.append(ATTR_ID + escapeString(id)); 923 924 tmp.append("\"/>"); 925 reportPrinter.println(tmp); 926 } 927 } 928 929 //----------------------------------------------------- 930 //-------- Worker routines for XML string escaping -------- 931 //----------------------------------------------------- 932 933 /** 934 * Lifted from org.apache.xml.serialize.transition.XMLSerializer 935 * 936 * @param ch character to get entity ref for 937 * @return String of entity name 938 */ getEntityRef(char ch)939 public static String getEntityRef(char ch) 940 { 941 942 // Encode special XML characters into the equivalent character references. 943 // These five are defined by default for all XML documents. 944 switch (ch) 945 { 946 case '<' : 947 return "lt"; 948 case '>' : 949 return "gt"; 950 case '"' : 951 return "quot"; 952 case '\'' : 953 return "apos"; 954 case '&' : 955 return "amp"; 956 } 957 958 return null; 959 } 960 961 /** 962 * Identifies the last printable character in the Unicode range 963 * that is supported by the encoding used with this serializer. 964 * For 8-bit encodings this will be either 0x7E or 0xFF. 965 * For 16-bit encodings this will be 0xFFFF. Characters that are 966 * not printable will be escaped using character references. 967 * Lifted from org.apache.xml.serialize.transition.BaseMarkupSerializer 968 */ 969 public static int _lastPrintable = 0x7E; 970 971 /** 972 * Lifted from org.apache.xml.serialize.transition.BaseMarkupSerializer 973 * 974 * @param ch character to escape 975 * @return String that is escaped 976 */ printEscaped(char ch)977 public static String printEscaped(char ch) 978 { 979 980 String charRef; 981 982 // If there is a suitable entity reference for this 983 // character, print it. The list of available entity 984 // references is almost but not identical between 985 // XML and HTML. 986 charRef = getEntityRef(ch); 987 988 if (charRef != null) 989 { 990 991 //_printer.printText( '&' ); // SC note we need to return a String for 992 //_printer.printText( charRef ); // someone else to serialize 993 //_printer.printText( ';' ); 994 return "&" + charRef + ";"; 995 } 996 else if ((ch >= ' ' && ch <= _lastPrintable && ch != 0xF7) 997 || ch == '\n' || ch == '\r' || ch == '\t') 998 { 999 1000 // If the character is not printable, print as character reference. 1001 // Non printables are below ASCII space but not tab or line 1002 // terminator, ASCII delete, or above a certain Unicode threshold. 1003 //_printer.printText( ch ); 1004 return String.valueOf(ch); 1005 } 1006 else 1007 { 1008 1009 //_printer.printText( "&#" ); 1010 //_printer.printText( Integer.toString( ch ) ); 1011 //_printer.printText( ';' ); 1012 return "&#" + Integer.toString(ch) + ";"; 1013 } 1014 } 1015 1016 /** 1017 * Escapes a string so it may be printed as text content or attribute 1018 * value. Non printable characters are escaped using character references. 1019 * Where the format specifies a deault entity reference, that reference 1020 * is used (e.g. <tt>&lt;</tt>). 1021 * Lifted from org.apache.xml.serialize.transition.BaseMarkupSerializer 1022 * 1023 * @param source The string to escape 1024 * @return String after escaping - needed for our application 1025 */ escapeString(String source)1026 public static String escapeString(String source) 1027 { 1028 // Check for null; just return null (callers shouldn't care) 1029 if (source == null) 1030 { 1031 return null; 1032 } 1033 StringBuffer sb = new StringBuffer(); 1034 final int n = source.length(); 1035 1036 for (int i = 0; i < n; ++i) 1037 { 1038 1039 //char c = source.charAt( i ); 1040 sb.append(printEscaped(source.charAt(i))); 1041 } 1042 1043 return sb.toString(); 1044 } 1045 } // end of class XMLFileLogger 1046 1047