001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: SerializerTraceWriter.java 468654 2006-10-28 07:09:23Z minchau $
020     */
021    package org.apache.xml.serializer;
022    
023    import java.io.IOException;
024    import java.io.OutputStream;
025    import java.io.Writer;
026    
027    /**
028     * This class wraps the real writer, it only purpose is to send
029     * CHARACTERTOSTREAM events to the trace listener. 
030     * Each method immediately sends the call to the wrapped writer unchanged, but
031     * in addition it collects characters to be issued to a trace listener.
032     * 
033     * In this way the trace
034     * listener knows what characters have been written to the output Writer.
035     * 
036     * There may still be differences in what the trace events say is going to the
037     * output writer and what is really going there. These differences will be due
038     * to the fact that this class is UTF-8 encoding before emiting the trace event
039     * and the underlying writer may not be UTF-8 encoding. There may also be
040     * encoding differences.  So the main pupose of this class is to provide a
041     * resonable facsimile of the true output.
042     * 
043     * @xsl.usage internal
044     */
045    final class SerializerTraceWriter extends Writer implements WriterChain
046    {
047    
048        /** The real writer to immediately write to.
049         * This reference may be null, in which case nothing is written out, but
050         * only the trace events are fired for output.
051         */
052        private final java.io.Writer m_writer;
053    
054        /** The tracer to send events to */
055        private final SerializerTrace m_tracer;
056    
057        /** The size of the internal buffer, just to keep too many
058         * events from being sent to the tracer
059         */
060        private int buf_length;
061    
062        /**
063         * Internal buffer to collect the characters to go to the trace listener.
064         * 
065         */
066        private byte buf[];
067    
068        /**
069         * How many bytes have been collected and still need to go to trace
070         * listener.
071         */
072        private int count;
073    
074        /**
075         * Creates or replaces the internal buffer, and makes sure it has a few
076         * extra bytes slight overflow of the last UTF8 encoded character.
077         * @param size
078         */
079        private void setBufferSize(int size)
080        {
081            buf = new byte[size + 3];
082            buf_length = size;
083            count = 0;
084        }
085    
086        /**
087         * Constructor.
088         * If the writer passed in is null, then this SerializerTraceWriter will
089         * only signal trace events of what would have been written to that writer.
090         * If the writer passed in is not null then the trace events will mirror
091         * what is going to that writer. In this way tools, such as a debugger, can
092         * gather information on what is being written out.
093         * 
094         * @param out the Writer to write to (possibly null)
095         * @param tracer the tracer to inform that characters are being written
096         */
097        public SerializerTraceWriter(Writer out, SerializerTrace tracer)
098        {
099            m_writer = out;
100            m_tracer = tracer;
101            setBufferSize(1024);
102        }
103    
104        /**
105         * Flush out the collected characters by sending them to the trace
106         * listener.  These characters are never written to the real writer
107         * (m_writer) because that has already happened with every method
108         * call. This method simple informs the listener of what has already
109         * happened.
110         * @throws IOException
111         */
112        private void flushBuffer() throws IOException
113        {
114    
115            // Just for tracing purposes
116            if (count > 0)
117            {
118                char[] chars = new char[count];
119                for(int i=0; i<count; i++)
120                    chars[i] = (char) buf[i];
121    
122                if (m_tracer != null)
123                    m_tracer.fireGenerateEvent(
124                        SerializerTrace.EVENTTYPE_OUTPUT_CHARACTERS,
125                        chars,
126                        0,
127                        chars.length);
128    
129                count = 0;
130            }
131        }
132    
133        /**
134         * Flush the internal buffer and flush the Writer
135         * @see java.io.Writer#flush()
136         */
137        public void flush() throws java.io.IOException
138        {
139            // send to the real writer
140            if (m_writer != null)
141                m_writer.flush();
142    
143            // from here on just for tracing purposes
144            flushBuffer();
145        }
146    
147        /**
148         * Flush the internal buffer and close the Writer
149         * @see java.io.Writer#close()
150         */
151        public void close() throws java.io.IOException
152        {
153            // send to the real writer
154            if (m_writer != null)   
155                m_writer.close();
156    
157            // from here on just for tracing purposes
158            flushBuffer();
159        }
160    
161    
162        /**
163         * Write a single character.  The character to be written is contained in
164         * the 16 low-order bits of the given integer value; the 16 high-order bits
165         * are ignored.
166         *
167         * <p> Subclasses that intend to support efficient single-character output
168         * should override this method.
169         *
170         * @param c  int specifying a character to be written.
171         * @exception  IOException  If an I/O error occurs
172         */
173        public void write(final int c) throws IOException
174        {
175            // send to the real writer
176            if (m_writer != null)
177                m_writer.write(c);
178    
179            // ---------- from here on just collect for tracing purposes
180    
181            /* If we are close to the end of the buffer then flush it.
182             * Remember the buffer can hold a few more characters than buf_length
183             */
184            if (count >= buf_length)
185                flushBuffer();
186    
187            if (c < 0x80)
188            {
189                buf[count++] = (byte) (c);
190            }
191            else if (c < 0x800)
192            {
193                buf[count++] = (byte) (0xc0 + (c >> 6));
194                buf[count++] = (byte) (0x80 + (c & 0x3f));
195            }
196            else
197            {
198                buf[count++] = (byte) (0xe0 + (c >> 12));
199                buf[count++] = (byte) (0x80 + ((c >> 6) & 0x3f));
200                buf[count++] = (byte) (0x80 + (c & 0x3f));
201            }
202        }
203    
204        /**
205         * Write a portion of an array of characters.
206         *
207         * @param  chars  Array of characters
208         * @param  start   Offset from which to start writing characters
209         * @param  length   Number of characters to write
210         *
211         * @exception  IOException  If an I/O error occurs
212         *
213         * @throws java.io.IOException
214         */
215        public void write(final char chars[], final int start, final int length)
216            throws java.io.IOException
217        {
218            // send to the real writer
219            if (m_writer != null)
220                m_writer.write(chars, start, length);
221    
222            // from here on just collect for tracing purposes
223            int lengthx3 = (length << 1) + length;
224    
225            if (lengthx3 >= buf_length)
226            {
227    
228                /* If the request length exceeds the size of the output buffer,
229                  * flush the output buffer and make the buffer bigger to handle.
230                  */
231    
232                flushBuffer();
233                setBufferSize(2 * lengthx3);
234    
235            }
236    
237            if (lengthx3 > buf_length - count)
238            {
239                flushBuffer();
240            }
241    
242            final int n = length + start;
243            for (int i = start; i < n; i++)
244            {
245                final char c = chars[i];
246    
247                if (c < 0x80)
248                    buf[count++] = (byte) (c);
249                else if (c < 0x800)
250                {
251                    buf[count++] = (byte) (0xc0 + (c >> 6));
252                    buf[count++] = (byte) (0x80 + (c & 0x3f));
253                }
254                else
255                {
256                    buf[count++] = (byte) (0xe0 + (c >> 12));
257                    buf[count++] = (byte) (0x80 + ((c >> 6) & 0x3f));
258                    buf[count++] = (byte) (0x80 + (c & 0x3f));
259                }
260            }
261    
262        }
263    
264        /**
265         * Write a string.
266         *
267         * @param  s  String to be written
268         *
269         * @exception  IOException  If an I/O error occurs
270         */
271        public void write(final String s) throws IOException
272        {
273            // send to the real writer
274            if (m_writer != null)
275                m_writer.write(s);
276    
277            // from here on just collect for tracing purposes
278            final int length = s.length();
279    
280            // We multiply the length by three since this is the maximum length
281            // of the characters that we can put into the buffer.  It is possible
282            // for each Unicode character to expand to three bytes.
283    
284            int lengthx3 = (length << 1) + length;
285    
286            if (lengthx3 >= buf_length)
287            {
288    
289                /* If the request length exceeds the size of the output buffer,
290                  * flush the output buffer and make the buffer bigger to handle.
291                  */
292    
293                flushBuffer();
294                setBufferSize(2 * lengthx3);
295            }
296    
297            if (lengthx3 > buf_length - count)
298            {
299                flushBuffer();
300            }
301    
302            for (int i = 0; i < length; i++)
303            {
304                final char c = s.charAt(i);
305    
306                if (c < 0x80)
307                    buf[count++] = (byte) (c);
308                else if (c < 0x800)
309                {
310                    buf[count++] = (byte) (0xc0 + (c >> 6));
311                    buf[count++] = (byte) (0x80 + (c & 0x3f));
312                }
313                else
314                {
315                    buf[count++] = (byte) (0xe0 + (c >> 12));
316                    buf[count++] = (byte) (0x80 + ((c >> 6) & 0x3f));
317                    buf[count++] = (byte) (0x80 + (c & 0x3f));
318                }
319            }
320        }
321    
322        /**
323         * Get the writer that this one directly wraps.
324         */
325        public Writer getWriter()
326        {
327            return m_writer;
328        }
329    
330        /**
331         * Get the OutputStream that is the at the end of the
332         * chain of writers.
333         */
334        public OutputStream getOutputStream()
335        {
336            OutputStream retval = null;
337            if (m_writer instanceof WriterChain)
338                retval = ((WriterChain) m_writer).getOutputStream();
339            return retval;
340        }
341    }