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 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 }