2020import android .content .res .Resources ;
2121import android .support .annotation .PluralsRes ;
2222import android .support .annotation .StringRes ;
23+ import android .text .Editable ;
2324import android .text .SpannableStringBuilder ;
2425import android .view .View ;
2526import android .widget .TextView ;
@@ -54,8 +55,8 @@ public final class Phrase {
5455 private final CharSequence pattern ;
5556
5657 /** All keys parsed from the original pattern, sans braces. */
57- private final Set <String > keys = new HashSet <String >();
58- private final Map <String , CharSequence > keysToValues = new HashMap <String , CharSequence >();
58+ private final Set <String > keys = new HashSet <>();
59+ private final Map <String , CharSequence > keysToValues = new HashMap <>();
5960
6061 /** Cached result after replacing all keys with corresponding values. */
6162 private CharSequence formatted ;
@@ -67,6 +68,8 @@ public final class Phrase {
6768 private char curChar ;
6869 private int curCharIndex ;
6970
71+ private boolean spanSupportedEnabled = true ;
72+
7073 /** Indicates parsing is complete. */
7174 private static final int EOF = 0 ;
7275
@@ -75,6 +78,7 @@ public final class Phrase {
7578 *
7679 * @throws IllegalArgumentException if pattern contains any syntax errors.
7780 */
81+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
7882 public static Phrase from (Fragment f , @ StringRes int patternResourceId ) {
7983 return from (f .getResources (), patternResourceId );
8084 }
@@ -84,6 +88,7 @@ public static Phrase from(Fragment f, @StringRes int patternResourceId) {
8488 *
8589 * @throws IllegalArgumentException if pattern contains any syntax errors.
8690 */
91+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
8792 public static Phrase from (View v , @ StringRes int patternResourceId ) {
8893 return from (v .getResources (), patternResourceId );
8994 }
@@ -93,6 +98,7 @@ public static Phrase from(View v, @StringRes int patternResourceId) {
9398 *
9499 * @throws IllegalArgumentException if pattern contains any syntax errors.
95100 */
101+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
96102 public static Phrase from (Context c , @ StringRes int patternResourceId ) {
97103 return from (c .getResources (), patternResourceId );
98104 }
@@ -102,6 +108,7 @@ public static Phrase from(Context c, @StringRes int patternResourceId) {
102108 *
103109 * @throws IllegalArgumentException if pattern contains any syntax errors.
104110 */
111+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
105112 public static Phrase from (Resources r , @ StringRes int patternResourceId ) {
106113 return from (r .getText (patternResourceId ));
107114 }
@@ -111,6 +118,7 @@ public static Phrase from(Resources r, @StringRes int patternResourceId) {
111118 *
112119 * @throws IllegalArgumentException if pattern contains any syntax errors.
113120 */
121+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
114122 public static Phrase fromPlural (View v , @ PluralsRes int patternResourceId , int quantity ) {
115123 return fromPlural (v .getResources (), patternResourceId , quantity );
116124 }
@@ -120,6 +128,7 @@ public static Phrase fromPlural(View v, @PluralsRes int patternResourceId, int q
120128 *
121129 * @throws IllegalArgumentException if pattern contains any syntax errors.
122130 */
131+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
123132 public static Phrase fromPlural (Context c , @ PluralsRes int patternResourceId , int quantity ) {
124133 return fromPlural (c .getResources (), patternResourceId , quantity );
125134 }
@@ -129,6 +138,7 @@ public static Phrase fromPlural(Context c, @PluralsRes int patternResourceId, in
129138 *
130139 * @throws IllegalArgumentException if pattern contains any syntax errors.
131140 */
141+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
132142 public static Phrase fromPlural (Resources r , @ PluralsRes int patternResourceId , int quantity ) {
133143 return from (r .getQuantityText (patternResourceId , quantity ));
134144 }
@@ -138,6 +148,7 @@ public static Phrase fromPlural(Resources r, @PluralsRes int patternResourceId,
138148 *
139149 * @throws IllegalArgumentException if pattern contains any syntax errors.
140150 */
151+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
141152 public static Phrase from (CharSequence pattern ) {
142153 return new Phrase (pattern );
143154 }
@@ -148,6 +159,7 @@ public static Phrase from(CharSequence pattern) {
148159 *
149160 * @throws IllegalArgumentException if the key is not in the pattern.
150161 */
162+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
151163 public Phrase put (String key , CharSequence value ) {
152164 if (!keys .contains (key )) {
153165 throw new IllegalArgumentException ("Invalid key: " + key );
@@ -167,6 +179,7 @@ public Phrase put(String key, CharSequence value) {
167179 *
168180 * @see #put(String, CharSequence)
169181 */
182+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
170183 public Phrase put (String key , int value ) {
171184 return put (key , Integer .toString (value ));
172185 }
@@ -176,6 +189,7 @@ public Phrase put(String key, int value) {
176189 *
177190 * @see #put(String, CharSequence)
178191 */
192+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
179193 public Phrase putOptional (String key , CharSequence value ) {
180194 return keys .contains (key ) ? put (key , value ) : this ;
181195 }
@@ -186,6 +200,7 @@ public Phrase putOptional(String key, CharSequence value) {
186200 *
187201 * @see #putOptional(String, CharSequence)
188202 */
203+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
189204 public Phrase putOptional (String key , int value ) {
190205 return keys .contains (key ) ? put (key , value ) : this ;
191206 }
@@ -195,16 +210,17 @@ public Phrase putOptional(String key, int value) {
195210 *
196211 * @throws IllegalArgumentException if any keys are not replaced.
197212 */
213+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
198214 public CharSequence format () {
199215 if (formatted == null ) {
200216 if (!keysToValues .keySet ().containsAll (keys )) {
201- Set <String > missingKeys = new HashSet <String >(keys );
217+ Set <String > missingKeys = new HashSet <>(keys );
202218 missingKeys .removeAll (keysToValues .keySet ());
203219 throw new IllegalArgumentException ("Missing keys: " + missingKeys );
204220 }
205221
206222 // Copy the original pattern to preserve all spans, such as bold, italic, etc.
207- SpannableStringBuilder sb = new SpannableStringBuilder (pattern );
223+ Editable sb = createEditable (pattern );
208224 for (Token t = head ; t != null ; t = t .next ) {
209225 t .expand (sb , keysToValues );
210226 }
@@ -214,6 +230,16 @@ public CharSequence format() {
214230 return formatted ;
215231 }
216232
233+ /**
234+ * This can be useful for unit tests that don't want to use {@link SpannableStringBuilder}
235+ * which can cause a stub exception or be no-op.
236+ * Default is <code>true</code>.
237+ */
238+ @ SuppressWarnings ("UnusedDeclaration" ) // Public API.
239+ public void setSpanSupportEnabled (boolean spanSupportedEnabled ) {
240+ this .spanSupportedEnabled = spanSupportedEnabled ;
241+ }
242+
217243 /** "Formats and sets as text in textView." */
218244 public void into (TextView textView ) {
219245 if (textView == null ) {
@@ -230,6 +256,13 @@ public void into(TextView textView) {
230256 return pattern .toString ();
231257 }
232258
259+ private Editable createEditable (CharSequence pattern ) {
260+ if (spanSupportedEnabled ) {
261+ return new SpannableStringBuilder (pattern );
262+ }
263+ return new SimpleEditable (pattern );
264+ }
265+
233266 private Phrase (CharSequence pattern ) {
234267 curChar = (pattern .length () > 0 ) ? pattern .charAt (0 ) : EOF ;
235268
@@ -335,7 +368,7 @@ protected Token(Token prev) {
335368 }
336369
337370 /** Replace text in {@code target} with this token's associated value. */
338- abstract void expand (SpannableStringBuilder target , Map <String , CharSequence > data );
371+ abstract void expand (Editable target , Map <String , CharSequence > data );
339372
340373 /** Returns the number of characters after expansion. */
341374 abstract int getFormattedLength ();
@@ -361,7 +394,7 @@ private static class TextToken extends Token {
361394 this .textLength = textLength ;
362395 }
363396
364- @ Override void expand (SpannableStringBuilder target , Map <String , CharSequence > data ) {
397+ @ Override void expand (Editable target , Map <String , CharSequence > data ) {
365398 // Don't alter spans in the target.
366399 }
367400
@@ -376,7 +409,7 @@ private static class LeftCurlyBracketToken extends Token {
376409 super (prev );
377410 }
378411
379- @ Override void expand (SpannableStringBuilder target , Map <String , CharSequence > data ) {
412+ @ Override void expand (Editable target , Map <String , CharSequence > data ) {
380413 int start = getFormattedStart ();
381414 target .replace (start , start + 2 , "{" );
382415 }
@@ -398,7 +431,7 @@ private static class KeyToken extends Token {
398431 this .key = key ;
399432 }
400433
401- @ Override void expand (SpannableStringBuilder target , Map <String , CharSequence > data ) {
434+ @ Override void expand (Editable target , Map <String , CharSequence > data ) {
402435 value = data .get (key );
403436
404437 int replaceFrom = getFormattedStart ();
0 commit comments