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.apache.maven.shared.utils.io.IOUtil; 23 import org.apache.maven.shared.utils.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 { 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 }