/* * Strongback * Copyright 2015, Strongback and individual contributors by the @authors tag. * See the COPYRIGHT.txt in the distribution for a full listing of individual * contributors. * * Licensed under the MIT License; you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://opensource.org/licenses/MIT * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.strongback.tools.utils; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.HashSet; import java.util.Properties; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Utility methods to make working with .properties files a bit easier * @see java.util.Properties * @author Zach Anderson */ public class PropertiesUtils { /** * Loads the specified properties file into memory * @param file the properties {@link File} to load * @return a {@link Properties} based on the specified {@link File} * @throws FileNotFoundException if the specified {@link File} does not exist or is a directory * @throws IOException if an error occurs while reading the {@link File} */ public static Properties load(File file) throws FileNotFoundException, IOException { Properties props = new Properties(); props.load(new FileReader(file)); return props; } /** * Combines multiple {@link Properties} into a single {@link Properties} without * modifying any of the original {@link Properties}. If the same key is defined in two {@link Properties}, * it will be assigned the value appearing in the latest {@link Properties} in the list. * @param props the {@link Properties} to concatenate * @return a single {@link Properties} containing all of the key value pairs */ public static Properties concat(Properties... props) { Properties out = new Properties(); for(Properties prop : props) { if ( prop != null ) prop.forEach(out::put); } return out; } /** * Replaces any ant style references <code>${property}</code> to another property in a {@link Properties} with the actual value of that property * in depth first order. * @param props the {@link Properties} to modify * @throws InvalidPropertiesException if a referenced property is undefined or is defined in terms of itself, directly * or indirectly */ public static void antify(Properties props) throws InvalidPropertiesException { Set<String> keys = props.stringPropertyNames(); for(String key : keys) { Set<String> v = new HashSet<>(); props.setProperty(key, resolve(key, props, v)); } } private static String resolve(String key, Properties props, Set<String> visted) throws InvalidPropertiesException { if(visted.contains(key)) throw new InvalidPropertiesException(key + " is defined cyclically."); visted.add(key); if(!props.containsKey(key)) { if(System.getProperty(key)!=null) return System.getProperty(key); throw new InvalidPropertiesException(key + " is undefined."); } String value = props.getProperty(key); //One or more of any character preceded by ${ and followed by } but not including them Matcher grabber = Pattern.compile("(?<=\\$\\{).+(?=\\})").matcher(value); Set<String> toResolve = new HashSet<>(); while(grabber.find()) { toResolve.add(grabber.group()); } // No further resolution is needed if(toResolve.isEmpty()) return value; for(String s : toResolve) { String resolution = resolve(s, props, visted); value = value.replace("${"+s+"}", resolution); } // Set the value so it doesn't need to be resolved again later props.setProperty(key, value); return value; } public static class InvalidPropertiesException extends Exception{ private static final long serialVersionUID = 1L; public InvalidPropertiesException(String msg) { super(msg); } } }