View Javadoc

1   /*
2    * Entagged Audio Tag library
3    * Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
4    * 
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *  
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   * 
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18   */
19  package entagged.audioformats.asf.util;
20  
21  import java.io.IOException;
22  import java.io.RandomAccessFile;
23  import java.io.UnsupportedEncodingException;
24  import java.math.BigInteger;
25  import java.util.Calendar;
26  import java.util.GregorianCalendar;
27  
28  import entagged.audioformats.asf.data.GUID;
29  
30  /***
31   * Some static Methods which are used in several Classes. <br>
32   * 
33   * @author Christian Laireiter
34   */
35  public class Utils {
36  
37      /***
38       * Stores the default line seperator of the current underlying system.
39       */
40      public final static String LINE_SEPARATOR = System
41              .getProperty("line.separator");
42  
43      /***
44       * Reads chars out of <code>raf</code> until <code>chars</code> is
45       * filled.
46       * 
47       * @param chars
48       *                   to be filled
49       * @param raf
50       *                   to be read
51       * @throws IOException
52       *                    read error, or file at end before <code>chars</code> is
53       *                    filled.
54       */
55      public static void fillChars(char[] chars, RandomAccessFile raf)
56              throws IOException {
57          if (chars == null) {
58              throw new IllegalArgumentException("Argument must not be null.");
59          }
60          for (int i = 0; i < chars.length; i++) {
61              chars[i] = raf.readChar();
62          }
63      }
64  
65      /***
66       * This method will create a byte[] at the size of <code>byteCount</code>
67       * and insert the bytes of <code>value</code> (starting from lowset byte)
68       * into it. <br>
69       * You can easily create a Word (16-bit), DWORD (32-bit), QWORD (64 bit) out
70       * of the value, ignoring the original type of value, since java
71       * automatically performs transformations. <br>
72       * <b>Warning: </b> This method works with unsigned numbers only.
73       * 
74       * @param value
75       *                   The value to be written into the result.
76       * @param byteCount
77       *                   The number of bytes the array has got.
78       * @return A byte[] with the size of <code>byteCount</code> containing the
79       *               lower byte values of <code>value</code>.
80       */
81      public static byte[] getBytes(long value, int byteCount) {
82          byte[] result = new byte[byteCount];
83          for (int i = 0; i < result.length; i++) {
84              result[i] = (byte) (value & 0xFF);
85              value >>>= 8;
86          }
87          return result;
88      }
89  
90      /***
91       * Since date values in asf files are given in 100 ns steps since first
92       * january of 1601 a little conversion must be done. <br>
93       * This method converts a date given in described manner to a calendar.
94       * 
95       * @param fileTime
96       *                   Time in 100ns since 1 jan 1601
97       * @return Calendar holding the date representation.
98       */
99      public static GregorianCalendar getDateOf(BigInteger fileTime) {
100         GregorianCalendar result = new GregorianCalendar(1601, 0, 1);
101         // lose anything beyond milliseconds, because calendar can't handle
102         // less value
103         fileTime = fileTime.divide(new BigInteger("10000"));
104         BigInteger maxInt = new BigInteger(String.valueOf(Integer.MAX_VALUE));
105         while (fileTime.compareTo(maxInt) > 0) {
106             result.add(Calendar.MILLISECOND, Integer.MAX_VALUE);
107             fileTime = fileTime.subtract(maxInt);
108         }
109         result.add(Calendar.MILLISECOND, fileTime.intValue());
110         return result;
111     }
112 
113     /***
114      * This method reads one byte from <code>raf</code> and creates an
115      * unsigned value of it. <br>
116      * 
117      * @param raf
118      *                   The file to read from.
119      * @return next 7 bits as number.
120      * @throws IOException
121      *                    read errors.
122      */
123     public static int read7Bit(RandomAccessFile raf) throws IOException {
124         int result = raf.read();
125         return result & 127;
126     }
127 
128     /***
129      * This method reads 8 bytes, interprets them as an unsigned number and
130      * creates a {@link BigInteger}
131      * 
132      * @param raf
133      *                   Input source
134      * @return 8 bytes unsigned number
135      * @throws IOException
136      *                    read errors.
137      */
138     public static BigInteger readBig64(RandomAccessFile raf) throws IOException {
139         byte[] bytes = new byte[8];
140         byte[] oa = new byte[8];
141         raf.readFully(bytes);
142         for (int i = 0; i < bytes.length; i++) {
143             oa[7 - i] = bytes[i];
144         }
145         BigInteger result = new BigInteger(oa);
146         return result;
147     }
148 
149     /***
150      * This method reads a UTF-16 String, which legth is given on the number of
151      * characters it consits of. <br>
152      * The filepointer of <code>raf</code> must be at the number of
153      * characters. This number contains the terminating zero character (UINT16).
154      * 
155      * @param raf
156      *                   Input source
157      * @return String
158      * @throws IOException
159      *                    read errors
160      */
161     public static String readCharacterSizedString(RandomAccessFile raf)
162             throws IOException {
163         StringBuffer result = new StringBuffer();
164         int strLen = readUINT16(raf);
165         int character = raf.read();
166         character |= raf.read() << 8;
167         do {
168             if (character != 0) {
169                 result.append((char) character);
170                 character = raf.read();
171                 character |= raf.read() << 8;
172             }
173         } while (character != 0 || (result.length() + 1) > strLen);
174         if (strLen != (result.length() + 1)) {
175             throw new IllegalStateException(
176                     "Invalid Data for current interpretation");
177         }
178         return result.toString();
179     }
180 
181     /***
182      * This Method reads a GUID (which is a 16 byte long sequence) from the
183      * given <code>raf</code> and creates a wrapper. <br>
184      * <b>Warning </b>: <br>
185      * There is no way of telling if a byte sequence is a guid or not. The next
186      * 16 bytes will be interpreted as a guid, whether it is or not.
187      * 
188      * @param raf
189      *                   Input source.
190      * @return A class wrapping the guid.
191      * @throws IOException
192      *                    happens when the file ends before guid could be extracted.
193      */
194     public static GUID readGUID(RandomAccessFile raf) throws IOException {
195         if (raf == null) {
196             throw new IllegalArgumentException("Argument must not be null");
197         }
198         int[] binaryGuid = new int[GUID.GUID_LENGTH];
199         for (int i = 0; i < binaryGuid.length; i++) {
200             binaryGuid[i] = raf.read();
201         }
202         return new GUID(binaryGuid);
203     }
204 
205     /***
206      * @see #readUINT64(RandomAccessFile)
207      * @param raf
208      * @return number
209      * @throws IOException
210      */
211     public static int readUINT16(RandomAccessFile raf) throws IOException {
212         int result = raf.read();
213         result |= raf.read() << 8;
214         return result;
215     }
216 
217     /***
218      * @see #readUINT64(RandomAccessFile)
219      * @param raf
220      * @return number
221      * @throws IOException
222      */
223     public static long readUINT32(RandomAccessFile raf) throws IOException {
224         long result = 0;
225         for (int i = 0; i <= 24; i += 8)
226             result |= raf.read() << i;
227         return result;
228     }
229 
230     /***
231      * Reads long as little endian.
232      * 
233      * @param raf
234      *                   Data source
235      * @return long value
236      * @throws IOException
237      *                    read error, or eof is reached before long is completed
238      */
239     public static long readUINT64(RandomAccessFile raf) throws IOException {
240         long result = 0;
241         for (int i = 0; i <= 56; i += 8)
242             result |= raf.read() << i;
243         return result;
244     }
245 
246     /***
247      * This method reads a UTF-16 encoded String, beginning with a 16-bit value
248      * representing the number of bytes needed. The String is terminated with as
249      * 16-bit ZERO. <br>
250      * 
251      * @param raf
252      *                   Input source
253      * @return read String.
254      * @throws IOException
255      *                    read errors.
256      */
257     public static String readUTF16LEStr(RandomAccessFile raf)
258             throws IOException {
259         int strLen = readUINT16(raf);
260         byte[] buf = new byte[strLen];
261         int read = raf.read(buf);
262         if (read == buf.length) {
263             /*
264              * Check on zero termination
265              */
266             if (buf.length >= 2) {
267                 if (buf[buf.length - 1] == 0 && buf[buf.length - 2] == 0) {
268                     byte[] copy = new byte[buf.length - 2];
269                     System.arraycopy(buf, 0, copy, 0, buf.length - 2);
270                     buf = copy;
271                 }
272             }
273             return new String(buf, "UTF-16LE");
274         }
275         throw new IllegalStateException(
276                 "Invalid Data for current interpretation");
277     }
278 
279     /***
280      * This method converts the given string into a byte[] in UTF-16LE encoding
281      * and checks whether the length doesn't exceed 65535 bytes. <br>
282      * 
283      * @param value
284      *                   The string to check.
285      * @throws IllegalArgumentException
286      *                    If byte representation takes more than 65535 bytes.
287      */
288     public static void checkStringLengthNullSafe(String value)
289             throws IllegalArgumentException {
290         if (value != null) {
291             try {
292                 byte[] tmp = value.getBytes("UTF-16LE");
293                 if (tmp.length > 65533) {
294                     throw new IllegalArgumentException(
295                             "\"UTF-16LE\" representation exceeds 65535 bytes."
296                                     + " (Including zero term character)");
297                 }
298             } catch (UnsupportedEncodingException e) {
299                 e.printStackTrace();
300             }
301         }
302     }
303 }