/* * Entagged Audio Tag library * Copyright (c) 2004-2005 Christian Laireiter <liree@web.de> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jaudiotagger.audio.asf.util; import org.jaudiotagger.audio.asf.data.AsfHeader; import org.jaudiotagger.audio.asf.data.GUID; import org.jaudiotagger.logging.ErrorMessage; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; /** * Some static Methods which are used in several Classes. <br> * * @author Christian Laireiter */ public class Utils { public static final long DIFF_BETWEEN_ASF_DATE_AND_JAVA_DATE = 11644470000000l; /** * Stores the default line separator of the current underlying system. */ public final static String LINE_SEPARATOR = System .getProperty("line.separator"); //$NON-NLS-1$ /** * */ private static final int MAXIMUM_STRING_LENGTH_ALLOWED = 32766; /** * This method checks given string will not exceed limit in bytes[] when * converted UTF-16LE encoding (2 bytes per character) and checks whether * the length doesn'timer exceed 65535 bytes. <br> * * @param value * The string to check. * @throws IllegalArgumentException * If byte representation takes more than 65535 bytes. */ public static void checkStringLengthNullSafe(String value) throws IllegalArgumentException { if (value != null) { if (value.length() > MAXIMUM_STRING_LENGTH_ALLOWED) { throw new IllegalArgumentException( ErrorMessage.WMA_LENGTH_OF_STRING_IS_TOO_LARGE .getMsg((value.length() * 2))); } } } /** * * @param value String to check for null * @return true unless string is too long */ public static boolean isStringLengthValidNullSafe(String value) { if (value != null) { if (value.length() > MAXIMUM_STRING_LENGTH_ALLOWED) { return false; } } return true; } /** * effectively copies a specified amount of bytes from one stream to * another. * * @param source * stream to read from * @param dest * stream to write to * @param amount * amount of bytes to copy * @throws IOException * on I/O errors, and if the source stream depletes before all * bytes have been copied. */ public static void copy(InputStream source, OutputStream dest, long amount) throws IOException { byte[] buf = new byte[8192]; long copied = 0; while (copied < amount) { int toRead = 8192; if ((amount - copied) < 8192) { toRead = (int) (amount - copied); } int read = source.read(buf, 0, toRead); if (read == -1) { throw new IOException( "Inputstream has to continue for another " + (amount - copied) + " bytes."); } dest.write(buf, 0, read); copied += read; } } /** * Copies all of the source to the destination.<br> * * @param source * source to read from * @param dest * stream to write to * @throws IOException * on I/O errors. */ public static void flush(final InputStream source, final OutputStream dest) throws IOException { final byte[] buf = new byte[8192]; int read; while ((read = source.read(buf)) != -1) { dest.write(buf, 0, read); } } /** * This method will create a byte[] at the size of <code>byteCount</code> * and insert the bytes of <code>value</code> (starting from lowset byte) * into it. <br> * You can easily create a Word (16-bit), DWORD (32-bit), QWORD (64 bit) out * of the value, ignoring the original type of value, since java * automatically performs transformations. <br> * <b>Warning: </b> This method works with unsigned numbers only. * * @param value * The value to be written into the result. * @param byteCount * The number of bytes the array has got. * @return A byte[] with the size of <code>byteCount</code> containing the * lower byte values of <code>value</code>. */ public static byte[] getBytes(final long value, final int byteCount) { byte[] result = new byte[byteCount]; for (int i = 0; i < result.length; i ++) { result[i] = (byte) ((value >>> (i*8)) & 0xFF); } return result; } /** * Convenience method to convert the given string into a byte sequence which * has the format of the charset given. * * @param source * string to convert. * @param charset * charset to apply * @return the source's binary representation according to the charset. */ public static byte[] getBytes(final String source, final Charset charset) { assert charset != null; assert source != null; final ByteBuffer encoded = charset.encode(source); final byte[] result = new byte[encoded.limit()]; encoded.rewind(); encoded.get(result); return result; } /** * Since date values in ASF files are given in 100 ns steps since first * january of 1601 a little conversion must be done. <br> * This method converts a date given in described manner to a calendar. * * @param fileTime * Time in 100ns since 1 jan 1601 * @return Calendar holding the date representation. */ /* Old method that ran very slowely and doesnt logical correct, how does dividing something at 10-4 by 10,000 convert it to 10 -3 public static GregorianCalendar getDateOf(final BigInteger fileTime) { final GregorianCalendar result = new GregorianCalendar(1601, 0, 1); // lose anything beyond milliseconds, because calendar can'timer handle // less value BigInteger time = fileTime.divide(new BigInteger("10000")); //$NON-NLS-1$ final BigInteger maxInt = new BigInteger(String .valueOf(Integer.MAX_VALUE)); while (time.compareTo(maxInt) > 0) { result.add(Calendar.MILLISECOND, Integer.MAX_VALUE); time = time.subtract(maxInt); } result.add(Calendar.MILLISECOND, time.intValue()); return result; } */ /** * Date values in ASF files are given in 100 ns (10 exp -4) steps since first * * @param fileTime * Time in 100ns since 1 jan 1601 * @return Calendar holding the date representation. */ public static GregorianCalendar getDateOf(final BigInteger fileTime) { final GregorianCalendar result = new GregorianCalendar(); // Divide by 10 to convert from -4 to -3 (millisecs) BigInteger time = fileTime.divide(new BigInteger("10")); // Construct Date taking into the diff between 1601 and 1970 Date date = new Date(time.longValue() - DIFF_BETWEEN_ASF_DATE_AND_JAVA_DATE); result.setTime(date); return result; } /** * Tests if the given string is <code>null</code> or just contains * whitespace characters. * * @param toTest * String to test. * @return see description. */ public static boolean isBlank(String toTest) { if (toTest == null) { return true; } for (int i = 0; i < toTest.length(); i++) { if(!Character.isWhitespace(toTest.charAt(i))) { return false; } } return true; } /** * Reads 8 bytes from stream and interprets them as a UINT64 which is * returned as {@link BigInteger}.<br> * * @param stream * stream to readm from. * @return a BigInteger which represents the read 8 bytes value. * @throws IOException if problem reading bytes */ public static BigInteger readBig64(InputStream stream) throws IOException { byte[] bytes = new byte[8]; byte[] oa = new byte[8]; int read = stream.read(bytes); if (read != 8) { // 8 bytes mandatory. throw new EOFException(); } for (int i = 0; i < bytes.length; i++) { oa[7 - i] = bytes[i]; } return new BigInteger(oa); } /** * Reads <code>size</code> bytes from the stream.<br> * * @param stream * stream to read from. * @param size * amount of bytes to read. * @return the read bytes. * @throws IOException * on I/O errors. */ public static byte[] readBinary(InputStream stream, long size) throws IOException { byte[] result = new byte[(int) size]; stream.read(result); return result; } /** * This method reads a UTF-16 String, which length is given on the number of * characters it consists of. <br> * The stream must be at the number of characters. This number contains the * terminating zero character (UINT16). * * @param stream * Input source * @return String * @throws IOException * read errors */ public static String readCharacterSizedString(InputStream stream) throws IOException { StringBuilder result = new StringBuilder(); int strLen = readUINT16(stream); int character = stream.read(); character |= stream.read() << 8; do { if (character != 0) { result.append((char) character); character = stream.read(); character |= stream.read() << 8; } } while (character != 0 || (result.length() + 1) > strLen); if (strLen != (result.length() + 1)) { throw new IllegalStateException( "Invalid Data for current interpretation"); //$NON-NLS-1$ } return result.toString(); } /** * This method reads a UTF-16 encoded String. <br> * For the use this method the number of bytes used by current string must * be known. <br> * The ASF specification recommends that those strings end with a * terminating zero. However it also says that it is not always the case. * * @param stream * Input source * @param strLen * Number of bytes the String may take. * @return read String. * @throws IOException * read errors. */ public static String readFixedSizeUTF16Str(InputStream stream, int strLen) throws IOException { byte[] strBytes = new byte[strLen]; int read = stream.read(strBytes); if (read == strBytes.length) { if (strBytes.length >= 2) { /* * Zero termination is recommended but optional. So check and * if, remove. */ if (strBytes[strBytes.length - 1] == 0 && strBytes[strBytes.length - 2] == 0) { byte[] copy = new byte[strBytes.length - 2]; System.arraycopy(strBytes, 0, copy, 0, strBytes.length - 2); strBytes = copy; } } return new String(strBytes, "UTF-16LE"); } throw new IllegalStateException( "Couldn'timer read the necessary amount of bytes."); } /** * This Method reads a GUID (which is a 16 byte long sequence) from the * given <code>raf</code> and creates a wrapper. <br> * <b>Warning </b>: <br> * There is no way of telling if a byte sequence is a guid or not. The next * 16 bytes will be interpreted as a guid, whether it is or not. * * @param stream * Input source. * @return A class wrapping the guid. * @throws IOException * happens when the file ends before guid could be extracted. */ public static GUID readGUID(InputStream stream) throws IOException { if (stream == null) { throw new IllegalArgumentException("Argument must not be null"); //$NON-NLS-1$ } int[] binaryGuid = new int[GUID.GUID_LENGTH]; for (int i = 0; i < binaryGuid.length; i++) { binaryGuid[i] = stream.read(); } return new GUID(binaryGuid); } /** * Reads 2 bytes from stream and interprets them as UINT16.<br> * * @param stream * stream to read from. * @return UINT16 value * @throws IOException * on I/O Errors. */ public static int readUINT16(InputStream stream) throws IOException { int result = stream.read(); result |= stream.read() << 8; return result; } /** * Reads 4 bytes from stream and interprets them as UINT32.<br> * * @param stream * stream to read from. * @return UINT32 value * @throws IOException * on I/O Errors. */ public static long readUINT32(InputStream stream) throws IOException { long result = 0; for (int i = 0; i <= 24; i += 8) { // Warning, always cast to long here. Otherwise it will be // shifted as int, which may produce a negative value, which will // then be extended to long and assign the long variable a negative // value. result |= (long) stream.read() << i; } return result; } /** * Reads long as little endian. * * @param stream * Data source * @return long value * @throws IOException * read error, or eof is reached before long is completed */ public static long readUINT64(InputStream stream) throws IOException { long result = 0; for (int i = 0; i <= 56; i += 8) { // Warning, always cast to long here. Otherwise it will be // shifted as int, which may produce a negative value, which will // then be extended to long and assign the long variable a negative // value. result |= (long) stream.read() << i; } return result; } /** * This method reads a UTF-16 encoded String, beginning with a 16-bit value * representing the number of bytes needed. The String is terminated with as * 16-bit ZERO. <br> * * @param stream * Input source * @return read String. * @throws IOException * read errors. */ public static String readUTF16LEStr(InputStream stream) throws IOException { int strLen = readUINT16(stream); byte[] buf = new byte[strLen]; int read = stream.read(buf); if (read == strLen || (strLen == 0 && read == -1)) { /* * Check on zero termination */ if (buf.length >= 2) { if (buf[buf.length - 1] == 0 && buf[buf.length - 2] == 0) { byte[] copy = new byte[buf.length - 2]; System.arraycopy(buf, 0, copy, 0, buf.length - 2); buf = copy; } } return new String(buf, AsfHeader.ASF_CHARSET.name()); } throw new IllegalStateException( "Invalid Data for current interpretation"); //$NON-NLS-1$ } /** * Writes the given value as UINT16 into the stream. * * @param number * value to write. * @param out * stream to write into. * @throws IOException * On I/O errors */ public static void writeUINT16(int number, OutputStream out) throws IOException { if (number < 0) { throw new IllegalArgumentException("positive value expected."); //$NON-NLS-1$ } byte[] toWrite = new byte[2]; for (int i = 0; i <= 8; i += 8) { toWrite[i / 8] = (byte) ((number >> i) & 0xFF); } out.write(toWrite); } /** * Writes the given value as UINT32 into the stream. * * @param number * value to write. * @param out * stream to write into. * @throws IOException * On I/O errors */ public static void writeUINT32(long number, OutputStream out) throws IOException { if (number < 0) { throw new IllegalArgumentException("positive value expected."); //$NON-NLS-1$ } byte[] toWrite = new byte[4]; for (int i = 0; i <= 24; i += 8) { toWrite[i / 8] = (byte) ((number >> i) & 0xFF); } out.write(toWrite); } /** * Writes the given value as UINT64 into the stream. * * @param number * value to write. * @param out * stream to write into. * @throws IOException * On I/O errors */ public static void writeUINT64(long number, OutputStream out) throws IOException { if (number < 0) { throw new IllegalArgumentException("positive value expected."); //$NON-NLS-1$ } byte[] toWrite = new byte[8]; for (int i = 0; i <= 56; i += 8) { toWrite[i / 8] = (byte) ((number >> i) & 0xFF); } out.write(toWrite); } }