1 /*
2 * Copyright (c) 2012 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.eclipse.jetty.spdy.api;
18
19 import java.nio.ByteBuffer;
20 import java.nio.charset.Charset;
21 import java.util.concurrent.atomic.AtomicInteger;
22
23 /**
24 * <p>A container for DATA frames metadata and content bytes.</p>
25 * <p>Specialized subclasses (like {@link StringDataInfo}) may be used by applications
26 * to send specific types of content.</p>
27 * <p>Applications may send multiple instances of {@link DataInfo}, usually of the same
28 * type, via {@link Stream#data(DataInfo)}. The last instance must have the
29 * {@link #isClose() close flag} set, so that the client knows that no more content is
30 * expected.</p>
31 * <p>Receivers of {@link DataInfo} via {@link StreamFrameListener#onData(Stream, DataInfo)}
32 * have two different APIs to read the data content bytes: a {@link #readInto(ByteBuffer) read}
33 * API that does not interact with flow control, and a {@link #consumeInto(ByteBuffer) drain}
34 * API that interacts with flow control.</p>
35 * <p>Flow control is defined so that when the sender wants to sends a number of bytes larger
36 * than the {@link Settings.ID#INITIAL_WINDOW_SIZE} value, it will stop sending as soon as it
37 * has sent a number of bytes equal to the window size. The receiver has to <em>consume</em>
38 * the data bytes that it received in order to tell the sender to send more bytes.</p>
39 * <p>Consuming the data bytes can be done only via {@link #consumeInto(ByteBuffer)} or by a combination
40 * of {@link #readInto(ByteBuffer)} and {@link #consume(int)} (possibly at different times).</p>
41 */
42 public abstract class DataInfo
43 {
44 /**
45 * <p>Flag that indicates that this {@link DataInfo} is the last frame in the stream.</p>
46 *
47 * @see #isClose()
48 * @see #getFlags()
49 */
50 public final static byte FLAG_CLOSE = 1;
51 /**
52 * <p>Flag that indicates that this {@link DataInfo}'s data is compressed.</p>
53 *
54 * @see #isCompress()
55 * @see #getFlags()
56 */
57 public final static byte FLAG_COMPRESS = 2;
58
59 private final AtomicInteger consumed = new AtomicInteger();
60 private boolean close;
61 private boolean compress;
62
63 /**
64 * <p>Creates a new {@link DataInfo} with the given close flag and no compression flag.</p>
65 *
66 * @param close the value of the close flag
67 */
68 public DataInfo(boolean close)
69 {
70 setClose(close);
71 }
72
73 /**
74 * <p>Creates a new {@link DataInfo} with the given close flag and given compression flag.</p>
75 *
76 * @param close the close flag
77 * @param compress the compress flag
78 */
79 public DataInfo(boolean close, boolean compress)
80 {
81 setClose(close);
82 setCompress(compress);
83 }
84
85 /**
86 * @return the value of the compress flag
87 * @see #setCompress(boolean)
88 */
89 public boolean isCompress()
90 {
91 return compress;
92 }
93
94 /**
95 * @param compress the value of the compress flag
96 * @see #isCompress()
97 */
98 public void setCompress(boolean compress)
99 {
100 this.compress = compress;
101 }
102
103 /**
104 * @return the value of the close flag
105 * @see #setClose(boolean)
106 */
107 public boolean isClose()
108 {
109 return close;
110 }
111
112 /**
113 * @param close the value of the close flag
114 * @see #isClose()
115 */
116 public void setClose(boolean close)
117 {
118 this.close = close;
119 }
120
121 /**
122 * @return the close and compress flags as integer
123 * @see #FLAG_CLOSE
124 * @see #FLAG_COMPRESS
125 */
126 public byte getFlags()
127 {
128 byte flags = isClose() ? FLAG_CLOSE : 0;
129 flags |= isCompress() ? FLAG_COMPRESS : 0;
130 return flags;
131 }
132
133 /**
134 * @return the total number of content bytes
135 * @see #available()
136 */
137 public abstract int length();
138
139 /**
140 * <p>Returns the available content bytes that can be read via {@link #readInto(ByteBuffer)}.</p>
141 * <p>Each invocation to {@link #readInto(ByteBuffer)} modifies the value returned by this method,
142 * until no more content bytes are available.</p>
143 *
144 * @return the available content bytes
145 * @see #readInto(ByteBuffer)
146 */
147 public abstract int available();
148
149 /**
150 * <p>Copies the content bytes of this {@link DataInfo} into the given {@link ByteBuffer}.</p>
151 * <p>If the given {@link ByteBuffer} cannot contain the whole content of this {@link DataInfo}
152 * then after the read {@link #available()} will return a positive value, and further content
153 * may be retrieved by invoking again this method with a new output buffer.</p>
154 *
155 * @param output the {@link ByteBuffer} to copy to bytes into
156 * @return the number of bytes copied
157 * @see #available()
158 * @see #consumeInto(ByteBuffer)
159 */
160 public abstract int readInto(ByteBuffer output);
161
162 /**
163 * <p>Reads and consumes the content bytes of this {@link DataInfo} into the given {@link ByteBuffer}.</p>
164 *
165 * @param output the {@link ByteBuffer} to copy the bytes into
166 * @return the number of bytes copied
167 * @see #consume(int)
168 */
169 public int consumeInto(ByteBuffer output)
170 {
171 int read = readInto(output);
172 consume(read);
173 return read;
174 }
175
176 /**
177 * <p>Consumes the given number of bytes from this {@link DataInfo}.</p>
178 *
179 * @param delta the number of bytes consumed
180 */
181 public void consume(int delta)
182 {
183 if (delta < 0)
184 throw new IllegalArgumentException();
185 int read = length() - available();
186 int newConsumed = consumed() + delta;
187 // if (newConsumed > read)
188 // throw new IllegalStateException("Consuming without reading: consumed " + newConsumed + " but only read " + read);
189 consumed.addAndGet(delta);
190 }
191
192 /**
193 * @return the number of bytes consumed
194 */
195 public int consumed()
196 {
197 return consumed.get();
198 }
199
200 /**
201 *
202 * @param charset the charset used to convert the bytes
203 * @param consume whether to consume the content
204 * @return a String with the content of this {@link DataInfo}
205 */
206 public String asString(String charset, boolean consume)
207 {
208 ByteBuffer buffer = asByteBuffer(consume);
209 return Charset.forName(charset).decode(buffer).toString();
210 }
211
212 /**
213 * @return a byte array with the content of this {@link DataInfo}
214 * @param consume whether to consume the content
215 */
216 public byte[] asBytes(boolean consume)
217 {
218 ByteBuffer buffer = asByteBuffer(consume);
219 byte[] result = new byte[buffer.remaining()];
220 buffer.get(result);
221 return result;
222 }
223
224 /**
225 * @return a {@link ByteBuffer} with the content of this {@link DataInfo}
226 * @param consume whether to consume the content
227 */
228 public ByteBuffer asByteBuffer(boolean consume)
229 {
230 ByteBuffer buffer = allocate(available());
231 if (consume)
232 consumeInto(buffer);
233 else
234 readInto(buffer);
235 buffer.flip();
236 return buffer;
237 }
238
239 protected ByteBuffer allocate(int size)
240 {
241 return ByteBuffer.allocate(size);
242 }
243
244 @Override
245 public String toString()
246 {
247 return String.format("DATA @%x available=%d consumed=%d close=%b compress=%b", hashCode(), available(), consumed(), isClose(), isCompress());
248 }
249 }