Coverage Report - org.jaudiotagger.audio.asf.util.Utils
 
Classes in this File Line Coverage Branch Coverage Complexity
Utils
91%
112/123
76%
56/74
3.111
 
 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 org.jaudiotagger.audio.asf.util;
 20  
 
 21  
 import org.jaudiotagger.audio.asf.data.AsfHeader;
 22  
 import org.jaudiotagger.audio.asf.data.GUID;
 23  
 
 24  
 import java.io.EOFException;
 25  
 import java.io.IOException;
 26  
 import java.io.InputStream;
 27  
 import java.io.OutputStream;
 28  
 import java.math.BigInteger;
 29  
 import java.nio.ByteBuffer;
 30  
 import java.nio.charset.Charset;
 31  
 import java.util.Calendar;
 32  
 import java.util.GregorianCalendar;
 33  
 
 34  
 /**
 35  
  * Some static Methods which are used in several Classes. <br>
 36  
  * 
 37  
  * @author Christian Laireiter
 38  
  */
 39  7
 public class Utils
 40  
 {
 41  
 
 42  
     /**
 43  
      * Stores the default line separator of the current underlying system.
 44  
      */
 45  7
     public final static String LINE_SEPARATOR = System.getProperty("line.separator"); //$NON-NLS-1$
 46  
 
 47  
     /**
 48  
      * This method converts the given string into a byte[] in UTF-16LE encoding
 49  
      * and checks whether the length doesn't exceed 65535 bytes. <br>
 50  
      * 
 51  
      * @param value The string to check.
 52  
      * @throws IllegalArgumentException If byte representation takes more than 65535 bytes.
 53  
      */
 54  
     public static void checkStringLengthNullSafe(String value) throws IllegalArgumentException
 55  
     {
 56  9025
         if (value != null)
 57  
         {
 58  9024
             byte[] tmp = getBytes(value, AsfHeader.ASF_CHARSET);
 59  9024
             if (tmp.length > 65533)
 60  
             {
 61  1
                 throw new IllegalArgumentException("\"UTF-16LE\" representation exceeds 65535 bytes. (Including zero term character)"); //$NON-NLS-1$
 62  
             }
 63  
         }
 64  9024
     }
 65  
 
 66  
     /**
 67  
      * effectively copies a specified amount of bytes from one stream to another.
 68  
      * @param source stream to read from
 69  
      * @param dest stream to write to
 70  
      * @param amount amount of bytes to copy
 71  
      * @throws IOException on I/O errors, and if the source stream depletes before all bytes have been copied. 
 72  
      */
 73  
     public static void copy(InputStream source, OutputStream dest, long amount) throws IOException
 74  
     {
 75  190
         byte[] buf = new byte[8192];
 76  190
         long copied = 0;
 77  382
         while (copied < amount)
 78  
         {
 79  192
             int toRead = 8192;
 80  192
             if ((amount - copied) < 8192)
 81  
             {
 82  190
                 toRead = (int) (amount - copied);
 83  
             }
 84  192
             int read = source.read(buf, 0, toRead);
 85  192
             if (read == -1)
 86  
             {
 87  0
                 throw new IOException("Inputstream has to continue for another " + (amount - copied) + " bytes.");
 88  
             }
 89  192
             dest.write(buf, 0, read);
 90  192
             copied += read;
 91  192
         }
 92  190
     }
 93  
 
 94  
     /**
 95  
      * Copies all of the source to the destination.<br>
 96  
      * @param source source to read from
 97  
      * @param dest stream to write to
 98  
      * @throws IOException on I/O errors.
 99  
      */
 100  
     public static void flush(InputStream source, OutputStream dest) throws IOException
 101  
     {
 102  29
         byte[] buf = new byte[8192];
 103  29
         int read = 0;
 104  3219
         while ((read = source.read(buf)) != -1)
 105  
         {
 106  3190
             dest.write(buf, 0, read);
 107  
         }
 108  29
     }
 109  
 
 110  
     /**
 111  
      * This method will create a byte[] at the size of <code>byteCount</code>
 112  
      * and insert the bytes of <code>value</code> (starting from lowset byte)
 113  
      * into it. <br>
 114  
      * You can easily create a Word (16-bit), DWORD (32-bit), QWORD (64 bit) out
 115  
      * of the value, ignoring the original type of value, since java
 116  
      * automatically performs transformations. <br>
 117  
      * <b>Warning: </b> This method works with unsigned numbers only.
 118  
      * 
 119  
      * @param value The value to be written into the result.
 120  
      * @param byteCount The number of bytes the array has got.
 121  
      * @return A byte[] with the size of <code>byteCount</code> containing the
 122  
      *         lower byte values of <code>value</code>.
 123  
      */
 124  
     public static byte[] getBytes(long value, int byteCount)
 125  
     {
 126  33
         byte[] result = new byte[byteCount];
 127  173
         for (int i = 0; i < result.length; i++)
 128  
         {
 129  140
             result[i] = (byte) (value & 0xFF);
 130  140
             value >>>= 8;
 131  
         }
 132  33
         return result;
 133  
     }
 134  
 
 135  
     /**
 136  
      * Convenience method to convert the given string into a byte sequence which has the format
 137  
      * of the charset given.
 138  
      * @param source string to convert.
 139  
      * @param charset charset to apply
 140  
      * @return the source's binary representation according to the charset.
 141  
      */
 142  
     public static byte[] getBytes(final String source, final Charset charset)
 143  
     {
 144  12472
         assert charset != null;
 145  12472
         assert source != null;
 146  12472
         final ByteBuffer encoded = charset.encode(source);
 147  12472
         final byte[] result = new byte[encoded.limit()];
 148  12472
         encoded.rewind();
 149  12472
         encoded.get(result);
 150  12472
         return result;
 151  
     }
 152  
 
 153  
     /**
 154  
      * Since date values in asf files are given in 100 ns steps since first
 155  
      * january of 1601 a little conversion must be done. <br>
 156  
      * This method converts a date given in described manner to a calendar.
 157  
      * 
 158  
      * @param fileTime Time in 100ns since 1 jan 1601
 159  
      * @return Calendar holding the date representation.
 160  
      */
 161  
     public static GregorianCalendar getDateOf(BigInteger fileTime)
 162  
     {
 163  55
         GregorianCalendar result = new GregorianCalendar(1601, 0, 1);
 164  
         // lose anything beyond milliseconds, because calendar can't handle
 165  
         // less value
 166  55
         fileTime = fileTime.divide(new BigInteger("10000")); //$NON-NLS-1$
 167  55
         BigInteger maxInt = new BigInteger(String.valueOf(Integer.MAX_VALUE));
 168  326813
         while (fileTime.compareTo(maxInt) > 0)
 169  
         {
 170  326758
             result.add(Calendar.MILLISECOND, Integer.MAX_VALUE);
 171  326758
             fileTime = fileTime.subtract(maxInt);
 172  
         }
 173  55
         result.add(Calendar.MILLISECOND, fileTime.intValue());
 174  55
         return result;
 175  
     }
 176  
 
 177  
     /**
 178  
      * Tests if the given string is <code>null</code> or just contains
 179  
      * whitespace characters.
 180  
      * 
 181  
      * @param toTest String to test.
 182  
      * @return see description.
 183  
      */
 184  
     public static boolean isBlank(String toTest)
 185  
     {
 186  1334
         boolean result = true;
 187  1334
         if (toTest != null)
 188  
         {
 189  2486
             for (int i = 0; result && i < toTest.length(); i++)
 190  
             {
 191  1156
                 result &= Character.isWhitespace(toTest.charAt(i));
 192  
             }
 193  
         }
 194  1334
         return result;
 195  
     }
 196  
 
 197  
     /**
 198  
      * Reads 8 bytes from stream and interprets them as a UINT64 which
 199  
      * is returned as {@link BigInteger}.<br>
 200  
      * 
 201  
      * @param stream stream to readm from.
 202  
      * @return a BigInteger which represents the read 8 bytes value.
 203  
      * @throws IOException 
 204  
      */
 205  
     public static BigInteger readBig64(InputStream stream) throws IOException
 206  
     {
 207  1322
         byte[] bytes = new byte[8];
 208  1322
         byte[] oa = new byte[8];
 209  1322
         int read = stream.read(bytes);
 210  1322
         if (read != 8)
 211  
         {
 212  
             // 8 bytes mandatory.
 213  0
             throw new EOFException();
 214  
         }
 215  11898
         for (int i = 0; i < bytes.length; i++)
 216  
         {
 217  10576
             oa[7 - i] = bytes[i];
 218  
         }
 219  1322
         BigInteger result = new BigInteger(oa);
 220  1322
         return result;
 221  
     }
 222  
 
 223  
     /**
 224  
      * Reads <code>size</code> bytes from the stream.<br> 
 225  
      *  
 226  
      * @param stream stream to read from.
 227  
      * @param size amount of bytes to read.
 228  
      * @return the read bytes.
 229  
      * @throws IOException on I/O errors.
 230  
      */
 231  
     public static byte[] readBinary(InputStream stream, long size) throws IOException
 232  
     {
 233  0
         byte[] result = new byte[(int) size];
 234  0
         stream.read(result);
 235  0
         return result;
 236  
     }
 237  
 
 238  
     /**
 239  
      * This method reads a UTF-16 String, which length is given on the number of
 240  
      * characters it consists of. <br>
 241  
      * The stream must be at the number of characters. This number contains the
 242  
      * terminating zero character (UINT16).
 243  
      * 
 244  
      * @param stream Input source
 245  
      * @return String
 246  
      * @throws IOException read errors
 247  
      */
 248  
     public static String readCharacterSizedString(InputStream stream) throws IOException
 249  
     {
 250  33
         StringBuffer result = new StringBuffer();
 251  33
         int strLen = readUINT16(stream);
 252  33
         int character = stream.read();
 253  33
         character |= stream.read() << 8;
 254  
         do
 255  
         {
 256  915
             if (character != 0)
 257  
             {
 258  915
                 result.append((char) character);
 259  915
                 character = stream.read();
 260  915
                 character |= stream.read() << 8;
 261  
             }
 262  
         }
 263  915
         while (character != 0 || (result.length() + 1) > strLen);
 264  33
         if (strLen != (result.length() + 1))
 265  
         {
 266  0
             throw new IllegalStateException("Invalid Data for current interpretation"); //$NON-NLS-1$
 267  
         }
 268  33
         return result.toString();
 269  
     }
 270  
 
 271  
     /**
 272  
      * This Method reads a GUID (which is a 16 byte long sequence) from the
 273  
      * given <code>raf</code> and creates a wrapper. <br>
 274  
      * <b>Warning </b>: <br>
 275  
      * There is no way of telling if a byte sequence is a guid or not. The next
 276  
      * 16 bytes will be interpreted as a guid, whether it is or not.
 277  
      * 
 278  
      * @param stream Input source.
 279  
      * @return A class wrapping the guid.
 280  
      * @throws IOException happens when the file ends before guid could be extracted.
 281  
      */
 282  
     public static GUID readGUID(InputStream stream) throws IOException
 283  
     {
 284  1449
         if (stream == null)
 285  
         {
 286  0
             throw new IllegalArgumentException("Argument must not be null"); //$NON-NLS-1$
 287  
         }
 288  1449
         int[] binaryGuid = new int[GUID.GUID_LENGTH];
 289  24633
         for (int i = 0; i < binaryGuid.length; i++)
 290  
         {
 291  23184
             binaryGuid[i] = stream.read();
 292  
         }
 293  1449
         return new GUID(binaryGuid);
 294  
     }
 295  
 
 296  
     /**
 297  
      * Reads 2 bytes from stream and interprets them as UINT16.<br> 
 298  
      * 
 299  
      * @param stream stream to read from.
 300  
      * @return UINT16 value
 301  
      * @throws IOException on I/O Errors.
 302  
      */
 303  
     public static int readUINT16(InputStream stream) throws IOException
 304  
     {
 305  4583
         int result = stream.read();
 306  4583
         result |= stream.read() << 8;
 307  4583
         return result;
 308  
     }
 309  
 
 310  
     /**
 311  
      * Reads 4 bytes from stream and interprets them as UINT32.<br> 
 312  
      * 
 313  
      * @param stream stream to read from.
 314  
      * @return UINT32 value
 315  
      * @throws IOException on I/O Errors.
 316  
      */
 317  
     public static long readUINT32(InputStream stream) throws IOException
 318  
     {
 319  701
         long result = 0;
 320  3505
         for (int i = 0; i <= 24; i += 8)
 321  
         {
 322  
             // Warning, always cast to long here. Otherwise it will be
 323  
             // shifted as int, which may produce a negative value, which will
 324  
             // then be extended to long and assign the long variable a negative
 325  
             // value.
 326  2804
             result |= (long) stream.read() << i;
 327  
         }
 328  701
         return result;
 329  
     }
 330  
 
 331  
     /**
 332  
      * Reads long as little endian.
 333  
      * 
 334  
      * @param stream Data source
 335  
      * @return long value
 336  
      * @throws IOException read error, or eof is reached before long is completed
 337  
      */
 338  
     public static long readUINT64(InputStream stream) throws IOException
 339  
     {
 340  333
         long result = 0;
 341  2997
         for (int i = 0; i <= 56; i += 8)
 342  
         {
 343  
             // Warning, always cast to long here. Otherwise it will be
 344  
             // shifted as int, which may produce a negative value, which will
 345  
             // then be extended to long and assign the long variable a negative
 346  
             // value.
 347  2664
             result |= (long) stream.read() << i;
 348  
         }
 349  333
         return result;
 350  
     }
 351  
 
 352  
     /**
 353  
      * This method reads a UTF-16 encoded String, beginning with a 16-bit value
 354  
      * representing the number of bytes needed. The String is terminated with as
 355  
      * 16-bit ZERO. <br>
 356  
      * 
 357  
      * @param stream Input source
 358  
      * @return read String.
 359  
      * @throws IOException read errors.
 360  
      */
 361  
     public static String readUTF16LEStr(InputStream stream) throws IOException
 362  
     {
 363  2451
         int strLen = readUINT16(stream);
 364  2451
         byte[] buf = new byte[strLen];
 365  2451
         int read = stream.read(buf);
 366  2451
         if (read == strLen || (strLen == 0 && read == -1))
 367  
         {
 368  
             /*
 369  
              * Check on zero termination
 370  
              */
 371  2451
             if (buf.length >= 2)
 372  
             {
 373  2450
                 if (buf[buf.length - 1] == 0 && buf[buf.length - 2] == 0)
 374  
                 {
 375  2449
                     byte[] copy = new byte[buf.length - 2];
 376  2449
                     System.arraycopy(buf, 0, copy, 0, buf.length - 2);
 377  2449
                     buf = copy;
 378  
                 }
 379  
             }
 380  2451
             return new String(buf, AsfHeader.ASF_CHARSET.name());
 381  
         }
 382  0
         throw new IllegalStateException("Invalid Data for current interpretation"); //$NON-NLS-1$
 383  
     }
 384  
 
 385  
     /**
 386  
      * Writes the given value as UINT16 into the stream.
 387  
      * 
 388  
      * @param number value to write.
 389  
      * @param out stream to write into.
 390  
      * @throws IOException On I/O errors
 391  
      */
 392  
     public static void writeUINT16(int number, OutputStream out) throws IOException
 393  
     {
 394  1202
         if (number < 0)
 395  
         {
 396  0
             throw new IllegalArgumentException("positive value expected."); //$NON-NLS-1$
 397  
         }
 398  1202
         byte[] toWrite = new byte[2];
 399  3606
         for (int i = 0; i <= 8; i += 8)
 400  
         {
 401  2404
             toWrite[i / 8] = (byte) ((number >> i) & 0xFF);
 402  
         }
 403  1202
         out.write(toWrite);
 404  1202
     }
 405  
 
 406  
     /**
 407  
      * Writes the given value as UINT32 into the stream.
 408  
      * 
 409  
      * @param number value to write.
 410  
      * @param out stream to write into.
 411  
      * @throws IOException On I/O errors
 412  
      */
 413  
     public static void writeUINT32(long number, OutputStream out) throws IOException
 414  
     {
 415  34
         if (number < 0)
 416  
         {
 417  0
             throw new IllegalArgumentException("positive value expected."); //$NON-NLS-1$
 418  
         }
 419  34
         byte[] toWrite = new byte[4];
 420  170
         for (int i = 0; i <= 24; i += 8)
 421  
         {
 422  136
             toWrite[i / 8] = (byte) ((number >> i) & 0xFF);
 423  
         }
 424  34
         out.write(toWrite);
 425  34
     }
 426  
 
 427  
     /**
 428  
      * Writes the given value as UINT64 into the stream.
 429  
      * 
 430  
      * @param number value to write.
 431  
      * @param out stream to write into.
 432  
      * @throws IOException On I/O errors
 433  
      */
 434  
     public static void writeUINT64(long number, OutputStream out) throws IOException
 435  
     {
 436  283
         if (number < 0)
 437  
         {
 438  0
             throw new IllegalArgumentException("positive value expected."); //$NON-NLS-1$
 439  
         }
 440  283
         byte[] toWrite = new byte[8];
 441  2547
         for (int i = 0; i <= 56; i += 8)
 442  
         {
 443  2264
             toWrite[i / 8] = (byte) ((number >> i) & 0xFF);
 444  
         }
 445  283
         out.write(toWrite);
 446  283
     }
 447  
 }