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.cling.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.Map;
26  import java.util.StringTokenizer;
27  import java.util.function.Function;
28  
29  import org.apache.maven.internal.impl.model.DefaultInterpolator;
30  
31  public class MavenPropertiesLoader {
32  
33      public static final String INCLUDES_PROPERTY = "${includes}"; // includes
34  
35      public static final String OVERRIDE_PREFIX =
36              "maven.override."; // prefix that marks that system property should override defaults.
37  
38      public static void loadProperties(
39              java.util.Properties properties, Path path, Function<String, String> callback, boolean escape)
40              throws IOException {
41          MavenProperties sp = new MavenProperties(false);
42          if (Files.exists(path)) {
43              sp.load(path);
44          }
45          properties.forEach(
46                  (k, v) -> sp.put(k.toString(), escape ? DefaultInterpolator.escape(v.toString()) : v.toString()));
47          loadIncludes(path, sp, callback);
48          substitute(sp, callback);
49          sp.forEach(properties::setProperty);
50      }
51  
52      public static void substitute(MavenProperties props, Function<String, String> callback) {
53          for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements(); ) {
54              String name = (String) e.nextElement();
55              String value = props.getProperty(name);
56              if (value == null) {
57                  value = callback.apply(name);
58              }
59              if (name.startsWith(OVERRIDE_PREFIX)) {
60                  String overrideName = name.substring(OVERRIDE_PREFIX.length());
61                  props.put(overrideName, substVars(value, name, props, callback));
62              } else {
63                  props.put(name, substVars(value, name, props, callback));
64              }
65          }
66          props.keySet().removeIf(k -> k.startsWith(OVERRIDE_PREFIX));
67      }
68  
69      private static MavenProperties loadPropertiesFile(
70              Path path, boolean failIfNotFound, Function<String, String> callback) throws IOException {
71          MavenProperties configProps = new MavenProperties(null, false);
72          if (Files.exists(path) || failIfNotFound) {
73              configProps.load(path);
74              loadIncludes(path, configProps, callback);
75              trimValues(configProps);
76          }
77          return configProps;
78      }
79  
80      private static void loadIncludes(Path configProp, MavenProperties configProps, Function<String, String> callback)
81              throws IOException {
82          String includes = configProps.get(INCLUDES_PROPERTY);
83          if (includes != null) {
84              includes = substVars(includes, INCLUDES_PROPERTY, configProps, callback);
85              StringTokenizer st = new StringTokenizer(includes, "?\",", true);
86              if (st.countTokens() > 0) {
87                  String location;
88                  do {
89                      location = nextLocation(st);
90                      if (location != null) {
91                          boolean mandatory = true;
92                          if (location.startsWith("?")) {
93                              mandatory = false;
94                              location = location.substring(1);
95                          }
96                          Path path = configProp.resolveSibling(location);
97                          MavenProperties props = loadPropertiesFile(path, mandatory, s -> {
98                              var v = callback.apply(s);
99                              return v != null ? v : configProps.getProperty(s);
100                         });
101                         configProps.putAll(props);
102                     }
103                 } while (location != null);
104             }
105         }
106         configProps.remove(INCLUDES_PROPERTY);
107     }
108 
109     private static void trimValues(MavenProperties configProps) {
110         configProps.replaceAll((k, v) -> v.trim());
111     }
112 
113     private static String nextLocation(StringTokenizer st) {
114         boolean optional = false;
115         String retVal = null;
116 
117         if (st.countTokens() > 0) {
118             String tokenList = "?\",";
119             StringBuilder tokBuf = new StringBuilder(10);
120             String tok;
121             boolean inQuote = false;
122             boolean tokStarted = false;
123             boolean exit = false;
124             while ((st.hasMoreTokens()) && (!exit)) {
125                 tok = st.nextToken(tokenList);
126                 switch (tok) {
127                     case "\"":
128                         inQuote = !inQuote;
129                         if (inQuote) {
130                             tokenList = "\"";
131                         } else {
132                             tokenList = "\" ";
133                         }
134                         break;
135                     case ",":
136                         if (tokStarted) {
137                             retVal = tokBuf.toString();
138                             tokStarted = false;
139                             tokBuf = new StringBuilder(10);
140                             exit = true;
141                         }
142                         break;
143                     case "?":
144                         optional = true;
145                         break;
146                     default:
147                         tokStarted = true;
148                         tokBuf.append(tok.trim());
149                         break;
150                 }
151             }
152 
153             // Handle case where end of token stream and
154             // still got data
155             if ((!exit) && (tokStarted)) {
156                 retVal = tokBuf.toString();
157             }
158         }
159 
160         return optional ? "?" + retVal : retVal;
161     }
162 
163     public static String substVars(
164             String value, String name, Map<String, String> props, Function<String, String> callback) {
165         return DefaultInterpolator.substVars(value, name, null, props, callback, null, false);
166     }
167 }