blob: 6aff419258408ca1263047c3ef553338a0ee1889 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2010 VMware Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware Inc. - initial contribution
*******************************************************************************/
package org.eclipse.virgo.test.launcher;
import java.util.Enumeration;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Stack;
import java.util.UUID;
/**
* Utility class for resolving placeholders inside a {@link Properties} instance. These placeholders can refer to other
* properties in the <code>Properties</code> instance. The place holders may also have a modifier in them
*
* <pre>
* ${com.springsource:modifier}
* </pre>
*
* where everything after the colon is considered the modifier. This class does not interpret these modifiers but
* rather delegates to a {@link PlaceholderValueTransformer} for processing.
* <p />
*
* <strong>Concurrent Semantics</strong><br />
*
* Threadsafe.
*/
public final class PropertyPlaceholderResolver {
private static final Pattern PATTERN = Pattern.compile("\\$\\{([^:\\}]*):?([^\\}]*)?\\}");
private static final PlaceholderValueTransformer IDENTITY_TRANSFORMER = new PlaceholderValueTransformer() {
public String transform(String propertyName, String propertyValue, String modifier) {
return propertyValue;
}
};
/**
* Resolves all placeholders in the supplied {@link Properties} instance.
*
* @param input the properties to resolve.
* @return the resolved properties.
*/
public Properties resolve(Properties input) {
return resolve(input, IDENTITY_TRANSFORMER);
}
/**
* Resolves all placeholders in the supplied {@link Properties} instance and transform any based on their modifiers.
*
* @param input the properties to resolve.
* @param transformer a transformer for handling property modifiers
* @return the resolved properties.
*/
public Properties resolve(Properties input, PlaceholderValueTransformer transformer) {
Properties result = new Properties();
Enumeration<?> propertyNames = input.propertyNames();
while (propertyNames.hasMoreElements()) {
String propertyName = (String) propertyNames.nextElement();
result.setProperty(propertyName, resolveProperty(propertyName, input, transformer));
}
return result;
}
/**
* Resolves all placeholders in the supplied string with values from a {@link Properties} instance.
*
* @param input the string to resolve
* @param props the properties to use for resolution
* @return the resolved string
*/
public String resolve(String input, Properties props) {
return resolve(input, props, IDENTITY_TRANSFORMER);
}
/**
* Resolves all placeholders in the supplied string with values from a {@link Properties} instance and transform any
* based on their modifiers.
*
* @param input the string to resolve
* @param props the properties to use for resolution
* @param transformer a transformer for handling property modifiers
* @return the resolved string
*/
public String resolve(String input, Properties props, PlaceholderValueTransformer transformer) {
String key = UUID.randomUUID().toString();
props.put(key, input);
String value = resolveProperty(key, props, transformer);
props.remove(key);
return value;
}
private String resolveProperty(String name, Properties props, PlaceholderValueTransformer transformer) {
Stack<String> visitState = new Stack<String>();
return resolve(name, props, transformer, visitState);
}
private String resolve(String name, Properties props, PlaceholderValueTransformer transformer, Stack<String> visitState) {
visitState.push(name);
String initialValue = props.getProperty(name);
if(initialValue == null) {
throw new RuntimeException("No value found for placeholder '" + name + "'");
}
Matcher matcher = PATTERN.matcher(initialValue);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String propName = matcher.group(1);
if (visitState.contains(propName)) {
throw new IllegalArgumentException(formatPropertyCycleMessage(visitState));
}
String value = resolve(propName, props, transformer, visitState);
if (matcher.group(2).length() > 0) {
value = transformer.transform(propName, value, matcher.group(2));
}
matcher.appendReplacement(sb, escapeBackslashes(value));
}
matcher.appendTail(sb);
visitState.pop();
return sb.toString();
}
private static String escapeBackslashes(String string) {
StringBuffer sb = new StringBuffer(string.length());
int bsIndex = string.indexOf("\\");
int pos = 0;
while (bsIndex != -1) {
sb.append(string.substring(pos,bsIndex+1));
sb.append("\\"); // another backslash
pos = bsIndex+1;
bsIndex = string.indexOf("\\",pos);
}
sb.append(string.substring(pos, string.length()));
return new String(sb);
}
private String formatPropertyCycleMessage(Stack<String> visitState) {
StringBuilder sb = new StringBuilder();
sb.append("Circular reference in property definitions: ");
for (String name : visitState) {
sb.append(name).append(" -> ");
}
sb.append(visitState.iterator().next());
return sb.toString();
}
/**
* An interface for property placeholder modifiers. Implementations of this interface are called when a property
* placeholder modifier is detected on a class.
* <p />
*
* <strong>Concurrent Semantics</strong><br />
*
* Implementations must be threadsafe.
*
*/
public static interface PlaceholderValueTransformer {
/**
* Transforms a property from its initial value to some other value
*
* @param propertyName the name of the property being transformed
* @param propertyValue the original value of the property
* @param modifier the modifer string attached to the placeholder
* @return A string that has been modified by this transformer and to be used in place of the original value
*/
String transform(String propertyName, String propertyValue, String modifier);
}
}