blob: 286fbe59894c5853004967270daf4ba88c20b2bc [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.tamaya.spi;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
* Class modelling the result of a request for a property value. A property value is basically identified by its key.
* There might be reasons, where one want to further analyze, which PropertySources provided a value and which not, so
* it is possible to createObject a PropertyValue with a null value.
*
* 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 final class ObjectValue extends PropertyValue{
private static final long serialVersionUID = 1L;
private static final Logger LOG = Logger.getLogger(ObjectValue.class.getName());
/** List of child properties. */
private Map<String, PropertyValue> fields = new HashMap<>();
/**
* Creates a new instance
* @param key the key, not {@code null}.
* @param parent the parent.
*/
ObjectValue(PropertyValue parent, String key){
super(parent, key);
}
/**
* Get the item's current value type.
* @return the value type, never null.
*/
public ValueType getValueType() {
return ValueType.MAP;
}
/**
* Get the fields of this instance.
* @return the current fields, never null.
*/
public Collection<PropertyValue> getValues(){
return Collections.unmodifiableCollection(this.fields.values());
}
/**
* Get the fields of this instance, filtered with the given predicate.
* @param predicate the predicate, not null.
* @return the current fields, never null.
*/
public Collection<PropertyValue> getValues(Predicate<PropertyValue> predicate){
return Collections.unmodifiableCollection(this.fields.values().stream()
.filter(predicate).collect(Collectors.toList()));
}
/**
* Get a single child getValue by name.
* @param name the child's name, not null.
* @return the child found, or null.
* @throws IllegalArgumentException if multiple getValues with the given name are existing (ambigous).
*/
public PropertyValue getValue(String name){
return this.fields.get(name);
}
/**
* Get a single child getValue with the given name, creates it if not existing.
* @param name the child's name, not null.
* @param valueSupplier the supplier to create a new instance, if no value is present, not null.
* @param <T> the target type.
* @return the child found or created, never null.
* @throws IllegalArgumentException if multiple getValues with the given name are existing (ambigous).
* @throws IllegalStateException if the instance is immutable.
* @see #isImmutable()
*/
public <T extends PropertyValue> T getOrSetValue(String name, Supplier<T> valueSupplier){
T field = (T)this.fields.get(name);
if(field==null){
checkImmutable();
field = valueSupplier.get();
this.fields.put(name, field);
incrementVersion();
}
return field;
}
/**
* Get the value's number of elements.
* @return the getNumChilds of this multi value.
*/
@Override
public int getSize() {
return this.fields.size();
}
@Override
public PropertyValue toPropertyValue(){
PropertyValue value = new PropertyValue(getParent(), getKey(), getValue());
value.setMeta(getMeta());
value.setVersion(getVersion());
return value;
}
@Override
public ObjectValue toObjectValue(){
return this;
}
@Override
public ListValue toListValue(){
ListValue array = new ListValue(getParent(), getKey());
array.setValue(getValue());
array.setMeta(getMeta());
array.setVersion(getVersion());
int index = 0;
for(PropertyValue val:fields.values()){
array.add(val.deepClone());
}
return array;
}
@Override
public Iterator<PropertyValue> iterator() {
return Collections.unmodifiableCollection(this.fields.values()).iterator();
}
/**
* Adds text values to the createObject.
* @param values the child's values, not null.
* @return the created values, not null.
* @throws IllegalStateException if the instance is immutable.
* @see #isImmutable()
*/
public Collection<PropertyValue> setValues(Map<String, String> values) {
checkImmutable();
List<PropertyValue> result = new ArrayList<>();
for(Map.Entry<String, String> en:values.entrySet()) {
PropertyValue val = setValue(en.getKey(), en.getValue());
result.add(val);
}
return result;
}
/**
* Adds another existing node, hereby setting the corresponding parent node.
* @param value the value, not null
* @param <T> the value type.
* @return the value added, not null.
* @throws IllegalStateException if the instance is immutable.
* @see #isImmutable()
*/
public <T extends PropertyValue> T set(T value) {
checkImmutable();
value.setParent(this);
this.fields.put(value.getKey(), value);
return value;
}
/**
* Sets the given key, value pair.
* @param k the key, not null.
* @param v the value, not null.
* @return the value added, not null.
*/
public PropertyValue setValue(String k, String v) {
return set(PropertyValue.createValue(k,v));
}
/**
* Sets the given list value.
* @param key the key, not null.
* @return the value added, not null.
*/
public ListValue setList(String key) {
return set(PropertyValue.createList(key));
}
/**
* Sets the given object vaƶue.
* @param key the key, not null.
* @return the value added, not null.
*/
public ObjectValue setObject(String key) {
return set(PropertyValue.createObject(key));
}
/**
* Adds a new child getValue, where the getValue is given in '.'-separated property notation,
* e.g. {@code a.b.c}.
* @param key the property key, e.g. {@code a.b.c}
* @param value the property value
* @return the new leaf-getValue created.
* @throws IllegalStateException if the instance is immutable.
* @see #isImmutable()
*/
public PropertyValue setValueWithCompositeKey(String key, String value) {
checkImmutable();
ObjectValue node = this;
StringTokenizer tokenizer = new StringTokenizer(key, "\\.", false);
while(tokenizer.hasMoreTokens()){
String token = tokenizer.nextToken().trim();
if(tokenizer.hasMoreTokens()) {
node = node.getOrSetValue(token, () -> PropertyValue.createObject(token));
}else{
return node.set(PropertyValue.createValue(token, value));
}
}
return null;
}
/**
* Adds multiple values, where the keys are given in '.'-separated property notation,
* e.g. {@code a.b.c}.
* @param values the values, not null.
* @return the value instances created.
* @throws IllegalStateException if the instance is immutable.
* @see #isImmutable()
*/
public Collection<PropertyValue> setValueWithCompositeKey(Map<String,String> values) {
checkImmutable();
List<PropertyValue> result = new ArrayList<>();
for(Map.Entry<String,String> en:values.entrySet()){
result.add(setValueWithCompositeKey(en.getKey(), en.getValue()));
}
return result;
}
/**
* Convert the getValue tree to a property map.
* @return the corresponding property map, not null.
*/
@Override
public Map<String,String> toMap(){
Map<String, String> map = new TreeMap<>();
for (PropertyValue n : fields.values()) {
switch(n.getValueType()){
case VALUE:
map.put(n.getQualifiedKey(), n.getValue());
break;
default:
for(PropertyValue val:n) {
map.putAll(val.toMap());
}
}
}
return map;
}
/**
* Clones this instance and all it's children, marking as mutable value.
* @return the new value clone.
*/
@Override
public ObjectValue mutable(){
return (ObjectValue)super.mutable();
}
@Override
protected ObjectValue deepClone(){
ObjectValue newProp = new ObjectValue(getParent(), getKey());
newProp.setMeta(getMeta());
fields.values().forEach(c -> newProp.set(c.deepClone().mutable()));
newProp.setVersion(getVersion());
newProp.setValue(getValue());
return newProp;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ObjectValue)) {
return false;
}
ObjectValue dataNode = (ObjectValue) o;
return Objects.equals(getKey(), dataNode.getKey()) &&
Objects.equals(fields, dataNode.fields) &&
Objects.equals(getMeta(), dataNode.getMeta());
}
@Override
public int hashCode() {
return Objects.hash(getParent(), getKey(), fields, getMeta());
}
@Override
public String toString() {
return "PropertyValue[MAP]{" +
'\'' +getQualifiedKey() + '\'' +
(getValue()!=null?", value='" + getValue() + '\'':"") +
", size='" + getSize() + '\'' +
(getMeta().isEmpty()?"":", metaData=" + getMeta()) +
'}';
}
}