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
007: * "License"); you may not use this file except in compliance
008: * with the License. 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,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *
019: */
020: package org.apache.mina.filter.codec.textline;
021:
022: import java.nio.charset.CharacterCodingException;
023: import java.nio.charset.Charset;
024: import java.nio.charset.CharsetDecoder;
025:
026: import org.apache.mina.common.AttributeKey;
027: import org.apache.mina.common.BufferDataException;
028: import org.apache.mina.common.IoBuffer;
029: import org.apache.mina.common.IoSession;
030: import org.apache.mina.filter.codec.ProtocolDecoder;
031: import org.apache.mina.filter.codec.ProtocolDecoderException;
032: import org.apache.mina.filter.codec.ProtocolDecoderOutput;
033: import org.apache.mina.filter.codec.RecoverableProtocolDecoderException;
034:
035: /**
036: * A {@link ProtocolDecoder} which decodes a text line into a string.
037: *
038: * @author The Apache MINA Project (dev@mina.apache.org)
039: * @version $Rev: 608906 $, $Date: 2008-01-04 09:29:40 -0700 (Fri, 04 Jan 2008) $,
040: */
041: public class TextLineDecoder implements ProtocolDecoder {
042: private final AttributeKey CONTEXT = new AttributeKey(getClass(),
043: "context");
044:
045: private final Charset charset;
046:
047: private final LineDelimiter delimiter;
048:
049: private IoBuffer delimBuf;
050:
051: private int maxLineLength = 1024;
052:
053: /**
054: * Creates a new instance with the current default {@link Charset}
055: * and {@link LineDelimiter#AUTO} delimiter.
056: */
057: public TextLineDecoder() {
058: this (LineDelimiter.AUTO);
059: }
060:
061: /**
062: * Creates a new instance with the current default {@link Charset}
063: * and the specified <tt>delimiter</tt>.
064: */
065: public TextLineDecoder(String delimiter) {
066: this (new LineDelimiter(delimiter));
067: }
068:
069: /**
070: * Creates a new instance with the current default {@link Charset}
071: * and the specified <tt>delimiter</tt>.
072: */
073: public TextLineDecoder(LineDelimiter delimiter) {
074: this (Charset.defaultCharset(), delimiter);
075: }
076:
077: /**
078: * Creates a new instance with the spcified <tt>charset</tt>
079: * and {@link LineDelimiter#AUTO} delimiter.
080: */
081: public TextLineDecoder(Charset charset) {
082: this (charset, LineDelimiter.AUTO);
083: }
084:
085: /**
086: * Creates a new instance with the spcified <tt>charset</tt>
087: * and the specified <tt>delimiter</tt>.
088: */
089: public TextLineDecoder(Charset charset, String delimiter) {
090: this (charset, new LineDelimiter(delimiter));
091: }
092:
093: /**
094: * Creates a new instance with the specified <tt>charset</tt>
095: * and the specified <tt>delimiter</tt>.
096: */
097: public TextLineDecoder(Charset charset, LineDelimiter delimiter) {
098: if (charset == null) {
099: throw new NullPointerException("charset");
100: }
101: if (delimiter == null) {
102: throw new NullPointerException("delimiter");
103: }
104:
105: this .charset = charset;
106: this .delimiter = delimiter;
107: }
108:
109: /**
110: * Returns the allowed maximum size of the line to be decoded.
111: * If the size of the line to be decoded exceeds this value, the
112: * decoder will throw a {@link BufferDataException}. The default
113: * value is <tt>1024</tt> (1KB).
114: */
115: public int getMaxLineLength() {
116: return maxLineLength;
117: }
118:
119: /**
120: * Sets the allowed maximum size of the line to be decoded.
121: * If the size of the line to be decoded exceeds this value, the
122: * decoder will throw a {@link BufferDataException}. The default
123: * value is <tt>1024</tt> (1KB).
124: */
125: public void setMaxLineLength(int maxLineLength) {
126: if (maxLineLength <= 0) {
127: throw new IllegalArgumentException("maxLineLength: "
128: + maxLineLength);
129: }
130:
131: this .maxLineLength = maxLineLength;
132: }
133:
134: public void decode(IoSession session, IoBuffer in,
135: ProtocolDecoderOutput out) throws Exception {
136: Context ctx = getContext(session);
137:
138: if (LineDelimiter.AUTO.equals(delimiter)) {
139: decodeAuto(ctx, in, out);
140: } else {
141: decodeNormal(ctx, in, out);
142: }
143: }
144:
145: private Context getContext(IoSession session) {
146: Context ctx;
147: ctx = (Context) session.getAttribute(CONTEXT);
148: if (ctx == null) {
149: ctx = new Context();
150: session.setAttribute(CONTEXT, ctx);
151: }
152: return ctx;
153: }
154:
155: public void finishDecode(IoSession session,
156: ProtocolDecoderOutput out) throws Exception {
157: }
158:
159: public void dispose(IoSession session) throws Exception {
160: Context ctx = (Context) session.getAttribute(CONTEXT);
161: if (ctx != null) {
162: session.removeAttribute(CONTEXT);
163: }
164: }
165:
166: private void decodeAuto(Context ctx, IoBuffer in,
167: ProtocolDecoderOutput out) throws CharacterCodingException,
168: ProtocolDecoderException {
169:
170: int matchCount = ctx.getMatchCount();
171:
172: // Try to find a match
173: int oldPos = in.position();
174: int oldLimit = in.limit();
175: while (in.hasRemaining()) {
176: byte b = in.get();
177: boolean matched = false;
178: switch (b) {
179: case '\r':
180: // Might be Mac, but we don't auto-detect Mac EOL
181: // to avoid confusion.
182: matchCount++;
183: break;
184: case '\n':
185: // UNIX
186: matchCount++;
187: matched = true;
188: break;
189: default:
190: matchCount = 0;
191: }
192:
193: if (matched) {
194: // Found a match.
195: int pos = in.position();
196: in.limit(pos);
197: in.position(oldPos);
198:
199: ctx.append(in);
200:
201: in.limit(oldLimit);
202: in.position(pos);
203:
204: if (ctx.getOverflowPosition() == 0) {
205: IoBuffer buf = ctx.getBuffer();
206: buf.flip();
207: buf.limit(buf.limit() - matchCount);
208: try {
209: out.write(buf.getString(ctx.getDecoder()));
210: } finally {
211: buf.clear();
212: }
213: } else {
214: int overflowPosition = ctx.getOverflowPosition();
215: ctx.reset();
216: throw new RecoverableProtocolDecoderException(
217: "Line is too long: " + overflowPosition);
218: }
219:
220: oldPos = pos;
221: matchCount = 0;
222: }
223: }
224:
225: // Put remainder to buf.
226: in.position(oldPos);
227: ctx.append(in);
228:
229: ctx.setMatchCount(matchCount);
230: }
231:
232: private void decodeNormal(Context ctx, IoBuffer in,
233: ProtocolDecoderOutput out) throws CharacterCodingException,
234: ProtocolDecoderException {
235:
236: int matchCount = ctx.getMatchCount();
237:
238: // Convert delimiter to ByteBuffer if not done yet.
239: if (delimBuf == null) {
240: IoBuffer tmp = IoBuffer.allocate(2).setAutoExpand(true);
241: tmp.putString(delimiter.getValue(), charset.newEncoder());
242: tmp.flip();
243: delimBuf = tmp;
244: }
245:
246: // Try to find a match
247: int oldPos = in.position();
248: int oldLimit = in.limit();
249: while (in.hasRemaining()) {
250: byte b = in.get();
251: if (delimBuf.get(matchCount) == b) {
252: matchCount++;
253: if (matchCount == delimBuf.limit()) {
254: // Found a match.
255: int pos = in.position();
256: in.limit(pos);
257: in.position(oldPos);
258:
259: ctx.append(in);
260:
261: in.limit(oldLimit);
262: in.position(pos);
263: if (ctx.getOverflowPosition() == 0) {
264: IoBuffer buf = ctx.getBuffer();
265: buf.flip();
266: buf.limit(buf.limit() - matchCount);
267: try {
268: out.write(buf.getString(ctx.getDecoder()));
269: } finally {
270: buf.clear();
271: }
272: } else {
273: int overflowPosition = ctx
274: .getOverflowPosition();
275: ctx.reset();
276: throw new RecoverableProtocolDecoderException(
277: "Line is too long: " + overflowPosition);
278: }
279:
280: oldPos = pos;
281: matchCount = 0;
282: }
283: } else {
284: // fix for DIRMINA-506
285: in.position(in.position() - matchCount);
286: matchCount = 0;
287: }
288: }
289:
290: // Put remainder to buf.
291: in.position(oldPos);
292: ctx.append(in);
293:
294: ctx.setMatchCount(matchCount);
295: }
296:
297: private class Context {
298: private final CharsetDecoder decoder;
299: private final IoBuffer buf;
300: private int matchCount = 0;
301: private int overflowPosition = 0;
302:
303: private Context() {
304: decoder = charset.newDecoder();
305: buf = IoBuffer.allocate(80).setAutoExpand(true);
306: }
307:
308: public CharsetDecoder getDecoder() {
309: return decoder;
310: }
311:
312: public IoBuffer getBuffer() {
313: return buf;
314: }
315:
316: public int getOverflowPosition() {
317: return overflowPosition;
318: }
319:
320: public int getMatchCount() {
321: return matchCount;
322: }
323:
324: public void setMatchCount(int matchCount) {
325: this .matchCount = matchCount;
326: }
327:
328: public void reset() {
329: overflowPosition = 0;
330: matchCount = 0;
331: decoder.reset();
332: }
333:
334: public void append(IoBuffer in) {
335: if (overflowPosition != 0) {
336: discard(in);
337: } else if (buf.position() > maxLineLength - in.remaining()) {
338: overflowPosition = buf.position();
339: buf.clear();
340: discard(in);
341: } else {
342: getBuffer().put(in);
343: }
344: }
345:
346: private void discard(IoBuffer in) {
347: if (Integer.MAX_VALUE - in.remaining() < overflowPosition) {
348: overflowPosition = Integer.MAX_VALUE;
349: } else {
350: overflowPosition += in.remaining();
351: }
352: in.position(in.limit());
353: }
354: }
355: }
|