View Javadoc

1   /*
2    *  Copyright 2006 Simon Raess
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  package net.sf.beep4j.internal;
17  
18  import java.nio.ByteBuffer;
19  import java.nio.charset.Charset;
20  
21  import net.sf.beep4j.ProtocolException;
22  import net.sf.beep4j.internal.util.Assert;
23  import net.sf.beep4j.internal.util.ByteUtil;
24  
25  /**
26   * Represents a data header as specified by the BEEP specification.
27   * 
28   * @author Simon Raess
29   */
30  public class DataHeader {
31  	
32  	private static final String EOL = "\r\n";
33  
34  	private static final char FINAL = '.';
35  
36  	private static final char INTERMEDIATE = '*';
37  
38  	private static final String SPACE = " ";
39  
40  	private static final Charset charset = Charset.forName("US-ASCII");
41  	
42  	/**
43  	 * The message type of the frame.
44  	 */
45  	protected MessageType type;
46  	
47  	/**
48  	 * The size of the frame's payload.
49  	 */
50  	protected int payloadSize;
51  	
52  	/**
53  	 * The channel number of the frame.
54  	 */
55  	protected int channel;
56  	
57  	/**
58  	 * The message number of the frame.
59  	 */
60  	protected int messageNumber;
61  	
62  	/**
63  	 * Whether this is an intermediate frame.
64  	 */
65  	protected boolean intermediate;
66  	
67  	/**
68  	 * The sequence number of the first byte in the frame's payload.
69  	 */
70  	protected long sequenceNumber;
71  	
72  	/**
73  	 * Creates a new DataHeader.
74  	 * 
75  	 * @param type the message type of the frame
76  	 * @param channel the channel number, must be greater or equal than zero
77  	 * @param messageNumber the message number of the frame
78  	 * @param intermediate whether this is an intermediate frame
79  	 * @param sequenceNumber the sequence number of the frame
80  	 * @param size the payload size of the frame
81  	 */
82  	public DataHeader(MessageType type, int channel, int messageNumber, boolean intermediate, long sequenceNumber, int size) {
83  		Assert.notNull("type", type);
84  		this.type = type;
85  		this.channel = channel;
86  		this.messageNumber = messageNumber;
87  		this.intermediate = intermediate;
88  		this.sequenceNumber = sequenceNumber;
89  		this.payloadSize = size;
90  	}
91  	
92  	/**
93  	 * Parses the passed in tokenized header line into a DataHeader.
94  	 * 
95  	 * @param tokens the tokenized header line
96  	 * @return the parsed header
97  	 */
98  	public static final DataHeader parseHeader(String[] tokens) {
99  		if (tokens.length == 0) {
100 			throw new ProtocolException("header has 0 tokens");
101 		}
102 		
103 		MessageType type = parseMessageType(tokens[0]);
104 		
105 		if (type == MessageType.ANS && tokens.length != 7) {
106 			throw new ProtocolException("expecting 7 tokens in ANS header, was " + tokens.length);
107 		} else if (type != MessageType.ANS && tokens.length != 6) {
108 			throw new ProtocolException("expecting 6 tokens in header, was " + tokens.length);
109 		}
110 		
111 		int channel = ByteUtil.parseUnsignedInt("channel number", tokens[1]);
112 		int messageNumber = ByteUtil.parseUnsignedInt("message number", tokens[2]);
113 		boolean intermediate = parseIntermediate(tokens[3]);
114 		long sequenceNumber = ByteUtil.parseUnsignedLong("sequence number", tokens[4]);
115 		int payloadSize = ByteUtil.parseUnsignedInt("size", tokens[5]);
116 		
117 		if (MessageType.ANS == type) {
118 			int answerNumber = ByteUtil.parseUnsignedInt("answer number", tokens[6]);
119 			return new ANSHeader(channel, messageNumber, intermediate, sequenceNumber, payloadSize, answerNumber);
120 		} else {
121 			return new DataHeader(type, channel, messageNumber, intermediate, sequenceNumber, payloadSize);
122 		}
123 	}
124 	
125 	private static MessageType parseMessageType(String s) {
126 		try {
127 			return MessageType.valueOf(s);
128 		} catch (IllegalArgumentException e) {
129 			throw new ProtocolException("'" + s + "' is an invalid message type");
130 		}
131 	}
132 		
133 	private static boolean parseIntermediate(String s) {
134 		if ("*".equals(s)) {
135 			return true;
136 		} else if (".".equals(s)) {
137 			return false;
138 		} else {
139 			throw new ProtocolException("'" + s + "' is an invalid intermediate indicator");
140 		}
141 	}
142 	
143 	public MessageType getType() {
144 		return type;
145 	}
146 
147 	public int getChannel() {
148 		return channel;
149 	}
150 
151 	public int getMessageNumber() {
152 		return messageNumber;
153 	}
154 
155 	public boolean isIntermediate() {
156 		return intermediate;
157 	}
158 
159 	public int getPayloadSize() {
160 		return payloadSize;
161 	}
162 
163 	public long getSequenceNumber() {
164 		return sequenceNumber;
165 	}
166 	
167 	/**
168 	 * Splits the header into two parts. The first part's size is set
169 	 * to the passed in parameter. It has the intermediate flag set to
170 	 * true. The second part has the remaining
171 	 * size plus its sequence number adapted.
172 	 *  
173 	 * @param size the size of the first part
174 	 * @return an array of two elements
175 	 */
176 	public DataHeader[] split(int size) {
177 		MessageType type = getType();
178 		
179 		DataHeader[] result = new DataHeader[2];
180 		result[0] = new DataHeader(type, channel, messageNumber, true, sequenceNumber, size);
181 		result[1] = new DataHeader(type, channel, messageNumber, false, sequenceNumber + size, payloadSize - size);
182 		
183 		return result;
184 	}
185 	
186 	/**
187 	 * Converts the header into a ByteBuffer.
188 	 * 
189 	 * @return the converted ByteBuffer 
190 	 */
191 	public ByteBuffer asByteBuffer() {
192 		StringBuilder builder = new StringBuilder(type.name());
193 		
194 		builder.append(SPACE);
195 		builder.append(channel);
196 		builder.append(SPACE);
197 		builder.append(messageNumber);
198 		builder.append(SPACE);
199 		builder.append(intermediate ? INTERMEDIATE : FINAL);
200 		builder.append(SPACE);
201 		builder.append(sequenceNumber);
202 		builder.append(SPACE);
203 		builder.append(payloadSize);
204 		builder.append(EOL);
205 		
206 		return charset.encode(builder.toString());
207 	}
208 	
209 	@Override
210 	public boolean equals(Object obj) {
211 		if (this == obj) {
212 			return true;
213 		} else if (obj == null) {
214 			return false;
215 		} else if (getClass() == obj.getClass()) {
216 			DataHeader header = (DataHeader) obj;
217 			return type == header.type
218 			    && channel == header.channel
219 			    && messageNumber == header.messageNumber
220 			    && intermediate == header.intermediate
221 			    && sequenceNumber == header.sequenceNumber
222 			    && payloadSize == header.payloadSize;
223 		} else {
224 			return false;
225 		}
226 	}
227 	
228 	@Override
229 	public int hashCode() {
230 		int result = 17;
231 		result = result * 23 + type.hashCode();
232 		result = result * 23 + channel;
233 		result = result * 23 + messageNumber; 
234 		result = result * 23 + (intermediate ? 1 : 0);
235 		result = result * 23 + payloadSize;
236 		result = result * 23 + new Long(sequenceNumber).hashCode();
237 		return result;
238 	}
239 	
240 	@Override
241 	public String toString() {
242 		return type.name() + " " 
243 			 + channel + " "
244 			 + messageNumber + " " 
245 		     + (intermediate ? "*" : ".") + " " 
246 		     + sequenceNumber + " "
247 		     + payloadSize;
248 	}
249 	
250 	/**
251 	 * Header for messages of type ANS. This header type has an additional
252 	 * property <code>answerNumber</code>.
253 	 * 
254 	 * @author Simon Raess
255 	 */
256 	public static class ANSHeader extends DataHeader {
257 		
258 		private final int answerNumber;
259 		
260 		public ANSHeader(int channel, int messageNumber, boolean intermediate, long sequenceNumber, int payloadSize, int answerNumber) {
261 			super(MessageType.ANS, channel, messageNumber, intermediate, sequenceNumber, payloadSize);
262 			this.answerNumber = answerNumber;
263 		}
264 		
265 		public int getAnswerNumber() {
266 			return answerNumber;
267 		}
268 		
269 		@Override
270 		public DataHeader[] split(int size) {
271 			DataHeader[] result = new DataHeader[2];
272 			result[0] = new ANSHeader(channel, messageNumber, true, sequenceNumber, size, answerNumber);
273 			result[1] = new ANSHeader(channel, messageNumber, false, sequenceNumber + size, payloadSize - size, answerNumber);
274 			return result;
275 		}
276 		
277 		@Override
278 		public ByteBuffer asByteBuffer() {
279 			StringBuilder builder = new StringBuilder();
280 			
281 			builder.append(type.name());
282 			builder.append(SPACE);
283 			builder.append(channel);
284 			builder.append(SPACE);
285 			builder.append(messageNumber);
286 			builder.append(SPACE);
287 			builder.append(intermediate ? INTERMEDIATE : FINAL);
288 			builder.append(SPACE);
289 			builder.append(sequenceNumber);
290 			builder.append(SPACE);
291 			builder.append(payloadSize);
292 			builder.append(SPACE);
293 			builder.append(answerNumber);
294 			builder.append(EOL);
295 			
296 			return charset.encode(builder.toString());
297 		}		
298 
299 		@Override
300 		public boolean equals(Object obj) {
301 			if (!super.equals(obj)) {
302 				return false;
303 			} else if (obj.getClass().equals(obj)) {
304 				ANSHeader header = (ANSHeader) obj;
305 				return answerNumber == header.answerNumber;
306 			} else {
307 				return false;
308 			}
309 		}
310 
311 		@Override
312 		public int hashCode() {
313 			int result = super.hashCode();
314 			result = result * 23 + answerNumber;
315 			return result;
316 		}
317 		
318 		 @Override
319 		public String toString() {
320 			 return super.toString() + " " + answerNumber;
321 		}
322 		 
323 	}
324 		
325 }