You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/18 17:03:18 UTC
[09/64] [abbrv] incubator-brooklyn git commit: BROOKLYN-162 - apply
org.apache package prefix to utils-common
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);
+ }
+ }
+
+}