KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jruby > util > Pack


1 /***** BEGIN LICENSE BLOCK *****
2  * Version: CPL 1.0/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Common Public
5  * License Version 1.0 (the "License"); you may not use this file
6  * except in compliance with the License. You may obtain a copy of
7  * the License at http://www.eclipse.org/legal/cpl-v10.html
8  *
9  * Software distributed under the License is distributed on an "AS
10  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11  * implied. See the License for the specific language governing
12  * rights and limitations under the License.
13  *
14  * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
15  * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
16  * Copyright (C) 2003-2004 Thomas E Enebo <enebo@acm.org>
17  * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
18  * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
19  * Copyright (C) 2005 Derek Berner <derek.berner@state.nm.us>
20  * Copyright (C) 2006 Evan Buswell <ebuswell@gmail.com>
21  * Copyright (C) 2007 Nick Sieger <nicksieger@gmail.com>
22  *
23  * Alternatively, the contents of this file may be used under the terms of
24  * either of the GNU General Public License Version 2 or later (the "GPL"),
25  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26  * in which case the provisions of the GPL or the LGPL are applicable instead
27  * of those above. If you wish to allow use of your version of this file only
28  * under the terms of either the GPL or the LGPL, and not to allow others to
29  * use your version of this file under the terms of the CPL, indicate your
30  * decision by deleting the provisions above and replace them with the notice
31  * and other provisions required by the GPL or the LGPL. If you do not delete
32  * the provisions above, a recipient may use your version of this file under
33  * the terms of any one of the CPL, the GPL or the LGPL.
34  ***** END LICENSE BLOCK *****/

35 package org.jruby.util;
36
37 import java.nio.ByteBuffer JavaDoc;
38 import java.nio.ByteOrder JavaDoc;
39 import java.nio.CharBuffer JavaDoc;
40 import java.nio.charset.CharacterCodingException JavaDoc;
41 import java.nio.charset.Charset JavaDoc;
42 import java.nio.charset.CharsetDecoder JavaDoc;
43 import java.nio.charset.CodingErrorAction JavaDoc;
44 import java.util.List JavaDoc;
45
46 import org.jruby.Ruby;
47 import org.jruby.RubyArray;
48 import org.jruby.RubyFloat;
49 import org.jruby.RubyKernel;
50 import org.jruby.RubyNumeric;
51 import org.jruby.RubyString;
52 import org.jruby.runtime.builtin.IRubyObject;
53
54 public class Pack {
55     private static final String JavaDoc sSp10 = " ";
56     private static final String JavaDoc sNil10 = "\000\000\000\000\000\000\000\000\000\000";
57     private static final int IS_STAR = -1;
58     /** Native pack type.
59      **/

60     private static final String JavaDoc NATIVE_CODES = "sSiIlL";
61     private static final String JavaDoc sTooFew = "too few arguments";
62     private static final byte[] hex_table;
63     private static final byte[] uu_table;
64     private static final byte[] b64_table;
65     private static final byte[] sHexDigits;
66     private static final int[] b64_xtable = new int[256];
67     private static final Converter[] converters = new Converter[256];
68
69     static {
70         hex_table = ByteList.plain("0123456789ABCDEF");
71         uu_table =
72             ByteList.plain("`!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_");
73         b64_table =
74             ByteList.plain("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
75         sHexDigits = ByteList.plain("0123456789abcdef0123456789ABCDEFx");
76
77         // b64_xtable for decoding Base 64
78
for (int i = 0; i < 256; i++) {
79             b64_xtable[i] = -1;
80         }
81         for (int i = 0; i < 64; i++) {
82             b64_xtable[(int)b64_table[i]] = i;
83         }
84         // short, little-endian (network)
85
converters['v'] = new Converter(2) {
86             public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
87                 return runtime.newFixnum(
88                         decodeShortUnsignedLittleEndian(enc));
89             }
90             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
91                 int s = o == runtime.getNil() ? 0 : (int) (RubyNumeric.num2long(o) & 0xffff);
92                    encodeShortLittleEndian(result, s);
93                }};
94         // single precision, little-endian
95
converters['e'] = new Converter(4) {
96             public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
97                 return RubyFloat.newFloat(runtime, decodeFloatLittleEndian(enc));
98             }
99             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
100                 float f = o == runtime.getNil() ? 0 : (float) RubyKernel.new_float(o,o).convertToFloat().getDoubleValue();
101                 encodeFloatLittleEndian(result, f);
102             }};
103         Converter tmp = new Converter(4) {
104             public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
105                 return RubyFloat.newFloat(runtime, decodeFloatBigEndian(enc));
106             }
107             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
108                 float f = o == runtime.getNil() ? 0 : (float) RubyKernel.new_float(o,o).convertToFloat().getDoubleValue();
109                 encodeFloatBigEndian(result, f);
110             }
111         };
112         converters['F'] = tmp; // single precision, native
113
converters['f'] = tmp; // single precision, native
114
converters['g'] = tmp; // single precision, native
115
// double precision, little-endian
116
converters['E'] = new Converter(8) {
117             public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
118                 return RubyFloat.newFloat(runtime, decodeDoubleLittleEndian(enc));
119             }
120             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
121                 double d = o == runtime.getNil() ? 0 : RubyKernel.new_float(o,o).convertToFloat().getDoubleValue();
122                 encodeDoubleLittleEndian(result, d);
123             }};
124         tmp = new Converter(8) {
125             public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
126                 return RubyFloat.newFloat(runtime, decodeDoubleBigEndian(enc));
127             }
128             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
129                 double d = o == runtime.getNil() ? 0 : RubyKernel.new_float(o,o).convertToFloat().getDoubleValue();
130                 encodeDoubleBigEndian(result, d);
131             }
132         };
133         converters['D'] = tmp; // double precision native
134
converters['d'] = tmp; // double precision native
135
converters['G'] = tmp; // double precision bigendian
136
converters['s'] = new Converter(2) { // signed short
137
public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
138                 return runtime.newFixnum(decodeShortBigEndian(enc));
139             }
140             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
141                 int s = o == runtime.getNil() ? 0 : (int) (RubyNumeric.num2long(o) & 0xffff);
142                 encodeShortBigEndian(result, s);
143             }};
144         tmp = new Converter(2) {
145             public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
146                 return runtime.newFixnum(
147                         decodeShortUnsignedBigEndian(enc));
148             }
149             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
150                 int s = o == runtime.getNil() ? 0 : (int) (RubyNumeric.num2long(o) & 0xffff);
151                 encodeShortBigEndian(result, s);
152             }
153         };
154         converters['S'] = tmp; // unsigned short
155
converters['n'] = tmp; // short network
156
converters['c'] = new Converter(1) { // signed char
157
public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
158                 int c = enc.get();
159                 return runtime.newFixnum(c > (char) 127 ? c-256 : c);
160             }
161             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
162                 char c = o == runtime.getNil() ? 0 : (char) (RubyNumeric.num2long(o) & 0xff);
163                 result.append(c);
164             }};
165         converters['C'] = new Converter(1) { // unsigned char
166
public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
167                 return runtime.newFixnum(enc.get() & 0xFF);
168             }
169             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
170                 char c = o == runtime.getNil() ? 0 : (char) (RubyNumeric.num2long(o) & 0xff);
171                 result.append(c);
172             }};
173         // long, little-endian
174
converters['V'] = new Converter(4) {
175             public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
176                 return runtime.newFixnum(
177                         decodeIntUnsignedLittleEndian(enc));
178             }
179             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
180                 int s = o == runtime.getNil() ? 0 : (int) RubyNumeric.num2long(o);
181                 encodeIntLittleEndian(result, s);
182             }};
183         tmp = new Converter(4) {
184             public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
185                 return runtime.newFixnum(
186                         decodeIntUnsignedBigEndian(enc));
187             }
188             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
189                 int s = o == runtime.getNil() ? 0 : (int) RubyNumeric.num2long(o);
190                 encodeIntBigEndian(result, s);
191             }
192         };
193         converters['I'] = tmp; // unsigned int, native
194
converters['L'] = tmp; // unsigned long (bugs?)
195
converters['N'] = tmp; // long, network
196
tmp = new Converter(4) {
197             public IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc enc) {
198                 return runtime.newFixnum(decodeIntBigEndian(enc));
199             }
200             public void encode(Ruby runtime, IRubyObject o, StringBuffer JavaDoc result){
201                 int s = (o == runtime.getNil() ? 0 :
202                     (int) (RubyNumeric.num2long(o)));
203                 encodeIntBigEndian(result, s);
204             }
205         };
206         converters['l'] = tmp; // long, native
207
converters['i'] = tmp; // int, native
208
}
209
210     /**
211      * encodes a String in base64 or its uuencode variant.
212      * appends the result of the encoding in a StringBuffer
213      * @param io2Append The StringBuffer which should receive the result
214      * @param i2Encode The String to encode
215      * @param iLength The max number of characters to encode
216      * @param iType the type of encoding required (this is the same type as used by the pack method)
217      * @return the io2Append buffer
218      **/

219     private static StringBuffer JavaDoc encodes(Ruby runtime, StringBuffer JavaDoc io2Append,String JavaDoc stringToEncode,int charCount,char encodingType) {
220         charCount = charCount < stringToEncode.length() ? charCount
221                                           : stringToEncode.length();
222         io2Append.ensureCapacity(charCount * 4 / 3 + 6);
223         int i = 0;
224         byte[] lTranslationTable = encodingType == 'u' ? uu_table : b64_table;
225         char lPadding;
226         char[] charsToEncode = stringToEncode.toCharArray();
227         if (encodingType == 'u') {
228             if (charCount >= lTranslationTable.length) {
229                 throw runtime.newArgumentError(
230                     ""
231                         + charCount
232                         + " is not a correct value for the number of bytes per line in a u directive. Correct values range from 0 to "
233                         + lTranslationTable.length);
234             }
235             io2Append.append((char)lTranslationTable[charCount]);
236             lPadding = '`';
237         } else {
238             lPadding = '=';
239         }
240         while (charCount >= 3) {
241             char lCurChar = charsToEncode[i++];
242             char lNextChar = charsToEncode[i++];
243             char lNextNextChar = charsToEncode[i++];
244             io2Append.append((char)lTranslationTable[077 & (lCurChar >>> 2)]);
245             io2Append.append((char)lTranslationTable[077 & (((lCurChar << 4) & 060) | ((lNextChar >>> 4) & 017))]);
246             io2Append.append((char)lTranslationTable[077 & (((lNextChar << 2) & 074) | ((lNextNextChar >>> 6) & 03))]);
247             io2Append.append((char)lTranslationTable[077 & lNextNextChar]);
248             charCount -= 3;
249         }
250         if (charCount == 2) {
251             char lCurChar = charsToEncode[i++];
252             char lNextChar = charsToEncode[i++];
253             io2Append.append((char)lTranslationTable[077 & (lCurChar >>> 2)]);
254             io2Append.append((char)lTranslationTable[077 & (((lCurChar << 4) & 060) | ((lNextChar >> 4) & 017))]);
255             io2Append.append((char)lTranslationTable[077 & (((lNextChar << 2) & 074) | (('\0' >> 6) & 03))]);
256             io2Append.append(lPadding);
257         } else if (charCount == 1) {
258             char lCurChar = charsToEncode[i++];
259             io2Append.append((char)lTranslationTable[077 & (lCurChar >>> 2)]);
260             io2Append.append((char)lTranslationTable[077 & (((lCurChar << 4) & 060) | (('\0' >>> 4) & 017))]);
261             io2Append.append(lPadding);
262             io2Append.append(lPadding);
263         }
264         io2Append.append('\n');
265         return io2Append;
266     }
267
268     /**
269      * encodes a String with the Quoted printable, MIME encoding (see RFC2045).
270      * appends the result of the encoding in a StringBuffer
271      * @param io2Append The StringBuffer which should receive the result
272      * @param i2Encode The String to encode
273      * @param iLength The max number of characters to encode
274      * @return the io2Append buffer
275      **/

276     private static StringBuffer JavaDoc qpencode(StringBuffer JavaDoc io2Append, String JavaDoc i2Encode, int iLength) {
277         io2Append.ensureCapacity(1024);
278         int lCurLineLength = 0;
279         int lPrevChar = -1;
280         char[] l2Encode = i2Encode.toCharArray();
281         try {
282             for (int i = 0;; i++) {
283                 char lCurChar = l2Encode[i];
284                 if (lCurChar > 126 || (lCurChar < 32 && lCurChar != '\n' && lCurChar != '\t') || lCurChar == '=') {
285                     io2Append.append('=');
286                     io2Append.append((char)hex_table[lCurChar >> 4]);
287                     io2Append.append((char)hex_table[lCurChar & 0x0f]);
288                     lCurLineLength += 3;
289                     lPrevChar = -1;
290                 } else if (lCurChar == '\n') {
291                     if (lPrevChar == ' ' || lPrevChar == '\t') {
292                         io2Append.append('=');
293                         io2Append.append(lCurChar);
294                     }
295                     io2Append.append(lCurChar);
296                     lCurLineLength = 0;
297                     lPrevChar = lCurChar;
298                 } else {
299                     io2Append.append(lCurChar);
300                     lCurLineLength++;
301                     lPrevChar = lCurChar;
302                 }
303                 if (lCurLineLength > iLength) {
304                     io2Append.append('=');
305                     io2Append.append('\n');
306                     lCurLineLength = 0;
307                     lPrevChar = '\n';
308                 }
309             }
310         } catch (ArrayIndexOutOfBoundsException JavaDoc e) {
311             //normal exit, this should be faster than a test at each iterations for string with more than
312
//about 40 char
313
}
314
315         if (lCurLineLength > 0) {
316             io2Append.append('=');
317             io2Append.append('\n');
318         }
319         return io2Append;
320     }
321
322     private static String JavaDoc convert2String(IRubyObject l2Conv) {
323         Ruby runtime = l2Conv.getRuntime();
324         if (l2Conv.getMetaClass() != runtime.getString()) {
325             l2Conv = l2Conv.convertToType("String", "to_s", true); //we may need a false here, not sure
326
}
327         return ((RubyString) l2Conv).toString();
328     }
329
330     /**
331      * Decodes <i>str</i> (which may contain binary data) according to the format
332      * string, returning an array of each value extracted.
333      * The format string consists of a sequence of single-character directives.<br/>
334      * Each directive may be followed by a number, indicating the number of times to repeat with this directive. An asterisk (``<code>*</code>'') will use up all
335      * remaining elements. <br/>
336      * The directives <code>sSiIlL</code> may each be followed by an underscore (``<code>_</code>'') to use the underlying platform's native size for the specified type; otherwise, it uses a platform-independent consistent size. <br/>
337      * Spaces are ignored in the format string.
338      * @see RubyArray#pack
339      * <table border="2" width="500" bgcolor="#ffe0e0">
340      * <tr>
341      * <td>
342      * <P></P>
343      * <b>Directives for <a HREF="ref_c_string.html#String.unpack">
344      * <code>String#unpack</code>
345      * </a>
346      * </b> <table class="codebox" cellspacing="0" border="0" cellpadding="3">
347      * <tr bgcolor="#ff9999">
348      * <td valign="top">
349      * <b>Format</b>
350      * </td>
351      * <td valign="top">
352      * <b>Function</b>
353      * </td>
354      * <td valign="top">
355      * <b>Returns</b>
356      * </td>
357      * </tr>
358      * <tr>
359      * <td valign="top">A</td>
360      * <td valign="top">String with trailing nulls and spaces removed.</td>
361      * <td valign="top">String</td>
362      * </tr>
363      * <tr>
364      * <td valign="top">a</td>
365      * <td valign="top">String.</td>
366      * <td valign="top">String</td>
367      * </tr>
368      * <tr>
369      * <td valign="top">B</td>
370      * <td valign="top">Extract bits from each character (msb first).</td>
371      * <td valign="top">String</td>
372      * </tr>
373      * <tr>
374      * <td valign="top">b</td>
375      * <td valign="top">Extract bits from each character (lsb first).</td>
376      * <td valign="top">String</td>
377      * </tr>
378      * <tr>
379      * <td valign="top">C</td>
380      * <td valign="top">Extract a character as an unsigned integer.</td>
381      * <td valign="top">Fixnum</td>
382      * </tr>
383      * <tr>
384      * <td valign="top">c</td>
385      * <td valign="top">Extract a character as an integer.</td>
386      * <td valign="top">Fixnum</td>
387      * </tr>
388      * <tr>
389      * <td valign="top">d</td>
390      * <td valign="top">Treat <em>sizeof(double)</em> characters as a native
391      * double.</td>
392      * <td valign="top">Float</td>
393      * </tr>
394      * <tr>
395      * <td valign="top">E</td>
396      * <td valign="top">Treat <em>sizeof(double)</em> characters as a double in
397      * little-endian byte order.</td>
398      * <td valign="top">Float</td>
399      * </tr>
400      * <tr>
401      * <td valign="top">e</td>
402      * <td valign="top">Treat <em>sizeof(float)</em> characters as a float in
403      * little-endian byte order.</td>
404      * <td valign="top">Float</td>
405      * </tr>
406      * <tr>
407      * <td valign="top">f</td>
408      * <td valign="top">Treat <em>sizeof(float)</em> characters as a native float.</td>
409      * <td valign="top">Float</td>
410      * </tr>
411      * <tr>
412      * <td valign="top">G</td>
413      * <td valign="top">Treat <em>sizeof(double)</em> characters as a double in
414      * network byte order.</td>
415      * <td valign="top">Float</td>
416      * </tr>
417      * <tr>
418      * <td valign="top">g</td>
419      * <td valign="top">Treat <em>sizeof(float)</em> characters as a float in
420      * network byte order.</td>
421      * <td valign="top">Float</td>
422      * </tr>
423      * <tr>
424      * <td valign="top">H</td>
425      * <td valign="top">Extract hex nibbles from each character (most
426      * significant first).</td>
427      * <td valign="top">String</td>
428      * </tr>
429      * <tr>
430      * <td valign="top">h</td>
431      * <td valign="top">Extract hex nibbles from each character (least
432      * significant first).</td>
433      * <td valign="top">String</td>
434      * </tr>
435      * <tr>
436      * <td valign="top">I</td>
437      * <td valign="top">Treat <em>sizeof(int)</em>
438      * <sup>1</sup> successive
439      * characters as an unsigned native integer.</td>
440      * <td valign="top">Integer</td>
441      * </tr>
442      * <tr>
443      * <td valign="top">i</td>
444      * <td valign="top">Treat <em>sizeof(int)</em>
445      * <sup>1</sup> successive
446      * characters as a signed native integer.</td>
447      * <td valign="top">Integer</td>
448      * </tr>
449      * <tr>
450      * <td valign="top">L</td>
451      * <td valign="top">Treat four<sup>1</sup> successive
452      * characters as an unsigned native
453      * long integer.</td>
454      * <td valign="top">Integer</td>
455      * </tr>
456      * <tr>
457      * <td valign="top">l</td>
458      * <td valign="top">Treat four<sup>1</sup> successive
459      * characters as a signed native
460      * long integer.</td>
461      * <td valign="top">Integer</td>
462      * </tr>
463      * <tr>
464      * <td valign="top">M</td>
465      * <td valign="top">Extract a quoted-printable string.</td>
466      * <td valign="top">String</td>
467      * </tr>
468      * <tr>
469      * <td valign="top">m</td>
470      * <td valign="top">Extract a base64 encoded string.</td>
471      * <td valign="top">String</td>
472      * </tr>
473      * <tr>
474      * <td valign="top">N</td>
475      * <td valign="top">Treat four characters as an unsigned long in network
476      * byte order.</td>
477      * <td valign="top">Fixnum</td>
478      * </tr>
479      * <tr>
480      * <td valign="top">n</td>
481      * <td valign="top">Treat two characters as an unsigned short in network
482      * byte order.</td>
483      * <td valign="top">Fixnum</td>
484      * </tr>
485      * <tr>
486      * <td valign="top">P</td>
487      * <td valign="top">Treat <em>sizeof(char *)</em> characters as a pointer, and
488      * return <em>len</em> characters from the referenced location.</td>
489      * <td valign="top">String</td>
490      * </tr>
491      * <tr>
492      * <td valign="top">p</td>
493      * <td valign="top">Treat <em>sizeof(char *)</em> characters as a pointer to a
494      * null-terminated string.</td>
495      * <td valign="top">String</td>
496      * </tr>
497      * <tr>
498      * <td valign="top">S</td>
499      * <td valign="top">Treat two<sup>1</sup> successive characters as an unsigned
500      * short in
501      * native byte order.</td>
502      * <td valign="top">Fixnum</td>
503      * </tr>
504      * <tr>
505      * <td valign="top">s</td>
506      * <td valign="top">Treat two<sup>1</sup> successive
507      * characters as a signed short in
508      * native byte order.</td>
509      * <td valign="top">Fixnum</td>
510      * </tr>
511      * <tr>
512      * <td valign="top">U</td>
513      * <td valign="top">Extract UTF-8 characters as unsigned integers.</td>
514      * <td valign="top">Integer</td>
515      * </tr>
516      * <tr>
517      * <td valign="top">u</td>
518      * <td valign="top">Extract a UU-encoded string.</td>
519      * <td valign="top">String</td>
520      * </tr>
521      * <tr>
522      * <td valign="top">V</td>
523      * <td valign="top">Treat four characters as an unsigned long in little-endian
524      * byte order.</td>
525      * <td valign="top">Fixnum</td>
526      * </tr>
527      * <tr>
528      * <td valign="top">v</td>
529      * <td valign="top">Treat two characters as an unsigned short in little-endian
530      * byte order.</td>
531      * <td valign="top">Fixnum</td>
532      * </tr>
533      * <tr>
534      * <td valign="top">X</td>
535      * <td valign="top">Skip backward one character.</td>
536      * <td valign="top">---</td>
537      * </tr>
538      * <tr>
539      * <td valign="top">x</td>
540      * <td valign="top">Skip forward one character.</td>
541      * <td valign="top">---</td>
542      * </tr>
543      * <tr>
544      * <td valign="top">Z</td>
545      * <td valign="top">String with trailing nulls removed.</td>
546      * <td valign="top">String</td>
547      * </tr>
548      * <tr>
549      * <td valign="top">@</td>
550      * <td valign="top">Skip to the offset given by the length argument.</td>
551      * <td valign="top">---</td>
552      * </tr>
553      * <tr>
554      * <td colspan="9" bgcolor="#ff9999" height="2"><img SRC="dot.gif" width="1" height="1"></td>
555      * </tr>
556      * </table>
557      * <P></P>
558      * <sup>1</sup>&nbsp;May be modified by appending ``_'' to the directive.
559      * <P></P>
560      * </td>
561      * </tr>
562      * </table>
563      *
564      **/

565     public static RubyArray unpack(Ruby runtime, ByteList encodedString,
566             ByteList formatString) {
567         RubyArray result = runtime.newArray();
568         // FIXME: potentially could just use ByteList here?
569
ByteBuffer JavaDoc format = ByteBuffer.wrap(formatString.bytes());
570         ByteBuffer JavaDoc encode = ByteBuffer.wrap(encodedString.bytes());
571         int type = 0;
572         int next = safeGet(format);
573
574         while (next != 0) {
575             type = next;
576             next = safeGet(format);
577             // Next indicates to decode using native encoding format
578
if (next == '_' || next == '!') {
579                 if (NATIVE_CODES.indexOf(type) == -1) {
580                     throw runtime.newArgumentError("'" + next +
581                             "' allowed only after types " + NATIVE_CODES);
582                 }
583                 next = safeGet(format);
584             }
585
586             // How many occurrences of 'type' we want
587
int occurrences = 0;
588             if (next == 0) {
589                 occurrences = 1;
590             } else {
591                 if (next == '*') {
592                     occurrences = IS_STAR;
593                     next = safeGet(format);
594                 } else if (Character.isDigit((char)(next & 0xFF))) {
595                     occurrences = 0;
596                     do {
597                         occurrences = occurrences * 10 + Character.digit((char)(next & 0xFF), 10);
598                         next = safeGet(format);
599                     } while (next != 0 && Character.isDigit((char)(next & 0xFF)));
600                 } else {
601                     occurrences = type == '@' ? 0 : 1;
602                 }
603             }
604
605             // See if we have a converter for the job...
606
Converter converter = converters[type];
607             if (converter != null) {
608                 decode(runtime, encode, occurrences, result, converter);
609                 type = next;
610                 continue;
611             }
612
613             // Otherwise the unpack should be here...
614
switch (type) {
615                 case '@' :
616                     encode.position(occurrences);
617                     break;
618                 case '%' :
619                     throw runtime.newArgumentError("% is not supported");
620                 case 'A' :
621                     {
622                     if (occurrences == IS_STAR || occurrences > encode.remaining()) {
623                         occurrences = encode.remaining();
624                     }
625
626                     byte[] potential = new byte[occurrences];
627                     encode.get(potential);
628
629                     for (int t = occurrences - 1; occurrences > 0; occurrences--, t--) {
630                         byte c = potential[t];
631
632                            if (c != '\0' && c != ' ') {
633                                break;
634                            }
635                     }
636
637                     result.append(RubyString.newString(runtime, new ByteList(potential, 0, occurrences,false)));
638                     }
639                     break;
640                 case 'Z' :
641                     {
642                     if (occurrences == IS_STAR || occurrences > encode.remaining()) {
643                         occurrences = encode.remaining();
644                     }
645
646                     byte[] potential = new byte[occurrences];
647                     encode.get(potential);
648
649                     for (int t = occurrences - 1; occurrences > 0; occurrences--, t--) {
650                         char c = (char)potential[t];
651
652                            if (c != '\0') {
653                                break;
654                            }
655                     }
656
657                     result.append(RubyString.newString(runtime, new ByteList(potential, 0, occurrences,false)));
658                     }
659                     break;
660                 case 'a' :
661                     if (occurrences == IS_STAR || occurrences > encode.remaining()) {
662                         occurrences = encode.remaining();
663                     }
664                     byte[] potential = new byte[occurrences];
665                     encode.get(potential);
666                     result.append(RubyString.newString(runtime, new ByteList(potential,false)));
667                     break;
668                 case 'b' :
669                     {
670                         if (occurrences == IS_STAR || occurrences > encode.remaining() * 8) {
671                             occurrences = encode.remaining() * 8;
672                         }
673                         int bits = 0;
674                         byte[] lElem = new byte[occurrences];
675                         for (int lCurByte = 0; lCurByte < occurrences; lCurByte++) {
676                             if ((lCurByte & 7) != 0) {
677                                 bits >>>= 1;
678                             } else {
679                                 bits = encode.get();
680                             }
681                             lElem[lCurByte] = (bits & 1) != 0 ? (byte)'1' : (byte)'0';
682                         }
683                         result.append(RubyString.newString(runtime, new ByteList(lElem,false)));
684                     }
685                     break;
686                 case 'B' :
687                     {
688                         if (occurrences == IS_STAR || occurrences > encode.remaining() * 8) {
689                             occurrences = encode.remaining() * 8;
690                         }
691                         int bits = 0;
692                         byte[] lElem = new byte[occurrences];
693                         for (int lCurByte = 0; lCurByte < occurrences; lCurByte++) {
694                             if ((lCurByte & 7) != 0)
695                                 bits <<= 1;
696                             else
697                                 bits = encode.get();
698                             lElem[lCurByte] = (bits & 128) != 0 ? (byte)'1' : (byte)'0';
699                         }
700
701                         result.append(RubyString.newString(runtime, new ByteList(lElem,false)));
702                     }
703                     break;
704                 case 'h' :
705                     {
706                         if (occurrences == IS_STAR || occurrences > encode.remaining() * 2) {
707                             occurrences = encode.remaining() * 2;
708                         }
709                         int bits = 0;
710                         byte[] lElem = new byte[occurrences];
711                         for (int lCurByte = 0; lCurByte < occurrences; lCurByte++) {
712                             if ((lCurByte & 1) != 0) {
713                                 bits >>>= 4;
714                             } else {
715                                 bits = encode.get();
716                             }
717                             lElem[lCurByte] = sHexDigits[bits & 15];
718                         }
719                         result.append(RubyString.newString(runtime, new ByteList(lElem,false)));
720                     }
721                     break;
722                 case 'H' :
723                     {
724                         if (occurrences == IS_STAR || occurrences > encode.remaining() * 2) {
725                             occurrences = encode.remaining() * 2;
726                         }
727                         int bits = 0;
728                         byte[] lElem = new byte[occurrences];
729                         for (int lCurByte = 0; lCurByte < occurrences; lCurByte++) {
730                             if ((lCurByte & 1) != 0)
731                                 bits <<= 4;
732                             else
733                                 bits = encode.get();
734                             lElem[lCurByte] = sHexDigits[(bits >>> 4) & 15];
735                         }
736                         result.append(RubyString.newString(runtime, new ByteList(lElem,false)));
737                     }
738                     break;
739
740                 case 'u':
741                 {
742                     int length = encode.remaining() * 3 / 4;
743                     byte[] lElem = new byte[length];
744                     int index = 0;
745                     int s;
746                     int total = 0;
747                     s = encode.get();
748                     while (encode.hasRemaining() && s > ' ' && s < 'a') {
749                         int a, b, c, d;
750                         byte[] hunk = new byte[3];
751
752                         int len = (s - ' ') & 077;
753                         s = safeGet(encode);
754                         total += len;
755                         if (total > length) {
756                             len -= total - length;
757                             total = length;
758                         }
759
760                         while (len > 0) {
761                             int mlen = len > 3 ? 3 : len;
762
763                             if (encode.hasRemaining() && s >= ' ') {
764                                 a = (s - ' ') & 077;
765                                 s = safeGet(encode);
766                             } else
767                                 a = 0;
768                             if (encode.hasRemaining() && s >= ' ') {
769                                 b = (s - ' ') & 077;
770                                 s = safeGet(encode);
771                             } else
772                                 b = 0;
773                             if (encode.hasRemaining() && s >= ' ') {
774                                 c = (s - ' ') & 077;
775                                 s = safeGet(encode);
776                             } else
777                                 c = 0;
778                             if (encode.hasRemaining() && s >= ' ') {
779                                 d = (s - ' ') & 077;
780                                 s = safeGet(encode);
781                             } else
782                                 d = 0;
783                             hunk[0] = (byte)((a << 2 | b >> 4) & 255);
784                             hunk[1] = (byte)((b << 4 | c >> 2) & 255);
785                             hunk[2] = (byte)((c << 6 | d) & 255);
786
787                             for (int i = 0; i < mlen; i++) lElem[index++] = hunk[i];
788                             len -= mlen;
789                         }
790                         if (s == '\r')
791                             s = safeGet(encode);
792                         if (s == '\n')
793                             s = safeGet(encode);
794                         else if (encode.hasRemaining()) {
795                             if (safeGet(encode) == '\n') {
796                                 safeGet(encode); // Possible Checksum Byte
797
} else if (encode.hasRemaining()) {
798                                 encode.position(encode.position() - 1);
799                             }
800                         }
801                     }
802                     result.append(RubyString.newString(runtime, new ByteList(lElem, 0, index,false)));
803                 }
804                 break;
805
806                 case 'm':
807                 {
808                     int length = encode.remaining()*3/4;
809                     byte[] lElem = new byte[length];
810                     int a = -1, b = -1, c = 0, d;
811                     int index = 0;
812                     while (encode.hasRemaining()) {
813                         int s;
814                         do {
815                             s = safeGet(encode);
816                         } while (s == '\r' || s == '\n');
817
818                         if ((a = b64_xtable[s]) == -1) break;
819                         s = safeGet(encode);
820                         if ((b = b64_xtable[s]) == -1) break;
821                         s = safeGet(encode);
822                         if ((c = b64_xtable[s]) == -1) break;
823                         s = safeGet(encode);
824                         if ((d = b64_xtable[s]) == -1) break;
825
826                         lElem[index++] = (byte)((a << 2 | b >> 4) & 255);
827                         lElem[index++] = (byte)((b << 4 | c >> 2) & 255);
828                         lElem[index++] = (byte)((c << 6 | d) & 255);
829                         a = -1;
830                     }
831                     if (a != -1 && b != -1) {
832                         lElem[index++] = (byte)((a << 2 | b >> 4) & 255);
833                         if(c != -1) {
834                             lElem[index++] = (byte)((b << 4 | c >> 2) & 255);
835                         }
836
837                     }
838                     result.append(RubyString.newString(runtime, new ByteList(lElem, 0, index,false)));
839                 }
840                 break;
841
842                 case 'M' :
843                     {
844                         byte[] lElem = new byte[Math.max(encode.remaining(),0)];
845                         int index = 0;
846                         for(;;) {
847                             byte c = safeGet(encode);
848                             if (!encode.hasRemaining()) break;
849                             if (c != '=') {
850                                 lElem[index++] = c;
851                             } else {
852                                 byte c1 = safeGet(encode);
853                                 if (!encode.hasRemaining()) break;
854                                 if (c1 == '\n') continue;
855                                 byte c2 = safeGet(encode);
856                                 if (!encode.hasRemaining()) break;
857                                 byte value = (byte)(Character.digit((char)(c1 & 0xFF), 16) * 16 + Character.digit((char)(c2 & 0xFF), 16));
858                                 lElem[index++] = value;
859                             }
860                         }
861                         result.append(RubyString.newString(runtime, new ByteList(lElem, 0, index,false)));
862                     }
863                     break;
864                 case 'U' :
865                     {
866                         if (occurrences == IS_STAR || occurrences > encode.remaining()) {
867                             occurrences = encode.remaining();
868                         }
869                         //get the correct substring
870
byte[] toUnpack = new byte[occurrences];
871                         encode.get(toUnpack);
872                         CharBuffer JavaDoc lUtf8 = null;
873                         try {
874                             Charset JavaDoc utf8 = Charset.forName("UTF-8");
875                             CharsetDecoder JavaDoc utf8Decoder = utf8.newDecoder();
876                             utf8Decoder.onMalformedInput(CodingErrorAction.REPORT);
877                             utf8Decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
878                             ByteBuffer JavaDoc buffer = ByteBuffer.wrap(toUnpack);
879
880                             lUtf8 = utf8Decoder.decode(buffer);
881                         } catch (CharacterCodingException JavaDoc cce) {
882                             // invalid incoming bytes; fail to encode.
883
throw runtime.newArgumentError("malformed UTF-8 character");
884                         }
885                         while (occurrences-- > 0 && lUtf8.hasRemaining()) {
886                             long lCurChar = lUtf8.get();
887                             result.append(runtime.newFixnum(lCurChar));
888                         }
889                     }
890                     break;
891                  case 'X':
892                      if (occurrences == IS_STAR) {
893                          occurrences = encode.limit() - encode.remaining();
894                      }
895
896                      try {
897                          encode.position(encode.position() - occurrences);
898                      } catch (IllegalArgumentException JavaDoc e) {
899                          throw runtime.newArgumentError("in `unpack': X outside of string");
900                      }
901                      break;
902                  case 'x':
903                       if (occurrences == IS_STAR) {
904                            occurrences = encode.remaining();
905                       }
906
907                       try {
908                           encode.position(encode.position() + occurrences);
909                       } catch (IllegalArgumentException JavaDoc e) {
910                           throw runtime.newArgumentError("in `unpack': x outside of string");
911                       }
912
913                      break;
914             }
915         }
916         return result;
917     }
918
919     private static byte safeGet(ByteBuffer JavaDoc encode) {
920         return encode.hasRemaining() ? encode.get() : 0;
921     }
922
923     public static void decode(Ruby runtime, ByteBuffer JavaDoc encode, int occurrences,
924             RubyArray result, Converter converter) {
925         int lPadLength = 0;
926
927         if (occurrences == IS_STAR) {
928             occurrences = encode.remaining() / converter.size;
929         } else if (occurrences > encode.remaining() / converter.size) {
930             lPadLength = occurrences - encode.remaining() / converter.size;
931             occurrences = encode.remaining() / converter.size;
932         }
933         for (; occurrences-- > 0;) {
934             result.append(converter.decode(runtime, encode));
935         }
936         for (; lPadLength-- > 0;)
937             result.append(runtime.getNil());
938     }
939
940     public static int encode(Ruby runtime, int occurrences, StringBuffer JavaDoc result,
941             List JavaDoc list, int index, Converter converter) {
942         int listSize = list.size();
943
944         while (occurrences-- > 0) {
945             if (listSize-- <= 0) {
946                 throw runtime.newArgumentError(sTooFew);
947             }
948
949             IRubyObject from = (IRubyObject) list.get(index++);
950
951             converter.encode(runtime, from, result);
952         }
953
954         return index;
955     }
956
957     public abstract static class Converter {
958         public int size;
959
960         public Converter(int size) {
961             this.size = size;
962         }
963
964         public abstract IRubyObject decode(Ruby runtime, ByteBuffer JavaDoc format);
965         public abstract void encode(Ruby runtime, IRubyObject from,
966                 StringBuffer JavaDoc result);
967     }
968
969     static class PtrList {
970         private byte[] buffer; // List to be managed
971
private int index; // Pointer location in list
972

973         public PtrList(byte[] bufferString) {
974             buffer = bufferString;
975             index = 0;
976         }
977
978         /**
979          * @return the number of elements between pointer and end of list
980          */

981         public int remaining() {
982             return buffer.length - index;
983         }
984
985         /**
986          * <p>Get substring from current point of desired length and advance
987          * pointer.</p>
988          *
989          * @param length of substring
990          * @return the substring
991          */

992         public String JavaDoc nextSubstring(int length) {
993             // Cannot get substring off end of buffer
994
if (index + length > buffer.length) {
995                 throw new IllegalArgumentException JavaDoc();
996             }
997
998             String JavaDoc substring = null;
999             substring = new String JavaDoc(ByteList.plain(buffer), index, length);
1000
1001            index += length;
1002
1003            return substring;
1004        }
1005
1006        public void setPosition(int position) {
1007            if (position < buffer.length) {
1008                index = position;
1009            }
1010        }
1011
1012        /**
1013         * @return numerical representation of ascii number at ptr
1014         */

1015        public int nextAsciiNumber() {
1016            int i = index;
1017
1018            for (; i < buffer.length; i++) {
1019                if (!Character.isDigit((char)(buffer[i] & 0xFF))) {
1020                    break;
1021                }
1022            }
1023
1024            // An exception will occur if no number is at ptr....
1025
int number = 0;
1026            Integer.parseInt(new String JavaDoc(ByteList.plain(buffer), index, i - index));
1027
1028            // An exception may occur here if an int can't hold this but ...
1029
index = i;
1030            return number;
1031        }
1032
1033        /**
1034         * @return length of list
1035         */

1036        public int getLength() {
1037            return buffer.length;
1038        }
1039
1040        /**
1041         * @return char at the pointer (advancing the pointer) or '\0' if at end.
1042         *
1043         * Note: the pointer gets advanced one past last character to indicate
1044         * that the whole buffer has been read.
1045         */

1046        public int nextByte() {
1047            byte next = 0;
1048
1049            if (index < buffer.length) {
1050                next = buffer[index++];
1051            } else if (index == buffer.length) {
1052                index++;
1053            }
1054
1055            return next & 0xFF;
1056        }
1057
1058        /**
1059         * <p>Backup the pointer occurrences times.</p>
1060         *
1061         * @throws IllegalArgumentException if it backs up past beginning
1062         * of buffer
1063         */

1064        public void backup(int occurrences) {
1065            index -= occurrences;
1066
1067            if (index < 0) {
1068                throw new IllegalArgumentException JavaDoc();
1069            }
1070        }
1071
1072        /**
1073         * @return true if index is at end of the buffer
1074         */

1075        public boolean isAtEnd() {
1076            return index > buffer.length;
1077        }
1078
1079        /**
1080         * @return the current pointer location in buffer
1081         */

1082        public int getIndex() {
1083            return index;
1084        }
1085    }
1086
1087    /**
1088     * shrinks a stringbuffer.
1089     * shrinks a stringbuffer by a number of characters.
1090     * @param i2Shrink the stringbuffer
1091     * @param iLength how much to shrink
1092     * @return the stringbuffer
1093     **/

1094    private static final StringBuffer JavaDoc shrink(StringBuffer JavaDoc i2Shrink, int iLength) {
1095        iLength = i2Shrink.length() - iLength;
1096
1097        if (iLength < 0) {
1098            throw new IllegalArgumentException JavaDoc();
1099        }
1100        i2Shrink.setLength(iLength);
1101        return i2Shrink;
1102    }
1103
1104    /**
1105     * grows a stringbuffer.
1106     * uses the Strings to pad the buffer for a certain length
1107     * @param i2Grow the buffer to grow
1108     * @param iPads the string used as padding
1109     * @param iLength how much padding is needed
1110     * @return the padded buffer
1111     **/

1112    private static final StringBuffer JavaDoc grow(StringBuffer JavaDoc i2Grow, String JavaDoc iPads, int iLength) {
1113        int lPadLength = iPads.length();
1114        while (iLength >= lPadLength) {
1115            i2Grow.append(iPads);
1116            iLength -= lPadLength;
1117        }
1118        i2Grow.append(iPads.substring(0, iLength));
1119        return i2Grow;
1120    }
1121
1122    /**
1123     * pack_pack
1124     *
1125     * Template characters for Array#pack Directive Meaning
1126     * <table class="codebox" cellspacing="0" border="0" cellpadding="3">
1127     * <tr bgcolor="#ff9999">
1128     * <td valign="top">
1129     * <b>Directive</b>
1130     * </td>
1131     * <td valign="top">
1132     * <b>Meaning</b>
1133     * </td>
1134     * </tr>
1135     * <tr>
1136     * <td valign="top">@</td>
1137     * <td valign="top">Moves to absolute position</td>
1138     * </tr>
1139     * <tr>
1140     * <td valign="top">A</td>
1141     * <td valign="top">ASCII string (space padded, count is width)</td>
1142     * </tr>
1143     * <tr>
1144     * <td valign="top">a</td>
1145     * <td valign="top">ASCII string (null padded, count is width)</td>
1146     * </tr>
1147     * <tr>
1148     * <td valign="top">B</td>
1149     * <td valign="top">Bit string (descending bit order)</td>
1150     * </tr>
1151     * <tr>
1152     * <td valign="top">b</td>
1153     * <td valign="top">Bit string (ascending bit order)</td>
1154     * </tr>
1155     * <tr>
1156     * <td valign="top">C</td>
1157     * <td valign="top">Unsigned char</td>
1158     * </tr>
1159     * <tr>
1160     * <td valign="top">c</td>
1161     * <td valign="top">Char</td>
1162     * </tr>
1163     * <tr>
1164     * <td valign="top">d</td>
1165     * <td valign="top">Double-precision float, native format</td>
1166     * </tr>
1167     * <tr>
1168     * <td valign="top">E</td>
1169     * <td valign="top">Double-precision float, little-endian byte order</td>
1170     * </tr>
1171     * <tr>
1172     * <td valign="top">e</td>
1173     * <td valign="top">Single-precision float, little-endian byte order</td>
1174     * </tr>
1175     * <tr>
1176     * <td valign="top">f</td>
1177     * <td valign="top">Single-precision float, native format</td>
1178     * </tr>
1179     * <tr>
1180     * <td valign="top">G</td>
1181     * <td valign="top">Double-precision float, network (big-endian) byte order</td>
1182     * </tr>
1183     * <tr>
1184     * <td valign="top">g</td>
1185     * <td valign="top">Single-precision float, network (big-endian) byte order</td>
1186     * </tr>
1187     * <tr>
1188     * <td valign="top">H</td>
1189     * <td valign="top">Hex string (high nibble first)</td>
1190     * </tr>
1191     * <tr>
1192     * <td valign="top">h</td>
1193     * <td valign="top">Hex string (low nibble first)</td>
1194     * </tr>
1195     * <tr>
1196     * <td valign="top">I</td>
1197     * <td valign="top">Unsigned integer</td>
1198     * </tr>
1199     * <tr>
1200     * <td valign="top">i</td>
1201     * <td valign="top">Integer</td>
1202     * </tr>
1203     * <tr>
1204     * <td valign="top">L</td>
1205     * <td valign="top">Unsigned long</td>
1206     * </tr>
1207     * <tr>
1208     * <td valign="top">l</td>
1209     * <td valign="top">Long</td>
1210     * </tr>
1211     * <tr>
1212     * <td valign="top">M</td>
1213     * <td valign="top">Quoted printable, MIME encoding (see RFC2045)</td>
1214     * </tr>
1215     * <tr>
1216     * <td valign="top">m</td>
1217     * <td valign="top">Base64 encoded string</td>
1218     * </tr>
1219     * <tr>
1220     * <td valign="top">N</td>
1221     * <td valign="top">Long, network (big-endian) byte order</td>
1222     * </tr>
1223     * <tr>
1224     * <td valign="top">n</td>
1225     * <td valign="top">Short, network (big-endian) byte-order</td>
1226     * </tr>
1227     * <tr>
1228     * <td valign="top">P</td>
1229     * <td valign="top">Pointer to a structure (fixed-length string)</td>
1230     * </tr>
1231     * <tr>
1232     * <td valign="top">p</td>
1233     * <td valign="top">Pointer to a null-terminated string</td>
1234     * </tr>
1235     * <tr>
1236     * <td valign="top">S</td>
1237     * <td valign="top">Unsigned short</td>
1238     * </tr>
1239     * <tr>
1240     * <td valign="top">s</td>
1241     * <td valign="top">Short</td>
1242     * </tr>
1243     * <tr>
1244     * <td valign="top">U</td>
1245     * <td valign="top">UTF-8</td>
1246     * </tr>
1247     * <tr>
1248     * <td valign="top">u</td>
1249     * <td valign="top">UU-encoded string</td>
1250     * </tr>
1251     * <tr>
1252     * <td valign="top">V</td>
1253     * <td valign="top">Long, little-endian byte order</td>
1254     * </tr>
1255     * <tr>
1256     * <td valign="top">v</td>
1257     * <td valign="top">Short, little-endian byte order</td>
1258     * </tr>
1259     * <tr>
1260     * <td valign="top">X</td>
1261     * <td valign="top">Back up a byte</td>
1262     * </tr>
1263     * <tr>
1264     * <td valign="top">x</td>
1265     * <td valign="top">Null byte</td>
1266     * </tr>
1267     * <tr>
1268     * <td valign="top">Z</td>
1269     * <td valign="top">Same as ``A''</td>
1270     * </tr>
1271     * <tr>
1272     * <td colspan="9" bgcolor="#ff9999" height="2"><img SRC="dot.gif" width="1" height="1"></td>
1273     * </tr>
1274     * </table>
1275     *
1276     *
1277     * Packs the contents of arr into a binary sequence according to the directives in
1278     * aTemplateString (see preceding table).
1279     * Directives ``A,'' ``a,'' and ``Z'' may be followed by a count, which gives the
1280     * width of the resulting field.
1281     * The remaining directives also may take a count, indicating the number of array
1282     * elements to convert.
1283     * If the count is an asterisk (``*''] = all remaining array elements will be
1284     * converted.
1285     * Any of the directives ``sSiIlL'' may be followed by an underscore (``_'') to use
1286     * the underlying platform's native size for the specified type; otherwise, they
1287     * use a platform-independent size. Spaces are ignored in the template string.
1288     * @see RubyString#unpack
1289     **/

1290    public static RubyString pack(Ruby runtime, List JavaDoc list, byte[] formatString) {
1291        ByteBuffer JavaDoc format = ByteBuffer.wrap((byte[])formatString);
1292        StringBuffer JavaDoc result = new StringBuffer JavaDoc();
1293        int listSize = list.size();
1294        int type = 0;
1295        int next = safeGet(format);
1296
1297        int idx = 0;
1298        String JavaDoc lCurElemString;
1299
1300        mainLoop: while (next != 0) {
1301            type = next;
1302            next = safeGet(format);
1303            while (Character.isWhitespace((char)(type&0xFF))) { // skip all spaces
1304
if (next == 0) break mainLoop;
1305                type = next;
1306                next = safeGet(format);
1307            }
1308
1309            if (next == '!' || next == '_') {
1310                if (NATIVE_CODES.indexOf(type) == -1) {
1311                    throw runtime.newArgumentError("'" + next +
1312                            "' allowed only after types " + NATIVE_CODES);
1313                }
1314
1315                next = safeGet(format);
1316            }
1317
1318            // Determine how many of type are needed (default: 1)
1319
int occurrences = 1;
1320            boolean isStar = false;
1321            if (next != 0) {
1322                if (next == '*') {
1323                    if ("@Xxu".indexOf(type) != -1) {
1324                        occurrences = 0;
1325                    } else {
1326                        occurrences = listSize;
1327                        isStar = true;
1328                    }
1329                    next = safeGet(format);
1330                } else if (Character.isDigit((char)(next & 0xFF))) {
1331                    occurrences = 0;
1332                    do {
1333                        occurrences = occurrences * 10 + Character.digit((char)(next & 0xFF), 10);
1334                        next = safeGet(format);
1335                    } while (next != 0 && Character.isDigit((char)(next & 0xFF)));
1336                }
1337            }
1338
1339            Converter converter = converters[type];
1340
1341            if (converter != null) {
1342                idx = encode(runtime, occurrences, result, list, idx, converter);
1343                continue;
1344            }
1345
1346            switch (type) {
1347                case '%' :
1348                    throw runtime.newArgumentError("% is not supported");
1349                case 'A' :
1350                case 'a' :
1351                case 'Z' :
1352                case 'B' :
1353                case 'b' :
1354                case 'H' :
1355                case 'h' :
1356                    {
1357                        if (listSize-- <= 0) {
1358                            throw runtime.newArgumentError(sTooFew);
1359                        }
1360
1361                        IRubyObject from = (IRubyObject) list.get(idx++);
1362                        lCurElemString = from == runtime.getNil() ? "" : from.convertToString().toString();
1363
1364                        if (isStar) {
1365                            occurrences = lCurElemString.length();
1366                        }
1367
1368                        switch (type) {
1369                            case 'a' :
1370                            case 'A' :
1371                            case 'Z' :
1372                                if (lCurElemString.length() >= occurrences) {
1373                                    result.append(lCurElemString.toCharArray(), 0, occurrences);
1374                                } else {//need padding
1375
//I'm fairly sure there is a library call to create a
1376
//string filled with a given char with a given length but I couldn't find it
1377
result.append(lCurElemString);
1378                                    occurrences -= lCurElemString.length();
1379                                    grow(result, (type == 'a') ? sNil10 : sSp10, occurrences);
1380                                }
1381                            break;
1382
1383                            //I believe there is a bug in the b and B case we skip a char too easily
1384
case 'b' :
1385                                {
1386                                    int currentByte = 0;
1387                                    int padLength = 0;
1388
1389                                    if (occurrences > lCurElemString.length()) {
1390                                        padLength = occurrences - lCurElemString.length();
1391                                        occurrences = lCurElemString.length();
1392                                    }
1393
1394                                    for (int i = 0; i < occurrences;) {
1395                                        if ((lCurElemString.charAt(i++) & 1) != 0) {//if the low bit is set
1396
currentByte |= 128; //set the high bit of the result
1397
}
1398
1399                                        if ((i & 7) == 0) {
1400                                            result.append((char) (currentByte & 0xff));
1401                                            currentByte = 0;
1402                                            continue;
1403                                        }
1404
1405                                           //if the index is not a multiple of 8, we are not on a byte boundary
1406
currentByte >>= 1; //shift the byte
1407
}
1408
1409                                    if ((occurrences & 7) != 0) { //if the length is not a multiple of 8
1410
currentByte >>= 7 - (occurrences & 7); //we need to pad the last byte
1411
result.append((char) (currentByte & 0xff));
1412                                    }
1413
1414                                    //do some padding, I don't understand the padding strategy
1415
result.setLength(result.length() + padLength);
1416                                }
1417                            break;
1418                            case 'B' :
1419                                {
1420                                    int currentByte = 0;
1421                                    int padLength = 0;
1422
1423                                    if (occurrences > lCurElemString.length()) {
1424                                        padLength = occurrences - lCurElemString.length();
1425                                        occurrences = lCurElemString.length();
1426                                    }
1427
1428                                    for (int i = 0; i < occurrences;) {
1429                                        currentByte |= lCurElemString.charAt(i++) & 1;
1430
1431                                        // we filled up current byte; append it and create next one
1432
if ((i & 7) == 0) {
1433                                            result.append((char) (currentByte & 0xff));
1434                                            currentByte = 0;
1435                                            continue;
1436                                        }
1437
1438                                        //if the index is not a multiple of 8, we are not on a byte boundary
1439
currentByte <<= 1;
1440                                    }
1441
1442                                    if ((occurrences & 7) != 0) { //if the length is not a multiple of 8
1443
currentByte <<= 7 - (occurrences & 7); //we need to pad the last byte
1444
result.append((char) (currentByte & 0xff));
1445                                    }
1446
1447                                    result.setLength(result.length() + padLength);
1448                                }
1449                            break;
1450                            case 'h' :
1451                                {
1452                                    int currentByte = 0;
1453                                    int padLength = 0;
1454
1455                                    if (occurrences > lCurElemString.length()) {
1456                                        padLength = occurrences - lCurElemString.length();
1457                                        occurrences = lCurElemString.length();
1458                                    }
1459
1460                                    for (int i = 0; i < occurrences;) {
1461                                        char currentChar = lCurElemString.charAt(i++);
1462
1463                                        if (Character.isJavaIdentifierStart(currentChar)) {
1464                                            //this test may be too lax but it is the same as in MRI
1465
currentByte |= (((currentChar & 15) + 9) & 15) << 4;
1466                                        } else {
1467                                            currentByte |= (currentChar & 15) << 4;
1468                                        }
1469
1470                                        if ((i & 1) != 0) {
1471                                            currentByte >>= 4;
1472                                        } else {
1473                                            result.append((char) (currentByte & 0xff));
1474                                            currentByte = 0;
1475                                        }
1476                                    }
1477
1478                                    if ((occurrences & 1) != 0) {
1479                                        result.append((char) (currentByte & 0xff));
1480                                    }
1481
1482                                    result.setLength(result.length() + padLength);
1483                                }
1484                            break;
1485                            case 'H' :
1486                                {
1487                                    int currentByte = 0;
1488                                    int padLength = 0;
1489
1490                                    if (occurrences > lCurElemString.length()) {
1491                                        padLength = occurrences - lCurElemString.length();
1492                                        occurrences = lCurElemString.length();
1493                                    }
1494
1495                                    for (int i = 0; i < occurrences;) {
1496                                        char currentChar = lCurElemString.charAt(i++);
1497
1498                                        if (Character.isJavaIdentifierStart(currentChar)) {
1499                                            //this test may be too lax but it is the same as in MRI
1500
currentByte |= ((currentChar & 15) + 9) & 15;
1501                                        } else {
1502                                            currentByte |= currentChar & 15;
1503                                        }
1504
1505                                        if ((i & 1) != 0) {
1506                                            currentByte <<= 4;
1507                                        } else {
1508                                            result.append((char) (currentByte & 0xff));
1509                                            currentByte = 0;
1510                                        }
1511                                    }
1512
1513                                    if ((occurrences & 1) != 0) {
1514                                        result.append((char) (currentByte & 0xff));
1515                                    }
1516
1517                                    result.setLength(result.length() + padLength);
1518                                }
1519                            break;
1520                        }
1521                        break;
1522                    }
1523
1524                case 'x' :
1525                    grow(result, sNil10, occurrences);
1526                    break;
1527                case 'X' :
1528                    try {
1529                        shrink(result, occurrences);
1530                    } catch (IllegalArgumentException JavaDoc e) {
1531                        throw runtime.newArgumentError("in `pack': X outside of string");
1532                    }
1533                    break;
1534                case '@' :
1535                    occurrences -= result.length();
1536                    if (occurrences > 0) {
1537                        grow(result, sNil10, occurrences);
1538                    }
1539                    occurrences = -occurrences;
1540                    if (occurrences > 0) {
1541                        shrink(result, occurrences);
1542                    }
1543                    break;
1544                case 'u' :
1545                case 'm' :
1546                    {
1547                        if (listSize-- <= 0) {
1548                            throw runtime.newArgumentError(sTooFew);
1549                        }
1550                        IRubyObject from = (IRubyObject) list.get(idx++);
1551                        lCurElemString = from == runtime.getNil() ? "" : from.convertToString().toString();
1552                        occurrences = occurrences <= 2 ? 45 : occurrences / 3 * 3;
1553
1554                        for (;;) {
1555                            encodes(runtime, result, lCurElemString, occurrences, (char)type);
1556
1557                            if (occurrences >= lCurElemString.length()) {
1558                                break;
1559                            }
1560
1561                            lCurElemString = lCurElemString.substring(occurrences);
1562                        }
1563                    }
1564                    break;
1565                case 'M' :
1566                    {
1567                       if (listSize-- <= 0) {
1568                           throw runtime.newArgumentError(sTooFew);
1569                       }
1570
1571                       IRubyObject from = (IRubyObject) list.get(idx++);
1572                       lCurElemString = from == runtime.getNil() ? "" : from.convertToString().toString();
1573
1574                       if (occurrences <= 1) {
1575                           occurrences = 72;
1576                       }
1577
1578                       qpencode(result, lCurElemString, occurrences);
1579                    }
1580                    break;
1581                case 'U' :
1582
1583                    char[] c = new char[occurrences];
1584                    for (int cIndex = 0; occurrences-- > 0; cIndex++) {
1585                        if (listSize-- <= 0) {
1586                           throw runtime.newArgumentError(sTooFew);
1587                        }
1588
1589                        IRubyObject from = (IRubyObject) list.get(idx++);
1590                        long l = from == runtime.getNil() ? 0 : RubyNumeric.num2long(from);
1591
1592                        c[cIndex] = (char) l;
1593                    }
1594
1595                    try {
1596                        byte[] bytes = new String JavaDoc(c).getBytes("UTF8");
1597                        result.append(RubyString.bytesToString(bytes));
1598                    } catch (java.io.UnsupportedEncodingException JavaDoc e) {
1599                        assert false : "can't convert to UTF8";
1600                    }
1601                    break;
1602            }
1603        }
1604        return runtime.newString(result.toString());
1605    }
1606
1607    /**
1608     * Retrieve an encoded int in little endian starting at index in the
1609     * string value.
1610     *
1611     * @param encode string to get int from
1612     * @return the decoded integer
1613     */

1614    private static int decodeIntLittleEndian(ByteBuffer JavaDoc encode) {
1615        encode.order(ByteOrder.LITTLE_ENDIAN);
1616        int value = encode.getInt();
1617        encode.order(ByteOrder.BIG_ENDIAN);
1618        return value;
1619    }
1620
1621    /**
1622     * Retrieve an encoded int in little endian starting at index in the
1623     * string value.
1624     *
1625     * @param encode string to get int from
1626     * @return the decoded integer
1627     */

1628    private static int decodeIntBigEndian(ByteBuffer JavaDoc encode) {
1629        return encode.getInt();
1630    }
1631
1632    /**
1633     * Retrieve an encoded int in big endian starting at index in the string
1634     * value.
1635     *
1636     * @param encode string to get int from
1637     * @return the decoded integer
1638     */

1639    private static long decodeIntUnsignedBigEndian(ByteBuffer JavaDoc encode) {
1640        return (long)encode.getInt() & 0xFFFFFFFFL;
1641    }
1642
1643    /**
1644     * Retrieve an encoded int in little endian starting at index in the
1645     * string value.
1646     *
1647     * @param encode the encoded string
1648     * @return the decoded integer
1649     */

1650    private static long decodeIntUnsignedLittleEndian(ByteBuffer JavaDoc encode) {
1651        encode.order(ByteOrder.LITTLE_ENDIAN);
1652        long value = encode.getInt() & 0xFFFFFFFFL;
1653        encode.order(ByteOrder.BIG_ENDIAN);
1654        return value;
1655    }
1656
1657    /**
1658     * Encode an int in little endian format into a packed representation.
1659     *
1660     * @param result to be appended to
1661     * @param s the integer to encode
1662     */

1663    private static void encodeIntLittleEndian(StringBuffer JavaDoc result, int s) {
1664        result.append((char) (s & 0xff)).append((char) ((s >> 8) & 0xff));
1665        result.append((char) ((s>>16) & 0xff)).append((char) ((s>>24) &0xff));
1666    }
1667
1668    /**
1669     * Encode an int in big-endian format into a packed representation.
1670     *
1671     * @param result to be appended to
1672     * @param s the integer to encode
1673     */

1674    private static void encodeIntBigEndian(StringBuffer JavaDoc result, int s) {
1675        result.append((char) ((s>>24) &0xff)).append((char) ((s>>16) &0xff));
1676        result.append((char) ((s >> 8) & 0xff)).append((char) (s & 0xff));
1677    }
1678
1679    /**
1680     * Decode a long in big-endian format from a packed value
1681     *
1682     * @param encode string to get int from
1683     * @return the long value
1684     */

1685    private static long decodeLongBigEndian(ByteBuffer JavaDoc encode) {
1686        int c1 = decodeIntBigEndian(encode);
1687        int c2 = decodeIntBigEndian(encode);
1688
1689        return ((long) c1 << 32) + (c2 & 0xffffffffL);
1690    }
1691
1692    /**
1693     * Decode a long in little-endian format from a packed value
1694     *
1695     * @param encode string to get int from
1696     * @return the long value
1697     */

1698    private static long decodeLongLittleEndian(ByteBuffer JavaDoc encode) {
1699        int c1 = decodeIntLittleEndian(encode);
1700        int c2 = decodeIntLittleEndian(encode);
1701
1702        return ((long) c2 << 32) + (c1 & 0xffffffffL);
1703    }
1704
1705    /**
1706     * Encode a long in little-endian format into a packed value
1707     *
1708     * @param result to pack long into
1709     * @param l is the long to encode
1710     */

1711    private static void encodeLongLittleEndian(StringBuffer JavaDoc result, long l) {
1712        encodeIntLittleEndian(result, (int) (l & 0xffffffff));
1713        encodeIntLittleEndian(result, (int) (l >>> 32));
1714    }
1715
1716    /**
1717     * Encode a long in big-endian format into a packed value
1718     *
1719     * @param result to pack long into
1720     * @param l is the long to encode
1721     */

1722    private static void encodeLongBigEndian(StringBuffer JavaDoc result, long l) {
1723        encodeIntBigEndian(result, (int) (l >>> 32));
1724        encodeIntBigEndian(result, (int) (l & 0xffffffff));
1725    }
1726
1727    /**
1728     * Decode a double from a packed value
1729     *
1730     * @param encode string to get int from
1731     * @return the double value
1732     */

1733    private static double decodeDoubleLittleEndian(ByteBuffer JavaDoc encode) {
1734        return Double.longBitsToDouble(decodeLongLittleEndian(encode));
1735    }
1736
1737    /**
1738     * Decode a double in big-endian from a packed value
1739     *
1740     * @param encode string to get int from
1741     * @return the double value
1742     */

1743    private static double decodeDoubleBigEndian(ByteBuffer JavaDoc encode) {
1744        return Double.longBitsToDouble(decodeLongBigEndian(encode));
1745    }
1746
1747    /**
1748     * Encode a double in little endian format into a packed value
1749     *
1750     * @param result to pack double into
1751     * @param d is the double to encode
1752     */

1753    private static void encodeDoubleLittleEndian(StringBuffer JavaDoc result, double d) {
1754        encodeLongLittleEndian(result, Double.doubleToLongBits(d));
1755    }
1756
1757    /**
1758     * Encode a double in big-endian format into a packed value
1759     *
1760     * @param result to pack double into
1761     * @param d is the double to encode
1762     */

1763    private static void encodeDoubleBigEndian(StringBuffer JavaDoc result, double d) {
1764        encodeLongBigEndian(result, Double.doubleToLongBits(d));
1765    }
1766
1767    /**
1768     * Decode a float in big-endian from a packed value
1769     *
1770     * @param encode string to get int from
1771     * @return the double value
1772     */

1773    private static float decodeFloatBigEndian(ByteBuffer JavaDoc encode) {
1774        return Float.intBitsToFloat(decodeIntBigEndian(encode));
1775    }
1776
1777    /**
1778     * Decode a float in little-endian from a packed value
1779     *
1780     * @param encode string to get int from
1781     * @return the double value
1782     */

1783    private static float decodeFloatLittleEndian(ByteBuffer JavaDoc encode) {
1784        return Float.intBitsToFloat(decodeIntLittleEndian(encode));
1785    }
1786
1787    /**
1788     * Encode a float in little endian format into a packed value
1789     * @param result to pack float into
1790     * @param f is the float to encode
1791     */

1792    private static void encodeFloatLittleEndian(StringBuffer JavaDoc result, float f) {
1793        encodeIntLittleEndian(result, Float.floatToIntBits(f));
1794    }
1795
1796    /**
1797     * Encode a float in big-endian format into a packed value
1798     * @param result to pack float into
1799     * @param f is the float to encode
1800     */

1801    private static void encodeFloatBigEndian(StringBuffer JavaDoc result, float f) {
1802        encodeIntBigEndian(result, Float.floatToIntBits(f));
1803    }
1804
1805    /**
1806     * Decode a short in big-endian from a packed value
1807     *
1808     * @param encode string to get int from
1809     * @return the short value
1810     */

1811    private static int decodeShortUnsignedLittleEndian(ByteBuffer JavaDoc encode) {
1812        encode.order(ByteOrder.LITTLE_ENDIAN);
1813        int value = encode.getShort() & 0xFFFF;
1814        encode.order(ByteOrder.BIG_ENDIAN);
1815        return value;
1816    }
1817
1818    /**
1819     * Decode a short in big-endian from a packed value
1820     *
1821     * @param encode string to get int from
1822     * @return the short value
1823     */

1824    private static int decodeShortUnsignedBigEndian(ByteBuffer JavaDoc encode) {
1825        int value = encode.getShort() & 0xFFFF;
1826        return value;
1827    }
1828
1829    /**
1830     * Decode a short in big-endian from a packed value
1831     *
1832     * @param encode string to get int from
1833     * @return the short value
1834     */

1835    private static short decodeShortBigEndian(ByteBuffer JavaDoc encode) {
1836        return encode.getShort();
1837    }
1838
1839    /**
1840     * Encode an short in little endian format into a packed representation.
1841     *
1842     * @param result to be appended to
1843     * @param s the short to encode
1844     */

1845    private static void encodeShortLittleEndian(StringBuffer JavaDoc result, int s) {
1846        result.append((char) (s & 0xff)).append((char) ((s & 0xff00) >> 8));
1847    }
1848
1849    /**
1850     * Encode an shortin big-endian format into a packed representation.
1851     *
1852     * @param result to be appended to
1853     * @param s the short to encode
1854     */

1855    private static void encodeShortBigEndian(StringBuffer JavaDoc result, int s) {
1856        result.append((char) ((s & 0xff00) >> 8)).append((char) (s & 0xff));
1857    }
1858}
1859
Popular Tags