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.tomcat.util.buf;
19
20 import java.io.IOException;
21 import java.io.OutputStream;
22 import java.io.OutputStreamWriter;
23 import java.io.UnsupportedEncodingException;
24
25 /** Efficient conversion of character to bytes.
26 *
27 * This uses the standard JDK mechansim - a writer - but provides mechanisms
28 * to recycle all the objects that are used. It is compatible with JDK1.1 and up,
29 * ( nio is better, but it's not available even in 1.2 or 1.3 )
30 *
31 */
32 public final class C2BConverter {
33
34 private static org.apache.juli.logging.Log log=
35 org.apache.juli.logging.LogFactory.getLog(C2BConverter.class );
36
37 private IntermediateOutputStream ios;
38 private WriteConvertor conv;
39 private ByteChunk bb;
40 private String enc;
41
42 /** Create a converter, with bytes going to a byte buffer
43 */
44 public C2BConverter(ByteChunk output, String encoding) throws IOException {
45 this.bb=output;
46 ios=new IntermediateOutputStream( output );
47 conv=new WriteConvertor( ios, encoding );
48 this.enc=encoding;
49 }
50
51 /** Create a converter
52 */
53 public C2BConverter(String encoding) throws IOException {
54 this( new ByteChunk(1024), encoding );
55 }
56
57 public ByteChunk getByteChunk() {
58 return bb;
59 }
60
61 public String getEncoding() {
62 return enc;
63 }
64
65 public void setByteChunk(ByteChunk bb) {
66 this.bb=bb;
67 ios.setByteChunk( bb );
68 }
69
70 /** Reset the internal state, empty the buffers.
71 * The encoding remain in effect, the internal buffers remain allocated.
72 */
73 public final void recycle() {
74 conv.recycle();
75 bb.recycle();
76 }
77
78 /** Generate the bytes using the specified encoding
79 */
80 public final void convert(char c[], int off, int len ) throws IOException {
81 conv.write( c, off, len );
82 }
83
84 /** Generate the bytes using the specified encoding
85 */
86 public final void convert(String s, int off, int len ) throws IOException {
87 conv.write( s, off, len );
88 }
89
90 /** Generate the bytes using the specified encoding
91 */
92 public final void convert(String s ) throws IOException {
93 conv.write( s );
94 }
95
96 /** Generate the bytes using the specified encoding
97 */
98 public final void convert(char c ) throws IOException {
99 conv.write( c );
100 }
101
102 /** Convert a message bytes chars to bytes
103 */
104 public final void convert(MessageBytes mb ) throws IOException {
105 int type=mb.getType();
106 if( type==MessageBytes.T_BYTES )
107 return;
108 ByteChunk orig=bb;
109 setByteChunk( mb.getByteChunk());
110 bb.recycle();
111 bb.allocate( 32, -1 );
112
113 if( type==MessageBytes.T_STR ) {
114 convert( mb.getString() );
115 // System.out.println("XXX Converting " + mb.getString() );
116 } else if( type==MessageBytes.T_CHARS ) {
117 CharChunk charC=mb.getCharChunk();
118 convert( charC.getBuffer(),
119 charC.getOffset(), charC.getLength());
120 //System.out.println("XXX Converting " + mb.getCharChunk() );
121 } else {
122 if (log.isDebugEnabled())
123 log.debug("XXX unknowon type " + type );
124 }
125 flushBuffer();
126 //System.out.println("C2B: XXX " + bb.getBuffer() + bb.getLength());
127 setByteChunk(orig);
128 }
129
130 /** Flush any internal buffers into the ByteOutput or the internal
131 * byte[]
132 */
133 public final void flushBuffer() throws IOException {
134 conv.flush();
135 }
136
137 }
138
139 // -------------------- Private implementation --------------------
140
141
142
143 /**
144 * Special writer class, where close() is overritten. The default implementation
145 * would set byteOutputter to null, and the writter can't be recycled.
146 *
147 * Note that the flush method will empty the internal buffers _and_ call
148 * flush on the output stream - that's why we use an intermediary output stream
149 * that overrides flush(). The idea is to have full control: flushing the
150 * char->byte converter should be independent of flushing the OutputStream.
151 *
152 * When a WriteConverter is created, it'll allocate one or 2 byte buffers,
153 * with a 8k size that can't be changed ( at least in JDK1.1 -> 1.4 ). It would
154 * also allocate a ByteOutputter or equivalent - again some internal buffers.
155 *
156 * It is essential to keep this object around and reuse it. You can use either
157 * pools or per thread data - but given that in most cases a converter will be
158 * needed for every thread and most of the time only 1 ( or 2 ) encodings will
159 * be used, it is far better to keep it per thread and eliminate the pool
160 * overhead too.
161 *
162 */
163 final class WriteConvertor extends OutputStreamWriter {
164 // stream with flush() and close(). overriden.
165 private IntermediateOutputStream ios;
166
167 // Has a private, internal byte[8192]
168
169 /** Create a converter.
170 */
171 public WriteConvertor( IntermediateOutputStream out, String enc )
172 throws UnsupportedEncodingException
173 {
174 super( out, enc );
175 ios=out;
176 }
177
178 /** Overriden - will do nothing but reset internal state.
179 */
180 public final void close() throws IOException {
181 // NOTHING
182 // Calling super.close() would reset out and cb.
183 }
184
185 /**
186 * Flush the characters only
187 */
188 public final void flush() throws IOException {
189 // Will flushBuffer and out()
190 // flushBuffer put any remaining chars in the byte[]
191 super.flush();
192 }
193
194 public final void write(char cbuf[], int off, int len) throws IOException {
195 // will do the conversion and call write on the output stream
196 super.write( cbuf, off, len );
197 }
198
199 /** Reset the buffer
200 */
201 public final void recycle() {
202 ios.disable();
203 try {
204 // System.out.println("Reseting writer");
205 flush();
206 } catch( Exception ex ) {
207 ex.printStackTrace();
208 }
209 ios.enable();
210 }
211
212 }
213
214
215 /** Special output stream where close() is overriden, so super.close()
216 is never called.
217
218 This allows recycling. It can also be disabled, so callbacks will
219 not be called if recycling the converter and if data was not flushed.
220 */
221 final class IntermediateOutputStream extends OutputStream {
222 private ByteChunk tbuff;
223 private boolean enabled=true;
224
225 public IntermediateOutputStream(ByteChunk tbuff) {
226 this.tbuff=tbuff;
227 }
228
229 public final void close() throws IOException {
230 // shouldn't be called - we filter it out in writer
231 throw new IOException("close() called - shouldn't happen ");
232 }
233
234 public final void flush() throws IOException {
235 // nothing - write will go directly to the buffer,
236 // we don't keep any state
237 }
238
239 public final void write(byte cbuf[], int off, int len) throws IOException {
240 // will do the conversion and call write on the output stream
241 if( enabled ) {
242 tbuff.append( cbuf, off, len );
243 }
244 }
245
246 public final void write( int i ) throws IOException {
247 throw new IOException("write( int ) called - shouldn't happen ");
248 }
249
250 // -------------------- Internal methods --------------------
251
252 void setByteChunk( ByteChunk bb ) {
253 tbuff=bb;
254 }
255
256 /** Temporary disable - this is used to recycle the converter without
257 * generating an output if the buffers were not flushed
258 */
259 final void disable() {
260 enabled=false;
261 }
262
263 /** Reenable - used to recycle the converter
264 */
265 final void enable() {
266 enabled=true;
267 }
268 }