001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.utils;
021
022import java.util.HashSet;
023import java.util.Iterator;
024import java.util.Map;
025import java.util.Properties;
026import java.util.Set;
027import java.util.regex.Matcher;
028import java.util.regex.Pattern;
029
030import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
031
032/**
033 * Resolves chained properties from a user-defined property file.
034 */
035public final class ChainedPropertyUtil {
036
037    /**
038     * Used to report undefined property in exception message.
039     */
040    public static final String UNDEFINED_PROPERTY_MESSAGE = "Undefined property: ";
041
042    /**
043     * Property variable expression pattern, matches property variables such as {@code ${basedir}}.
044     */
045    private static final Pattern PROPERTY_VARIABLE_PATTERN = Pattern.compile("\\$\\{([^\\s}]+)}");
046
047    /**
048     * Prevent instantiation.
049     */
050    private ChainedPropertyUtil() {
051    }
052
053    /**
054     * Accepts user defined properties and returns new properties
055     * with all chained properties resolved.
056     *
057     * @param properties the underlying properties to use for property resolution.
058     * @return resolved properties
059     * @throws CheckstyleException when chained property is not defined
060     */
061    public static Properties getResolvedProperties(Properties properties)
062            throws CheckstyleException {
063        final Set<String> unresolvedPropertyNames =
064            new HashSet<>(properties.stringPropertyNames());
065        Iterator<String> unresolvedPropertyIterator = unresolvedPropertyNames.iterator();
066        final Map<Object, Object> comparisonProperties = new Properties();
067
068        while (unresolvedPropertyIterator.hasNext()) {
069            final String propertyName = unresolvedPropertyIterator.next();
070            String propertyValue = properties.getProperty(propertyName);
071            final Matcher matcher = PROPERTY_VARIABLE_PATTERN.matcher(propertyValue);
072
073            while (matcher.find()) {
074                final String propertyVariableExpression = matcher.group();
075                final String unresolvedPropertyName =
076                    getPropertyNameFromExpression(propertyVariableExpression);
077
078                final String resolvedPropertyValue =
079                    properties.getProperty(unresolvedPropertyName);
080
081                if (resolvedPropertyValue != null) {
082                    propertyValue = propertyValue.replace(propertyVariableExpression,
083                        resolvedPropertyValue);
084                    properties.setProperty(propertyName, propertyValue);
085                }
086            }
087
088            if (allChainedPropertiesAreResolved(propertyValue)) {
089                unresolvedPropertyIterator.remove();
090            }
091
092            if (!unresolvedPropertyIterator.hasNext()) {
093
094                if (comparisonProperties.equals(properties)) {
095                    // At this point, we will have not resolved any properties in two iterations,
096                    // so unresolvable properties exist.
097                    throw new CheckstyleException(UNDEFINED_PROPERTY_MESSAGE
098                        + unresolvedPropertyNames);
099                }
100                comparisonProperties.putAll(properties);
101                unresolvedPropertyIterator = unresolvedPropertyNames.iterator();
102            }
103
104        }
105        return properties;
106    }
107
108    /**
109     * Gets an unresolved property name from a property variable expression
110     * by stripping the preceding '${' and trailing '}'.
111     *
112     * @param variableExpression the full property variable expression
113     * @return property name
114     */
115    private static String getPropertyNameFromExpression(String variableExpression) {
116        final int propertyStartIndex = variableExpression.lastIndexOf('{') + 1;
117        final int propertyEndIndex = variableExpression.lastIndexOf('}');
118        return variableExpression.substring(propertyStartIndex, propertyEndIndex);
119    }
120
121    /**
122     * Checks if all chained properties have been resolved. Essentially,
123     * this means that there exist no matches for PROPERTY_VARIABLE_PATTERN in the
124     * property value string.
125     *
126     * @param propertyValue the property value to check
127     * @return true if all chained properties are resolved
128     */
129    private static boolean allChainedPropertiesAreResolved(String propertyValue) {
130        return !PROPERTY_VARIABLE_PATTERN.matcher(propertyValue).find();
131    }
132
133}