Added: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/ExtProperties.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/ExtProperties.java?rev=1753054&view=auto ============================================================================== --- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/ExtProperties.java (added) +++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/ExtProperties.java Sun Jul 17 10:46:07 2016 @@ -0,0 +1,1725 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.apache.velocity.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.security.AccessController; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * This class extends normal Java properties by adding the possibility + * to use the same key many times concatenating the value strings + * instead of overwriting them. + * <p> + * <b>Please consider using the <code>PropertiesConfiguration</code> class in + * Commons-Configuration as soon as it is released.</b> + * <p> + * The Extended Properties syntax is explained here: + * + * <ul> + * <li> + * Each property has the syntax <code>key = value</code> + * </li> + * <li> + * The <i>key</i> may use any character but the equal sign '='. + * </li> + * <li> + * <i>value</i> may be separated on different lines if a backslash + * is placed at the end of the line that continues below. + * </li> + * <li> + * If <i>value</i> is a list of strings, each token is separated + * by a comma ','. + * </li> + * <li> + * Commas in each token are escaped placing a backslash right before + * the comma. + * </li> + * <li> + * Backslashes are escaped by using two consecutive backslashes i.e. \\ + * </li> + * <li> + * If a <i>key</i> is used more than once, the values are appended + * as if they were on the same line separated with commas. + * </li> + * <li> + * Blank lines and lines starting with character '#' are skipped. + * </li> + * <li> + * If a property is named "include" (or whatever is defined by + * setInclude() and getInclude() and the value of that property is + * the full path to a file on disk, that file will be included into + * the ConfigurationsRepository. You can also pull in files relative + * to the parent configuration file. So if you have something + * like the following: + * + * include = additional.properties + * + * Then "additional.properties" is expected to be in the same + * directory as the parent configuration file. + * + * Duplicate name values will be replaced, so be careful. + * + * </li> + * </ul> + * + * <p>Here is an example of a valid extended properties file: + * + * <p><pre> + * # lines starting with # are comments + * + * # This is the simplest property + * key = value + * + * # A long property may be separated on multiple lines + * longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ + * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + * + * # This is a property with many tokens + * tokens_on_a_line = first token, second token + * + * # This sequence generates exactly the same result + * tokens_on_multiple_lines = first token + * tokens_on_multiple_lines = second token + * + * # commas may be escaped in tokens + * commas.escaped = Hi\, what'up? + * </pre> + * + * <p><b>NOTE</b>: this class has <b>not</b> been written for + * performance nor low memory usage. In fact, it's way slower than it + * could be and generates too much memory garbage. But since + * performance is not an issue during intialization (and there is not + * much time to improve it), I wrote it this way. If you don't like + * it, go ahead and tune it up! + * + * This class is a clone of org.apache.commons.collections.ExtendedProperties + * (which has been removed from commons-collections-4.0) + * + * @since 2.0 + * @version $Revision: $ + * @version $Id: ExtProperties.java$ + * + * @author <a href="mailto:[email protected]">Stefano Mazzocchi</a> + * @author <a href="mailto:[email protected]">Jon S. Stevens</a> + * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a> + * @author <a href="mailto:[email protected]">Jason van Zyl</a> + * @author <a href="mailto:[email protected]">Geir Magnusson Jr.</a> + * @author <a href="mailto:[email protected]">Leon Messerschmidt</a> + * @author <a href="mailto:[email protected]">Kent Johnson</a> + * @author <a href="mailto:[email protected]">Daniel Rall</a> + * @author <a href="mailto:[email protected]">Ilkka Priha</a> + * @author Janek Bogucki + * @author Mohan Kishore + * @author Stephen Colebourne + * @author Shinobu Kawai + * @author <a href="mailto:[email protected]">Henning P. Schmiedehausen</a> + * @author <a href="mailto:[email protected]">Claude Brisson</a> + */ +public class ExtProperties extends Hashtable<String,Object> { + + /** + * Default configurations repository. + */ + private ExtProperties defaults; + + /** + * The file connected to this repository (holding comments and + * such). + * + * @serial + */ + protected String file; + + /** + * Base path of the configuration file used to create + * this ExtProperties object. + */ + protected String basePath; + + /** + * File separator. + */ + protected String fileSeparator; + { + try { + fileSeparator = (String) AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return System.getProperty("file.separator"); + } + }); + } catch (SecurityException ex) { + fileSeparator = File.separator; + } + } + + /** + * Has this configuration been initialized. + */ + protected boolean isInitialized = false; + + /** + * This is the name of the property that can point to other + * properties file for including other properties files. + */ + protected static String include = "include"; + + /** + * These are the keys in the order they listed + * in the configuration file. This is useful when + * you wish to perform operations with configuration + * information in a particular order. + */ + protected ArrayList keysAsListed = new ArrayList(); + + protected final static String START_TOKEN="${"; + protected final static String END_TOKEN="}"; + + + /** + * Interpolate key names to handle ${key} stuff + * + * @param base string to interpolate + * @return returns the key name with the ${key} substituted + */ + protected String interpolate(String base) { + // COPIED from [configuration] 2003-12-29 + return (interpolateHelper(base, null)); + } + + /** + * Recursive handler for multiple levels of interpolation. + * + * When called the first time, priorVariables should be null. + * + * @param base string with the ${key} variables + * @param priorVariables serves two purposes: to allow checking for + * loops, and creating a meaningful exception message should a loop + * occur. It's 0'th element will be set to the value of base from + * the first call. All subsequent interpolated variables are added + * afterward. + * + * @return the string with the interpolation taken care of + */ + protected String interpolateHelper(String base, List priorVariables) { + // COPIED from [configuration] 2003-12-29 + if (base == null) { + return null; + } + + // on the first call initialize priorVariables + // and add base as the first element + if (priorVariables == null) { + priorVariables = new ArrayList(); + priorVariables.add(base); + } + + int begin = -1; + int end = -1; + int prec = 0 - END_TOKEN.length(); + String variable = null; + StringBuffer result = new StringBuffer(); + + // FIXME: we should probably allow the escaping of the start token + while (((begin = base.indexOf(START_TOKEN, prec + END_TOKEN.length())) > -1) + && ((end = base.indexOf(END_TOKEN, begin)) > -1)) { + result.append(base.substring(prec + END_TOKEN.length(), begin)); + variable = base.substring(begin + START_TOKEN.length(), end); + + // if we've got a loop, create a useful exception message and throw + if (priorVariables.contains(variable)) { + String initialBase = priorVariables.remove(0).toString(); + priorVariables.add(variable); + StringBuffer priorVariableSb = new StringBuffer(); + + // create a nice trace of interpolated variables like so: + // var1->var2->var3 + for (Iterator it = priorVariables.iterator(); it.hasNext();) { + priorVariableSb.append(it.next()); + if (it.hasNext()) { + priorVariableSb.append("->"); + } + } + + throw new IllegalStateException( + "infinite loop in property interpolation of " + initialBase + ": " + priorVariableSb.toString()); + } + // otherwise, add this variable to the interpolation list. + else { + priorVariables.add(variable); + } + + //QUESTION: getProperty or getPropertyDirect + Object value = getProperty(variable); + if (value != null) { + result.append(interpolateHelper(value.toString(), priorVariables)); + + // pop the interpolated variable off the stack + // this maintains priorVariables correctness for + // properties with multiple interpolations, e.g. + // prop.name=${some.other.prop1}/blahblah/${some.other.prop2} + priorVariables.remove(priorVariables.size() - 1); + } else if (defaults != null && defaults.getString(variable, null) != null) { + result.append(defaults.getString(variable)); + } else { + //variable not defined - so put it back in the value + result.append(START_TOKEN).append(variable).append(END_TOKEN); + } + prec = end; + } + result.append(base.substring(prec + END_TOKEN.length(), base.length())); + + return result.toString(); + } + + /** + * Inserts a backslash before every comma and backslash. + */ + private static String escape(String s) { + StringBuffer buf = new StringBuffer(s); + for (int i = 0; i < buf.length(); i++) { + char c = buf.charAt(i); + if (c == ',' || c == '\\') { + buf.insert(i, '\\'); + i++; + } + } + return buf.toString(); + } + + /** + * Removes a backslash from every pair of backslashes. + */ + private static String unescape(String s) { + StringBuffer buf = new StringBuffer(s); + for (int i = 0; i < buf.length() - 1; i++) { + char c1 = buf.charAt(i); + char c2 = buf.charAt(i + 1); + if (c1 == '\\' && c2 == '\\') { + buf.deleteCharAt(i); + } + } + return buf.toString(); + } + + /** + * Counts the number of successive times 'ch' appears in the + * 'line' before the position indicated by the 'index'. + */ + private static int countPreceding(String line, int index, char ch) { + int i; + for (i = index - 1; i >= 0; i--) { + if (line.charAt(i) != ch) { + break; + } + } + return index - 1 - i; + } + + /** + * Checks if the line ends with odd number of backslashes + */ + private static boolean endsWithSlash(String line) { + if (!line.endsWith("\\")) { + return false; + } + return (countPreceding(line, line.length() - 1, '\\') % 2 == 0); + } + + /** + * This class is used to read properties lines. These lines do + * not terminate with new-line chars but rather when there is no + * backslash sign a the end of the line. This is used to + * concatenate multiple lines for readability. + */ + static class PropertiesReader extends LineNumberReader { + /** + * Constructor. + * + * @param reader A Reader. + */ + public PropertiesReader(Reader reader) { + super(reader); + } + + /** + * Read a property. + * + * @return a String property + * @throws IOException if there is difficulty reading the source. + */ + public String readProperty() throws IOException { + StringBuffer buffer = new StringBuffer(); + String line = readLine(); + while (line != null) { + line = line.trim(); + if ((line.length() != 0) && (line.charAt(0) != '#')) { + if (endsWithSlash(line)) { + line = line.substring(0, line.length() - 1); + buffer.append(line); + } else { + buffer.append(line); + return buffer.toString(); // normal method end + } + } + line = readLine(); + } + return null; // EOF reached + } + } + + /** + * This class divides into tokens a property value. Token + * separator is "," but commas into the property value are escaped + * using the backslash in front. + */ + static class PropertiesTokenizer extends StringTokenizer { + /** + * The property delimiter used while parsing (a comma). + */ + static final String DELIMITER = ","; + + /** + * Constructor. + * + * @param string A String. + */ + public PropertiesTokenizer(String string) { + super(string, DELIMITER); + } + + /** + * Check whether the object has more tokens. + * + * @return True if the object has more tokens. + */ + public boolean hasMoreTokens() { + return super.hasMoreTokens(); + } + + /** + * Get next token. + * + * @return A String. + */ + public String nextToken() { + StringBuffer buffer = new StringBuffer(); + + while (hasMoreTokens()) { + String token = super.nextToken(); + if (endsWithSlash(token)) { + buffer.append(token.substring(0, token.length() - 1)); + buffer.append(DELIMITER); + } else { + buffer.append(token); + break; + } + } + + return buffer.toString().trim(); + } + } + + /** + * Creates an empty extended properties object. + */ + public ExtProperties() { + super(); + } + + /** + * Creates and loads the extended properties from the specified file. + * + * @param file the filename to load + * @throws IOException if a file error occurs + */ + public ExtProperties(String file) throws IOException { + this(file, null); + } + + /** + * Creates and loads the extended properties from the specified file. + * + * @param file the filename to load + * @param defaultFile a second filename to load default values from + * @throws IOException if a file error occurs + */ + public ExtProperties(String file, String defaultFile) throws IOException { + this.file = file; + + basePath = new File(file).getAbsolutePath(); + basePath = basePath.substring(0, basePath.lastIndexOf(fileSeparator) + 1); + + FileInputStream in = null; + try { + in = new FileInputStream(file); + this.load(in); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException ex) {} + } + + if (defaultFile != null) { + defaults = new ExtProperties(defaultFile); + } + } + + /** + * Indicate to client code whether property + * resources have been initialized or not. + */ + public boolean isInitialized() { + return isInitialized; + } + + /** + * Gets the property value for including other properties files. + * By default it is "include". + * + * @return A String. + */ + public String getInclude() { + return include; + } + + /** + * Sets the property value for including other properties files. + * By default it is "include". + * + * @param inc A String. + */ + public void setInclude(String inc) { + include = inc; + } + + /** + * Load the properties from the given input stream. + * + * @param input the InputStream to load from + * @throws IOException if an IO error occurs + */ + public void load(InputStream input) throws IOException { + load(input, null); + } + + /** + * Load the properties from the given input stream + * and using the specified encoding. + * + * @param input the InputStream to load from + * @param enc the encoding to use + * @throws IOException if an IO error occurs + */ + public synchronized void load(InputStream input, String enc) throws IOException { + PropertiesReader reader = null; + if (enc != null) { + try { + reader = new PropertiesReader(new InputStreamReader(input, enc)); + + } catch (UnsupportedEncodingException ex) { + // Another try coming up.... + } + } + + if (reader == null) { + try { + reader = new PropertiesReader(new InputStreamReader(input, "8859_1")); + + } catch (UnsupportedEncodingException ex) { + // ISO8859-1 support is required on java platforms but.... + // If it's not supported, use the system default encoding + reader = new PropertiesReader(new InputStreamReader(input)); + } + } + + try { + while (true) { + String line = reader.readProperty(); + if (line == null) { + return; // EOF + } + int equalSign = line.indexOf('='); + + if (equalSign > 0) { + String key = line.substring(0, equalSign).trim(); + String value = line.substring(equalSign + 1).trim(); + + // Configure produces lines like this ... just ignore them + if ("".equals(value)) { + continue; + } + + if (getInclude() != null && key.equalsIgnoreCase(getInclude())) { + // Recursively load properties files. + File file = null; + + if (value.startsWith(fileSeparator)) { + // We have an absolute path so we'll use this + file = new File(value); + + } else { + // We have a relative path, and we have two + // possible forms here. If we have the "./" form + // then just strip that off first before continuing. + if (value.startsWith("." + fileSeparator)) { + value = value.substring(2); + } + + file = new File(basePath + value); + } + + if (file != null && file.exists() && file.canRead()) { + load(new FileInputStream(file)); + } + } else { + addProperty(key, value); + } + } + } + } finally { + // Loading is initializing + isInitialized = true; + } + } + + /** + * Gets a property from the configuration. + * + * @param key property to retrieve + * @return value as object. Will return user value if exists, + * if not then default value if exists, otherwise null + */ + public Object getProperty(String key) { + // first, try to get from the 'user value' store + Object obj = this.get(key); + + if (obj == null) { + // if there isn't a value there, get it from the + // defaults if we have them + if (defaults != null) { + obj = defaults.get(key); + } + } + + return obj; + } + + /** + * Add a property to the configuration. If it already + * exists then the value stated here will be added + * to the configuration entry. For example, if + * + * <code>resource.loader = file</code> + * + * is already present in the configuration and you + * + * <code>addProperty("resource.loader", "classpath")</code> + * + * Then you will end up with a Vector like the + * following: + * + * <code>["file", "classpath"]</code> + * + * @param key the key to add + * @param value the value to add + */ + public void addProperty(String key, Object value) { + if (value instanceof String) { + String str = (String) value; + if (str.indexOf(PropertiesTokenizer.DELIMITER) > 0) { + // token contains commas, so must be split apart then added + PropertiesTokenizer tokenizer = new PropertiesTokenizer(str); + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + addPropertyInternal(key, unescape(token)); + } + } else { + // token contains no commas, so can be simply added + addPropertyInternal(key, unescape(str)); + } + } else { + addPropertyInternal(key, value); + } + + // Adding a property connotes initialization + isInitialized = true; + } + + /** + * Adds a key/value pair to the map. This routine does + * no magic morphing. It ensures the keylist is maintained + * + * @param key the key to store at + * @param value the decoded object to store + */ + private void addPropertyDirect(String key, Object value) { + // safety check + if (!containsKey(key)) { + keysAsListed.add(key); + } + put(key, value); + } + + /** + * Adds a decoded property to the map w/o checking for commas - used + * internally when a property has been broken up into + * strings that could contain escaped commas to prevent + * the inadvertent vectorization. + * <p> + * Thanks to Leon Messerschmidt for this one. + * + * @param key the key to store at + * @param value the decoded object to store + */ + private void addPropertyInternal(String key, Object value) { + Object current = this.get(key); + + if (current instanceof String) { + // one object already in map - convert it to a vector + List values = new Vector(2); + values.add(current); + values.add(value); + put(key, values); + + } else if (current instanceof List) { + // already a list - just add the new token + ((List) current).add(value); + + } else { + // brand new key - store in keysAsListed to retain order + if (!containsKey(key)) { + keysAsListed.add(key); + } + put(key, value); + } + } + + /** + * Set a property, this will replace any previously + * set values. Set values is implicitly a call + * to clearProperty(key), addProperty(key,value). + * + * @param key the key to set + * @param value the value to set + */ + public void setProperty(String key, Object value) { + clearProperty(key); + addProperty(key, value); + } + + /** + * Save the properties to the given output stream. + * <p> + * The stream is not closed, but it is flushed. + * + * @param output an OutputStream, may be null + * @param header a textual comment to act as a file header + * @throws IOException if an IO error occurs + */ + public synchronized void save(OutputStream output, String header) throws IOException { + if (output == null) { + return; + } + PrintWriter theWrtr = new PrintWriter(output); + if (header != null) { + theWrtr.println(header); + } + + Enumeration theKeys = keys(); + while (theKeys.hasMoreElements()) { + String key = (String) theKeys.nextElement(); + Object value = get(key); + if (value != null) { + if (value instanceof String) { + StringBuffer currentOutput = new StringBuffer(); + currentOutput.append(key); + currentOutput.append("="); + currentOutput.append(escape((String) value)); + theWrtr.println(currentOutput.toString()); + + } else if (value instanceof List) { + List values = (List) value; + for (Iterator it = values.iterator(); it.hasNext(); ) { + String currentElement = (String) it.next(); + StringBuffer currentOutput = new StringBuffer(); + currentOutput.append(key); + currentOutput.append("="); + currentOutput.append(escape(currentElement)); + theWrtr.println(currentOutput.toString()); + } + } + } + theWrtr.println(); + theWrtr.flush(); + } + } + + /** + * Combines an existing Hashtable with this Hashtable. + * <p> + * Warning: It will overwrite previous entries without warning. + * + * @param props the properties to combine + */ + public void combine(ExtProperties props) { + for (Iterator it = props.getKeys(); it.hasNext();) { + String key = (String) it.next(); + setProperty(key, props.get(key)); + } + } + + /** + * Clear a property in the configuration. + * + * @param key the property key to remove along with corresponding value + */ + public void clearProperty(String key) { + if (containsKey(key)) { + // we also need to rebuild the keysAsListed or else + // things get *very* confusing + for (int i = 0; i < keysAsListed.size(); i++) { + if (( keysAsListed.get(i)).equals(key)) { + keysAsListed.remove(i); + break; + } + } + remove(key); + } + } + + /** + * Get the list of the keys contained in the configuration + * repository. + * + * @return an Iterator over the keys + */ + public Iterator getKeys() { + return keysAsListed.iterator(); + } + + /** + * Get the list of the keys contained in the configuration + * repository that match the specified prefix. + * + * @param prefix the prefix to match + * @return an Iterator of keys that match the prefix + */ + public Iterator getKeys(String prefix) { + Iterator keys = getKeys(); + ArrayList matchingKeys = new ArrayList(); + + while (keys.hasNext()) { + Object key = keys.next(); + + if (key instanceof String && ((String) key).startsWith(prefix)) { + matchingKeys.add(key); + } + } + return matchingKeys.iterator(); + } + + /** + * Create an ExtProperties object that is a subset + * of this one. Take into account duplicate keys + * by using the setProperty() in ExtProperties. + * + * @param prefix the prefix to get a subset for + * @return a new independent ExtProperties + */ + public ExtProperties subset(String prefix) { + ExtProperties c = new ExtProperties(); + Iterator keys = getKeys(); + boolean validSubset = false; + + while (keys.hasNext()) { + Object key = keys.next(); + + if (key instanceof String && ((String) key).startsWith(prefix)) { + if (!validSubset) { + validSubset = true; + } + + /* + * Check to make sure that c.subset(prefix) doesn't + * blow up when there is only a single property + * with the key prefix. This is not a useful + * subset but it is a valid subset. + */ + String newKey = null; + if (((String) key).length() == prefix.length()) { + newKey = prefix; + } else { + newKey = ((String) key).substring(prefix.length() + 1); + } + + /* + * use addPropertyDirect() - this will plug the data as + * is into the Map, but will also do the right thing + * re key accounting + */ + c.addPropertyDirect(newKey, get(key)); + } + } + + if (validSubset) { + return c; + } else { + return null; + } + } + + /** + * Display the configuration for debugging purposes to System.out. + */ + public void display() { + Iterator i = getKeys(); + + while (i.hasNext()) { + String key = (String) i.next(); + Object value = get(key); + System.out.println(key + " => " + value); + } + } + + /** + * Get a string associated with the given configuration key. + * + * @param key The configuration key. + * @return The associated string. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a String. + */ + public String getString(String key) { + return getString(key, null); + } + + /** + * Get a string associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated string if key is found, + * default value otherwise. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a String. + */ + public String getString(String key, String defaultValue) { + Object value = get(key); + + if (value instanceof String) { + return interpolate((String) value); + + } else if (value == null) { + if (defaults != null) { + return interpolate(defaults.getString(key, defaultValue)); + } else { + return interpolate(defaultValue); + } + } else if (value instanceof List) { + return interpolate((String) ((List) value).get(0)); + } else { + throw new ClassCastException('\'' + key + "' doesn't map to a String object"); + } + } + + /** + * Get a list of properties associated with the given + * configuration key. + * + * @param key The configuration key. + * @return The associated properties if key is found. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a String/List. + * @throws IllegalArgumentException if one of the tokens is + * malformed (does not contain an equals sign). + */ + public Properties getProperties(String key) { + return getProperties(key, new Properties()); + } + + /** + * Get a list of properties associated with the given + * configuration key. + * + * @param key The configuration key. + * @return The associated properties if key is found. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a String/List. + * @throws IllegalArgumentException if one of the tokens is + * malformed (does not contain an equals sign). + */ + public Properties getProperties(String key, Properties defaults) { + /* + * Grab an array of the tokens for this key. + */ + String[] tokens = getStringArray(key); + + // Each token is of the form 'key=value'. + Properties props = new Properties(defaults); + for (int i = 0; i < tokens.length; i++) { + String token = tokens[i]; + int equalSign = token.indexOf('='); + if (equalSign > 0) { + String pkey = token.substring(0, equalSign).trim(); + String pvalue = token.substring(equalSign + 1).trim(); + props.put(pkey, pvalue); + } else { + throw new IllegalArgumentException('\'' + token + "' does not contain " + "an equals sign"); + } + } + return props; + } + + /** + * Get an array of strings associated with the given configuration + * key. + * + * @param key The configuration key. + * @return The associated string array if key is found. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a String/List. + */ + public String[] getStringArray(String key) { + Object value = get(key); + + List values; + if (value instanceof String) { + values = new Vector(1); + values.add(value); + + } else if (value instanceof List) { + values = (List) value; + + } else if (value == null) { + if (defaults != null) { + return defaults.getStringArray(key); + } else { + return new String[0]; + } + } else { + throw new ClassCastException('\'' + key + "' doesn't map to a String/List object"); + } + + String[] tokens = new String[values.size()]; + for (int i = 0; i < tokens.length; i++) { + tokens[i] = (String) values.get(i); + } + + return tokens; + } + + /** + * Get a Vector of strings associated with the given configuration + * key. + * + * @param key The configuration key. + * @return The associated Vector. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Vector. + */ + public Vector getVector(String key) { + return getVector(key, null); + } + + /** + * Get a Vector of strings associated with the given configuration key. + * <p> + * The list is a copy of the internal data of this object, and as + * such you may alter it freely. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated Vector. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Vector. + */ + public Vector getVector(String key, Vector defaultValue) { + Object value = get(key); + + if (value instanceof List) { + return new Vector((List) value); + + } else if (value instanceof String) { + Vector values = new Vector(1); + values.add(value); + put(key, values); + return values; + + } else if (value == null) { + if (defaults != null) { + return defaults.getVector(key, defaultValue); + } else { + return ((defaultValue == null) ? new Vector() : defaultValue); + } + } else { + throw new ClassCastException('\'' + key + "' doesn't map to a Vector object"); + } + } + + /** + * Get a List of strings associated with the given configuration key. + * <p> + * The list is a copy of the internal data of this object, and as + * such you may alter it freely. + * + * @param key The configuration key. + * @return The associated List object. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a List. + * @since Commons Collections 3.2 + */ + public List getList(String key) { + return getList(key, null); + } + + /** + * Get a List of strings associated with the given configuration key. + * <p> + * The list is a copy of the internal data of this object, and as + * such you may alter it freely. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated List. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a List. + * @since Commons Collections 3.2 + */ + public List getList(String key, List defaultValue) { + Object value = get(key); + + if (value instanceof List) { + return new ArrayList((List) value); + + } else if (value instanceof String) { + List values = new ArrayList(1); + values.add(value); + put(key, values); + return values; + + } else if (value == null) { + if (defaults != null) { + return defaults.getList(key, defaultValue); + } else { + return ((defaultValue == null) ? new ArrayList() : defaultValue); + } + } else { + throw new ClassCastException('\'' + key + "' doesn't map to a List object"); + } + } + + /** + * Get a boolean associated with the given configuration key. + * + * @param key The configuration key. + * @return The associated boolean. + * @throws NoSuchElementException is thrown if the key doesn't + * map to an existing object. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Boolean. + */ + public boolean getBoolean(String key) { + Boolean b = getBoolean(key, null); + if (b != null) { + return b.booleanValue(); + } else { + throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); + } + } + + /** + * Get a boolean associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated boolean. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Boolean. + */ + public boolean getBoolean(String key, boolean defaultValue) { + return getBoolean(key, new Boolean(defaultValue)).booleanValue(); + } + + /** + * Get a boolean associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated boolean if key is found and has valid + * format, default value otherwise. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Boolean. + */ + public Boolean getBoolean(String key, Boolean defaultValue) { + + Object value = get(key); + + if (value instanceof Boolean) { + return (Boolean) value; + + } else if (value instanceof String) { + String s = testBoolean((String) value); + Boolean b = new Boolean(s); + put(key, b); + return b; + + } else if (value == null) { + if (defaults != null) { + return defaults.getBoolean(key, defaultValue); + } else { + return defaultValue; + } + } else { + throw new ClassCastException('\'' + key + "' doesn't map to a Boolean object"); + } + } + + /** + * Test whether the string represent by value maps to a boolean + * value or not. We will allow <code>true</code>, <code>on</code>, + * and <code>yes</code> for a <code>true</code> boolean value, and + * <code>false</code>, <code>off</code>, and <code>no</code> for + * <code>false</code> boolean values. Case of value to test for + * boolean status is ignored. + * + * @param value the value to test for boolean state + * @return <code>true</code> or <code>false</code> if the supplied + * text maps to a boolean value, or <code>null</code> otherwise. + */ + public String testBoolean(String value) { + String s = value.toLowerCase(); + + if (s.equals("true") || s.equals("on") || s.equals("yes")) { + return "true"; + } else if (s.equals("false") || s.equals("off") || s.equals("no")) { + return "false"; + } else { + return null; + } + } + + /** + * Get a byte associated with the given configuration key. + * + * @param key The configuration key. + * @return The associated byte. + * @throws NoSuchElementException is thrown if the key doesn't + * map to an existing object. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Byte. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public byte getByte(String key) { + Byte b = getByte(key, null); + if (b != null) { + return b.byteValue(); + } else { + throw new NoSuchElementException('\'' + key + " doesn't map to an existing object"); + } + } + + /** + * Get a byte associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated byte. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Byte. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public byte getByte(String key, byte defaultValue) { + return getByte(key, new Byte(defaultValue)).byteValue(); + } + + /** + * Get a byte associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated byte if key is found and has valid + * format, default value otherwise. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Byte. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public Byte getByte(String key, Byte defaultValue) { + Object value = get(key); + + if (value instanceof Byte) { + return (Byte) value; + + } else if (value instanceof String) { + Byte b = new Byte((String) value); + put(key, b); + return b; + + } else if (value == null) { + if (defaults != null) { + return defaults.getByte(key, defaultValue); + } else { + return defaultValue; + } + } else { + throw new ClassCastException('\'' + key + "' doesn't map to a Byte object"); + } + } + + /** + * Get a short associated with the given configuration key. + * + * @param key The configuration key. + * @return The associated short. + * @throws NoSuchElementException is thrown if the key doesn't + * map to an existing object. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Short. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public short getShort(String key) { + Short s = getShort(key, null); + if (s != null) { + return s.shortValue(); + } else { + throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); + } + } + + /** + * Get a short associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated short. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Short. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public short getShort(String key, short defaultValue) { + return getShort(key, new Short(defaultValue)).shortValue(); + } + + /** + * Get a short associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated short if key is found and has valid + * format, default value otherwise. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Short. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public Short getShort(String key, Short defaultValue) { + Object value = get(key); + + if (value instanceof Short) { + return (Short) value; + + } else if (value instanceof String) { + Short s = new Short((String) value); + put(key, s); + return s; + + } else if (value == null) { + if (defaults != null) { + return defaults.getShort(key, defaultValue); + } else { + return defaultValue; + } + } else { + throw new ClassCastException('\'' + key + "' doesn't map to a Short object"); + } + } + + /** + * The purpose of this method is to get the configuration resource + * with the given name as an integer. + * + * @param name The resource name. + * @return The value of the resource as an integer. + */ + public int getInt(String name) { + return getInteger(name); + } + + /** + * The purpose of this method is to get the configuration resource + * with the given name as an integer, or a default value. + * + * @param name The resource name + * @param def The default value of the resource. + * @return The value of the resource as an integer. + */ + public int getInt(String name, int def) { + return getInteger(name, def); + } + + /** + * Get a int associated with the given configuration key. + * + * @param key The configuration key. + * @return The associated int. + * @throws NoSuchElementException is thrown if the key doesn't + * map to an existing object. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Integer. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public int getInteger(String key) { + Integer i = getInteger(key, null); + if (i != null) { + return i.intValue(); + } else { + throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); + } + } + + /** + * Get a int associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated int. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Integer. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public int getInteger(String key, int defaultValue) { + Integer i = getInteger(key, null); + + if (i == null) { + return defaultValue; + } + return i.intValue(); + } + + /** + * Get a int associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated int if key is found and has valid + * format, default value otherwise. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Integer. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public Integer getInteger(String key, Integer defaultValue) { + Object value = get(key); + + if (value instanceof Integer) { + return (Integer) value; + + } else if (value instanceof String) { + Integer i = new Integer((String) value); + put(key, i); + return i; + + } else if (value == null) { + if (defaults != null) { + return defaults.getInteger(key, defaultValue); + } else { + return defaultValue; + } + } else { + throw new ClassCastException('\'' + key + "' doesn't map to a Integer object"); + } + } + + /** + * Get a long associated with the given configuration key. + * + * @param key The configuration key. + * @return The associated long. + * @throws NoSuchElementException is thrown if the key doesn't + * map to an existing object. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Long. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public long getLong(String key) { + Long l = getLong(key, null); + if (l != null) { + return l.longValue(); + } else { + throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); + } + } + + /** + * Get a long associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated long. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Long. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public long getLong(String key, long defaultValue) { + return getLong(key, new Long(defaultValue)).longValue(); + } + + /** + * Get a long associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated long if key is found and has valid + * format, default value otherwise. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Long. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public Long getLong(String key, Long defaultValue) { + Object value = get(key); + + if (value instanceof Long) { + return (Long) value; + + } else if (value instanceof String) { + Long l = new Long((String) value); + put(key, l); + return l; + + } else if (value == null) { + if (defaults != null) { + return defaults.getLong(key, defaultValue); + } else { + return defaultValue; + } + } else { + throw new ClassCastException('\'' + key + "' doesn't map to a Long object"); + } + } + + /** + * Get a float associated with the given configuration key. + * + * @param key The configuration key. + * @return The associated float. + * @throws NoSuchElementException is thrown if the key doesn't + * map to an existing object. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Float. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public float getFloat(String key) { + Float f = getFloat(key, null); + if (f != null) { + return f.floatValue(); + } else { + throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); + } + } + + /** + * Get a float associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated float. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Float. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public float getFloat(String key, float defaultValue) { + return getFloat(key, new Float(defaultValue)).floatValue(); + } + + /** + * Get a float associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated float if key is found and has valid + * format, default value otherwise. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Float. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public Float getFloat(String key, Float defaultValue) { + Object value = get(key); + + if (value instanceof Float) { + return (Float) value; + + } else if (value instanceof String) { + Float f = new Float((String) value); + put(key, f); + return f; + + } else if (value == null) { + if (defaults != null) { + return defaults.getFloat(key, defaultValue); + } else { + return defaultValue; + } + } else { + throw new ClassCastException('\'' + key + "' doesn't map to a Float object"); + } + } + + /** + * Get a double associated with the given configuration key. + * + * @param key The configuration key. + * @return The associated double. + * @throws NoSuchElementException is thrown if the key doesn't + * map to an existing object. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Double. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public double getDouble(String key) { + Double d = getDouble(key, null); + if (d != null) { + return d.doubleValue(); + } else { + throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); + } + } + + /** + * Get a double associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated double. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Double. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public double getDouble(String key, double defaultValue) { + return getDouble(key, new Double(defaultValue)).doubleValue(); + } + + /** + * Get a double associated with the given configuration key. + * + * @param key The configuration key. + * @param defaultValue The default value. + * @return The associated double if key is found and has valid + * format, default value otherwise. + * @throws ClassCastException is thrown if the key maps to an + * object that is not a Double. + * @throws NumberFormatException is thrown if the value mapped + * by the key has not a valid number format. + */ + public Double getDouble(String key, Double defaultValue) { + Object value = get(key); + + if (value instanceof Double) { + return (Double) value; + + } else if (value instanceof String) { + Double d = new Double((String) value); + put(key, d); + return d; + + } else if (value == null) { + if (defaults != null) { + return defaults.getDouble(key, defaultValue); + } else { + return defaultValue; + } + } else { + throw new ClassCastException('\'' + key + "' doesn't map to a Double object"); + } + } + + /** + * Convert a standard properties class into a configuration class. + * <p> + * NOTE: From Commons Collections 3.2 this method will pick up + * any default parent Properties of the specified input object. + * + * @param props the properties object to convert + * @return new ExtProperties created from props + */ + public static ExtProperties convertProperties(Properties props) { + ExtProperties c = new ExtProperties(); + + for (Enumeration e = props.propertyNames(); e.hasMoreElements();) { + String s = (String) e.nextElement(); + c.setProperty(s, props.getProperty(s)); + } + + return c; + } + + /** + * Convert a Map into a configuration class. + * <p> + * NOTE: From Commons Collections 3.2 this method will pick up + * any default parent Properties of the specified input object. + * + * @param props the Map object to convert + * @return new ExtProperties created from props + */ + public static ExtProperties convertProperties(Map props) { + ExtProperties c = new ExtProperties(); + + for (Map.Entry entry : (Set<Map.Entry>)props.entrySet()) + { + c.setProperty(String.valueOf(entry.getKey()), entry.getValue()); + } + return c; + } + +}
Modified: velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingResourceLoader.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingResourceLoader.java?rev=1753054&r1=1753053&r2=1753054&view=diff ============================================================================== --- velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingResourceLoader.java (original) +++ velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingResourceLoader.java Sun Jul 17 10:46:07 2016 @@ -21,10 +21,10 @@ package org.apache.velocity.test.misc; import java.io.Reader; -import org.apache.commons.collections.ExtendedProperties; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.runtime.resource.Resource; import org.apache.velocity.runtime.resource.loader.ResourceLoader2; +import org.apache.velocity.util.ExtProperties; /** * Resource Loader that always throws an exception. Used to test @@ -36,7 +36,7 @@ import org.apache.velocity.runtime.resou public class ExceptionGeneratingResourceLoader extends ResourceLoader2 { - public void init(ExtendedProperties configuration) + public void init(ExtProperties configuration) { }
