org.apache.openjpa.lib.conf.ConfigurationImpl Maven / Gradle / Ivy
The newest version!
/*
* 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.openjpa.lib.conf;
import java.awt.Image;
import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.EventSetDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyDescriptor;
import java.io.Externalizable;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.log.LogFactory;
import org.apache.openjpa.lib.log.LogFactoryImpl;
import org.apache.openjpa.lib.log.NoneLogFactory;
import org.apache.openjpa.lib.util.Closeable;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.MultiClassLoader;
import org.apache.openjpa.lib.util.ParseException;
import org.apache.openjpa.lib.util.Services;
import org.apache.openjpa.lib.util.StringDistance;
import org.apache.openjpa.lib.util.StringUtil;
/**
* Default implementation of the {@link Configuration} interface.
* Subclasses can choose to obtain configuration
* information from JNDI, Properties, a Bean-builder, etc. This class
* provides base configuration functionality, including serialization,
* the equals
and hashCode
contracts, and default
* property loading.
* Property descriptors for {@link Value} instances are constructed from
* the {@link Localizer} for the package of the configuration class. The
* following localized strings will be used for describing a value, where
* name is the last token of the value's property string:
*
* - name-name: The name that will be displayed for the
* option in a user interface; required.
* - name-desc: A brief description of the option; required.
* - name-type: The type or category name for this option;
* required.
* - name-expert: True if this is an expert option, false
* otherwise; defaults to false.
* - name-values: Set of expected or common values, excluding
* alias keys; optional.
* - name-interface: The class name of an interface whose
* discoverable implementations should be included in the set of expected
* or common values; optional.
* - name-cat: The hierarchical category for the property
* name, separated by ".".
*
- name-displayorder: The order in which the property should
* be displayer.
*
*
* @author Abe White
*/
public class ConfigurationImpl
implements Configuration, Externalizable, ValueListener {
private static final String SEP = J2DoPrivHelper.getLineSeparator();
private static final Localizer _loc = Localizer.forPackage(ConfigurationImpl.class);
public ObjectValue logFactoryPlugin;
public StringValue id;
private String _product = null;
private int _readOnlyState = INIT_STATE_LIQUID;
private Map _props = null;
private boolean _globals = false;
private String _auto = null;
private final List _vals = new ArrayList<>();
private Set _supportedKeys = new TreeSet<>();
// property listener helper
private PropertyChangeSupport _changeSupport = null;
// cache descriptors
private PropertyDescriptor[] _pds = null;
private MethodDescriptor[] _mds = null;
// An additional (and optional) classloader to load custom plugins.
private ClassLoader _userCL;
//Ant task needs to defer the resource loading
//until the classpath setting is loaded properly
private boolean _deferResourceLoading = false;
/**
* Default constructor. Attempts to load default properties through
* system's configured {@link ProductDerivation}s.
*/
public ConfigurationImpl() {
this(true);
}
/**
* Constructor.
*
* @param loadGlobals whether to attempt to load the global properties
*/
public ConfigurationImpl(boolean loadGlobals) {
setProductName("openjpa");
logFactoryPlugin = addPlugin("Log", true);
String[] aliases = new String[]{
"true", LogFactoryImpl.class.getName(),
"openjpa", LogFactoryImpl.class.getName(),
"commons", "org.apache.openjpa.lib.log.CommonsLogFactory",
"log4j", "org.apache.openjpa.lib.log.Log4JLogFactory",
"log4j2", "org.apache.openjpa.lib.log.Log4J2LogFactory",
"slf4j", "org.apache.openjpa.lib.log.SLF4JLogFactory",
"jul", "org.apache.openjpa.lib.log.JULLogFactory",
"none", NoneLogFactory.class.getName(),
"false", NoneLogFactory.class.getName(),
};
logFactoryPlugin.setAliases(aliases);
logFactoryPlugin.setDefault(aliases[0]);
logFactoryPlugin.setString(aliases[0]);
logFactoryPlugin.setInstantiatingGetter("getLogFactory");
id = addString("Id");
if (loadGlobals)
loadGlobals();
}
/**
* Automatically load global values from the system's
* {@link ProductDerivation}s, and from System properties.
*/
public boolean loadGlobals() {
MultiClassLoader loader = AccessController
.doPrivileged(J2DoPrivHelper.newMultiClassLoaderAction());
loader.addClassLoader(AccessController.doPrivileged(
J2DoPrivHelper.getContextClassLoaderAction()));
loader.addClassLoader(getClass().getClassLoader());
ConfigurationProvider provider = ProductDerivations.loadGlobals(loader);
if (provider != null)
provider.setInto(this);
// let system properties override other globals
try {
Properties systemProperties = AccessController.doPrivileged(
J2DoPrivHelper.getPropertiesAction());
HashMap sysPropHM = null;
synchronized(systemProperties) {
// Prevent concurrent modification of systemProperties until HashMap ctor is completed.
sysPropHM = new HashMap(systemProperties);
}
fromProperties(sysPropHM);
} catch (SecurityException se) {
// security manager might disallow
}
_globals = true;
if (provider == null) {
Log log = getConfigurationLog();
if (log.isTraceEnabled())
log.trace(_loc.get("no-default-providers"));
return false;
}
return true;
}
@Override
public String getProductName() {
return _product;
}
@Override
public void setProductName(String name) {
_product = name;
}
@Override
public LogFactory getLogFactory() {
if (logFactoryPlugin.get() == null)
logFactoryPlugin.instantiate(LogFactory.class, this);
return (LogFactory) logFactoryPlugin.get();
}
@Override
public void setLogFactory(LogFactory logFactory) {
logFactoryPlugin.set(logFactory);
}
@Override
public String getLog() {
return logFactoryPlugin.getString();
}
@Override
public void setLog(String log) {
logFactoryPlugin.setString(log);
}
@Override
public Log getLog(String category) {
return getLogFactory().getLog(category);
}
@Override
public String getId() {
return id.get();
}
@Override
public void setId(String id) {
this.id.set(id);
}
/**
* Returns the logging channel openjpa.Runtime
by default.
*/
@Override
public Log getConfigurationLog() {
return getLog("openjpa.Runtime");
}
@Override
public Value[] getValues() {
return (Value[]) _vals.toArray(new Value[_vals.size()]);
}
/**
* Gets the registered Value for the given propertyName.
*
* @param property can be either fully-qualified name or the simple name
* with which the value has been registered. A value may have multiple
* equivalent names and this method searches with all equivalent names.
*/
@Override
public Value getValue(String property) {
if (property == null)
return null;
// search backwards so that custom values added after construction
// are found quickly, since this will be the std way of accessing them
for (int i = _vals.size()-1; i >= 0; i--) {
if (_vals.get(i).matches(property))
return _vals.get(i);
}
return null;
}
@Override
public void setReadOnly(int newState) {
if (newState >= _readOnlyState) {
_readOnlyState = newState;
}
}
public boolean isDeferResourceLoading() {
return _deferResourceLoading;
}
public void setDeferResourceLoading(boolean deferResourceLoading) {
this._deferResourceLoading = deferResourceLoading;
}
@Override
public void instantiateAll() {
StringWriter errs = null;
PrintWriter stack = null;
String getterName;
Method getter;
Object getterTarget;
for(Value val : _vals) {
getterName = val.getInstantiatingGetter();
if (getterName == null)
continue;
getterTarget = this;
if (getterName.startsWith("this.")) {
getterName = getterName.substring("this.".length());
getterTarget = val;
}
try {
getter = getterTarget.getClass().getMethod(getterName,
(Class[]) null);
getter.invoke(getterTarget, (Object[]) null);
} catch (Throwable t) {
if (t instanceof InvocationTargetException)
t = ((InvocationTargetException) t).getTargetException();
if (errs == null) {
errs = new StringWriter();
stack = new PrintWriter(errs);
} else
errs.write(SEP);
t.printStackTrace(stack);
stack.flush();
}
}
if (errs != null)
throw new RuntimeException(_loc.get("get-prop-errs",
errs.toString()).getMessage());
}
@Override
public boolean isReadOnly() {
return _readOnlyState==INIT_STATE_FROZEN;
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
if (_changeSupport == null)
_changeSupport = new PropertyChangeSupport(this);
_changeSupport.addPropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
if (_changeSupport != null)
_changeSupport.removePropertyChangeListener(listener);
}
@Override
public void valueChanged(Value val) {
if (_changeSupport == null && _props == null)
return;
String newString = val.getString();
if (_changeSupport != null)
_changeSupport.firePropertyChange(val.getProperty(), null, newString);
// keep cached props up to date
if (_props != null) {
if (newString == null)
Configurations.removeProperty(val.getProperty(), _props);
else if (Configurations.containsProperty(val, _props)
|| val.getDefault() == null
|| !val.getDefault().equals(newString))
setValue(_props, val);
}
}
/**
* Closes all closeable values and plugins.
*/
@Override
public final void close() {
ProductDerivations.beforeClose(this);
preClose();
ObjectValue val;
for(Value v : _vals) {
if (v instanceof Closeable) {
try {
((Closeable)v).close();
}
catch (Exception e) {
// noop
}
continue;
}
if (!(v instanceof ObjectValue))
continue;
val = (ObjectValue) v;
if (val.get() instanceof Closeable) {
try {
((Closeable) val.get()).close();
} catch (Exception e) {
}
}
}
}
/**
* Invoked by final method {@link #close} after invoking the
* {@link ProductDerivation#beforeConfigurationClose} callbacks
* but before performing internal close operations.
*
* @since 0.9.7
*/
protected void preClose() {
}
///////////////////////////
// BeanInfo implementation
///////////////////////////
@Override
public BeanInfo[] getAdditionalBeanInfo() {
return new BeanInfo[0];
}
@Override
public BeanDescriptor getBeanDescriptor() {
return new BeanDescriptor(getClass());
}
@Override
public int getDefaultEventIndex() {
return 0;
}
@Override
public int getDefaultPropertyIndex() {
return 0;
}
@Override
public EventSetDescriptor[] getEventSetDescriptors() {
return new EventSetDescriptor[0];
}
@Override
public Image getIcon(int kind) {
return null;
}
@Override
public synchronized MethodDescriptor[] getMethodDescriptors() {
if (_mds != null)
return _mds;
PropertyDescriptor[] pds = getPropertyDescriptors();
List descs = new ArrayList<>();
for (PropertyDescriptor pd : pds) {
Method write = pd.getWriteMethod();
Method read = pd.getReadMethod();
if (read != null && write != null) {
descs.add(new MethodDescriptor(write));
descs.add(new MethodDescriptor(read));
}
}
_mds = (MethodDescriptor[]) descs.
toArray(new MethodDescriptor[descs.size()]);
return _mds;
}
@Override
public synchronized PropertyDescriptor[] getPropertyDescriptors() {
if (_pds != null)
return _pds;
_pds = new PropertyDescriptor[_vals.size()];
List failures = null;
Value val;
for (int i = 0; i < _vals.size(); i++) {
val = (Value) _vals.get(i);
try {
_pds[i] = getPropertyDescriptor(val);
} catch (MissingResourceException | IntrospectionException mre) {
if (failures == null)
failures = new ArrayList<>();
failures.add(val.getProperty());
}
}
if (failures != null)
throw new ParseException(_loc.get("invalid-property-descriptors",
failures));
return _pds;
}
/**
* Create a property descriptor for the given value.
*/
private PropertyDescriptor getPropertyDescriptor(Value val)
throws IntrospectionException {
String prop = val.getProperty();
prop = prop.substring(prop.lastIndexOf('.') + 1);
// set up property descriptor
PropertyDescriptor pd;
try {
pd = new PropertyDescriptor(Introspector.decapitalize(prop),
getClass());
} catch (IntrospectionException ie) {
// if there aren't any methods for this value(i.e., if it's a
// dynamically-added value), then an IntrospectionException will
// be thrown. Try to create a PD with no read or write methods.
pd = new PropertyDescriptor(Introspector.decapitalize(prop),
(Method) null, (Method) null);
}
pd.setDisplayName(findLocalized(prop + "-name", true, val.getScope()));
pd.setShortDescription(findLocalized(prop + "-desc", true,
val.getScope()));
pd.setExpert("true".equals(findLocalized(prop + "-expert", false,
val.getScope())));
try {
pd.setReadMethod(getClass().getMethod("get"
+ StringUtil.capitalize(prop), (Class[]) null));
pd.setWriteMethod(getClass().getMethod("set"
+ StringUtil.capitalize(prop), new Class[]
{ pd.getReadMethod().getReturnType() }));
} catch (Throwable t) {
// if an error occurs, it might be because the value is a
// dynamic property.
}
String type = findLocalized(prop + "-type", true, val.getScope());
if (type != null)
pd.setValue(ATTRIBUTE_TYPE, type);
String cat = findLocalized(prop + "-cat", false, val.getScope());
if (cat != null)
pd.setValue(ATTRIBUTE_CATEGORY, cat);
pd.setValue(ATTRIBUTE_XML, toXMLName(prop));
String order = findLocalized(prop + "-displayorder", false,
val.getScope());
if (order != null)
pd.setValue(ATTRIBUTE_ORDER, order);
// collect allowed values from alias keys, listed values, and
// interface implementors
Collection allowed = new TreeSet<>();
List aliases = Collections.emptyList();
if (val.getAliases() != null) {
aliases = Arrays.asList(val.getAliases());
for (int i = 0; i < aliases.size(); i += 2)
allowed.add(aliases.get(i));
}
String[] vals = StringUtil.split(findLocalized(prop
+ "-values", false, val.getScope()), ",", 0);
for (String s : vals)
if (!aliases.contains(s))
allowed.add(s);
try {
Class> intf = Class.forName(findLocalized(prop
+ "-interface", true, val.getScope()), false,
getClass().getClassLoader());
pd.setValue(ATTRIBUTE_INTERFACE, intf.getName());
String[] impls = Services.getImplementors(intf);
for (String impl : impls)
if (!aliases.contains(impl))
allowed.add(impl);
} catch (Throwable t) {
}
if (!allowed.isEmpty())
pd.setValue(ATTRIBUTE_ALLOWED_VALUES, (String[]) allowed.toArray
(new String[allowed.size()]));
return pd;
}
/**
* Find the given localized string, or return null if not found.
*/
@SuppressWarnings("unchecked")
private String findLocalized(String key, boolean fatal, Class> scope) {
// find the localizer package that contains this key
Localizer loc = null;
// check the package that the value claims to be defined in, if
// available, before we start guessing.
if (scope != null) {
loc = Localizer.forPackage(scope);
try {
return loc.getFatal(key).getMessage();
} catch (MissingResourceException mse) {
}
}
for (Class cls = getClass(); cls != Object.class;
cls = cls.getSuperclass()) {
loc = Localizer.forPackage(cls);
try {
return loc.getFatal(key).getMessage();
} catch (MissingResourceException mse) {
}
}
if (fatal)
throw new MissingResourceException(key, getClass().getName(), key);
return null;
}
////////////////
// To/from maps
////////////////
/**
* An internal method to retrieve properties, to support 2 public methods,
* getAllProperties() and toProperties(boolean).
*
* @param storeDefaults
* whether or not to retrieve a property if its value is the
* default value.
*/
@Override
public Map toProperties(boolean storeDefaults) {
// clone properties before making any modifications; we need to keep
// the internal properties instance consistent to maintain equals and
// hashcode contracts
Map clone;
if (_props == null)
clone = new TreeMap<>();
else if (_props instanceof Properties)
clone = (Map) ((Properties) _props).clone();
else
clone = new TreeMap(_props);
// if no existing properties or the properties should contain entries
// with default values, add values to properties
if (_props == null || storeDefaults) {
String str;
for (Value val : _vals) {
// NOTE: Following was removed to hide Value.INVISIBLE properties, like connectionPassword
// if key in existing properties, we already know value is up to date
//if (_props != null && Configurations.containsProperty(val, _props) && val.isVisible())
// continue;
str = val.getString();
if ((str != null && (storeDefaults || !str.equals(val.getDefault()))))
setValue(clone, val);
}
if (_props == null)
_props = new TreeMap(clone);
}
return clone;
}
@Override
public void fromProperties(Map map) {
if (map == null || map.isEmpty())
return;
if (isReadOnly())
throw new IllegalStateException(_loc.get("read-only").getMessage());
// if the only previous call was to load defaults, forget them.
// this way we preserve the original formatting of the user's props
// instead of the defaults. this is important for caching on
// configuration objects
if (_globals) {
_props = null;
_globals = false;
}
// copy the input to avoid mutation issues
if (map instanceof HashMap)
map = (Map) ((HashMap) map).clone();
else if (map instanceof Properties)
map = (Map) ((Properties) map).clone();
else
map = new LinkedHashMap(map);
Map remaining = new HashMap(map);
boolean ser = true;
Object o;
for (Value val : _vals) {
o = findValue(map, val);
if (o == null)
continue;
if (o instanceof String) {
// OPENJPA-1830 Do not overwrite existing string values with "******"
if ((!Objects.equals((String) o, val.getString())) &&
(!Objects.equals((String) o, Value.INVISIBLE)))
val.setString((String) o);
} else {
ser &= o instanceof Serializable;
val.setObject(o);
}
Configurations.removeProperty(val.getProperty(), remaining);
}
// convention is to point product at a resource with the
// .properties System property; remove that property so we
// we don't warn about it
Configurations.removeProperty("properties", remaining);
Configurations.removeProperty("Id", remaining, map);
// now warn if there are any remaining properties that there
// is an unhandled prop, and remove the unknown properties
Map.Entry entry;
for (Object value : remaining.entrySet()) {
entry = (Map.Entry) value;
Object key = entry.getKey();
if (key != null) {
warnInvalidProperty((String) key);
map.remove(key);
}
}
// cache properties
if (_props == null && ser)
_props = map;
}
@Override
public List getPropertyKeys(String propertyName) {
Value value = getValue(propertyName);
return value == null ? Collections.EMPTY_LIST : value.getPropertyKeys();
}
/**
* Gets all known property keys.
* The keys are harvested from the property names (including the equivalent names) of the registered values.
* A key may be prefixed if the corresponding property name was without a prefix.
* @see #fixPrefix(String)
* The Values that are {@linkplain Value#makePrivate() marked private} are filtered out.
*/
@Override
public Set getPropertyKeys() {
synchronized (_supportedKeys) {
if (_supportedKeys.size() == 0) {
for (Value val : _vals) {
if (val.isPrivate())
continue;
List keys = val.getPropertyKeys();
for (String key : keys) {
_supportedKeys.add(fixPrefix(key));
}
}
}
}
//OJ2257: Return a copy of _supportedKeys as calls to this method (e.g.
//BrokerImpl.getSupportedProperties()) may add to this set.
return new TreeSet<>(_supportedKeys);
}
/**
* Adds a prefix "openjpa."
to the given key, if necessary. A key is
* considered without prefix if it starts neither of "openjpa."
,
* "java."
, "javax."
and {@code "jakarta."}.
*/
String fixPrefix(String key) {
return (key == null || hasKnownPrefix(key)) ? key : "openjpa."+key;
}
boolean hasKnownPrefix(String key) {
String[] prefixes = ProductDerivations.getConfigurationPrefixes();
for (String prefix : prefixes) {
if (key.startsWith(prefix))
return true;
}
return false;
}
/**
* Adds o
to map
under key for val
.
* Use this method instead of attempting to add the value directly because
* this will account for the property prefix.
*/
private void setValue(Map map, Value val) {
Object key = val.getLoadKey();
if (key == null) {
List keys = val.getPropertyKeys();
for (String k : keys) {
if (hasKnownPrefix(k)) {
key = k;
break;
}
}
if (key == null) {
key = "openjpa." + val.getProperty();
}
}
Object external = val.isHidden() ? Value.INVISIBLE :
val instanceof ObjectValue ? val.getString() : val.get();
map.put(key, external);
}
/**
* Look up the given value, testing all available prefixes and all possible
* property names. Detects if the given map contains multiple keys that
* are equivalent names for the given value.
*/
private Object findValue(Map map, Value val) {
Object result = null;
List partialKeys = val.getPropertyKeys();
for (String partialKey : partialKeys) {
String key = ProductDerivations.getConfigurationKey(
partialKey, map);
if (map.containsKey(key)) {
// do not return immediately. Looping through all equivalent
// property names will detect if the Map contains multiple keys
// that are equivalent as it tries to set load key.
val.setLoadKey(key);
result = map.get(key);
}
}
return result;
}
/**
* Issue a warning that the specified property is not valid.
*/
private void warnInvalidProperty(String propName) {
if (propName != null &&
(propName.startsWith("java.") || propName.startsWith("jakarta.persistence")|| propName.startsWith("sun.")))
return;
if (!isInvalidProperty(propName))
return;
Log log = getConfigurationLog();
if (log == null || !log.isWarnEnabled())
return;
// try to find the closest string to the invalid property
// so that we can provide a hint in case of a misspelling
String closest = StringDistance.getClosestLevenshteinDistance
(propName, newPropertyList(), 15);
if (closest == null)
log.warn(_loc.get("invalid-property", propName));
else
log.warn(_loc.get("invalid-property-hint", propName, closest));
}
/**
* Return a comprehensive list of recognized map keys.
*/
private Collection newPropertyList() {
String[] prefixes = ProductDerivations.getConfigurationPrefixes();
List l = new ArrayList<>(_vals.size() * prefixes.length);
for(Value v : _vals) {
for (String prefix : prefixes) {
l.add(prefix + "." + v.getProperty());
}
}
return l;
}
/**
* Returns true if the specified property name should raise a warning
* if it is not found in the list of known properties.
*/
protected boolean isInvalidProperty(String propName) {
// handle warnings for openjpa.SomeString, but not for
// openjpa.some.subpackage.SomeString, since it might be valid for some
// specific implementation of OpenJPA
String[] prefixes = ProductDerivations.getConfigurationPrefixes();
for (String prefix : prefixes) {
if (propName.toLowerCase(Locale.ENGLISH).startsWith(prefix)
&& propName.length() > prefix.length() + 1
&& propName.indexOf('.', prefix.length()) == prefix.length()
&& propName.indexOf('.', prefix.length() + 1) == -1
&& "openjpa".equals(prefix))
return true;
}
return false;
}
//////////////////////
// Auto-configuration
//////////////////////
/**
* This method loads the named resource as a properties file. It is
* useful for auto-configuration tools so users can specify a
* properties
value with the name of a resource.
*/
public void setProperties(String resourceName) throws IOException {
if (!_deferResourceLoading) {
String anchor = null;
if (resourceName.indexOf("#") != -1) {
anchor = resourceName.substring(resourceName.lastIndexOf("#") + 1);
resourceName = resourceName.substring(0, resourceName.length() - anchor.length() - 1);
}
ProductDerivations.load(resourceName, anchor, getClass().getClassLoader()).setInto(this);
}
_auto = resourceName;
}
/**
* This method loads the named file as a properties file. It is
* useful for auto-configuration tools so users can specify a
* propertiesFile
value with the name of a file.
*/
public void setPropertiesFile(File file) throws IOException {
ProductDerivations.load(file, null, getClass().getClassLoader()).
setInto(this);
setDeferResourceLoading(false);
_auto = file.toString();
}
/**
* Return the resource that was set via auto-configuration methods
* {@link #setProperties} or {@link #setPropertiesFile}, or null if none.
*/
public String getPropertiesResource() {
return _auto;
}
/////////////
// Utilities
/////////////
/**
* Performs an equality check based on equality of values.
* {@link Value#equals(Object) Equality} of Values varies if the Value is
* {@link Value#isDynamic() dynamic}.
*/
@Override
public boolean equals(Object other) {
if (other == this)
return true;
if (other == null)
return false;
if (!getClass().equals(other.getClass()))
return false;
// compare properties
ConfigurationImpl conf = (ConfigurationImpl) other;
if (_vals.size() != conf.getValues().length)
return false;
for(Value v : _vals) {
String propName = v.getProperty();
Value thisV = this.getValue(propName);
Value thatV = conf.getValue(propName);
if (!thisV.equals(thatV)) {
return false;
}
}
return true;
}
/**
* Computes hash code based on the hashCodes of the values.
* {@link Value#hashCode() HashCode} of a Value varies if the Value is
* {@link Value#isDynamic() dynamic}.
*/
@Override
public int hashCode() {
int hash = 0;
for(Value v : _vals) {
hash += v.hashCode();
}
return hash;
}
/**
* Convert propName
to a lowercase-with-hyphens-style string.
* This algorithm is only designed for mixes of uppercase and lowercase
* letters and lone digits. A more sophisticated conversion should probably
* be handled by a proper parser generator or regular expressions.
*/
public static String toXMLName(String propName) {
if (propName == null)
return null;
StringBuilder buf = new StringBuilder();
char c;
for (int i = 0; i < propName.length(); i++) {
c = propName.charAt(i);
// convert sequences of all-caps to downcase with dashes around
// them. put a trailing cap that is followed by downcase into the
// downcase word.
if (i != 0 && Character.isUpperCase(c)
&& (Character.isLowerCase(propName.charAt(i-1))
|| (i > 1 && i < propName.length() - 1
&& Character.isUpperCase(propName.charAt(i-1))
&& Character.isLowerCase(propName.charAt(i+1)))))
buf.append('-');
// surround sequences of digits with dashes.
if (i != 0
&& ((!Character.isLetter(c) && Character.isLetter(propName
.charAt(i - 1)))
|| (Character.isLetter(c) && !Character.isLetter(propName
.charAt(i - 1)))))
buf.append('-');
buf.append(Character.toLowerCase(c));
}
return buf.toString();
}
/**
* Implementation of the {@link Externalizable} interface to read from
* the properties written by {@link #writeExternal}.
*/
@Override
@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
fromProperties((Map) in.readObject());
_props = (Map) in.readObject();
_globals = in.readBoolean();
}
/**
* Implementation of the {@link Externalizable} interface to write
* the properties returned by {@link #toProperties}.
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(toProperties(true));
out.writeObject(_props);
out.writeBoolean(_globals);
}
/**
* Uses {@link #toProperties} and {@link #fromProperties} to clone
* configuration.
*/
@Override
public Object clone() {
try {
Constructor cons = getClass().getConstructor
(new Class[]{ boolean.class });
ConfigurationImpl clone = (ConfigurationImpl) cons.newInstance
(new Object[]{ Boolean.FALSE });
clone.fromProperties(toProperties(true));
clone._props = (_props == null) ? null : new HashMap(_props);
clone._globals = _globals;
return clone;
} catch (RuntimeException re) {
throw re;
} catch (Exception e) {
throw new ParseException(e);
}
}
@Override
public boolean removeValue(Value val) {
if (!_vals.remove(val))
return false;
val.removeListener(this);
return true;
}
@Override
public T addValue(T val) {
_vals.add(val);
val.addListener(this);
return val;
}
/**
* Add the given value to the set of configuration properties.
*/
public StringValue addString(String property) {
StringValue val = new StringValue(property);
addValue(val);
return val;
}
/**
* Add the given value to the set of configuration properties.
*/
public FileValue addFile(String property) {
FileValue val = new FileValue(property);
addValue(val);
return val;
}
/**
* Add the given value to the set of configuration properties.
*/
public IntValue addInt(String property) {
IntValue val = new IntValue(property);
addValue(val);
return val;
}
/**
* Add the given value to the set of configuration properties.
*/
public DoubleValue addDouble(String property) {
DoubleValue val = new DoubleValue(property);
addValue(val);
return val;
}
/**
* Add the given value to the set of configuration properties.
*/
public BooleanValue addBoolean(String property) {
BooleanValue val = new BooleanValue(property);
addValue(val);
val.setDefault("false");
return val;
}
/**
* Add the given value to the set of configuration properties.
*/
public StringListValue addStringList(String property) {
StringListValue val = new StringListValue(property);
addValue(val);
return val;
}
/**
* Add the given value to the set of configuration properties.
*/
public ObjectValue addObject(String property) {
ObjectValue val = new ObjectValue(property);
addValue(val);
return val;
}
/**
* Add the given value to the set of configuration properties.
*/
public PluginValue addPlugin(String property, boolean singleton) {
PluginValue val = new PluginValue(property, singleton);
addValue(val);
return val;
}
/**
* Add the given value to the set of configuration properties.
*/
public PluginListValue addPluginList(String property) {
PluginListValue val = new PluginListValue(property);
addValue(val);
return val;
}
@Override
public ClassLoader getUserClassLoader() {
return _userCL;
}
@Override
public void setUserClassLoader(ClassLoader cl) {
_userCL = cl;
}
}