• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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>&lt;resultfile fileName="<i>name of result file</i>"&gt;</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>&lt;/resultfile&gt;</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>&lt;testfile desc="<i>test description</i>" time="<i>timestamp</i>"&gt;</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>&lt;fileresult desc="<i>test description</i>" result="<i>pass/fail status</i>" time="<i>timestamp</i>"&gt;
459      * &lt;/testfile&gt;</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>&lt;testcase desc="<i>case description</i>"&gt;</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>&lt;caseresult desc="<i>case description</i>" result="<i>pass/fail status</i>"&gt;
509      * &lt;/testcase&gt;</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>&lt;message level="##"&gt;msg&lt;/message&gt;</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>&lt;arbitrary level="##"&gt;&lt;![CDATA[
562      * msg
563      * ]]&gt;&lt;/arbitrary&gt;</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>&lt;statistic level="##" desc="msg"&gt;&lt;longval&gt;1234&lt;/longval&gt;&lt;doubleval&gt;1.234&lt;/doubleval&gt;&lt;/statistic&gt;</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 &lt;throwable&gt; 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>&lt;<i>element_text</i> level="##"
648      * attribute1="value1"
649      * attribute2="value2"
650      * attribute<i>n</i>="value<i>n</i>"&gt;
651      * msg
652      * &lt;/<i>element_text</i>&gt;</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>&lt;hashtable level="##" desc="msg"/&gt;
703      * &nbsp;&nbsp;&lt;hashitem key="key1"&gt;value1&lt;/hashitem&gt;
704      * &nbsp;&nbsp;&lt;hashitem key="key2"&gt;value2&lt;/hashitem&gt;
705      * &lt;/hashtable&gt;</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>&lt;checkresult result="PASS" desc="comment"/&gt;</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>&lt;checkresult result="AMBG" desc="comment"/&gt;</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>&lt;checkresult result="FAIL" desc="comment"/&gt;</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>&lt;checkresult result="ERRR" desc="comment"/&gt;</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>&lt;checkresult result="PASS" desc="comment"/&gt;</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>&lt;checkresult result="AMBG" desc="comment"/&gt;</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>&lt;checkresult result="FAIL" desc="comment"/&gt;</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>&lt;checkresult result="ERRR" desc="comment"/&gt;</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>&amp;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