View Javadoc

    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one
    3    *  or more contributor license agreements.  See the NOTICE file
    4    *  distributed with this work for additional information
    5    *  regarding copyright ownership.  The ASF licenses this file
    6    *  to you under the Apache License, Version 2.0 (the
    7    *  "License"); you may not use this file except in compliance
    8    *  with the License.  You may obtain a copy of the License at
    9    *
    10   *    http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   *  Unless required by applicable law or agreed to in writing,
    13   *  software distributed under the License is distributed on an
    14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   *  KIND, either express or implied.  See the License for the
    16   *  specific language governing permissions and limitations
    17   *  under the License.
    18   *
    19   */
    20  package org.apache.mina.filter.codec.textline;
    21
    22  import java.nio.charset.CharacterCodingException;
    23  import java.nio.charset.Charset;
    24  import java.nio.charset.CharsetDecoder;
    25
    26  import org.apache.mina.common.BufferDataException;
    27  import org.apache.mina.common.ByteBuffer;
    28  import org.apache.mina.common.IoSession;
    29  import org.apache.mina.filter.codec.ProtocolDecoder;
    30  import org.apache.mina.filter.codec.ProtocolDecoderOutput;
    31
    32  /**
    33   * A {@link ProtocolDecoder} which decodes a text line into a string.
    34   *
    35   * @author The Apache Directory Project (mina-dev@directory.apache.org)
    36   * @version $Rev: 636237 $, $Date: 2008-03-12 16:35:18 +0900 (Wed, 12 Mar 2008) $,
    37   */
    38  public class TextLineDecoder implements ProtocolDecoder {
    39      private static final String CONTEXT = TextLineDecoder.class.getName()
    40              + ".context";
    41
    42      private final Charset charset;
    43
    44      private final LineDelimiter delimiter;
    45
    46      private ByteBuffer delimBuf;
    47
    48      private int maxLineLength = 1024;
    49
    50      /**
    51       * Creates a new instance with the current default {@link Charset}
    52       * and {@link LineDelimiter#AUTO} delimiter.
    53       */
    54      public TextLineDecoder() {
    55          this(Charset.defaultCharset(), LineDelimiter.AUTO);
    56      }
    57
    58      /**
    59       * Creates a new instance with the spcified <tt>charset</tt>
    60       * and {@link LineDelimiter#AUTO} delimiter.
    61       */
    62      public TextLineDecoder(Charset charset) {
    63          this(charset, LineDelimiter.AUTO);
    64      }
    65
    66      /**
    67       * Creates a new instance with the specified <tt>charset</tt>
    68       * and the specified <tt>delimiter</tt>.
    69       */
    70      public TextLineDecoder(Charset charset, LineDelimiter delimiter) {
    71          if (charset == null) {
    72              throw new NullPointerException("charset");
    73          }
    74          if (delimiter == null) {
    75              throw new NullPointerException("delimiter");
    76          }
    77
    78          this.charset = charset;
    79          this.delimiter = delimiter;
    80      }
    81
    82      /**
    83       * Returns the allowed maximum size of the line to be decoded.
    84       * If the size of the line to be decoded exceeds this value, the
    85       * decoder will throw a {@link BufferDataException}.  The default
    86       * value is <tt>1024</tt> (1KB).
    87       */
    88      public int getMaxLineLength() {
    89          return maxLineLength;
    90      }
    91
    92      /**
    93       * Sets the allowed maximum size of the line to be decoded.
    94       * If the size of the line to be decoded exceeds this value, the
    95       * decoder will throw a {@link BufferDataException}.  The default
    96       * value is <tt>1024</tt> (1KB).
    97       */
    98      public void setMaxLineLength(int maxLineLength) {
    99          if (maxLineLength <= 0) {
    100             throw new IllegalArgumentException("maxLineLength: "
    101                     + maxLineLength);
    102         }
    103
    104         this.maxLineLength = maxLineLength;
    105     }
    106
    107     public void decode(IoSession session, ByteBuffer in,
    108             ProtocolDecoderOutput out) throws Exception {
    109         Context ctx = getContext(session);
    110
    111         if (LineDelimiter.AUTO.equals(delimiter)) {
    112             decodeAuto(ctx, in, out);
    113         } else {
    114             decodeNormal(ctx, in, out);
    115         }
    116     }
    117
    118     private Context getContext(IoSession session) {
    119         Context ctx = (Context) session.getAttribute(CONTEXT);
    120         if (ctx == null) {
    121             ctx = new Context();
    122             session.setAttribute(CONTEXT, ctx);
    123         }
    124         return ctx;
    125     }
    126
    127     public void finishDecode(IoSession session, ProtocolDecoderOutput out)
    128             throws Exception {
    129     }
    130
    131     public void dispose(IoSession session) throws Exception {
    132         Context ctx = (Context) session.getAttribute(CONTEXT);
    133         if (ctx != null) {
    134             ctx.getBuffer().release();
    135             session.removeAttribute(CONTEXT);
    136         }
    137     }
    138
    139     private void decodeAuto(Context ctx, ByteBuffer in, ProtocolDecoderOutput out)
    140             throws CharacterCodingException {
    141
    142         int matchCount = ctx.getMatchCount();
    143
    144         // Try to find a match
    145         int oldPos = in.position();
    146         int oldLimit = in.limit();
    147         while (in.hasRemaining()) {
    148             byte b = in.get();
    149             boolean matched = false;
    150             switch (b) {
    151             case '\r':
    152                 // Might be Mac, but we don't auto-detect Mac EOL
    153                 // to avoid confusion.
    154                 matchCount++;
    155                 break;
    156             case '\n':
    157                 // UNIX
    158                 matchCount++;
    159                 matched = true;
    160                 break;
    161             default:
    162                 matchCount = 0;
    163             }
    164
    165             if (matched) {
    166                 // Found a match.
    167                 int pos = in.position();
    168                 in.limit(pos);
    169                 in.position(oldPos);
    170
    171                 ctx.append(in);
    172
    173                 in.limit(oldLimit);
    174                 in.position(pos);
    175
    176                 if (ctx.getOverflowPosition() == 0) {
    177                     ByteBuffer buf = ctx.getBuffer();
    178                     buf.flip();
    179                     buf.limit(buf.limit() - matchCount);
    180                     try {
    181                         out.write(buf.getString(ctx.getDecoder()));
    182                     } finally {
    183                         buf.clear();
    184                     }
    185                 } else {
    186                     int overflowPosition = ctx.getOverflowPosition();
    187                     ctx.reset();
    188                     throw new BufferDataException(
    189                             "Line is too long: " + overflowPosition);
    190                 }
    191
    192                 oldPos = pos;
    193                 matchCount = 0;
    194             }
    195         }
    196
    197         // Put remainder to buf.
    198         in.position(oldPos);
    199         ctx.append(in);
    200
    201         ctx.setMatchCount(matchCount);
    202     }
    203
    204     private void decodeNormal(Context ctx, ByteBuffer in, ProtocolDecoderOutput out)
    205             throws CharacterCodingException {
    206
    207         int matchCount = ctx.getMatchCount();
    208
    209         // Convert delimiter to ByteBuffer if not done yet.
    210         if (delimBuf == null) {
    211             ByteBuffer tmp = ByteBuffer.allocate(2).setAutoExpand(true);
    212             tmp.putString(delimiter.getValue(), charset.newEncoder());
    213             tmp.flip();
    214             delimBuf = tmp;
    215         }
    216
    217         // Try to find a match
    218         int oldPos = in.position();
    219         int oldLimit = in.limit();
    220         while (in.hasRemaining()) {
    221             byte b = in.get();
    222             if (delimBuf.get(matchCount) == b) {
    223                 matchCount++;
    224                 if (matchCount == delimBuf.limit()) {
    225                     // Found a match.
    226                     int pos = in.position();
    227                     in.limit(pos);
    228                     in.position(oldPos);
    229
    230                     ctx.append(in);
    231
    232                     in.limit(oldLimit);
    233                     in.position(pos);
    234
    235                     if (ctx.getOverflowPosition() == 0) {
    236                         ByteBuffer buf = ctx.getBuffer();
    237                         buf.flip();
    238                         buf.limit(buf.limit() - matchCount);
    239                         try {
    240                             out.write(buf.getString(ctx.getDecoder()));
    241                         } finally {
    242                             buf.clear();
    243                         }
    244                     } else {
    245                         int overflowPosition = ctx.getOverflowPosition();
    246                         ctx.reset();
    247                         throw new BufferDataException(
    248                                 "Line is too long: " + overflowPosition);
    249                     }
    250
    251                     oldPos = pos;
    252                     matchCount = 0;
    253                 }
    254             } else {
    255                 // fix for DIRMINA-506 & DIRMINA-536
    256                 in.position(Math.max(0, in.position() - matchCount));
    257                 matchCount = 0;
    258             }
    259         }
    260
    261         // Put remainder to buf.
    262         in.position(oldPos);
    263         ctx.append(in);
    264
    265         ctx.setMatchCount(matchCount);
    266     }
    267
    268     private class Context {
    269         private final CharsetDecoder decoder;
    270         private final ByteBuffer buf;
    271         private int matchCount = 0;
    272         private int overflowPosition = 0;
    273
    274         private Context() {
    275             decoder = charset.newDecoder();
    276             buf = ByteBuffer.allocate(80).setAutoExpand(true);
    277         }
    278
    279         public CharsetDecoder getDecoder() {
    280             return decoder;
    281         }
    282
    283         public ByteBuffer getBuffer() {
    284             return buf;
    285         }
    286
    287         public int getOverflowPosition() {
    288             return overflowPosition;
    289         }
    290
    291         public int getMatchCount() {
    292             return matchCount;
    293         }
    294
    295         public void setMatchCount(int matchCount) {
    296             this.matchCount = matchCount;
    297         }
    298
    299         public void reset() {
    300             overflowPosition = 0;
    301             matchCount = 0;
    302             decoder.reset();
    303         }
    304
    305         public void append(ByteBuffer in) {
    306             if (overflowPosition != 0) {
    307                 discard(in);
    308             } else if (buf.position() > maxLineLength - in.remaining()) {
    309                     overflowPosition = buf.position();
    310                     buf.clear();
    311                     discard(in);
    312             } else {
    313                 getBuffer().put(in);
    314             }
    315         }
    316
    317         private void discard(ByteBuffer in) {
    318             if (Integer.MAX_VALUE - in.remaining() < overflowPosition) {
    319                 overflowPosition = Integer.MAX_VALUE;
    320             } else {
    321                 overflowPosition += in.remaining();
    322             }
    323             in.position(in.limit());
    324         }
    325     }
    326 }