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