Skip to content

Commit

Permalink
Simplified tree support.
Browse files Browse the repository at this point in the history
  • Loading branch information
tomas-langer committed Sep 23, 2021
1 parent 5b69e07 commit 234b274
Show file tree
Hide file tree
Showing 4 changed files with 442 additions and 2 deletions.
191 changes: 190 additions & 1 deletion api/src/main/java/org/eclipse/microprofile/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import org.eclipse.microprofile.config.spi.ConfigSource;
import org.eclipse.microprofile.config.spi.Converter;
Expand Down Expand Up @@ -129,7 +131,9 @@ public interface Config {
* if the property cannot be converted to the specified type
* @throws java.util.NoSuchElementException
* if the property is not defined or is defined as an empty string or the converter returns {@code null}
* @deprecated use {@link #get(String)} and {@link #as(Class)} instead
*/
@Deprecated
<T> T getValue(String propertyName, Class<T> propertyType);

/**
Expand All @@ -148,7 +152,9 @@ public interface Config {
* @param propertyName
* The configuration property name
* @return the resolved property value as a {@link ConfigValue}
* @deprecated use {@link #get(String)} and methods on the config node
*/
@Deprecated
ConfigValue getConfigValue(String propertyName);

/**
Expand All @@ -171,7 +177,9 @@ public interface Config {
* @throws java.util.NoSuchElementException
* if the property isn't present in the configuration or is defined as an empty string or the converter
* returns {@code null}
* @deprecated use {@link #get(String)} and #asList(Class)
*/
@Deprecated
default <T> List<T> getValues(String propertyName, Class<T> propertyType) {
@SuppressWarnings("unchecked")
Class<T[]> arrayType = (Class<T[]>) Array.newInstance(propertyType, 0).getClass();
Expand All @@ -198,7 +206,9 @@ default <T> List<T> getValues(String propertyName, Class<T> propertyType) {
*
* @throws IllegalArgumentException
* if the property cannot be converted to the specified type
* @deprecated use {@link #get(String)} and {@link #as(Class)} instead
*/
@Deprecated
<T> Optional<T> getOptionalValue(String propertyName, Class<T> propertyType);

/**
Expand All @@ -219,7 +229,9 @@ default <T> List<T> getValues(String propertyName, Class<T> propertyType) {
*
* @throws java.lang.IllegalArgumentException
* if the property cannot be converted to the specified type
* @deprecated use {@link #get(String)} and #asList(Class)
*/
@Deprecated
default <T> Optional<List<T>> getOptionalValues(String propertyName, Class<T> propertyType) {
@SuppressWarnings("unchecked")
Class<T[]> arrayType = (Class<T[]>) Array.newInstance(propertyType, 0).getClass();
Expand Down Expand Up @@ -257,7 +269,7 @@ default <T> Optional<List<T>> getOptionalValues(String propertyName, Class<T> pr
* The returned sources will be sorted by descending ordinal value and name, which can be iterated in a thread-safe
* manner. The {@link java.lang.Iterable Iterable} contains a fixed number of {@linkplain ConfigSource configuration
* sources}, determined at application start time, and the config sources themselves may be static or dynamic.
*
*
* @return the configuration sources
*/
Iterable<ConfigSource> getConfigSources();
Expand Down Expand Up @@ -291,4 +303,181 @@ default <T> Optional<List<T>> getOptionalValues(String propertyName, Class<T> pr
* If the current provider does not support unwrapping to the given type
*/
<T> T unwrap(Class<T> type);

/*
* Tree handling methods
*/

/**
* Fully qualified key of this config node (such as {@code server.port}).
* Returns an empty String for root config.
*
* @return key of this config
*/
String key();

/**
* Name of this node - the last element of a fully qualified key.
* <p>
* For example for key {@code server.port} this method would return {@code port}.
*
* @return name of this node
*/
String name();

/**
* Single sub-node for the specified name.
* For example if requested for key {@code server}, this method would return a config
* representing the {@code server} node, which would have for example a child {@code port}.
*
* @param name name of the nested node to retrieve
* @return sub node, never null
*/
Config get(String name);

/**
* A detached node removes prefixes of each sub-node of the current node.
* <p>
* Let's assume this node is {@code server} and contains {@code host} and {@code port}.
* The method {@link #key()} for {@code host} would return {@code server.host}.
* If we call a method {@link #key()} on a detached instance, it would return just {@code host}.
*
* @return a detached config instance
*/
Config detach();

/**
* Type of this node.
*
* @return type
*/
Type type();

/**
* Returns {@code true} if the node exists, whether an object, a list, or a
* value node.
*
* @return {@code true} if the node exists
*/
default boolean exists() {
return type() != Type.MISSING;
}

/**
* Returns {@code true} if this configuration node has a direct value.
* <p>
* Example (using properties files) for each node type:
* <p>
* {@link Type#OBJECT} - the node {@code server.tls} is an object node with direct value:
* <pre>
* # this is not recommended, yet it is possible:
* server.tls=true
* server.tls.version=1.2
* server.tls.keystore=abc.p12
* </pre>
* <p>
* {@link Type#LIST} - the node {@code server.ports} is a list node with direct value:
* TODO this may actually not be supported by the spec, as it can only be achieved through properties
* <pre>
* # this is not recommended, yet it is possible:
* server.ports=8080
* server.ports.0=8081
* server.ports.1=8082
* </pre>
* <p>
* {@link Type#VALUE} - the nodes {@code server.port} and {@code server.host} are values
* <pre>
* server.port=8080
* server.host=localhost
* </pre>
*
* @return {@code true} if the node has direct value, {@code false} otherwise.
*/
boolean hasValue();

/**
* Typed value created using a converter function.
* The converter is called only if this config node exists.
*
* @param converter to create an instance from config node
* @param <T> type of the object
* @return converted value of this node, or an empty optional if this node does not exist
* @throws java.lang.IllegalArgumentException if this config node cannot be converted to the desired type
*/
<T> Optional<T> as(Function<Config, T> converter);

/**
* Typed value created using a configured converter.
*
* @param type class to convert to
* @param <T> type of the object
* @return converted value of this node, or empty optional if this node does not exist
* @throws java.lang.IllegalArgumentException if this config node cannot be converted to the desired type
*/
<T> Optional<T> as(Class<T> type);

/**
* Map to a list of typed values.
* This method is only available if the current node is a {@link org.eclipse.microprofile.config.Config.Type#LIST}
* or if it has a direct value. In case a direct value of type String exists, it expects comma separated elements.
*
* @param type class to convert each element to
* @param <T> type of the object
* @return list of typed values
* @throws java.lang.IllegalArgumentException if this config node cannot be converted to the desired type
*/
<T> Optional<List<T>> asList(Class<T> type);

/**
* Contains the (known) config values as a map of key->value pairs.
*
* @return map of sub keys of this config node, or empty if this node does not exist
*/
Optional<Map<String, String>> asMap();

/**
* A list of child nodes.
* In case this node is {@link org.eclipse.microprofile.config.Config.Type#LIST} returns the list in the correct order.
* In case this node is {@link org.eclipse.microprofile.config.Config.Type#OBJECT} returns all direct child nodes
* (in an unknown order)
*
* @return list of child nodes, or empty if this node does not exist
*/
Optional<List<Config>> asNodeList();

/*
* Shortcut helper methods
*/
default Optional<String> asString() {
return as(String.class);
}

default Optional<Integer> asInt() {
return as(Integer.class);
}

/**
* Config node type.
*/
enum Type {
/**
* Object node with named members and a possible direct value.
*/
OBJECT,
/**
* List node with a list of indexed parameters.
* Note that a list node can also be accessed as an object node - child elements
* have indexed keys starting from {@code 0}.
* List nodes may also have a direct value.
*/
LIST,
/**
* Value node is a leaf node - it does not have any child nodes, only direct value.
*/
VALUE,
/**
* Node is missing, it will return only empty values.
*/
MISSING
}
}
135 changes: 135 additions & 0 deletions api/src/main/java/org/eclipse/microprofile/config/spi/ConfigNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright (c) 2017, 2021 Oracle and/or its affiliates.
*
* Licensed 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.eclipse.microprofile.config.spi;

import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* Marker interface identifying a config node implementation.
*/
public interface ConfigNode {
/**
* Key of this config node.
*
* @return key of this node
*/
String key();

/**
* Get the type of this node.
*
* @return NodeType this node represents
*/
NodeType nodeType();

/**
* Get the direct value of this config node. Any node type can have a direct value.
*
* @return a value if present, {@code empty} otherwise
*/
Optional<String> value();

/**
* Each node may have a direct value, and in addition may be an object node or a list node.
* This method returns true for any node with direct value.
*
* @return true if this node contains a value
*/
default boolean hasValue() {
return value().isPresent();
}

/**
* Config source that provided this value.
*
* @return config source
*/
ConfigSource configSource();

/**
* The actual priority of the config source that provided this value.
*
* @return config source priority
* @see #configSource()
*/
Integer sourcePriority();

/**
* Base types of config nodes.
*/
enum NodeType {
/**
* An object (complex structure), optionally may have a value.
*/
OBJECT,
/**
* A list of values, optionally may have a value.
*/
LIST,
/**
* Only has value.
*/
VALUE
}

/**
* Single string-based configuration value.
*/
interface ValueNode extends ConfigNode {
@Override
default NodeType nodeType() {
return NodeType.VALUE;
}

/**
* Get the value of this value node.
* @return string with the node value
*/
String get();
}

/**
* ConfigNode-based list of configuration values.
* <p>
* List may contains instances of
* {@link ValueNode}, {@link ListNode} as well as {@link ObjectNode}.
*/
interface ListNode extends ConfigNode, List<ConfigNode> {
@Override
default NodeType nodeType() {
return NodeType.LIST;
}
}

/**
* Configuration node representing a hierarchical structure.
* <p>
* In the map exposed by this interface, the map keys are {@code String}s
* containing the fully-qualified dotted names of the config keys and the
* map values are the corresponding {@link ValueNode} or {@link ListNode}
* instances. The map never contains {@link ObjectNode} values because the
* {@link ObjectNode} is implemented as a flat map.
*/
interface ObjectNode extends ConfigNode, Map<String, ConfigNode> {
@Override
default NodeType nodeType() {
return NodeType.OBJECT;
}
}
}
Loading

0 comments on commit 234b274

Please sign in to comment.