1 /* 2 * Copyright 2002-2012 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 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 17 package org.springframework.util; 18 19 import java.util.HashMap; 20 import java.util.HashSet; 21 import java.util.Map; 22 import java.util.Properties; 23 import java.util.Set; 24 25 import org.apache.commons.logging.Log; 26 import org.apache.commons.logging.LogFactory; 27 28 /** 29 * Utility class for working with Strings that have placeholder values in them. A placeholder takes the form 30 * <code>${name}</code>. Using <code>PropertyPlaceholderHelper</code> these placeholders can be substituted for 31 * user-supplied values. <p> Values for substitution can be supplied using a {@link Properties} instance or 32 * using a {@link PlaceholderResolver}. 33 * 34 * @author Juergen Hoeller 35 * @author Rob Harrop 36 * @since 3.0 37 */ 38 public class PropertyPlaceholderHelper { 39 40 private static final Log logger = LogFactory.getLog(PropertyPlaceholderHelper.class); 41 42 private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4); 43 44 static { 45 wellKnownSimplePrefixes.put("}", "{"); 46 wellKnownSimplePrefixes.put("]", "["); 47 wellKnownSimplePrefixes.put(")", "("); 48 } 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 /** 63 * Creates a new <code>PropertyPlaceholderHelper</code> that uses the supplied prefix and suffix. 64 * Unresolvable placeholders are ignored. 65 * @param placeholderPrefix the prefix that denotes the start of a placeholder. 66 * @param placeholderSuffix the suffix that denotes the end of a placeholder. 67 */ PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix)68 public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) { 69 this(placeholderPrefix, placeholderSuffix, null, true); 70 } 71 72 /** 73 * Creates a new <code>PropertyPlaceholderHelper</code> that uses the supplied prefix and suffix. 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 77 * and the associated default value, if any 78 * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored 79 * (<code>true</code>) or cause an exception (<code>false</code>). 80 */ PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator, boolean ignoreUnresolvablePlaceholders)81 public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, 82 String valueSeparator, boolean ignoreUnresolvablePlaceholders) { 83 84 Assert.notNull(placeholderPrefix, "placeholderPrefix must not be null"); 85 Assert.notNull(placeholderSuffix, "placeholderSuffix must not be null"); 86 this.placeholderPrefix = placeholderPrefix; 87 this.placeholderSuffix = placeholderSuffix; 88 String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix); 89 if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) { 90 this.simplePrefix = simplePrefixForSuffix; 91 } 92 else { 93 this.simplePrefix = this.placeholderPrefix; 94 } 95 this.valueSeparator = valueSeparator; 96 this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; 97 } 98 99 100 /** 101 * Replaces all placeholders of format <code>${name}</code> with the corresponding property 102 * from the supplied {@link Properties}. 103 * @param value the value containing the placeholders to be replaced. 104 * @param properties the <code>Properties</code> to use for replacement. 105 * @return the supplied value with placeholders replaced inline. 106 */ replacePlaceholders(String value, final Properties properties)107 public String replacePlaceholders(String value, final Properties properties) { 108 Assert.notNull(properties, "Argument 'properties' must not be null."); 109 return replacePlaceholders(value, new PlaceholderResolver() { 110 public String resolvePlaceholder(String placeholderName) { 111 return properties.getProperty(placeholderName); 112 } 113 }); 114 } 115 116 /** 117 * Replaces all placeholders of format <code>${name}</code> with the value returned from the supplied 118 * {@link PlaceholderResolver}. 119 * @param value the value containing the placeholders to be replaced. 120 * @param placeholderResolver the <code>PlaceholderResolver</code> to use for replacement. 121 * @return the supplied value with placeholders replaced inline. 122 */ 123 public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { 124 Assert.notNull(value, "Argument 'value' must not be null."); 125 return parseStringValue(value, placeholderResolver, new HashSet<String>()); 126 } 127 128 protected String parseStringValue( 129 String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { 130 131 StringBuilder buf = new StringBuilder(strVal); 132 133 int startIndex = strVal.indexOf(this.placeholderPrefix); 134 while (startIndex != -1) { 135 int endIndex = findPlaceholderEndIndex(buf, startIndex); 136 if (endIndex != -1) { 137 String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex); 138 String originalPlaceholder = placeholder; 139 if (!visitedPlaceholders.add(originalPlaceholder)) { 140 throw new IllegalArgumentException( 141 "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); 142 } 143 // Recursive invocation, parsing placeholders contained in the placeholder key. 144 placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); 145 // Now obtain the value for the fully resolved key... 146 String propVal = placeholderResolver.resolvePlaceholder(placeholder); 147 if (propVal == null && this.valueSeparator != null) { 148 int separatorIndex = placeholder.indexOf(this.valueSeparator); 149 if (separatorIndex != -1) { 150 String actualPlaceholder = placeholder.substring(0, separatorIndex); 151 String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); 152 propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); 153 if (propVal == null) { 154 propVal = defaultValue; 155 } 156 } 157 } 158 if (propVal != null) { 159 // Recursive invocation, parsing placeholders contained in the 160 // previously resolved placeholder value. 161 propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); 162 buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); 163 if (logger.isTraceEnabled()) { 164 logger.trace("Resolved placeholder '" + placeholder + "'"); 165 } 166 startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length()); 167 } 168 else if (this.ignoreUnresolvablePlaceholders) { 169 // Proceed with unprocessed value. 170 startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); 171 } 172 else { 173 throw new IllegalArgumentException("Could not resolve placeholder '" + 174 placeholder + "'" + " in string value \"" + strVal + "\""); 175 } 176 visitedPlaceholders.remove(originalPlaceholder); 177 } 178 else { 179 startIndex = -1; 180 } 181 } 182 183 return buf.toString(); 184 } 185 186 private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { 187 int index = startIndex + this.placeholderPrefix.length(); 188 int withinNestedPlaceholder = 0; 189 while (index < buf.length()) { 190 if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { 191 if (withinNestedPlaceholder > 0) { 192 withinNestedPlaceholder--; 193 index = index + this.placeholderSuffix.length(); 194 } 195 else { 196 return index; 197 } 198 } 199 else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) { 200 withinNestedPlaceholder++; 201 index = index + this.simplePrefix.length(); 202 } 203 else { 204 index++; 205 } 206 } 207 return -1; 208 } 209 210 211 /** 212 * Strategy interface used to resolve replacement values for placeholders contained in Strings. 213 * @see PropertyPlaceholderHelper 214 */ 215 public static interface PlaceholderResolver { 216 217 /** 218 * Resolves the supplied placeholder name into the replacement value. 219 * @param placeholderName the name of the placeholder to resolve. 220 * @return the replacement value or <code>null</code> if no replacement is to be made. 221 */ 222 String resolvePlaceholder(String placeholderName); 223 } 224 225 } 226