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