View Javadoc
1   package org.apache.maven.plugin.surefire.report;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.shared.utils.xml.PrettyPrintXMLWriter;
23  import org.apache.maven.shared.utils.xml.XMLWriter;
24  import org.apache.maven.surefire.report.ReportEntry;
25  import org.apache.maven.surefire.report.ReporterException;
26  import org.apache.maven.surefire.report.SafeThrowable;
27  import org.apache.maven.surefire.util.internal.StringUtils;
28  
29  import java.io.BufferedOutputStream;
30  import java.io.File;
31  import java.io.FileOutputStream;
32  import java.io.FilterOutputStream;
33  import java.io.IOException;
34  import java.io.OutputStream;
35  import java.io.OutputStreamWriter;
36  import java.util.ArrayList;
37  import java.util.Collections;
38  import java.util.LinkedHashMap;
39  import java.util.List;
40  import java.util.Map;
41  import java.util.Map.Entry;
42  import java.util.StringTokenizer;
43  
44  import static org.apache.commons.io.IOUtils.closeQuietly;
45  import static org.apache.maven.plugin.surefire.report.DefaultReporterFactory.TestResultType;
46  import static org.apache.maven.plugin.surefire.report.FileReporterUtils.stripIllegalFilenameChars;
47  import static org.apache.maven.plugin.surefire.report.ReportEntryType.SUCCESS;
48  import static org.apache.maven.surefire.util.internal.StringUtils.UTF_8;
49  import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
50  
51  @SuppressWarnings( { "javadoc", "checkstyle:javadoctype" } )
52  // CHECKSTYLE_OFF: LineLength
53  /*
54   * XML format reporter writing to <code>TEST-<i>reportName</i>[-<i>suffix</i>].xml</code> file like written and read
55   * by Ant's <a href="http://ant.apache.org/manual/Tasks/junit.html"><code>&lt;junit&gt;</code></a> and
56   * <a href="http://ant.apache.org/manual/Tasks/junitreport.html"><code>&lt;junitreport&gt;</code></a> tasks,
57   * then supported by many tools like CI servers.
58   * <br>
59   * <pre>&lt;?xml version="1.0" encoding="UTF-8"?>
60   * &lt;testsuite name="<i>suite name</i>" [group="<i>group</i>"] tests="<i>0</i>" failures="<i>0</i>" errors="<i>0</i>" skipped="<i>0</i>" time="<i>0,###.###</i>">
61   *  &lt;properties>
62   *    &lt;property name="<i>name</i>" value="<i>value</i>"/>
63   *    [...]
64   *  &lt;/properties>
65   *  &lt;testcase time="<i>0,###.###</i>" name="<i>test name</i> [classname="<i>class name</i>"] [group="<i>group</i>"]"/>
66   *  &lt;testcase time="<i>0,###.###</i>" name="<i>test name</i> [classname="<i>class name</i>"] [group="<i>group</i>"]">
67   *    &lt;<b>error</b> message="<i>message</i>" type="<i>exception class name</i>"><i>stacktrace</i>&lt;/error>
68   *    &lt;system-out><i>system out content (present only if not empty)</i>&lt;/system-out>
69   *    &lt;system-err><i>system err content (present only if not empty)</i>&lt;/system-err>
70   *  &lt;/testcase>
71   *  &lt;testcase time="<i>0,###.###</i>" name="<i>test name</i> [classname="<i>class name</i>"] [group="<i>group</i>"]">
72   *    &lt;<b>failure</b> message="<i>message</i>" type="<i>exception class name</i>"><i>stacktrace</i>&lt;/failure>
73   *    &lt;system-out><i>system out content (present only if not empty)</i>&lt;/system-out>
74   *    &lt;system-err><i>system err content (present only if not empty)</i>&lt;/system-err>
75   *  &lt;/testcase>
76   *  &lt;testcase time="<i>0,###.###</i>" name="<i>test name</i> [classname="<i>class name</i>"] [group="<i>group</i>"]">
77   *    &lt;<b>skipped</b>/>
78   *  &lt;/testcase>
79   *  [...]</pre>
80   *
81   * @author Kristian Rosenvold
82   * @see <a href="http://wiki.apache.org/ant/Proposals/EnhancedTestReports">Ant's format enhancement proposal</a>
83   *      (not yet implemented by Ant 1.8.2)
84   */
85  @Deprecated // this is no more stateless due to existence of testClassMethodRunHistoryMap since of 2.19. Rename to StatefulXmlReporter in 3.0.
86  public class StatelessXmlReporter
87  {
88      private final File reportsDirectory;
89  
90      private final String reportNameSuffix;
91  
92      private final boolean trimStackTrace;
93  
94      private final int rerunFailingTestsCount;
95  
96      private final String xsdSchemaLocation;
97  
98      // Map between test class name and a map between test method name
99      // and the list of runs for each test method
100     private final Map<String, Map<String, List<WrappedReportEntry>>> testClassMethodRunHistoryMap;
101 
102     public StatelessXmlReporter( File reportsDirectory, String reportNameSuffix, boolean trimStackTrace,
103                                  int rerunFailingTestsCount,
104                                  Map<String, Map<String, List<WrappedReportEntry>>> testClassMethodRunHistoryMap,
105                                  String xsdSchemaLocation )
106     {
107         this.reportsDirectory = reportsDirectory;
108         this.reportNameSuffix = reportNameSuffix;
109         this.trimStackTrace = trimStackTrace;
110         this.rerunFailingTestsCount = rerunFailingTestsCount;
111         this.testClassMethodRunHistoryMap = testClassMethodRunHistoryMap;
112         this.xsdSchemaLocation = xsdSchemaLocation;
113     }
114 
115     public void testSetCompleted( WrappedReportEntry testSetReportEntry, TestSetStats testSetStats )
116     {
117         String testClassName = testSetReportEntry.getName();
118 
119         Map<String, List<WrappedReportEntry>> methodRunHistoryMap = getAddMethodRunHistoryMap( testClassName );
120 
121         // Update testClassMethodRunHistoryMap
122         for ( WrappedReportEntry methodEntry : testSetStats.getReportEntries() )
123         {
124             getAddMethodEntryList( methodRunHistoryMap, methodEntry );
125         }
126 
127         OutputStream outputStream = getOutputStream( testSetReportEntry );
128         OutputStreamWriter fw = getWriter( outputStream );
129         try
130         {
131             XMLWriter ppw = new PrettyPrintXMLWriter( fw );
132             ppw.setEncoding( StringUtils.UTF_8.name() );
133 
134             createTestSuiteElement( ppw, testSetReportEntry, testSetStats, testSetReportEntry.elapsedTimeAsString() );
135 
136             showProperties( ppw, testSetReportEntry.getSystemProperties() );
137 
138             // Iterate through all the test methods in the test class
139             for ( Entry<String, List<WrappedReportEntry>> entry : methodRunHistoryMap.entrySet() )
140             {
141                 List<WrappedReportEntry> methodEntryList = entry.getValue();
142                 if ( methodEntryList == null )
143                 {
144                     throw new IllegalStateException( "Get null test method run history" );
145                 }
146 
147                 if ( !methodEntryList.isEmpty() )
148                 {
149                     if ( rerunFailingTestsCount > 0 )
150                     {
151                         switch ( getTestResultType( methodEntryList ) )
152                         {
153                             case success:
154                                 for ( WrappedReportEntry methodEntry : methodEntryList )
155                                 {
156                                     if ( methodEntry.getReportEntryType() == SUCCESS )
157                                     {
158                                         startTestElement( ppw, methodEntry, reportNameSuffix,
159                                                           methodEntryList.get( 0 ).elapsedTimeAsString() );
160                                         ppw.endElement();
161                                     }
162                                 }
163                                 break;
164                             case error:
165                             case failure:
166                                 // When rerunFailingTestsCount is set to larger than 0
167                                 startTestElement( ppw, methodEntryList.get( 0 ), reportNameSuffix,
168                                                   methodEntryList.get( 0 ).elapsedTimeAsString() );
169                                 boolean firstRun = true;
170                                 for ( WrappedReportEntry singleRunEntry : methodEntryList )
171                                 {
172                                     if ( firstRun )
173                                     {
174                                         firstRun = false;
175                                         getTestProblems( fw, ppw, singleRunEntry, trimStackTrace, outputStream,
176                                                          singleRunEntry.getReportEntryType().getXmlTag(), false );
177                                         createOutErrElements( fw, ppw, singleRunEntry, outputStream );
178                                     }
179                                     else
180                                     {
181                                         getTestProblems( fw, ppw, singleRunEntry, trimStackTrace, outputStream,
182                                                          singleRunEntry.getReportEntryType().getRerunXmlTag(), true );
183                                     }
184                                 }
185                                 ppw.endElement();
186                                 break;
187                             case flake:
188                                 String runtime = "";
189                                 // Get the run time of the first successful run
190                                 for ( WrappedReportEntry singleRunEntry : methodEntryList )
191                                 {
192                                     if ( singleRunEntry.getReportEntryType() == SUCCESS )
193                                     {
194                                         runtime = singleRunEntry.elapsedTimeAsString();
195                                         break;
196                                     }
197                                 }
198                                 startTestElement( ppw, methodEntryList.get( 0 ), reportNameSuffix, runtime );
199                                 for ( WrappedReportEntry singleRunEntry : methodEntryList )
200                                 {
201                                     if ( singleRunEntry.getReportEntryType() != SUCCESS )
202                                     {
203                                         getTestProblems( fw, ppw, singleRunEntry, trimStackTrace, outputStream,
204                                                          singleRunEntry.getReportEntryType().getFlakyXmlTag(), true );
205                                     }
206                                 }
207                                 ppw.endElement();
208 
209                                 break;
210                             case skipped:
211                                 startTestElement( ppw, methodEntryList.get( 0 ), reportNameSuffix,
212                                                   methodEntryList.get( 0 ).elapsedTimeAsString() );
213                                 getTestProblems( fw, ppw, methodEntryList.get( 0 ), trimStackTrace, outputStream,
214                                                  methodEntryList.get( 0 ).getReportEntryType().getXmlTag(), false );
215                                 ppw.endElement();
216                                 break;
217                             default:
218                                 throw new IllegalStateException( "Get unknown test result type" );
219                         }
220                     }
221                     else
222                     {
223                         // rerunFailingTestsCount is smaller than 1, but for some reasons a test could be run
224                         // for more than once
225                         for ( WrappedReportEntry methodEntry : methodEntryList )
226                         {
227                             startTestElement( ppw, methodEntry, reportNameSuffix, methodEntry.elapsedTimeAsString() );
228                             if ( methodEntry.getReportEntryType() != SUCCESS )
229                             {
230                                 getTestProblems( fw, ppw, methodEntry, trimStackTrace, outputStream,
231                                                  methodEntry.getReportEntryType().getXmlTag(), false );
232                                 createOutErrElements( fw, ppw, methodEntry, outputStream );
233                             }
234                             ppw.endElement();
235                         }
236                     }
237                 }
238             }
239             ppw.endElement(); // TestSuite
240         }
241         finally
242         {
243             closeQuietly( fw );
244         }
245     }
246 
247     /**
248      * Clean testClassMethodRunHistoryMap
249      */
250     public void cleanTestHistoryMap()
251     {
252         testClassMethodRunHistoryMap.clear();
253     }
254 
255     /**
256      * Get the result of a test from a list of its runs in WrappedReportEntry
257      *
258      * @param methodEntryList the list of runs for a given test
259      * @return the TestResultType for the given test
260      */
261     private TestResultType getTestResultType( List<WrappedReportEntry> methodEntryList )
262     {
263         List<ReportEntryType> testResultTypeList = new ArrayList<ReportEntryType>();
264         for ( WrappedReportEntry singleRunEntry : methodEntryList )
265         {
266             testResultTypeList.add( singleRunEntry.getReportEntryType() );
267         }
268 
269         return DefaultReporterFactory.getTestResultType( testResultTypeList, rerunFailingTestsCount );
270     }
271 
272     private Map<String, List<WrappedReportEntry>> getAddMethodRunHistoryMap( String testClassName )
273     {
274         Map<String, List<WrappedReportEntry>> methodRunHistoryMap = testClassMethodRunHistoryMap.get( testClassName );
275         if ( methodRunHistoryMap == null )
276         {
277             methodRunHistoryMap = Collections.synchronizedMap( new LinkedHashMap<String, List<WrappedReportEntry>>() );
278             testClassMethodRunHistoryMap.put( testClassName, methodRunHistoryMap );
279         }
280         return methodRunHistoryMap;
281     }
282 
283     private OutputStream getOutputStream( WrappedReportEntry testSetReportEntry )
284     {
285         File reportFile = getReportFile( testSetReportEntry, reportsDirectory, reportNameSuffix );
286 
287         File reportDir = reportFile.getParentFile();
288 
289         //noinspection ResultOfMethodCallIgnored
290         reportDir.mkdirs();
291 
292         try
293         {
294             return new BufferedOutputStream( new FileOutputStream( reportFile ), 16 * 1024 );
295         }
296         catch ( Exception e )
297         {
298             throw new ReporterException( "When writing report", e );
299         }
300     }
301 
302     private static OutputStreamWriter getWriter( OutputStream fos )
303     {
304         return new OutputStreamWriter( fos, UTF_8 );
305     }
306 
307     private static void getAddMethodEntryList( Map<String, List<WrappedReportEntry>> methodRunHistoryMap,
308                                                WrappedReportEntry methodEntry )
309     {
310         List<WrappedReportEntry> methodEntryList = methodRunHistoryMap.get( methodEntry.getName() );
311         if ( methodEntryList == null )
312         {
313             methodEntryList = new ArrayList<WrappedReportEntry>();
314             methodRunHistoryMap.put( methodEntry.getName(), methodEntryList );
315         }
316         methodEntryList.add( methodEntry );
317     }
318 
319     private static File getReportFile( ReportEntry report, File reportsDirectory, String reportNameSuffix )
320     {
321         String reportName = "TEST-" + report.getName();
322         String customizedReportName = isBlank( reportNameSuffix ) ? reportName : reportName + "-" + reportNameSuffix;
323         return new File( reportsDirectory, stripIllegalFilenameChars( customizedReportName + ".xml" ) );
324     }
325 
326     private static void startTestElement( XMLWriter ppw, WrappedReportEntry report, String reportNameSuffix,
327                                           String timeAsString )
328     {
329         ppw.startElement( "testcase" );
330         ppw.addAttribute( "name", report.getReportName() );
331         if ( report.getGroup() != null )
332         {
333             ppw.addAttribute( "group", report.getGroup() );
334         }
335         if ( report.getSourceName() != null )
336         {
337             if ( reportNameSuffix != null && !reportNameSuffix.isEmpty() )
338             {
339                 ppw.addAttribute( "classname", report.getSourceName() + "(" + reportNameSuffix + ")" );
340             }
341             else
342             {
343                 ppw.addAttribute( "classname", report.getSourceName() );
344             }
345         }
346         ppw.addAttribute( "time", timeAsString );
347     }
348 
349     private void createTestSuiteElement( XMLWriter ppw, WrappedReportEntry report, TestSetStats testSetStats,
350                                          String timeAsString )
351     {
352         ppw.startElement( "testsuite" );
353 
354         ppw.addAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" );
355         ppw.addAttribute( "xsi:noNamespaceSchemaLocation", xsdSchemaLocation );
356 
357         ppw.addAttribute( "name", report.getReportName( reportNameSuffix ) );
358 
359         if ( report.getGroup() != null )
360         {
361             ppw.addAttribute( "group", report.getGroup() );
362         }
363 
364         ppw.addAttribute( "time", timeAsString );
365 
366         ppw.addAttribute( "tests", String.valueOf( testSetStats.getCompletedCount() ) );
367 
368         ppw.addAttribute( "errors", String.valueOf( testSetStats.getErrors() ) );
369 
370         ppw.addAttribute( "skipped", String.valueOf( testSetStats.getSkipped() ) );
371 
372         ppw.addAttribute( "failures", String.valueOf( testSetStats.getFailures() ) );
373     }
374 
375     private static void getTestProblems( OutputStreamWriter outputStreamWriter, XMLWriter ppw,
376                                          WrappedReportEntry report, boolean trimStackTrace, OutputStream fw,
377                                          String testErrorType, boolean createOutErrElementsInside )
378     {
379         ppw.startElement( testErrorType );
380 
381         String stackTrace = report.getStackTrace( trimStackTrace );
382 
383         if ( report.getMessage() != null && !report.getMessage().isEmpty() )
384         {
385             ppw.addAttribute( "message", extraEscape( report.getMessage(), true ) );
386         }
387 
388         if ( report.getStackTraceWriter() != null )
389         {
390             //noinspection ThrowableResultOfMethodCallIgnored
391             SafeThrowable t = report.getStackTraceWriter().getThrowable();
392             if ( t != null )
393             {
394                 if ( t.getMessage() != null )
395                 {
396                     int delimiter = stackTrace.indexOf( ":" );
397                     String type = delimiter == -1 ? stackTrace : stackTrace.substring( 0, delimiter );
398                     ppw.addAttribute( "type", type );
399                 }
400                 else
401                 {
402                     ppw.addAttribute( "type", new StringTokenizer( stackTrace ).nextToken() );
403                 }
404             }
405         }
406 
407         boolean hasNestedElements = createOutErrElementsInside & stackTrace != null;
408 
409         if ( stackTrace != null )
410         {
411             if ( hasNestedElements )
412             {
413                 ppw.startElement( "stackTrace" );
414             }
415 
416             ppw.writeText( extraEscape( stackTrace, false ) );
417 
418             if ( hasNestedElements )
419             {
420                 ppw.endElement();
421             }
422         }
423 
424         if ( createOutErrElementsInside )
425         {
426             createOutErrElements( outputStreamWriter, ppw, report, fw );
427         }
428 
429         ppw.endElement(); // entry type
430     }
431 
432     // Create system-out and system-err elements
433     private static void createOutErrElements( OutputStreamWriter outputStreamWriter, XMLWriter ppw,
434                                               WrappedReportEntry report, OutputStream fw )
435     {
436         EncodingOutputStream eos = new EncodingOutputStream( fw );
437         addOutputStreamElement( outputStreamWriter, eos, ppw, report.getStdout(), "system-out" );
438         addOutputStreamElement( outputStreamWriter, eos, ppw, report.getStdErr(), "system-err" );
439     }
440 
441     private static void addOutputStreamElement( OutputStreamWriter outputStreamWriter,
442                                          EncodingOutputStream eos, XMLWriter xmlWriter,
443                                          Utf8RecodingDeferredFileOutputStream utf8RecodingDeferredFileOutputStream,
444                                          String name )
445     {
446         if ( utf8RecodingDeferredFileOutputStream != null && utf8RecodingDeferredFileOutputStream.getByteCount() > 0 )
447         {
448             xmlWriter.startElement( name );
449 
450             try
451             {
452                 xmlWriter.writeText( "" ); // Cheat sax to emit element
453                 outputStreamWriter.flush();
454                 utf8RecodingDeferredFileOutputStream.close();
455                 eos.getUnderlying().write( ByteConstantsHolder.CDATA_START_BYTES ); // emit cdata
456                 utf8RecodingDeferredFileOutputStream.writeTo( eos );
457                 eos.getUnderlying().write( ByteConstantsHolder.CDATA_END_BYTES );
458                 eos.flush();
459             }
460             catch ( IOException e )
461             {
462                 throw new ReporterException( "When writing xml report stdout/stderr", e );
463             }
464             xmlWriter.endElement();
465         }
466     }
467 
468     /**
469      * Adds system properties to the XML report.
470      * <br>
471      *
472      * @param xmlWriter The test suite to report to
473      */
474     private static void showProperties( XMLWriter xmlWriter, Map<String, String> systemProperties )
475     {
476         xmlWriter.startElement( "properties" );
477         for ( final Entry<String, String> entry : systemProperties.entrySet() )
478         {
479             final String key = entry.getKey();
480             String value = entry.getValue();
481 
482             if ( value == null )
483             {
484                 value = "null";
485             }
486 
487             xmlWriter.startElement( "property" );
488 
489             xmlWriter.addAttribute( "name", key );
490 
491             xmlWriter.addAttribute( "value", extraEscape( value, true ) );
492 
493             xmlWriter.endElement();
494         }
495         xmlWriter.endElement();
496     }
497 
498     /**
499      * Handle stuff that may pop up in java that is not legal in xml
500      *
501      * @param message   The string
502      * @param attribute true if the escaped value is inside an attribute
503      * @return The escaped string
504      */
505     private static String extraEscape( String message, boolean attribute )
506     {
507         // Someday convert to xml 1.1 which handles everything but 0 inside string
508         return containsEscapesIllegalXml10( message ) ? escapeXml( message, attribute ) : message;
509     }
510 
511     private static final class EncodingOutputStream
512         extends FilterOutputStream
513     {
514         private int c1;
515 
516         private int c2;
517 
518         EncodingOutputStream( OutputStream out )
519         {
520             super( out );
521         }
522 
523         OutputStream getUnderlying()
524         {
525             return out;
526         }
527 
528         private boolean isCdataEndBlock( int c )
529         {
530             return c1 == ']' && c2 == ']' && c == '>';
531         }
532 
533         @Override
534         public void write( int b )
535             throws IOException
536         {
537             if ( isCdataEndBlock( b ) )
538             {
539                 out.write( ByteConstantsHolder.CDATA_ESCAPE_STRING_BYTES );
540             }
541             else if ( isIllegalEscape( b ) )
542             {
543                 // uh-oh!  This character is illegal in XML 1.0!
544                 // http://www.w3.org/TR/1998/REC-xml-19980210#charsets
545                 // we're going to deliberately doubly-XML escape it...
546                 // there's nothing better we can do! :-(
547                 // SUREFIRE-456
548                 out.write( ByteConstantsHolder.AMP_BYTES );
549                 out.write( String.valueOf( b ).getBytes( UTF_8 ) );
550                 out.write( ';' ); // & Will be encoded to amp inside xml encodingSHO
551             }
552             else
553             {
554                 out.write( b );
555             }
556             c1 = c2;
557             c2 = b;
558         }
559     }
560 
561     private static boolean containsEscapesIllegalXml10( String message )
562     {
563         int size = message.length();
564         for ( int i = 0; i < size; i++ )
565         {
566             if ( isIllegalEscape( message.charAt( i ) ) )
567             {
568                 return true;
569             }
570 
571         }
572         return false;
573     }
574 
575     private static boolean isIllegalEscape( char c )
576     {
577         return isIllegalEscape( (int) c );
578     }
579 
580     private static boolean isIllegalEscape( int c )
581     {
582         return c >= 0 && c < 32 && c != '\n' && c != '\r' && c != '\t';
583     }
584 
585     private static String escapeXml( String text, boolean attribute )
586     {
587         StringBuilder sb = new StringBuilder( text.length() * 2 );
588         for ( int i = 0; i < text.length(); i++ )
589         {
590             char c = text.charAt( i );
591             if ( isIllegalEscape( c ) )
592             {
593                 // uh-oh!  This character is illegal in XML 1.0!
594                 // http://www.w3.org/TR/1998/REC-xml-19980210#charsets
595                 // we're going to deliberately doubly-XML escape it...
596                 // there's nothing better we can do! :-(
597                 // SUREFIRE-456
598                 sb.append( attribute ? "&#" : "&amp#" ).append( (int) c ).append(
599                     ';' ); // & Will be encoded to amp inside xml encodingSHO
600             }
601             else
602             {
603                 sb.append( c );
604             }
605         }
606         return sb.toString();
607     }
608 
609     private static final class ByteConstantsHolder
610     {
611         private static final byte[] CDATA_START_BYTES;
612 
613         private static final byte[] CDATA_END_BYTES;
614 
615         private static final byte[] CDATA_ESCAPE_STRING_BYTES;
616 
617         private static final byte[] AMP_BYTES;
618 
619         static
620         {
621             CDATA_START_BYTES = "<![CDATA[".getBytes( UTF_8 );
622             CDATA_END_BYTES = "]]>".getBytes( UTF_8 );
623             CDATA_ESCAPE_STRING_BYTES = "]]><![CDATA[>".getBytes( UTF_8 );
624             AMP_BYTES = "&amp#".getBytes( UTF_8 );
625         }
626     }
627 }