View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package org.jboss.netty.handler.codec.frame;
17  
18  import static org.jboss.netty.buffer.ChannelBuffers.*;
19  
20  import java.nio.ByteOrder;
21  
22  import org.jboss.netty.buffer.ChannelBuffer;
23  import org.jboss.netty.buffer.ChannelBufferFactory;
24  import org.jboss.netty.channel.Channel;
25  import org.jboss.netty.channel.ChannelHandler.Sharable;
26  import org.jboss.netty.channel.ChannelHandlerContext;
27  import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
28  
29  /**
30   * An encoder that prepends the length of the message.  The length value is
31   * prepended as a binary form.  It is encoded in either big endian or little
32   * endian depending on the default {@link ByteOrder} of the current
33   * {@link ChannelBufferFactory}.
34   * <p>
35   * For example, <tt>{@link LengthFieldPrepender}(2)</tt> will encode the
36   * following 12-bytes string:
37   * <pre>
38   * +----------------+
39   * | "HELLO, WORLD" |
40   * +----------------+
41   * </pre>
42   * into the following:
43   * <pre>
44   * +--------+----------------+
45   * + 0x000C | "HELLO, WORLD" |
46   * +--------+----------------+
47   * </pre>
48   * If you turned on the {@code lengthIncludesLengthFieldLength} flag in the
49   * constructor, the encoded data would look like the following
50   * (12 (original data) + 2 (prepended data) = 14 (0xE)):
51   * <pre>
52   * +--------+----------------+
53   * + 0x000E | "HELLO, WORLD" |
54   * +--------+----------------+
55   * </pre>
56   */
57  @Sharable
58  public class LengthFieldPrepender extends OneToOneEncoder {
59  
60      private final int lengthFieldLength;
61      private final boolean lengthIncludesLengthFieldLength;
62  
63      /**
64       * Creates a new instance.
65       *
66       * @param lengthFieldLength the length of the prepended length field.
67       *                          Only 1, 2, 3, 4, and 8 are allowed.
68       *
69       * @throws IllegalArgumentException
70       *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
71       */
72      public LengthFieldPrepender(int lengthFieldLength) {
73          this(lengthFieldLength, false);
74      }
75  
76      /**
77       * Creates a new instance.
78       *
79       * @param lengthFieldLength the length of the prepended length field.
80       *                          Only 1, 2, 3, 4, and 8 are allowed.
81       * @param lengthIncludesLengthFieldLength
82       *                          if {@code true}, the length of the prepended
83       *                          length field is added to the value of the
84       *                          prepended length field.
85       *
86       * @throws IllegalArgumentException
87       *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
88       */
89      public LengthFieldPrepender(
90              int lengthFieldLength, boolean lengthIncludesLengthFieldLength) {
91          if (lengthFieldLength != 1 && lengthFieldLength != 2 &&
92              lengthFieldLength != 3 && lengthFieldLength != 4 &&
93              lengthFieldLength != 8) {
94              throw new IllegalArgumentException(
95                      "lengthFieldLength must be either 1, 2, 3, 4, or 8: " +
96                      lengthFieldLength);
97          }
98  
99          this.lengthFieldLength = lengthFieldLength;
100         this.lengthIncludesLengthFieldLength = lengthIncludesLengthFieldLength;
101     }
102 
103     @Override
104     protected Object encode(
105             ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
106         if (!(msg instanceof ChannelBuffer)) {
107             return msg;
108         }
109 
110         ChannelBuffer body = (ChannelBuffer) msg;
111         ChannelBuffer header = channel.getConfig().getBufferFactory().getBuffer(body.order(), lengthFieldLength);
112 
113         int length = lengthIncludesLengthFieldLength?
114                 body.readableBytes() + lengthFieldLength : body.readableBytes();
115         switch (lengthFieldLength) {
116         case 1:
117             if (length >= 256) {
118                 throw new IllegalArgumentException(
119                         "length does not fit into a byte: " + length);
120             }
121             header.writeByte((byte) length);
122             break;
123         case 2:
124             if (length >= 65536) {
125                 throw new IllegalArgumentException(
126                         "length does not fit into a short integer: " + length);
127             }
128             header.writeShort((short) length);
129             break;
130         case 3:
131             if (length >= 16777216) {
132                 throw new IllegalArgumentException(
133                         "length does not fit into a medium integer: " + length);
134             }
135             header.writeMedium(length);
136             break;
137         case 4:
138             header.writeInt(length);
139             break;
140         case 8:
141             header.writeLong(length);
142             break;
143         default:
144             throw new Error("should not reach here");
145         }
146         return wrappedBuffer(header, body);
147     }
148 }