001/**
002 * Copyright 2005-2015 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.testtools.common;
017
018import java.io.FileInputStream;
019import java.io.FileNotFoundException;
020import java.io.IOException;
021import java.io.InputStream;
022import java.util.ArrayList;
023import java.util.Enumeration;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Map;
029import java.util.Properties;
030
031/**
032 * <p>
033 * Util Properties methods which have come from refactoring test generation using freemarker, providing overriding of
034 * file properties from System Properties as well as setting file properties as System Properties, as well as turning
035 * numbered properties to a List.
036 * </p>
037 * @author Kuali Rice Team (rice.collab@kuali.org)
038 */
039public class PropertiesUtils {
040
041    /**
042     * <p>
043     * Plain load Properties from the given inputStream.
044     * </p>
045     *
046     * @param inputStream to read properties from
047     * @return Properties read from given Inputstream
048     * @throws IOException
049     */
050    public Properties loadProperties(InputStream inputStream) throws IOException {
051        Properties props = new Properties();
052
053        if(inputStream != null) {
054            props.load(inputStream);
055        }
056
057        return props;
058    }
059
060    /**
061     * <p>
062     * Read Properties from given Inputstream or Resource Loaction if the InputStream is null.
063     * </p><p>
064     * If a FileNotFoundException is thrown opening the InputStream an attempt will be made to read as a resource.
065     * </p>
066     *
067     * @param fileLocation null means use resourceLocation
068     * @param resourceLocation load resource as a stream {@code getClass().getClassLoader().getResourceAsStream(resourceLocation);}
069     * @return Properties read from given Inputstream or Resource Loaction if the InputStream is null
070     * @throws IOException
071     * @deprecated {@see #loadProperties(String)}
072     */
073    @Deprecated
074    public Properties loadProperties(String fileLocation, String resourceLocation) throws IOException {
075        Properties props = null;
076        InputStream in = null;
077        if(fileLocation != null) {
078            try {
079                in = new FileInputStream(fileLocation);
080            } catch (FileNotFoundException fio) {
081                System.out.println(fio.getMessage() + " trying to read as resource.");
082                if (resourceLocation != null) {
083                    System.out.println("Trying to read as " + resourceLocation+ " as a resource.");
084                    in = getClass().getClassLoader().getResourceAsStream(resourceLocation);
085                }
086            }
087        } else {
088            in = getClass().getClassLoader().getResourceAsStream(resourceLocation);
089            if (in == null) {
090                System.out.println("Unable to read " + fileLocation + " as a file or " + resourceLocation + " as a resource stream");
091            }
092        }
093        if(in != null) {
094            props = loadProperties(in);
095            in.close();
096        }
097
098        return props;
099    }
100
101    /**
102     * <p>
103     * Read Properties from given Loaction
104     * </p><p>
105     * If a FileNotFoundException is thrown opening the InputStream an attempt will be made to read as a resource.
106     * </p>
107     *
108     * @param location
109     * @return Properties read from given Inputstream or Resource Loaction if the InputStream is null
110     * @throws IOException
111     */
112    public Properties loadProperties(String location) throws IOException {
113        Properties props = null;
114        InputStream in = null;
115        try {
116            in = new FileInputStream(location);
117        } catch (FileNotFoundException fio) {
118            System.out.println(fio.getMessage() + " trying to read as resource.");
119            in = getClass().getClassLoader().getResourceAsStream(location);
120            if (in == null) {
121                System.out.println("Unable to read " + location + " as a resource stream");
122            }
123        }
124        if(in != null) {
125            props = loadProperties(in);
126            in.close();
127        }
128
129        return props;
130    }
131
132    public Properties loadPropertiesWithSystemAndOverrides(String location) throws IOException {
133        Properties properties =  loadProperties(location);
134        return systemPropertiesAndOverride(properties);
135    }
136
137    /**
138     * <p>
139     * Beware of classloader/timing issues!  Sometimes properties get added to the System properties after the point
140     * you might expect.  Resulting in the System property not really being set for when you expected, such as with
141     * settign statics.  Looks like WebDriverLegacyITBase has this going on with public.remote.url
142     * </p>
143     *
144     * @param location file or resource to load properties from, file is attempted first
145     * @return Properties
146     * @throws IOException
147     */
148    public Properties loadPropertiesWithSystemAndOverridesIntoSystem(String location) throws IOException {
149        Properties properties = loadProperties(location);
150        properties = systemPropertiesAndOverride(properties);
151
152        Iterator propKeys = properties.keySet().iterator();
153        while (propKeys.hasNext()) {
154            String key = (String)propKeys.next();
155            if (System.getProperty(key) == null) {
156                System.setProperty(key, properties.getProperty(key));
157            }
158        }
159        return properties;
160    }
161
162    public Properties loadPropertiesWithSystemOverrides(String location) throws IOException {
163        Properties properties =  loadProperties(location);
164        return systemPropertiesOverride(properties);
165    }
166
167    /**
168     * <p>
169     * Read the properties from an inputStream overridding keys defined as JVM arguments.
170     * </p><p>
171     * {@see #systemPropertiesOverride}
172     * </p>
173     *
174     * @param inputStream to read properties from
175     * @return Properties loaded from inputStream and overridden with JVM arguments
176     * @throws IOException
177     */
178    public  Properties loadPropertiesWithSystemOverrides(InputStream inputStream) throws IOException {
179        Properties props = loadProperties(inputStream);
180        props = systemPropertiesOverride(props);
181        return props;
182    }
183
184    /**
185     * <p>
186     * Read the properties from an inputStream overridding keys defined as JVM arguments and transforming numbered keys
187     * into a list.
188     * </p><p>
189     * {@see #systemPropertiesOverride}
190     * {@see #transformNumberedPropertiesToList}
191     * </p>
192     *
193     * @param inputStream
194     * @return Properties loaded from inputStream, overridden with JVM arguments, and keys ending in numbers transformed to a List
195     * @throws IOException
196     */
197    public Properties loadPropertiesWithSystemOverridesAndNumberedPropertiesToList(InputStream inputStream) throws IOException {
198        Properties props = loadProperties(inputStream);
199        props = systemPropertiesOverride(props);
200        props = transformNumberedPropertiesToList(props);
201        return props;
202    }
203
204    /**
205     * <p>
206     * Given a key that ends in a number, remove the number.
207     * </p>
208     *
209     * @param numberedKey in the form of some.key.number
210     * @return some.key part of some.key.number
211     */
212    public String removeNumber(final String numberedKey) {
213        String unnumberedKey = numberedKey;
214        int firstNumberIndex = unnumberedKey.length() - 1;
215        while (Character.isDigit(unnumberedKey.charAt(firstNumberIndex))) {
216            firstNumberIndex--;
217        }
218        unnumberedKey = unnumberedKey.substring(0, firstNumberIndex + 1);
219
220        return unnumberedKey;
221    }
222
223    public Properties systemPropertiesAndOverride(Properties props) {
224        return systemPropertiesAndOverride(props, null);
225    }
226
227    /**
228     * <p>
229     * Override the given Properties with JVM argument {@code -Dkey=value}.
230     * </p>
231     *
232     * @param props properties to update with System.getProperty overrides.
233     */
234    public Properties systemPropertiesOverride(Properties props) {
235        return systemPropertiesOverride(props, null);
236    }
237
238    /**
239     * <p>
240     * In addition to overriding file properties from System Properties, System properties are added to the returned
241     * Properties.
242     * </p><p>
243     * {@see #systemPropertiesOverride}
244     * </p>
245     *
246     *
247     * @param props Properties with System Properties added and Overriding file properties
248     * @param arg filter System Properties added to Properties by the Property key starting with arg
249     * @return
250     */
251    public Properties systemPropertiesAndOverride(Properties props, String arg) {
252        PropertiesUtils propUtils = new PropertiesUtils();
253        props = propUtils.systemPropertiesOverride(props, arg);
254
255        Iterator iter = System.getProperties().stringPropertyNames().iterator();
256        while (iter.hasNext()) {
257            String key = (String) iter.next();
258            if (arg == null || arg.equals("")) {
259                if (!props.containsValue(key)) {
260                    props.setProperty(key, System.getProperty(key));
261                }
262            } else {
263                if (key.startsWith(arg) && !props.containsValue(key)) {
264                    props.setProperty(key, System.getProperty(key));
265                }
266            }
267        }
268        return props;
269    }
270
271    /**
272     * <p>
273     * Override the given Properties with JVM argument {@code -Darg.key=value}.
274     * </p><p>
275     * -Dkey.propertyname= to override the property value for propertyname.
276     * </p>
277     *
278     * @param props properties to update with System.getProperty overrides.
279     * @param arg optional value that the property names will be appended to.
280     */
281    public Properties systemPropertiesOverride(Properties props, String arg) {
282        Enumeration<?> names = props.propertyNames();
283        Object nameObject;
284        String key;
285        while (names.hasMoreElements()) {
286
287            nameObject = names.nextElement();
288            if (nameObject instanceof String) {
289
290                key = (String)nameObject;
291                if (arg == null || arg.isEmpty()) {
292                    props.setProperty(key, System.getProperty(key, props.getProperty(key)));
293                } else {
294                    props.setProperty(key, System.getProperty(arg + "." + key, props.getProperty(key)));
295                }
296            }
297        }
298        return props;
299    }
300
301    /**
302     * <p>
303     * Transform the given Properties keys which end in numbers to a List placed in a Map with the map key being the unumbered
304     * part of the Properties key with an s appended to it.
305     * </p>
306     *
307     * @param props Properties to have keys ending in
308     */
309    public Properties transformNumberedPropertiesToList(Properties props) {
310        String key = null;
311        String unnumberedKey = null;
312        List<String> keyList = null;
313        List<String> removeKeys = new LinkedList<String>();
314
315        // unnumber keys and place their values in a list
316        Iterator keys = props.keySet().iterator();
317        Map<String, List<String>> keysLists = new HashMap<String, List<String>>();
318        while (keys.hasNext()) {
319            key = (String)keys.next();
320            if (Character.isDigit(key.charAt(key.length()-1))) {
321                unnumberedKey = removeNumber(key);
322                if (keysLists.get(unnumberedKey) == null) {
323                    keyList = new ArrayList<String>();
324                    keyList.add(props.getProperty(key));
325                    keysLists.put(unnumberedKey, keyList);
326                    removeKeys.add(key);
327                } else {
328                    keyList = keysLists.get(unnumberedKey);
329                    keyList.add(props.getProperty(key));
330                    keysLists.put(unnumberedKey, keyList);
331                    removeKeys.add(key);
332                }
333            }
334        }
335
336        // remove keys that where unnumbered
337        Iterator removeKey = removeKeys.iterator();
338        while (removeKey.hasNext()) {
339            key = (String)removeKey.next();
340            props.remove(key);
341        }
342
343        // put new unnumbered key values mapped by unnumber key with an s appended to it.
344        Iterator newKeys = keysLists.keySet().iterator();
345        String newKey = null;
346        while (newKeys.hasNext()) {
347            newKey = (String)newKeys.next();
348            props.put(newKey + "s", keysLists.get(newKey));
349        }
350        return props;
351    }
352}