ChainedPropertyUtil.java
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2021 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.utils;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
/**
* Resolves chained properties from a user-defined property file.
*/
public final class ChainedPropertyUtil {
/**
* Used to report undefined property in exception message.
*/
public static final String UNDEFINED_PROPERTY_MESSAGE = "Undefined property: ";
/**
* Property variable expression pattern, matches property variables such as {@code ${basedir}}.
*/
private static final Pattern PROPERTY_VARIABLE_PATTERN = Pattern.compile("\\$\\{([^\\s}]+)}");
/**
* Prevent instantiation.
*/
private ChainedPropertyUtil() {
}
/**
* Accepts user defined properties and returns new properties
* with all chained properties resolved.
*
* @param properties the underlying properties to use
* for property resolution.
* @return resolved properties
* @throws CheckstyleException when chained property is not defined
*/
public static Properties getResolvedProperties(Properties properties)
throws CheckstyleException {
final Set<String> unresolvedPropertyNames =
new HashSet<>(properties.stringPropertyNames());
Iterator<String> unresolvedPropertyIterator = unresolvedPropertyNames.iterator();
final Map<Object, Object> comparisonProperties = new Properties();
while (unresolvedPropertyIterator.hasNext()) {
final String propertyName = unresolvedPropertyIterator.next();
String propertyValue = properties.getProperty(propertyName);
final Matcher matcher = PROPERTY_VARIABLE_PATTERN.matcher(propertyValue);
while (matcher.find()) {
final String propertyVariableExpression = matcher.group();
final String unresolvedPropertyName =
getPropertyNameFromExpression(propertyVariableExpression);
final String resolvedPropertyValue =
properties.getProperty(unresolvedPropertyName);
if (resolvedPropertyValue != null) {
propertyValue = propertyValue.replace(propertyVariableExpression,
resolvedPropertyValue);
properties.setProperty(propertyName, propertyValue);
}
}
if (allChainedPropertiesAreResolved(propertyValue)) {
unresolvedPropertyIterator.remove();
}
if (!unresolvedPropertyIterator.hasNext()) {
if (comparisonProperties.equals(properties)) {
// At this point, we will have not resolved any properties in two iterations,
// so unresolvable properties exist.
throw new CheckstyleException(UNDEFINED_PROPERTY_MESSAGE
+ unresolvedPropertyNames);
}
comparisonProperties.putAll(properties);
unresolvedPropertyIterator = unresolvedPropertyNames.iterator();
}
}
return properties;
}
/**
* Gets an unresolved property name from a property variable expression
* by stripping the preceding '${' and trailing '}'.
*
* @param variableExpression the full property variable expression
* @return property name
*/
private static String getPropertyNameFromExpression(String variableExpression) {
final int propertyStartIndex = variableExpression.lastIndexOf('{') + 1;
final int propertyEndIndex = variableExpression.lastIndexOf('}');
return variableExpression.substring(propertyStartIndex, propertyEndIndex);
}
/**
* Checks if all chained properties have been resolved. Essentially,
* this means that there exist no matches for PROPERTY_VARIABLE_PATTERN in the
* property value string.
*
* @param propertyValue the property value to check
* @return true if all chained properties are resolved
*/
private static boolean allChainedPropertiesAreResolved(String propertyValue) {
return !PROPERTY_VARIABLE_PATTERN.matcher(propertyValue).find();
}
}