/** * Copyright (c) 2013-2016 Angelo ZERR. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation */ package tern.utils; /** * * Utilities for {@link String}. * */ public class StringUtils { private final static int[] EMPTY_REGIONS = new int[0]; public static final String UTF_8 = "UTF-8"; public static final String TRUE = "true"; public static final String FALSE = "false"; public static final String YES = "yes"; public static final String NO = "no"; public static final String[] EMPTY_ARRAY = new String[0]; private StringUtils() { } public static boolean isEmpty(String str) { return str == null || str.length() == 0; } public static boolean hasLength(CharSequence str) { return str != null && str.length() > 0; } public static boolean hasLength(String str) { return hasLength(((CharSequence) (str))); } public static boolean hasText(CharSequence str) { if (!hasLength(str)) return false; int strLen = str.length(); for (int i = 0; i < strLen; i++) if (!Character.isWhitespace(str.charAt(i))) return true; return false; } public static boolean hasText(String str) { return hasText(((CharSequence) (str))); } public static boolean isQuoted(String string) { if (string == null || string.length() < 2) return false; int lastIndex = string.length() - 1; char firstChar = string.charAt(0); char lastChar = string.charAt(lastIndex); return firstChar == '\'' && lastChar == '\'' || firstChar == '"' && lastChar == '"'; } public static String normalizeSpace(String s) { if (s == null) { return null; } int len = s.length(); if (len < 1) { return ""; } int st = 0; int off = 0; /* avoid getfield opcode */ char[] val = s.toCharArray(); /* avoid getfield opcode */ int count = s.length(); boolean parse = true; char c; while (parse) { c = val[off + st]; parse = isParse(len, st, c); if (parse) { st++; } } parse = true; while ((st < len) && (val[off + len - 1] <= ' ')) { c = val[off + len - 1]; parse = isParse(len, st, c); if (parse) { len--; } } return ((st > 0) || (len < count)) ? s.substring(st, len) : s; } private static boolean isParse(int len, int st, char c) { return (st < len) && (c == ' ' || c == '\r' || c == '\n' || c == '\t'); } public static boolean asBoolean(String value) { return asBoolean(value, false); } public static boolean asBoolean(String value, boolean defaultValue) { if (value == null) return defaultValue; value = value.trim(); if (defaultValue) return !(FALSE.equals(value.toLowerCase()) || NO.equals(value .toLowerCase())); return TRUE.equals(value.toLowerCase()) || YES.equals(value.toLowerCase()); } // ----------- copied from JDT https://github.com/eclipse/eclipse.jdt.core/blob/master/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/StringOperation.java /** * Answers all the regions in a given name matching a given <i>pattern</i> * pattern (e.g. "H*M??"). * </p><p> * Each of these regions is made of its starting index and its length in the given * name. They are all concatenated in a single array of <code>int</code> * which therefore always has an even length. * </p><p> * Note that each region is disjointed from the following one.<br> * E.g. if the regions are <code>{ start1, length1, start2, length2 }</code>, * then <code>start1+length1</code> will always be smaller than * <code>start2</code>. * </p><p> * <pre> * Examples: * <ol> * <li> pattern = "N???Po*Ex?eption" * name = NullPointerException * result: { 0, 1, 4, 2, 11, 2, 14, 6 }</li> * <li> pattern = "Ha*M*ent*" * name = "HashMapEntry" * result: { 0, 2, 4, 1, 7, 3 }</li> * </ol></pre> *</p> * @see CharOperation#match(char[], char[], boolean) for more details on the * pattern match behavior * * @param pattern the given pattern * @param patternStart the given pattern start * @param patternEnd the given pattern end * @param name the given name * @param nameStart the given name start * @param nameEnd the given name end * @param isCaseSensitive flag to know if the matching should be case sensitive * @return an array of <code>int</code> having two slots per returned * regions (first one is the starting index of the region and the second * one the length of the region).<br> * Note that it may be <code>null</code> if the given name does not match * the pattern * @since 3.5 */ public static final int[] getPatternMatchingRegions( String pattern, int patternStart, int patternEnd, String name, int nameStart, int nameEnd, boolean isCaseSensitive) { /* !!!!!!!!!! WARNING !!!!!!!!!! * The algorithm used in this method has been fully inspired from * CharOperation#match(char[], int, int, char[], int, int, boolean). * * So, if any change needs to be applied in the algorithm, do NOT forget * to backport it in the CharOperation method! */ if (name == null) return null; // null name cannot match if (pattern == null) { // null pattern cannot match any region // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=264816 return EMPTY_REGIONS; } int iPattern = patternStart; int iName = nameStart; // init segments parts if (patternEnd < 0) patternEnd = pattern.length(); if (nameEnd < 0) nameEnd = name.length(); int questions = 0; int parts = 0; char previous = 0; for (int i=patternStart; i<patternEnd; i++) { char ch = pattern.charAt(i); switch (ch) { case '?': questions++; break; case '*': break; default: switch (previous) { case 0 : case '?': case '*': parts++; break; } } previous = ch; } if (parts == 0) { if (questions <= (nameEnd - nameStart)) return EMPTY_REGIONS; return null; } int[] segments = new int[parts*2]; /* check first segment */ int count = 0; int start = iName; char patternChar = 0; previous = 0; while ((iPattern < patternEnd) && (patternChar = pattern.charAt(iPattern)) != '*') { if (iName == nameEnd) return null; if (patternChar == '?') { switch (previous) { case 0: case '?': break; default: segments[count++] = start; segments[count++] = iPattern-start; break; } } else { if (isCaseSensitive) { if (patternChar != name.charAt(iName)) { return null; } } else if (toLowerCase(patternChar) != toLowerCase(name.charAt(iName))) { return null; } switch (previous) { case 0: case '?': start = iPattern; break; } } iName++; iPattern++; previous = patternChar; } /* check sequence of star+segment */ int segmentStart; if (patternChar == '*') { if (iPattern > 0 && previous != '?') { segments[count++] = start; segments[count++] = iName-start; start = iName; } segmentStart = ++iPattern; // skip star } else { if (iName == nameEnd) { if (count == (parts*2)) return segments; int end = patternEnd; if (previous == '?') { // last char was a '?' => purge all trailing '?' while (pattern.charAt(--end-1) == '?') { if (end == start) { return new int[] { patternStart, patternEnd-patternStart }; } } } return new int[] { start, end-start }; } return null; } int prefixStart = iName; int previousCount = count; previous = patternChar; char previousSegment = patternChar; checkSegment : while (iName < nameEnd) { if (iPattern == patternEnd) { iPattern = segmentStart; // mismatch - restart current segment iName = ++prefixStart; previous = previousSegment; continue checkSegment; } /* segment is ending */ if ((patternChar = pattern.charAt(iPattern)) == '*') { segmentStart = ++iPattern; // skip star if (segmentStart == patternEnd) { if (count < (parts*2)) { segments[count++] = start; segments[count++] = iName-start; } return segments; } switch (previous) { case '*': case '?': break; default: segments[count++] = start; segments[count++] = iName-start; break; } prefixStart = iName; start = prefixStart; previous = patternChar; previousSegment = patternChar; continue checkSegment; } /* check current name character */ previousCount = count; if (patternChar == '?') { switch (previous) { case '*': case '?': break; default: segments[count++] = start; segments[count++] = iName-start; break; } } else { boolean mismatch; if (isCaseSensitive) { mismatch = name.charAt(iName) != patternChar; } else { mismatch = toLowerCase(name.charAt(iName)) != toLowerCase(patternChar); } if (mismatch) { iPattern = segmentStart; // mismatch - restart current segment iName = ++prefixStart; start = prefixStart; count = previousCount; previous = previousSegment; continue checkSegment; } switch (previous) { case '?': start = iName; break; } } iName++; iPattern++; previous = patternChar; } if ((segmentStart == patternEnd) || (iName == nameEnd && iPattern == patternEnd) || (iPattern == patternEnd - 1 && pattern.charAt(iPattern) == '*')) { if (count < (parts*2)) { segments[count++] = start; segments[count++] = iName-start; } return segments; } return null; } private static char toLowerCase(char c) { return Character.toLowerCase(c); } }