| /* |
| * 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. |
| */ |
| /* |
| * $Id: SerializerTraceWriter.java 468654 2006-10-28 07:09:23Z minchau $ |
| */ |
| package org.apache.xml.serializer; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.Writer; |
| |
| /** |
| * This class wraps the real writer, it only purpose is to send |
| * CHARACTERTOSTREAM events to the trace listener. |
| * Each method immediately sends the call to the wrapped writer unchanged, but |
| * in addition it collects characters to be issued to a trace listener. |
| * |
| * In this way the trace |
| * listener knows what characters have been written to the output Writer. |
| * |
| * There may still be differences in what the trace events say is going to the |
| * output writer and what is really going there. These differences will be due |
| * to the fact that this class is UTF-8 encoding before emiting the trace event |
| * and the underlying writer may not be UTF-8 encoding. There may also be |
| * encoding differences. So the main pupose of this class is to provide a |
| * resonable facsimile of the true output. |
| * |
| * @xsl.usage internal |
| */ |
| final class SerializerTraceWriter extends Writer implements WriterChain |
| { |
| |
| /** The real writer to immediately write to. |
| * This reference may be null, in which case nothing is written out, but |
| * only the trace events are fired for output. |
| */ |
| private final java.io.Writer m_writer; |
| |
| /** The tracer to send events to */ |
| private final SerializerTrace m_tracer; |
| |
| /** The size of the internal buffer, just to keep too many |
| * events from being sent to the tracer |
| */ |
| private int buf_length; |
| |
| /** |
| * Internal buffer to collect the characters to go to the trace listener. |
| * |
| */ |
| private byte buf[]; |
| |
| /** |
| * How many bytes have been collected and still need to go to trace |
| * listener. |
| */ |
| private int count; |
| |
| /** |
| * Creates or replaces the internal buffer, and makes sure it has a few |
| * extra bytes slight overflow of the last UTF8 encoded character. |
| * @param size |
| */ |
| private void setBufferSize(int size) |
| { |
| buf = new byte[size + 3]; |
| buf_length = size; |
| count = 0; |
| } |
| |
| /** |
| * Constructor. |
| * If the writer passed in is null, then this SerializerTraceWriter will |
| * only signal trace events of what would have been written to that writer. |
| * If the writer passed in is not null then the trace events will mirror |
| * what is going to that writer. In this way tools, such as a debugger, can |
| * gather information on what is being written out. |
| * |
| * @param out the Writer to write to (possibly null) |
| * @param tracer the tracer to inform that characters are being written |
| */ |
| public SerializerTraceWriter(Writer out, SerializerTrace tracer) |
| { |
| m_writer = out; |
| m_tracer = tracer; |
| setBufferSize(1024); |
| } |
| |
| /** |
| * Flush out the collected characters by sending them to the trace |
| * listener. These characters are never written to the real writer |
| * (m_writer) because that has already happened with every method |
| * call. This method simple informs the listener of what has already |
| * happened. |
| * @throws IOException |
| */ |
| private void flushBuffer() throws IOException |
| { |
| |
| // Just for tracing purposes |
| if (count > 0) |
| { |
| char[] chars = new char[count]; |
| for(int i=0; i<count; i++) |
| chars[i] = (char) buf[i]; |
| |
| if (m_tracer != null) |
| m_tracer.fireGenerateEvent( |
| SerializerTrace.EVENTTYPE_OUTPUT_CHARACTERS, |
| chars, |
| 0, |
| chars.length); |
| |
| count = 0; |
| } |
| } |
| |
| /** |
| * Flush the internal buffer and flush the Writer |
| * @see java.io.Writer#flush() |
| */ |
| public void flush() throws java.io.IOException |
| { |
| // send to the real writer |
| if (m_writer != null) |
| m_writer.flush(); |
| |
| // from here on just for tracing purposes |
| flushBuffer(); |
| } |
| |
| /** |
| * Flush the internal buffer and close the Writer |
| * @see java.io.Writer#close() |
| */ |
| public void close() throws java.io.IOException |
| { |
| // send to the real writer |
| if (m_writer != null) |
| m_writer.close(); |
| |
| // from here on just for tracing purposes |
| flushBuffer(); |
| } |
| |
| |
| /** |
| * Write a single character. The character to be written is contained in |
| * the 16 low-order bits of the given integer value; the 16 high-order bits |
| * are ignored. |
| * |
| * <p> Subclasses that intend to support efficient single-character output |
| * should override this method. |
| * |
| * @param c int specifying a character to be written. |
| * @exception IOException If an I/O error occurs |
| */ |
| public void write(final int c) throws IOException |
| { |
| // send to the real writer |
| if (m_writer != null) |
| m_writer.write(c); |
| |
| // ---------- from here on just collect for tracing purposes |
| |
| /* If we are close to the end of the buffer then flush it. |
| * Remember the buffer can hold a few more characters than buf_length |
| */ |
| if (count >= buf_length) |
| flushBuffer(); |
| |
| if (c < 0x80) |
| { |
| buf[count++] = (byte) (c); |
| } |
| else if (c < 0x800) |
| { |
| buf[count++] = (byte) (0xc0 + (c >> 6)); |
| buf[count++] = (byte) (0x80 + (c & 0x3f)); |
| } |
| else |
| { |
| buf[count++] = (byte) (0xe0 + (c >> 12)); |
| buf[count++] = (byte) (0x80 + ((c >> 6) & 0x3f)); |
| buf[count++] = (byte) (0x80 + (c & 0x3f)); |
| } |
| } |
| |
| /** |
| * Write a portion of an array of characters. |
| * |
| * @param chars Array of characters |
| * @param start Offset from which to start writing characters |
| * @param length Number of characters to write |
| * |
| * @exception IOException If an I/O error occurs |
| * |
| * @throws java.io.IOException |
| */ |
| public void write(final char chars[], final int start, final int length) |
| throws java.io.IOException |
| { |
| // send to the real writer |
| if (m_writer != null) |
| m_writer.write(chars, start, length); |
| |
| // from here on just collect for tracing purposes |
| int lengthx3 = (length << 1) + length; |
| |
| if (lengthx3 >= buf_length) |
| { |
| |
| /* If the request length exceeds the size of the output buffer, |
| * flush the output buffer and make the buffer bigger to handle. |
| */ |
| |
| flushBuffer(); |
| setBufferSize(2 * lengthx3); |
| |
| } |
| |
| if (lengthx3 > buf_length - count) |
| { |
| flushBuffer(); |
| } |
| |
| final int n = length + start; |
| for (int i = start; i < n; i++) |
| { |
| final char c = chars[i]; |
| |
| if (c < 0x80) |
| buf[count++] = (byte) (c); |
| else if (c < 0x800) |
| { |
| buf[count++] = (byte) (0xc0 + (c >> 6)); |
| buf[count++] = (byte) (0x80 + (c & 0x3f)); |
| } |
| else |
| { |
| buf[count++] = (byte) (0xe0 + (c >> 12)); |
| buf[count++] = (byte) (0x80 + ((c >> 6) & 0x3f)); |
| buf[count++] = (byte) (0x80 + (c & 0x3f)); |
| } |
| } |
| |
| } |
| |
| /** |
| * Write a string. |
| * |
| * @param s String to be written |
| * |
| * @exception IOException If an I/O error occurs |
| */ |
| public void write(final String s) throws IOException |
| { |
| // send to the real writer |
| if (m_writer != null) |
| m_writer.write(s); |
| |
| // from here on just collect for tracing purposes |
| final int length = s.length(); |
| |
| // We multiply the length by three since this is the maximum length |
| // of the characters that we can put into the buffer. It is possible |
| // for each Unicode character to expand to three bytes. |
| |
| int lengthx3 = (length << 1) + length; |
| |
| if (lengthx3 >= buf_length) |
| { |
| |
| /* If the request length exceeds the size of the output buffer, |
| * flush the output buffer and make the buffer bigger to handle. |
| */ |
| |
| flushBuffer(); |
| setBufferSize(2 * lengthx3); |
| } |
| |
| if (lengthx3 > buf_length - count) |
| { |
| flushBuffer(); |
| } |
| |
| for (int i = 0; i < length; i++) |
| { |
| final char c = s.charAt(i); |
| |
| if (c < 0x80) |
| buf[count++] = (byte) (c); |
| else if (c < 0x800) |
| { |
| buf[count++] = (byte) (0xc0 + (c >> 6)); |
| buf[count++] = (byte) (0x80 + (c & 0x3f)); |
| } |
| else |
| { |
| buf[count++] = (byte) (0xe0 + (c >> 12)); |
| buf[count++] = (byte) (0x80 + ((c >> 6) & 0x3f)); |
| buf[count++] = (byte) (0x80 + (c & 0x3f)); |
| } |
| } |
| } |
| |
| /** |
| * Get the writer that this one directly wraps. |
| */ |
| public Writer getWriter() |
| { |
| return m_writer; |
| } |
| |
| /** |
| * Get the OutputStream that is the at the end of the |
| * chain of writers. |
| */ |
| public OutputStream getOutputStream() |
| { |
| OutputStream retval = null; |
| if (m_writer instanceof WriterChain) |
| retval = ((WriterChain) m_writer).getOutputStream(); |
| return retval; |
| } |
| } |