blob: ab364d1150f437e93551e096145f696e5bc24359 [file] [log] [blame]
/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.ParseException;
/**
* A configuration value.
*
* @author Marc Prud'hommeaux
* @author Pinaki Poddar
*/
public abstract class Value implements Cloneable {
private static final String[] EMPTY_ALIASES = new String[0];
private static final Localizer s_loc = Localizer.forPackage(Value.class);
public static final String INVISIBLE = "******";
private String prop = null;
private String loadKey = null;
private String def = null;
private String[] aliases = null;
private String getter = null;
private List<ValueListener> listeners = null;
private boolean aliasListComprehensive = false;
private Class scope = null;
private boolean isDynamic = false;
private String originalValue = null;
private Set<String> otherNames = null;
private boolean _hidden = false;
private boolean _private = false;
/**
* Default constructor.
*/
public Value() {
}
/**
* Constructor. Supply the property name.
*
* @see #setProperty
*/
public Value(String prop) {
setProperty(prop);
}
/**
* The property name that will be used when setting or
* getting this value in a {@link Map}.
*/
public String getProperty() {
return prop;
}
/**
* The property name that will be used when setting or
* getting this value in a {@link Map}.
*/
public void setProperty(String prop) {
this.prop = prop;
}
/**
* Adds a moniker that is equivalent to the original property key used
* during construction.
*
* @since 2.0.0
*/
public void addEquivalentKey(String other) {
if (otherNames == null)
otherNames = new HashSet<>();
otherNames.add(other);
}
/**
* Gets the unmodifiable view of the equivalent keys or an empty set if
* no equivalent key has been added.
*
* @since 2.0.0
*/
public Set<String> getEquivalentKeys() {
return otherNames == null ? Collections.EMPTY_SET
: Collections.unmodifiableSet(otherNames);
}
/**
* Gets unmodifiable view of all the property keys set on this receiver.
* The 0-th element in the returned list is always the same as the original
* key returned by {@link #getProperty()} method.
*
* @since 2.0.0
*/
public List<String> getPropertyKeys() {
List<String> result = new ArrayList<>(1 +
(otherNames ==null ? 0 : otherNames.size()));
result.add(getProperty());
if (otherNames != null)
result.addAll(otherNames);
return Collections.unmodifiableList(result);
}
/**
* Affirms if the given key matches the property (or any of its equivalent).
*
* @since 2.0.0
*/
public boolean matches(String p) {
return getProperty().equals(p) ||
(otherNames != null && otherNames.contains(p));
}
/**
* The key under which this value was loaded, or null.
*/
public String getLoadKey() {
return loadKey;
}
/**
* Sets key under which this value was loaded.
* @exception if called with a non-null key which is different from an
* already loaded key.
*/
public void setLoadKey(String key) {
if (this.loadKey != null && key != null && !this.loadKey.equals(key))
throw new ParseException(s_loc.get("multiple-load-key",
loadKey, key));
loadKey = key;
}
/**
* Aliases for the value in the form key1, value1, key2, value2, ...
* All alias values must be in string form.
*/
public String[] getAliases() {
return (aliases == null) ? EMPTY_ALIASES : aliases;
}
/**
* Aliases for the value in the form key1, value1, key2, value2, ...
* All alias values must be in string form.
* <p>
* To avoid potential side-effects, this method copies the array passed in.
*/
public void setAliases(String[] aliases) {
String [] aStrings = new String[aliases.length];
System.arraycopy(aliases, 0, aStrings, 0, aStrings.length);
this.aliases = aStrings;
}
/**
* Replaces an existing alias, or adds the given alias to the front of the
* alias list if it does not already exist. All alias values must be in
* string form.
*/
public void setAlias(String key, String value) {
aliases = setAlias(key, value, aliases);
}
/**
* Set an alias into a current alias list, returning the new list.
*/
protected String[] setAlias(String key, String value, String[] aliases) {
if (aliases == null)
aliases = EMPTY_ALIASES;
for (int i = 0; i < aliases.length; i += 2) {
if (key.equals(aliases[i])) {
aliases[i + 1] = value;
return aliases;
}
}
// add as new alias
String[] newAliases = new String[aliases.length + 2];
System.arraycopy(aliases, 0, newAliases, 2, aliases.length);
newAliases[0] = key;
newAliases[1] = value;
return newAliases;
}
/**
* Whether or not the alias list defines all possible settings for this
* value. If so, an error will be generated when attempting to invoke
* any method on this value with an unknown option.
*/
public boolean isAliasListComprehensive() {
return aliasListComprehensive;
}
/**
* Whether or not the alias list defines all possible settings for this
* value. If so, an error will be generated when attempting to invoke
* any method on this value with an unknown option.
*/
public void setAliasListComprehensive(boolean aliasListIsComprehensive) {
this.aliasListComprehensive = aliasListIsComprehensive;
}
/**
* Alias the given setting.
*/
public String alias(String str) {
return alias(str, aliases, false);
}
/**
* Alias the given setting.
*/
protected String alias(String str, String[] aliases, boolean nullNotFound) {
if (str != null)
str = str.trim();
if (aliases == null || aliases.length == 0)
return (nullNotFound) ? null : str;
boolean empty = str != null && str.length() == 0;
for (int i = 1; i < aliases.length; i += 2)
if (Objects.equals(str, aliases[i])
|| (empty && aliases[i] == null))
return aliases[i - 1];
return (nullNotFound) ? null : str;
}
/**
* Unalias the given setting.
*/
public String unalias(String str) {
return unalias(str, aliases, false);
}
/**
* Unalias the given setting.
*/
protected String unalias(String str, String[] aliases,
boolean nullNotFound) {
if (str != null)
str = str.trim();
boolean empty = str != null && str.length() == 0;
if (str == null || (empty && def != null))
str = def;
if (aliases != null)
for (int i = 0; i < aliases.length; i += 2)
if (Objects.equals(str, aliases[i])
|| Objects.equals(str, aliases[i + 1])
|| (empty && aliases[i] == null))
return aliases[i + 1];
if (isAliasListComprehensive() && aliases != null)
throw new ParseException(s_loc.get("invalid-enumerated-config",
getProperty(), str, Arrays.asList(aliases)));
return (nullNotFound) ? null : str;
}
/**
* The default value for the property as a string.
*/
public String getDefault() {
return def;
}
/**
* The default value for the property as a string.
*/
public void setDefault(String def) {
this.def = def;
}
/**
* The name of the getter method for the instantiated value of this
* property(as opposed to the string value)
*/
public String getInstantiatingGetter() {
return getter;
}
/**
* The name of the getter method for the instantiated value of this
* property(as opposed to the string value). If the string starts with
* <code>this.</code>, then the getter will be looked up on the value
* instance itself. Otherwise, the getter will be looked up on the
* configuration instance.
*/
public void setInstantiatingGetter(String getter) {
this.getter = getter;
}
/**
* A class defining the scope in which this value is defined. This will
* be used by the configuration framework to look up metadata about
* the value.
*/
public Class getScope() {
return scope;
}
/**
* A class defining the scope in which this value is defined. This will
* be used by the configuration framework to look up metadata about
* the value.
*/
public void setScope(Class cls) {
scope = cls;
}
/**
* Return a stringified version of this value. If the current value has
* a short alias key, the alias key is returned.
*/
public String getString() {
return alias(getInternalString());
}
/**
* Set this value from the given string. If the given string is null or
* empty and a default is defined, the default is used. If the given
* string(or default) is an alias key, it will be converted to the
* corresponding value internally.
* <br>
* If this Value is being set to a non-default value for the first time
* (as designated by <code>originalString</code> being null), then the
* value is remembered as <em>original</em>. This original value is used
* for equality and hashCode computation if this Value is
* {@link #isDynamic() dynamic}.
*
*/
public void setString(String val) {
assertChangeable();
String str = unalias(val);
try {
setInternalString(str);
if (originalValue == null && val != null && !isDefault(val)) {
originalValue = getString();
}
} catch (ParseException pe) {
throw pe;
} catch (RuntimeException re) {
throw new ParseException(prop + ": " + val, re);
}
}
/**
* Set this value as an object.
* <br>
* If this Value is being set to a non-default value for the first time
* (as designated by <code>originalString</code> being null), then the
* value is remembered as <em>original</em>. This original value is used
* for equality and hashCode computation if this Value is
* {@link #isDynamic() dynamic}.
*
*/
public void setObject(Object obj) {
// if setting to null set as string to get defaults into play
if (obj == null && def != null)
setString(null);
else {
try {
setInternalObject(obj);
if (originalValue == null && obj != null && !isDefault(obj)) {
originalValue = getString();
}
} catch (ParseException pe) {
throw pe;
} catch (RuntimeException re) {
throw new ParseException(prop + ": " + obj, re);
}
}
}
/**
* Gets the original value. Original value denotes the Stringified form of
* this Value, from which it has been set, if ever. If this Value has never
* been set to a non-default value, then returns the default value, which
* itself can be null.
*
* @since 1.1.0
*/
public String getOriginalValue() {
return (originalValue == null) ? getDefault() : originalValue;
}
boolean isDefault(Object val) {
return val != null && val.toString().equals(getDefault());
}
/**
* Returns the type of the property that this Value represents.
*/
public abstract Class<?> getValueType();
/**
* Return the internal string form of this value.
*/
protected abstract String getInternalString();
/**
* Set this value from the given string.
*/
protected abstract void setInternalString(String str);
/**
* Set this value from an object.
*/
protected abstract void setInternalObject(Object obj);
/**
* Gets unmodifable list of listeners for value changes.
*/
public List<ValueListener> getListeners() {
return Collections.unmodifiableList(this.listeners);
}
/**
* Listener for value changes.
*/
public void addListener(ValueListener listener) {
if (listener == null)
return;
if (listeners == null)
listeners = new ArrayList<>();
listeners.add(listener);
}
public void removeListener(ValueListener listener) {
if (listener == null)
return;
listeners.remove(listener);
}
/**
* Subclasses should call this method when their internal value changes.
*/
public void valueChanged() {
if (listeners == null)
return;
for (ValueListener listener : listeners) {
listener.valueChanged(this);
}
}
/**
* Asserts if this receiver can be changed.
* Subclasses <em>must</em> invoke this method before changing its
* internal state.
*
* This receiver can not be changed if all of the following is true
* <LI>this receiver is not dynamic
* <LI>ValueListener attached to this receiver is a Configuration
* <LI>Configuration is read-only
*/
protected void assertChangeable() {
if (!isDynamic() && containsReadOnlyConfigurationAsListener()) {
throw new RuntimeException(s_loc.get("veto-change",
this.getProperty()).toString());
}
}
boolean containsReadOnlyConfigurationAsListener() {
if (listeners == null)
return false;
for (ValueListener listener : listeners) {
if (listener instanceof Configuration
&& ((Configuration)listener).isReadOnly())
return true;
}
return false;
}
/**
* Sets if this receiver can be mutated even when the configuration it
* belongs to has been {@link Configuration#isReadOnly() frozen}.
*
* @since 1.1.0
*/
public void setDynamic(boolean flag) {
isDynamic = flag;
}
/**
* Affirms if this receiver can be mutated even when the configuration it
* belongs to has been {@link Configuration#isReadOnly() frozen}.
*
* @since 1.1.0
*/
public boolean isDynamic() {
return isDynamic;
}
/**
* Use {@link #getOriginalValue() original value} instead of
* {@link #getString() current value} because they are one and the same
* for non-dynamic Values and ensures that modifying dynamic Values do not
* impact equality or hashCode contract.
*/
@Override
public int hashCode() {
String str = (isDynamic()) ? getOriginalValue() : getString();
int strHash = (str == null) ? 0 : str.hashCode();
int propHash = (prop == null) ? 0 : prop.hashCode();
return strHash ^ propHash;
}
/**
* Use {@link #getOriginalValue() original value} instead of
* {@link #getString() current value} because they are one and the same
* for non-dynamic Values and ensures that modifying dynamic Values do not
* impact equality or hashCode contract.
*/
@Override
public boolean equals(Object other) {
if (other == this)
return true;
if (!(other instanceof Value))
return false;
Value o = (Value) other;
String thisStr = (isDynamic()) ? getOriginalValue() : getString();
String thatStr = (isDynamic()) ? o.getOriginalValue() : o.getString();
return (isDynamic() == o.isDynamic())
&& Objects.equals(prop, o.getProperty())
&& Objects.equals(thisStr, thatStr);
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException cnse) {
return null;
}
}
/**
* Affirms if the value for this Value is visible.
* Certain sensitive value such as password can be made invisible
* so that it is not returned to the user code.
*/
public boolean isHidden() {
return _hidden;
}
/**
* Hides the value of this Value from being output to the caller.
*/
public void hide() {
_hidden = true;
}
/**
* Affirms if this Value is used for internal purpose only and not exposed as a supported property.
* @see Configuration#getPropertyKeys()
*/
public boolean isPrivate() {
return _private;
}
/**
* Marks this Value for internal purpose only.
*/
public void makePrivate() {
_private = true;
}
/**
* Get the actual data stored in this value.
*/
public abstract Object get();
@Override
public String toString() {
return getProperty()+ ":" + get() + "[" + getValueType().getName() + "]";
}
}