| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.jasper.runtime; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.Writer; |
| |
| import jakarta.servlet.ServletResponse; |
| import jakarta.servlet.jsp.JspWriter; |
| |
| import org.apache.jasper.Constants; |
| import org.apache.jasper.compiler.Localizer; |
| |
| /** |
| * Write text to a character-output stream, buffering characters to provide efficient writing of single characters, |
| * arrays, and strings. Provide support for discarding the output that has been buffered. This needs revisiting when the |
| * buffering problems in the JSP spec are fixed -akv. |
| */ |
| public class JspWriterImpl extends JspWriter { |
| |
| private Writer out; |
| private ServletResponse response; |
| private char[] cb; |
| private int nextChar; |
| private boolean flushed = false; |
| private boolean closed = false; |
| |
| /** |
| * Default constructor. |
| */ |
| public JspWriterImpl() { |
| super(Constants.DEFAULT_BUFFER_SIZE, true); |
| } |
| |
| /** |
| * Create a new buffered character-output stream that uses an output buffer of the given size. |
| * |
| * @param response A Servlet Response |
| * @param sz Output-buffer size, a positive integer |
| * @param autoFlush <code>true</code> to automatically flush on buffer full, <code>false</code> to throw an overflow |
| * exception in that case |
| * |
| * @exception IllegalArgumentException If sz is < 0 |
| */ |
| public JspWriterImpl(ServletResponse response, int sz, boolean autoFlush) { |
| super(sz, autoFlush); |
| if (sz < 0) { |
| throw new IllegalArgumentException(Localizer.getMessage("jsp.error.negativeBufferSize")); |
| } |
| this.response = response; |
| cb = sz == 0 ? null : new char[sz]; |
| nextChar = 0; |
| } |
| |
| void init(ServletResponse response, int sz, boolean autoFlush) { |
| this.response = response; |
| if (sz > 0 && (cb == null || sz > cb.length)) { |
| cb = new char[sz]; |
| } |
| nextChar = 0; |
| this.autoFlush = autoFlush; |
| this.bufferSize = sz; |
| } |
| |
| /** |
| * Package-level access |
| */ |
| void recycle() { |
| flushed = false; |
| closed = false; |
| out = null; |
| nextChar = 0; |
| response = null; |
| } |
| |
| /** |
| * Flush the output buffer to the underlying character stream, without flushing the stream itself. This method is |
| * non-private only so that it may be invoked by PrintStream. |
| * |
| * @throws IOException Error writing buffered data |
| */ |
| protected final void flushBuffer() throws IOException { |
| if (bufferSize == 0) { |
| return; |
| } |
| flushed = true; |
| ensureOpen(); |
| if (nextChar == 0) { |
| return; |
| } |
| initOut(); |
| out.write(cb, 0, nextChar); |
| nextChar = 0; |
| } |
| |
| private void initOut() throws IOException { |
| if (out == null) { |
| try { |
| out = response.getWriter(); |
| } catch (IllegalStateException e) { |
| /* |
| * At some point in the processing something (most likely the default servlet as the target of a |
| * <jsp:forward ... /> action) wrote directly to the OutputStream rather than the Writer. Wrap the |
| * OutputStream in a Writer so the JSP engine can use the Writer it is expecting to use. |
| */ |
| out = new PrintWriter(response.getOutputStream()); |
| } |
| } |
| } |
| |
| @Override |
| public final void clear() throws IOException { |
| if ((bufferSize == 0) && (out != null)) { |
| // clear() is illegal after any unbuffered output (JSP.5.5) |
| throw new IllegalStateException(Localizer.getMessage("jsp.error.ise_on_clear")); |
| } |
| if (flushed) { |
| throw new IOException(Localizer.getMessage("jsp.error.attempt_to_clear_flushed_buffer")); |
| } |
| ensureOpen(); |
| nextChar = 0; |
| } |
| |
| @Override |
| public void clearBuffer() throws IOException { |
| if (bufferSize == 0) { |
| throw new IllegalStateException(Localizer.getMessage("jsp.error.ise_on_clear")); |
| } |
| ensureOpen(); |
| nextChar = 0; |
| } |
| |
| private void bufferOverflow() throws IOException { |
| throw new IOException(Localizer.getMessage("jsp.error.overflow")); |
| } |
| |
| @Override |
| public void flush() throws IOException { |
| flushBuffer(); |
| if (out != null) { |
| out.flush(); |
| } |
| } |
| |
| @Override |
| public void close() throws IOException { |
| if (response == null || closed) { |
| // multiple calls to close is OK |
| return; |
| } |
| flush(); |
| if (out != null) { |
| out.close(); |
| } |
| out = null; |
| closed = true; |
| } |
| |
| @Override |
| public int getRemaining() { |
| return bufferSize - nextChar; |
| } |
| |
| /** check to make sure that the stream has not been closed */ |
| private void ensureOpen() throws IOException { |
| if (response == null || closed) { |
| throw new IOException(Localizer.getMessage("jsp.error.stream.closed")); |
| } |
| } |
| |
| |
| @Override |
| public void write(int c) throws IOException { |
| ensureOpen(); |
| if (bufferSize == 0) { |
| initOut(); |
| out.write(c); |
| } else { |
| if (nextChar >= bufferSize) { |
| if (autoFlush) { |
| flushBuffer(); |
| } else { |
| bufferOverflow(); |
| } |
| } |
| cb[nextChar++] = (char) c; |
| } |
| } |
| |
| /** |
| * Our own little min method, to avoid loading java.lang.Math if we've run out of file descriptors, and we're trying |
| * to print a stack trace. |
| */ |
| private static int min(int a, int b) { |
| return Math.min(a, b); |
| } |
| |
| @Override |
| public void write(char[] cbuf, int off, int len) throws IOException { |
| ensureOpen(); |
| |
| if (bufferSize == 0) { |
| initOut(); |
| out.write(cbuf, off, len); |
| return; |
| } |
| |
| if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) { |
| throw new IndexOutOfBoundsException(); |
| } else if (len == 0) { |
| return; |
| } |
| |
| if (len >= bufferSize) { |
| /* |
| * If the request length exceeds the size of the output buffer, flush the buffer and then write the data |
| * directly. In this way buffered streams will cascade harmlessly. |
| */ |
| if (autoFlush) { |
| flushBuffer(); |
| } else { |
| bufferOverflow(); |
| } |
| initOut(); |
| out.write(cbuf, off, len); |
| return; |
| } |
| |
| int b = off, t = off + len; |
| while (b < t) { |
| int d = min(bufferSize - nextChar, t - b); |
| System.arraycopy(cbuf, b, cb, nextChar, d); |
| b += d; |
| nextChar += d; |
| if (nextChar >= bufferSize) { |
| if (autoFlush) { |
| flushBuffer(); |
| } else { |
| bufferOverflow(); |
| } |
| } |
| } |
| |
| } |
| |
| @Override |
| public void write(char[] buf) throws IOException { |
| write(buf, 0, buf.length); |
| } |
| |
| @Override |
| public void write(String s, int off, int len) throws IOException { |
| ensureOpen(); |
| if (bufferSize == 0) { |
| initOut(); |
| out.write(s, off, len); |
| return; |
| } |
| int b = off, t = off + len; |
| while (b < t) { |
| int d = min(bufferSize - nextChar, t - b); |
| s.getChars(b, b + d, cb, nextChar); |
| b += d; |
| nextChar += d; |
| if (nextChar >= bufferSize) { |
| if (autoFlush) { |
| flushBuffer(); |
| } else { |
| bufferOverflow(); |
| } |
| } |
| } |
| } |
| |
| |
| @Override |
| public void newLine() throws IOException { |
| write(System.lineSeparator()); |
| } |
| |
| |
| /* Methods that do not terminate lines */ |
| |
| @Override |
| public void print(boolean b) throws IOException { |
| write(b ? "true" : "false"); |
| } |
| |
| @Override |
| public void print(char c) throws IOException { |
| write(String.valueOf(c)); |
| } |
| |
| @Override |
| public void print(int i) throws IOException { |
| write(String.valueOf(i)); |
| } |
| |
| @Override |
| public void print(long l) throws IOException { |
| write(String.valueOf(l)); |
| } |
| |
| @Override |
| public void print(float f) throws IOException { |
| write(String.valueOf(f)); |
| } |
| |
| @Override |
| public void print(double d) throws IOException { |
| write(String.valueOf(d)); |
| } |
| |
| @Override |
| public void print(char[] s) throws IOException { |
| write(s); |
| } |
| |
| @Override |
| public void print(String s) throws IOException { |
| if (s == null) { |
| s = "null"; |
| } |
| write(s); |
| } |
| |
| @Override |
| public void print(Object obj) throws IOException { |
| write(String.valueOf(obj)); |
| } |
| |
| /* Methods that do terminate lines */ |
| |
| @Override |
| public void println() throws IOException { |
| newLine(); |
| } |
| |
| @Override |
| public void println(boolean x) throws IOException { |
| print(x); |
| println(); |
| } |
| |
| @Override |
| public void println(char x) throws IOException { |
| print(x); |
| println(); |
| } |
| |
| @Override |
| public void println(int x) throws IOException { |
| print(x); |
| println(); |
| } |
| |
| @Override |
| public void println(long x) throws IOException { |
| print(x); |
| println(); |
| } |
| |
| @Override |
| public void println(float x) throws IOException { |
| print(x); |
| println(); |
| } |
| |
| @Override |
| public void println(double x) throws IOException { |
| print(x); |
| println(); |
| } |
| |
| @Override |
| public void println(char[] x) throws IOException { |
| print(x); |
| println(); |
| } |
| |
| @Override |
| public void println(String x) throws IOException { |
| print(x); |
| println(); |
| } |
| |
| @Override |
| public void println(Object x) throws IOException { |
| print(x); |
| println(); |
| } |
| |
| } |