Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions src/main/java/com/squareup/phrase/Phrase.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -54,8 +55,8 @@ public final class Phrase {
private final CharSequence pattern;

/** All keys parsed from the original pattern, sans braces. */
private final Set<String> keys = new HashSet<String>();
private final Map<String, CharSequence> keysToValues = new HashMap<String, CharSequence>();
private final Set<String> keys = new HashSet<>();
private final Map<String, CharSequence> keysToValues = new HashMap<>();

/** Cached result after replacing all keys with corresponding values. */
private CharSequence formatted;
Expand All @@ -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;

Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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));
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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));
}
Expand All @@ -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);
}
Expand All @@ -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);
Expand All @@ -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));
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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<String> missingKeys = new HashSet<String>(keys);
Set<String> 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);
}
Expand All @@ -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 <code>true</code>.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should change that since I switched it, it should say <code>false</code>

*/
@SuppressWarnings("UnusedDeclaration") // Public API.
public void setSpanSupportEnabled(boolean spanSupportedEnabled) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered just making this variable final, and having new factory methods for it? There'd be a lot of method proliferation I guess.

I was thinking of something like Phrase.stubSpan().from(...) or Phrase.stubSpanFrom(...) or something. That way you just have a small amount of code to add across a lot of tests.

I'd just like someone to avoid running into this method in their normal Phrase usage.

this.spanSupportedEnabled = spanSupportedEnabled;
}

/** "Formats and sets as text in textView." */
public void into(TextView textView) {
if (textView == null) {
Expand All @@ -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;

Expand Down Expand Up @@ -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<String, CharSequence> data);
abstract void expand(Editable target, Map<String, CharSequence> data);

/** Returns the number of characters after expansion. */
abstract int getFormattedLength();
Expand All @@ -362,7 +395,7 @@ private static class TextToken extends Token {
this.textLength = textLength;
}

@Override void expand(SpannableStringBuilder target, Map<String, CharSequence> data) {
@Override void expand(Editable target, Map<String, CharSequence> data) {
// Don't alter spans in the target.
}

Expand All @@ -377,7 +410,7 @@ private static class LeftCurlyBracketToken extends Token {
super(prev);
}

@Override void expand(SpannableStringBuilder target, Map<String, CharSequence> data) {
@Override void expand(Editable target, Map<String, CharSequence> data) {
int start = getFormattedStart();
target.replace(start, start + 2, "{");
}
Expand All @@ -399,7 +432,7 @@ private static class KeyToken extends Token {
this.key = key;
}

@Override void expand(SpannableStringBuilder target, Map<String, CharSequence> data) {
@Override void expand(Editable target, Map<String, CharSequence> data) {
value = data.get(key);

int replaceFrom = getFormattedStart();
Expand Down
134 changes: 134 additions & 0 deletions src/main/java/com/squareup/phrase/SimpleEditable.java
Original file line number Diff line number Diff line change
@@ -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> T[] getSpans(int start, int end, Class<T> 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();
}
}
Loading