| /* |
| * 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.tamaya.spi; |
| |
| import java.io.Serializable; |
| import java.util.*; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * Class modelling the result of a request for a property createValue. A property createValue is basically identified by its key. |
| * There might be reasons, where one want to further analyze, which PropertySources provided a createValue and which not, so |
| * it is possible to createObject a PropertyValue with a null createValue. |
| * |
| * A PropertyValue represents an abstract data point in a configuration structure read. PropertyValues actually |
| * represent a tree, with additional functionality for representing data lists/arrays using indexed children |
| * names. This allows to support a full mapping of common document based configuration formats, such as JSON, YAML, |
| * XML and more. |
| */ |
| public class PropertyValue implements Serializable, Iterable<PropertyValue>{ |
| |
| private static final long serialVersionUID = 2L; |
| private static final int EMPTY = 0; |
| private static final String SOURCE = "source"; |
| /** The requested key. */ |
| private String key; |
| /** The createValue. */ |
| private String value; |
| /** The parent value, null if it's a root value. */ |
| private PropertyValue parent; |
| /** The createValue version, used for determining config changes. */ |
| private AtomicInteger version = new AtomicInteger(); |
| /** Flag to mark a createValue as immutable. */ |
| private boolean immutable; |
| /** Additional metadata provided by the provider. */ |
| private final Map<String,String> metaEntries = new HashMap<>(); |
| |
| /** |
| * Enum of the different supported value types. |
| */ |
| public enum ValueType{ |
| /** A multi valued property value, which contains named child properties. */ |
| MAP, |
| /** A multi valued property value, which contains unnamed child properties. */ |
| ARRAY, |
| /** A simple value property. */ |
| VALUE |
| } |
| |
| |
| /** |
| * Creates a new (invisible) root, which is a node with an empty name. |
| * @return a new empty root, never null. |
| */ |
| public static ObjectValue createObject(){ |
| return new ObjectValue(""); |
| } |
| |
| /** |
| * Creates a new (invisible) root, which is a node with an empty name. |
| * @return a new empty root, never null. |
| */ |
| public static ListValue createList(){ |
| return new ListValue(""); |
| } |
| |
| /** |
| * Creates a new createValue of type {@link ValueType#VALUE}. |
| * @param key the key, not {@code null}. |
| * @param value the createValue, not null. |
| * @return a new createValue instance. |
| */ |
| public static PropertyValue createValue(String key, String value){ |
| return new PropertyValue(key, value); |
| } |
| |
| /** |
| * Creates a new createValue of type {@link ValueType#ARRAY}. |
| * @param key the key, not {@code null}. |
| * @return a new createValue instance. |
| */ |
| public static ListValue createList(String key){ |
| return new ListValue(key); |
| } |
| |
| /** |
| * Creates a new createValue of type {@link ValueType#MAP}. |
| * @param key the key, not {@code null}. |
| * @return a new createValue instance. |
| */ |
| public static ObjectValue createObject(String key){ |
| return new ObjectValue(key); |
| } |
| |
| /** |
| * Maps a mapProperties of {@code Map<String,String>} to a {@code Map<String,PropertyValue>}. |
| * @param config the String based mapProperties, not {@code null}. |
| * @param source the source name, not {@code null}. |
| * @return the corresponding createValue based mapProperties. |
| */ |
| public static Map<String, PropertyValue> mapProperties(Map<String, String> config, String source) { |
| return mapProperties(config, source, null); |
| } |
| |
| /** |
| * Maps a mapProperties of {@code Map<String,String>} to a {@code Map<String,PropertyValue>}. |
| * |
| * @param config the String based mapProperties, not {@code null}. |
| * @param source the source name, not {@code null}. |
| * @param metaData additional metadata, not {@code null}. |
| * @return the corresponding createValue based mapProperties. |
| */ |
| public static Map<String, PropertyValue> mapProperties(Map<String, String> config, String source, |
| Map<String,String> metaData) { |
| return mapProperties(config, source, metaData, null); |
| } |
| |
| /** |
| * Maps a mapProperties of {@code Map<String,String>} to a {@code Map<String,PropertyValue>}. |
| * |
| * @param config the String based mapProperties, not {@code null}. |
| * @param source the source name, not {@code null}. |
| * @param metaData additional metadata, not {@code null}. |
| * @return the corresponding createValue based mapProperties. |
| */ |
| public static Map<String, PropertyValue> mapProperties(Map<String, String> config, String source, |
| Map<String,String> metaData, String prefix) { |
| Objects.requireNonNull(config, "Config must be given."); |
| |
| Map<String, PropertyValue> result = new HashMap<>(config.size()); |
| |
| for(Map.Entry<String,String> en:config.entrySet()){ |
| PropertyValue pv = createValue(en.getKey(), en.getValue()); |
| if(metaData!=null) { |
| pv.setMeta(metaData); |
| } |
| if(source!=null){ |
| pv.setMeta(SOURCE, source); |
| } |
| if(prefix==null) { |
| result.put(en.getKey(), pv); |
| }else{ |
| result.put(prefix + en.getKey(), pv.setKey(prefix=en.getKey())); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Creates a new instance |
| * @param key the key, not {@code null}. |
| */ |
| public PropertyValue(String key){ |
| this(key, null); |
| } |
| |
| /** |
| * Creates a new instance |
| * @param key the key, not {@code null}. |
| * @param value the initial text createValue. |
| */ |
| protected PropertyValue(String key, String value){ |
| this.key = Objects.requireNonNull(key); |
| this.value = value; |
| } |
| |
| /** |
| * Checks if the instance is immutable. |
| * @return true, if the instance is immutable. |
| */ |
| public final boolean isImmutable(){ |
| return immutable; |
| } |
| |
| /** |
| * Sets this instance and also all its direct an indirect children to immutable. Any further changes will throw |
| * an {@link IllegalStateException}. |
| * @return this instance for chaining. |
| */ |
| public PropertyValue immutable(){ |
| this.immutable = true; |
| return this; |
| } |
| |
| /** |
| * Clones this instance and all it's children, marking as mutable createValue. |
| * @return the new createValue clone. |
| */ |
| public PropertyValue mutable(){ |
| if(!immutable){ |
| return this; |
| } |
| return deepClone(); |
| } |
| |
| /** |
| * Get the item's current createValue type. |
| * @return the createValue type, never null. |
| */ |
| public ValueType getValueType() { |
| return ValueType.VALUE; |
| } |
| |
| /** |
| * The requested key. |
| * @return the, key never {@code null}. |
| */ |
| public final String getKey() { |
| return key; |
| } |
| |
| /** |
| * Get the node's createValue. |
| * @return the createValue, or null. |
| */ |
| public String getValue() { |
| return this.value; |
| } |
| |
| /** |
| * Get the source. |
| * @return the source, or null. |
| * @deprecated Use {@code getMeta("source")}. |
| */ |
| @Deprecated |
| public String getSource() { |
| return this.metaEntries.get(SOURCE); |
| } |
| |
| |
| /** |
| * Sets the createValue. |
| * @param value the createValue |
| * @return this value for chaining. |
| * @throws IllegalStateException if the instance is immutable. |
| * @see #isImmutable() |
| */ |
| public PropertyValue setValue(String value) { |
| checkImmutable(); |
| if(!Objects.equals(this.value, value)) { |
| this.value = value; |
| incrementVersion(); |
| } |
| return this; |
| } |
| |
| /** |
| * Get a qualified name of a value in property format using '.' as separator, e.g. |
| * {@code a.b.c} or {@code a.b.c[0]} for indexed entries. Entries hereby also can have multiple |
| * levels of indexing, e.g. {@code a[1].b.c[14].d} is a valid option. |
| * |
| * The qualified key is defined by {@link #getQualifiedKey()} of it's parent concatenated with the key |
| * of this node. If there is no parent, or the parent's qualified key is empty only {@link #getKey()} |
| * is returned. Additionally if the current values is an indeyed createValue the key is extended by the |
| * index in brackets, e.g. {@code [0], [1], ...}. All the subsequent keys are valid qualified keys: |
| * <pre> |
| * a |
| * a.b |
| * a[0].b |
| * [0] |
| * a.b[4].c.d[0].[1].any |
| * </pre> |
| * |
| * @return the qualified key, never null.. |
| */ |
| public String getQualifiedKey(){ |
| if(parent==null){ |
| return key; |
| } |
| String parentName = parent.getQualifiedKey(); |
| if(parent instanceof ListValue){ |
| return parentName+"["+((ListValue)parent).getIndex(this)+"]"; |
| }else{ |
| if(!parentName.isEmpty()){ |
| parentName+="."; |
| } |
| return parentName+key; |
| } |
| } |
| |
| /** |
| * Get the value's parent. |
| * @return the parent, or null. |
| */ |
| public final PropertyValue getParent(){ |
| return parent; |
| } |
| |
| /** |
| * Get the values version, the version is updated with each change written. |
| * @return the version. |
| */ |
| public final int getVersion(){ |
| return version.get(); |
| } |
| |
| |
| /** |
| * Checks if the value is a root value. |
| * @return true, if the current value is a root value. |
| */ |
| public final boolean isRoot() { |
| return parent == null; |
| } |
| |
| /** |
| * Checks if the value is a leaf value (has no values). |
| * @return true, if the current value is a leaf value. |
| */ |
| public final boolean isLeaf(){ |
| return getValueType()==ValueType.VALUE; |
| } |
| |
| /** |
| * Creates a full configuration map for this key, createValue pair and all its getMeta context data. This map |
| * is also used for subsequent processing, like createValue filtering. |
| * @return the property createValue entry map. |
| */ |
| public final Map<String, String> getMeta() { |
| return Collections.unmodifiableMap(metaEntries); |
| } |
| |
| /** |
| * Access the given key from this createValue. Valid keys are the key or any getMeta-context key. |
| * @param key the key, not {@code null}. |
| * @return the createValue found, or {@code null}. |
| * @deprecated Use {@link #getMeta(String)} instead of. |
| */ |
| @Deprecated |
| public String getMetaEntry(String key) { |
| return this.metaEntries.get(Objects.requireNonNull(key)); |
| } |
| |
| /** |
| * Access the given key from this createValue. Valid keys are the key or any getMeta-context key. |
| * @param key the key, not {@code null}. |
| * @param <T> the target type. |
| * @return the createValue found, or {@code null}. |
| */ |
| public final <T> T getMeta(String key) { |
| return (T)this.metaEntries.get(Objects.requireNonNull(key)); |
| } |
| |
| /** |
| * Get the createValue's number of elements. |
| * @return the getNumChilds of this multi createValue. |
| */ |
| public int getSize() { |
| return EMPTY; |
| } |
| |
| @Override |
| public Iterator<PropertyValue> iterator() { |
| return Collections.emptyIterator(); |
| } |
| |
| |
| /** |
| * Replaces/sets the context data. |
| * @param metaEntries the context data to be applied, not {@code null}. |
| * @return the builder for chaining. |
| * @throws IllegalStateException if the instance is immutable. |
| * @see #isImmutable() |
| */ |
| public final PropertyValue setMeta(Map<String, String> metaEntries) { |
| checkImmutable(); |
| if(!Objects.equals(this.metaEntries, metaEntries)) { |
| this.metaEntries.clear(); |
| this.metaEntries.putAll(metaEntries); |
| version.incrementAndGet(); |
| } |
| return this; |
| } |
| |
| /** |
| * Add an additional context data information. |
| * @param key the context data key, not {@code null}. |
| * @param value the context createValue, not {@code null} (will be converted to String). |
| * @return the builder for chaining. |
| * @throws IllegalStateException if the instance is immutable. |
| * @see #isImmutable() |
| */ |
| public final PropertyValue setMeta(String key, Object value) { |
| checkImmutable(); |
| Objects.requireNonNull(key, "Meta key must be given."); |
| Objects.requireNonNull(value, "Meta value must be given."); |
| if(!Objects.equals(this.metaEntries.get(key), value.toString())) { |
| this.metaEntries.put(key, value.toString()); |
| version.incrementAndGet(); |
| } |
| return this; |
| } |
| |
| /** |
| * Removes a getMeta entry. |
| * @param key the entry's key, not {@code null}. |
| * @return the builder for chaining. |
| * @throws IllegalStateException if the instance is immutable. |
| * @see #isImmutable() |
| */ |
| public final PropertyValue removeMeta(String key) { |
| checkImmutable(); |
| Objects.requireNonNull(key, "Key must be given."); |
| if(this.metaEntries.containsKey(key)) { |
| this.metaEntries.remove(key); |
| version.incrementAndGet(); |
| } |
| return this; |
| } |
| |
| |
| /** |
| * Convert the value tree to a property map. |
| * @return the corresponding property map, not null. |
| */ |
| public Map<String,String> toMap(){ |
| Map<String, String> map = new TreeMap<>(); |
| if(value!=null) { |
| map.put(getQualifiedKey(), value); |
| } |
| return map; |
| } |
| |
| /** |
| * Convert the value tree to a property mapProperties using local keys. |
| * @return the corresponding property mapProperties, not null. |
| */ |
| public Map<String,String> toLocalMap(){ |
| Map<String, String> map = new TreeMap<>(); |
| if(value!=null) { |
| map.put(getKey(), value); |
| } |
| return map; |
| } |
| |
| |
| /** |
| * Create a String representation of the tree. |
| * @return the corresponding String representation, not null. |
| */ |
| @Override |
| public String toString() { |
| return String.valueOf(value); |
| } |
| |
| /** |
| * Convert an instance to a Object PropertyValue. |
| * @return the list value, never null. |
| */ |
| public ObjectValue toObjectValue(){ |
| ObjectValue ov = new ObjectValue(getKey()); |
| ov.setParent(getParent()); |
| ov.setValue("value", value); |
| return ov; |
| } |
| |
| /** |
| * Convert an instance to a List PropertyValue. |
| * @return the list value, never null. |
| */ |
| public ListValue toListValue(){ |
| ListValue lv = new ListValue(getKey()); |
| lv.setParent(getParent()); |
| lv.addValue(value); |
| return lv; |
| } |
| |
| |
| /** |
| * Creates a deep clone of this intance. |
| * @return a clone, never null. |
| */ |
| protected PropertyValue deepClone() { |
| PropertyValue newProp = new PropertyValue(getKey(), this.value); |
| newProp.setParent(getParent()); |
| newProp.setMeta(getMeta()); |
| newProp.setVersion(getVersion()); |
| newProp.setValue(getValue()); |
| return newProp; |
| } |
| |
| /** |
| * @throws IllegalStateException if the instance is immutable. |
| */ |
| protected final void checkImmutable(){ |
| if(immutable){ |
| throw new IllegalStateException("Instance is immutable."); |
| } |
| } |
| |
| /** |
| * Called to mark a change on this instance. |
| * @return the new version. |
| */ |
| protected final int incrementVersion(){ |
| checkImmutable(); |
| return version.incrementAndGet(); |
| } |
| |
| /** |
| * Sets the new version, used iternally when cloning. |
| * @param version the new version. |
| */ |
| protected final void setVersion(int version) { |
| this.version.set(version); |
| } |
| |
| /** |
| * Changes the entry's key, mapping also corresponding context entries. |
| * |
| * @param key the new key, not {@code null}. |
| * @return the builder for chaining. |
| * @throws IllegalStateException if the instance is immutable. |
| * @see #isImmutable() |
| */ |
| public PropertyValue setKey(String key) { |
| checkImmutable(); |
| if(!Objects.equals(this.key, key)) { |
| this.key = Objects.requireNonNull(key); |
| version.incrementAndGet(); |
| } |
| return this; |
| } |
| |
| /** |
| * Sets the new parent, used iternally when converting between value types. |
| * @param parent the parent value. |
| * @return the simple value, never null. |
| */ |
| protected PropertyValue setParent(PropertyValue parent){ |
| this.parent = parent; |
| return this; |
| } |
| |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (!(o instanceof PropertyValue)) { |
| return false; |
| } |
| PropertyValue dataNode = (PropertyValue) o; |
| return Objects.equals(getKey(), dataNode.getKey()) && |
| Objects.equals(value, dataNode.value); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(getParent(), getKey(), value, getMeta()); |
| } |
| |
| } |