| /* |
| * 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() + "]"; |
| } |
| } |