📄 extendedproperties.java
字号:
// copied from commons-collections-3.1
/*
* Copyright 2001-2004 The Apache Software Foundation
*
* Licensed 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 gov.lbl.dsd.sea.nio.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.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Properties;
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
* like 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!
*
* @since Commons Collections 1.0
* @version $Revision: 1.1 $ $Date: 2004/07/21 20:54:43 $
*
* @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
* @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
* @author <a href="mailto:daveb@miceda-data">Dave Bryson</a>
* @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
* @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
* @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
* @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
* @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
* @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a>
* @author Janek Bogucki
* @author Mohan Kishore
* @author Stephen Colebourne
*/
public class ExtendedProperties extends Hashtable {
/**
* Default configurations repository.
*/
private ExtendedProperties 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 ExtendedProperties object.
*/
protected String basePath;
/**
* File separator.
*/
protected String fileSeparator = System.getProperty("file.separator");
/**
* Has this configuration been intialized.
*/
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();
try {
while (true) {
String line = readLine().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);
break;
}
}
}
} catch (NullPointerException ex) {
return null;
}
return buffer.toString();
}
}
/**
* 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.
*
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -