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 * @version $Id: PropertyUtils.java 1055685 2011-01-05 23:14:08Z dennisl $
37 */
38 public final class PropertyUtils
39 {
40 /**
41 * Private empty constructor to prevent instantiation.
42 */
43 private PropertyUtils()
44 {
45 // prevent instantiation
46 }
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 if ( !propFile.exists() )
64 {
65 throw new FileNotFoundException( propFile.toString() );
66 }
67
68 final Properties fileProps = new Properties();
69 final FileInputStream inStream = new FileInputStream( propFile );
70 try
71 {
72 fileProps.load( inStream );
73 }
74 finally
75 {
76 IOUtil.close( inStream );
77 }
78
79 final Properties combinedProps = new Properties();
80 combinedProps.putAll( baseProps == null ? new Properties() : baseProps );
81 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 for ( Iterator iter = fileProps.keySet().iterator(); iter.hasNext(); )
93 {
94 final String k = (String) iter.next();
95 final String propValue = getPropertyValue( k, combinedProps );
96 fileProps.setProperty( k, propValue );
97 }
98
99 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 final Properties baseProps = new Properties();
116
117 if ( useSystemProps )
118 {
119 baseProps.putAll( System.getProperties() );
120 }
121
122 final Properties resolvedProps = new Properties();
123 try
124 {
125 resolvedProps.putAll( loadPropertyFile( propfile, baseProps ) );
126 }
127 catch ( FileNotFoundException e )
128 {
129 if ( fail )
130 {
131 throw new FileNotFoundException( propfile.toString() );
132 }
133 }
134
135 if ( useSystemProps )
136 {
137 resolvedProps.putAll( baseProps );
138 }
139
140 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 String v = p.getProperty( k );
165 String ret = "";
166 int idx, idx2;
167
168 while ( ( idx = v.indexOf( "${" ) ) >= 0 )
169 {
170 // append prefix to result
171 ret += v.substring( 0, idx );
172
173 // strip prefix from original
174 v = v.substring( idx + 2 );
175
176 // if no matching } then bail
177 if ( ( idx2 = v.indexOf( '}' ) ) < 0 )
178 {
179 break;
180 }
181
182 // strip out the key and resolve it
183 // resolve the key/value for the ${statement}
184 String nk = v.substring( 0, idx2 );
185 v = v.substring( idx2 + 1 );
186 String nv = p.getProperty( nk );
187
188 // try global environment..
189 if ( nv == null && !StringUtils.isEmpty( nk ) )
190 {
191 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 if ( nv == null || nv.equals( k ) || k.equals( nk ) )
200 {
201 ret += "${" + nk + "}";
202 }
203 else
204 {
205 v = nv + v;
206 }
207 }
208 return ret + v;
209 }
210 }