001/**
002 * Copyright 2005-2018 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    public Properties loadProperties(String fileLocation, String resourceLocation) throws IOException {
074        Properties props = null;
075        InputStream in = null;
076        if(fileLocation != null) {
077            try {
078                in = new FileInputStream(fileLocation);
079            } catch (FileNotFoundException fio) {
080                System.out.println(fio.getMessage() + " trying to read as resource.");
081                if (resourceLocation != null) {
082                    System.out.println("Trying to read as " + resourceLocation+ " as a resource.");
083                    in = getClass().getClassLoader().getResourceAsStream(resourceLocation);
084                }
085            }
086        } else {
087            in = getClass().getClassLoader().getResourceAsStream(resourceLocation);
088            if (in == null) {
089                System.out.println("Unable to read " + fileLocation + " as a file or " + resourceLocation + " as a resource stream");
090            }
091        }
092        if(in != null) {
093            props = loadProperties(in);
094            in.close();
095        }
096
097        return props;
098    }
099
100    /**
101     * <p>
102     * Read Properties from given Loaction
103     * </p><p>
104     * If a FileNotFoundException is thrown opening the InputStream an attempt will be made to read as a resource.
105     * </p>
106     *
107     * @param location
108     * @return Properties read from given Inputstream or Resource Loaction if the InputStream is null
109     * @throws IOException
110     */
111    public Properties loadProperties(String location) throws IOException {
112        Properties props = null;
113        InputStream in = null;
114        try {
115            in = new FileInputStream(location);
116        } catch (FileNotFoundException fio) {
117            System.out.println(fio.getMessage() + " trying to read as resource.");
118            in = getClass().getClassLoader().getResourceAsStream(location);
119            if (in == null) {
120                System.out.println("Unable to read " + location + " as a resource stream");
121            }
122        }
123        if(in != null) {
124            props = loadProperties(in);
125            in.close();
126        }
127
128        return props;
129    }
130
131    public Properties loadPropertiesWithSystemAndOverrides(String location) throws IOException {
132        Properties properties =  loadProperties(location);
133        return systemPropertiesAndOverride(properties);
134    }
135
136    /**
137     * <p>
138     * Beware of classloader/timing issues!  Sometimes properties get added to the System properties after the point
139     * you might expect.  Resulting in the System property not really being set for when you expected, such as with
140     * settign statics.  Looks like WebDriverLegacyITBase has this going on with public.remote.url
141     * </p>
142     *
143     * @param location file or resource to load properties from, file is attempted first
144     * @return Properties
145     * @throws IOException
146     */
147    public Properties loadPropertiesWithSystemAndOverridesIntoSystem(String location) throws IOException {
148        Properties properties = loadProperties(location);
149        properties = systemPropertiesAndOverride(properties);
150
151        Iterator propKeys = properties.keySet().iterator();
152        while (propKeys.hasNext()) {
153            String key = (String)propKeys.next();
154            if (System.getProperty(key) == null) {
155                System.setProperty(key, properties.getProperty(key));
156            }
157        }
158        return properties;
159    }
160
161    public Properties loadPropertiesWithSystemOverrides(String location) throws IOException {
162        Properties properties =  loadProperties(location);
163        return systemPropertiesOverride(properties);
164    }
165
166    /**
167     * <p>
168     * Read the properties from an inputStream overridding keys defined as JVM arguments.
169     * </p><p>
170     * {@see #systemPropertiesOverride}
171     * </p>
172     *
173     * @param inputStream to read properties from
174     * @return Properties loaded from inputStream and overridden with JVM arguments
175     * @throws IOException
176     */
177    public  Properties loadPropertiesWithSystemOverrides(InputStream inputStream) throws IOException {
178        Properties props = loadProperties(inputStream);
179        props = systemPropertiesOverride(props);
180        return props;
181    }
182
183    /**
184     * <p>
185     * Read the properties from an inputStream overridding keys defined as JVM arguments and transforming numbered keys
186     * into a list.
187     * </p><p>
188     * {@see #systemPropertiesOverride}
189     * {@see #transformNumberedPropertiesToList}
190     * </p>
191     *
192     * @param inputStream
193     * @return Properties loaded from inputStream, overridden with JVM arguments, and keys ending in numbers transformed to a List
194     * @throws IOException
195     */
196    public Properties loadPropertiesWithSystemOverridesAndNumberedPropertiesToList(InputStream inputStream) throws IOException {
197        Properties props = loadProperties(inputStream);
198        props = systemPropertiesOverride(props);
199        props = transformNumberedPropertiesToList(props);
200        return props;
201    }
202
203    /**
204     * <p>
205     * Given a key that ends in a number, remove the number.
206     * </p>
207     *
208     * @param numberedKey in the form of some.key.number
209     * @return some.key part of some.key.number
210     */
211    public String removeNumber(final String numberedKey) {
212        String unnumberedKey = numberedKey;
213        int firstNumberIndex = unnumberedKey.length() - 1;
214        while (Character.isDigit(unnumberedKey.charAt(firstNumberIndex))) {
215            firstNumberIndex--;
216        }
217        unnumberedKey = unnumberedKey.substring(0, firstNumberIndex + 1);
218
219        return unnumberedKey;
220    }
221
222    public Properties systemPropertiesAndOverride(Properties props) {
223        return systemPropertiesAndOverride(props, null);
224    }
225
226    /**
227     * <p>
228     * Override the given Properties with JVM argument {@code -Dkey=value}.
229     * </p>
230     *
231     * @param props properties to update with System.getProperty overrides.
232     */
233    public Properties systemPropertiesOverride(Properties props) {
234        return systemPropertiesOverride(props, null);
235    }
236
237    /**
238     * <p>
239     * In addition to overriding file properties from System Properties, System properties are added to the returned
240     * Properties.
241     * </p><p>
242     * {@see #systemPropertiesOverride}
243     * </p>
244     *
245     *
246     * @param props Properties with System Properties added and Overriding file properties
247     * @param arg filter System Properties added to Properties by the Property key starting with arg
248     * @return
249     */
250    public Properties systemPropertiesAndOverride(Properties props, String arg) {
251        PropertiesUtils propUtils = new PropertiesUtils();
252        props = propUtils.systemPropertiesOverride(props, arg);
253
254        Iterator iter = System.getProperties().stringPropertyNames().iterator();
255        while (iter.hasNext()) {
256            String key = (String) iter.next();
257            if (arg == null || arg.equals("")) {
258                if (!props.contains(key)) {
259                    props.setProperty(key, System.getProperty(key));
260                }
261            } else {
262                if (key.startsWith(arg) && !props.contains(key)) {
263                    props.setProperty(key, System.getProperty(key));
264                }
265            }
266        }
267        return props;
268    }
269
270    /**
271     * <p>
272     * Override the given Properties with JVM argument {@code -Darg.key=value}.
273     * </p><p>
274     * -Dkey.propertyname= to override the property value for propertyname.
275     * </p>
276     *
277     * @param props properties to update with System.getProperty overrides.
278     * @param arg optional value that the property names will be appended to.
279     */
280    public Properties systemPropertiesOverride(Properties props, String arg) {
281        Enumeration<?> names = props.propertyNames();
282        Object nameObject;
283        String key;
284        while (names.hasMoreElements()) {
285
286            nameObject = names.nextElement();
287            if (nameObject instanceof String) {
288
289                key = (String)nameObject;
290                if (arg == null || arg.isEmpty()) {
291                    props.setProperty(key, System.getProperty(key, props.getProperty(key)));
292                } else {
293                    props.setProperty(key, System.getProperty(arg + "." + key, props.getProperty(key)));
294                }
295            }
296        }
297        return props;
298    }
299
300    /**
301     * <p>
302     * Transform the given Properties keys which end in numbers to a List placed in a Map with the map key being the unumbered
303     * part of the Properties key with an s appended to it.
304     * </p>
305     *
306     * @param props Properties to have keys ending in
307     */
308    public Properties transformNumberedPropertiesToList(Properties props) {
309        String key = null;
310        String unnumberedKey = null;
311        List<String> keyList = null;
312        List<String> removeKeys = new LinkedList<String>();
313
314        // unnumber keys and place their values in a list
315        Iterator keys = props.keySet().iterator();
316        Map<String, List<String>> keysLists = new HashMap<String, List<String>>();
317        while (keys.hasNext()) {
318            key = (String)keys.next();
319            if (Character.isDigit(key.charAt(key.length()-1))) {
320                unnumberedKey = removeNumber(key);
321                if (keysLists.get(unnumberedKey) == null) {
322                    keyList = new ArrayList<String>();
323                    keyList.add(props.getProperty(key));
324                    keysLists.put(unnumberedKey, keyList);
325                    removeKeys.add(key);
326                } else {
327                    keyList = keysLists.get(unnumberedKey);
328                    keyList.add(props.getProperty(key));
329                    keysLists.put(unnumberedKey, keyList);
330                    removeKeys.add(key);
331                }
332            }
333        }
334
335        // remove keys that where unnumbered
336        Iterator removeKey = removeKeys.iterator();
337        while (removeKey.hasNext()) {
338            key = (String)removeKey.next();
339            props.remove(key);
340        }
341
342        // put new unnumbered key values mapped by unnumber key with an s appended to it.
343        Iterator newKeys = keysLists.keySet().iterator();
344        String newKey = null;
345        while (newKeys.hasNext()) {
346            newKey = (String)newKeys.next();
347            props.put(newKey + "s", keysLists.get(newKey));
348        }
349        return props;
350    }
351}