Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
PropertyUtils |
|
| 5.0;5 |
1 | package org.apache.maven.shared.filtering; | |
2 | ||
3 | /* | |
4 | * Licensed to the Apache Software Foundation (ASF) under one | |
5 | * or more contributor license agreements. See the NOTICE file | |
6 | * distributed with this work for additional information | |
7 | * regarding copyright ownership. The ASF licenses this file | |
8 | * to you under the Apache License, Version 2.0 (the | |
9 | * "License"); you may not use this file except in compliance | |
10 | * with the License. You may obtain a copy of the License at | |
11 | * | |
12 | * http://www.apache.org/licenses/LICENSE-2.0 | |
13 | * | |
14 | * Unless required by applicable law or agreed to in writing, | |
15 | * software distributed under the License is distributed on an | |
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
17 | * KIND, either express or implied. See the License for the | |
18 | * specific language governing permissions and limitations | |
19 | * under the License. | |
20 | */ | |
21 | ||
22 | import org.codehaus.plexus.util.IOUtil; | |
23 | import org.codehaus.plexus.util.StringUtils; | |
24 | ||
25 | import java.io.File; | |
26 | import java.io.FileInputStream; | |
27 | import java.io.FileNotFoundException; | |
28 | import java.io.IOException; | |
29 | import java.util.Iterator; | |
30 | import java.util.Properties; | |
31 | ||
32 | ||
33 | /** | |
34 | * @author <a href="mailto:kenney@neonics.com">Kenney Westerhof</a> | |
35 | * @author William Ferguson | |
36 | * | |
37 | */ | |
38 | public final class PropertyUtils | |
39 | { | |
40 | /** | |
41 | * Private empty constructor to prevent instantiation. | |
42 | */ | |
43 | private PropertyUtils() | |
44 | 0 | { |
45 | // prevent instantiation | |
46 | 0 | } |
47 | ||
48 | /** | |
49 | * Reads a property file, resolving all internal variables, using the supplied base properties. | |
50 | * <p> | |
51 | * The properties are resolved iteratively, so if the value of property A refers to property B, | |
52 | * then after resolution the value of property B will contain the value of property B. | |
53 | * </p> | |
54 | * | |
55 | * @param propFile The property file to load. | |
56 | * @param baseProps Properties containing the initial values to substitute into the properties file. | |
57 | * @return Properties object containing the properties in the file with their values fully resolved. | |
58 | * @throws IOException if profile does not exist, or cannot be read. | |
59 | */ | |
60 | public static Properties loadPropertyFile( File propFile, Properties baseProps ) | |
61 | throws IOException | |
62 | { | |
63 | 76 | if ( !propFile.exists() ) |
64 | { | |
65 | 2 | throw new FileNotFoundException( propFile.toString() ); |
66 | } | |
67 | ||
68 | 74 | final Properties fileProps = new Properties(); |
69 | 74 | final FileInputStream inStream = new FileInputStream( propFile ); |
70 | try | |
71 | { | |
72 | 74 | fileProps.load( inStream ); |
73 | } | |
74 | finally | |
75 | { | |
76 | 74 | IOUtil.close( inStream ); |
77 | 74 | } |
78 | ||
79 | 74 | final Properties combinedProps = new Properties(); |
80 | 74 | combinedProps.putAll( baseProps == null ? new Properties() : baseProps ); |
81 | 74 | combinedProps.putAll( fileProps ); |
82 | ||
83 | // The algorithm iterates only over the fileProps which is all that is required to resolve | |
84 | // the properties defined within the file. This is slightly different to current, however | |
85 | // I suspect that this was the actual original intent. | |
86 | // | |
87 | // The difference is that #loadPropertyFile(File, boolean, boolean) also resolves System properties | |
88 | // whose values contain expressions. I believe this is unexpected and is not validated by the test cases, | |
89 | // as can be verified by replacing the implementation of #loadPropertyFile(File, boolean, boolean) | |
90 | // with the commented variant I have provided that reuses this method. | |
91 | ||
92 | 74 | for ( Iterator iter = fileProps.keySet().iterator(); iter.hasNext(); ) |
93 | { | |
94 | 302 | final String k = (String) iter.next(); |
95 | 302 | final String propValue = getPropertyValue( k, combinedProps ); |
96 | 302 | fileProps.setProperty( k, propValue ); |
97 | 302 | } |
98 | ||
99 | 74 | return fileProps; |
100 | } | |
101 | ||
102 | /** | |
103 | * Reads a property file, resolving all internal variables. | |
104 | * | |
105 | * @param propfile The property file to load | |
106 | * @param fail whether to throw an exception when the file cannot be loaded or to return null | |
107 | * @param useSystemProps whether to incorporate System.getProperties settings into the returned Properties object. | |
108 | * @return the loaded and fully resolved Properties object | |
109 | * @throws IOException if profile does not exist, or cannot be read. | |
110 | */ | |
111 | public static Properties loadPropertyFile( File propfile, boolean fail, boolean useSystemProps ) | |
112 | throws IOException | |
113 | { | |
114 | ||
115 | 6 | final Properties baseProps = new Properties(); |
116 | ||
117 | 6 | if ( useSystemProps ) |
118 | { | |
119 | 2 | baseProps.putAll( System.getProperties() ); |
120 | } | |
121 | ||
122 | 6 | final Properties resolvedProps = new Properties(); |
123 | try | |
124 | { | |
125 | 6 | resolvedProps.putAll( loadPropertyFile( propfile, baseProps ) ); |
126 | } | |
127 | 2 | catch ( FileNotFoundException e ) |
128 | { | |
129 | 2 | if ( fail ) |
130 | { | |
131 | 2 | throw new FileNotFoundException( propfile.toString() ); |
132 | } | |
133 | 4 | } |
134 | ||
135 | 4 | if ( useSystemProps ) |
136 | { | |
137 | 2 | resolvedProps.putAll( baseProps ); |
138 | } | |
139 | ||
140 | 4 | return resolvedProps; |
141 | } | |
142 | ||
143 | ||
144 | /** | |
145 | * Retrieves a property value, replacing values like ${token} | |
146 | * using the Properties to look them up. | |
147 | * | |
148 | * It will leave unresolved properties alone, trying for System | |
149 | * properties, and implements reparsing (in the case that | |
150 | * the value of a property contains a key), and will | |
151 | * not loop endlessly on a pair like | |
152 | * test = ${test}. | |
153 | * | |
154 | * @param k | |
155 | * @param p | |
156 | * @return The filtered property value. | |
157 | */ | |
158 | private static String getPropertyValue( String k, Properties p ) | |
159 | { | |
160 | // This can also be done using InterpolationFilterReader, | |
161 | // but it requires reparsing the file over and over until | |
162 | // it doesn't change. | |
163 | ||
164 | 302 | String v = p.getProperty( k ); |
165 | 302 | String ret = ""; |
166 | int idx, idx2; | |
167 | ||
168 | 424 | while ( ( idx = v.indexOf( "${" ) ) >= 0 ) |
169 | { | |
170 | // append prefix to result | |
171 | 134 | ret += v.substring( 0, idx ); |
172 | ||
173 | // strip prefix from original | |
174 | 134 | v = v.substring( idx + 2 ); |
175 | ||
176 | // if no matching } then bail | |
177 | 134 | if ( ( idx2 = v.indexOf( '}' ) ) < 0 ) |
178 | { | |
179 | 12 | break; |
180 | } | |
181 | ||
182 | // strip out the key and resolve it | |
183 | // resolve the key/value for the ${statement} | |
184 | 122 | String nk = v.substring( 0, idx2 ); |
185 | 122 | v = v.substring( idx2 + 1 ); |
186 | 122 | String nv = p.getProperty( nk ); |
187 | ||
188 | // try global environment.. | |
189 | 122 | if ( nv == null && !StringUtils.isEmpty( nk ) ) |
190 | { | |
191 | 62 | nv = System.getProperty( nk ); |
192 | } | |
193 | ||
194 | // if the key cannot be resolved, | |
195 | // leave it alone ( and don't parse again ) | |
196 | // else prefix the original string with the | |
197 | // resolved property ( so it can be parsed further ) | |
198 | // taking recursion into account. | |
199 | 122 | if ( nv == null || nv.equals( k ) || k.equals( nk ) ) |
200 | { | |
201 | 102 | ret += "${" + nk + "}"; |
202 | } | |
203 | else | |
204 | { | |
205 | 20 | v = nv + v; |
206 | } | |
207 | 122 | } |
208 | 302 | return ret + v; |
209 | } | |
210 | } |