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