http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/StringPredicates.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringPredicates.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringPredicates.java new file mode 100644 index 0000000..2006e65 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringPredicates.java @@ -0,0 +1,310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.text; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.util.collections.MutableSet; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +public class StringPredicates { + + /** predicate form of {@link Strings#isBlank(CharSequence)} */ + public static <T extends CharSequence> Predicate<T> isBlank() { + return new IsBlank<T>(); + } + + private static final class IsBlank<T extends CharSequence> implements Predicate<T> { + @Override + public boolean apply(@Nullable CharSequence input) { + return Strings.isBlank(input); + } + + @Override + public String toString() { + return "isBlank()"; + } + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static Predicate<CharSequence> isBlankOld() { + return new Predicate<CharSequence>() { + @Override + public boolean apply(@Nullable CharSequence input) { + return Strings.isBlank(input); + } + @Override + public String toString() { + return "isBlank"; + } + }; + } + + + /** Tests if object is non-null and not a blank string. + * <p> + * Predicate form of {@link Strings#isNonBlank(CharSequence)} also accepting objects non-null, for convenience */ + public static <T> Predicate<T> isNonBlank() { + return new IsNonBlank<T>(); + } + + private static final class IsNonBlank<T> implements Predicate<T> { + @Override + public boolean apply(@Nullable Object input) { + if (input==null) return false; + if (!(input instanceof CharSequence)) return true; + return Strings.isNonBlank((CharSequence)input); + } + + @Override + public String toString() { + return "isNonBlank()"; + } + } + + // ----------------- + + public static <T extends CharSequence> Predicate<T> containsLiteralIgnoreCase(final String fragment) { + return new ContainsLiteralIgnoreCase<T>(fragment); + } + + private static final class ContainsLiteralIgnoreCase<T extends CharSequence> implements Predicate<T> { + private final String fragment; + + private ContainsLiteralIgnoreCase(String fragment) { + this.fragment = fragment; + } + + @Override + public boolean apply(@Nullable CharSequence input) { + return Strings.containsLiteralIgnoreCase(input, fragment); + } + + @Override + public String toString() { + return "containsLiteralCaseInsensitive("+fragment+")"; + } + } + + public static <T extends CharSequence> Predicate<T> containsLiteral(final String fragment) { + return new ContainsLiteral<T>(fragment); + } + + private static final class ContainsLiteral<T extends CharSequence> implements Predicate<T> { + private final String fragment; + + private ContainsLiteral(String fragment) { + this.fragment = fragment; + } + + @Override + public boolean apply(@Nullable CharSequence input) { + return Strings.containsLiteral(input, fragment); + } + + @Override + public String toString() { + return "containsLiteral("+fragment+")"; + } + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static Predicate<CharSequence> containsLiteralCaseInsensitiveOld(final String fragment) { + return new Predicate<CharSequence>() { + @Override + public boolean apply(@Nullable CharSequence input) { + return Strings.containsLiteralIgnoreCase(input, fragment); + } + @Override + public String toString() { + return "containsLiteralCaseInsensitive("+fragment+")"; + } + }; + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static Predicate<CharSequence> containsLiteralOld(final String fragment) { + return new Predicate<CharSequence>() { + @Override + public boolean apply(@Nullable CharSequence input) { + return Strings.containsLiteral(input, fragment); + } + @Override + public String toString() { + return "containsLiteral("+fragment+")"; + } + }; + } + + // ----------------- + + public static <T extends CharSequence> Predicate<T> containsAllLiterals(final String... fragments) { + List<Predicate<CharSequence>> fragmentPredicates = Lists.newArrayList(); + for (String fragment : fragments) { + fragmentPredicates.add(containsLiteral(fragment)); + } + return Predicates.and(fragmentPredicates); + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static Predicate<CharSequence> containsAllLiteralsOld(final String... fragments) { + return Predicates.and(Iterables.transform(Arrays.asList(fragments), new Function<String,Predicate<CharSequence>>() { + @Override + public Predicate<CharSequence> apply(String input) { + return containsLiteral(input); + } + })); + } + + // ----------------- + + public static Predicate<CharSequence> containsRegex(final String regex) { + // "Pattern" ... what a bad name :) + return Predicates.containsPattern(regex); + } + + // ----------------- + + public static <T extends CharSequence> Predicate<T> startsWith(final String prefix) { + return new StartsWith<T>(prefix); + } + + private static final class StartsWith<T extends CharSequence> implements Predicate<T> { + private final String prefix; + private StartsWith(String prefix) { + this.prefix = prefix; + } + @Override + public boolean apply(CharSequence input) { + return (input != null) && input.toString().startsWith(prefix); + } + @Override + public String toString() { + return "startsWith("+prefix+")"; + } + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static Predicate<CharSequence> startsWithOld(final String prefix) { + return new Predicate<CharSequence>() { + @Override + public boolean apply(CharSequence input) { + return (input != null) && input.toString().startsWith(prefix); + } + }; + } + + // ----------------- + + /** true if the object *is* a {@link CharSequence} starting with the given prefix */ + public static Predicate<Object> isStringStartingWith(final String prefix) { + return Predicates.<Object>and(Predicates.instanceOf(CharSequence.class), + Predicates.compose(startsWith(prefix), StringFunctions.toStringFunction())); + } + + /** @deprecated since 0.7.0 kept only to allow conversion of anonymous inner classes */ + @SuppressWarnings("unused") @Deprecated + private static Predicate<Object> isStringStartingWithOld(final String prefix) { + return new Predicate<Object>() { + @Override + public boolean apply(Object input) { + return (input instanceof CharSequence) && input.toString().startsWith(prefix); + } + }; + } + + // --------------- + + public static <T> Predicate<T> equalToAny(Iterable<T> vals) { + return new EqualToAny<T>(vals); + } + + private static class EqualToAny<T> implements Predicate<T>, Serializable { + private static final long serialVersionUID = 6209304291945204422L; + private final Set<T> vals; + + public EqualToAny(Iterable<? extends T> vals) { + this.vals = MutableSet.copyOf(vals); // so allows nulls + } + @Override + public boolean apply(T input) { + return vals.contains(input); + } + @Override + public String toString() { + return "equalToAny("+vals+")"; + } + } + + // ----------- + + public static <T extends CharSequence> Predicate<T> matchesRegex(final String regex) { + return new MatchesRegex<T>(regex); + } + + protected static class MatchesRegex<T extends CharSequence> implements Predicate<T> { + protected final String regex; + protected MatchesRegex(String regex) { + this.regex = regex; + } + @Override + public boolean apply(CharSequence input) { + return (input != null) && input.toString().matches(regex); + } + @Override + public String toString() { + return "matchesRegex("+regex+")"; + } + } + + public static <T extends CharSequence> Predicate<T> matchesGlob(final String glob) { + return new MatchesGlob<T>(glob); + } + + protected static class MatchesGlob<T extends CharSequence> implements Predicate<T> { + protected final String glob; + protected MatchesGlob(String glob) { + this.glob = glob; + } + @Override + public boolean apply(CharSequence input) { + return (input != null) && WildcardGlobs.isGlobMatched(glob, input.toString()); + } + @Override + public String toString() { + return "matchesGlob("+glob+")"; + } + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/StringShortener.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringShortener.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringShortener.java new file mode 100644 index 0000000..bcf88a3 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringShortener.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.text; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** utility which takes a bunch of segments and applies shortening rules to them */ +public class StringShortener { + + protected Map<String,String> wordsByIdInOrder = new LinkedHashMap<String,String>(); + protected String separator = null; + + protected interface ShorteningRule { + /** returns the new list, with the relevant items in the list replaced */ + public int apply(LinkedHashMap<String, String> words, int maxlen, int length); + } + + protected class TruncationRule implements ShorteningRule { + public TruncationRule(String id, int len) { + this.id = id; + this.len = len; + } + String id; + int len; + + public int apply(LinkedHashMap<String, String> words, int maxlen, int length) { + String v = words.get(id); + if (v!=null && v.length()>len) { + int charsToRemove = v.length() - len; + if (length-charsToRemove < maxlen) charsToRemove = length-maxlen; + words.put(id, v.substring(0, v.length() - charsToRemove)); + length -= charsToRemove; + if (charsToRemove==v.length() && separator!=null && length>0) + length -= separator.length(); + } + return length; + } + } + + protected class RemovalRule implements ShorteningRule { + public RemovalRule(String id) { + this.id = id; + } + String id; + + public int apply(LinkedHashMap<String, String> words, int maxlen, int length) { + String v = words.get(id); + if (v!=null) { + words.remove(id); + length -= v.length(); + if (separator!=null && length>0) + length -= separator.length(); + } + return length; + } + } + + private List<ShorteningRule> rules = new ArrayList<StringShortener.ShorteningRule>(); + + + public StringShortener separator(String separator) { + this.separator = separator; + return this; + } + + public StringShortener append(String id, String text) { + String old = wordsByIdInOrder.put(id, text); + if (old!=null) { + throw new IllegalStateException("Cannot append with id '"+id+"' when id already present"); + } + // TODO expose a replace or update + return this; + } + + public StringShortener truncate(String id, int len) { + String v = wordsByIdInOrder.get(id); + if (v!=null && v.length()>len) { + wordsByIdInOrder.put(id, v.substring(0, len)); + } + return this; + } + + public StringShortener canTruncate(String id, int len) { + rules.add(new TruncationRule(id, len)); + return this; + } + + public StringShortener canRemove(String id) { + rules.add(new RemovalRule(id)); + return this; + } + + public String getStringOfMaxLength(int maxlen) { + LinkedHashMap<String, String> words = new LinkedHashMap<String,String>(); + words.putAll(wordsByIdInOrder); + int length = 0; + for (String w: words.values()) { + if (!Strings.isBlank(w)) { + length += w.length(); + if (separator!=null) + length += separator.length(); + } + } + if (separator!=null && length>0) + // remove trailing separator if one had been added + length -= separator.length(); + + List<ShorteningRule> rulesLeft = new ArrayList<ShorteningRule>(); + rulesLeft.addAll(rules); + + while (length > maxlen && !rulesLeft.isEmpty()) { + ShorteningRule r = rulesLeft.remove(0); + length = r.apply(words, maxlen, length); + } + + StringBuilder sb = new StringBuilder(); + for (String w: words.values()) { + if (!Strings.isBlank(w)) { + if (separator!=null && sb.length()>0) + sb.append(separator); + sb.append(w); + } + } + + String result = sb.toString(); + if (result.length() > maxlen) result = result.substring(0, maxlen); + + return result; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java new file mode 100644 index 0000000..0ab9a3a --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/Strings.java @@ -0,0 +1,945 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.text; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.time.Time; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Functions; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.Ordering; + +public class Strings { + + /** The empty {@link String}. */ + public static final String EMPTY = ""; + + /** + * Checks if the given string is null or is an empty string. + * Useful for pre-String.isEmpty. And useful for StringBuilder etc. + * + * @param s the String to check + * @return true if empty or null, false otherwise. + * + * @see #isNonEmpty(CharSequence) + * @see #isBlank(CharSequence) + * @see #isNonBlank(CharSequence) + */ + public static boolean isEmpty(CharSequence s) { + // Note guava has com.google.common.base.Strings.isNullOrEmpty(String), + // but that is just for String rather than CharSequence + return s == null || s.length()==0; + } + + /** + * Checks if the given string is empty or only consists of whitespace. + * + * @param s the String to check + * @return true if blank, empty or null, false otherwise. + * + * @see #isEmpty(CharSequence) + * @see #isNonEmpty(CharSequence) + * @see #isNonBlank(CharSequence) + */ + public static boolean isBlank(CharSequence s) { + return isEmpty(s) || CharMatcher.WHITESPACE.matchesAllOf(s); + } + + /** + * The inverse of {@link #isEmpty(CharSequence)}. + * + * @param s the String to check + * @return true if non empty, false otherwise. + * + * @see #isEmpty(CharSequence) + * @see #isBlank(CharSequence) + * @see #isNonBlank(CharSequence) + */ + public static boolean isNonEmpty(CharSequence s) { + return !isEmpty(s); + } + + /** + * The inverse of {@link #isBlank(CharSequence)}. + * + * @param s the String to check + * @return true if non blank, false otherwise. + * + * @see #isEmpty(CharSequence) + * @see #isNonEmpty(CharSequence) + * @see #isBlank(CharSequence) + */ + public static boolean isNonBlank(CharSequence s) { + return !isBlank(s); + } + + /** @return a {@link Maybe} object which is absent if the argument {@link #isBlank(CharSequence)} */ + public static <T extends CharSequence> Maybe<T> maybeNonBlank(T s) { + if (isNonBlank(s)) return Maybe.of(s); + return Maybe.absent(); + } + + /** throws IllegalArgument if string not empty; cf. guava Preconditions.checkXxxx */ + public static void checkNonEmpty(CharSequence s) { + if (s==null) throw new IllegalArgumentException("String must not be null"); + if (s.length()==0) throw new IllegalArgumentException("String must not be empty"); + } + /** throws IllegalArgument if string not empty; cf. guava Preconditions.checkXxxx */ + public static void checkNonEmpty(CharSequence s, String message) { + if (isEmpty(s)) throw new IllegalArgumentException(message); + } + + /** + * Removes suffix from the end of the string. Returns string if it does not end with suffix. + */ + public static String removeFromEnd(String string, String suffix) { + if (isEmpty(string)) { + return string; + } else if (!isEmpty(suffix) && string.endsWith(suffix)) { + return string.substring(0, string.length() - suffix.length()); + } else { + return string; + } + } + + /** removes the first suffix in the list which is present at the end of string + * and returns that string; ignores subsequent suffixes if a matching one is found; + * returns the original string if no suffixes are at the end + * @deprecated since 0.7.0 use {@link #removeFromEnd(String, String)} or {@link #removeAllFromEnd(String, String...)} + */ + @Deprecated + public static String removeFromEnd(String string, String ...suffixes) { + if (isEmpty(string)) return string; + for (String suffix : suffixes) + if (suffix!=null && string.endsWith(suffix)) return string.substring(0, string.length() - suffix.length()); + return string; + } + + /** + * As removeFromEnd, but repeats until all such suffixes are gone + */ + public static String removeAllFromEnd(String string, String... suffixes) { + if (isEmpty(string)) return string; + int index = string.length(); + boolean anotherLoopNeeded = true; + while (anotherLoopNeeded) { + if (isEmpty(string)) return string; + anotherLoopNeeded = false; + for (String suffix : suffixes) + if (!isEmpty(suffix) && string.startsWith(suffix, index - suffix.length())) { + index -= suffix.length(); + anotherLoopNeeded = true; + break; + } + } + return string.substring(0, index); + } + + /** + * Removes prefix from the beginning of string. Returns string if it does not begin with prefix. + */ + public static String removeFromStart(String string, String prefix) { + if (isEmpty(string)) { + return string; + } else if (!isEmpty(prefix) && string.startsWith(prefix)) { + return string.substring(prefix.length()); + } else { + return string; + } + } + + /** removes the first prefix in the list which is present at the start of string + * and returns that string; ignores subsequent prefixes if a matching one is found; + * returns the original string if no prefixes match + * @deprecated since 0.7.0 use {@link #removeFromStart(String, String)} + */ + @Deprecated + public static String removeFromStart(String string, String ...prefixes) { + if (isEmpty(string)) return string; + for (String prefix : prefixes) + if (prefix!=null && string.startsWith(prefix)) return string.substring(prefix.length()); + return string; + } + + /** + * As {@link #removeFromStart(String, String)}, repeating until all such prefixes are gone. + */ + public static String removeAllFromStart(String string, String... prefixes) { + int index = 0; + boolean anotherLoopNeeded = true; + while (anotherLoopNeeded) { + if (isEmpty(string)) return string; + anotherLoopNeeded = false; + for (String prefix : prefixes) { + if (!isEmpty(prefix) && string.startsWith(prefix, index)) { + index += prefix.length(); + anotherLoopNeeded = true; + break; + } + } + } + return string.substring(index); + } + + /** convenience for {@link com.google.common.base.Joiner} */ + public static String join(Iterable<? extends Object> list, String separator) { + if (list==null) return null; + boolean app = false; + StringBuilder out = new StringBuilder(); + for (Object s: list) { + if (app) out.append(separator); + out.append(s); + app = true; + } + return out.toString(); + } + /** convenience for {@link com.google.common.base.Joiner} */ + public static String join(Object[] list, String separator) { + boolean app = false; + StringBuilder out = new StringBuilder(); + for (Object s: list) { + if (app) out.append(separator); + out.append(s); + app = true; + } + return out.toString(); + } + + /** convenience for joining lines together */ + public static String lines(String ...lines) { + return Joiner.on("\n").join(Arrays.asList(lines)); + } + + /** NON-REGEX - replaces all key->value entries from the replacement map in source (non-regex) */ + @SuppressWarnings("rawtypes") + public static String replaceAll(String source, Map replacements) { + for (Object rr: replacements.entrySet()) { + Map.Entry r = (Map.Entry)rr; + source = replaceAllNonRegex(source, ""+r.getKey(), ""+r.getValue()); + } + return source; + } + + /** NON-REGEX replaceAll - see the better, explicitly named {@link #replaceAllNonRegex(String, String, String)}. */ + public static String replaceAll(String source, String pattern, String replacement) { + return replaceAllNonRegex(source, pattern, replacement); + } + + /** + * Replaces all instances in source, of the given pattern, with the given replacement + * (not interpreting any arguments as regular expressions). + * <p> + * This is actually the same as the very ambiguous {@link String#replace(CharSequence, CharSequence)}, + * which does replace all, but not using regex like the similarly ambiguous {@link String#replaceAll(String, String)} as. + * Alternatively see {@link #replaceAllRegex(String, String, String)}. + */ + public static String replaceAllNonRegex(String source, String pattern, String replacement) { + if (source==null) return source; + StringBuilder result = new StringBuilder(source.length()); + for (int i=0; i<source.length(); ) { + if (source.substring(i).startsWith(pattern)) { + result.append(replacement); + i += pattern.length(); + } else { + result.append(source.charAt(i)); + i++; + } + } + return result.toString(); + } + + /** REGEX replacement -- explicit method name for reabaility, doing same as {@link String#replaceAll(String, String)}. */ + public static String replaceAllRegex(String source, String pattern, String replacement) { + return source.replaceAll(pattern, replacement); + } + + /** Valid non alphanumeric characters for filenames. */ + public static final String VALID_NON_ALPHANUM_FILE_CHARS = "-_."; + + /** + * Returns a valid filename based on the input. + * + * A valid filename starts with the first alphanumeric character, then include + * all alphanumeric characters plus those in {@link #VALID_NON_ALPHANUM_FILE_CHARS}, + * with any runs of invalid characters being replaced by {@literal _}. + * + * @throws NullPointerException if the input string is null. + * @throws IllegalArgumentException if the input string is blank. + */ + public static String makeValidFilename(String s) { + Preconditions.checkNotNull(s, "Cannot make valid filename from null string"); + Preconditions.checkArgument(isNonBlank(s), "Cannot make valid filename from blank string"); + return CharMatcher.anyOf(VALID_NON_ALPHANUM_FILE_CHARS).or(CharMatcher.JAVA_LETTER_OR_DIGIT) + .negate() + .trimAndCollapseFrom(s, '_'); + } + + /** + * A {@link CharMatcher} that matches valid Java identifier characters. + * + * @see Character#isJavaIdentifierPart(char) + */ + public static final CharMatcher IS_JAVA_IDENTIFIER_PART = CharMatcher.forPredicate(new Predicate<Character>() { + @Override + public boolean apply(@Nullable Character input) { + return input != null && Character.isJavaIdentifierPart(input); + } + }); + + /** + * Returns a valid Java identifier name based on the input. + * + * Removes certain characterss (like apostrophe), replaces one or more invalid + * characterss with {@literal _}, and prepends {@literal _} if the first character + * is only valid as an identifier part (not start). + * <p> + * The result is usually unique to s, though this isn't guaranteed, for example if + * all characters are invalid. For a unique identifier use {@link #makeValidUniqueJavaName(String)}. + * + * @see #makeValidUniqueJavaName(String) + */ + public static String makeValidJavaName(String s) { + if (s==null) return "__null"; + if (s.length()==0) return "__empty"; + String name = IS_JAVA_IDENTIFIER_PART.negate().collapseFrom(CharMatcher.is('\'').removeFrom(s), '_'); + if (!Character.isJavaIdentifierStart(s.charAt(0))) return "_" + name; + return name; + } + + /** + * Returns a unique valid java identifier name based on the input. + * + * Translated as per {@link #makeValidJavaName(String)} but with {@link String#hashCode()} + * appended where necessary to guarantee uniqueness. + * + * @see #makeValidJavaName(String) + */ + public static String makeValidUniqueJavaName(String s) { + String name = makeValidJavaName(s); + if (isEmpty(s) || IS_JAVA_IDENTIFIER_PART.matchesAllOf(s) || CharMatcher.is('\'').matchesNoneOf(s)) { + return name; + } else { + return name + "_" + s.hashCode(); + } + } + + /** @see {@link Identifiers#makeRandomId(int)} */ + public static String makeRandomId(int l) { + return Identifiers.makeRandomId(l); + } + + /** pads the string with 0's at the left up to len; no padding if i longer than len */ + public static String makeZeroPaddedString(int i, int len) { + return makePaddedString(""+i, len, "0", ""); + } + + /** pads the string with "pad" at the left up to len; no padding if base longer than len */ + public static String makePaddedString(String base, int len, String left_pad, String right_pad) { + String s = ""+(base==null ? "" : base); + while (s.length()<len) s=left_pad+s+right_pad; + return s; + } + + public static void trimAll(String[] s) { + for (int i=0; i<s.length; i++) + s[i] = (s[i]==null ? "" : s[i].trim()); + } + + /** creates a string from a real number, with specified accuracy (more iff it comes for free, ie integer-part); + * switches to E notation if needed to fit within maxlen; can be padded left up too (not useful) + * @param x number to use + * @param maxlen maximum length for the numeric string, if possible (-1 to suppress) + * @param prec number of digits accuracy desired (more kept for integers) + * @param leftPadLen will add spaces at left if necessary to make string this long (-1 to suppress) [probably not usef] + * @return such a string + */ + public static String makeRealString(double x, int maxlen, int prec, int leftPadLen) { + return makeRealString(x, maxlen, prec, leftPadLen, 0.00000000001, true); + } + /** creates a string from a real number, with specified accuracy (more iff it comes for free, ie integer-part); + * switches to E notation if needed to fit within maxlen; can be padded left up too (not useful) + * @param x number to use + * @param maxlen maximum length for the numeric string, if possible (-1 to suppress) + * @param prec number of digits accuracy desired (more kept for integers) + * @param leftPadLen will add spaces at left if necessary to make string this long (-1 to suppress) [probably not usef] + * @param skipDecimalThreshhold if positive it will not add a decimal part if the fractional part is less than this threshhold + * (but for a value 3.00001 it would show zeroes, e.g. with 3 precision and positive threshhold <= 0.00001 it would show 3.00); + * if zero or negative then decimal digits are always shown + * @param useEForSmallNumbers whether to use E notation for numbers near zero (e.g. 0.001) + * @return such a string + */ + public static String makeRealString(double x, int maxlen, int prec, int leftPadLen, double skipDecimalThreshhold, boolean useEForSmallNumbers) { + if (x<0) return "-"+makeRealString(-x, maxlen, prec, leftPadLen); + NumberFormat df = DecimalFormat.getInstance(); + //df.setMaximumFractionDigits(maxlen); + df.setMinimumFractionDigits(0); + //df.setMaximumIntegerDigits(prec); + df.setMinimumIntegerDigits(1); + df.setGroupingUsed(false); + String s; + if (x==0) { + if (skipDecimalThreshhold>0 || prec<=1) s="0"; + else { + s="0.0"; + while (s.length()<prec+1) s+="0"; + } + } else { +// long bits= Double.doubleToLongBits(x); +// int s = ((bits >> 63) == 0) ? 1 : -1; +// int e = (int)((bits >> 52) & 0x7ffL); +// long m = (e == 0) ? +// (bits & 0xfffffffffffffL) << 1 : +// (bits & 0xfffffffffffffL) | 0x10000000000000L; +// //s*m*2^(e-1075); + int log = (int)Math.floor(Math.log10(x)); + int numFractionDigits = (log>=prec ? 0 : prec-log-1); + if (numFractionDigits>0) { //need decimal digits + if (skipDecimalThreshhold>0) { + int checkFractionDigits = 0; + double multiplier = 1; + while (checkFractionDigits < numFractionDigits) { + if (Math.abs(x - Math.rint(x*multiplier)/multiplier)<skipDecimalThreshhold) + break; + checkFractionDigits++; + multiplier*=10; + } + numFractionDigits = checkFractionDigits; + } + df.setMinimumFractionDigits(numFractionDigits); + df.setMaximumFractionDigits(numFractionDigits); + } else { + //x = Math.rint(x); + df.setMaximumFractionDigits(0); + } + s = df.format(x); + if (maxlen>0 && s.length()>maxlen) { + //too long: + double signif = x/Math.pow(10,log); + if (s.indexOf(getDefaultDecimalSeparator())>=0) { + //have a decimal point; either we are very small 0.000001 + //or prec is larger than maxlen + if (Math.abs(x)<1 && useEForSmallNumbers) { + //very small-- use alternate notation + s = makeRealString(signif, -1, prec, -1) + "E"+log; + } else { + //leave it alone, user error or E not wanted + } + } else { + //no decimal point, integer part is too large, use alt notation + s = makeRealString(signif, -1, prec, -1) + "E"+log; + } + } + } + if (leftPadLen>s.length()) + return makePaddedString(s, leftPadLen, " ", ""); + else + return s; + } + + /** creates a string from a real number, with specified accuracy (more iff it comes for free, ie integer-part); + * switches to E notation if needed to fit within maxlen; can be padded left up too (not useful) + * @param x number to use + * @param maxlen maximum length for the numeric string, if possible (-1 to suppress) + * @param prec number of digits accuracy desired (more kept for integers) + * @param leftPadLen will add spaces at left if necessary to make string this long (-1 to suppress) [probably not usef] + * @return such a string + */ + public static String makeRealStringNearZero(double x, int maxlen, int prec, int leftPadLen) { + if (Math.abs(x)<0.0000000001) x=0; + NumberFormat df = DecimalFormat.getInstance(); + //df.setMaximumFractionDigits(maxlen); + df.setMinimumFractionDigits(0); + //df.setMaximumIntegerDigits(prec); + df.setMinimumIntegerDigits(1); + df.setGroupingUsed(false); + String s; + if (x==0) { + if (prec<=1) s="0"; + else { + s="0.0"; + while (s.length()<prec+1) s+="0"; + } + } else { +// long bits= Double.doubleToLongBits(x); +// int s = ((bits >> 63) == 0) ? 1 : -1; +// int e = (int)((bits >> 52) & 0x7ffL); +// long m = (e == 0) ? +// (bits & 0xfffffffffffffL) << 1 : +// (bits & 0xfffffffffffffL) | 0x10000000000000L; +// //s*m*2^(e-1075); + int log = (int)Math.floor(Math.log10(x)); + int scale = (log>=prec ? 0 : prec-log-1); + if (scale>0) { //need decimal digits + double scale10 = Math.pow(10, scale); + x = Math.rint(x*scale10)/scale10; + df.setMinimumFractionDigits(scale); + df.setMaximumFractionDigits(scale); + } else { + //x = Math.rint(x); + df.setMaximumFractionDigits(0); + } + s = df.format(x); + if (maxlen>0 && s.length()>maxlen) { + //too long: + double signif = x/Math.pow(10,log); + if (s.indexOf('.')>=0) { + //have a decimal point; either we are very small 0.000001 + //or prec is larger than maxlen + if (Math.abs(x)<1) { + //very small-- use alternate notation + s = makeRealString(signif, -1, prec, -1) + "E"+log; + } else { + //leave it alone, user error + } + } else { + //no decimal point, integer part is too large, use alt notation + s = makeRealString(signif, -1, prec, -1) + "E"+log; + } + } + } + if (leftPadLen>s.length()) + return makePaddedString(s, leftPadLen, " ", ""); + else + return s; + } + + /** returns the first word (whitespace delimited text), or null if there is none (input null or all whitespace) */ + public static String getFirstWord(String s) { + if (s==null) return null; + int start = 0; + while (start<s.length()) { + if (!Character.isWhitespace(s.charAt(start))) + break; + start++; + } + int end = start; + if (end >= s.length()) + return null; + while (end<s.length()) { + if (Character.isWhitespace(s.charAt(end))) + break; + end++; + } + return s.substring(start, end); + } + + /** returns the last word (whitespace delimited text), or null if there is none (input null or all whitespace) */ + public static String getLastWord(String s) { + if (s==null) return null; + int end = s.length()-1; + while (end >= 0) { + if (!Character.isWhitespace(s.charAt(end))) + break; + end--; + } + int start = end; + if (start < 0) + return null; + while (start >= 0) { + if (Character.isWhitespace(s.charAt(start))) + break; + start--; + } + return s.substring(start+1, end+1); + } + + /** returns the first word after the given phrase, or null if no such phrase; + * if the character immediately after the phrase is not whitespace, the non-whitespace + * sequence starting with that character will be returned */ + public static String getFirstWordAfter(String context, String phrase) { + if (context==null || phrase==null) return null; + int index = context.indexOf(phrase); + if (index<0) return null; + return getFirstWord(context.substring(index + phrase.length())); + } + + /** + * searches in context for the given phrase, and returns the <b>untrimmed</b> remainder of the first line + * on which the phrase is found + */ + public static String getRemainderOfLineAfter(String context, String phrase) { + if (context == null || phrase == null) return null; + int index = context.indexOf(phrase); + if (index < 0) return null; + int lineEndIndex = context.indexOf("\n", index); + if (lineEndIndex <= 0) { + return context.substring(index + phrase.length()); + } else { + return context.substring(index + phrase.length(), lineEndIndex); + } + } + + /** @deprecated use {@link Time#makeTimeStringRounded(long)} */ + @Deprecated + public static String makeTimeString(long utcMillis) { + return Time.makeTimeStringRounded(utcMillis); + } + + /** returns e.g. { "prefix01", ..., "prefix96" }; + * see more functional NumericRangeGlobExpander for "prefix{01-96}" + */ + public static String[] makeArray(String prefix, int count) { + String[] result = new String[count]; + int len = (""+count).length(); + for (int i=1; i<=count; i++) + result[i-1] = prefix + makePaddedString("", len, "0", ""+i); + return result; + } + + public static String[] combineArrays(String[] ...arrays) { + int totalLen = 0; + for (String[] array : arrays) { + if (array!=null) totalLen += array.length; + } + String[] result = new String[totalLen]; + int i=0; + for (String[] array : arrays) { + if (array!=null) for (String s : array) { + result[i++] = s; + } + } + return result; + } + + public static String toInitialCapOnly(String value) { + if (value==null || value.length()==0) return value; + return value.substring(0, 1).toUpperCase(Locale.ENGLISH) + value.substring(1).toLowerCase(Locale.ENGLISH); + } + + public static String reverse(String name) { + return new StringBuffer(name).reverse().toString(); + } + + public static boolean isLowerCase(String s) { + return s.toLowerCase().equals(s); + } + + public static String makeRepeated(char c, int length) { + StringBuilder result = new StringBuilder(length); + for (int i = 0; i < length; i++) { + result.append(c); + } + return result.toString(); + } + + public static String trim(String s) { + if (s==null) return null; + return s.trim(); + } + + public static String trimEnd(String s) { + if (s==null) return null; + return ("a"+s).trim().substring(1); + } + + /** returns up to maxlen characters from the start of s */ + public static String maxlen(String s, int maxlen) { + return maxlenWithEllipsis(s, maxlen, ""); + } + + /** as {@link #maxlenWithEllipsis(String, int, String) with "..." as the ellipsis */ + public static String maxlenWithEllipsis(String s, int maxlen) { + return maxlenWithEllipsis(s, maxlen, "..."); + } + /** as {@link #maxlenWithEllipsis(String, int) but replacing the last few chars with the given ellipsis */ + public static String maxlenWithEllipsis(String s, int maxlen, String ellipsis) { + if (s==null) return null; + if (ellipsis==null) ellipsis=""; + if (s.length()<=maxlen) return s; + return s.substring(0, Math.max(maxlen-ellipsis.length(), 0))+ellipsis; + } + + /** returns toString of the object if it is not null, otherwise null */ + public static String toString(Object o) { + return toStringWithValueForNull(o, null); + } + + /** returns toString of the object if it is not null, otherwise the given value */ + public static String toStringWithValueForNull(Object o, String valueIfNull) { + if (o==null) return valueIfNull; + return o.toString(); + } + + public static boolean containsLiteralIgnoreCase(CharSequence input, CharSequence fragment) { + if (input==null) return false; + if (isEmpty(fragment)) return true; + int lastValidStartPos = input.length()-fragment.length(); + char f0u = Character.toUpperCase(fragment.charAt(0)); + char f0l = Character.toLowerCase(fragment.charAt(0)); + i: for (int i=0; i<=lastValidStartPos; i++) { + char ii = input.charAt(i); + if (ii==f0l || ii==f0u) { + for (int j=1; j<fragment.length(); j++) { + if (Character.toLowerCase(input.charAt(i+j))!=Character.toLowerCase(fragment.charAt(j))) + continue i; + } + return true; + } + } + return false; + } + + public static boolean containsLiteral(CharSequence input, CharSequence fragment) { + if (input==null) return false; + if (isEmpty(fragment)) return true; + int lastValidStartPos = input.length()-fragment.length(); + char f0 = fragment.charAt(0); + i: for (int i=0; i<=lastValidStartPos; i++) { + char ii = input.charAt(i); + if (ii==f0) { + for (int j=1; j<fragment.length(); j++) { + if (input.charAt(i+j)!=fragment.charAt(j)) + continue i; + } + return true; + } + } + return false; + } + + /** Returns a size string using metric suffixes from {@link ByteSizeStrings#metric()}, e.g. 23.5MB */ + public static String makeSizeString(long sizeInBytes) { + return ByteSizeStrings.metric().makeSizeString(sizeInBytes); + } + + /** Returns a size string using ISO suffixes from {@link ByteSizeStrings#iso()}, e.g. 23.5MiB */ + public static String makeISOSizeString(long sizeInBytes) { + return ByteSizeStrings.iso().makeSizeString(sizeInBytes); + } + + /** Returns a size string using Java suffixes from {@link ByteSizeStrings#java()}, e.g. 23m */ + public static String makeJavaSizeString(long sizeInBytes) { + return ByteSizeStrings.java().makeSizeString(sizeInBytes); + } + + /** returns a configurable shortener */ + public static StringShortener shortener() { + return new StringShortener(); + } + + public static Supplier<String> toStringSupplier(Object src) { + return Suppliers.compose(Functions.toStringFunction(), Suppliers.ofInstance(src)); + } + + /** wraps a call to {@link String#format(String, Object...)} in a toString, i.e. using %s syntax, + * useful for places where we want deferred evaluation + * (e.g. as message to {@link Preconditions} to skip concatenation when not needed) */ + public static FormattedString format(String pattern, Object... args) { + return new FormattedString(pattern, args); + } + + /** returns "s" if the argument is not 1, empty string otherwise; useful when constructing plurals */ + public static String s(int count) { + return count==1 ? "" : "s"; + } + /** as {@link #s(int)} based on size of argument */ + public static String s(@Nullable Map<?,?> x) { + return s(x==null ? 0 : x.size()); + } + /** as {@link #s(int)} based on size of argument */ + public static String s(Iterable<?> x) { + if (x==null) return s(0); + return s(x.iterator()); + } + /** as {@link #s(int)} based on size of argument */ + public static String s(Iterator<?> x) { + int count = 0; + if (x==null || !x.hasNext()) {} + else { + x.next(); count++; + if (x.hasNext()) count++; + } + return s(count); + } + + /** returns "ies" if the argument is not 1, "y" otherwise; useful when constructing plurals */ + public static String ies(int count) { + return count==1 ? "y" : "ies"; + } + /** as {@link #ies(int)} based on size of argument */ + public static String ies(@Nullable Map<?,?> x) { + return ies(x==null ? 0 : x.size()); + } + /** as {@link #ies(int)} based on size of argument */ + public static String ies(Iterable<?> x) { + if (x==null) return ies(0); + return ies(x.iterator()); + } + /** as {@link #ies(int)} based on size of argument */ + public static String ies(Iterator<?> x) { + int count = 0; + if (x==null || !x.hasNext()) {} + else { + x.next(); count++; + if (x.hasNext()) count++; + } + return ies(count); + } + + /** converts a map of any objects to a map of strings, using the tostring, and returning "null" for nulls + * @deprecated since 0.7.0 use {@link #toStringMap(Map, String)} to remove ambiguity about how to handle null */ + // NB previously the javadoc here was wrong, said it returned null not "null" + @Deprecated + public static Map<String, String> toStringMap(Map<?,?> map) { + return toStringMap(map, "null"); + } + /** converts a map of any objects to a map of strings, using {@link Object#toString()}, + * with the second argument used where a value (or key) is null */ + public static Map<String, String> toStringMap(Map<?,?> map, String valueIfNull) { + if (map==null) return null; + Map<String,String> result = MutableMap.<String, String>of(); + for (Map.Entry<?,?> e: map.entrySet()) { + result.put(toStringWithValueForNull(e.getKey(), valueIfNull), toStringWithValueForNull(e.getValue(), valueIfNull)); + } + return result; + } + + /** converts a list of any objects to a list of strings, using {@link Object#toString()}, + * with the second argument used where an entry is null */ + public static List<String> toStringList(List<?> list, String valueIfNull) { + if (list==null) return null; + List<String> result = MutableList.of(); + for (Object v: list) result.add(toStringWithValueForNull(v, valueIfNull)); + return result; + } + + /** returns base repeated count times */ + public static String repeat(String base, int count) { + if (base==null) return null; + StringBuilder result = new StringBuilder(); + for (int i=0; i<count; i++) + result.append(base); + return result.toString(); + } + + /** returns comparator which compares based on length, with shorter ones first (and null before that); + * in event of a tie, it uses the toString order */ + public static Ordering<String> lengthComparator() { + return Ordering.<Integer>natural().onResultOf(StringFunctions.length()).compound(Ordering.<String>natural()).nullsFirst(); + } + + public static boolean isMultiLine(String s) { + if (s==null) return false; + if (s.indexOf('\n')>=0 || s.indexOf('\r')>=0) return true; + return false; + } + public static String getFirstLine(String s) { + int idx = s.indexOf('\n'); + if (idx==-1) return s; + return s.substring(0, idx); + } + + /** looks for first section of text in following the prefix and, if present, before the suffix; + * null if the prefix is not present in the string, and everything after the prefix if suffix is not present in the string; + * if either prefix or suffix is null, it is treated as the start/end of the string */ + public static String getFragmentBetween(String input, String prefix, String suffix) { + if (input==null) return null; + int index; + if (prefix!=null) { + index = input.indexOf(prefix); + if (index==-1) return null; + input = input.substring(index + prefix.length()); + } + if (suffix!=null) { + index = input.indexOf(suffix); + if (index>=0) input = input.substring(0, index); + } + return input; + } + + public static int getWordCount(String phrase, boolean respectQuotes) { + if (phrase==null) return 0; + phrase = phrase.trim(); + if (respectQuotes) + return new QuotedStringTokenizer(phrase).remainderAsList().size(); + else + return Collections.list(new StringTokenizer(phrase)).size(); + } + + public static char getDecimalSeparator(Locale locale) { + DecimalFormatSymbols dfs = new DecimalFormatSymbols(locale); + return dfs.getDecimalSeparator(); + } + + public static char getDefaultDecimalSeparator() { + return getDecimalSeparator(Locale.getDefault()); + } + + /** replaces each sequence of whitespace in the first string with the replacement in the second string */ + public static String collapseWhitespace(String x, String whitespaceReplacement) { + if (x==null) return null; + return replaceAllRegex(x, "\\s+", whitespaceReplacement); + } + + public static String toLowerCase(String value) { + if (value==null || value.length()==0) return value; + return value.toLowerCase(Locale.ENGLISH); + } + + /** + * @return null if var is null or empty string, otherwise return var + */ + public static String emptyToNull(String var) { + if (isNonEmpty(var)) { + return var; + } else { + return null; + } + } + + /** Returns canonicalized string from the given object, made "unique" by: + * <li> putting sets into the toString order + * <li> appending a hash code if it's longer than the max (and the max is bigger than 0) */ + public static String toUniqueString(Object x, int optionalMax) { + if (x instanceof Iterable && !(x instanceof List)) { + // unsorted collections should have a canonical order imposed + MutableList<String> result = MutableList.of(); + for (Object xi: (Iterable<?>)x) { + result.add(toUniqueString(xi, optionalMax)); + } + Collections.sort(result); + x = result.toString(); + } + if (x==null) return "{null}"; + String xs = x.toString(); + if (xs.length()<=optionalMax || optionalMax<=0) return xs; + return maxlenWithEllipsis(xs, optionalMax-8)+"/"+Integer.toHexString(xs.hashCode()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/text/WildcardGlobs.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/WildcardGlobs.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/WildcardGlobs.java new file mode 100644 index 0000000..d30b58f --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/WildcardGlobs.java @@ -0,0 +1,382 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.text; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Throwables; + +public class WildcardGlobs { + + /** returns true iff the target matches the given pattern, + * under simplified bash rules -- viz permitting * and ? and comma delimited patterns inside curly braces + * @throws InvalidPatternException */ + public static boolean isGlobMatched(String globPattern, String targetText) throws InvalidPatternException { + List<String> patterns = getGlobsAfterBraceExpansion(globPattern); + for (String p : patterns) { + if (isNoBraceGlobMatched(p, targetText)) + return true; + } + return false; + } + + /** whether a glob-ish string without braces (e.g. containing just ? and * chars) matches; + * can be used directly, also used implicitly by isGlobMatched after glob expansion */ + public static boolean isNoBraceGlobMatched(String globPattern, String target) { + int pi=0, ti=0; + while (pi<globPattern.length() && ti<target.length()) { + char pc = globPattern.charAt(pi); + char tc = target.charAt(pi); + if (pc=='?') { + pi++; ti++; + continue; + } + if (pc!='*') { + if (pc!=tc) return false; + pi++; ti++; + continue; + } + //match 0 or more chars + String prest = globPattern.substring(pi+1); + while (ti<=target.length()) { + if (isNoBraceGlobMatched(prest, target.substring(ti))) + return true; + ti++; + } + return false; + } + while (pi<globPattern.length() && globPattern.charAt(pi)=='*') + pi++; + return (pi==globPattern.length() && ti==target.length()); + } + + /** returns a list with no curly braces in any entries, + * and guaranteeing order such that any {..,X,..,Y,..} will result in X being before Y in the resulting list; + * e.g. given a{,b,c} gives a ab and ac; no special treatment of numeric ranges, quotes, or parentheses + * (see SpecialistGlobExpander for that) */ + public static List<String> getGlobsAfterBraceExpansion(String pattern) throws InvalidPatternException { + return getGlobsAfterBraceExpansion(pattern, false, PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR); + } + + /** if a string contains a demarcated phrase, e.g. between open and close parentheses, or inside unescaped quotes + * this argument determines how that phrase is treated with regards to brace expansion */ + public enum PhraseTreatment { + /** the region is treated like any other region */ + NOT_A_SPECIAL_CHAR, + /** the interior will be expanded if there is a {x,y} expression _entirely_ inside the phrase, but otherwise commas inside it will be ignored; + * it will be an error if there is a { char inside the phrase, or if the phrase is not internally well-formed with regards to the phrase characters, + * (e.g. if quotes are interior expandable and parens are anything but not_a_special_char (e.g. interior expandable or interior not expandable) + * then any expression inside a quoted phrase must have matching parentheses) */ + INTERIOR_EXPANDABLE, + /** the interior will not be expanded at all, not if there's a comma inside, and not even if there is a {x,y} expression entirely inside; the braces will be left; + * interior of parenthetical phrases must have matching parentheses (to determine the right end parenthesis), + * apart from parentheses inside any quoted phrases when quotes are interior_not_expandable which will be ignored; + * quotes inside not_expandable paren phrases will be ignored */ + INTERIOR_NOT_EXPANDABLE + }; + + protected static class ExpressionToExpand { + String resultSoFar; + String todo; + String operatorStack; + public ExpressionToExpand(String resultSoFar, String todo, String operatorStack) { + super(); + this.resultSoFar = resultSoFar; + this.todo = todo; + this.operatorStack = operatorStack; + } + @Override + public String toString() { + return "ExpressionToExpand["+todo+":"+resultSoFar+"/"+operatorStack+"]"; + } + } + /** returns a list with no curly braces in any entries; e.g. given a{,b} gives a and ab; + * quotes and parentheses are kept, but their contents may be excluded from expansion or otherwise treated specially as per the flag. + * with allowNumericRanges, "{1-3}" is permitted for {1,2,3}. */ + public static List<String> getGlobsAfterBraceExpansion(String pattern, boolean allowNumericRanges, PhraseTreatment quoteTreatment, PhraseTreatment parenthesesTreatment) throws InvalidPatternException { + List<ExpressionToExpand> patterns = new ArrayList<ExpressionToExpand>(); + List<String> result = new ArrayList<String>(); + patterns.add(new ExpressionToExpand("", pattern, "")); + while (!patterns.isEmpty()) { + ExpressionToExpand cs = patterns.remove(0); + StringBuffer resultSoFar = new StringBuffer(cs.resultSoFar); + String operatorStack = cs.operatorStack; + boolean inQuote = operatorStack.contains("\""); + boolean expanded = false; + for (int i=0; i<cs.todo.length(); i++) { + assert !expanded; + char c = cs.todo.charAt(i); + boolean inParen = operatorStack.contains("(") && + (!inQuote || operatorStack.lastIndexOf('\"')<operatorStack.lastIndexOf('(')); + if (inQuote && !(inParen && parenthesesTreatment.equals(PhraseTreatment.INTERIOR_NOT_EXPANDABLE))) { + if (c=='"') { + if (i>0 && cs.todo.charAt(i-1)=='\\') { + //this escaped quote, keep + resultSoFar.append(c); + continue; + } + //unquote + resultSoFar.append(c); + inQuote = false; + if (operatorStack.charAt(operatorStack.length()-1)!='\"') + throw new InvalidPatternException("Quoted string contents not valid, after parsing "+resultSoFar); + operatorStack = operatorStack.substring(0, operatorStack.length()-1); + continue; + } + if (quoteTreatment.equals(PhraseTreatment.INTERIOR_NOT_EXPANDABLE)) { + resultSoFar.append(c); + continue; + } + //interior is expandable, continue parsing as usual below + } + if (inParen) { + if (c==')') { + //unparen + resultSoFar.append(c); + if (operatorStack.charAt(operatorStack.length()-1)!='(') + throw new InvalidPatternException("Parenthetical contents not valid, after parsing "+resultSoFar); + operatorStack = operatorStack.substring(0, operatorStack.length()-1); + continue; + } + if (parenthesesTreatment.equals(PhraseTreatment.INTERIOR_NOT_EXPANDABLE)) { + resultSoFar.append(c); + if (c=='(') + operatorStack+="("; + continue; + } + //interior is expandable, continue parsing as usual below + } + + if (c=='"' && !quoteTreatment.equals(PhraseTreatment.NOT_A_SPECIAL_CHAR)) { + resultSoFar.append(c); + inQuote = true; + operatorStack += "\""; + continue; + } + if (c=='(' && !parenthesesTreatment.equals(PhraseTreatment.NOT_A_SPECIAL_CHAR)) { + resultSoFar.append(c); + operatorStack += "("; + continue; + } + + if (c!='{') { + resultSoFar.append(c); + continue; + } + + //brace.. we will need to expand + expanded = true; + String operatorStackBeforeExpansion = operatorStack; + int braceStartIndex = i; + int tokenStartIndex = i+1; + + //find matching close brace + List<String> tokens = new ArrayList<String>(); + operatorStack += "{"; + while (true) { + if (++i>=cs.todo.length()) { + throw new InvalidPatternException("Curly brace not closed, parsing '"+cs.todo.substring(braceStartIndex)+"' after "+resultSoFar); + } + c = cs.todo.charAt(i); + inParen = operatorStack.contains("(") && + (!inQuote || operatorStack.lastIndexOf('\"')<operatorStack.lastIndexOf('(')); + if (inQuote && !(inParen && parenthesesTreatment.equals(PhraseTreatment.INTERIOR_NOT_EXPANDABLE))) { + if (c=='"') { + if (i>0 && cs.todo.charAt(i-1)=='\\') { + //this is escaped quote, doesn't affect status + continue; + } + //unquote + inQuote = false; + if (operatorStack.charAt(operatorStack.length()-1)!='\"') + throw new InvalidPatternException("Quoted string contents not valid, after parsing "+resultSoFar+cs.todo.substring(braceStartIndex, i)); + operatorStack = operatorStack.substring(0, operatorStack.length()-1); + continue; + } + if (quoteTreatment.equals(PhraseTreatment.INTERIOR_NOT_EXPANDABLE)) { + continue; + } + //interior is expandable, continue parsing as usual below + } + if (inParen) { + if (c==')') { + //unparen + if (operatorStack.charAt(operatorStack.length()-1)!='(') + throw new InvalidPatternException("Parenthetical contents not valid, after parsing "+resultSoFar+cs.todo.substring(braceStartIndex, i)); + operatorStack = operatorStack.substring(0, operatorStack.length()-1); + continue; + } + if (parenthesesTreatment.equals(PhraseTreatment.INTERIOR_NOT_EXPANDABLE)) { + if (c=='(') + operatorStack+="("; + continue; + } + //interior is expandable, continue parsing as usual below + } + + if (c=='"' && !quoteTreatment.equals(PhraseTreatment.NOT_A_SPECIAL_CHAR)) { + inQuote = true; + operatorStack += "\""; + continue; + } + if (c=='(' && !parenthesesTreatment.equals(PhraseTreatment.NOT_A_SPECIAL_CHAR)) { + operatorStack += "("; + continue; + } + + if (c=='}') { + if (operatorStack.charAt(operatorStack.length()-1)!='{') + throw new InvalidPatternException("Brace contents not valid, mismatched operators "+operatorStack+" after parsing "+resultSoFar+cs.todo.substring(braceStartIndex, i)); + operatorStack = operatorStack.substring(0, operatorStack.length()-1); + if (operatorStack.equals(operatorStackBeforeExpansion)) { + tokens.add(cs.todo.substring(tokenStartIndex, i)); + break; + } + continue; + } + + if (c==',') { + if (operatorStack.length()==operatorStackBeforeExpansion.length()+1) { + tokens.add(cs.todo.substring(tokenStartIndex, i)); + tokenStartIndex = i+1; + continue; + } + continue; + } + + if (c=='{') { + operatorStack += c; + continue; + } + + //any other char is irrelevant + continue; + } + + assert operatorStack.equals(operatorStackBeforeExpansion); + assert cs.todo.charAt(i)=='}'; + assert !tokens.isEmpty(); + + String suffix = cs.todo.substring(i+1); + + List<ExpressionToExpand> newPatterns = new ArrayList<ExpressionToExpand>(); + for (String token : tokens) { + //System.out.println("adding: "+pre+token+post); + if (allowNumericRanges && token.matches("\\s*[0-9]+\\s*-\\s*[0-9]+\\s*")) { + int dashIndex = token.indexOf('-'); + String startS = token.substring(0, dashIndex).trim(); + String endS = token.substring(dashIndex+1).trim(); + + int start = Integer.parseInt(startS); + int end = Integer.parseInt(endS); + + if (startS.startsWith("-")) startS=startS.substring(1).trim(); + if (endS.startsWith("-")) endS=endS.substring(1).trim(); + int minLen = Math.min(startS.length(), endS.length()); + + for (int ti=start; ti<=end; ti++) { + //partial support for negative numbers, but of course they cannot (yet) be specified in the regex above so it is moot + String tokenI = ""+Math.abs(ti); + while (tokenI.length()<minLen) tokenI = "0"+tokenI; + if (ti<0) tokenI = "-"+tokenI; + newPatterns.add(new ExpressionToExpand(resultSoFar.toString(), tokenI+suffix, operatorStackBeforeExpansion)); + } + } else { + newPatterns.add(new ExpressionToExpand(resultSoFar.toString(), token+suffix, operatorStackBeforeExpansion)); + } + } + // insert new patterns at the start, so we continue to expand them next + patterns.addAll(0, newPatterns); + + break; + } + if (!expanded) { + if (operatorStack.length()>0) { + throw new InvalidPatternException("Unclosed operators "+operatorStack+" parsing "+resultSoFar); + } + result.add(resultSoFar.toString()); + } + } + assert !result.isEmpty(); + return result; + } + + public static class InvalidPatternException extends RuntimeException { + private static final long serialVersionUID = -1969068264338310749L; + public InvalidPatternException(String msg) { + super(msg); + } + } + + + /** expands globs as per #getGlobsAfterBraceExpansion, + * but also handles numeric ranges, + * and optionally allows customized treatment of quoted regions and/or parentheses. + * <p> + * simple example: machine-{0-3}-{a,b} returns 8 values, + * machine-0-a machine-0-b machine-1-a ... machine-3-b; + * NB leading zeroes are meaningful, so {00-03} expands as 00, 01, 02, 03 + * <p> + * quote INTERIOR_NOT_EXPANDABLE example: a{b,"c,d"} return ab ac,d + * <p> + * for more detail on special treatment of quote and parentheses see PhraseTreatment and WildcardGlobsTest + */ + public static class SpecialistGlobExpander { + + private boolean expandNumericRanges; + private PhraseTreatment quoteTreatment; + private PhraseTreatment parenthesesTreatment; + + public SpecialistGlobExpander(boolean expandNumericRanges, PhraseTreatment quoteTreatment, PhraseTreatment parenthesesTreatment) { + this.expandNumericRanges = expandNumericRanges; + this.quoteTreatment = quoteTreatment; + this.parenthesesTreatment = parenthesesTreatment; + } + /** expands glob, including custom syntax for numeric part */ + public List<String> expand(String glob) throws InvalidPatternException { + return getGlobsAfterBraceExpansion(glob, expandNumericRanges, quoteTreatment, parenthesesTreatment); + } + + /** returns true iff the target matches the given pattern, + * under simplified bash rules -- viz permitting * and ? and comma delimited patterns inside curly braces, + * as well as things like {1,2,5-10} (and also {01,02,05-10} to keep leading 0) + * @throws InvalidPatternException */ + public boolean isGlobMatchedNumeric(String globPattern, String targetText) throws InvalidPatternException { + List<String> patterns = expand(globPattern); + for (String p : patterns) { + if (isNoBraceGlobMatched(p, targetText)) + return true; + } + return false; + } + + /** expands glob, including custom syntax for numeric part, but to an array, and re-throwing the checked exception as a runtime exception */ + public String[] expandToArrayUnchecked(String glob) { + try { + return expand(glob).toArray(new String[0]); + } catch (InvalidPatternException e) { + throw Throwables.propagate(e); + } + } + + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/time/CountdownTimer.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/time/CountdownTimer.java b/utils/common/src/main/java/org/apache/brooklyn/util/time/CountdownTimer.java new file mode 100644 index 0000000..508657d --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/time/CountdownTimer.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.time; + +import java.util.concurrent.TimeUnit; + +import org.apache.brooklyn.util.exceptions.Exceptions; + +import com.google.common.base.Stopwatch; + +public class CountdownTimer { + + Stopwatch stopwatch = Stopwatch.createUnstarted(); + Duration limit; + + private CountdownTimer(Duration limit) { + this.limit = limit; + } + + /** starts the timer, either initially or if {@link #pause()}d; no-op if already running */ + public synchronized CountdownTimer start() { + if (!stopwatch.isRunning()) stopwatch.start(); + return this; + } + + /** pauses the timer, if running; no-op if not running */ + public synchronized CountdownTimer pause() { + if (stopwatch.isRunning()) stopwatch.stop(); + return this; + } + + /** returns underlying stopwatch, which caller can inspect for more details or modify */ + public Stopwatch getStopwatch() { + return stopwatch; + } + + /** how much total time this timer should run for */ + public Duration getLimit() { + return limit; + } + + /** return how long the timer has been running (longer than limit if {@link #isExpired()}) */ + public Duration getDurationElapsed() { + return Duration.nanos(stopwatch.elapsed(TimeUnit.NANOSECONDS)); + } + + /** returns how much time is left (negative if {@link #isExpired()}) */ + public Duration getDurationRemaining() { + return Duration.millis(limit.toMilliseconds() - stopwatch.elapsed(TimeUnit.MILLISECONDS)); + } + + /** true iff the timer has been running for the duration specified at creation time */ + public boolean isExpired() { + return stopwatch.elapsed(TimeUnit.MILLISECONDS) > limit.toMilliseconds(); + } + + /** true iff timer is running (even if it is expired) */ + public boolean isRunning() { + return stopwatch.isRunning(); + } + + // --- constructor methods + + public static CountdownTimer newInstanceStarted(Duration duration) { + return new CountdownTimer(duration).start(); + } + + public static CountdownTimer newInstancePaused(Duration duration) { + return new CountdownTimer(duration).pause(); + } + + /** block (on this object) until completed + * @throws InterruptedException */ + public synchronized void waitForExpiry() throws InterruptedException { + while (waitOnForExpiry(this)) {}; + } + + /** as {@link #waitForExpiry()} but catches and wraps InterruptedException as unchecked RuntimeInterruptedExcedption */ + public synchronized void waitForExpiryUnchecked() { + waitOnForExpiryUnchecked(this); + } + + /** block on the given argument until the timer is completed or the object receives a notified; + * callers must be synchronized on the waitTarget + * @return true if the object is notified (or receives a spurious wake), false if the duration is expired + * @throws InterruptedException */ + public boolean waitOnForExpiry(Object waitTarget) throws InterruptedException { + Duration remainder = getDurationRemaining(); + if (remainder.toMilliseconds() <= 0) + return false; + waitTarget.wait(remainder.toMilliseconds()); + return true; + } + /** as {@link #waitOnForExpiry(Object)} but catches and wraps InterruptedException as unchecked RuntimeInterruptedExcedption */ + public boolean waitOnForExpiryUnchecked(Object waitTarget) { + try { + return waitOnForExpiry(waitTarget); + } catch (InterruptedException e) { + throw Exceptions.propagate(e); + } + } + +}
