View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.cli.props;
20  
21  import java.io.IOException;
22  import java.nio.file.Files;
23  import java.nio.file.Path;
24  import java.util.Enumeration;
25  import java.util.StringTokenizer;
26  import java.util.function.Function;
27  
28  import static org.apache.maven.cli.props.InterpolationHelper.substVars;
29  
30  public class MavenPropertiesLoader {
31  
32      public static final String INCLUDES_PROPERTY = "${includes}"; // includes
33  
34      public static final String OVERRIDE_PREFIX =
35              "maven.override."; // prefix that marks that system property should override defaults.
36  
37      public static void loadProperties(
38              java.util.Properties properties, Path path, Function<String, String> callback, boolean escape)
39              throws IOException {
40          MavenProperties sp = new MavenProperties(false);
41          if (Files.exists(path)) {
42              sp.load(path);
43          }
44          properties.forEach(
45                  (k, v) -> sp.put(k.toString(), escape ? InterpolationHelper.escape(v.toString()) : v.toString()));
46          loadIncludes(path, sp, callback);
47          substitute(sp, callback);
48          sp.forEach(properties::setProperty);
49      }
50  
51      public static void substitute(MavenProperties props, Function<String, String> callback) {
52          for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements(); ) {
53              String name = (String) e.nextElement();
54              String value = props.getProperty(name);
55              if (value == null) {
56                  value = callback.apply(name);
57              }
58              if (name.startsWith(OVERRIDE_PREFIX)) {
59                  String overrideName = name.substring(OVERRIDE_PREFIX.length());
60                  props.put(overrideName, substVars(value, name, null, props, callback));
61              } else {
62                  props.put(name, substVars(value, name, null, props, callback));
63              }
64          }
65          props.keySet().removeIf(k -> k.startsWith(OVERRIDE_PREFIX));
66      }
67  
68      private static MavenProperties loadPropertiesFile(
69              Path path, boolean failIfNotFound, Function<String, String> callback) throws IOException {
70          MavenProperties configProps = new MavenProperties(null, false);
71          if (Files.exists(path) || failIfNotFound) {
72              configProps.load(path);
73              loadIncludes(path, configProps, callback);
74              trimValues(configProps);
75          }
76          return configProps;
77      }
78  
79      private static void loadIncludes(Path configProp, MavenProperties configProps, Function<String, String> callback)
80              throws IOException {
81          String includes = configProps.get(INCLUDES_PROPERTY);
82          if (includes != null) {
83              includes = substVars(includes, INCLUDES_PROPERTY, null, configProps, callback);
84              StringTokenizer st = new StringTokenizer(includes, "?\",", true);
85              if (st.countTokens() > 0) {
86                  String location;
87                  do {
88                      location = nextLocation(st);
89                      if (location != null) {
90                          boolean mandatory = true;
91                          if (location.startsWith("?")) {
92                              mandatory = false;
93                              location = location.substring(1);
94                          }
95                          Path path = configProp.resolveSibling(location);
96                          MavenProperties props = loadPropertiesFile(path, mandatory, s -> {
97                              var v = callback.apply(s);
98                              return v != null ? v : configProps.getProperty(s);
99                          });
100                         configProps.putAll(props);
101                     }
102                 } while (location != null);
103             }
104         }
105         configProps.remove(INCLUDES_PROPERTY);
106     }
107 
108     private static void trimValues(MavenProperties configProps) {
109         configProps.replaceAll((k, v) -> v.trim());
110     }
111 
112     private static String nextLocation(StringTokenizer st) {
113         boolean optional = false;
114         String retVal = null;
115 
116         if (st.countTokens() > 0) {
117             String tokenList = "?\",";
118             StringBuilder tokBuf = new StringBuilder(10);
119             String tok;
120             boolean inQuote = false;
121             boolean tokStarted = false;
122             boolean exit = false;
123             while ((st.hasMoreTokens()) && (!exit)) {
124                 tok = st.nextToken(tokenList);
125                 switch (tok) {
126                     case "\"":
127                         inQuote = !inQuote;
128                         if (inQuote) {
129                             tokenList = "\"";
130                         } else {
131                             tokenList = "\" ";
132                         }
133                         break;
134                     case ",":
135                         if (tokStarted) {
136                             retVal = tokBuf.toString();
137                             tokStarted = false;
138                             tokBuf = new StringBuilder(10);
139                             exit = true;
140                         }
141                         break;
142                     case "?":
143                         optional = true;
144                         break;
145                     default:
146                         tokStarted = true;
147                         tokBuf.append(tok.trim());
148                         break;
149                 }
150             }
151 
152             // Handle case where end of token stream and
153             // still got data
154             if ((!exit) && (tokStarted)) {
155                 retVal = tokBuf.toString();
156             }
157         }
158 
159         return optional ? "?" + retVal : retVal;
160     }
161 }