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.InputStream;
022: import java.io.InputStreamReader;
023: import java.io.UnsupportedEncodingException;
024:
025: /** Efficient conversion of bytes to character .
026: *
027: * This uses the standard JDK mechansim - a reader - but provides mechanisms
028: * to recycle all the objects that are used. It is compatible with JDK1.1
029: * and up,
030: * ( nio is better, but it's not available even in 1.2 or 1.3 )
031: *
032: * Not used in the current code, the performance gain is not very big
033: * in the current case ( since String is created anyway ), but it will
034: * be used in a later version or after the remaining optimizations.
035: */
036: public class B2CConverter {
037:
038: private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
039: .getLog(B2CConverter.class);
040:
041: private IntermediateInputStream iis;
042: private ReadConvertor conv;
043: private String encoding;
044:
045: protected B2CConverter() {
046: }
047:
048: /** Create a converter, with bytes going to a byte buffer
049: */
050: public B2CConverter(String encoding) throws IOException {
051: this .encoding = encoding;
052: reset();
053: }
054:
055: /** Reset the internal state, empty the buffers.
056: * The encoding remain in effect, the internal buffers remain allocated.
057: */
058: public void recycle() {
059: conv.recycle();
060: }
061:
062: static final int BUFFER_SIZE = 8192;
063: char result[] = new char[BUFFER_SIZE];
064:
065: /** Convert a buffer of bytes into a chars
066: */
067: public void convert(ByteChunk bb, CharChunk cb) throws IOException {
068: // Set the ByteChunk as input to the Intermediate reader
069: iis.setByteChunk(bb);
070: convert(cb);
071: }
072:
073: private void convert(CharChunk cb) throws IOException {
074: try {
075: // read from the reader
076: while (true) { // conv.ready() ) {
077: int cnt = conv.read(result, 0, BUFFER_SIZE);
078: if (cnt <= 0) {
079: // End of stream ! - we may be in a bad state
080: if (debug > 0)
081: log("EOF");
082: // reset();
083: return;
084: }
085: if (debug > 1)
086: log("Converted: " + new String(result, 0, cnt));
087:
088: // XXX go directly
089: cb.append(result, 0, cnt);
090: }
091: } catch (IOException ex) {
092: if (debug > 0)
093: log("Reseting the converter " + ex.toString());
094: reset();
095: throw ex;
096: }
097: }
098:
099: public void reset() throws IOException {
100: // destroy the reader/iis
101: iis = new IntermediateInputStream();
102: conv = new ReadConvertor(iis, encoding);
103: }
104:
105: private final int debug = 0;
106:
107: void log(String s) {
108: if (log.isDebugEnabled())
109: log.debug("B2CConverter: " + s);
110: }
111:
112: // -------------------- Not used - the speed improvemnt is quite small
113:
114: /*
115: private Hashtable decoders;
116: public static final boolean useNewString=false;
117: public static final boolean useSpecialDecoders=true;
118: private UTF8Decoder utfD;
119: // private char[] conversionBuff;
120: CharChunk conversionBuf;
121:
122:
123: private static String decodeString(ByteChunk mb, String enc)
124: throws IOException
125: {
126: byte buff=mb.getBuffer();
127: int start=mb.getStart();
128: int end=mb.getEnd();
129: if( useNewString ) {
130: if( enc==null) enc="UTF8";
131: return new String( buff, start, end-start, enc );
132: }
133: B2CConverter b2c=null;
134: if( useSpecialDecoders &&
135: (enc==null || "UTF8".equalsIgnoreCase(enc))) {
136: if( utfD==null ) utfD=new UTF8Decoder();
137: b2c=utfD;
138: }
139: if(decoders == null ) decoders=new Hashtable();
140: if( enc==null ) enc="UTF8";
141: b2c=(B2CConverter)decoders.get( enc );
142: if( b2c==null ) {
143: if( useSpecialDecoders ) {
144: if( "UTF8".equalsIgnoreCase( enc ) ) {
145: b2c=new UTF8Decoder();
146: }
147: }
148: if( b2c==null )
149: b2c=new B2CConverter( enc );
150: decoders.put( enc, b2c );
151: }
152: if( conversionBuf==null ) conversionBuf=new CharChunk(1024);
153:
154: try {
155: conversionBuf.recycle();
156: b2c.convert( this, conversionBuf );
157: //System.out.println("XXX 1 " + conversionBuf );
158: return conversionBuf.toString();
159: } catch( IOException ex ) {
160: ex.printStackTrace();
161: return null;
162: }
163: }
164:
165: */
166: }
167:
168: // -------------------- Private implementation --------------------
169:
170: /**
171: *
172: */
173: final class ReadConvertor extends InputStreamReader {
174: // stream with flush() and close(). overriden.
175: private IntermediateInputStream iis;
176:
177: // Has a private, internal byte[8192]
178:
179: /** Create a converter.
180: */
181: public ReadConvertor(IntermediateInputStream in, String enc)
182: throws UnsupportedEncodingException {
183: super (in, enc);
184: iis = in;
185: }
186:
187: /** Overriden - will do nothing but reset internal state.
188: */
189: public final void close() throws IOException {
190: // NOTHING
191: // Calling super.close() would reset out and cb.
192: }
193:
194: public final int read(char cbuf[], int off, int len)
195: throws IOException {
196: // will do the conversion and call write on the output stream
197: return super .read(cbuf, off, len);
198: }
199:
200: /** Reset the buffer
201: */
202: public final void recycle() {
203: }
204: }
205:
206: /** Special output stream where close() is overriden, so super.close()
207: is never called.
208:
209: This allows recycling. It can also be disabled, so callbacks will
210: not be called if recycling the converter and if data was not flushed.
211: */
212: final class IntermediateInputStream extends InputStream {
213: byte buf[];
214: int pos;
215: int len;
216: int end;
217:
218: public IntermediateInputStream() {
219: }
220:
221: public final void close() throws IOException {
222: // shouldn't be called - we filter it out in writer
223: throw new IOException("close() called - shouldn't happen ");
224: }
225:
226: public final int read(byte cbuf[], int off, int len)
227: throws IOException {
228: if (pos >= end)
229: return -1;
230: if (pos + len > end) {
231: len = end - pos;
232: }
233: if (len <= 0) {
234: return 0;
235: }
236: System.arraycopy(buf, pos, cbuf, off, len);
237: pos += len;
238: return len;
239: }
240:
241: public final int read() throws IOException {
242: return (pos < end) ? (buf[pos++] & 0xff) : -1;
243: }
244:
245: // -------------------- Internal methods --------------------
246:
247: void setBuffer(byte b[], int p, int l) {
248: buf = b;
249: pos = p;
250: len = l;
251: end = pos + len;
252: }
253:
254: void setByteChunk(ByteChunk mb) {
255: buf = mb.getBytes();
256: pos = mb.getStart();
257: len = mb.getLength();
258: end = pos + len;
259: }
260:
261: }
|