From 663ca149cbf17439772b54ee219ef501b57c2a90 Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Fri, 11 Mar 2016 10:50:56 -0800 Subject: [PATCH] API to disable span support --- src/main/java/com/squareup/phrase/Phrase.java | 49 +++++-- .../com/squareup/phrase/SimpleEditable.java | 134 ++++++++++++++++++ .../java/com/squareup/phrase/PhraseTest.java | 13 +- 3 files changed, 181 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/squareup/phrase/SimpleEditable.java diff --git a/src/main/java/com/squareup/phrase/Phrase.java b/src/main/java/com/squareup/phrase/Phrase.java index a3eb622..ed3b140 100644 --- a/src/main/java/com/squareup/phrase/Phrase.java +++ b/src/main/java/com/squareup/phrase/Phrase.java @@ -20,6 +20,7 @@ import android.content.res.Resources; import android.support.annotation.PluralsRes; import android.support.annotation.StringRes; +import android.text.Editable; import android.text.SpannableStringBuilder; import android.view.View; import android.widget.TextView; @@ -54,8 +55,8 @@ public final class Phrase { private final CharSequence pattern; /** All keys parsed from the original pattern, sans braces. */ - private final Set keys = new HashSet(); - private final Map keysToValues = new HashMap(); + private final Set keys = new HashSet<>(); + private final Map keysToValues = new HashMap<>(); /** Cached result after replacing all keys with corresponding values. */ private CharSequence formatted; @@ -67,6 +68,8 @@ public final class Phrase { private char curChar; private int curCharIndex; + private boolean spanSupportedEnabled = true; + /** Indicates parsing is complete. */ private static final int EOF = 0; @@ -75,6 +78,7 @@ public final class Phrase { * * @throws IllegalArgumentException if pattern contains any syntax errors. */ + @SuppressWarnings("UnusedDeclaration") // Public API. public static Phrase from(Fragment f, @StringRes int patternResourceId) { return from(f.getResources(), patternResourceId); } @@ -84,6 +88,7 @@ public static Phrase from(Fragment f, @StringRes int patternResourceId) { * * @throws IllegalArgumentException if pattern contains any syntax errors. */ + @SuppressWarnings("UnusedDeclaration") // Public API. public static Phrase from(View v, @StringRes int patternResourceId) { return from(v.getResources(), patternResourceId); } @@ -93,6 +98,7 @@ public static Phrase from(View v, @StringRes int patternResourceId) { * * @throws IllegalArgumentException if pattern contains any syntax errors. */ + @SuppressWarnings("UnusedDeclaration") // Public API. public static Phrase from(Context c, @StringRes int patternResourceId) { return from(c.getResources(), patternResourceId); } @@ -102,6 +108,7 @@ public static Phrase from(Context c, @StringRes int patternResourceId) { * * @throws IllegalArgumentException if pattern contains any syntax errors. */ + @SuppressWarnings("UnusedDeclaration") // Public API. public static Phrase from(Resources r, @StringRes int patternResourceId) { return from(r.getText(patternResourceId)); } @@ -111,6 +118,7 @@ public static Phrase from(Resources r, @StringRes int patternResourceId) { * * @throws IllegalArgumentException if pattern contains any syntax errors. */ + @SuppressWarnings("UnusedDeclaration") // Public API. public static Phrase fromPlural(View v, @PluralsRes int patternResourceId, int quantity) { return fromPlural(v.getResources(), patternResourceId, quantity); } @@ -120,6 +128,7 @@ public static Phrase fromPlural(View v, @PluralsRes int patternResourceId, int q * * @throws IllegalArgumentException if pattern contains any syntax errors. */ + @SuppressWarnings("UnusedDeclaration") // Public API. public static Phrase fromPlural(Context c, @PluralsRes int patternResourceId, int quantity) { return fromPlural(c.getResources(), patternResourceId, quantity); } @@ -129,6 +138,7 @@ public static Phrase fromPlural(Context c, @PluralsRes int patternResourceId, in * * @throws IllegalArgumentException if pattern contains any syntax errors. */ + @SuppressWarnings("UnusedDeclaration") // Public API. public static Phrase fromPlural(Resources r, @PluralsRes int patternResourceId, int quantity) { return from(r.getQuantityText(patternResourceId, quantity)); } @@ -138,6 +148,7 @@ public static Phrase fromPlural(Resources r, @PluralsRes int patternResourceId, * * @throws IllegalArgumentException if pattern contains any syntax errors. */ + @SuppressWarnings("UnusedDeclaration") // Public API. public static Phrase from(CharSequence pattern) { return new Phrase(pattern); } @@ -148,6 +159,7 @@ public static Phrase from(CharSequence pattern) { * * @throws IllegalArgumentException if the key is not in the pattern. */ + @SuppressWarnings("UnusedDeclaration") // Public API. public Phrase put(String key, CharSequence value) { if (!keys.contains(key)) { throw new IllegalArgumentException("Invalid key: " + key); @@ -167,6 +179,7 @@ public Phrase put(String key, CharSequence value) { * * @see #put(String, CharSequence) */ + @SuppressWarnings("UnusedDeclaration") // Public API. public Phrase put(String key, int value) { return put(key, Integer.toString(value)); } @@ -176,6 +189,7 @@ public Phrase put(String key, int value) { * * @see #put(String, CharSequence) */ + @SuppressWarnings("UnusedDeclaration") // Public API. public Phrase putOptional(String key, CharSequence value) { return keys.contains(key) ? put(key, value) : this; } @@ -186,6 +200,7 @@ public Phrase putOptional(String key, CharSequence value) { * * @see #putOptional(String, CharSequence) */ + @SuppressWarnings("UnusedDeclaration") // Public API. public Phrase putOptional(String key, int value) { return keys.contains(key) ? put(key, value) : this; } @@ -195,16 +210,17 @@ public Phrase putOptional(String key, int value) { * * @throws IllegalArgumentException if any keys are not replaced. */ + @SuppressWarnings("UnusedDeclaration") // Public API. public CharSequence format() { if (formatted == null) { if (!keysToValues.keySet().containsAll(keys)) { - Set missingKeys = new HashSet(keys); + Set missingKeys = new HashSet<>(keys); missingKeys.removeAll(keysToValues.keySet()); throw new IllegalArgumentException("Missing keys: " + missingKeys); } // Copy the original pattern to preserve all spans, such as bold, italic, etc. - SpannableStringBuilder sb = new SpannableStringBuilder(pattern); + Editable sb = createEditable(pattern); for (Token t = head; t != null; t = t.next) { t.expand(sb, keysToValues); } @@ -214,6 +230,16 @@ public CharSequence format() { return formatted; } + /** + * This can be useful for unit tests that don't want to use {@link SpannableStringBuilder} + * which can cause a stub exception or be no-op. + * Default is true. + */ + @SuppressWarnings("UnusedDeclaration") // Public API. + public void setSpanSupportEnabled(boolean spanSupportedEnabled) { + this.spanSupportedEnabled = spanSupportedEnabled; + } + /** "Formats and sets as text in textView." */ public void into(TextView textView) { if (textView == null) { @@ -230,6 +256,13 @@ public void into(TextView textView) { return pattern.toString(); } + private Editable createEditable(CharSequence pattern) { + if (spanSupportedEnabled) { + return new SpannableStringBuilder(pattern); + } + return new SimpleEditable(pattern); + } + private Phrase(CharSequence pattern) { curChar = (pattern.length() > 0) ? pattern.charAt(0) : EOF; @@ -336,7 +369,7 @@ protected Token(Token prev) { } /** Replace text in {@code target} with this token's associated value. */ - abstract void expand(SpannableStringBuilder target, Map data); + abstract void expand(Editable target, Map data); /** Returns the number of characters after expansion. */ abstract int getFormattedLength(); @@ -362,7 +395,7 @@ private static class TextToken extends Token { this.textLength = textLength; } - @Override void expand(SpannableStringBuilder target, Map data) { + @Override void expand(Editable target, Map data) { // Don't alter spans in the target. } @@ -377,7 +410,7 @@ private static class LeftCurlyBracketToken extends Token { super(prev); } - @Override void expand(SpannableStringBuilder target, Map data) { + @Override void expand(Editable target, Map data) { int start = getFormattedStart(); target.replace(start, start + 2, "{"); } @@ -399,7 +432,7 @@ private static class KeyToken extends Token { this.key = key; } - @Override void expand(SpannableStringBuilder target, Map data) { + @Override void expand(Editable target, Map data) { value = data.get(key); int replaceFrom = getFormattedStart(); diff --git a/src/main/java/com/squareup/phrase/SimpleEditable.java b/src/main/java/com/squareup/phrase/SimpleEditable.java new file mode 100644 index 0000000..753fd6c --- /dev/null +++ b/src/main/java/com/squareup/phrase/SimpleEditable.java @@ -0,0 +1,134 @@ +package com.squareup.phrase; + +import android.support.annotation.NonNull; +import android.text.Editable; +import android.text.InputFilter; + +import static java.util.regex.Matcher.quoteReplacement; +import static java.util.regex.Pattern.quote; + +/** + * Provides basic support for replacing tokens with values without spans. This + * is used when {@link Phrase#setSpanSupportEnabled(boolean)} is turned off. + */ +final class SimpleEditable implements Editable { + private String text; + + public SimpleEditable(CharSequence text) { + this(text.toString()); + } + + public SimpleEditable(String text) { + this.text = text; + } + + @Override public Editable replace(int st, int en, CharSequence source, int start, int end) { + text = text.replaceFirst(quote(text.substring(st, en)), quoteReplacement(source.toString())); + return this; + } + + @Override public Editable replace(int st, int en, CharSequence text) { + return replace(st, en, text, 0, text.length()); + } + + @Override public Editable insert(int where, CharSequence text, int start, int end) { + throw new UnsupportedOperationException("Not implemented."); + } + + @Override public Editable insert(int where, CharSequence text) { + throw new UnsupportedOperationException("Not implemented."); + } + + @Override public Editable delete(int st, int en) { + throw new UnsupportedOperationException("Not implemented."); + } + + @Override public Editable append(CharSequence text) { + throw new UnsupportedOperationException("Not implemented."); + } + + @Override public Editable append(CharSequence text, int start, int end) { + throw new UnsupportedOperationException("Not implemented."); + } + + @Override public Editable append(char text) { + throw new UnsupportedOperationException("Not implemented."); + } + + @Override public void clear() { + } + + @Override public void clearSpans() { + } + + @Override public void setFilters(InputFilter[] filters) { + } + + @Override public InputFilter[] getFilters() { + return new InputFilter[0]; + } + + @Override public void getChars(int start, int end, char[] dest, int destoff) { + } + + @Override public void setSpan(Object what, int start, int end, int flags) { + } + + @Override public void removeSpan(Object what) { + } + + @Override public T[] getSpans(int start, int end, Class type) { + return null; + } + + @Override public int getSpanStart(Object tag) { + return 0; + } + + @Override public int getSpanEnd(Object tag) { + return 0; + } + + @Override public int getSpanFlags(Object tag) { + return 0; + } + + @Override public int nextSpanTransition(int start, int limit, Class type) { + return 0; + } + + @Override public int length() { + return text.length(); + } + + @Override public char charAt(int index) { + return text.charAt(index); + } + + @Override public CharSequence subSequence(int start, int end) { + return text.subSequence(start, end); + } + + @NonNull @Override public String toString() { + return text; + } + + @Override public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + + if (o.getClass() == String.class) { + return text.equals(o); + } + + if (getClass() != o.getClass()) return false; + + SimpleEditable that = (SimpleEditable) o; + + return text.equals(that.text); + } + + @Override public int hashCode() { + return text.hashCode(); + } +} diff --git a/src/test/java/com/squareup/phrase/PhraseTest.java b/src/test/java/com/squareup/phrase/PhraseTest.java index cdfe07d..9b809f5 100644 --- a/src/test/java/com/squareup/phrase/PhraseTest.java +++ b/src/test/java/com/squareup/phrase/PhraseTest.java @@ -19,7 +19,6 @@ import android.text.Spannable; import android.text.SpannableStringBuilder; import android.widget.TextView; - import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -63,7 +62,7 @@ public class PhraseTest { try { from("{"); fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException ignored) { } } @@ -79,7 +78,7 @@ public class PhraseTest { try { from("hi { {age}."); fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException ignored) { } } @@ -133,7 +132,7 @@ public void formatFailsFastWhenKeysAreMissing() { try { gender.format(); fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException ignored) { } } @@ -141,7 +140,7 @@ public void formatFailsFastWhenKeysAreMissing() { try { gender.put("bogusKey", "whatever"); fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException ignored) { } } @@ -149,7 +148,7 @@ public void formatFailsFastWhenKeysAreMissing() { try { from("illegal {} pattern"); fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException ignored) { } } @@ -171,7 +170,7 @@ public void formatFailsFastWhenKeysAreMissing() { try { from("{_foo}"); fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException ignored) { } }