View Javadoc

1   /*
2    * Copyright (c) 2005 The University of Reading
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    * 1. Redistributions of source code must retain the above copyright
9    *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. Neither the name of the University of Reading, nor the names of the
14   *    authors or contributors may be used to endorse or promote products
15   *    derived from this software without specific prior written permission.
16   * 
17   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   */
28  
29  package uk.ac.rdg.resc.jstyx.messages;
30  
31  import org.apache.mina.common.ByteBuffer;
32  import java.nio.ByteOrder;
33  
34  import uk.ac.rdg.resc.jstyx.StyxUtils;
35  import uk.ac.rdg.resc.jstyx.types.Qid;
36  import uk.ac.rdg.resc.jstyx.types.ULong;
37  import uk.ac.rdg.resc.jstyx.types.DirEntry;
38  
39  /***
40   * Wrapper class for a ByteBuffer that allows Styx primitives to be easily
41   * read from and written to the buffer.  Reads and writes unsigned bytes, shorts
42   * and integers, in addition to other Styx types.
43   * Before using the methods in this class, you should check that the required
44   * number of bytes are available in the ByteBuffer. The easiest way to do this
45   * is to read the message length (the first four bytes in the Styx message) and
46   * check that this number of bytes are available in the buffer.
47   * @todo do these functions need to be thread-safe?  Are two different threads
48   * ever going to want to access the same buffer simultaneously?
49   * @todo does this belong in the messages package?
50   *
51   * @author Jon Blower
52   * $Revision: 507 $
53   * $Date: 2005-12-01 08:21:56 +0000 (Thu, 01 Dec 2005) $
54   * $Log$
55   * Revision 1.5  2005/12/01 08:21:56  jonblower
56   * Fixed javadoc comments
57   *
58   * Revision 1.4  2005/11/03 07:46:55  jonblower
59   * Trying to fix bug with sending RreadMessages
60   *
61   * Revision 1.3  2005/03/16 17:56:22  jonblower
62   * Replaced use of java.nio.ByteBuffer with MINA's ByteBuffer to minimise copying of buffers
63   *
64   * Revision 1.2  2005/03/11 14:02:16  jonblower
65   * Merged MINA-Test_20059309 into main line of development
66   *
67   * Revision 1.1.2.1  2005/03/10 11:35:28  jonblower
68   * Moved from JStyx root package
69   *
70   * Revision 1.2  2005/03/09 17:01:23  jonblower
71   * Added more methods reflecting methods in underlying ByteBuffer
72   *
73   * Revision 1.1.1.1  2005/02/16 18:58:16  jonblower
74   * Initial import
75   *
76   */
77  public class StyxBuffer
78  {
79      
80      private ByteBuffer buf;  // The underlying org.apache.mina.common.ByteBuffer
81      
82      /*** 
83       * Creates a new instance of StyxBuffer
84       * @param buf The ByteBuffer to wrap
85       * @throws IllegalArgumentException if the ByteBuffer is null
86       */
87      public StyxBuffer(ByteBuffer buf)
88      {
89          if (buf == null)
90          {
91              throw new IllegalArgumentException("Byte buffer cannot be null");
92          }
93          this.buf = buf;
94          // Make sure that the byte order is little-endian
95          this.buf.order(ByteOrder.LITTLE_ENDIAN);
96      }
97      
98      /***
99       * @return the next unsigned byte as an integer between 0 and 255
100      */
101     public int getUByte()
102     {
103         byte b = buf.get();
104         return b & 0xff;
105     }
106     
107     /***
108      * Puts an unsigned byte into the buffer at the current position
109      * @param b The value of the byte as an integer between 0 and 255
110      * @return this StyxBuffer (allows chaining of put commands)
111      * @throws IllegalArgumentException if the parameter is out of range
112      */
113     public StyxBuffer putUByte(int b)
114     {
115         if (b < 0 || b > StyxUtils.MAXUBYTE)
116         {
117             throw new IllegalArgumentException("Value (" + b + ") out of range of UByte (0-255)");
118         }
119         buf.put((byte)b);
120         return this;
121     }
122     
123     /***
124      * @return the next unsigned short (2 bytes) as an integer between 0 and 65335
125      */
126     public int getUShort()
127     {
128         short s = buf.getShort();
129         return s & 0xffff;
130     }
131     
132     /***
133      * Puts an unsigned short (2 bytes) into the buffer at the current position
134      * @param s The value of the short as an integer between 0 and 65535
135      * @return this StyxBuffer (allows chaining of put commands)
136      * @throws IllegalArgumentException if the parameter is out of range
137      */
138     public StyxBuffer putUShort(int s)
139     {
140         if (s < 0 || s > StyxUtils.MAXUSHORT)
141         {
142             throw new IllegalArgumentException("Value (" + s + 
143                 ") out of range of UShort (0-" + StyxUtils.MAXUSHORT + ")");
144         }
145         buf.putShort((short)s);
146         return this;
147     }
148     
149     /***
150      * Puts an unsigned short (2 bytes) into the buffer at the given position
151      * @param index The place in the buffer at which to put the data
152      * @param s The value of the short as an integer between 0 and 65535
153      * @return this StyxBuffer (allows chaining of put commands)
154      * @throws IllegalArgumentException if the parameter is out of range
155      */
156     public StyxBuffer putUShort(int index, int s)
157     {
158         if (s < 0 || s > StyxUtils.MAXUSHORT)
159         {
160             throw new IllegalArgumentException("Value (" + s + 
161                 ") out of range of UShort (0-" + StyxUtils.MAXUSHORT + ")");
162         }
163         buf.putShort(index, (short)s);
164         return this;
165     }
166     
167     /***
168      * @return the next unsigned int (4 bytes) as a long between 0 and 4,294,967,295
169      */
170     public long getUInt()
171     {
172         int i = buf.getInt();
173         return i & 0xffffffffL;
174     }
175     
176     /***
177      * Puts an unsigned int (4 bytes) into the buffer at the current position
178      * @param l The value of the int as a long integer between 0 and 4,294,967,295
179      * @return this StyxBuffer (allows chaining of put commands)
180      * @throws IllegalArgumentException if the parameter is out of range
181      */
182     public StyxBuffer putUInt(long l)
183     {
184         if (l < 0 || l > StyxUtils.MAXUINT)
185         {
186             throw new IllegalArgumentException("Value (" + l + ") out of range of UInt (0-4294967295)");
187         }
188         buf.putInt((int)l);
189         return this;
190     }
191     
192     /***
193      * Puts an unsigned int (4 bytes) into the buffer at the given position
194      * @param index The place in the buffer at which to put the data
195      * @param l The value of the int as a long integer between 0 and 4,294,967,295
196      * @return this StyxBuffer (allows chaining of put commands)
197      * @throws IllegalArgumentException if the parameter is out of range
198      */
199     public StyxBuffer putUInt(int index, long l)
200     {
201         if (l < 0 || l > StyxUtils.MAXUINT)
202         {
203             throw new IllegalArgumentException("Value (" + l + ") out of range of UInt (0-4294967295)");
204         }
205         buf.putInt(index, (int)l);
206         return this;
207     }
208     
209     /***
210      * @return the next ULong (unsigned 8-byte integer)
211      */
212     public ULong getULong()
213     {
214         byte[] bytes = new byte[8];
215         buf.get(bytes);
216         return new ULong(bytes);
217     }
218     
219     /***
220      * Puts the given ULong to the buffer at the current position
221      * @param ulong The ulong to write
222      * @return this StyxBuffer (allows chaining of put commands)
223      */
224     public StyxBuffer putULong(ULong ulong)
225     {
226         byte[] bytes = ulong.getBytes();
227         buf.put(bytes);
228         return this;
229     }
230     
231     /***
232      * @return the next string
233      */
234     public String getString()
235     {
236         // First get the length of the string (2-byte integer)
237         int strLen = this.getUShort();
238         // Create a byte array to hold the bytes containing the String
239         // TODO: inefficient? Can we create the string without copying the bytes first?
240         byte[] bytes = new byte[strLen];
241         // Now get this number of bytes from the buffer
242         buf.get(bytes);
243         return StyxUtils.utf8ToString(bytes);
244     }
245     
246     /***
247      * Puts a string into the buffer at the current position. First converts
248      * the string to an array of bytes in UTF-8.
249      * @param s The string to write to the buffer
250      * @return this StyxBuffer (allows chaining of put commands)
251      */
252     public StyxBuffer putString(String s)
253     {
254         byte[] bytes = StyxUtils.strToUTF8(s);
255         this.putUShort(bytes.length);
256         buf.put(bytes);
257         return this;
258     }
259     
260     /***
261      * @return The next Qid (server's representation of a file)
262      */
263     public Qid getQid()
264     {
265         int type = this.getUByte();    // The type of the file
266         long version = this.getUInt(); // The version number of the file
267         // Now get the path of the file - an unsigned 8-byte integer
268         byte[] pathBytes = new byte[8];
269         buf.get(pathBytes);
270         return new Qid(type, version, new ULong(pathBytes));        
271     }
272     
273     /***
274      * Puts the given Qid to the buffer at the current position
275      * @param qid The Qid to write
276      * @return this StyxBuffer (allows chaining of put commands)
277      */
278     public StyxBuffer putQid(Qid qid)
279     {
280         this.putUByte(qid.getType()).putUInt(qid.getVersion()).putULong(qid.getPath());
281         return this;        
282     }
283     
284     /***
285      * @return The next DirEntry (Directory entry)
286      */
287     public DirEntry getDirEntry()
288     {
289         int dirEntrySize = this.getUShort();      // The size in bytes of the rest of the directory entry
290         int type = this.getUShort();              // 2 bytes, for kernel use
291         long dev = this.getUInt();                // 4 bytes, for kernel use
292         Qid qid = this.getQid();                  // the server's representation of this file
293         long mode = this.getUInt();               // 4 bytes, permissions and flags
294         long lastAccessTime = this.getUInt();     // last access time
295         long lastModTime = this.getUInt();        // 4 bytes, last modification time
296         ULong fileLength = this.getULong();       // length of file in bytes
297         String fileName = this.getString();       // file name
298         String uid = this.getString();            // owner name
299         String gid = this.getString();            // group name
300         String lastModifiedBy = this.getString(); // Name of user who last modified the file
301         
302         return new DirEntry(type, dev, qid, mode, lastAccessTime,
303             lastModTime, fileLength, fileName, uid, gid, lastModifiedBy);
304     }
305     
306     /***
307      * Puts the given DirEntry to the buffer at the current position
308      * @param dir The DirEntry to write
309      * @return this StyxBuffer (allows chaining of put commands)
310      */
311     public StyxBuffer putDirEntry(DirEntry dir)
312     {
313         this.putUShort(dir.getSize() - 2);
314         this.putUShort(dir.getType());
315         this.putUInt(dir.getDev());
316         this.putQid(dir.getQid());
317         this.putUInt(dir.getMode());
318         this.putUInt(dir.getLastAccessTime());
319         this.putUInt(dir.getLastModifiedTime());
320         this.putULong(dir.getFileLength());
321         this.putString(dir.getFileName());
322         this.putString(dir.getOwner());
323         this.putString(dir.getGroup());
324         this.putString(dir.getLastModifiedBy());
325         
326         return this;
327     }
328     
329     /***
330      * Gets a chunk of data from the buffer. After calling this method
331      * successfully, the position of the buffer will be increased by <code>size</code>.
332      * @param size the number of bytes to get
333      * @return A new byte array containing a <i>copy</i> of the data in this buffer
334      * @throws IllegalArgumentException if there aren't enough bytes left in the buffer
335      */
336     public byte[] getData(int size)
337     {
338         if (this.buf.remaining() < size)
339         {
340             throw new IllegalArgumentException("There are fewer than " + size
341                 + " bytes remaining in the buffer");
342         }
343         // Copy the right number of bytes to a new buffer
344         byte[] bytes = new byte[size];
345         this.buf.get(bytes);
346         return bytes;
347     }
348     
349     public StyxBuffer put(byte[] bytes)
350     {
351         this.buf.put(bytes);
352         return this;
353     }
354     
355     public StyxBuffer put(byte[] bytes, int offset, int length)
356     {
357         this.buf.put(bytes, offset, length);
358         return this;
359     }
360     
361     /***
362      * Puts a chunk of data to the buffer.
363      * @param data The data to write. The position and limit of this buffer will
364      * be unaffected by this method.
365      * @param size The number of bytes to write
366      * @return this StyxBuffer (allows chaining of put commands)
367      * @throws IllegalArgumentException if the buffer does not contain at least
368      * <code>size</code> bytes, or if there is not enough space in the output
369      * buffer for the data.
370      */
371     public StyxBuffer putData(ByteBuffer data, long size)
372     {
373         if (data.remaining() < size)
374         {
375             throw new IllegalArgumentException("There are fewer than " + size +
376                 " bytes remaining in the input data buffer.");
377         }
378         if (this.buf.remaining() < size)
379         {
380             throw new IllegalArgumentException("There is not enough space for "
381                 + size + " bytes in the output buffer.");
382         }
383         // Remember the current position and limit of the buffer
384         int pos = data.position();
385         int limit = data.limit();
386         // Set the new limit of the buffer
387         data.limit(data.position() + (int)size);
388         // Now we can write the data in the knowledge that the correct number
389         // of bytes will be written
390         this.buf.put(data);
391         // Now set the old position and limit back
392         data.position(pos);
393         data.limit(limit);
394         
395         return this;
396     }
397     
398     
399     
400     /***
401      * Puts a chunk of data to the buffer.
402      * @param data The data to write. The position and limit of this buffer will
403      * be unaffected by this method.  All the remaining data in the buffer will
404      * be written.
405      * @return this StyxBuffer (allows chaining of put commands)
406      */
407     public StyxBuffer putData(ByteBuffer data)
408     {
409         return this.putData(data, data.remaining());
410     }
411     
412     /***
413      * Gets the underlying ByteBuffer
414      */
415     public ByteBuffer getBuffer()
416     {
417         return this.buf;
418     }
419     
420     /***
421      * @return the number of bytes remaining in the underlying buffer
422      */
423     public int remaining()
424     {
425         return this.buf.remaining();
426     }
427     
428     /***
429      * Flip the underlying buffer
430      */
431     public void flip()
432     {
433         this.buf.flip();
434     }
435 }