Properties.java
// Copyright (c) ZeroC, Inc.
package com.zeroc.Ice;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PushbackInputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Represents a set of properties used to configure Ice and Ice-based applications. A property is a key/value pair,
* where both the key and the value are strings. By convention, property keys should have the form
* {@code application-name[.category[.sub-category]].name}.
* This class is thread-safe: multiple threads can safely read and write the properties.
*/
public final class Properties {
private static final int ParseStateKey = 0;
private static final int ParseStateValue = 1;
private final HashMap<String, PropertyValue> _propertySet = new HashMap<>();
private final List<String> _optInPrefixes;
/** Constructs an empty property set. */
public Properties() {
this(List.of());
}
/**
* Constructs a property set, loads the configuration files specified by the {@code Ice.Config} property or the
* {@code ICE_CONFIG} environment variable, and then parses Ice properties from {@code args}.
*
* <p>This constructor loads properties from files specified by the {@code ICE_CONFIG} environment variable when
* there is no {@code --Ice.Config} command-line argument.
*
* @param args the command-line arguments. This constructor parses arguments starting with {@code --} and one of the
* reserved prefixes (Ice, IceSSL, etc.) as properties. If there is an argument starting with
* {@code --Ice.Config}, this constructor loads the specified configuration file. When the same property is set
* in a configuration file and through a command-line argument, the command-line setting takes precedence.
*/
public Properties(String[] args) {
this(args, (Properties) null, null);
}
/**
* Constructs a property set, loads the configuration files specified by the {@code Ice.Config} property or the
* {@code ICE_CONFIG} environment variable, and then parses Ice properties from {@code args}.
*
* <p>This constructor loads properties from files specified by the {@code ICE_CONFIG} environment variable when
* there is no {@code --Ice.Config} command-line argument.
*
* @param args the command-line arguments. This constructor parses arguments starting with {@code --} and one of the
* reserved prefixes (Ice, IceSSL, etc.) as properties. If there is an argument starting with
* {@code --Ice.Config}, this constructor loads the specified configuration file. When the same property is set
* in a configuration file and through a command-line argument, the command-line setting takes precedence.
* @param remainingArgs if non-null, this list will be cleared and filled with any command-line arguments that were
* not used to set properties.
*/
public Properties(String[] args, List<String> remainingArgs) {
this(args, (Properties) null, remainingArgs);
}
/**
* Constructs a property set, loads the configuration files specified by the {@code Ice.Config} property or the
* {@code ICE_CONFIG} environment variable, and then parses Ice properties from {@code args}.
*
* <p>This constructor loads properties from files specified by the {@code ICE_CONFIG} environment variable when
* there is no {@code --Ice.Config} command-line argument.
*
* @param args the command-line arguments. This constructor parses arguments starting with {@code --} and one of the
* reserved prefixes (Ice, IceSSL, etc.) as properties. If there is an argument starting with
* {@code --Ice.Config}, this constructor loads the specified configuration file. When the same property is set
* in a configuration file and through a command-line argument, the command-line setting takes precedence.
* @param defaults default values for the new {@code Properties} object.
* Settings in configuration files and {@code args} override these defaults.
*/
public Properties(String[] args, Properties defaults) {
this(args, defaults, null);
}
/**
* Constructs a property set, loads the configuration files specified by the {@code Ice.Config} property or the
* {@code ICE_CONFIG} environment variable, and then parses Ice properties from {@code args}.
*
* <p>This constructor loads properties from files specified by the {@code ICE_CONFIG} environment variable when
* there is no {@code --Ice.Config} command-line argument.
*
* @param args the command-line arguments. This constructor parses arguments starting with {@code --} and one of the
* reserved prefixes (Ice, IceSSL, etc.) as properties. If there is an argument starting with
* {@code --Ice.Config}, this constructor loads the specified configuration file. When the same property is set
* in a configuration file and through a command-line argument, the command-line setting takes precedence.
* @param defaults default values for the new {@code Properties} object.
* Settings in configuration files and {@code args} override these defaults.
* @param remainingArgs if non-null, this list will be cleared and filled with any command-line arguments that were
* not used to set properties.
*/
public Properties(String[] args, Properties defaults, List<String> remainingArgs) {
this(defaults);
loadArgs(args, remainingArgs);
}
/**
* Constructs a property set with additional opt-in prefixes.
*
* @param optInPrefixes -
*
* @hidden optInPrefixes is only for internal use in Java.
*/
public Properties(List<String> optInPrefixes) {
_optInPrefixes = List.copyOf(optInPrefixes); // _optInPrefixes is immutable
}
/**
* Constructs a property set, loads the configuration files specified by the {@code Ice.Config} property or the
* {@code ICE_CONFIG} environment variable, and then parses Ice properties from {@code args}.
*
* <p>This constructor loads properties from files specified by the {@code ICE_CONFIG} environment variable when
* there is no {@code --Ice.Config} command-line argument.
*
* @param args the command-line arguments. This constructor parses arguments starting with {@code --} and one of the
* reserved prefixes (Ice, IceSSL, etc.) as properties. If there is an argument starting with
* {@code --Ice.Config}, this constructor loads the specified configuration file. When the same property is set
* in a configuration file and through a command-line argument, the command-line setting takes precedence.
* @param remainingArgs if non-null, this list will be cleared and filled with any command-line arguments that were
* not used to set properties.
* @param optInPrefixes -
*
* @hidden optInPrefixes is only for internal use in Java.
*/
public Properties(String[] args, List<String> remainingArgs, List<String> optInPrefixes) {
this(optInPrefixes);
loadArgs(args, remainingArgs);
}
/**
* Gets a property by key.
*
* @param key the property key
* @return the property value, or the empty string if the property is not set
* @see #setProperty
*/
public synchronized String getProperty(String key) {
PropertyValue pv = _propertySet.get(key);
if (pv != null) {
pv.used = true;
return pv.value;
} else {
return "";
}
}
/**
* Gets an Ice property by key.
*
* @param key the property key
* @return the property value, or the default value for this property if the property is not set
* @throws PropertyException if the property is not a known Ice property
* @see #setProperty
*/
public synchronized String getIceProperty(String key) {
PropertyValue pv = _propertySet.get(key);
if (pv != null) {
pv.used = true;
return pv.value;
} else {
return getDefaultProperty(key);
}
}
/**
* Gets a property by key.
*
* @param key the property key
* @param value the default value to return if the property is not set
* @return the property value or the default value if the property is not set
* @see #setProperty
*/
public synchronized String getPropertyWithDefault(String key, String value) {
PropertyValue pv = _propertySet.get(key);
if (pv != null) {
pv.used = true;
return pv.value;
} else {
return value;
}
}
/**
* Gets a property as an integer.
*
* @param key the property key
* @return the property value interpreted as an integer, or {@code 0} if the property is not set
* @throws PropertyException if the property value is not a valid integer
* @see #setProperty
*/
public int getPropertyAsInt(String key) {
return getPropertyAsIntWithDefault(key, 0);
}
/**
* Gets an Ice property as an integer.
*
* @param key the property key
* @return the property value interpreted as an integer, or the default value if the property is not set
* @throws PropertyException if the property is not a known Ice property or its value is not a valid integer
* @see #setProperty
*/
public synchronized int getIcePropertyAsInt(String key) {
String defaultValueString = getDefaultProperty(key);
int defaultValue = 0;
if (defaultValueString != "") {
// These defaults are assigned by us and are guaranteed to be integers.
defaultValue = Integer.parseInt(defaultValueString);
}
return getPropertyAsIntWithDefault(key, defaultValue);
}
/**
* Gets a property as an integer.
*
* @param key the property key
* @param value the default value to return if the property does not exist
* @return the property value interpreted as an integer, or the default value if the property is not set
* @throws PropertyException if the property value is not a valid integer
* @see #setProperty
*/
public synchronized int getPropertyAsIntWithDefault(String key, int value) {
PropertyValue pv = _propertySet.get(key);
if (pv != null) {
pv.used = true;
try {
return Integer.parseInt(pv.value);
} catch (NumberFormatException ex) {
throw new PropertyException("property '" + key + "' has an invalid integer value: '" + pv.value + "'");
}
}
return value;
}
/**
* Gets a property as a list of strings. The strings must be separated by whitespace or comma. The strings in the
* list can contain whitespace and commas if they are enclosed in single or double quotes. If quotes are mismatched,
* an empty list is returned. Within single quotes or double quotes, you can escape the quote in question with
* a backslash, e.g. O'Reilly can be written as {@code O'Reilly}, {@code "O'Reilly"} or {@code 'O\'Reilly'}.
*
* @param key the property key
* @return the property value interpreted as a list of strings, or an empty list if the property is not set
* @see #setProperty
*/
public String[] getPropertyAsList(String key) {
return getPropertyAsListWithDefault(key, null);
}
/**
* Gets an Ice property as a list of strings. The strings must be separated by whitespace or comma. The strings in
* the list can contain whitespace and commas if they are enclosed in single or double quotes. If quotes are
* mismatched, the default list is returned. Within single quotes or double quotes, you can escape the quote in
* question with a backslash, e.g. O'Reilly can be written as {@code O'Reilly}, {@code "O'Reilly"} or
* {@code 'O\'Reilly'}.
*
* @param key the property key
* @return the property value interpreted as a list of strings, or the default value if the property is not set
* @throws PropertyException if the property is not a known Ice property
* @see #setProperty
*/
public synchronized String[] getIcePropertyAsList(String key) {
String[] defaultList = StringUtil.splitString(getDefaultProperty(key), ", \t\r\n");
return getPropertyAsListWithDefault(key, defaultList);
}
/**
* Gets a property as a list of strings. The strings must be separated by whitespace or comma. The strings in the
* list can contain whitespace and commas if they are enclosed in single or double quotes. If quotes are mismatched,
* the default list is returned. Within single quotes or double quotes, you can escape the quote in question with
* a backslash, e.g. O'Reilly can be written as {@code O'Reilly}, {@code "O'Reilly"} or {@code 'O\'Reilly'}.
*
* @param key the property key
* @param value the default value to return if the property is not set
* @return the property value interpreted as a list of strings, or the default value if the property is not set
* @see #setProperty
*/
public synchronized String[] getPropertyAsListWithDefault(String key, String[] value) {
if (value == null) {
value = new String[0];
}
PropertyValue pv = _propertySet.get(key);
if (pv != null) {
pv.used = true;
String[] result = StringUtil.splitString(pv.value, ", \t\r\n");
if (result == null) {
String msg = "mismatched quotes in property " + key + "'s value, returning default value";
Util.getProcessLogger().warning(msg);
return value;
}
if (result.length == 0) {
result = value;
}
return result;
} else {
return value;
}
}
/**
* Gets all properties whose keys begin with {@code prefix}. If {@code prefix} is the empty string,
* then all properties are returned.
*
* @param prefix the prefix to search for
* @return the matching property set
*/
public synchronized Map<String, String> getPropertiesForPrefix(String prefix) {
HashMap<String, String> result = new HashMap<>();
for (Map.Entry<String, PropertyValue> p : _propertySet.entrySet()) {
String key = p.getKey();
if (prefix.isEmpty() || key.startsWith(prefix)) {
PropertyValue pv = p.getValue();
pv.used = true;
result.put(key, pv.value);
}
}
return result;
}
/**
* Sets a property. To unset a property, set it to the empty string.
*
* @param key the property key
* @param value the property value
* @see #getProperty
*/
public void setProperty(String key, String value) {
// Trim whitespace
if (key != null) {
key = key.trim();
}
if (key == null || key.isEmpty()) {
throw new InitializationException("Attempt to set property with empty key");
}
// Check if the property is in an Ice property prefix. If so, check that it's a valid property.
PropertyArray propertyArray = findIcePropertyArray(key);
if (propertyArray != null) {
if (propertyArray.isOptIn() && _optInPrefixes.stream().noneMatch(propertyArray.name()::equals)) {
throw new PropertyException("unable to set '" + key + "': property prefix '" + propertyArray.name()
+ "' is opt-in and must be explicitly enabled");
}
Property prop = findProperty(key.substring(propertyArray.name().length() + 1), propertyArray);
if (prop == null) {
throw new PropertyException("unknown Ice property: " + key);
}
// If the property is deprecated, log a warning
if (prop.deprecated()) {
Util.getProcessLogger().warning("setting deprecated property: " + key);
}
}
synchronized (this) {
// Set or clear the property.
if (value != null && !value.isEmpty()) {
PropertyValue pv = _propertySet.get(key);
if (pv != null) {
pv.value = value;
} else {
pv = new PropertyValue(value, false);
}
_propertySet.put(key, pv);
} else {
_propertySet.remove(key);
}
}
}
/**
* Gets a sequence of command-line options that is equivalent to this property set. Each element of the
* returned sequence is a command-line option of the form {@code --key=value}.
*
* @return the command line options for this property set
*/
public synchronized String[] getCommandLineOptions() {
String[] result = new String[_propertySet.size()];
int i = 0;
for (Map.Entry<String, PropertyValue> p : _propertySet.entrySet()) {
result[i++] = "--" + p.getKey() + "=" + p.getValue().value;
}
assert (i == result.length);
return result;
}
/**
* Converts a sequence of command-line options into properties. All options that start with {@code --prefix.} are
* converted into properties. If the prefix is empty, all options that begin with {@code --} are converted to
* properties.
*
* @param prefix the property prefix, or the empty string to convert all options starting with {@code --}
* @param options the command-line options
* @return the command-line options that do not start with the specified prefix, in their original order
*/
public String[] parseCommandLineOptions(String prefix, String[] options) {
if (!prefix.isEmpty() && prefix.charAt(prefix.length() - 1) != '.') {
prefix += '.';
}
prefix = "--" + prefix;
ArrayList<String> result = new ArrayList<>();
for (String opt : options) {
if (opt.startsWith(prefix)) {
if (opt.indexOf('=') == -1) {
opt += "=1";
}
parseLine(opt.substring(2));
} else {
result.add(opt);
}
}
return result.toArray(new String[0]);
}
/**
* Converts a sequence of command-line options into properties. All options that start with one of the
* reserved Ice prefixes ({@code --Ice}, {@code --IceSSL}, etc.) are converted into properties.
*
* @param options the command-line options
* @return the command-line options that do not start with one of the reserved prefixes, in their original order
*/
public String[] parseIceCommandLineOptions(String[] options) {
String[] args = options;
for (PropertyArray props : PropertyNames.validProps) {
args = parseCommandLineOptions(props.name(), args);
}
return args;
}
/**
* Loads properties from a file.
*
* @param file the property file
*/
public void load(String file) {
if (System.getProperty("os.name").startsWith("Windows")
&& (file.startsWith("HKCU\\") || file.startsWith("HKLM\\"))) {
try {
java.lang.Process process = Runtime.getRuntime().exec(new String[]{"reg", "query", file});
process.waitFor();
if (process.exitValue() != 0) {
throw new InitializationException("Could not read Windows registry key '" + file + "'");
}
java.io.InputStream is = process.getInputStream();
StringWriter sw = new StringWriter();
int c;
while ((c = is.read()) != -1) {
sw.write(c);
}
String[] result = sw.toString().split("\n");
for (String line : result) {
int pos = line.indexOf("REG_SZ");
if (pos != -1) {
setProperty(
line.substring(0, pos).trim(),
line.substring(pos + 6, line.length()).trim());
continue;
}
pos = line.indexOf("REG_EXPAND_SZ");
if (pos != -1) {
String name = line.substring(0, pos).trim();
line = line.substring(pos + 13, line.length()).trim();
while (true) {
int start = line.indexOf('%', 0);
int end = line.indexOf('%', start + 1);
// If there isn't more %var% break the loop
if (start == -1 || end == -1) {
break;
}
String envKey = line.substring(start + 1, end);
String envValue = System.getenv(envKey);
if (envValue == null) {
envValue = "";
}
envKey = "%" + envKey + "%";
do {
line = line.replace(envKey, envValue);
} while (line.indexOf(envKey) != -1);
}
setProperty(name, line);
continue;
}
}
} catch (LocalException ex) {
throw ex;
} catch (Exception ex) {
throw new InitializationException("Could not read Windows registry key `" + file + "'", ex);
}
} else {
PushbackInputStream is = null;
try {
java.io.InputStream f = Util.openResource(getClass().getClassLoader(), file);
if (f == null) {
throw new FileException("failed to open '" + file + "'");
}
// Skip UTF-8 BOM if present.
byte[] bom = new byte[3];
is = new PushbackInputStream(f, bom.length);
int read = is.read(bom, 0, bom.length);
if (read < 3
|| bom[0] != (byte) 0xEF
|| bom[1] != (byte) 0xBB
|| bom[2] != (byte) 0xBF) {
if (read > 0) {
is.unread(bom, 0, read);
}
}
InputStreamReader isr = new InputStreamReader(is, "UTF-8");
BufferedReader br = new BufferedReader(isr);
parse(br);
} catch (IOException ex) {
throw new FileException("Cannot read '" + file + "'", ex);
} finally {
if (is != null) {
try {
is.close();
} catch (Throwable ex) {
// Ignore.
}
}
}
}
}
/**
* Creates a copy of this property set.
*
* @return a copy of this property set
*/
public Properties _clone() {
return new Properties(this);
}
/**
* Gets the properties that were never read.
*
* @return a list of unused properties
*/
public synchronized List<String> getUnusedProperties() {
List<String> unused = new ArrayList<>();
for (Map.Entry<String, PropertyValue> p : _propertySet.entrySet()) {
PropertyValue pv = p.getValue();
if (!pv.used) {
unused.add(p.getKey());
}
}
return unused;
}
private Properties(Properties defaults) {
if (defaults != null) {
synchronized (defaults) {
for (Map.Entry<String, PropertyValue> p : defaults._propertySet.entrySet()) {
_propertySet.put(p.getKey(), p.getValue().clone());
}
}
_optInPrefixes = defaults._optInPrefixes; // _optInPrefixes is immutable
} else {
_optInPrefixes = List.of();
}
}
//
// Helper method called exclusively by constructors.
//
private void loadArgs(String[] args, List<String> remainingArgs) {
boolean loadConfigFiles = false;
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith("--Ice.Config")) {
String line = args[i];
if (line.indexOf('=') == -1) {
line += "=1";
}
parseLine(line.substring(2));
loadConfigFiles = true;
String[] arr = new String[args.length - 1];
System.arraycopy(args, 0, arr, 0, i);
if (i < args.length - 1) {
System.arraycopy(args, i + 1, arr, i, args.length - i - 1);
}
args = arr;
}
}
if (!loadConfigFiles) {
// If Ice.Config is not set, load from ICE_CONFIG (if set)
loadConfigFiles = !_propertySet.containsKey("Ice.Config");
}
if (loadConfigFiles) {
loadConfig();
}
args = parseIceCommandLineOptions(args);
if (remainingArgs != null) {
remainingArgs.clear();
if (args.length > 0) {
remainingArgs.addAll(Arrays.asList(args));
}
}
}
private void parse(BufferedReader in) {
try {
String line;
while ((line = in.readLine()) != null) {
parseLine(line);
}
} catch (IOException ex) {
throw new SyscallException(ex);
}
}
private void parseLine(String line) {
String key = "";
String value = "";
int state = ParseStateKey;
String whitespace = "";
String escapedspace = "";
boolean finished = false;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
switch (state) {
case ParseStateKey: {
switch (c) {
case '\\':
if (i < line.length() - 1) {
c = line.charAt(++i);
switch (c) {
case '\\':
case '#':
case '=':
key += whitespace;
whitespace = "";
key += c;
break;
case ' ':
if (!key.isEmpty()) {
whitespace += c;
}
break;
default:
key += whitespace;
whitespace = "";
key += '\\';
key += c;
break;
}
} else {
key += whitespace;
key += c;
}
break;
case ' ':
case '\t':
case '\r':
case '\n':
if (!key.isEmpty()) {
whitespace += c;
}
break;
case '=':
whitespace = "";
state = ParseStateValue;
break;
case '#':
finished = true;
break;
default:
key += whitespace;
whitespace = "";
key += c;
break;
}
break;
}
case ParseStateValue: {
switch (c) {
case '\\':
if (i < line.length() - 1) {
c = line.charAt(++i);
switch (c) {
case '\\':
case '#':
case '=':
value += value.isEmpty() ? escapedspace : whitespace;
whitespace = "";
escapedspace = "";
value += c;
break;
case ' ':
whitespace += c;
escapedspace += c;
break;
default:
value += value.isEmpty() ? escapedspace : whitespace;
whitespace = "";
escapedspace = "";
value += '\\';
value += c;
break;
}
} else {
value += value.isEmpty() ? escapedspace : whitespace;
value += c;
}
break;
case ' ':
case '\t':
case '\r':
case '\n':
if (!value.isEmpty()) {
whitespace += c;
}
break;
case '#':
finished = true;
break;
default:
value += value.isEmpty() ? escapedspace : whitespace;
whitespace = "";
escapedspace = "";
value += c;
break;
}
break;
}
}
if (finished) {
break;
}
}
value += escapedspace;
if ((state == ParseStateKey && !key.isEmpty()) || (state == ParseStateValue && key.isEmpty())) {
Util.getProcessLogger().warning("invalid config file entry: \"" + line + "\"");
return;
} else if (key.isEmpty()) {
return;
}
setProperty(key, value);
}
private void loadConfig() {
String value = getIceProperty("Ice.Config");
if (value.isEmpty() || "1".equals(value)) {
try {
value = System.getenv("ICE_CONFIG");
if (value == null) {
value = "";
}
} catch (java.lang.SecurityException ex) {
value = "";
}
}
if (!value.isEmpty()) {
for (String file : value.split(",")) {
load(file.trim());
}
_propertySet.put("Ice.Config", new PropertyValue(value, true));
}
}
/**
* Searches a property array for a property with the given key.
*
* @param key the key to search for
* @param propertyArray the property array to search
* @return the property if found, {@code null} otherwise
*/
static Property findProperty(String key, PropertyArray propertyArray) {
for (Property prop : propertyArray.properties()) {
String pattern = prop.pattern();
// If the key is an exact match, return the property unless it has a property class
// which is prefix only. If the key is a regex match, return the property. A property
// cannot have a property class and use regex.
if (key.equals(pattern)) {
if (prop.propertyArray() != null && prop.propertyArray().prefixOnly()) {
return null;
}
return prop;
} else if (prop.usesRegex() && key.matches(pattern)) {
return prop;
}
// If the property has a property class, check if the key is a prefix of the property.
if (prop.propertyArray() != null) {
// Check if the key is a prefix of the property.
// The key must be:
// - shorter than the property pattern
// - the property pattern must start with the key
// - the pattern character after the key must be a dot
if (key.length() > pattern.length() && key.startsWith(pattern) && key.charAt(pattern.length()) == '.') {
String substring = key.substring(pattern.length() + 1);
// Check if the suffix is a valid property. If so, return it. If it's not,
// continue searching the current property array.
Property foundProp = findProperty(substring, prop.propertyArray());
if (foundProp != null) {
return foundProp;
}
}
}
}
return null;
}
/**
* Validates the properties for a given prefix.
*
* @param prefix the prefix to validate
* @param properties the properties to consider
* @param propertyArray the property array to search against
* @throws PropertyException if any unknown properties are found
*/
static void validatePropertiesWithPrefix(String prefix, Properties properties, PropertyArray propertyArray) {
// Do not check for unknown properties if Ice prefix, ie Ice, Glacier2, etc
for (PropertyArray props : PropertyNames.validProps) {
if (prefix.startsWith(props.name() + ".")) {
return;
}
}
var unknownProperties =
properties.getPropertiesForPrefix(prefix + ".").keySet().stream()
.filter(
key ->
findProperty(
key.substring(prefix.length() + 1),
propertyArray)
== null)
.collect(Collectors.toList());
if (unknownProperties.size() > 0) {
throw new PropertyException("found unknown properties for " + propertyArray.name() + ": '" + prefix
+ "'\n " + String.join("\n ", unknownProperties));
}
}
/**
* Finds the Ice property array for a given property name.
*
* @param key the property key
* @return the property array if found, {@code null} otherwise
*/
private static PropertyArray findIcePropertyArray(String key) {
int dotPos = key.indexOf('.');
// If the key doesn't contain a dot, it's not a valid Ice property.
if (dotPos == -1) {
return null;
}
String prefix = key.substring(0, dotPos);
return Arrays.stream(PropertyNames.validProps)
.filter(properties -> properties.name().equals(prefix))
.findFirst()
.orElse(null);
}
/**
* Finds the default value for an Ice property.
*
* @param key the Ice property name
* @return the default value for the property, or the empty string if the property doesn't have a default value
* @throws PropertyException if the property is unknown
*/
private static String getDefaultProperty(String key) {
PropertyArray propertyArray = findIcePropertyArray(key);
if (propertyArray == null) {
throw new PropertyException("unknown Ice property: " + key);
}
Property prop = findProperty(key.substring(propertyArray.name().length() + 1), propertyArray);
if (prop == null) {
throw new PropertyException("unknown Ice property: " + key);
}
return prop.defaultValue();
}
static class PropertyValue {
public PropertyValue(String v, boolean u) {
value = v;
used = u;
}
public PropertyValue clone() {
return new PropertyValue(value, used);
}
public String value;
public boolean used;
}
}