001/* 002 * Copyright 2012 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community License, Version 1.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl1.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.ole.utility; 017 018import java.util.HashMap; 019import java.util.HashSet; 020import java.util.Map; 021import java.util.Properties; 022import java.util.Set; 023 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026import org.springframework.util.Assert; 027import org.springframework.util.StringUtils; 028 029/** 030 * Utility class for working with Strings that have placeholder values in them. A placeholder takes the form <code>${name}</code>. Using 031 * <code>PropertyPlaceholderHelper</code> these placeholders can be substituted for user-supplied values. 032 * <p/> 033 * Values for substitution can be supplied using a {@link Properties} instance or using a {@link PlaceholderResolver}. 034 * 035 * @author Juergen Hoeller 036 * @author Rob Harrop 037 * @since 3.0 038 */ 039public class PropertyPlaceholderHelper { 040 041 private static final Logger logger = LoggerFactory.getLogger(PropertyPlaceholderHelper.class); 042 043 private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4); 044 045 static { 046 wellKnownSimplePrefixes.put("}", "{"); 047 wellKnownSimplePrefixes.put("]", "["); 048 wellKnownSimplePrefixes.put(")", "("); 049 } 050 051 private final String placeholderPrefix; 052 053 private final String placeholderSuffix; 054 055 private final String simplePrefix; 056 057 private final String valueSeparator; 058 059 private final boolean ignoreUnresolvablePlaceholders; 060 061 /** 062 * Creates a new <code>PropertyPlaceholderHelper</code> that uses the supplied prefix and suffix. Unresolvable placeholders are ignored. 063 * 064 * @param placeholderPrefix the prefix that denotes the start of a placeholder. 065 * @param placeholderSuffix the suffix that denotes the end of a placeholder. 066 */ 067 public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) { 068 this(placeholderPrefix, placeholderSuffix, null, true); 069 } 070 071 /** 072 * Creates a new <code>PropertyPlaceholderHelper</code> that uses the supplied prefix and suffix. 073 * 074 * @param placeholderPrefix the prefix that denotes the start of a placeholder 075 * @param placeholderSuffix the suffix that denotes the end of a placeholder 076 * @param valueSeparator the separating character between the placeholder variable and the associated default value, if any 077 * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored (<code>true</code>) or cause an exception ( 078 * <code>false</code>). 079 */ 080 public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator, boolean ignoreUnresolvablePlaceholders) { 081 082 Assert.notNull(placeholderPrefix, "placeholderPrefix must not be null"); 083 Assert.notNull(placeholderSuffix, "placeholderSuffix must not be null"); 084 this.placeholderPrefix = placeholderPrefix; 085 this.placeholderSuffix = placeholderSuffix; 086 String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix); 087 if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) { 088 this.simplePrefix = simplePrefixForSuffix; 089 } else { 090 this.simplePrefix = this.placeholderPrefix; 091 } 092 this.valueSeparator = valueSeparator; 093 this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; 094 } 095 096 /** 097 * Replaces all placeholders of format <code>${name}</code> with the corresponding property from the supplied {@link Properties} . 098 * 099 * @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}