-
Notifications
You must be signed in to change notification settings - Fork 116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tree node support. #711
base: main
Are you sure you want to change the base?
Tree node support. #711
Conversation
Can one of the admins verify this patch? |
/* | ||
* Shortcut helper methods | ||
*/ | ||
default Optional<String> asString() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only callable if the hasValue() returns true. I am wondering whether we layer this further and move some of the methods to LeafNode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is practically not true - there can be a converter from config node to string that could work even for list nodes (e.g. converting a list to a comma separated string)
/** | ||
* Object node with named members and a possible direct value. | ||
*/ | ||
OBJECT, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the Type structure, but not the enum value names. I think it is pretty much: Parent, List, Leaf, Empty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parent is not a very good name, as it can have a value (it is basically a tree node that may contain child nodes and a value).
Leaf is OK. Empty vs. Missing - I think missing is more spot on (as this config node is missing from configuration). Empty may mean something different (e.g. the node exists, but is empty).
The values should still be all upper case (best practice for naming enum values)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed value to LEAF
c85bb8e
to
234b274
Compare
…t a tree node config source without implementing this method.
*/ | ||
enum Type { | ||
/** | ||
* This is for backward compatibility - request each property separately. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be FLAT
to state all properties are visible with a flat structure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The naming could be anything, I just wanted to have an option that is backward compatible. If FLAT
sounds better, no problem.
@@ -150,4 +152,18 @@ | |||
* if the given value was {@code null} | |||
*/ | |||
T convert(String value) throws IllegalArgumentException, NullPointerException; | |||
|
|||
/** | |||
* Convert the given config node (may be an object, list or value) to the specified type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this config node is a node instead of leaf, I assume an IllegalArugumentException
should be thrown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A converter may be capable of converting object nodes or list nodes as well. If the converter converts to a POJO for example, accepting an Object node is absolutely fine
* | ||
* @param changeRunnable runnable to call when this config source changes | ||
*/ | ||
default void changeListener(Runnable changeRunnable) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think onChange
is a better name.
* | ||
* @return node content or empty optional if the underlying config source does not exist | ||
*/ | ||
default Optional<ConfigNode> load() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should remove this method if we update the following method load(String key)
to state that if the key
is null, it will load the whole tree. Otherwise, load the tree with the specified root
. I would change the signature load(String root)
while null
means the whole root.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am against using null
as a valid parameter value (anywhere, anytime).
I can see two options here:
- use empty string instead (as the root node does not have a name, so the key could be an empty string)
- use a dedicated
Key
class that would have the concept of a root and use that
/** | ||
* An object (complex structure), optionally may have a value. | ||
*/ | ||
OBJECT, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still find this confusing. LIST
and VALUE
are all OBJECT
. How about STRUCTURE
or COMPLEX
to differentiate them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is paritally taken from JSON, where JsonObject
is the specific type (something that contains other nodes) and JsonStructure
is the superset.
I am using List
instead of Array
, as I don't like arrays much in general. But otherwise this copies JSON-P naming
JsonObject
-> OBJECT
JsonArray
-> LIST (because collections are better than arrays ;) )
JsonValue
-> VALUE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't make to the correlation with Json before you mentioned. I think if we prefix with Config, it might be less confusing, but refering to ConfigNode.OBJECT as ConfigNode.ConfigOBJECT is mouthful. It is not ideal, but I have not got a better name as yet:(. Maybe someone else has.
/** | ||
* Config node type. | ||
*/ | ||
enum Type { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have NodeType
under ConfigNode
and this Type
again for config node type. Can we list one out and refer to it in both Config
and ConfigNode
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because these are different scopes and different meanings.
One is intended for SPI (ConfigNode.Type) that supports just three types.
The other is for API (Config.Type) that supports also MISSING
.
User's do not see ConfigNode
unless they implement ConfigSource
; SPI does not use Config.
So separation of concerns.
* | ||
* @return a detached config instance | ||
*/ | ||
Config detach(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure about the use case here. When do you need to do this? Is this purly about shorter the key string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is to create a config that "cuts" the prefix from all of its nodes. Use case is when you need to configure a component that expects a map (such as JSON-B, CDI).
If you would not have such a method, you would need to create a map, and then rebuild the map by doing a substring on each key
import java.util.Optional; | ||
|
||
/** | ||
* Marker interface identifying a config node implementation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't "marker interface" usually refer to interfaces without methods?
* | ||
* @return NodeType this node represents | ||
*/ | ||
NodeType nodeType(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some time, in the far, far future, the spec will require a minimum of Java 17. Should we prepare for this day by declaring the type enum to be a temporary workaround until this interface can be sealed
in a future version?
This PR shows how tree structure could be supported using MP config.
I have updated the proposal with a bit simplified version.
API changes
Config now has a method
get(String name)
to obtain a child node of the current node.It also has several typed methods to get the current node as a typed value
SPI changes
Config source can now provide the full tree instead of a single node
Converter can convert from a node (object node, list node, value node) to a type
This PR was created as a result of discussion on Jakarta Config meeting, so we have a vehicle to discuss tree structure in configuration.
Some decisions that I made and explanation:
isMutable
andchangeListener
) onConfigSource
. Unfortunately the tree structure is connected with mutability, as if a source provides the full tree, it is no longer queried for each node separately, and so the existing approach of asking each source in sequence (and thus supporting some kind of "maybe mutability") is no longer possible. The new approach is allowing a config source to declare its support for mutations, and an event support to notify configuration of such a change. The configuration may or may not be interested in changes - if not, methodchangeListener
is not called...getType()
and default methods to the config source that support each type. This is for backward compatibility (and also to make it easy for config source implementation - you just need to know about a single interface). This is a bit more complicated for config implementation, as it needs to check for source type, and then call the correct method to get values