KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > filesys > util > DataPacker


1 /*
2  * Copyright (C) 2005 Alfresco, Inc.
3  *
4  * Licensed under the Mozilla Public License version 1.1
5  * with a permitted attribution clause. You may obtain a
6  * copy of the License at
7  *
8  * http://www.alfresco.org/legal/license.txt
9  *
10  * Unless required by applicable law or agreed to in writing,
11  * software distributed under the License is distributed on an
12  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific
14  * language governing permissions and limitations under the
15  * License.
16  */

17 package org.alfresco.filesys.util;
18
19 /**
20  * The data packing class is a static class that is used to pack and unpack basic data types to/from
21  * network byte order and Intel byte order.
22  */

23 public final class DataPacker
24 {
25
26     // Flag to indicate the byte order of the platform that we are currently
27
// running on.
28

29     private static boolean bigEndian = false;
30
31     /**
32      * Return the current endian setting.
33      *
34      * @return true if the system is big endian, else false.
35      */

36     public final static boolean isBigEndian()
37     {
38         return bigEndian;
39     }
40
41     /**
42      * Unpack a null terminated data string from the data buffer.
43      *
44      * @param typ Data type, as specified by SMBDataType.
45      * @param bytarray Byte array to unpack the string value from.
46      * @param pos Offset to start unpacking the string value.
47      * @param maxlen Maximum length of data to be searched for a null character.
48      * @param uni String is Unicode if true, else ASCII
49      * @return String, else null if the terminating null character was not found.
50      */

51     public final static String JavaDoc getDataString(char typ, byte[] bytarray, int pos, int maxlen, boolean uni)
52     {
53
54         // Check if the data string has the required data type
55

56         if (bytarray[pos++] == (byte) typ)
57         {
58
59             // Extract the null terminated string
60

61             if (uni == true)
62                 return getUnicodeString(bytarray, wordAlign(pos), maxlen / 2);
63             else
64                 return getString(bytarray, pos, maxlen - 1);
65         }
66
67         // Invalid data type
68

69         return null;
70     }
71
72     /**
73      * Unpack a 32-bit integer.
74      *
75      * @param buf Byte buffer containing the integer to be unpacked.
76      * @param pos Position within the buffer that the integer is stored.
77      * @return The unpacked 32-bit integer value.
78      * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
79      */

80     public final static int getInt(byte[] buf, int pos) throws java.lang.IndexOutOfBoundsException JavaDoc
81     {
82
83         // Check if the byte array is long enough
84

85         if (buf.length < pos + 3)
86             throw new java.lang.IndexOutOfBoundsException JavaDoc();
87
88         // Unpack the 32-bit value
89

90         int i1 = (int) buf[pos] & 0xFF;
91         int i2 = (int) buf[pos + 1] & 0xFF;
92         int i3 = (int) buf[pos + 2] & 0xFF;
93         int i4 = (int) buf[pos + 3] & 0xFF;
94
95         int iVal = (i1 << 24) + (i2 << 16) + (i3 << 8) + i4;
96
97         // Return the unpacked value
98

99         return iVal;
100     }
101
102     /**
103      * Unpack a 32-bit integer that is stored in Intel format.
104      *
105      * @param bytarray Byte array containing the Intel integer to be unpacked.
106      * @param pos Offset that the Intel integer is stored within the byte array.
107      * @return Unpacked integer value.
108      * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
109      */

110     public final static int getIntelInt(byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException JavaDoc
111     {
112
113         // Check if the byte array is long enough to restore the int
114

115         if (bytarray.length < pos + 3)
116             throw new java.lang.IndexOutOfBoundsException JavaDoc();
117
118         // Determine the byte ordering for this platform, and restore the int
119

120         int iVal = 0;
121
122         // Restore the int value from the byte array
123

124         int i1 = (int) bytarray[pos + 3] & 0xFF;
125         int i2 = (int) bytarray[pos + 2] & 0xFF;
126         int i3 = (int) bytarray[pos + 1] & 0xFF;
127         int i4 = (int) bytarray[pos] & 0xFF;
128
129         iVal = (i1 << 24) + (i2 << 16) + (i3 << 8) + i4;
130
131         // Return the int value
132

133         return iVal;
134     }
135
136     /**
137      * Unpack a 64-bit long.
138      *
139      * @param buf Byte buffer containing the integer to be unpacked.
140      * @param pos Position within the buffer that the integer is stored.
141      * @return The unpacked 64-bit long value.
142      * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
143      */

144     public final static long getLong(byte[] buf, int pos) throws java.lang.IndexOutOfBoundsException JavaDoc
145     {
146
147         // Check if the byte array is long enough to restore the long
148

149         if (buf.length < pos + 7)
150             throw new java.lang.IndexOutOfBoundsException JavaDoc();
151
152         // Restore the long value from the byte array
153

154         long lVal = 0L;
155
156         for (int i = 0; i < 8; i++)
157         {
158
159             // Get the current byte, shift the value and add to the return value
160

161             long curVal = (long) buf[pos + i] & 0xFF;
162             curVal = curVal << ((7 - i) * 8);
163             lVal += curVal;
164         }
165
166         // Return the long value
167

168         return lVal;
169     }
170
171     /**
172      * Unpack a 64-bit integer that is stored in Intel format.
173      *
174      * @param bytarray Byte array containing the Intel long to be unpacked.
175      * @param pos Offset that the Intel integer is stored within the byte array.
176      * @return Unpacked long value.
177      * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
178      */

179     public final static long getIntelLong(byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException JavaDoc
180     {
181
182         // Check if the byte array is long enough to restore the long
183

184         if (bytarray.length < pos + 7)
185             throw new java.lang.IndexOutOfBoundsException JavaDoc();
186
187         // Restore the long value from the byte array
188

189         long lVal = 0L;
190
191         for (int i = 0; i < 8; i++)
192         {
193
194             // Get the current byte, shift the value and add to the return value
195

196             long curVal = (long) bytarray[pos + i] & 0xFF;
197             curVal = curVal << (i * 8);
198             lVal += curVal;
199         }
200
201         // Return the long value
202

203         return lVal;
204     }
205
206     /**
207      * Unpack a 16-bit value that is stored in Intel format.
208      *
209      * @param bytarray Byte array containing the short value to be unpacked.
210      * @param pos Offset to start unpacking the short value.
211      * @return Unpacked short value.
212      * @exception java.lang.IndexOutOfBoiundsException If there is not enough data in the buffer.
213      */

214     public final static int getIntelShort(byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException JavaDoc
215     {
216
217         // Check if the byte array is long enough to restore the int
218

219         if (bytarray.length < pos)
220             throw new java.lang.IndexOutOfBoundsException JavaDoc();
221
222         // Restore the short value from the byte array
223

224         int sVal = (((int) bytarray[pos + 1] << 8) + ((int) bytarray[pos] & 0xFF));
225
226         // Return the short value
227

228         return sVal & 0xFFFF;
229     }
230
231     /**
232      * Unpack a 16-bit value.
233      *
234      * @param bytarray Byte array containing the short to be unpacked.
235      * @param pos Offset within the byte array that the short is stored.
236      * @return Unpacked short value.
237      * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
238      */

239     public final static int getShort(byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException JavaDoc
240     {
241
242         // Check if the byte array is long enough to restore the int
243

244         if (bytarray.length < pos)
245             throw new java.lang.IndexOutOfBoundsException JavaDoc();
246
247         // Determine the byte ordering for this platform, and restore the short
248

249         int sVal = 0;
250
251         if (bigEndian == true)
252         {
253
254             // Big endian
255

256             sVal = ((((int) bytarray[pos + 1]) << 8) + ((int) bytarray[pos] & 0xFF));
257         }
258         else
259         {
260
261             // Little endian
262

263             sVal = ((((int) bytarray[pos]) << 8) + ((int) bytarray[pos + 1] & 0xFF));
264         }
265
266         // Return the short value
267

268         return sVal & 0xFFFF;
269     }
270
271     /**
272      * Unpack a null terminated string from the data buffer.
273      *
274      * @param bytarray Byte array to unpack the string value from.
275      * @param pos Offset to start unpacking the string value.
276      * @param maxlen Maximum length of data to be searched for a null character.
277      * @return String, else null if the terminating null character was not found.
278      */

279     public final static String JavaDoc getString(byte[] bytarray, int pos, int maxlen)
280     {
281
282         // Search for the trailing null
283

284         int maxpos = pos + maxlen;
285         int endpos = pos;
286
287         while (bytarray[endpos] != 0x00 && endpos < maxpos)
288             endpos++;
289
290         // Check if we reached the end of the buffer
291

292         if (endpos <= maxpos)
293             return new String JavaDoc(bytarray, pos, endpos - pos);
294         return null;
295     }
296
297     /**
298      * Unpack a null terminated string from the data buffer. The string may be ASCII or Unicode.
299      *
300      * @param bytarray Byte array to unpack the string value from.
301      * @param pos Offset to start unpacking the string value.
302      * @param maxlen Maximum length of data to be searched for a null character.
303      * @param isUni Unicode string if true, else ASCII string
304      * @return String, else null if the terminating null character was not found.
305      */

306     public final static String JavaDoc getString(byte[] bytarray, int pos, int maxlen, boolean isUni)
307     {
308
309         // Get a string from the buffer
310

311         String JavaDoc str = null;
312
313         if (isUni)
314             str = getUnicodeString(bytarray, pos, maxlen);
315         else
316             str = getString(bytarray, pos, maxlen);
317
318         // return the string
319

320         return str;
321     }
322
323     /**
324      * Unpack a null terminated Unicode string from the data buffer.
325      *
326      * @param byt Byte array to unpack the string value from.
327      * @param pos Offset to start unpacking the string value.
328      * @param maxlen Maximum length of data to be searched for a null character.
329      * @return String, else null if the terminating null character was not found.
330      */

331     public final static String JavaDoc getUnicodeString(byte[] byt, int pos, int maxlen)
332     {
333
334         // Check for an empty string
335

336         if (maxlen == 0)
337             return "";
338
339         // Search for the trailing null
340

341         int maxpos = pos + (maxlen * 2);
342         int endpos = pos;
343         char[] chars = new char[maxlen];
344         int cpos = 0;
345         char curChar;
346
347         do
348         {
349
350             // Get a Unicode character from the buffer
351

352             curChar = (char) (((byt[endpos + 1] & 0xFF) << 8) + (byt[endpos] & 0xFF));
353
354             // Add the character to the array
355

356             chars[cpos++] = curChar;
357
358             // Update the buffer pointer
359

360             endpos += 2;
361
362         } while (curChar != 0 && endpos < maxpos);
363
364         // Check if we reached the end of the buffer
365

366         if (endpos <= maxpos)
367         {
368             if (curChar == 0)
369                 cpos--;
370             return new String JavaDoc(chars, 0, cpos);
371         }
372         return null;
373     }
374
375     /**
376      * Pack a 32-bit integer into the supplied byte buffer.
377      *
378      * @param val Integer value to be packed.
379      * @param bytarray Byte buffer to pack the integer value into.
380      * @param pos Offset to start packing the integer value.
381      * @exception java.lang.IndexOutOfBoundsException If the buffer does not have enough space.
382      */

383     public final static void putInt(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException JavaDoc
384     {
385
386         // Check if the byte array is long enough to store the int
387

388         if (bytarray.length < pos + 3)
389             throw new java.lang.IndexOutOfBoundsException JavaDoc();
390
391         // Pack the integer value
392

393         bytarray[pos] = (byte) ((val >> 24) & 0xFF);
394         bytarray[pos + 1] = (byte) ((val >> 16) & 0xFF);
395         bytarray[pos + 2] = (byte) ((val >> 8) & 0xFF);
396         bytarray[pos + 3] = (byte) (val & 0xFF);
397     }
398
399     /**
400      * Pack an 32-bit integer value in Intel format.
401      *
402      * @param val Integer value to be packed.
403      * @param bytarray Byte array to pack the value into.
404      * @param pos Offset to start packing the integer value.
405      * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
406      */

407     public final static void putIntelInt(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException JavaDoc
408     {
409
410         // Check if the byte array is long enough to store the int
411

412         if (bytarray.length < pos + 3)
413             throw new java.lang.IndexOutOfBoundsException JavaDoc();
414
415         // Store the int value in the byte array
416

417         bytarray[pos + 3] = (byte) ((val >> 24) & 0xFF);
418         bytarray[pos + 2] = (byte) ((val >> 16) & 0xFF);
419         bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
420         bytarray[pos] = (byte) (val & 0xFF);
421     }
422
423     /**
424      * Pack a 64-bit integer value into the buffer
425      *
426      * @param val Integer value to be packed.
427      * @param bytarray Byte array to pack the value into.
428      * @param pos Offset to start packing the integer value.
429      * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
430      */

431     public final static void putLong(long val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException JavaDoc
432     {
433
434         // Check if the byte array is long enough to store the int
435

436         if (bytarray.length < pos + 7)
437             throw new java.lang.IndexOutOfBoundsException JavaDoc();
438
439         // Store the long value in the byte array
440

441         bytarray[pos] = (byte) ((val >> 56) & 0xFF);
442         bytarray[pos + 1] = (byte) ((val >> 48) & 0xFF);
443         bytarray[pos + 2] = (byte) ((val >> 40) & 0xFF);
444         bytarray[pos + 3] = (byte) ((val >> 32) & 0xFF);
445         bytarray[pos + 4] = (byte) ((val >> 24) & 0xFF);
446         bytarray[pos + 5] = (byte) ((val >> 16) & 0xFF);
447         bytarray[pos + 6] = (byte) ((val >> 8) & 0xFF);
448         bytarray[pos + 7] = (byte) (val & 0xFF);
449     }
450
451     /**
452      * Pack a 64-bit integer value in Intel format.
453      *
454      * @param val Integer value to be packed.
455      * @param bytarray Byte array to pack the value into.
456      * @param pos Offset to start packing the integer value.
457      * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
458      */

459     public final static void putIntelLong(long val, byte[] bytarray, int pos)
460             throws java.lang.IndexOutOfBoundsException JavaDoc
461     {
462
463         // Check if the byte array is long enough to store the int
464

465         if (bytarray.length < pos + 7)
466             throw new java.lang.IndexOutOfBoundsException JavaDoc();
467
468         // Store the long value in the byte array
469

470         bytarray[pos + 7] = (byte) ((val >> 56) & 0xFF);
471         bytarray[pos + 6] = (byte) ((val >> 48) & 0xFF);
472         bytarray[pos + 5] = (byte) ((val >> 40) & 0xFF);
473         bytarray[pos + 4] = (byte) ((val >> 32) & 0xFF);
474         bytarray[pos + 3] = (byte) ((val >> 24) & 0xFF);
475         bytarray[pos + 2] = (byte) ((val >> 16) & 0xFF);
476         bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
477         bytarray[pos] = (byte) (val & 0xFF);
478     }
479
480     /**
481      * Pack a 64-bit integer value in Intel format.
482      *
483      * @param val Integer value to be packed.
484      * @param bytarray Byte array to pack the value into.
485      * @param pos Offset to start packing the integer value.
486      * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
487      */

488     public final static void putIntelLong(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException JavaDoc
489     {
490
491         // Check if the byte array is long enough to store the int
492

493         if (bytarray.length < pos + 7)
494             throw new java.lang.IndexOutOfBoundsException JavaDoc();
495
496         // Store the int value in the byte array
497

498         bytarray[pos + 7] = (byte) 0;
499         bytarray[pos + 6] = (byte) 0;
500         bytarray[pos + 5] = (byte) 0;
501         bytarray[pos + 4] = (byte) 0;
502         bytarray[pos + 3] = (byte) ((val >> 24) & 0xFF);
503         bytarray[pos + 2] = (byte) ((val >> 16) & 0xFF);
504         bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
505         bytarray[pos] = (byte) (val & 0xFF);
506     }
507
508     /**
509      * Pack a 16 bit value in Intel byte order.
510      *
511      * @param val Short value to be packed.
512      * @param bytarray Byte array to pack the short value into.
513      * @param pos Offset to start packing the short value.
514      * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
515      */

516     public final static void putIntelShort(int val, byte[] bytarray, int pos)
517             throws java.lang.IndexOutOfBoundsException JavaDoc
518     {
519
520         // Check if the byte array is long enough to store the short
521

522         if (bytarray.length < pos)
523             throw new java.lang.IndexOutOfBoundsException JavaDoc();
524
525         // Pack the short value
526

527         bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
528         bytarray[pos] = (byte) (val & 0xFF);
529     }
530
531     /**
532      * Pack a 16-bit value into the supplied byte buffer.
533      *
534      * @param val Short value to be packed.
535      * @param bytarray Byte array to pack the short value into.
536      * @param pos Offset to start packing the short value.
537      * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
538      */

539     public final static void putShort(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException JavaDoc
540     {
541
542         // Check if the byte array is long enough to store the short
543

544         if (bytarray.length < pos)
545             throw new java.lang.IndexOutOfBoundsException JavaDoc();
546
547         // Pack the short value
548

549         bytarray[pos] = (byte) ((val >> 8) & 0xFF);
550         bytarray[pos + 1] = (byte) (val & 0xFF);
551     }
552
553     /**
554      * Pack a string into a data buffer
555      *
556      * @param str String to be packed into the buffer
557      * @param bytarray Byte array to pack the string into
558      * @param pos Position to start packing the string
559      * @param nullterm true if the string should be null terminated, else false
560      * @return The ending buffer position
561      */

562     public final static int putString(String JavaDoc str, byte[] bytarray, int pos, boolean nullterm)
563     {
564
565         // Get the string as a byte array
566

567         byte[] byts = str.getBytes();
568
569         // Pack the data bytes
570

571         int bufpos = pos;
572
573         for (int i = 0; i < byts.length; i++)
574             bytarray[bufpos++] = byts[i];
575
576         // Null terminate the string, if required
577

578         if (nullterm == true)
579             bytarray[bufpos++] = 0;
580
581         // Return the next free buffer position
582

583         return bufpos;
584     }
585
586     /**
587      * Pack a string into a data buffer
588      *
589      * @param str String to be packed into the buffer
590      * @param fldLen Field length, will be space padded if short
591      * @param bytarray Byte array to pack the string into
592      * @param pos Position to start packing the string
593      * @return The ending buffer position
594      */

595     public final static int putString(String JavaDoc str, int fldLen, byte[] bytarray, int pos)
596     {
597
598         // Get the string as a byte array
599

600         byte[] byts = str.getBytes();
601
602         // Pack the data bytes
603

604         int bufpos = pos;
605         int idx = 0;
606
607         while (idx < fldLen)
608         {
609             if (idx < byts.length)
610                 bytarray[bufpos++] = byts[idx];
611             else
612                 bytarray[bufpos++] = (byte) 0;
613             idx++;
614         }
615
616         // Return the next free buffer position
617

618         return bufpos;
619     }
620
621     /**
622      * Pack a string into a data buffer. The string may be ASCII or Unicode.
623      *
624      * @param str String to be packed into the buffer
625      * @param bytarray Byte array to pack the string into
626      * @param pos Position to start packing the string
627      * @param nullterm true if the string should be null terminated, else false
628      * @param isUni true if the string should be packed as Unicode, false to pack as ASCII
629      * @return The ending buffer position
630      */

631     public final static int putString(String JavaDoc str, byte[] bytarray, int pos, boolean nullterm, boolean isUni)
632     {
633
634         // Pack the string
635

636         int newpos = -1;
637
638         if (isUni)
639             newpos = putUnicodeString(str, bytarray, pos, nullterm);
640         else
641             newpos = putString(str, bytarray, pos, nullterm);
642
643         // Return the end of string buffer position
644

645         return newpos;
646     }
647
648     /**
649      * Pack a Unicode string into a data buffer
650      *
651      * @param str String to be packed into the buffer
652      * @param bytarray Byte array to pack the string into
653      * @param pos Position to start packing the string
654      * @param nullterm true if the string should be null terminated, else false
655      * @return The ending buffer position
656      */

657     public final static int putUnicodeString(String JavaDoc str, byte[] bytarray, int pos, boolean nullterm)
658     {
659
660         // Pack the data bytes
661

662         int bufpos = pos;
663
664         for (int i = 0; i < str.length(); i++)
665         {
666
667             // Get the current character from the string
668

669             char ch = str.charAt(i);
670
671             // Pack the unicode character
672

673             bytarray[bufpos++] = (byte) (ch & 0xFF);
674             bytarray[bufpos++] = (byte) ((ch & 0xFF00) >> 8);
675         }
676
677         // Null terminate the string, if required
678

679         if (nullterm == true)
680         {
681             bytarray[bufpos++] = 0;
682             bytarray[bufpos++] = 0;
683         }
684
685         // Return the next free buffer position
686

687         return bufpos;
688     }
689
690     /**
691      * Pack nulls into the buffer.
692      *
693      * @param buf Buffer to pack data into.
694      * @param pos Position to start packing.
695      * @param cnt Number of nulls to pack.
696      * @exception java.lang.ArrayIndexOutOfBoundsException If the buffer does not have enough space.
697      */

698     public final static void putZeros(byte[] buf, int pos, int cnt) throws java.lang.ArrayIndexOutOfBoundsException JavaDoc
699     {
700
701         // Check if the buffer is big enough
702

703         if (buf.length < (pos + cnt))
704             throw new java.lang.ArrayIndexOutOfBoundsException JavaDoc();
705
706         // Pack the nulls
707

708         for (int i = 0; i < cnt; i++)
709             buf[pos + i] = 0;
710     }
711
712     /**
713      * Align a buffer offset on a word boundary
714      *
715      * @param pos int
716      * @return int
717      */

718     public final static int wordAlign(int pos)
719     {
720         return (pos + 1) & 0xFFFFFFFE;
721     }
722
723     /**
724      * Align a buffer offset on a longword boundary
725      *
726      * @param pos int
727      * @return int
728      */

729     public final static int longwordAlign(int pos)
730     {
731         return (pos + 3) & 0xFFFFFFFC;
732     }
733
734     /**
735      * Calculate the string length in bytes
736      *
737      * @param str String
738      * @param uni boolean
739      * @param nul boolean
740      * @return int
741      */

742     public final static int getStringLength(String JavaDoc str, boolean uni, boolean nul)
743     {
744
745         // Calculate the string length in bytes
746

747         int len = str.length();
748         if (nul)
749             len += 1;
750         if (uni)
751             len *= 2;
752
753         return len;
754     }
755
756     /**
757      * Calculate the new buffer position after the specified string and encoding (ASCII or Unicode)
758      *
759      * @param pos int
760      * @param str String
761      * @param uni boolean
762      * @param nul boolean
763      * @return int
764      */

765     public final static int getBufferPosition(int pos, String JavaDoc str, boolean uni, boolean nul)
766     {
767
768         // Calculate the new buffer position
769

770         int len = str.length();
771         if (nul)
772             len += 1;
773         if (uni)
774             len *= 2;
775
776         return pos + len;
777     }
778 }
Popular Tags