001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.lang3.text;
018
019 import java.util.ArrayList;
020 import java.util.List;
021 import java.util.Map;
022
023 /**
024 * Substitutes variables within a string by values.
025 * <p>
026 * This class takes a piece of text and substitutes all the variables within it.
027 * The default definition of a variable is <code>${variableName}</code>.
028 * The prefix and suffix can be changed via constructors and set methods.
029 * <p>
030 * Variable values are typically resolved from a map, but could also be resolved
031 * from system properties, or by supplying a custom variable resolver.
032 * <p>
033 * The simplest example is to use this class to replace Java System properties. For example:
034 * <pre>
035 * StrSubstitutor.replaceSystemProperties(
036 * "You are running with java.version = ${java.version} and os.name = ${os.name}.");
037 * </pre>
038 * <p>
039 * Typical usage of this class follows the following pattern: First an instance is created
040 * and initialized with the map that contains the values for the available variables.
041 * If a prefix and/or suffix for variables should be used other than the default ones,
042 * the appropriate settings can be performed. After that the <code>replace()</code>
043 * method can be called passing in the source text for interpolation. In the returned
044 * text all variable references (as long as their values are known) will be resolved.
045 * The following example demonstrates this:
046 * <pre>
047 * Map valuesMap = HashMap();
048 * valuesMap.put("animal", "quick brown fox");
049 * valuesMap.put("target", "lazy dog");
050 * String templateString = "The ${animal} jumped over the ${target}.";
051 * StrSubstitutor sub = new StrSubstitutor(valuesMap);
052 * String resolvedString = sub.replace(templateString);
053 * </pre>
054 * yielding:
055 * <pre>
056 * The quick brown fox jumped over the lazy dog.
057 * </pre>
058 * <p>
059 * In addition to this usage pattern there are some static convenience methods that
060 * cover the most common use cases. These methods can be used without the need of
061 * manually creating an instance. However if multiple replace operations are to be
062 * performed, creating and reusing an instance of this class will be more efficient.
063 * <p>
064 * Variable replacement works in a recursive way. Thus, if a variable value contains
065 * a variable then that variable will also be replaced. Cyclic replacements are
066 * detected and will cause an exception to be thrown.
067 * <p>
068 * Sometimes the interpolation's result must contain a variable prefix. As an example
069 * take the following source text:
070 * <pre>
071 * The variable ${${name}} must be used.
072 * </pre>
073 * Here only the variable's name refered to in the text should be replaced resulting
074 * in the text (assuming that the value of the <code>name</code> variable is <code>x</code>):
075 * <pre>
076 * The variable ${x} must be used.
077 * </pre>
078 * To achieve this effect there are two possibilities: Either set a different prefix
079 * and suffix for variables which do not conflict with the result text you want to
080 * produce. The other possibility is to use the escape character, by default '$'.
081 * If this character is placed before a variable reference, this reference is ignored
082 * and won't be replaced. For example:
083 * <pre>
084 * The variable $${${name}} must be used.
085 * </pre>
086 *
087 * @author Apache Software Foundation
088 * @author Oliver Heger
089 * @version $Id: StrSubstitutor.java 902944 2010-01-25 19:32:08Z sebb $
090 * @since 2.2
091 */
092 public class StrSubstitutor {
093
094 /**
095 * Constant for the default escape character.
096 */
097 public static final char DEFAULT_ESCAPE = '$';
098 /**
099 * Constant for the default variable prefix.
100 */
101 public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${");
102 /**
103 * Constant for the default variable suffix.
104 */
105 public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}");
106
107 /**
108 * Stores the escape character.
109 */
110 private char escapeChar;
111 /**
112 * Stores the variable prefix.
113 */
114 private StrMatcher prefixMatcher;
115 /**
116 * Stores the variable suffix.
117 */
118 private StrMatcher suffixMatcher;
119 /**
120 * Variable resolution is delegated to an implementor of VariableResolver.
121 */
122 private StrLookup<?> variableResolver;
123
124 //-----------------------------------------------------------------------
125 /**
126 * Replaces all the occurrences of variables in the given source object with
127 * their matching values from the map.
128 *
129 * @param source the source text containing the variables to substitute, null returns null
130 * @param valueMap the map with the values, may be null
131 * @return the result of the replace operation
132 */
133 public static <V> String replace(Object source, Map<String, V> valueMap) {
134 return new StrSubstitutor(valueMap).replace(source);
135 }
136
137 /**
138 * Replaces all the occurrences of variables in the given source object with
139 * their matching values from the map. This method allows to specifiy a
140 * custom variable prefix and suffix
141 *
142 * @param source the source text containing the variables to substitute, null returns null
143 * @param valueMap the map with the values, may be null
144 * @param prefix the prefix of variables, not null
145 * @param suffix the suffix of variables, not null
146 * @return the result of the replace operation
147 * @throws IllegalArgumentException if the prefix or suffix is null
148 */
149 public static <V> String replace(Object source, Map<String, V> valueMap, String prefix, String suffix) {
150 return new StrSubstitutor(valueMap, prefix, suffix).replace(source);
151 }
152
153 /**
154 * Replaces all the occurrences of variables in the given source object with
155 * their matching values from the system properties.
156 *
157 * @param source the source text containing the variables to substitute, null returns null
158 * @return the result of the replace operation
159 */
160 public static String replaceSystemProperties(Object source) {
161 return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source);
162 }
163
164 //-----------------------------------------------------------------------
165 /**
166 * Creates a new instance with defaults for variable prefix and suffix
167 * and the escaping character.
168 */
169 public StrSubstitutor() {
170 this((StrLookup<?>) null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
171 }
172
173 /**
174 * Creates a new instance and initializes it. Uses defaults for variable
175 * prefix and suffix and the escaping character.
176 *
177 * @param valueMap the map with the variables' values, may be null
178 */
179 public <V> StrSubstitutor(Map<String, V> valueMap) {
180 this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
181 }
182
183 /**
184 * Creates a new instance and initializes it. Uses a default escaping character.
185 *
186 * @param valueMap the map with the variables' values, may be null
187 * @param prefix the prefix for variables, not null
188 * @param suffix the suffix for variables, not null
189 * @throws IllegalArgumentException if the prefix or suffix is null
190 */
191 public <V> StrSubstitutor(Map<String, V> valueMap, String prefix, String suffix) {
192 this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
193 }
194
195 /**
196 * Creates a new instance and initializes it.
197 *
198 * @param valueMap the map with the variables' values, may be null
199 * @param prefix the prefix for variables, not null
200 * @param suffix the suffix for variables, not null
201 * @param escape the escape character
202 * @throws IllegalArgumentException if the prefix or suffix is null
203 */
204 public <V> StrSubstitutor(Map<String, V> valueMap, String prefix, String suffix, char escape) {
205 this(StrLookup.mapLookup(valueMap), prefix, suffix, escape);
206 }
207
208 /**
209 * Creates a new instance and initializes it.
210 *
211 * @param variableResolver the variable resolver, may be null
212 */
213 public StrSubstitutor(StrLookup<?> variableResolver) {
214 this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
215 }
216
217 /**
218 * Creates a new instance and initializes it.
219 *
220 * @param variableResolver the variable resolver, may be null
221 * @param prefix the prefix for variables, not null
222 * @param suffix the suffix for variables, not null
223 * @param escape the escape character
224 * @throws IllegalArgumentException if the prefix or suffix is null
225 */
226 public StrSubstitutor(StrLookup<?> variableResolver, String prefix, String suffix, char escape) {
227 this.setVariableResolver(variableResolver);
228 this.setVariablePrefix(prefix);
229 this.setVariableSuffix(suffix);
230 this.setEscapeChar(escape);
231 }
232
233 /**
234 * Creates a new instance and initializes it.
235 *
236 * @param variableResolver the variable resolver, may be null
237 * @param prefixMatcher the prefix for variables, not null
238 * @param suffixMatcher the suffix for variables, not null
239 * @param escape the escape character
240 * @throws IllegalArgumentException if the prefix or suffix is null
241 */
242 public StrSubstitutor(
243 StrLookup<?> variableResolver, StrMatcher prefixMatcher, StrMatcher suffixMatcher, char escape) {
244 this.setVariableResolver(variableResolver);
245 this.setVariablePrefixMatcher(prefixMatcher);
246 this.setVariableSuffixMatcher(suffixMatcher);
247 this.setEscapeChar(escape);
248 }
249
250 //-----------------------------------------------------------------------
251 /**
252 * Replaces all the occurrences of variables with their matching values
253 * from the resolver using the given source string as a template.
254 *
255 * @param source the string to replace in, null returns null
256 * @return the result of the replace operation
257 */
258 public String replace(String source) {
259 if (source == null) {
260 return null;
261 }
262 StrBuilder buf = new StrBuilder(source);
263 if (substitute(buf, 0, source.length()) == false) {
264 return source;
265 }
266 return buf.toString();
267 }
268
269 /**
270 * Replaces all the occurrences of variables with their matching values
271 * from the resolver using the given source string as a template.
272 * <p>
273 * Only the specified portion of the string will be processed.
274 * The rest of the string is not processed, and is not returned.
275 *
276 * @param source the string to replace in, null returns null
277 * @param offset the start offset within the array, must be valid
278 * @param length the length within the array to be processed, must be valid
279 * @return the result of the replace operation
280 */
281 public String replace(String source, int offset, int length) {
282 if (source == null) {
283 return null;
284 }
285 StrBuilder buf = new StrBuilder(length).append(source, offset, length);
286 if (substitute(buf, 0, length) == false) {
287 return source.substring(offset, offset + length);
288 }
289 return buf.toString();
290 }
291
292 //-----------------------------------------------------------------------
293 /**
294 * Replaces all the occurrences of variables with their matching values
295 * from the resolver using the given source array as a template.
296 * The array is not altered by this method.
297 *
298 * @param source the character array to replace in, not altered, null returns null
299 * @return the result of the replace operation
300 */
301 public String replace(char[] source) {
302 if (source == null) {
303 return null;
304 }
305 StrBuilder buf = new StrBuilder(source.length).append(source);
306 substitute(buf, 0, source.length);
307 return buf.toString();
308 }
309
310 /**
311 * Replaces all the occurrences of variables with their matching values
312 * from the resolver using the given source array as a template.
313 * The array is not altered by this method.
314 * <p>
315 * Only the specified portion of the array will be processed.
316 * The rest of the array is not processed, and is not returned.
317 *
318 * @param source the character array to replace in, not altered, null returns null
319 * @param offset the start offset within the array, must be valid
320 * @param length the length within the array to be processed, must be valid
321 * @return the result of the replace operation
322 */
323 public String replace(char[] source, int offset, int length) {
324 if (source == null) {
325 return null;
326 }
327 StrBuilder buf = new StrBuilder(length).append(source, offset, length);
328 substitute(buf, 0, length);
329 return buf.toString();
330 }
331
332 //-----------------------------------------------------------------------
333 /**
334 * Replaces all the occurrences of variables with their matching values
335 * from the resolver using the given source buffer as a template.
336 * The buffer is not altered by this method.
337 *
338 * @param source the buffer to use as a template, not changed, null returns null
339 * @return the result of the replace operation
340 */
341 public String replace(StringBuffer source) {
342 if (source == null) {
343 return null;
344 }
345 StrBuilder buf = new StrBuilder(source.length()).append(source);
346 substitute(buf, 0, buf.length());
347 return buf.toString();
348 }
349
350 /**
351 * Replaces all the occurrences of variables with their matching values
352 * from the resolver using the given source buffer as a template.
353 * The buffer is not altered by this method.
354 * <p>
355 * Only the specified portion of the buffer will be processed.
356 * The rest of the buffer is not processed, and is not returned.
357 *
358 * @param source the buffer to use as a template, not changed, null returns null
359 * @param offset the start offset within the array, must be valid
360 * @param length the length within the array to be processed, must be valid
361 * @return the result of the replace operation
362 */
363 public String replace(StringBuffer source, int offset, int length) {
364 if (source == null) {
365 return null;
366 }
367 StrBuilder buf = new StrBuilder(length).append(source, offset, length);
368 substitute(buf, 0, length);
369 return buf.toString();
370 }
371
372 //-----------------------------------------------------------------------
373 /**
374 * Replaces all the occurrences of variables with their matching values
375 * from the resolver using the given source builder as a template.
376 * The builder is not altered by this method.
377 *
378 * @param source the builder to use as a template, not changed, null returns null
379 * @return the result of the replace operation
380 */
381 public String replace(StrBuilder source) {
382 if (source == null) {
383 return null;
384 }
385 StrBuilder buf = new StrBuilder(source.length()).append(source);
386 substitute(buf, 0, buf.length());
387 return buf.toString();
388 }
389
390 /**
391 * Replaces all the occurrences of variables with their matching values
392 * from the resolver using the given source builder as a template.
393 * The builder is not altered by this method.
394 * <p>
395 * Only the specified portion of the builder will be processed.
396 * The rest of the builder is not processed, and is not returned.
397 *
398 * @param source the builder to use as a template, not changed, null returns null
399 * @param offset the start offset within the array, must be valid
400 * @param length the length within the array to be processed, must be valid
401 * @return the result of the replace operation
402 */
403 public String replace(StrBuilder source, int offset, int length) {
404 if (source == null) {
405 return null;
406 }
407 StrBuilder buf = new StrBuilder(length).append(source, offset, length);
408 substitute(buf, 0, length);
409 return buf.toString();
410 }
411
412 //-----------------------------------------------------------------------
413 /**
414 * Replaces all the occurrences of variables in the given source object with
415 * their matching values from the resolver. The input source object is
416 * converted to a string using <code>toString</code> and is not altered.
417 *
418 * @param source the source to replace in, null returns null
419 * @return the result of the replace operation
420 */
421 public String replace(Object source) {
422 if (source == null) {
423 return null;
424 }
425 StrBuilder buf = new StrBuilder().append(source);
426 substitute(buf, 0, buf.length());
427 return buf.toString();
428 }
429
430 //-----------------------------------------------------------------------
431 /**
432 * Replaces all the occurrences of variables within the given source buffer
433 * with their matching values from the resolver.
434 * The buffer is updated with the result.
435 *
436 * @param source the buffer to replace in, updated, null returns zero
437 * @return true if altered
438 */
439 public boolean replaceIn(StringBuffer source) {
440 if (source == null) {
441 return false;
442 }
443 return replaceIn(source, 0, source.length());
444 }
445
446 /**
447 * Replaces all the occurrences of variables within the given source buffer
448 * with their matching values from the resolver.
449 * The buffer is updated with the result.
450 * <p>
451 * Only the specified portion of the buffer will be processed.
452 * The rest of the buffer is not processed, but it is not deleted.
453 *
454 * @param source the buffer to replace in, updated, null returns zero
455 * @param offset the start offset within the array, must be valid
456 * @param length the length within the buffer to be processed, must be valid
457 * @return true if altered
458 */
459 public boolean replaceIn(StringBuffer source, int offset, int length) {
460 if (source == null) {
461 return false;
462 }
463 StrBuilder buf = new StrBuilder(length).append(source, offset, length);
464 if (substitute(buf, 0, length) == false) {
465 return false;
466 }
467 source.replace(offset, offset + length, buf.toString());
468 return true;
469 }
470
471 //-----------------------------------------------------------------------
472 /**
473 * Replaces all the occurrences of variables within the given source
474 * builder with their matching values from the resolver.
475 *
476 * @param source the builder to replace in, updated, null returns zero
477 * @return true if altered
478 */
479 public boolean replaceIn(StrBuilder source) {
480 if (source == null) {
481 return false;
482 }
483 return substitute(source, 0, source.length());
484 }
485
486 /**
487 * Replaces all the occurrences of variables within the given source
488 * builder with their matching values from the resolver.
489 * <p>
490 * Only the specified portion of the builder will be processed.
491 * The rest of the builder is not processed, but it is not deleted.
492 *
493 * @param source the builder to replace in, null returns zero
494 * @param offset the start offset within the array, must be valid
495 * @param length the length within the builder to be processed, must be valid
496 * @return true if altered
497 */
498 public boolean replaceIn(StrBuilder source, int offset, int length) {
499 if (source == null) {
500 return false;
501 }
502 return substitute(source, offset, length);
503 }
504
505 //-----------------------------------------------------------------------
506 /**
507 * Internal method that substitutes the variables.
508 * <p>
509 * Most users of this class do not need to call this method. This method will
510 * be called automatically by another (public) method.
511 * <p>
512 * Writers of subclasses can override this method if they need access to
513 * the substitution process at the start or end.
514 *
515 * @param buf the string builder to substitute into, not null
516 * @param offset the start offset within the builder, must be valid
517 * @param length the length within the builder to be processed, must be valid
518 * @return true if altered
519 */
520 protected boolean substitute(StrBuilder buf, int offset, int length) {
521 return substitute(buf, offset, length, null) > 0;
522 }
523
524 /**
525 * Recursive handler for multiple levels of interpolation. This is the main
526 * interpolation method, which resolves the values of all variable references
527 * contained in the passed in text.
528 *
529 * @param buf the string builder to substitute into, not null
530 * @param offset the start offset within the builder, must be valid
531 * @param length the length within the builder to be processed, must be valid
532 * @param priorVariables the stack keeping track of the replaced variables, may be null
533 * @return the length change that occurs, unless priorVariables is null when the int
534 * represents a boolean flag as to whether any change occurred.
535 */
536 private int substitute(StrBuilder buf, int offset, int length, List<String> priorVariables) {
537 StrMatcher prefixMatcher = getVariablePrefixMatcher();
538 StrMatcher suffixMatcher = getVariableSuffixMatcher();
539 char escape = getEscapeChar();
540
541 boolean top = (priorVariables == null);
542 boolean altered = false;
543 int lengthChange = 0;
544 char[] chars = buf.buffer;
545 int bufEnd = offset + length;
546 int pos = offset;
547 while (pos < bufEnd) {
548 int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd);
549 if (startMatchLen == 0) {
550 pos++;
551 } else {
552 // found variable start marker
553 if (pos > offset && chars[pos - 1] == escape) {
554 // escaped
555 buf.deleteCharAt(pos - 1);
556 chars = buf.buffer; // in case buffer was altered
557 lengthChange--;
558 altered = true;
559 bufEnd--;
560 } else {
561 // find suffix
562 int startPos = pos;
563 pos += startMatchLen;
564 int endMatchLen = 0;
565 while (pos < bufEnd) {
566 endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd);
567 if (endMatchLen == 0) {
568 pos++;
569 } else {
570 // found variable end marker
571 String varName = new String(chars, startPos + startMatchLen,
572 pos - startPos - startMatchLen);
573 pos += endMatchLen;
574 int endPos = pos;
575
576 // on the first call initialize priorVariables
577 if (priorVariables == null) {
578 priorVariables = new ArrayList<String>();
579 priorVariables.add(new String(chars, offset, length));
580 }
581
582 // handle cyclic substitution
583 checkCyclicSubstitution(varName, priorVariables);
584 priorVariables.add(varName);
585
586 // resolve the variable
587 String varValue = resolveVariable(varName, buf, startPos, endPos);
588 if (varValue != null) {
589 // recursive replace
590 int varLen = varValue.length();
591 buf.replace(startPos, endPos, varValue);
592 altered = true;
593 int change = substitute(buf, startPos, varLen, priorVariables);
594 change = change + (varLen - (endPos - startPos));
595 pos += change;
596 bufEnd += change;
597 lengthChange += change;
598 chars = buf.buffer; // in case buffer was altered
599 }
600
601 // remove variable from the cyclic stack
602 priorVariables.remove(priorVariables.size() - 1);
603 break;
604 }
605 }
606 }
607 }
608 }
609 if (top) {
610 return (altered ? 1 : 0);
611 }
612 return lengthChange;
613 }
614
615 /**
616 * Checks if the specified variable is already in the stack (list) of variables.
617 *
618 * @param varName the variable name to check
619 * @param priorVariables the list of prior variables
620 */
621 private void checkCyclicSubstitution(String varName, List<String> priorVariables) {
622 if (priorVariables.contains(varName) == false) {
623 return;
624 }
625 StrBuilder buf = new StrBuilder(256);
626 buf.append("Infinite loop in property interpolation of ");
627 buf.append(priorVariables.remove(0));
628 buf.append(": ");
629 buf.appendWithSeparators(priorVariables, "->");
630 throw new IllegalStateException(buf.toString());
631 }
632
633 /**
634 * Internal method that resolves the value of a variable.
635 * <p>
636 * Most users of this class do not need to call this method. This method is
637 * called automatically by the substitution process.
638 * <p>
639 * Writers of subclasses can override this method if they need to alter
640 * how each substitution occurs. The method is passed the variable's name
641 * and must return the corresponding value. This implementation uses the
642 * {@link #getVariableResolver()} with the variable's name as the key.
643 *
644 * @param variableName the name of the variable, not null
645 * @param buf the buffer where the substitution is occurring, not null
646 * @param startPos the start position of the variable including the prefix, valid
647 * @param endPos the end position of the variable including the suffix, valid
648 * @return the variable's value or <b>null</b> if the variable is unknown
649 */
650 protected String resolveVariable(String variableName, StrBuilder buf, int startPos, int endPos) {
651 StrLookup<?> resolver = getVariableResolver();
652 if (resolver == null) {
653 return null;
654 }
655 return resolver.lookup(variableName);
656 }
657
658 // Escape
659 //-----------------------------------------------------------------------
660 /**
661 * Returns the escape character.
662 *
663 * @return the character used for escaping variable references
664 */
665 public char getEscapeChar() {
666 return this.escapeChar;
667 }
668
669 /**
670 * Sets the escape character.
671 * If this character is placed before a variable reference in the source
672 * text, this variable will be ignored.
673 *
674 * @param escapeCharacter the escape character (0 for disabling escaping)
675 */
676 public void setEscapeChar(char escapeCharacter) {
677 this.escapeChar = escapeCharacter;
678 }
679
680 // Prefix
681 //-----------------------------------------------------------------------
682 /**
683 * Gets the variable prefix matcher currently in use.
684 * <p>
685 * The variable prefix is the characer or characters that identify the
686 * start of a variable. This prefix is expressed in terms of a matcher
687 * allowing advanced prefix matches.
688 *
689 * @return the prefix matcher in use
690 */
691 public StrMatcher getVariablePrefixMatcher() {
692 return prefixMatcher;
693 }
694
695 /**
696 * Sets the variable prefix matcher currently in use.
697 * <p>
698 * The variable prefix is the characer or characters that identify the
699 * start of a variable. This prefix is expressed in terms of a matcher
700 * allowing advanced prefix matches.
701 *
702 * @param prefixMatcher the prefix matcher to use, null ignored
703 * @return this, to enable chaining
704 * @throws IllegalArgumentException if the prefix matcher is null
705 */
706 public StrSubstitutor setVariablePrefixMatcher(StrMatcher prefixMatcher) {
707 if (prefixMatcher == null) {
708 throw new IllegalArgumentException("Variable prefix matcher must not be null!");
709 }
710 this.prefixMatcher = prefixMatcher;
711 return this;
712 }
713
714 /**
715 * Sets the variable prefix to use.
716 * <p>
717 * The variable prefix is the characer or characters that identify the
718 * start of a variable. This method allows a single character prefix to
719 * be easily set.
720 *
721 * @param prefix the prefix character to use
722 * @return this, to enable chaining
723 */
724 public StrSubstitutor setVariablePrefix(char prefix) {
725 return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
726 }
727
728 /**
729 * Sets the variable prefix to use.
730 * <p>
731 * The variable prefix is the characer or characters that identify the
732 * start of a variable. This method allows a string prefix to be easily set.
733 *
734 * @param prefix the prefix for variables, not null
735 * @return this, to enable chaining
736 * @throws IllegalArgumentException if the prefix is null
737 */
738 public StrSubstitutor setVariablePrefix(String prefix) {
739 if (prefix == null) {
740 throw new IllegalArgumentException("Variable prefix must not be null!");
741 }
742 return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix));
743 }
744
745 // Suffix
746 //-----------------------------------------------------------------------
747 /**
748 * Gets the variable suffix matcher currently in use.
749 * <p>
750 * The variable suffix is the characer or characters that identify the
751 * end of a variable. This suffix is expressed in terms of a matcher
752 * allowing advanced suffix matches.
753 *
754 * @return the suffix matcher in use
755 */
756 public StrMatcher getVariableSuffixMatcher() {
757 return suffixMatcher;
758 }
759
760 /**
761 * Sets the variable suffix matcher currently in use.
762 * <p>
763 * The variable suffix is the characer or characters that identify the
764 * end of a variable. This suffix is expressed in terms of a matcher
765 * allowing advanced suffix matches.
766 *
767 * @param suffixMatcher the suffix matcher to use, null ignored
768 * @return this, to enable chaining
769 * @throws IllegalArgumentException if the suffix matcher is null
770 */
771 public StrSubstitutor setVariableSuffixMatcher(StrMatcher suffixMatcher) {
772 if (suffixMatcher == null) {
773 throw new IllegalArgumentException("Variable suffix matcher must not be null!");
774 }
775 this.suffixMatcher = suffixMatcher;
776 return this;
777 }
778
779 /**
780 * Sets the variable suffix to use.
781 * <p>
782 * The variable suffix is the characer or characters that identify the
783 * end of a variable. This method allows a single character suffix to
784 * be easily set.
785 *
786 * @param suffix the suffix character to use
787 * @return this, to enable chaining
788 */
789 public StrSubstitutor setVariableSuffix(char suffix) {
790 return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
791 }
792
793 /**
794 * Sets the variable suffix to use.
795 * <p>
796 * The variable suffix is the characer or characters that identify the
797 * end of a variable. This method allows a string suffix to be easily set.
798 *
799 * @param suffix the suffix for variables, not null
800 * @return this, to enable chaining
801 * @throws IllegalArgumentException if the suffix is null
802 */
803 public StrSubstitutor setVariableSuffix(String suffix) {
804 if (suffix == null) {
805 throw new IllegalArgumentException("Variable suffix must not be null!");
806 }
807 return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix));
808 }
809
810 // Resolver
811 //-----------------------------------------------------------------------
812 /**
813 * Gets the VariableResolver that is used to lookup variables.
814 *
815 * @return the VariableResolver
816 */
817 public StrLookup<?> getVariableResolver() {
818 return this.variableResolver;
819 }
820
821 /**
822 * Sets the VariableResolver that is used to lookup variables.
823 *
824 * @param variableResolver the VariableResolver
825 */
826 public void setVariableResolver(StrLookup<?> variableResolver) {
827 this.variableResolver = variableResolver;
828 }
829
830 }