package org.farng.mp3; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Stack; import java.util.StringTokenizer; /** * This is a holder class that contains static methods that I use in my library. They may or may not be useful for * anyone else extending the library. * * @author Eric Farng * @version $Revision: 1637 $ */ public class TagUtility { /** * integer difference between ASCII 'A' and ASCII 'a' */ private static final int UPPERCASE; /** * Convenience <code>HashMap</code> to help fix capitilization of words. It maps all words in * <code>TagConstants.upperLowerCase</code> from all lower case to their desired capitilziation. */ private static final Map capitalizationMap; static { UPPERCASE = (int) 'A' - (int) 'a'; capitalizationMap = new HashMap(32); final Iterator iterator = TagOptionSingleton.getInstance().getUpperLowerCaseWordListIterator(); while (iterator.hasNext()) { final String word = (String) iterator.next(); capitalizationMap.put(word.toLowerCase(), word); } } /** * Creates a new TagUtility object. */ private TagUtility() { super(); } /** * Given an ID, get the ID3v2 frame description or the Lyrics3 field description. This takes any kind of ID (four or * three letter ID3v2 IDs, and three letter Lyrics3 IDs) * * @param identifier frame identifier * * @return frame description */ public static String getFrameDescription(final String identifier) { if (identifier == null) { throw new NullPointerException("Identifier is null"); } String returnValue = null; if (identifier.length() > 2) { if (identifier.length() == 4) { final String idPrefix = identifier.substring(0, 4); returnValue = (String) TagConstant.id3v2_4FrameIdToString.get(idPrefix); if (returnValue == null) { returnValue = (String) TagConstant.id3v2_3FrameIdToString.get(idPrefix); } } if (returnValue == null) { returnValue = (String) TagConstant.id3v2_2FrameIdToString.get(identifier.substring(0, 3)); } if (returnValue == null) { returnValue = (String) TagConstant.lyrics3v2FieldIdToString.get(identifier.substring(0, 3)); } } return returnValue; } /** * Returns true if the identifier is a valid ID3v2.2 frame identifier * * @param identifier string to test * * @return true if the identifier is a valid ID3v2.2 frame identifier */ public static boolean isID3v2_2FrameIdentifier(final String identifier) { if (identifier == null) { throw new NullPointerException("Identifier is null"); } if (identifier.length() < 3) { return false; } else if (identifier.length() == 3) { return TagConstant.id3v2_2FrameIdToString.containsKey(identifier); } else { final String upperIdentifier = identifier.toUpperCase(); if (upperIdentifier.charAt(3) >= 'A' && upperIdentifier.charAt(3) <= 'Z') { return TagConstant.id3v2_2FrameIdToString.containsKey(upperIdentifier.substring(0, 4)); } return TagConstant.id3v2_2FrameIdToString.containsKey(upperIdentifier.subSequence(0, 3)); } } /** * Returns true if the identifier is a valid ID3v2.3 frame identifier * * @param identifier string to test * * @return true if the identifier is a valid ID3v2.3 frame identifier */ public static boolean isID3v2_3FrameIdentifier(final String identifier) { if (identifier == null) { throw new NullPointerException("Identifier is null"); } if (identifier.length() < 4) { return false; } return TagConstant.id3v2_3FrameIdToString.containsKey(identifier.substring(0, 4)); } /** * Returns true if the identifier is a valid ID3v2.4 frame identifier * * @param identifier string to test * * @return true if the identifier is a valid ID3v2.4 frame identifier */ public static boolean isID3v2_4FrameIdentifier(final String identifier) { if (identifier == null) { throw new NullPointerException("Identifier is null"); } if (identifier.length() < 4) { return false; } return TagConstant.id3v2_4FrameIdToString.containsKey(identifier.substring(0, 4)); } /** * Returns true if the identifier is a valid Lyrics3v2 frame identifier * * @param identifier string to test * * @return true if the identifier is a valid Lyrics3v2 frame identifier */ public static boolean isLyrics3v2FieldIdentifier(final String identifier) { if (identifier == null) { throw new NullPointerException("Identifier is null"); } if (identifier.length() < 3) { return false; } return TagConstant.lyrics3v2FieldIdToString.containsKey(identifier.substring(0, 3)); } /** * Returns true if the string has matching parenthesis. This method matches all four parenthesis and also enclosed * parenthesis. * * @param str string to test * * @return true if the string has matching parenthesis */ public static boolean isMatchingParenthesis(final String str) { if (str == null) { throw new NullPointerException("String is null"); } final TagOptionSingleton option = TagOptionSingleton.getInstance(); final int length = str.length(); for (int i = 0; i < length; i++) { final char ch = str.charAt(i); if (option.isCloseParenthesis(Character.toString(ch))) { return false; } if (option.isOpenParenthesis(Character.toString(ch))) { i = findMatchingParenthesis(str, i); if (i < 0) { return false; } } } return true; } /** * Given an object, try to return it as a <code>long</code>. This tries to parse a string, and takes <code>Long, * Short, Byte, Integer</code> objects and gets their value. An exception is not explicityly thrown here because it * would causes too many other methods to also throw it. * * @param value object to find long from. * * @return <code>long</code> value */ public static long getWholeNumber(final Object value) { if (value == null) { throw new NullPointerException("Value is null"); } final long number; if (value instanceof String) { number = Long.parseLong((String) value); } else if (value instanceof Byte) { number = ((Byte) value).byteValue(); } else if (value instanceof Short) { number = ((Short) value).shortValue(); } else if (value instanceof Integer) { number = ((Integer) value).intValue(); } else if (value instanceof Long) { number = ((Long) value).longValue(); } else { throw new IllegalArgumentException("Unsupported value class: " + value.getClass().getName()); } return number; } /** * Add a timestamp string to a given string. This is used in the GUI and I'm not sure why it is defined here. * * @param text textarea string to insert to * @param origPos current position of the cursor * * @return new string to use in the text area */ public static String addTimeStampToTextArea(final String text, final int origPos) { //todo move this to a GUI class //todo fix the case of adding time stamp to EOLN, EOLN, EOF (adding final String newText; if (text.length() == 0) { // special empty case newText = "[00:00]"; } else { int i = origPos; i = Math.min(i, text.length() - 1); // if at end of whole string if (text.charAt(i) == '\n') { i--; // if at the end of line } for (; i > 0; i--) { if (text.charAt(i) == '\n') { break; } } if (i == 0) { // if at very first character newText = "[00:00]" + text; } else { i++; final String before = text.substring(0, i); final String after = text.substring(i); newText = before + "[00:00]" + after; } } return newText; } public static String appendBeforeExtension(final String filename, final String addition) { if (addition == null) { return filename; } if (filename == null) { return addition; } final int index = filename.lastIndexOf('.'); if (index < 0) { return filename + addition; } return filename.substring(0, index) + addition + filename.substring(index); } public static String convertFrameID2_2to2_3(final String identifier) { if (identifier == null) { throw new NullPointerException("Identifier is null"); } if (identifier.length() < 3) { return null; } return (String) TagConstant.id3v2_2ToId3v2_3.get(identifier.subSequence(0, 3)); } public static String convertFrameID2_2to2_4(final String identifier) { if (identifier == null) { throw new NullPointerException("Identifier is null"); } if (identifier.length() < 3) { return null; } String id = (String) TagConstant.id3v2_2ToId3v2_3.get(identifier.substring(0, 3)); if (id != null) { id = (String) TagConstant.id3v2_3ToId3v2_4.get(id); } return id; } public static String convertFrameID2_3to2_2(final String identifier) { if (identifier == null) { throw new NullPointerException("Identifier is null"); } if (identifier.length() < 4) { return null; } return (String) TagConstant.id3v2_3ToId3v2_2.get(identifier.substring(0, 4)); } public static String convertFrameID2_3to2_4(final String identifier) { if (identifier == null) { throw new NullPointerException("Identifier is null"); } if (identifier.length() < 4) { return null; } return (String) TagConstant.id3v2_3ToId3v2_4.get(identifier.substring(0, 4)); } public static String convertFrameID2_4to2_2(final String identifier) { if (identifier == null) { throw new NullPointerException("Identifier is null"); } if (identifier.length() < 4) { return null; } String id = (String) TagConstant.id3v2_4ToId3v2_3.get(identifier.substring(0, 4)); if (id != null) { id = (String) TagConstant.id3v2_3ToId3v2_2.get(id); } return id; } public static String convertFrameID2_4to2_3(final String identifier) { if (identifier == null) { throw new NullPointerException("Identifier is null"); } if (identifier.length() < 4) { return null; } return (String) TagConstant.id3v2_4ToId3v2_3.get(identifier); } /** * Copy the source file to the destination file. The destination file will be deleted first before copying starts. */ public static void copyFile(final File source, final File destination) throws FileNotFoundException, IOException { if (source == null) { throw new NullPointerException("Source is null"); } if (destination == null) { throw new NullPointerException("Destination is null"); } if (source.exists() == false) { throw new NullPointerException("Source file not found."); } FileInputStream fio = null; BufferedInputStream bio = null; FileOutputStream fos = null; BufferedOutputStream bos = null; final byte[] buffer; try { if (destination.exists()) { destination.delete(); } fio = new FileInputStream(source); bio = new BufferedInputStream(fio); fos = new FileOutputStream(destination); bos = new BufferedOutputStream(fos); buffer = new byte[1024]; int b = bio.read(buffer); while (b != -1) { bos.write(buffer, 0, b); b = bio.read(buffer); } } finally { if (bio != null) { bio.close(); } if (bos != null) { bos.flush(); bos.close(); } if (fos != null) { fos.close(); } if (fio != null) { fio.close(); } } } /** * Unable to instantiate abstract classes, so can't call the copy constructor. So find out the instianted class name * and call the copy constructor through reflection. */ public static Object copyObject(final Object copyObject) { final Constructor constructor; final Class[] constructorParameterArray; final Object[] parameterArray; if (copyObject == null) { return null; } try { constructorParameterArray = new Class[1]; constructorParameterArray[0] = copyObject.getClass(); constructor = copyObject.getClass().getConstructor(constructorParameterArray); parameterArray = new Object[1]; parameterArray[0] = copyObject; return constructor.newInstance(parameterArray); } catch (NoSuchMethodException ex) { throw new IllegalArgumentException("NoSuchMethodException: Error finding constructor to create copy"); } catch (IllegalAccessException ex) { throw new IllegalArgumentException("IllegalAccessException: No access to run constructor to create copy"); } catch (InstantiationException ex) { throw new IllegalArgumentException("InstantiationException: Unable to instantiate constructor to copy"); } catch (java.lang.reflect.InvocationTargetException ex) { throw new IllegalArgumentException("InvocationTargetException: Unable to invoke constructor to create copy"); } } /** * return the index of the matching of parenthesis. This will match all four parenthesis and enclosed parenthesis. * * @param str string to search * @param index index of string to start searching. This index should point to the opening parenthesis. * * @return index of the matching parenthesis. -1 is returned if none is found, or if the parenthesis are * unbalanced. */ public static int findMatchingParenthesis(final String str, final int index) { if (str == null) { throw new NullPointerException("String is null"); } if ((index < 0) || (index >= str.length())) { throw new IndexOutOfBoundsException("Index to image string is out of bounds: offset = " + index + ", string.length()" + str.length()); } final TagOptionSingleton option = TagOptionSingleton.getInstance(); final Stack stack = new Stack(); String chString; String open; char ch; if (index >= 0) { final int length = str.length(); if (length == 0) { return 0; } for (int i = index; i < length; i++) { ch = str.charAt(i); chString = ch + ""; if (option.isOpenParenthesis(chString)) { stack.push(chString); } if (option.isCloseParenthesis(chString)) { if (stack.size() <= 0) { return -1; } open = (String) stack.pop(); if (option.getCloseParenthesis(open).equals(chString) == false) { return -1; } } if (stack.size() <= 0) { return i; } } } return -1; } /** * Find the first whole number that can be parsed from the string * * @param str string to search * * @return first whole number that can be parsed from the string */ public static long findNumber(final String str) throws TagException { return findNumber(str, 0); } /** * Find the first whole number that can be parsed from the string * * @param str string to search * @param offset start seaching from this index * * @return first whole number that can be parsed from the string */ public static long findNumber(final String str, final int offset) throws TagException { if (str == null) { throw new NullPointerException("String is null"); } if ((offset < 0) || (offset >= str.length())) { throw new IndexOutOfBoundsException("Offset to image string is out of bounds: offset = " + offset + ", string.length()" + str.length()); } int i; int j; final long num; i = offset; while (i < str.length()) { if (((str.charAt(i) >= '0') && (str.charAt(i) <= '9')) || (str.charAt(i) == '-')) { break; } i++; } j = i + 1; while (j < str.length()) { if (((str.charAt(j) < '0') || (str.charAt(j) > '9'))) { break; } j++; } if ((j <= str.length()) && (j > i)) { num = Long.parseLong(str.substring(i, j)); } else { throw new TagException("Unable to find integer in string: " + str); } return num; } /** * String formatting function to pad the given string with the given character * * @param str string to pad * @param length total length of new string * @param ch character to pad the string with * @param padBefore if true, add the padding at the start of the string. if false, add the padding at the end of the * string. * * @return new padded string. */ public static String padString(final String str, final int length, final char ch, final boolean padBefore) { if (length < 0) { return str; } else if (length == 0) { if (str == null) { return ""; } return str; } int strLength = 0; if (str != null) { strLength = str.length(); } if (strLength >= length) { return str; } final char[] buffer = new char[length]; int next = 0; if (padBefore) { for (int i = 0; i < (length - strLength); i++) { buffer[next++] = ch; } } if (str != null) { for (int i = 0; i < strLength; i++) { buffer[next++] = str.charAt(i); } } if (padBefore == false) { for (int i = 0; i < (length - strLength); i++) { buffer[next++] = ch; } } return new String(buffer); } /** * Replace the Unix end of line character with the DOS end of line character. * * @param text string to search and replace * * @return replaced string */ public static String replaceEOLNwithCRLF(final String text) { String newText = null; if (text != null) { newText = ""; int oldPos = 0; int newPos = text.indexOf('\n'); while (newPos >= 0) { newText += (text.substring(oldPos, newPos) + TagConstant.SEPERATOR_LINE); oldPos = newPos + 1; newPos = text.indexOf('\n', oldPos); } newText += text.substring(oldPos); } return newText; } /** * Search the <code>source</code> string for any occurance of <code>oldString</code> and replaced them all with * <code>newString</code>. This searches for the entire word of old string. A blank space is appended to the front * and back of <code>oldString</code> */ public static String replaceWord(String source, final String oldString, String newString) { if (source == null) { throw new NullPointerException("Source is null"); } if (oldString == null) { throw new NullPointerException("Old string (string to be replaced) is null"); } if ((source.length() > 0) && (oldString.length() > 0)) { if (newString == null) { newString = ""; } final StringBuffer str = new StringBuffer(source); int index = str.indexOf(oldString); final int length = oldString.length(); while (index >= 0) { if (((index == 0) && Character.isWhitespace(str.charAt(index + length))) || (Character.isWhitespace(str.charAt(index - 1)) && ((index + length) >= str.length())) || (Character.isWhitespace(str.charAt(index - 1)) && Character.isWhitespace(str.charAt(index + length)))) { str.replace(index, index + length, newString); } index = str.indexOf(oldString, index); } source = str.toString(); } return source; } /** * Remove all occurances of the given character from the string argument. * * @param str String to search * @param ch character to remove * * @return new String without the given charcter */ public static String stripChar(final String str, final char ch) { if (str == null) { throw new NullPointerException("String is null"); } final char[] buffer = new char[str.length()]; int next = 0; for (int i = 0; i < str.length(); i++) { if (str.charAt(i) != ch) { buffer[next++] = str.charAt(i); } } return new String(buffer, 0, next); } /** * Change the given string into sentence case. Sentence case has the first words always capitalized. Any words in * <code>TagConstants.upperLowerCase</code> will be capitalized that way. Any other words will be turned lower * case. * * @param str String to modify * @param keepUppercase if true, keep a word if it is already all in UPPERCASE * * @return new string in sentence case. */ public static String toSentenceCase(final String str, final boolean keepUppercase) { if (str == null) { throw new NullPointerException("String is null"); } final StringTokenizer tokenizer = new StringTokenizer(str); String token; final int numberTokens = tokenizer.countTokens(); int countedTokens = 0; final StringBuffer newString = new StringBuffer(); // Capitalize first word of all sentences. if (tokenizer.hasMoreTokens()) { token = tokenizer.nextToken(); newString.append(capitalizeWord(token, keepUppercase)); newString.append(' '); countedTokens++; } // go through all remainder tokens while (tokenizer.hasMoreTokens() && (countedTokens < numberTokens)) { token = tokenizer.nextToken(); countedTokens++; if (capitalizationMap.containsKey(token.toLowerCase())) { newString.append(capitalizationMap.get(token.toLowerCase())); } else if (keepUppercase && token.toUpperCase().equals(token)) { newString.append(token); } else { newString.append(token.toLowerCase()); } newString.append(' '); } // remove trailing space if (newString.length() > 0) { newString.deleteCharAt(newString.length() - 1); } return newString.toString(); } /** * Change the given string to title case. The first and last words of the string are always capitilized. Any words * in <code>TagConstants.upperLowerCase</code> will be capitalized that way. Any other words will be capitalized. * * @param str String to modify * @param keepUppercase if true, keep a word if it is already all in UPPERCASE * * @return new capitlized string. */ public static String toTitleCase(final String str, final boolean keepUppercase) { if (str == null) { throw new NullPointerException("String is null"); } final StringTokenizer tokenizer = new StringTokenizer(str); String token; final int numberTokens = tokenizer.countTokens(); int countedTokens = 0; final StringBuffer newString = new StringBuffer(); // Capitalize first word of all titles. if (tokenizer.hasMoreTokens()) { token = tokenizer.nextToken(); newString.append(capitalizeWord(token, keepUppercase)); newString.append(' '); countedTokens++; } // go through all remainder tokens except last while (tokenizer.hasMoreTokens() && (countedTokens < (numberTokens - 1))) { token = tokenizer.nextToken(); countedTokens++; if (capitalizationMap.containsKey(token.toLowerCase())) { newString.append(capitalizationMap.get(token.toLowerCase())); } else { newString.append(capitalizeWord(token, keepUppercase)); } newString.append(' '); } // Capitalize last word of all titles. if (tokenizer.hasMoreTokens()) { token = tokenizer.nextToken(); newString.append(capitalizeWord(token, keepUppercase)); newString.append(' '); } // remove trailing space if (newString.length() > 0) { newString.deleteCharAt(newString.length() - 1); } return newString.toString(); } /** * truncate a string if it longer than the argument * * @param str String to truncate * @param len maximum desired length of new string */ public static String truncate(final String str, final int len) { if (str == null) { throw new NullPointerException("String is null"); } if (len < 0) { throw new IndexOutOfBoundsException("Length is less than zero"); } if (str.length() > len) { return str.substring(0, len); } return str.trim(); } /** * Capitalize the word with the first letter upper case and all others lower case. * * @param word word to capitalize. * @param keepUppercase if true, keep a word if it is already all in UPPERCASE * * @return new capitalized word. */ private static StringBuffer capitalizeWord(String word, final boolean keepUppercase) { if (word == null) { return null; } final StringBuffer wordBuffer = new StringBuffer(); int index = 0; if (keepUppercase && word.toUpperCase().equals(word)) { wordBuffer.append(word); } else { word = word.toLowerCase(); final int len = word.length(); char ch; ch = word.charAt(index); while (((ch < 'a') || (ch > 'z')) && (index < (len - 1))) { ch = word.charAt(++index); } if (index < len) { wordBuffer.append(word.substring(0, index)); wordBuffer.append((char) (ch + UPPERCASE)); wordBuffer.append(word.substring(index + 1)); } else { wordBuffer.append(word); } } return wordBuffer; } }