View Javadoc
1   /*
2    * Copyright 2012 The Kuali Foundation.
3    * 
4    * Licensed under the Educational Community License, Version 1.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl1.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.utility;
17  
18  import java.util.HashMap;
19  import java.util.HashSet;
20  import java.util.Map;
21  import java.util.Properties;
22  import java.util.Set;
23  
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  import org.springframework.util.Assert;
27  import org.springframework.util.StringUtils;
28  
29  /**
30   * Utility class for working with Strings that have placeholder values in them. A placeholder takes the form <code>${name}</code>. Using
31   * <code>PropertyPlaceholderHelper</code> these placeholders can be substituted for user-supplied values.
32   * <p/>
33   * Values for substitution can be supplied using a {@link Properties} instance or using a {@link PlaceholderResolver}.
34   *
35   * @author Juergen Hoeller
36   * @author Rob Harrop
37   * @since 3.0
38   */
39  public class PropertyPlaceholderHelper {
40  
41      private static final Logger logger = LoggerFactory.getLogger(PropertyPlaceholderHelper.class);
42  
43      private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);
44  
45      static {
46          wellKnownSimplePrefixes.put("}", "{");
47          wellKnownSimplePrefixes.put("]", "[");
48          wellKnownSimplePrefixes.put(")", "(");
49      }
50  
51      private final String placeholderPrefix;
52  
53      private final String placeholderSuffix;
54  
55      private final String simplePrefix;
56  
57      private final String valueSeparator;
58  
59      private final boolean ignoreUnresolvablePlaceholders;
60  
61      /**
62       * Creates a new <code>PropertyPlaceholderHelper</code> that uses the supplied prefix and suffix. Unresolvable placeholders are ignored.
63       *
64       * @param placeholderPrefix the prefix that denotes the start of a placeholder.
65       * @param placeholderSuffix the suffix that denotes the end of a placeholder.
66       */
67      public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
68          this(placeholderPrefix, placeholderSuffix, null, true);
69      }
70  
71      /**
72       * Creates a new <code>PropertyPlaceholderHelper</code> that uses the supplied prefix and suffix.
73       *
74       * @param placeholderPrefix              the prefix that denotes the start of a placeholder
75       * @param placeholderSuffix              the suffix that denotes the end of a placeholder
76       * @param valueSeparator                 the separating character between the placeholder variable and the associated default value, if any
77       * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored (<code>true</code>) or cause an exception (
78       *                                       <code>false</code>).
79       */
80      public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator, boolean ignoreUnresolvablePlaceholders) {
81  
82          Assert.notNull(placeholderPrefix, "placeholderPrefix must not be null");
83          Assert.notNull(placeholderSuffix, "placeholderSuffix must not be null");
84          this.placeholderPrefix = placeholderPrefix;
85          this.placeholderSuffix = placeholderSuffix;
86          String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
87          if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
88              this.simplePrefix = simplePrefixForSuffix;
89          } else {
90              this.simplePrefix = this.placeholderPrefix;
91          }
92          this.valueSeparator = valueSeparator;
93          this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
94      }
95  
96      /**
97       * Replaces all placeholders of format <code>${name}</code> with the corresponding property from the supplied {@link Properties} .
98       *
99       * @param value      the value containing the placeholders to be replaced.
100      * @param properties the <code>Properties</code> to use for replacement.
101      * @return the supplied value with placeholders replaced inline.
102      */
103     public String replacePlaceholders(String value, final Properties properties) {
104         Assert.notNull(properties, "Argument 'properties' must not be null.");
105         return replacePlaceholders(value, new PlaceholderResolver() {
106             @Override
107             public String resolvePlaceholder(String placeholderName) {
108                 return properties.getProperty(placeholderName);
109             }
110         });
111     }
112 
113     /**
114      * Replaces all placeholders of format <code>${name}</code> with the value returned from the supplied {@link PlaceholderResolver}.
115      *
116      * @param value               the value containing the placeholders to be replaced.
117      * @param placeholderResolver the <code>PlaceholderResolver</code> to use for replacement.
118      * @return the supplied value with placeholders replaced inline.
119      */
120     public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
121         Assert.notNull(value, "Argument 'value' must not be null.");
122         return parseStringValue(value, placeholderResolver, new HashSet<String>());
123     }
124 
125     protected String parseStringValue(String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
126 
127         StringBuilder buf = new StringBuilder(strVal);
128 
129         int startIndex = strVal.indexOf(this.placeholderPrefix);
130         while (startIndex != -1) {
131             int endIndex = findPlaceholderEndIndex(buf, startIndex);
132             if (endIndex != -1) {
133                 String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
134                 if (!visitedPlaceholders.add(placeholder)) {
135                     throw new IllegalArgumentException("Circular placeholder reference '" + placeholder + "' in property definitions");
136                 }
137                 // Recursive invocation, parsing placeholders contained in the placeholder key.
138                 placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
139 
140                 // Now obtain the value for the fully resolved key...
141                 String propVal = placeholderResolver.resolvePlaceholder(placeholder);
142                 if (propVal == null && this.valueSeparator != null) {
143                     int separatorIndex = placeholder.indexOf(this.valueSeparator);
144                     if (separatorIndex != -1) {
145                         String actualPlaceholder = placeholder.substring(0, separatorIndex);
146                         String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
147                         propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
148                         if (propVal == null) {
149                             propVal = defaultValue;
150                         }
151                     }
152                 }
153                 if (propVal != null) {
154                     // Recursive invocation, parsing placeholders contained in the
155                     // previously resolved placeholder value.
156                     propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
157                     buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
158                     if (logger.isTraceEnabled()) {
159                         logger.trace("Resolved placeholder '" + placeholder + "'");
160                     }
161                     startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length());
162                 } else if (this.ignoreUnresolvablePlaceholders) {
163                     // Proceed with unprocessed value.
164                     startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
165                 } else {
166                     throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'");
167                 }
168 
169                 visitedPlaceholders.remove(placeholder);
170             } else {
171                 startIndex = -1;
172             }
173         }
174 
175         return buf.toString();
176     }
177 
178     private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
179         int index = startIndex + this.placeholderPrefix.length();
180         int withinNestedPlaceholder = 0;
181         while (index < buf.length()) {
182             if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
183                 if (withinNestedPlaceholder > 0) {
184                     withinNestedPlaceholder--;
185                     index = index + this.placeholderSuffix.length();
186                 } else {
187                     return index;
188                 }
189             } else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
190                 withinNestedPlaceholder++;
191                 index = index + this.simplePrefix.length();
192             } else {
193                 index++;
194             }
195         }
196         return -1;
197     }
198 
199     /**
200      * Strategy interface used to resolve replacement values for placeholders contained in Strings.
201      *
202      * @see PropertyPlaceholderHelper
203      */
204     public static interface PlaceholderResolver {
205 
206         /**
207          * Resolves the supplied placeholder name into the replacement value.
208          *
209          * @param placeholderName the name of the placeholder to resolve.
210          * @return the replacement value or <code>null</code> if no replacement is to be made.
211          */
212         String resolvePlaceholder(String placeholderName);
213     }
214 
215 }