/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.core.util; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import org.teiid.core.BundleUtil; import org.teiid.core.CorePlugin; import org.teiid.core.TeiidRuntimeException; /** * Static utility methods for common tasks having to do with * java.util.Properties. */ public final class PropertiesUtils { public static class InvalidPropertyException extends TeiidRuntimeException { private static final long serialVersionUID = 1586068295007497776L; public InvalidPropertyException(BundleUtil.Event event, String propertyName, Object value, Class<?> expectedType, Throwable cause) { super(event, cause, CorePlugin.Util.getString("InvalidPropertyException.message", propertyName, value, expectedType.getSimpleName())); //$NON-NLS-1$ } } /** * Performs a correct deep clone of the properties object by capturing * all properties in the default(s) and placing them directly into the * new Properties object. If the input is an instance of * <code>UnmodifiableProperties</code>, this method returns an * <code>UnmodifiableProperties</code> instance around a new (flattened) * copy of the underlying Properties object. */ public static Properties clone( Properties props ) { return clone(props, null, false); } /** * Performs a correct deep clone of the properties object by capturing * all properties in the default(s) and placing them directly into the * new Properties object. If the input is an instance of * <code>UnmodifiableProperties</code>, this method returns an * <code>UnmodifiableProperties</code> instance around a new (flattened) * copy of the underlying Properties object. */ public static Properties clone( Properties props, Properties defaults, boolean deepClone ) { Properties result = null; if ( defaults != null ) { if ( deepClone ) { defaults = clone(defaults); } result = new Properties(defaults); } else { result = new Properties(); } putAll(result, props); return result; } /** * This method implements a 'compareTo' logic for two Properties objects, * equivalent to calling <code>p1.compareTo(p2)</code> if the * {@link java.util.Properties Properties} class implemented * {@link java.lang.Comparable Comparable} (which it does not). * @param p1 the first Properties instance to compare; may be null * @param p2 the second Properties instance to compare; may be null * @return a negative integer, zero, or a positive integer as <code>p1</code> * is less than, equal to, or greater than <code>p2</code>, respectively. */ public static int compare( Properties p1, Properties p2 ) { if ( p1 != null ) { if ( p2 == null ) { return 1; } } else { if ( p2 != null ) { return -1; } return 0; } // Compare the number of property values ... int diff = p1.size() - p2.size(); if ( diff != 0 ) { return diff; } // Iterate through the properties and compare values ... Map.Entry entry = null; Object p1Value = null; Object p2Value = null; Iterator iter = p1.entrySet().iterator(); while ( iter.hasNext() ) { entry = (Map.Entry) iter.next(); p1Value = entry.getValue(); p2Value = p2.get(entry.getKey()); if ( p1Value != null ) { if ( p2Value == null ) { return 1; } if ( p1Value instanceof Comparable ) { diff = ((Comparable)p1Value).compareTo(p2Value); } else { diff = p1Value.toString().compareTo(p2Value.toString()); } if ( diff != 0 ) { return diff; } } else { if ( p2Value != null ) { return -1; } } } return 0; } /** * <p>This method is intended to replace the use of the <code>putAll</code> * method of <code>Properties</code> inherited from <code>java.util.Hashtable</code>. * The problem with that method is that, since it is inherited from * <code>Hashtable</code>, <i>default</i> properties are lost. * </p> * <p>For example, the following code * <pre><code> * Properties a; * Properties b; * //initialize ... * a.putAll(b); * </code></pre> * will fail <i>if</i> <code>b</code> had been constructed with a default * <code>Properties</code> object. Those defaults would be lost and * not added to <code>a</code>.</p> * * <p>The above code could be correctly performed with this method, * like this: * <pre><code> * Properties a; * Properties b; * //initialize ... * PropertiesUtils.putAll(a,b); * </code></pre> * In the above example, <code>a</code> is modified - properties are added to * it (note that if <code>a</code> has defaults they will remain unaffected.) * The properties from <code>b</code>, <i>including defaults</i>, will be * added to <code>a</code> using its <code>setProperty</code> method - * these new properties will overwrite any pre-existing ones of the same * name. * </p> * * @param addToThis This Properties object is modified; the properties * of the other parameter are added to this. The added property values * will replace any current property values of the same names. * @param withThese The properties (including defaults) of this * object are added to the "addToThis" parameter. */ public static void putAll(Properties addToThis, Properties withThese) { if ( withThese != null && addToThis != null ) { Enumeration enumeration = withThese.propertyNames(); while ( enumeration.hasMoreElements() ) { String propName = (String) enumeration.nextElement(); Object propValue = withThese.get(propName); if ( propValue == null ) { //defaults can only be retrieved as strings propValue = withThese.getProperty(propName); } if ( propValue != null ) { addToThis.put(propName, propValue); } } } } public static void setOverrideProperies(Properties base, Properties override) { Enumeration overrideEnum = override.propertyNames(); while (overrideEnum.hasMoreElements()) { String key = (String)overrideEnum.nextElement(); String value = base.getProperty(key); String overRideValue = override.getProperty(key); if (value != null && !value.equals(overRideValue)) { base.setProperty(key, overRideValue); } } } public static int getIntProperty(Properties props, String propName, int defaultValue) throws InvalidPropertyException { String stringVal = props.getProperty(propName); if(stringVal == null) { return defaultValue; } stringVal = stringVal.trim(); if (stringVal.length() == 0) { return defaultValue; } try { return Integer.parseInt(stringVal); } catch(NumberFormatException e) { throw new InvalidPropertyException(CorePlugin.Event.TEIID10037, propName, stringVal, Integer.class, e); } } public static long getLongProperty(Properties props, String propName, long defaultValue) { String stringVal = props.getProperty(propName); if(stringVal == null) { return defaultValue; } stringVal = stringVal.trim(); if (stringVal.length() == 0) { return defaultValue; } try { return Long.parseLong(stringVal); } catch(NumberFormatException e) { throw new InvalidPropertyException( CorePlugin.Event.TEIID10038, propName, stringVal, Long.class, e); } } public static float getFloatProperty(Properties props, String propName, float defaultValue) { String stringVal = props.getProperty(propName); if(stringVal == null) { return defaultValue; } stringVal = stringVal.trim(); if (stringVal.length() == 0) { return defaultValue; } try { return Float.parseFloat(stringVal); } catch(NumberFormatException e) { throw new InvalidPropertyException(CorePlugin.Event.TEIID10039, propName, stringVal, Float.class, e); } } public static double getDoubleProperty(Properties props, String propName, double defaultValue) { String stringVal = props.getProperty(propName); if(stringVal == null) { return defaultValue; } stringVal = stringVal.trim(); if (stringVal.length() == 0) { return defaultValue; } try { return Double.parseDouble(stringVal); } catch(NumberFormatException e) { throw new InvalidPropertyException(CorePlugin.Event.TEIID10040, propName, stringVal, Double.class, e); } } public static boolean getBooleanProperty(Properties props, String propName, boolean defaultValue) { String stringVal = props.getProperty(propName); if(stringVal == null) { return defaultValue; } stringVal = stringVal.trim(); if (stringVal.length() == 0) { return defaultValue; } try { return Boolean.valueOf(stringVal); } catch(NumberFormatException e) { throw new InvalidPropertyException(CorePlugin.Event.TEIID10041, propName, stringVal, Float.class, e); } } /** * Read the header part of a properties file into a String. * @param fileName * @return * @throws IOException * @since 4.3 */ public static String loadHeader(String fileName) throws IOException { FileReader fr = null; BufferedReader br = null; try { fr = new FileReader(fileName); br = new BufferedReader(fr); String header = br.readLine(); if (header != null && header.indexOf('#') == 0) { header = header.substring(1); } return header; } finally { if (br != null) { br.close(); } if (fr != null) { fr.close(); } } } public static Properties load(String fileName) throws IOException { InputStream is = null; try { Properties props = new Properties(); is = new FileInputStream(fileName); props.load(is); return props; } finally { if (is != null) { is.close(); } } } public static Properties loadFromURL(URL url) throws MalformedURLException, IOException { Properties result = new Properties(); InputStream is = null; try { is = url.openStream(); result.load(is); } finally { if (is != null) { is.close(); } } return result; } public static Properties loadAsResource(Class clazz, String resourceName) throws IOException { InputStream is = null; Properties configProps = new Properties(); try { is = clazz.getResourceAsStream(resourceName); ArgCheck.isNotNull(is); if (is != null) { configProps.load(is); } } finally { if (is != null) { try { is.close(); } catch (Exception ce) { } } } return configProps; } public static Properties sort(Properties props) { List names = new ArrayList(); Enumeration enumeration = props.propertyNames(); while ( enumeration.hasMoreElements() ) { String name = (String) enumeration.nextElement(); names.add(name); } Collections.sort(names); Properties newProps = new Properties(); Iterator iter = names.iterator(); while ( iter.hasNext() ) { String name = (String) iter.next(); String propValue = props.getProperty(name); if ( propValue != null ) { newProps.setProperty(name, propValue); } } return newProps; } /** * Write the specified properties to the specified file, * with the specified header. * Results may not be sorted. * @param fileName * @param props * @param header * @throws IOException * @since 4.3 */ public static void print(String fileName, Properties props, String header) throws IOException { FileOutputStream stream = null; try { stream = new FileOutputStream(fileName); props.store(stream, header); stream.flush(); } finally { try { if (stream != null) { stream.close(); } } catch (Exception e) { } } } /** * Write the specified properties to the specified file, * with the specified header. * Results are sorted by property name. */ public static void print( String fileName, Properties props ) throws IOException { FileOutputStream stream = null; PrintStream writer = null; try { stream = new FileOutputStream(fileName); writer = new PrintStream(stream); List names = new ArrayList(); Enumeration enumeration = props.propertyNames(); while ( enumeration.hasMoreElements() ) { String name = (String) enumeration.nextElement(); names.add(name); } Collections.sort(names); StringBuffer sb; for (Iterator nIt=names.iterator(); nIt.hasNext(); ) { String name = (String) nIt.next(); String value = props.getProperty(name); sb = new StringBuffer(); sb.append(name); sb.append("="); //$NON-NLS-1$ sb.append(value); writer.println(sb.toString()); } writer.flush(); } finally { try { if (writer != null) { writer.close(); } } catch (Exception e){ } try { if (stream != null) { stream.close(); } } catch (Exception e){ } } } public static void print( java.io.PrintStream stream, Properties props ) { if (props != null) { Collection sorted = sortPropertiesForPrinting(props); for (Iterator it=sorted.iterator(); it.hasNext(); ) { String value = (String) it.next(); stream.println(value); } } } private static final String NEWLINE = "\n"; //$NON-NLS-1$ public static String prettyPrint( Properties props ) { if (props != null) { Collection sorted = sortPropertiesForPrinting(props); StringBuffer outBuf = new StringBuffer(); for (Iterator it=sorted.iterator(); it.hasNext(); ) { String value = (String) it.next(); outBuf.append(value); outBuf.append(NEWLINE); } return outBuf.toString(); } return ""; //$NON-NLS-1$ } /** * Sorts the properties and returns a collection of entries * where each entry can be printed. Each entry will print in the * format of: Property: <code>name</code> = <code>value</code> */ private static final String APREFIX = "Property '"; //$NON-NLS-1$ private static final String AEQUAL = "'='"; //$NON-NLS-1$ private static final String ASUFFIX = "'"; //$NON-NLS-1$ private static final String TAB = "\t"; //$NON-NLS-1$ public static Collection sortPropertiesForPrinting(Properties props) { Collection sortedProps = new ArrayList(props.size()); List names = new ArrayList(); Enumeration enumeration = props.propertyNames(); while ( enumeration.hasMoreElements() ) { String name = (String) enumeration.nextElement(); names.add(name); } Collections.sort(names); StringBuffer sb; for (Iterator nIt=names.iterator(); nIt.hasNext(); ) { String name = (String) nIt.next(); String value = null; if (PasswordMaskUtil.doesNameEndWithPasswordSuffix(name)){ value = PasswordMaskUtil.MASK_STRING; } else { value = props.getProperty(name); value= saveConvert(value, false); } name = saveConvert(name, true); sb = new StringBuffer(APREFIX); sb.append(name); sb.append(TAB); sb.append(AEQUAL); sb.append(value); sb.append(ASUFFIX); // sortedProps.add(APREFIX + name + TAB + AEQUAL + value + ASUFFIX); sortedProps.add(sb.toString()); } return sortedProps; } // private static final String keyValueSeparators = "=: \t\r\n\f"; // private static final String strictKeyValueSeparators = "=:"; private static final String specialSaveChars = "=: \t\r\n\f#!"; //$NON-NLS-1$ // private static final String whiteSpaceChars = " \t\r\n\f"; /* * Converts unicodes to encoded \uxxxx * and writes out any of the characters in specialSaveChars * with a preceding slash */ public static String saveConvert(String theString, boolean escapeSpace) { if ( theString == null ) { return ""; //$NON-NLS-1$ } int len = theString.length(); StringBuffer outBuffer = new StringBuffer(len*2); for(int x=0; x<len; x++) { char aChar = theString.charAt(x); switch(aChar) { case ' ': if (x == 0 || escapeSpace) outBuffer.append('\\'); outBuffer.append(' '); break; case '\\':outBuffer.append('\\'); outBuffer.append('\\'); break; case '\t':outBuffer.append('\\'); outBuffer.append('t'); break; case '\n':outBuffer.append('\\'); outBuffer.append('n'); break; case '\r':outBuffer.append('\\'); outBuffer.append('r'); break; case '\f':outBuffer.append('\\'); outBuffer.append('f'); break; default: if ((aChar < 0x0020) || (aChar > 0x007e)) { outBuffer.append('\\'); outBuffer.append('u'); outBuffer.append(toHex((aChar >> 12) & 0xF)); outBuffer.append(toHex((aChar >> 8) & 0xF)); outBuffer.append(toHex((aChar >> 4) & 0xF)); outBuffer.append(toHex( aChar & 0xF)); } else { if (specialSaveChars.indexOf(aChar) != -1) outBuffer.append('\\'); outBuffer.append(aChar); } } } return outBuffer.toString(); } /** * Convert a nibble to a hex character * @param nibble the nibble to convert. */ public static char toHex(int nibble) { return hexDigit[(nibble & 0xF)]; } /** A table of hex digits */ private static final char[] hexDigit = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; public static String toHex(byte[] bytes) { StringBuilder sb = new StringBuilder(bytes.length * 2); for (byte b : bytes) { sb.append(toHex(b >>> 4)); sb.append(toHex(b)); } return sb.toString(); } public static void toHex(StringBuilder sb, InputStream is) throws IOException { int i = 0; while ((i = is.read()) != -1) { byte b = (byte)i; sb.append(toHex(b >>> 4)); sb.append(toHex(b)); } } public static final void copyProperty(Properties srcProperties, String srcPropName, Properties tgtProperties, String tgtPropName) { if(srcProperties == null || srcPropName == null || tgtProperties == null || tgtPropName == null) { return; } String value = srcProperties.getProperty(srcPropName); if(value != null) { tgtProperties.setProperty(tgtPropName, value); } } /** * The specialty of nested properties is, in a given property file * there can be values with pattern like "${...}" * <code> * key1=value1 * key2=${key1}/value2 * </code> * where the value of the <code>key2</code> should resolve to <code>value1/value2</code> * also if the property in the pattern <code>${..}</code> is not found in the loaded * properties, an exception is thrown. Multiple nesting is OK, however recursive nested is not supported. * @param original - Original properties to be resolved * @return resolved properties object. * @since 4.4 */ public static Properties resolveNestedProperties(Properties original) { for(Enumeration e = original.propertyNames(); e.hasMoreElements();) { String key = (String)e.nextElement(); String value = original.getProperty(key); // this will take care of the if there are any non-string properties, // no nesting allowed on these. if (value == null) { continue; } boolean matched = true; boolean modified = false; while(matched) { // now match the pattern, then extract and find the value int start = value.indexOf("${"); //$NON-NLS-1$ int end = start; if (start != -1) { end = value.indexOf('}', start); } matched = ((start != -1) && (end != -1)); if (matched) { String nestedkey = value.substring(start+2, end); String nestedvalue = original.getProperty(nestedkey); // in cases where the key and the nestedkey are the same, this has to be bypassed // because it will cause an infinite loop, and because there will be no value // for the nestedkey that doesnt contain ${..} in the value if (key.equals(nestedkey)) { matched = false; } else { // this will handle case where we did not resolve, mark it blank if (nestedvalue == null) { throw new TeiidRuntimeException(CorePlugin.Event.TEIID10042, CorePlugin.Util.gs(CorePlugin.Event.TEIID10042, nestedkey)); } value = value.substring(0,start)+nestedvalue+value.substring(end+1); modified = true; } } } if(modified) { original.setProperty(key, value); } } return original; } public static void setBeanProperties(Object bean, Properties props, String prefix) { setBeanProperties(bean, props, prefix, false); } public static void setBeanProperties(Object bean, Properties props, String prefix, boolean caseSensitive) { // Move all prop names to lower case so we can use reflection to get // method names and look them up in the connection props. Map<?, ?> map = props; if (!caseSensitive) { map = caseInsensitiveProps(props); } final Method[] methods = bean.getClass().getMethods(); for (int i = 0; i < methods.length; i++) { final Method method = methods[i]; final String methodName = method.getName(); // If setter ... if (! methodName.startsWith("set") || method.getParameterTypes().length != 1 ) { //$NON-NLS-1$ continue; } // Get the property name String propertyName = methodName.substring(3); // remove the "set" if (prefix != null) { if (caseSensitive) { propertyName = prefix + "." + Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1, propertyName.length()); //$NON-NLS-1$ } else { propertyName = prefix + "." + propertyName; //$NON-NLS-1$ } } Object propertyValue = map.get(propertyName); if (propertyValue != null || map.containsKey(propertyName)) { setProperty(bean, propertyValue, method, propertyName); } } } public static void setBeanProperty(Object bean, String name, Object value) { final Method[] methods = bean.getClass().getMethods(); for (int i = 0; i < methods.length; i++) { final Method method = methods[i]; final String methodName = method.getName(); // If setter ... if (! methodName.startsWith("set") || method.getParameterTypes().length != 1 || !StringUtil.endsWithIgnoreCase(methodName, name)) { //$NON-NLS-1$ continue; } // Get the property name final String propertyName = methodName.substring(3); // remove the "set" setProperty(bean, value, method, propertyName); } } private static Class<?> setProperty(Object bean, Object value, final Method method, final String propertyName) { final Class<?> argType = method.getParameterTypes()[0]; try { Object[] params = new Object[] {value}; if (value != null && !argType.isAssignableFrom(value.getClass())) { params = new Object[] {StringUtil.valueOf(value.toString(), argType)}; } method.invoke(bean, params); } catch (Throwable e) { throw new InvalidPropertyException(CorePlugin.Event.TEIID10044, propertyName, value, argType, e); } return argType; } private static TreeMap<String, String> caseInsensitiveProps(final Properties connectionProps) { final TreeMap<String, String> caseInsensitive = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); final Enumeration<?> itr = connectionProps.propertyNames(); while ( itr.hasMoreElements() ) { final String name = (String) itr.nextElement(); String propValue = connectionProps.getProperty(name); if (propValue != null || connectionProps.containsKey(name)) { caseInsensitive.put(name, propValue); } } return caseInsensitive; } }