View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.struts2.jasper.runtime;
19  
20  import org.apache.struts2.jasper.Constants;
21  
22  import javax.servlet.jsp.JspWriter;
23  import javax.servlet.jsp.tagext.BodyContent;
24  import java.io.CharArrayReader;
25  import java.io.IOException;
26  import java.io.Reader;
27  import java.io.Writer;
28  
29  /***
30   * Write text to a character-output stream, buffering characters so as
31   * to provide for the efficient writing of single characters, arrays,
32   * and strings.
33   * <p/>
34   * Provide support for discarding for the output that has been buffered.
35   *
36   * @author Rajiv Mordani
37   * @author Jan Luehe
38   */
39  public class BodyContentImpl extends BodyContent {
40  
41      private static final String LINE_SEPARATOR =
42              System.getProperty("line.separator");
43      private static final boolean LIMIT_BUFFER =
44              Boolean.valueOf(System.getProperty("org.apache.struts2.jasper.runtime.BodyContentImpl.LIMIT_BUFFER", "false")).booleanValue();
45  
46      private char[] cb;
47      private int nextChar;
48      private boolean closed;
49  
50      // Enclosed writer to which any output is written
51      private Writer writer;
52  
53      // See comment in setWriter()
54      private int bufferSizeSave;
55  
56      /***
57       * Constructor.
58       */
59      public BodyContentImpl(JspWriter enclosingWriter) {
60          super(enclosingWriter);
61          bufferSize = Constants.DEFAULT_TAG_BUFFER_SIZE;
62          cb = new char[bufferSize];
63          nextChar = 0;
64          closed = false;
65      }
66  
67      /***
68       * Write a single character.
69       */
70      public void write(int c) throws IOException {
71          if (writer != null) {
72              writer.write(c);
73          } else {
74              ensureOpen();
75              if (nextChar >= bufferSize) {
76                  reAllocBuff(1);
77              }
78              cb[nextChar++] = (char) c;
79          }
80      }
81  
82      /***
83       * Write a portion of an array of characters.
84       * <p/>
85       * <p> Ordinarily this method stores characters from the given array into
86       * this stream's buffer, flushing the buffer to the underlying stream as
87       * needed.  If the requested length is at least as large as the buffer,
88       * however, then this method will flush the buffer and write the characters
89       * directly to the underlying stream.  Thus redundant
90       * <code>DiscardableBufferedWriter</code>s will not copy data
91       * unnecessarily.
92       *
93       * @param cbuf A character array
94       * @param off  Offset from which to start reading characters
95       * @param len  Number of characters to write
96       */
97      public void write(char[] cbuf, int off, int len) throws IOException {
98          if (writer != null) {
99              writer.write(cbuf, off, len);
100         } else {
101             ensureOpen();
102 
103             if ((off < 0) || (off > cbuf.length) || (len < 0) ||
104                     ((off + len) > cbuf.length) || ((off + len) < 0)) {
105                 throw new IndexOutOfBoundsException();
106             } else if (len == 0) {
107                 return;
108             }
109 
110             if (len >= bufferSize - nextChar)
111                 reAllocBuff(len);
112 
113             System.arraycopy(cbuf, off, cb, nextChar, len);
114             nextChar += len;
115         }
116     }
117 
118     /***
119      * Write an array of characters.  This method cannot be inherited from the
120      * Writer class because it must suppress I/O exceptions.
121      */
122     public void write(char[] buf) throws IOException {
123         if (writer != null) {
124             writer.write(buf);
125         } else {
126             write(buf, 0, buf.length);
127         }
128     }
129 
130     /***
131      * Write a portion of a String.
132      *
133      * @param s   String to be written
134      * @param off Offset from which to start reading characters
135      * @param len Number of characters to be written
136      */
137     public void write(String s, int off, int len) throws IOException {
138         if (writer != null) {
139             writer.write(s, off, len);
140         } else {
141             ensureOpen();
142             if (len >= bufferSize - nextChar)
143                 reAllocBuff(len);
144 
145             s.getChars(off, off + len, cb, nextChar);
146             nextChar += len;
147         }
148     }
149 
150     /***
151      * Write a string.  This method cannot be inherited from the Writer class
152      * because it must suppress I/O exceptions.
153      */
154     public void write(String s) throws IOException {
155         if (writer != null) {
156             writer.write(s);
157         } else {
158             write(s, 0, s.length());
159         }
160     }
161 
162     /***
163      * Write a line separator.  The line separator string is defined by the
164      * system property <tt>line.separator</tt>, and is not necessarily a single
165      * newline ('\n') character.
166      *
167      * @throws IOException If an I/O error occurs
168      */
169     public void newLine() throws IOException {
170         if (writer != null) {
171             writer.write(LINE_SEPARATOR);
172         } else {
173             write(LINE_SEPARATOR);
174         }
175     }
176 
177     /***
178      * Print a boolean value.  The string produced by <code>{@link
179      * java.lang.String#valueOf(boolean)}</code> is translated into bytes
180      * according to the platform's default character encoding, and these bytes
181      * are written in exactly the manner of the <code>{@link
182      * #write(int)}</code> method.
183      *
184      * @param b The <code>boolean</code> to be printed
185      * @throws IOException
186      */
187     public void print(boolean b) throws IOException {
188         if (writer != null) {
189             writer.write(b ? "true" : "false");
190         } else {
191             write(b ? "true" : "false");
192         }
193     }
194 
195     /***
196      * Print a character.  The character is translated into one or more bytes
197      * according to the platform's default character encoding, and these bytes
198      * are written in exactly the manner of the <code>{@link
199      * #write(int)}</code> method.
200      *
201      * @param c The <code>char</code> to be printed
202      * @throws IOException
203      */
204     public void print(char c) throws IOException {
205         if (writer != null) {
206             writer.write(String.valueOf(c));
207         } else {
208             write(String.valueOf(c));
209         }
210     }
211 
212     /***
213      * Print an integer.  The string produced by <code>{@link
214      * java.lang.String#valueOf(int)}</code> is translated into bytes according
215      * to the platform's default character encoding, and these bytes are
216      * written in exactly the manner of the <code>{@link #write(int)}</code>
217      * method.
218      *
219      * @param i The <code>int</code> to be printed
220      * @throws IOException
221      */
222     public void print(int i) throws IOException {
223         if (writer != null) {
224             writer.write(String.valueOf(i));
225         } else {
226             write(String.valueOf(i));
227         }
228     }
229 
230     /***
231      * Print a long integer.  The string produced by <code>{@link
232      * java.lang.String#valueOf(long)}</code> is translated into bytes
233      * according to the platform's default character encoding, and these bytes
234      * are written in exactly the manner of the
235      * <code>{@link #write(int)}</code> method.
236      *
237      * @param l The <code>long</code> to be printed
238      * @throws IOException
239      */
240     public void print(long l) throws IOException {
241         if (writer != null) {
242             writer.write(String.valueOf(l));
243         } else {
244             write(String.valueOf(l));
245         }
246     }
247 
248     /***
249      * Print a floating-point number.  The string produced by <code>{@link
250      * java.lang.String#valueOf(float)}</code> is translated into bytes
251      * according to the platform's default character encoding, and these bytes
252      * are written in exactly the manner of the
253      * <code>{@link #write(int)}</code> method.
254      *
255      * @param f The <code>float</code> to be printed
256      * @throws IOException
257      */
258     public void print(float f) throws IOException {
259         if (writer != null) {
260             writer.write(String.valueOf(f));
261         } else {
262             write(String.valueOf(f));
263         }
264     }
265 
266     /***
267      * Print a double-precision floating-point number.  The string produced by
268      * <code>{@link java.lang.String#valueOf(double)}</code> is translated into
269      * bytes according to the platform's default character encoding, and these
270      * bytes are written in exactly the manner of the <code>{@link
271      * #write(int)}</code> method.
272      *
273      * @param d The <code>double</code> to be printed
274      * @throws IOException
275      */
276     public void print(double d) throws IOException {
277         if (writer != null) {
278             writer.write(String.valueOf(d));
279         } else {
280             write(String.valueOf(d));
281         }
282     }
283 
284     /***
285      * Print an array of characters.  The characters are converted into bytes
286      * according to the platform's default character encoding, and these bytes
287      * are written in exactly the manner of the
288      * <code>{@link #write(int)}</code> method.
289      *
290      * @param s The array of chars to be printed
291      * @throws NullPointerException If <code>s</code> is <code>null</code>
292      * @throws IOException
293      */
294     public void print(char[] s) throws IOException {
295         if (writer != null) {
296             writer.write(s);
297         } else {
298             write(s);
299         }
300     }
301 
302     /***
303      * Print a string.  If the argument is <code>null</code> then the string
304      * <code>"null"</code> is printed.  Otherwise, the string's characters are
305      * converted into bytes according to the platform's default character
306      * encoding, and these bytes are written in exactly the manner of the
307      * <code>{@link #write(int)}</code> method.
308      *
309      * @param s The <code>String</code> to be printed
310      * @throws IOException
311      */
312     public void print(String s) throws IOException {
313         if (s == null) s = "null";
314         if (writer != null) {
315             writer.write(s);
316         } else {
317             write(s);
318         }
319     }
320 
321     /***
322      * Print an object.  The string produced by the <code>{@link
323      * java.lang.String#valueOf(Object)}</code> method is translated into bytes
324      * according to the platform's default character encoding, and these bytes
325      * are written in exactly the manner of the
326      * <code>{@link #write(int)}</code> method.
327      *
328      * @param obj The <code>Object</code> to be printed
329      * @throws IOException
330      */
331     public void print(Object obj) throws IOException {
332         if (writer != null) {
333             writer.write(String.valueOf(obj));
334         } else {
335             write(String.valueOf(obj));
336         }
337     }
338 
339     /***
340      * Terminate the current line by writing the line separator string.  The
341      * line separator string is defined by the system property
342      * <code>line.separator</code>, and is not necessarily a single newline
343      * character (<code>'\n'</code>).
344      *
345      * @throws IOException
346      */
347     public void println() throws IOException {
348         newLine();
349     }
350 
351     /***
352      * Print a boolean value and then terminate the line.  This method behaves
353      * as though it invokes <code>{@link #print(boolean)}</code> and then
354      * <code>{@link #println()}</code>.
355      *
356      * @throws IOException
357      */
358     public void println(boolean x) throws IOException {
359         print(x);
360         println();
361     }
362 
363     /***
364      * Print a character and then terminate the line.  This method behaves as
365      * though it invokes <code>{@link #print(char)}</code> and then
366      * <code>{@link #println()}</code>.
367      *
368      * @throws IOException
369      */
370     public void println(char x) throws IOException {
371         print(x);
372         println();
373     }
374 
375     /***
376      * Print an integer and then terminate the line.  This method behaves as
377      * though it invokes <code>{@link #print(int)}</code> and then
378      * <code>{@link #println()}</code>.
379      *
380      * @throws IOException
381      */
382     public void println(int x) throws IOException {
383         print(x);
384         println();
385     }
386 
387     /***
388      * Print a long integer and then terminate the line.  This method behaves
389      * as though it invokes <code>{@link #print(long)}</code> and then
390      * <code>{@link #println()}</code>.
391      *
392      * @throws IOException
393      */
394     public void println(long x) throws IOException {
395         print(x);
396         println();
397     }
398 
399     /***
400      * Print a floating-point number and then terminate the line.  This method
401      * behaves as though it invokes <code>{@link #print(float)}</code> and then
402      * <code>{@link #println()}</code>.
403      *
404      * @throws IOException
405      */
406     public void println(float x) throws IOException {
407         print(x);
408         println();
409     }
410 
411     /***
412      * Print a double-precision floating-point number and then terminate the
413      * line.  This method behaves as though it invokes <code>{@link
414      * #print(double)}</code> and then <code>{@link #println()}</code>.
415      *
416      * @throws IOException
417      */
418     public void println(double x) throws IOException {
419         print(x);
420         println();
421     }
422 
423     /***
424      * Print an array of characters and then terminate the line.  This method
425      * behaves as though it invokes <code>{@link #print(char[])}</code> and
426      * then <code>{@link #println()}</code>.
427      *
428      * @throws IOException
429      */
430     public void println(char x[]) throws IOException {
431         print(x);
432         println();
433     }
434 
435     /***
436      * Print a String and then terminate the line.  This method behaves as
437      * though it invokes <code>{@link #print(String)}</code> and then
438      * <code>{@link #println()}</code>.
439      *
440      * @throws IOException
441      */
442     public void println(String x) throws IOException {
443         print(x);
444         println();
445     }
446 
447     /***
448      * Print an Object and then terminate the line.  This method behaves as
449      * though it invokes <code>{@link #print(Object)}</code> and then
450      * <code>{@link #println()}</code>.
451      *
452      * @throws IOException
453      */
454     public void println(Object x) throws IOException {
455         print(x);
456         println();
457     }
458 
459     /***
460      * Clear the contents of the buffer. If the buffer has been already
461      * been flushed then the clear operation shall throw an IOException
462      * to signal the fact that some data has already been irrevocably
463      * written to the client response stream.
464      *
465      * @throws IOException If an I/O error occurs
466      */
467     public void clear() throws IOException {
468         if (writer != null) {
469             throw new IOException();
470         } else {
471             nextChar = 0;
472             if (LIMIT_BUFFER && (cb.length > Constants.DEFAULT_TAG_BUFFER_SIZE)) {
473                 bufferSize = Constants.DEFAULT_TAG_BUFFER_SIZE;
474                 cb = new char[bufferSize];
475             }
476         }
477     }
478 
479     /***
480      * Clears the current contents of the buffer. Unlike clear(), this
481      * mehtod will not throw an IOException if the buffer has already been
482      * flushed. It merely clears the current content of the buffer and
483      * returns.
484      *
485      * @throws IOException If an I/O error occurs
486      */
487     public void clearBuffer() throws IOException {
488         if (writer == null) {
489             this.clear();
490         }
491     }
492 
493     /***
494      * Close the stream, flushing it first.  Once a stream has been closed,
495      * further write() or flush() invocations will cause an IOException to be
496      * thrown.  Closing a previously-closed stream, however, has no effect.
497      *
498      * @throws IOException If an I/O error occurs
499      */
500     public void close() throws IOException {
501         if (writer != null) {
502             writer.close();
503         } else {
504             closed = true;
505         }
506     }
507 
508     /***
509      * @return the number of bytes unused in the buffer
510      */
511     public int getRemaining() {
512         return (writer == null) ? bufferSize - nextChar : 0;
513     }
514 
515     /***
516      * Return the value of this BodyJspWriter as a Reader.
517      * Note: this is after evaluation!!  There are no scriptlets,
518      * etc in this stream.
519      *
520      * @return the value of this BodyJspWriter as a Reader
521      */
522     public Reader getReader() {
523         return (writer == null) ? new CharArrayReader(cb, 0, nextChar) : null;
524     }
525 
526     /***
527      * Return the value of the BodyJspWriter as a String.
528      * Note: this is after evaluation!!  There are no scriptlets,
529      * etc in this stream.
530      *
531      * @return the value of the BodyJspWriter as a String
532      */
533     public String getString() {
534         return (writer == null) ? new String(cb, 0, nextChar) : null;
535     }
536 
537     /***
538      * Write the contents of this BodyJspWriter into a Writer.
539      * Subclasses are likely to do interesting things with the
540      * implementation so some things are extra efficient.
541      *
542      * @param out The writer into which to place the contents of this body
543      *            evaluation
544      */
545     public void writeOut(Writer out) throws IOException {
546         if (writer == null) {
547             out.write(cb, 0, nextChar);
548             // Flush not called as the writer passed could be a BodyContent and
549             // it doesn't allow to flush.
550         }
551     }
552 
553     /***
554      * Sets the writer to which all output is written.
555      */
556     void setWriter(Writer writer) {
557         this.writer = writer;
558         closed = false;
559         if (writer != null) {
560             // According to the spec, the JspWriter returned by 
561             // JspContext.pushBody(java.io.Writer writer) must behave as
562             // though it were unbuffered. This means that its getBufferSize()
563             // must always return 0. The implementation of
564             // JspWriter.getBufferSize() returns the value of JspWriter's
565             // 'bufferSize' field, which is inherited by this class. 
566             // Therefore, we simply save the current 'bufferSize' (so we can 
567             // later restore it should this BodyContentImpl ever be reused by
568             // a call to PageContext.pushBody()) before setting it to 0.
569             if (bufferSize != 0) {
570                 bufferSizeSave = bufferSize;
571                 bufferSize = 0;
572             }
573         } else {
574             bufferSize = bufferSizeSave;
575             clearBody();
576         }
577     }
578 
579     private void ensureOpen() throws IOException {
580         if (closed) throw new IOException("Stream closed");
581     }
582 
583     /***
584      * Reallocates buffer since the spec requires it to be unbounded.
585      */
586     private void reAllocBuff(int len) {
587 
588         if (bufferSize + len <= cb.length) {
589             bufferSize = cb.length;
590             return;
591         }
592 
593         if (len < cb.length) {
594             len = cb.length;
595         }
596 
597         bufferSize = cb.length + len;
598         char[] tmp = new char[bufferSize];
599 
600         System.arraycopy(cb, 0, tmp, 0, cb.length);
601         cb = tmp;
602         tmp = null;
603 
604     }
605 
606 
607 }