001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.tomcat.util.buf;
018:
019: import java.io.IOException;
020: import java.io.OutputStream;
021: import java.io.OutputStreamWriter;
022: import java.io.UnsupportedEncodingException;
023:
024: /** Efficient conversion of character to bytes.
025: *
026: * This uses the standard JDK mechansim - a writer - but provides mechanisms
027: * to recycle all the objects that are used. It is compatible with JDK1.1 and up,
028: * ( nio is better, but it's not available even in 1.2 or 1.3 )
029: *
030: */
031: public final class C2BConverter {
032: private IntermediateOutputStream ios;
033: private WriteConvertor conv;
034: private ByteChunk bb;
035: private String enc;
036:
037: /** Create a converter, with bytes going to a byte buffer
038: */
039: public C2BConverter(ByteChunk output, String encoding)
040: throws IOException {
041: this .bb = output;
042: ios = new IntermediateOutputStream(output);
043: conv = new WriteConvertor(ios, encoding);
044: this .enc = encoding;
045: }
046:
047: /** Create a converter
048: */
049: public C2BConverter(String encoding) throws IOException {
050: this (new ByteChunk(1024), encoding);
051: }
052:
053: public ByteChunk getByteChunk() {
054: return bb;
055: }
056:
057: public String getEncoding() {
058: return enc;
059: }
060:
061: public void setByteChunk(ByteChunk bb) {
062: this .bb = bb;
063: ios.setByteChunk(bb);
064: }
065:
066: /** Reset the internal state, empty the buffers.
067: * The encoding remain in effect, the internal buffers remain allocated.
068: */
069: public final void recycle() {
070: conv.recycle();
071: bb.recycle();
072: }
073:
074: /** Generate the bytes using the specified encoding
075: */
076: public final void convert(char c[], int off, int len)
077: throws IOException {
078: conv.write(c, off, len);
079: }
080:
081: /** Generate the bytes using the specified encoding
082: */
083: public final void convert(String s) throws IOException {
084: conv.write(s);
085: }
086:
087: /** Generate the bytes using the specified encoding
088: */
089: public final void convert(char c) throws IOException {
090: conv.write(c);
091: }
092:
093: /** Convert a message bytes chars to bytes
094: */
095: public final void convert(MessageBytes mb) throws IOException {
096: int type = mb.getType();
097: if (type == MessageBytes.T_BYTES)
098: return;
099: ByteChunk orig = bb;
100: setByteChunk(mb.getByteChunk());
101: bb.recycle();
102: bb.allocate(32, -1);
103:
104: if (type == MessageBytes.T_STR) {
105: convert(mb.getString());
106: // System.out.println("XXX Converting " + mb.getString() );
107: } else if (type == MessageBytes.T_CHARS) {
108: CharChunk charC = mb.getCharChunk();
109: convert(charC.getBuffer(), charC.getOffset(), charC
110: .getLength());
111: //System.out.println("XXX Converting " + mb.getCharChunk() );
112: } else {
113: System.out.println("XXX unknowon type " + type);
114: }
115: flushBuffer();
116: //System.out.println("C2B: XXX " + bb.getBuffer() + bb.getLength());
117: setByteChunk(orig);
118: }
119:
120: /** Flush any internal buffers into the ByteOutput or the internal
121: * byte[]
122: */
123: public final void flushBuffer() throws IOException {
124: conv.flush();
125: }
126:
127: }
128:
129: // -------------------- Private implementation --------------------
130:
131: /**
132: * Special writer class, where close() is overritten. The default implementation
133: * would set byteOutputter to null, and the writter can't be recycled.
134: *
135: * Note that the flush method will empty the internal buffers _and_ call
136: * flush on the output stream - that's why we use an intermediary output stream
137: * that overrides flush(). The idea is to have full control: flushing the
138: * char->byte converter should be independent of flushing the OutputStream.
139: *
140: * When a WriteConverter is created, it'll allocate one or 2 byte buffers,
141: * with a 8k size that can't be changed ( at least in JDK1.1 -> 1.4 ). It would
142: * also allocate a ByteOutputter or equivalent - again some internal buffers.
143: *
144: * It is essential to keep this object around and reuse it. You can use either
145: * pools or per thread data - but given that in most cases a converter will be
146: * needed for every thread and most of the time only 1 ( or 2 ) encodings will
147: * be used, it is far better to keep it per thread and eliminate the pool
148: * overhead too.
149: *
150: */
151: final class WriteConvertor extends OutputStreamWriter {
152: // stream with flush() and close(). overriden.
153: private IntermediateOutputStream ios;
154:
155: // Has a private, internal byte[8192]
156:
157: /** Create a converter.
158: */
159: public WriteConvertor(IntermediateOutputStream out, String enc)
160: throws UnsupportedEncodingException {
161: super (out, enc);
162: ios = out;
163: }
164:
165: /** Overriden - will do nothing but reset internal state.
166: */
167: public final void close() throws IOException {
168: // NOTHING
169: // Calling super.close() would reset out and cb.
170: }
171:
172: /**
173: * Flush the characters only
174: */
175: public final void flush() throws IOException {
176: // Will flushBuffer and out()
177: // flushBuffer put any remaining chars in the byte[]
178: super .flush();
179: }
180:
181: public final void write(char cbuf[], int off, int len)
182: throws IOException {
183: // will do the conversion and call write on the output stream
184: super .write(cbuf, off, len);
185: }
186:
187: /** Reset the buffer
188: */
189: public final void recycle() {
190: ios.disable();
191: try {
192: // System.out.println("Reseting writer");
193: flush();
194: } catch (Exception ex) {
195: ex.printStackTrace();
196: }
197: ios.enable();
198: }
199:
200: }
201:
202: /** Special output stream where close() is overriden, so super.close()
203: is never called.
204:
205: This allows recycling. It can also be disabled, so callbacks will
206: not be called if recycling the converter and if data was not flushed.
207: */
208: final class IntermediateOutputStream extends OutputStream {
209: private ByteChunk tbuff;
210: private boolean enabled = true;
211:
212: public IntermediateOutputStream(ByteChunk tbuff) {
213: this .tbuff = tbuff;
214: }
215:
216: public final void close() throws IOException {
217: // shouldn't be called - we filter it out in writer
218: throw new IOException("close() called - shouldn't happen ");
219: }
220:
221: public final void flush() throws IOException {
222: // nothing - write will go directly to the buffer,
223: // we don't keep any state
224: }
225:
226: public final void write(byte cbuf[], int off, int len)
227: throws IOException {
228: // will do the conversion and call write on the output stream
229: if (enabled) {
230: tbuff.append(cbuf, off, len);
231: }
232: }
233:
234: public final void write(int i) throws IOException {
235: throw new IOException("write( int ) called - shouldn't happen ");
236: }
237:
238: // -------------------- Internal methods --------------------
239:
240: void setByteChunk(ByteChunk bb) {
241: tbuff = bb;
242: }
243:
244: /** Temporary disable - this is used to recycle the converter without
245: * generating an output if the buffers were not flushed
246: */
247: final void disable() {
248: enabled = false;
249: }
250:
251: /** Reenable - used to recycle the converter
252: */
253: final void enable() {
254: enabled = true;
255: }
256: }
|