View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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 }