1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.utils;
21
22 import java.util.HashSet;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.Properties;
26 import java.util.Set;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
31
32 /**
33 * Resolves chained properties from a user-defined property file.
34 */
35 public final class ChainedPropertyUtil {
36
37 /**
38 * Used to report undefined property in exception message.
39 */
40 public static final String UNDEFINED_PROPERTY_MESSAGE = "Undefined property: ";
41
42 /**
43 * Property variable expression pattern, matches property variables such as {@code ${basedir}}.
44 */
45 private static final Pattern PROPERTY_VARIABLE_PATTERN = Pattern.compile("\\$\\{([^\\s}]+)}");
46
47 /**
48 * Prevent instantiation.
49 */
50 private ChainedPropertyUtil() {
51 }
52
53 /**
54 * Accepts user defined properties and returns new properties
55 * with all chained properties resolved.
56 *
57 * @param properties the underlying properties to use
58 * for property resolution.
59 * @return resolved properties
60 * @throws CheckstyleException when chained property is not defined
61 */
62 public static Properties getResolvedProperties(Properties properties)
63 throws CheckstyleException {
64 final Set<String> unresolvedPropertyNames =
65 new HashSet<>(properties.stringPropertyNames());
66 Iterator<String> unresolvedPropertyIterator = unresolvedPropertyNames.iterator();
67 final Map<Object, Object> comparisonProperties = new Properties();
68
69 while (unresolvedPropertyIterator.hasNext()) {
70 final String propertyName = unresolvedPropertyIterator.next();
71 String propertyValue = properties.getProperty(propertyName);
72 final Matcher matcher = PROPERTY_VARIABLE_PATTERN.matcher(propertyValue);
73
74 while (matcher.find()) {
75 final String propertyVariableExpression = matcher.group();
76 final String unresolvedPropertyName =
77 getPropertyNameFromExpression(propertyVariableExpression);
78
79 final String resolvedPropertyValue =
80 properties.getProperty(unresolvedPropertyName);
81
82 if (resolvedPropertyValue != null) {
83 propertyValue = propertyValue.replace(propertyVariableExpression,
84 resolvedPropertyValue);
85 properties.setProperty(propertyName, propertyValue);
86 }
87 }
88
89 if (allChainedPropertiesAreResolved(propertyValue)) {
90 unresolvedPropertyIterator.remove();
91 }
92
93 if (!unresolvedPropertyIterator.hasNext()) {
94
95 if (comparisonProperties.equals(properties)) {
96 // At this point, we will have not resolved any properties in two iterations,
97 // so unresolvable properties exist.
98 throw new CheckstyleException(UNDEFINED_PROPERTY_MESSAGE
99 + unresolvedPropertyNames);
100 }
101 comparisonProperties.putAll(properties);
102 unresolvedPropertyIterator = unresolvedPropertyNames.iterator();
103 }
104
105 }
106 return properties;
107 }
108
109 /**
110 * Gets an unresolved property name from a property variable expression
111 * by stripping the preceding '${' and trailing '}'.
112 *
113 * @param variableExpression the full property variable expression
114 * @return property name
115 */
116 private static String getPropertyNameFromExpression(String variableExpression) {
117 final int propertyStartIndex = variableExpression.lastIndexOf('{') + 1;
118 final int propertyEndIndex = variableExpression.lastIndexOf('}');
119 return variableExpression.substring(propertyStartIndex, propertyEndIndex);
120 }
121
122 /**
123 * Checks if all chained properties have been resolved. Essentially,
124 * this means that there exist no matches for PROPERTY_VARIABLE_PATTERN in the
125 * property value string.
126 *
127 * @param propertyValue the property value to check
128 * @return true if all chained properties are resolved
129 */
130 private static boolean allChainedPropertiesAreResolved(String propertyValue) {
131 return !PROPERTY_VARIABLE_PATTERN.matcher(propertyValue).find();
132 }
133 }