/* * ==================== * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of the Common Development * and Distribution License("CDDL") (the "License"). You may not use this file * except in compliance with the License. * * You can obtain a copy of the License at * http://opensource.org/licenses/cddl1.php * See the License for the specific language governing permissions and limitations * under the License. * * When distributing the Covered Code, include this CDDL Header Notice in each file * and include the License file at http://opensource.org/licenses/cddl1.php. * If applicable, add the following below this CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== */ package org.identityconnectors.common; import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.Random; import java.util.regex.Matcher; /** * String Utilities. */ public final class StringUtil { /** * Never allow this to be instantiated. */ private StringUtil() { throw new AssertionError(); } /** * Finds the index of the first digit and starts from the index specified. * * @param str * String to search for a digit. * @param startidx * Starting index from which to search * @return -1 if not found otherwise the index. */ public static int indexOfDigit(final String str, final int startidx) { int ret = -1; if (str != null) { for (int i = startidx; i < str.length(); i++) { // get the first digit.. if (Character.isDigit(str.charAt(i))) { ret = i; break; } } } return ret; } /** * Finds the index of the first digit. * * @param str * String to seach for a digit. * @return -1 if not found otherwise the index. */ public static int indexOfDigit(final String str) { return indexOfDigit(str, 0); } /** * Finds the index of the first non digit and starts from the index * specified. * * @param str * String to seach for a non digit. * @param startidx * Starting index from which to search. * @return -1 if not found otherwise the index. */ public static int indexOfNonDigit(final String str, final int startidx) { int ret = -1; if (str != null) { for (int i = startidx; i < str.length(); i++) { // get the first digit.. if (!Character.isDigit(str.charAt(i))) { ret = i; break; } } } return ret; } /** * Finds the index of the first non digit. * * @param str * String to seach for a non digit. * @return -1 if not found otherwise the index. */ public static int indexOfNonDigit(final String str) { return indexOfNonDigit(str, 0); } /** * Return the string of digits from string. * * @param str * Source string to search. */ public static String subDigitString(final String str) { return subDigitString(str, 0); } /** * Return the string of digits from string. * * @param str * Source string to search. * @param idx * Start index from which to search. */ public static String subDigitString(final String str, final int idx) { String ret = null; final int sidx = indexOfDigit(str, idx); if (sidx != -1) { final int eidx = indexOfNonDigit(str, sidx); ret = (eidx == -1) ? str.substring(sidx) : str.substring(sidx, eidx); } return ret; } /** * Removes the attribute from the source string and returns. */ public static String stripXmlAttribute(String src, String attrName) { String ret = null; // quick exit.. if (src == null) { return null; } // find the attribute and remove all occurances of it.. final char[] quote = new char[] { '\'', '"' }; ret = src; while (true) { int start = ret.indexOf(attrName); // no more attributes if (start == -1) { break; } // find the end of the attribute final int openQuote = indexOf(ret, quote, start); // there a problem because there's no open quote.. if (openQuote == -1) { break; } // look for the closed quote int closeQuote = indexOf(ret, quote, openQuote + 1); if (closeQuote == -1) { break; } // remove the space either before or after the attribute if (start - 1 >= 0 && ret.charAt(start - 1) == ' ') { start -= 1; } else if (closeQuote + 1 < ret.length() && ret.charAt(closeQuote + 1) == ' ') { closeQuote += 1; } // construct new string from parts.. final StringBuilder builder = new StringBuilder(); builder.append(ret.substring(0, start)); builder.append(ret.substring(closeQuote + 1)); ret = builder.toString(); } return ret; } /** * Removes newline characters (0x0a and 0x0d) from a string. */ public static String stripNewlines(final String src) { String dest = null; if (src != null) { final StringBuilder b = new StringBuilder(); final int max = src.length(); for (int i = 0; i < max; i++) { final char c = src.charAt(i); if (c != 0x0a && c != 0x0d) { b.append(c); } } dest = b.toString(); } return dest; } /** * Finds the start index of the comparison string regards of case. * * @param src * String to search. * @param cmp * Comparsion string to find. * @return -1 if not found otherwise the index of the starting character. */ public static int indexOfIgnoreCase(final String src, final String cmp) { // quick check exit... if (src == null || cmp == null) { return -1; } final String isrc = src.toUpperCase(); final String icmp = cmp.toUpperCase(); return isrc.indexOf(icmp); } private static final String END_XMLCOMMENT = "-->"; private static final String START_XMLCOMMENT = "<!--"; /** * Strip XML comments. */ public static String stripXmlComments(final String src) { // quick exit for invalid data if (src == null) { return null; } // loop until all comments are removed.. String ret = src; while (true) { final int start = ret.indexOf(START_XMLCOMMENT); // no xml comment if (start == -1) { break; } final int end = ret.indexOf(END_XMLCOMMENT, start); // exit invalid xml.. if (end == -1) { break; } // construct new string from parts.. final StringBuilder builder = new StringBuilder(); builder.append(ret.substring(0, start)); builder.append(ret.substring(end + END_XMLCOMMENT.length())); ret = builder.toString(); } return ret; } public static int indexOf(final String src, final char[] ch) { return indexOf(src, ch, 0); } public static int indexOf(final String src, final char[] ch, final int idx) { int ret = Integer.MAX_VALUE; for (int i = 0; i < ch.length; i++) { final int tmp = src.indexOf(ch[i], idx); if (tmp != -1 && tmp < ret) { ret = tmp; } } return (ret == Integer.MAX_VALUE) ? -1 : ret; } /** * Determines if a string is empty. Empty is defined as null or empty * string. * * <pre> * StringUtil.isEmpty(null) = true * StringUtil.isEmpty("") = true * StringUtil.isEmpty(" ") = false * StringUtil.isEmpty("bob") = false * StringUtil.isEmpty(" bob ") = false * </pre> * * @param val * string to evaluate as empty. * @return true if the string is empty else false. */ public static boolean isEmpty(final String val) { return (val == null) ? true : "".equals(val) ? true : false; } /** * Determines if a string is not empty. Its the exact opposite for * {@link #isEmpty(String)}. * * @param val * string to evaluate. * @return true if the string is not empty */ public static boolean isNotEmpty(final String val) { return !isEmpty(val); } /** * Checks if a String is whitespace, empty ("") or null. * * <pre> * StringUtil.isBlank(null) = true * StringUtil.isBlank("") = true * StringUtil.isBlank(" ") = true * StringUtil.isBlank("bob") = false * StringUtil.isBlank(" bob ") = false * </pre> * * @param val * the String to check, may be null * * @return {@code true} if the String is null, empty or whitespace */ public static boolean isBlank(final String val) { return (val == null) ? true : isEmpty(val.trim()); } /** * Checks if a String is not empty (""), not null and not whitespace only. * * <pre> * StringUtil.isBlank(null) = true * StringUtil.isBlank("") = true * StringUtil.isBlank(" ") = true * StringUtil.isBlank("bob") = false * StringUtil.isBlank(" bob ") = false * </pre> * * @param val * the String to check, may be null * * @return {@code true} if the String is not empty and not null and not * whitespace */ public static boolean isNotBlank(final String val) { return !isBlank(val); } /** * Returns a properties object w/ the key/value pairs parsed from the string * passed in. */ public static Properties toProperties(final String value) { final Properties ret = new Properties(); // make sure there's a value present.. if (isNotBlank(value)) { try { // get the bytes.. final byte[] bytes = value.getBytes("ISO-8859-1"); // load into the properties object.. ret.load(new ByteArrayInputStream(bytes)); } catch (RuntimeException ex) { // don't stop the runtime exception throw ex; } catch (Exception ex) { // throw the error.. throw new IllegalStateException(ex); } } return ret; } /** * Simple variable replacement internally using regular expressions. * * <pre> * String o = "Some string with a ${variable} in it."; * String n = replaceVariable(o, "variable", "something"); * String r = "Some string with a something in it"; * assert r.equals(n); * </pre> * * @param o * Original string to do the replacement on. * @param var * String representation of the variable to replace. * @param val * Value to replace the variable with. * @return String will all the variables replaced with the value. * * @throws IllegalArgumentException * if o is null, var is blank, or val is null. */ public static String replaceVariable(final String o, final String var, final String val) { try { if (o == null || isBlank(var) || val == null) { throw new IllegalArgumentException(); } final String regex = VAR_REG_EX_START + var + VAR_REG_EX_END; final String value = Matcher.quoteReplacement(val); return o.replaceAll(regex, value); } catch (RuntimeException e) { // catch from reqex too.. final StringBuilder bld = new StringBuilder(); bld.append(" var: ").append(var); bld.append(" val: ").append(val); bld.append(" o: ").append(o); throw new IllegalArgumentException(bld.toString()); } } private static final String VAR_REG_EX_START = "\\$\\{"; private static final String VAR_REG_EX_END = "\\}"; /** * Determines if the string parameter 'str' ends with the character value. * * @param str * String to check for the character at the end. * @param value * The character to look for at the end of the string. * @return true if character parameter is found at the end of the string * parameter otherwise false. */ public static boolean endsWith(final String str, final char value) { return StringUtil.isBlank(str) ? false : str.charAt(str.length() - 1) == value; } /** * Parses a line into a List of strings. * * @param line * String to parse. * @param fsep * Field separator * @param tqul * Text qualifier. * @return list of string separated by a delimiter passed in by 'fsep' and * text is qualified by the parameter 'tqul'. */ public static List<String> parseLine(final String line, final char fsep, final char tqul) { assert isNotBlank(line); List<String> fields = new ArrayList<String>(); // sometimes, a line will end with the delimiter; make sure we do // not create a blank key/value pair for it int length = endsWith(line, fsep) ? line.length() - 1 : line.length(); int j = 0; char ch, nextCh; int whitespace = 0; boolean inQuotes = false; boolean fieldStarted = false; boolean fieldFinished = false; StringBuilder field = new StringBuilder(); while (j < length) { ch = line.charAt(j); if (isWhitespace(ch)) { if (fieldStarted) { whitespace++; field.append(ch); } } else { fieldStarted = true; if (ch == tqul) { whitespace = 0; if (inQuotes) { if (j + 1 < length) { nextCh = line.charAt(j + 1); if (nextCh == tqul) { field.append(ch); j++; // skip the extra double quote } else { inQuotes = false; } } else { inQuotes = false; } } else { inQuotes = true; } } else if (ch == fsep) { if (inQuotes) { whitespace = 0; field.append(ch); } else { fieldFinished = true; } } else { whitespace = 0; field.append(ch); } } // else (not whitespace) if (fieldFinished) { String f = field.toString(); // Trim any white space that occurred at the end of the // field. // We can't just use trim() because there may have been // double quotes around leading and/or trailing whitespace // that the user wants to keep, and we've filtered out // the double quotes at this point. if (whitespace > 0) { f = f.substring(0, f.length() - whitespace); } fields.add(f); field.setLength(0); whitespace = 0; fieldStarted = false; fieldFinished = false; } j++; } if (inQuotes) { fields = null; throw new IllegalStateException("Unterminated quotation mark detected."); } else { // Either we were at the end of a field when we reached // the end of the line or we ended with a comma, so there // is one more empty field after the comma. Parse the // field in either case. String f = field.toString(); // Trim any white space that occurred at the end of the field. // We can't just use trim() because there may have been // double quotes around leading and/or trailing whitespace // that the user wants to keep, and we've filtered out // the double quotes at this point. if (whitespace > 0) { f = f.substring(0, f.length() - whitespace); } fields.add(f); } return fields; } /** * Determine if this is a white space character. Whitespace characters are * defined as the character ' ' and the tab character. */ public static boolean isWhitespace(final char ch) { return (ch == ' ' || ch == '\t'); } /** * Create a random Unicode string. */ public static String randomString() { return randomString(new Random()); } /** * Create a random length Unicode string based on the {@link Random} object * passed in. */ public static String randomString(final Random r) { return randomString(r, Math.abs(r.nextInt(257))); } /** * Create a random string of fixed length based on the {@link Random} object * passed in. Insure that the string is built w/ Unicode characters. * * @param r * used to get random unicode characters. * @param length * fixed length of string. * @return a randomly generated string based on the parameters. */ public static String randomString(final Random r, final int length) { final StringBuilder bld = new StringBuilder(length); while (bld.length() < length) { // get a random 16 bit number.. final int rnd = r.nextInt() & 0x0000ffff; if (Character.isLetter(rnd)) { bld.append((char) rnd); } } return bld.toString(); } }