/* * Jajuk * Copyright (C) The Jajuk Team * http://jajuk.info * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package org.jajuk.util; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import org.apache.commons.lang.StringUtils; import org.jajuk.base.AlbumManager; import org.jajuk.base.File; import org.jajuk.base.FileManager; import org.jajuk.base.GenreManager; import org.jajuk.base.ItemManager; import org.jajuk.base.PropertyMetaInformation; import org.jajuk.base.Track; import org.jajuk.base.TrackManager; import org.jajuk.util.error.JajukException; /** * Set of convenient classes for string manipulation. */ public final class UtilString { /** The list of characters that we need to escape in strings. */ private final static String ESCAPE_CHARACTERS = "\\[](){}.*+?$^|-"; private static final ThreadLocal<DateFormat> dateFormat = new ThreadLocal<DateFormat>(); /** Constant date FORMATTER, one by thread for perfs, we need an instance by thread because this class is not thread safe. */ private static final ThreadLocal<SimpleDateFormat> FORMATTER = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat(Const.ADDITION_DATE_FORMAT, Locale.getDefault()); } }; /** * private constructor to avoid instantiating utility class. */ private UtilString() { } /** * Apply the Album pattern. * * @param file file to apply pattern to * @param sPattern * @param bMandatory are all needed tags mandatory ? * @param normalize * @param out * @param track * * @return the string * * @throws JajukException the jajuk exception */ private static String applyAlbumPattern(final org.jajuk.base.File file, final String sPattern, final boolean bMandatory, final boolean normalize, final String out, final Track track) throws JajukException { String ret = out; String sValue; if (sPattern.contains(Const.PATTERN_ALBUM)) { sValue = track.getAlbum().getName(); if (normalize) { sValue = UtilSystem.getNormalizedFilename(sValue); } if (!sValue.equals(Const.UNKNOWN_ALBUM)) { ret = ret.replace(Const.PATTERN_ALBUM, AlbumManager.format(sValue)); } else { if (bMandatory) { throw new JajukException(149, file.getAbsolutePath()); } else { ret = ret.replace(Const.PATTERN_ALBUM, Messages.getString(Const.UNKNOWN_ALBUM)); } } } return ret; } /** * Apply the Year pattern. * * @param file file to apply pattern to * @param sPattern * @param bMandatory are all needed tags mandatory ? * @param out * @param track * * @return the string * * @throws JajukException the jajuk exception */ private static String applyYearPattern(final org.jajuk.base.File file, final String sPattern, final boolean bMandatory, final String out, final Track track) throws JajukException { String ret = out; if (sPattern.contains(Const.PATTERN_YEAR)) { if (track.getYear().getValue() != 0) { ret = ret.replace(Const.PATTERN_YEAR, track.getYear().getValue() + ""); } else { if (bMandatory) { throw new JajukException(148, file.getAbsolutePath()); } else { ret = ret.replace(Const.PATTERN_YEAR, "?"); } } } return ret; } /** * Apply the Track pattern. * * @param sPattern * @param normalize * @param out * @param track * * @return the string */ private static String applyTrackPattern(final String sPattern, final boolean normalize, final String out, final Track track) { String ret = out; String sValue; if (sPattern.contains(Const.PATTERN_TRACKNAME)) { sValue = track.getName(); if (normalize) { sValue = UtilSystem.getNormalizedFilename(sValue); } ret = ret.replace(Const.PATTERN_TRACKNAME, sValue); } return ret; } /** * Apply the Track Order pattern. * * @param file file to apply pattern to * @param sPattern * @param bMandatory are all needed tags mandatory ? * @param out * @param track * * @return the string * * @throws JajukException the jajuk exception */ private static String applyTrackOrderPattern(final org.jajuk.base.File file, final String sPattern, final boolean bMandatory, final String out, final Track track) throws JajukException { if (sPattern.contains(Const.PATTERN_TRACKORDER)) { // override Order from filename if not set explicitly long lOrder = handleOrder(file, bMandatory, track); // prepend one digit numbers with "0" if (lOrder < 10) { return out.replace(Const.PATTERN_TRACKORDER, "0" + lOrder); } else { return out.replace(Const.PATTERN_TRACKORDER, lOrder + ""); } } return out; } /** * Handle order. * * * @param file * @param bMandatory * @param track * * @return the long * * @throws JajukException the jajuk exception */ private static long handleOrder(final org.jajuk.base.File file, final boolean bMandatory, final Track track) throws JajukException { long lOrder = track.getOrder(); if (lOrder == 0) { final String sFilename = file.getName(); if (Character.isDigit(sFilename.charAt(0))) { final String sTo = sFilename.substring(0, 3).trim().replaceAll("[^0-9]", ""); for (final char c : sTo.toCharArray()) { if (!Character.isDigit(c)) { throw new JajukException(152, file.getAbsolutePath()); } } lOrder = Long.parseLong(sTo); } else { if (bMandatory) { throw new JajukException(152, file.getAbsolutePath()); } else { lOrder = 0; } } } return lOrder; } /** * Apply the Genre pattern. * * @param file file to apply pattern to * @param sPattern * @param bMandatory are all needed tags mandatory ? * @param normalize * @param out * @param track * * @return the string * * @throws JajukException the jajuk exception */ private static String applyGenrePattern(final org.jajuk.base.File file, final String sPattern, final boolean bMandatory, final boolean normalize, final String out, final Track track) throws JajukException { String ret = out; String sValue; if (sPattern.contains(Const.PATTERN_GENRE)) { sValue = track.getGenre().getName(); if (normalize) { sValue = UtilSystem.getNormalizedFilename(sValue); } if (!sValue.equals(Const.UNKNOWN_GENRE)) { ret = ret.replace(Const.PATTERN_GENRE, GenreManager.format(sValue)); } else { if (bMandatory) { throw new JajukException(153, file.getAbsolutePath()); } else { ret = ret.replace(Const.PATTERN_GENRE, Messages.getString(Const.UNKNOWN_GENRE)); } } } return ret; } /** * Apply the Artist pattern. * * @param file file to apply pattern to * @param sPattern * @param bMandatory are all needed tags mandatory ? * @param normalize * @param out * @param track * * @return the string * * @throws JajukException the jajuk exception */ private static String applyArtistPattern(final org.jajuk.base.File file, final String sPattern, final boolean bMandatory, final boolean normalize, final String out, final Track track) throws JajukException { String ret = out; String sValue; if (sPattern.contains(Const.PATTERN_ARTIST)) { sValue = track.getArtist().getName(); if (normalize) { sValue = UtilSystem.getNormalizedFilename(sValue); } if (!sValue.equals(Const.UNKNOWN_ARTIST)) { ret = ret.replaceAll(Const.PATTERN_ARTIST, ItemManager.format(sValue)); } else { if (bMandatory) { throw new JajukException(150, file.getAbsolutePath()); } else { ret = ret.replaceAll(Const.PATTERN_ARTIST, Messages.getString(Const.UNKNOWN_ARTIST)); } } } return ret; } /** * Apply a pattern. This replaces certain patterns in the provided Pattern * with information from the file and returns the result. * * @param file file to apply pattern to * @param sPattern * @param bMandatory are all needed tags mandatory ? * @param normalize Remove characters non compatible with filenames in fil systems * * @return computed string * make sure the created string can be used as file name on target * file system * * @throws JajukException if some tags are missing */ public static String applyPattern(final org.jajuk.base.File file, final String sPattern, final boolean bMandatory, final boolean normalize) throws JajukException { String out = sPattern; final Track track = file.getTrack(); // Check Artist name out = UtilString.applyArtistPattern(file, sPattern, bMandatory, normalize, out, track); // Check Album artist, use artist name if no album artist out = UtilString.applyAlbumArtistPattern(sPattern, normalize, out, track); // Check Genre name out = UtilString.applyGenrePattern(file, sPattern, bMandatory, normalize, out, track); // Check Album Name out = UtilString.applyAlbumPattern(file, sPattern, bMandatory, normalize, out, track); // Check Track Order out = UtilString.applyTrackOrderPattern(file, sPattern, bMandatory, out, track); // Check Track name out = UtilString.applyTrackPattern(sPattern, normalize, out, track); // Check Year Value out = UtilString.applyYearPattern(file, sPattern, bMandatory, out, track); // Check Disc Value out = UtilString.applyDiscPattern(file, sPattern, bMandatory, out, track); // Check Custom Properties out = UtilString.applyCustomPattern(file, sPattern, normalize, out, track); return out; } /** * Apply Custom property pattern. * * @param sPattern * @param normalize * @param out * @param track * @return the string */ private static String applyCustomPattern(final org.jajuk.base.File file, String sPattern, boolean normalize, String out, Track track) { String ret = out; String sValue; //Merge files and tracks properties. file wins in they both contain a custom property with the same name. Map<String, Object> properties = track.getProperties(); properties.putAll(file.getProperties()); Collection<PropertyMetaInformation> customProperties = FileManager.getInstance() .getCustomProperties(); customProperties.addAll(TrackManager.getInstance().getCustomProperties()); Iterator<PropertyMetaInformation> it2 = customProperties.iterator(); while (it2.hasNext()) { PropertyMetaInformation meta = it2.next(); if (sPattern.contains("%" + meta.getName())) { Object o = properties.get(meta.getName()); if (o != null) { sValue = o.toString(); } else { sValue = meta.getDefaultValue().toString(); } if (normalize) { sValue = UtilSystem.getNormalizedFilename(sValue); } ret = ret.replaceAll("%" + meta.getName(), sValue); } } return ret; } /** * Apply album artist pattern. * * @param sPattern * @param normalize * @param out * @param track * @return the string */ private static String applyAlbumArtistPattern(String sPattern, boolean normalize, String out, Track track) { String ret = out; String sValue; if (sPattern.contains(Const.PATTERN_ALBUM_ARTIST)) { sValue = track.getAlbumArtistOrArtist(); if (normalize) { sValue = UtilSystem.getNormalizedFilename(sValue); } ret = ret.replaceAll(Const.PATTERN_ALBUM_ARTIST, ItemManager.format(sValue)); } return ret; } /** * Apply disc pattern. * * @param file * @param sPattern * @param bMandatory * @param out * @param track * @return the string * @throws JajukException the jajuk exception */ private static String applyDiscPattern(File file, String sPattern, boolean bMandatory, String out, Track track) throws JajukException { if (sPattern.contains(Const.PATTERN_DISC)) { // override Order from filename if not set explicitly long lDiscNumber = handleDiscNumber(file, bMandatory, track); // prepend one digit numbers with "0" if (lDiscNumber < 10) { return out.replace(Const.PATTERN_DISC, "0" + lDiscNumber); } else { return out.replace(Const.PATTERN_DISC, lDiscNumber + ""); } } return out; } /** * Handle disc number. * * * @param file * @param bMandatory * @param track * * @return the long * * @throws JajukException the jajuk exception */ private static long handleDiscNumber(File file, boolean bMandatory, Track track) throws JajukException { long lDiscNumber = track.getDiscNumber(); if (lDiscNumber == 0) { final String sFilename = file.getName(); if (Character.isDigit(sFilename.charAt(0))) { final String sTo = sFilename.substring(0, 3).trim().replaceAll("[^0-9]", ""); for (final char c : sTo.toCharArray()) { if (!Character.isDigit(c)) { throw new JajukException(152, file.getAbsolutePath()); } } lDiscNumber = Long.parseLong(sTo); } else { if (bMandatory) { throw new JajukException(152, file.getAbsolutePath()); } else { lDiscNumber = 0; } } } return lDiscNumber; } /** * Contains non digit or letters. * * @param s String to analyse * * @return whether the given string contains non digit or letter characters */ public static boolean containsNonDigitOrLetters(final String s) { boolean bOK = false; for (int i = 0; i < s.length(); i++) { if (!Character.isLetterOrDigit(s.charAt(i))) { bOK = true; break; } } return bOK; } /** * Encode URLS. * * @param s * * @return the string */ public static String encodeURL(final String s) { return s.replaceAll(" +", "+"); } /* * Escape (in the regexp sense) a string Source Search reserved: $ ( ) * + - . ? [ \ ] ^ { | } * http://mindprod.com/jgloss/regex.html */ /** * Escape string. * * * @param s * * @return the string */ public static String escapeString(String s) { int length = s.length(); StringBuilder buffer = new StringBuilder(2 * length); for (int i = 0; i != length; i++) { char c = s.charAt(i); // if we have a character that needs to be escaped, we prepend backslash // before it if (ESCAPE_CHARACTERS.indexOf(c) != -1) { buffer.append('\\'); } // now append the actual character buffer.append(c); } return buffer.toString(); } /** * Format an object to a string. * * @param oValue * @param meta * @param bHuman is this string intended to be human-readable ? * @return the string */ public static String format(final Object oValue, final PropertyMetaInformation meta, final boolean bHuman) { final Class<?> cType = meta.getType(); // default (works for strings, long and double) String sValue = oValue.toString(); if (cType.equals(Date.class)) { if (bHuman) { sValue = getLocaleDateFormatter().format((Date) oValue); } else { sValue = UtilString.getAdditionDateFormatter().format((Date) oValue); } } else if (cType.equals(Class.class)) { sValue = oValue.getClass().getName(); } return sValue; } /** * Gets the locale date formatter. * * @return locale date FORMATTER instance */ public static DateFormat getLocaleDateFormatter() { // store the dateFormat as ThreadLocal to avoid performance impact via the costly construction if (dateFormat.get() == null) { dateFormat.set(DateFormat.getDateInstance(DateFormat.DEFAULT, Locale.getDefault())); } return dateFormat.get(); } /** * Formatter for properties dialog window. * * @param sDesc * * @return the string */ public static String formatPropertyDesc(final String sDesc) { return "<HTML><center><b><font size=+0 color=#000000>" + sDesc + "</font></b><HTML>"; } /** * format genre: first letter uppercase and others lowercase. * * @param genre * * @return the string */ public static String formatGenre(final String genre) { if (genre.length() == 0) { return ""; } if (genre.length() == 1) { return genre.substring(0, 1).toUpperCase(Locale.getDefault()); } // construct string with first letter uppercase and rest lowercase return genre.substring(0, 1).toUpperCase(Locale.getDefault()) + genre.toLowerCase(Locale.getDefault()).substring(1); } /** * Performs some cleanups for strings comming from tag libs. * * @param s * * @return the string */ public static String formatTag(final String s) { // we delete all non char characters to avoid parsing errors char c; final StringBuilder sb = new StringBuilder(s.length()); for (int i = 0; i < s.length(); i++) { c = s.charAt(i); if (UtilString.isChar(c)) { sb.append(c); } } return sb.toString().trim(); } /** * Format a time from secs to a human readable format. * * @param lTime * * @return the string */ public static String formatTimeBySec(final long lTime) { // Convert time to int for performance reasons int l = (int) lTime; if (l == -1) { // means we are in repeat mode return "--:--"; } else if (l < 0) { // make sure to to get negative values l = 0; } final int hours = l / 3600; final int mins = l / 60 - (hours * 60); final int secs = l - (hours * 3600) - (mins * 60); final StringBuilder sbResult = new StringBuilder(8); if (hours > 0) { sbResult.append(UtilString.padNumber(hours, 2)).append(":"); } return sbResult.append(UtilString.padNumber(mins, 2)).append(":") .append(UtilString.padNumber(secs, 2)).toString(); } /** * Format a string before XML write * <p> * see http://www.w3.org/TR/2000/REC-xml-20001006 * <p> * substrings * <p> ' to ' * <p> " to " * <p> < to < * <p>> to > * <p> & to & * * @param s * * @return the string */ public static String formatXML(final String s) { String sOut = replaceReservedXMLChars(s); final StringBuilder sbOut = new StringBuilder(sOut.length()); /* * Transform String to XML-valid characters. XML 1.0 specs ; Character Range * [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | * [#x10000-#x10FFFF] any Unicode character, excluding the surrogate blocks, * FFFE, and FFFF. */ for (int i = 0; i < sOut.length(); i++) { final char c = sOut.charAt(i); if (UtilString.isChar(c)) { sbOut.append(c); } } return sbOut.toString(); } /** * Replace reserved xml chars. * * @param s * * @return the string */ private static String replaceReservedXMLChars(final String s) { String sOut = s; if (s.contains("&")) { sOut = sOut.replaceAll("&", "&"); } if (s.contains("\'")) { sOut = sOut.replaceAll("\'", "'"); } if (s.contains("\"")) { sOut = sOut.replaceAll("\"", """); } if (s.contains("<")) { sOut = sOut.replaceAll("<", "<"); } if (s.contains(">")) { sOut = sOut.replaceAll(">", ">"); } return sOut; } /** * Gets the addition date formatter. * * @return Thread-safe addition date simple format instance */ public static DateFormat getAdditionDateFormatter() { return FORMATTER.get(); } /** * Gets the anonymized jajuk properties. * * @return Anonymized Jajuk properties (for log or quality agent) */ public static Properties getAnonymizedJajukProperties() { final Properties properties = (Properties) Conf.getProperties().clone(); // We remove sensible data from logs properties.remove("jajuk.network.proxy_login"); properties.remove("jajuk.network.proxy_port"); properties.remove("jajuk.network.proxy_hostname"); properties.remove("jajuk.options.p2p.password"); return properties; } /** * Gets the anonymized system properties. * * @return Anonymized System properties (for log or quality agent) */ public static Properties getAnonymizedSystemProperties() { final Properties properties = (Properties) System.getProperties().clone(); // We remove sensible data from logs /* * can contain external program paths */ properties.remove("java.library.path"); properties.remove("java.class.path"); // user name is private properties.remove("user.name"); properties.remove("java.ext.dirs"); properties.remove("sun.boot.class.path"); properties.remove("deployment.user.security.trusted.certs"); properties.remove("deployment.user.security.trusted.clientauthcerts"); properties.remove("jajuk.log"); return properties; } /** * Make sure to reduce a string to the given size. * * @param sIn Input string, example: blabla * @param iSize max size, example: 3 * * @return bla... */ public static String getLimitedString(final String sIn, final int iSize) { String sOut = sIn; if (sIn.length() > iSize) { sOut = sIn.substring(0, iSize) + "..."; } return sOut; } /** * Checks if is char. * * @param ucs4char char to test * * @return whether the char is valid, code taken from Apache sax * implementation */ public static boolean isChar(final int ucs4char) { return ((ucs4char >= 32) && (ucs4char <= 55295)) || (ucs4char == 10) || (ucs4char == 9) || (ucs4char == 13) || ((ucs4char >= 57344) && (ucs4char <= 65533)) || ((ucs4char >= 0x10000) && (ucs4char <= 0x10ffff)); } /** * Checks if is xml valid. * * @param s * * @return whether given string is XML-valid */ public static boolean isXMLValid(final String s) { // check invalid chars for (int i = 0; i < s.length(); i++) { final char c = s.charAt(i); // check reserved chars if (-1 != "&\'\"<>".indexOf(c)) { return false; } if (!UtilString.isChar(c)) { return false; } } return true; } /** * Return whether a string is null or void * @param str the string to test * @return whether a string is null or void */ public static boolean isEmpty(String str) { return StringUtils.isEmpty(str); } /** * Return whether a string is neither null nor void * @param str the string to test * @return whether a string is neither null nor void */ public static boolean isNotEmpty(String str) { return !StringUtils.isEmpty(str); } /** * Pad an int with zeros. * * @param l the number to be padded * @param size the targeted size * * @return the string */ public static String padNumber(final long l, final int size) { final StringBuilder sb = new StringBuilder(Long.toString(l)); while (sb.length() < size) { sb.insert(0, '0'); } return sb.toString(); } /** * Parse a string to an object. * * @param sValue * @param cType * * @return parsed item * * @throws ParseException the parse exception * @throws ClassNotFoundException the class not found exception */ public static Object parse(final String sValue, final Class<?> cType) throws ParseException, ClassNotFoundException { Object oDefaultValue = sValue; // String by default if (cType.equals(Boolean.class)) { oDefaultValue = handleBoolean(sValue); } else if (cType.equals(Date.class)) { oDefaultValue = getAdditionDateFormatter().parseObject(sValue); } else if (cType.equals(Long.class)) { oDefaultValue = Long.parseLong(sValue); } else if (cType.equals(Double.class)) { oDefaultValue = Double.parseDouble(sValue); } else if (cType.equals(Class.class)) { oDefaultValue = Class.forName(sValue); } return oDefaultValue; } /** * Handle boolean. * * * @param sValue * * @return the boolean */ private static Boolean handleBoolean(final String sValue) { Boolean oValue; // "y" and "n" is an old boolean // attribute notation prior to 1.0 if ("y".equals(sValue)) { oValue = true; } else if ("n".equals(sValue)) { oValue = false; } else { oValue = fastBooleanParser(sValue); } return oValue; } /** * Fast long parser, low level check, replacement of Long.parseLong() * * CAUTION : do not use if the value can be negative or you will get * unexpected results * * @param in must be a set of digits with a size > 0 and be positive * * @return the long */ public static long fastLongParser(String in) { int length = in.length(); if (length == 1) { return in.charAt(0) - 48; } long out = 0; int length2 = length - 1; for (int i = 0; i < length; i++) { int digit = in.charAt(i) - 48; out += digit * Math.pow(10, (length2 - i)); } return out; } /** * Fast Boolean parser, low level check, replacement of Boolean.parseBoolean() * * @param in must be a string beginning by true or false (lower case) * * @return true, if fast boolean parser */ public static boolean fastBooleanParser(String in) { return (in.charAt(0) == 't'); } /** * Rot13 encode/decode, * <p> * Thx http://www.idevelopment.info/data/Programming/java/security/ * java_cryptography_extension/rot13.java * </p> * * @param in text to encode / decode in rote 13 * * @return encoded /decoded text */ public static String rot13(final String in) { if (StringUtils.isBlank(in)) { return ""; } int abyte = 0; final StringBuilder tempReturn = new StringBuilder(); for (int i = 0; i < in.length(); i++) { abyte = in.charAt(i); int cap = abyte & 32; abyte &= ~cap; abyte = ((abyte >= 'A') && (abyte <= 'Z') ? ((abyte - 'A' + 13) % 26 + 'A') : abyte) | cap; tempReturn.append((char) abyte); } return tempReturn.toString(); } /** * Matches ignore case and order. * * @param tested the string to be tested * @param key the search criteria, can be several words separated by a space * * @return whether the given tested string matches the key */ public static boolean matchesIgnoreCaseAndOrder(final String tested, final String key) { String testedLower = tested.toLowerCase(Locale.getDefault()); String keyLower = key.toLowerCase(Locale.getDefault()); StringTokenizer st = new StringTokenizer(testedLower, " "); while (st.hasMoreTokens()) { String token = st.nextToken(); if (keyLower.indexOf(token) == -1) { return false; } } return true; } /** * Encode a string to unicode representation (ie \\uxxxx\\uyyyyy...) * * @param in string to encode * * @return encoded string */ public static String encodeToUnicode(String in) { StringBuilder sb = new StringBuilder(in.length() * 5); for (int i = 0; i < in.length(); i++) { char c = in.charAt(i); byte hi = (byte) (c >>> 8); byte lo = (byte) (c & 0xff); sb.append("\\u"); sb.append(byteToHex(hi) + byteToHex(lo)); } return sb.toString(); } /** * Convert byte to hexadecimal representation. * * @param b * * @return the string */ public static String byteToHex(byte b) { char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; char[] array = { hexDigit[(b >> 4) & 0x0f], hexDigit[b & 0x0f] }; return new String(array); } /** * Returns a concatenation of argument array. * * @param strings strings to be concatened * * @return concatenation of given strings */ public static String concat(Object... strings) { StringBuilder sb = new StringBuilder(); for (Object element : strings) { sb.append(element); } return sb.toString(); } /** * Code token from aTunes 1.14.0 *Copyright (C) 2006-2009 Alex Aranda, Sylvain * Gaudard, Thomas Beckers and contributors Returns list of text between * specified chars. Both chars are included in result elements. Returns empty * list if chars are not found in string in given order For example given * string "ab cd (ef) gh (ij)" and chars '(' and ')' will return a list with * two strings: "(ef)" and "(ij)" * * @param string * @param beginChar * @param endChar * * @return the text between chars */ public static final List<String> getTextBetweenChars(String string, char beginChar, char endChar) { List<String> result = new ArrayList<String>(); if (string == null || string.indexOf(beginChar) == -1 || string.indexOf(endChar) == -1) { return result; } String auxStr = string; int beginIndex = auxStr.indexOf(beginChar); int endIndex = auxStr.indexOf(endChar); while (beginIndex != -1 && endIndex != -1) { if (beginIndex < endIndex) { result.add(auxStr.substring(beginIndex, endIndex + 1)); } auxStr = auxStr.substring(endIndex + 1); beginIndex = auxStr.indexOf(beginChar); endIndex = auxStr.indexOf(endChar); } return result; } }