Clover coverage report - Code Coverage for tapestry release 3.1-alpha-1
Coverage timestamp: Mon Feb 21 2005 09:16:14 EST
file stats: LOC: 817   Methods: 32
NCLOC: 331   Classes: 1
30 day Evaluation Version distributed via the Maven Jar Repository. Clover is not free. You have 30 days to evaluate it. Please visit http://www.thecortex.net/clover to obtain a licensed version of Clover
 
 Source file Conditionals Statements Methods TOTAL
AbstractMarkupWriter.java 76.4% 83.1% 81.2% 81.1%
coverage coverage
 1   
 // Copyright 2004, 2005 The Apache Software Foundation
 2   
 //
 3   
 // Licensed under the Apache License, Version 2.0 (the "License");
 4   
 // you may not use this file except in compliance with the License.
 5   
 // You may obtain a copy of the License at
 6   
 //
 7   
 //     http://www.apache.org/licenses/LICENSE-2.0
 8   
 //
 9   
 // Unless required by applicable law or agreed to in writing, software
 10   
 // distributed under the License is distributed on an "AS IS" BASIS,
 11   
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12   
 // See the License for the specific language governing permissions and
 13   
 // limitations under the License.
 14   
 
 15   
 package org.apache.tapestry;
 16   
 
 17   
 import java.io.BufferedWriter;
 18   
 import java.io.OutputStream;
 19   
 import java.io.OutputStreamWriter;
 20   
 import java.io.PrintWriter;
 21   
 import java.io.UnsupportedEncodingException;
 22   
 import java.io.Writer;
 23   
 import java.util.Stack;
 24   
 
 25   
 import org.apache.tapestry.util.ContentType;
 26   
 import org.apache.tapestry.util.text.ICharacterTranslator;
 27   
 import org.apache.tapestry.util.text.ICharacterTranslatorSource;
 28   
 
 29   
 /**
 30   
  * Abstract base class implementing the {@link IMarkupWriter} interface.
 31   
  * This class is used to create a Generic Tag Markup Language (GTML) output.   
 32   
  * It is more sophisticated than <code>PrintWriter</code> in that it maintains   
 33   
  * a concept hierarchy of open GTML tags. It also supplies a number of other
 34   
  * of the features that are useful when creating GTML.
 35   
  *
 36   
  * Elements are started with the {@link #begin(String)} 
 37   
  * or {@link #beginEmpty(String)}
 38   
  * methods. Once they are started, attributes for the elements may be set with
 39   
  * the various <code>attribute()</code> methods. The element is closed off
 40   
  * (i.e., the closing '&gt;' character is written) when any other method
 41   
  * is invoked (exception: methods which do not produce output, such as
 42   
  * {@link #flush()}). The <code>end()</code> methods end an element,
 43   
  * writing an GTML close tag to the output.
 44   
  *
 45   
  * <p>TBD:
 46   
  * <ul>
 47   
  * <li>Support XML and XHTML
 48   
  *  <li>What to do with Unicode characters with a value greater than 255?
 49   
  * </ul>
 50   
  *
 51   
  * <p>This class is derived from the original class 
 52   
  * <code>com.primix.servlet.HTMLWriter</code>,
 53   
  * part of the <b>ServletUtils</b> framework available from
 54   
  * <a href="http://www.gjt.org/servlets/JCVSlet/list/gjt/com/primix/servlet">The Giant 
 55   
  * Java Tree</a>.
 56   
  *
 57   
  * @author Howard Ship, David Solis
 58   
  * @since 0.2.9
 59   
  *
 60   
  **/
 61   
 
 62   
 public abstract class AbstractMarkupWriter implements IMarkupWriter
 63   
 {
 64   
     /**
 65   
      * The encoding to be used should it be omitted in the constructors.
 66   
      * This is only used for backward compatibility. New code always provides the encoding.
 67   
      */
 68   
 
 69   
     private static final String DEFAULT_ENCODING = "utf-8";
 70   
 
 71   
     /**
 72   
      * The underlying {@link PrintWriter} that output is sent to. 
 73   
      *  
 74   
      **/
 75   
 
 76   
     private PrintWriter _writer;
 77   
 
 78   
     /**
 79   
      * Indicates whether a tag is open or not. A tag is opened by
 80   
      * {@link #begin(String)} or {@link #beginEmpty(String)}.
 81   
      * It stays open while calls to the <code>attribute()</code>
 82   
      * methods are made. It is closed
 83   
      * (the '&gt;' is written) when any other method is invoked.
 84   
      *
 85   
      **/
 86   
 
 87   
     private boolean _openTag = false;
 88   
 
 89   
     /**
 90   
      *  Indicates that the tag was opened with 
 91   
      *  {@link #beginEmpty(String)}, which affects
 92   
      *  how the tag is closed (a slash is added to indicate the
 93   
      *  lack of a body).  This is compatible with HTML, but reflects
 94   
      *  an XML/XHTML leaning.
 95   
      * 
 96   
      *  @since 2.2
 97   
      * 
 98   
      **/
 99   
 
 100   
     private boolean _emptyTag = false;
 101   
 
 102   
     /**
 103   
      * A Stack of Strings used to track the active tag elements. Elements are active
 104   
      * until the corresponding close tag is written.  The {@link #push(String)} method
 105   
      * adds elements to the stack, {@link #pop()} removes them.
 106   
      *
 107   
      **/
 108   
 
 109   
     private Stack _activeElementStack;
 110   
 
 111   
     /**
 112   
      * The depth of the open tag stack.
 113   
      * @see #_activeElementStack
 114   
      *
 115   
      **/
 116   
 
 117   
     private int _depth = 0;
 118   
 
 119   
     private char[] _buffer;
 120   
 
 121   
     /**
 122   
      *  Implemented in concrete subclasses to provide an indication of which
 123   
      *  characters are 'safe' to insert directly into the response.  The index
 124   
      *  into the array is the character, if the value at the index is false (or the
 125   
      *  index out of range), then the character is escaped.
 126   
      *
 127   
      **/
 128   
 
 129   
     private ICharacterTranslator _translator;
 130   
     
 131   
     private String _contentType;
 132   
 
 133   
     /**
 134   
      *  Indicates whether {@link #close()} should close the 
 135   
      *  underlying {@link PrintWriter}.
 136   
      * 
 137   
      *  @since 3.0
 138   
      * 
 139   
      **/
 140   
 
 141   
     private boolean _propagateClose = true;
 142   
 
 143  676
     public String getContentType()
 144   
     {
 145  676
         return _contentType;
 146   
     }
 147   
 
 148   
     abstract public IMarkupWriter getNestedWriter();
 149   
 
 150   
     /**
 151   
      *  General constructor used by subclasses.
 152   
      * 
 153   
      *  @param safe an array of flags indicating which characters
 154   
      *  can be passed directly through with out filtering.  Characters marked
 155   
      *  unsafe, or outside the range defined by safe, are converted to entities.
 156   
      *  @param entities a set of prefered entities, unsafe characters with
 157   
      *  a defined entity use the entity, other characters are converted
 158   
      *  to numeric entities.
 159   
      *  @param contentType the MIME type of the content produced by the writer.
 160   
      *  @param encoding the encoding of content produced by the writer.
 161   
      *  @param stream stream to which content will be written.
 162   
      *
 163   
      **/
 164   
 
 165  195
     protected AbstractMarkupWriter(
 166   
         ICharacterTranslatorSource translatorSource,
 167   
         String contentType,
 168   
         String encoding,
 169   
         OutputStream stream)
 170   
     {
 171  195
         if (translatorSource == null || contentType == null || encoding == null)
 172  0
             throw new IllegalArgumentException(
 173   
                 Tapestry.getMessage("AbstractMarkupWriter.missing-constructor-parameters"));
 174   
 
 175  195
         ContentType contentTypeObject = generateFullContentType(contentType, encoding);
 176  195
         _contentType = contentTypeObject.unparse();
 177   
         
 178  195
         encoding = contentTypeObject.getParameter("charset");
 179  195
         _translator = translatorSource.getTranslator(encoding);
 180   
         
 181  195
         setOutputStream(stream, encoding);
 182   
     }
 183   
 
 184   
     /**
 185   
      *  General constructor used by subclasses.
 186   
      *  This constructor is left for backward compatibility. It is preferred that 
 187   
      *  it is not used since it does not specify an encoding for conversion.
 188   
      * 
 189   
      *  @param safe an array of flags indicating which characters
 190   
      *  can be passed directly through with out filtering.  Characters marked
 191   
      *  unsafe, or outside the range defined by safe, are converted to entities.
 192   
      *  @param entities a set of prefered entities, unsafe characters with
 193   
      *  a defined entity use the entity, other characters are converted
 194   
      *  to numeric entities.
 195   
      *  @param contentType the type of content produced by the
 196   
      *  writer.
 197   
      *  @param stream stream to which content will be written.
 198   
      **/
 199   
 
 200  0
     protected AbstractMarkupWriter(
 201   
         ICharacterTranslatorSource translatorSource,
 202   
         String contentType,
 203   
         OutputStream stream)
 204   
     {
 205  0
         this(translatorSource, contentType);
 206   
 
 207  0
         ContentType contentTypeObject = new ContentType(contentType);
 208  0
         String encoding = contentTypeObject.getParameter("charset");
 209   
 
 210  0
         setOutputStream(stream, encoding);
 211   
     }
 212   
 
 213   
     /**
 214   
      *  Creates new markup writer around the underlying {@link PrintWriter}.
 215   
      * 
 216   
      *  <p>This is primarily used by {@link org.apache.tapestry.engine.TagSupportService},
 217   
      *  which is inlcuding content, and therefore this method will not
 218   
      *  close the writer when the markup writer is closed.
 219   
      * 
 220   
      *  @since 3.0
 221   
      *  
 222   
      **/
 223   
 
 224  0
     protected AbstractMarkupWriter(
 225   
         ICharacterTranslatorSource translatorSource,
 226   
         String contentType,
 227   
         PrintWriter writer)
 228   
     {
 229  0
         this(translatorSource, contentType);
 230   
 
 231   
         // When the markup writer is closed, the underlying writer
 232   
         // is NOT closed.
 233   
 
 234  0
         _propagateClose = false;
 235  0
         _writer = writer;
 236   
     }
 237   
 
 238   
     /**
 239   
      *  Special constructor used for nested response writers.
 240   
      *  The subclass is responsible for creating the writer.
 241   
      * 
 242   
      **/
 243   
 
 244  309
     protected AbstractMarkupWriter(ICharacterTranslatorSource translatorSource, String contentType)
 245   
     {
 246  309
         if (translatorSource == null || contentType == null)
 247  0
             throw new IllegalArgumentException(
 248   
                 Tapestry.getMessage("AbstractMarkupWriter.missing-constructor-parameters"));
 249   
 
 250  309
         ContentType contentTypeObject = generateFullContentType(contentType, DEFAULT_ENCODING);
 251  309
         _contentType = contentTypeObject.unparse();
 252   
         
 253  309
         String encoding = contentTypeObject.getParameter("charset");
 254  309
         _translator = translatorSource.getTranslator(encoding);
 255   
     }
 256   
 
 257   
     /**
 258   
      * Ensures that the content type has a charset (encoding) parameter.
 259   
      * 
 260   
      * @param contentType The content type, e.g. text/html. It may contain a charset parameter.
 261   
      * @param encoding The value of the charset parameter of the content type if it is not already present.
 262   
      * @return The content type containing a charset parameter, e.g. text/html;charset=utf-8 
 263   
      */
 264  504
     private ContentType generateFullContentType(String contentType, String encoding)
 265   
     {
 266  504
         ContentType contentTypeObject = new ContentType(contentType);
 267  504
         if (contentTypeObject.getParameter("charset") == null)
 268  195
             contentTypeObject.setParameter("charset", encoding);
 269  504
         return contentTypeObject;
 270   
     }
 271   
 
 272  309
     protected void setWriter(PrintWriter writer)
 273   
     {
 274  309
         _writer = writer;
 275   
     }
 276   
 
 277  195
     protected void setOutputStream(OutputStream stream, String encoding)
 278   
     {
 279  195
         try
 280   
         {
 281  195
             OutputStreamWriter owriter;
 282  195
             if (encoding != null)
 283  195
                 owriter = new OutputStreamWriter(stream, encoding);
 284   
             else
 285  0
                 owriter = new OutputStreamWriter(stream);
 286  195
             Writer bwriter = new BufferedWriter(owriter);
 287   
 
 288  195
             _writer = new PrintWriter(bwriter);
 289   
         }
 290   
         catch (UnsupportedEncodingException e)
 291   
         {
 292  0
             throw new IllegalArgumentException(
 293   
                 Tapestry.format("illegal-encoding", encoding));
 294   
         }
 295   
     }
 296   
 
 297   
     /**
 298   
      * Writes an integer attribute into the currently open tag.
 299   
      *
 300   
      * <p>TBD: Validate that name is legal.
 301   
      *
 302   
      * @throws IllegalStateException if there is no open tag.
 303   
      *
 304   
      **/
 305   
 
 306  263
     public void attribute(String name, int value)
 307   
     {
 308  263
         checkTagOpen();
 309   
 
 310  263
         _writer.print(' ');
 311  263
         _writer.print(name);
 312  263
         _writer.print("=\"");
 313  263
         _writer.print(value);
 314  263
         _writer.print('"');
 315   
     }
 316   
 
 317   
     /**
 318   
      *  Writes a boolean attribute into the currently open tag.
 319   
      *
 320   
      *  <p>TBD: Validate that name is legal.
 321   
      *
 322   
      *  @throws IllegalStateException if there is no open tag.
 323   
      *
 324   
      *  @since 3.0
 325   
      *
 326   
      **/
 327   
 
 328  2
     public void attribute(String name, boolean value)
 329   
     {
 330  2
         checkTagOpen();
 331   
 
 332  2
         _writer.print(' ');
 333  2
         _writer.print(name);
 334  2
         _writer.print("=\"");
 335  2
         _writer.print(value);
 336  2
         _writer.print('"');
 337   
     }
 338   
 
 339   
     /**
 340   
      *  Writes an attribute into the most recently opened tag. This must be called after
 341   
      *  {@link #begin(String)}
 342   
      *  and before any other kind of writing (which closes the tag).
 343   
      *
 344   
      *  <p>The value may be null. A null value will be rendered as an empty string.
 345   
      *
 346   
      *  <p>Troublesome characters in the value are converted to thier GTML entities, much
 347   
      *  like a <code>print()</code> method, with the following exceptions:
 348   
      *  <ul>
 349   
      *  <li>The double quote (&quot;) is converted to &amp;quot;
 350   
      *  <li>The ampersand (&amp;) is passed through unchanged
 351   
      *  </ul>
 352   
      *
 353   
      *  @throws IllegalStateException if there is no open tag.
 354   
      *  @param name The name of the attribute to write (no validation
 355   
      *  is done on the name).
 356   
      *  @param value The value to write.  If null, the attribute
 357   
      *  name is written as the value.  Otherwise, the
 358   
      *  value is written, 
 359   
      **/
 360   
 
 361  5913
     public void attribute(String name, String value)
 362   
     {
 363  5913
         checkTagOpen();
 364   
 
 365  5913
         _writer.print(' ');
 366   
 
 367   
         // Could use a check here that name contains only valid characters
 368   
 
 369  5913
         _writer.print(name);
 370  5913
         _writer.print("=\"");
 371   
 
 372  5913
         if (value != null)
 373   
         {
 374  5912
             int length = value.length();
 375   
 
 376  5912
             if (_buffer == null || _buffer.length < length)
 377  771
                 _buffer = new char[length];
 378   
 
 379  5912
             value.getChars(0, length, _buffer, 0);
 380   
 
 381  5912
             safePrint(_buffer, 0, length, true);
 382   
         }
 383   
 
 384  5913
         _writer.print('"');
 385   
 
 386   
     }
 387   
 
 388   
     /**
 389   
       *  Similar to {@link #attribute(String, String)} but no escaping of invalid elements
 390   
       *  is done for the value.
 391   
       * 
 392   
       *  @throws IllegalStateException if there is no open tag.
 393   
       *
 394   
       *  @since 3.0
 395   
       *
 396   
       **/
 397  7
     public void attributeRaw(String name, String value)
 398   
     {
 399  7
         if (value == null)
 400   
         {
 401  0
             attribute(name, value);
 402  0
             return;
 403   
         }
 404   
 
 405  7
         checkTagOpen();
 406   
 
 407  7
         _writer.print(' ');
 408   
 
 409  7
         _writer.print(name);
 410   
 
 411  7
         _writer.print("=\"");
 412  7
         _writer.print(value);
 413  7
         _writer.print('"');
 414   
     }
 415   
 
 416   
     /**
 417   
      * Closes any existing tag then starts a new element. The new element is pushed
 418   
      * onto the active element stack.
 419   
      **/
 420   
 
 421  11159
     public void begin(String name)
 422   
     {
 423  11159
         if (_openTag)
 424  3121
             closeTag();
 425   
 
 426  11159
         push(name);
 427   
 
 428  11159
         _writer.print('<');
 429  11159
         _writer.print(name);
 430   
 
 431  11159
         _openTag = true;
 432  11159
         _emptyTag = false;
 433   
     }
 434   
 
 435   
     /**
 436   
      * Starts an element that will not later be matched with an <code>end()</code>
 437   
      * call. This is useful for elements such as &lt;hr;&gt; or &lt;br&gt; that
 438   
      * do not need closing tags.
 439   
      *
 440   
      **/
 441   
 
 442  2823
     public void beginEmpty(String name)
 443   
     {
 444  2823
         if (_openTag)
 445  103
             closeTag();
 446   
 
 447  2823
         _writer.print('<');
 448  2823
         _writer.print(name);
 449   
 
 450  2823
         _openTag = true;
 451  2823
         _emptyTag = true;
 452   
     }
 453   
 
 454   
     /**
 455   
      * Invokes <code>checkError()</code> on the
 456   
      *  <code>PrintWriter</code> used to format output.
 457   
      **/
 458   
 
 459  0
     public boolean checkError()
 460   
     {
 461  0
         return _writer.checkError();
 462   
     }
 463   
 
 464  6185
     private void checkTagOpen()
 465   
     {
 466  6185
         if (!_openTag)
 467  0
             throw new IllegalStateException(
 468   
                 Tapestry.getMessage("AbstractMarkupWriter.tag-not-open"));
 469   
     }
 470   
 
 471   
     /**
 472   
      * Closes this <code>IMarkupWriter</code>. Any active elements are closed. The
 473   
      * {@link PrintWriter} is then  sent {@link PrintWriter#close()}.
 474   
      *
 475   
      **/
 476   
 
 477  483
     public void close()
 478   
     {
 479  483
         if (_openTag)
 480  2
             closeTag();
 481   
 
 482   
         // Close any active elements.
 483   
 
 484  483
         while (_depth > 0)
 485   
         {
 486  15
             _writer.print("</");
 487  15
             _writer.print(pop());
 488  15
             _writer.print('>');
 489   
         }
 490   
 
 491  483
         if (_propagateClose)
 492  483
             _writer.close();
 493   
 
 494  483
         _writer = null;
 495  483
         _activeElementStack = null;
 496  483
         _buffer = null;
 497   
     }
 498   
 
 499   
     /**
 500   
      *  Closes the most recently opened element by writing the '&gt;' that ends
 501   
      *  it. May write a slash before the '&gt;' if the tag
 502   
      *  was opened by {@link #beginEmpty(String)}.
 503   
      * 
 504   
      *  <p>Once this is invoked, the <code>attribute()</code> methods
 505   
      *  may not be used until a new element is opened with {@link #begin(String)} or
 506   
      *  or {@link #beginEmpty(String)}.
 507   
      **/
 508   
 
 509  13980
     public void closeTag()
 510   
     {
 511  13980
         if (_emptyTag)
 512  2822
             _writer.print('/');
 513   
 
 514  13980
         _writer.print('>');
 515   
 
 516  13980
         _openTag = false;
 517  13980
         _emptyTag = false;
 518   
     }
 519   
 
 520   
     /**
 521   
      * Writes an GTML comment. Any open tag is first closed. 
 522   
      * The method takes care of
 523   
      * providing the <code>&lt;!--</code> and <code>--&gt;</code>, 
 524   
      * including a blank line after the close of the comment.
 525   
      *
 526   
      * <p>Most characters are valid inside an GTML comment, so no check
 527   
      * of the contents is made (much like {@link #printRaw(String)}.
 528   
      *
 529   
      **/
 530   
 
 531  676
     public void comment(String value)
 532   
     {
 533  676
         if (_openTag)
 534  0
             closeTag();
 535   
 
 536  676
         _writer.print("<!-- ");
 537  676
         _writer.print(value);
 538  676
         _writer.println(" -->");
 539   
     }
 540   
 
 541   
     /**
 542   
      * Ends the element most recently started by {@link #begin(String)}. 
 543   
      * The name of the tag
 544   
      * is popped off of the active element stack and used to form an GTML close tag.
 545   
      *
 546   
      * <p>TBD: Error checking for the open element stack empty.
 547   
      **/
 548   
 
 549  5329
     public void end()
 550   
     {
 551  5329
         if (_openTag)
 552  7
             closeTag();
 553   
 
 554  5329
         _writer.print("</");
 555  5329
         _writer.print(pop());
 556  5329
         _writer.print('>');
 557   
     }
 558   
 
 559   
     /**
 560   
      * Ends the most recently started element with the given name. This will
 561   
      * also end any other intermediate elements. This is very useful for easily
 562   
      * ending a table or even an entire page.
 563   
      *
 564   
      * <p>TBD: Error check if the name matches nothing on the open tag stack.
 565   
      **/
 566   
 
 567  2871
     public void end(String name)
 568   
     {
 569  2871
         if (_openTag)
 570  7
             closeTag();
 571   
 
 572  2871
         while (true)
 573   
         {
 574  5813
             String tagName = pop();
 575   
 
 576  5813
             _writer.print("</");
 577  5813
             _writer.print(tagName);
 578  5813
             _writer.print('>');
 579   
 
 580  5813
             if (tagName.equals(name))
 581  2871
                 break;
 582   
         }
 583   
     }
 584   
 
 585   
     /**
 586   
      * Forwards <code>flush()</code> to this <code>AbstractMarkupWriter</code>'s 
 587   
      * <code>PrintWriter</code>.
 588   
      *
 589   
      **/
 590   
 
 591  0
     public void flush()
 592   
     {
 593  0
         _writer.flush();
 594   
     }
 595   
 
 596   
     /**
 597   
      *  Removes the top element from the active element stack and returns it.
 598   
      *
 599   
      **/
 600   
 
 601  11157
     protected final String pop()
 602   
     {
 603  11157
         String result = (String) _activeElementStack.pop();
 604  11157
         _depth--;
 605   
 
 606  11157
         return result;
 607   
     }
 608   
 
 609   
     /**
 610   
      *
 611   
      * The primary <code>print()</code> method, used by most other methods.
 612   
      *
 613   
      * <p>Prints the character array, first closing any open tag. Problematic characters
 614   
      * ('&lt;', '&gt;' and '&amp;') are converted to their
 615   
      * GTML entities.
 616   
      *
 617   
      * <p>All 'unsafe' characters are properly converted to either a named
 618   
      * or numeric GTML entity.  This can be somewhat expensive, so use
 619   
      * {@link #printRaw(char[], int, int)} if the data to print is guarenteed
 620   
      * to be safe.
 621   
      *
 622   
      * <p>Does <em>nothing</em> if <code>data</code> is null.
 623   
      *
 624   
      * <p>Closes any open tag.
 625   
      *
 626   
      **/
 627   
 
 628  9330
     public void print(char[] data, int offset, int length)
 629   
     {
 630  9330
         if (data == null)
 631  0
             return;
 632   
 
 633  9330
         if (_openTag)
 634  8958
             closeTag();
 635   
 
 636  9330
         safePrint(data, offset, length, false);
 637   
     }
 638   
 
 639   
     /**
 640   
      * Prints a single character. If the character is not a 'safe' character,
 641   
      * such as '&lt;', then it's GTML entity (named or numeric) is printed instead.
 642   
      *
 643   
      * <p>Closes any open tag.
 644   
      *
 645   
      **/
 646   
 
 647  0
     public void print(char value)
 648   
     {
 649  0
         if (_openTag)
 650  0
             closeTag();
 651   
 
 652  0
         String translation = _translator.translate(value);
 653   
         
 654  0
         if (translation == null)
 655  0
             _writer.print(value);
 656   
         else
 657  0
             _writer.print(translation);
 658   
     }
 659   
 
 660   
     /**
 661   
      * Prints an integer.
 662   
      *
 663   
      * <p>Closes any open tag.
 664   
      *
 665   
      **/
 666   
 
 667  0
     public void print(int value)
 668   
     {
 669  0
         if (_openTag)
 670  0
             closeTag();
 671   
 
 672  0
         _writer.print(value);
 673   
     }
 674   
 
 675   
     /**
 676   
      * Invokes {@link #print(char[], int, int)} to print the string.  Use
 677   
      * {@link #printRaw(String)} if the character data is known to be safe.
 678   
      *
 679   
      * <p>Does <em>nothing</em> if <code>value</code> is null.
 680   
      *
 681   
      * <p>Closes any open tag.
 682   
      *
 683   
      * @see #print(char[], int, int)
 684   
      *
 685   
      **/
 686   
 
 687  9330
     public void print(String value)
 688   
     {
 689  9330
         if (value == null)
 690  0
             return;
 691   
 
 692  9330
         int length = value.length();
 693   
 
 694  9330
         if (_buffer == null || _buffer.length < length)
 695  208
             _buffer = new char[length];
 696   
 
 697  9330
         value.getChars(0, length, _buffer, 0);
 698   
 
 699  9330
         print(_buffer, 0, length);
 700   
     }
 701   
 
 702   
     /**
 703   
      * Closes the open tag (if any), then prints a line seperator to the output stream.
 704   
      *
 705   
      **/
 706   
 
 707  4222
     public void println()
 708   
     {
 709  4222
         if (_openTag)
 710  1418
             closeTag();
 711   
 
 712  4222
         _writer.println();
 713   
     }
 714   
 
 715   
     /**
 716   
      * Prints and portion of an output buffer to the stream.
 717   
      * No escaping of invalid GTML elements is done, which
 718   
      * makes this more effecient than <code>print()</code>. 
 719   
      * Does <em>nothing</em> if <code>buffer</code>
 720   
      * is null.
 721   
      *
 722   
      * <p>Closes any open tag.
 723   
      *
 724   
      **/
 725   
 
 726  4111
     public void printRaw(char[] buffer, int offset, int length)
 727   
     {
 728  4111
         if (buffer == null)
 729  0
             return;
 730   
 
 731  4111
         if (_openTag)
 732  309
             closeTag();
 733   
 
 734  4111
         _writer.write(buffer, offset, length);
 735   
     }
 736   
 
 737   
     /**
 738   
      * Prints output to the stream. No escaping of invalid GTML elements is done, which
 739   
      * makes this more effecient than <code>print()</code>. Does <em>nothing</em> 
 740   
      * if <code>value</code>
 741   
      * is null.
 742   
      *
 743   
      * <p>Closes any open tag.
 744   
      *
 745   
      **/
 746   
 
 747  200
     public void printRaw(String value)
 748   
     {
 749  200
         if (value == null)
 750  0
             return;
 751   
 
 752  200
         if (_openTag)
 753  8
             closeTag();
 754   
 
 755  200
         _writer.print(value);
 756   
     }
 757   
 
 758   
     /**
 759   
      *  Adds an element to the active element stack.
 760   
      *
 761   
      **/
 762   
 
 763  11159
     protected final void push(String name)
 764   
     {
 765  11159
         if (_activeElementStack == null)
 766  281
             _activeElementStack = new Stack();
 767   
 
 768  11159
         _activeElementStack.push(name);
 769   
 
 770  11159
         _depth++;
 771   
     }
 772   
 
 773   
     /**
 774   
      * Internal support for safe printing.  Ensures that all characters emitted
 775   
      * are safe: either valid GTML characters or GTML entities (named or numeric).
 776   
      **/
 777   
 
 778  15242
     private void safePrint(char[] data, int offset, int length, boolean isAttribute)
 779   
     {
 780  15242
         int safelength = 0;
 781  15242
         int start = offset;
 782   
 
 783  15242
         for (int i = 0; i < length; i++)
 784   
         {
 785  372326
             char ch = data[offset + i];
 786   
 
 787   
             // Ignore safe characters.  Outside an attribute, quotes
 788   
             // are ok and are not escaped.
 789   
 
 790  372326
             String translation;
 791  372326
             if (ch == '"' && !isAttribute)
 792  37
                 translation = null;
 793   
             else
 794  372289
                 translation = _translator.translate(ch);
 795   
             
 796  372326
             if (translation == null)
 797   
             {
 798  371922
                 safelength++;
 799  371922
                 continue;
 800   
             }
 801   
 
 802   
             // Write the safe stuff.
 803   
 
 804  404
             if (safelength > 0)
 805  349
                 _writer.write(data, start, safelength);
 806   
 
 807  404
             _writer.print(translation);
 808   
 
 809  404
             start = offset + i + 1;
 810  404
             safelength = 0;
 811   
         }
 812   
 
 813  15242
         if (safelength > 0)
 814  15111
             _writer.write(data, start, safelength);
 815   
     }
 816   
 
 817   
 }