diff --git a/README.adoc b/README.adoc
index def1181..fdbd476 100644
--- a/README.adoc
+++ b/README.adoc
@@ -15,12 +15,13 @@ The module `io.github.mmm.base` (artifactId `mmm-base`) provides a minimal set o
This library offers the following features:
* `ApplicationException` for exceptions with `UUID`, `code`, and distinction between user and technical errors.
-* `CharFilter` interface
+* `CharFilter` interface and implementations
* `Justification` to justify strings
* Placements such as `Alignment`, `Direction`, `Orientation`, etc.
* Converter and parser for `Temporal` values
* `CompareOperator` to compate strings, numbers, temporals, or any other comparable object
* `CaseHelper` to deal with lower/upper case
+* etc.
== Usage
@@ -37,3 +38,66 @@ Module Dependency:
```java
requires transitive io.github.mmm.base;
```
+
+= mmm-base-metainfo
+
+image:https://img.shields.io/maven-central/v/io.github.m-m-m/mmm-base-metainfo.svg?label=Maven%20Central["Maven Central",link=https://search.maven.org/search?q=g:io.github.m-m-m]
+image:https://javadoc.io/badge2/io.github.m-m-m/mmm-base-metainfo/javadoc.svg["base JavaDoc", link=https://javadoc.io/doc/io.github.m-m-m/mmm-base-metainfo]
+
+The module `io.github.mmm.base.metainfo` (artifactId `mmm-base-metainfo`) provides a minimal set of APIs for dealing with meta-information.
+
+== Features
+
+This library offers the following features:
+
+* `MetaInfo` interface to instantiate and access meta-information.
+* `MetaInfos` to annotate meta-information.
+
+== Usage
+
+Maven Dependency:
+```xml
+
+ io.github.m-m-m
+ mmm-base-metainfo
+ ${mmm.base.version}
+
+```
+
+Module Dependency:
+```java
+ requires transitive io.github.mmm.base.metainfo;
+```
+
+= mmm-base-placement
+
+image:https://img.shields.io/maven-central/v/io.github.m-m-m/mmm-base-placement.svg?label=Maven%20Central["Maven Central",link=https://search.maven.org/search?q=g:io.github.m-m-m]
+image:https://javadoc.io/badge2/io.github.m-m-m/mmm-base-placement/javadoc.svg["base JavaDoc", link=https://javadoc.io/doc/io.github.m-m-m/mmm-base-placement]
+
+The module `io.github.mmm.base.placement` (artifactId `mmm-base-placement`) provides a enums for placement.
+
+== Features
+
+This library offers the following enums:
+
+* `Alignment` for 2-D alignments.
+* `Direction` for 2-D direction.
+* `HorizontalAlignment` for horizontal alignment (1-D).
+* `Orientation` with `HORIZONTAL` and `VERTICAL`.
+* `VerticalAlignment` for vertical alignment (1-D).
+
+== Usage
+
+Maven Dependency:
+```xml
+
+ io.github.m-m-m
+ mmm-base-placement
+ ${mmm.base.version}
+
+```
+
+Module Dependency:
+```java
+ requires transitive io.github.mmm.base.placement;
+```
\ No newline at end of file
diff --git a/metainfo/pom.xml b/metainfo/pom.xml
new file mode 100644
index 0000000..ea8b0d3
--- /dev/null
+++ b/metainfo/pom.xml
@@ -0,0 +1,20 @@
+
+
+ 4.0.0
+
+ io.github.m-m-m
+ mmm-base-parent
+ ${revision}
+
+ mmm-base-metainfo
+ jar
+ ${project.artifactId}
+ Representation of generic metadata.
+
+
+ ${project.groupId}
+ mmm-base
+
+
+
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/MetaInfo.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/MetaInfo.java
new file mode 100644
index 0000000..9aeb585
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/MetaInfo.java
@@ -0,0 +1,297 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo;
+
+import java.lang.reflect.AnnotatedElement;
+import java.util.Map;
+import java.util.Properties;
+
+import io.github.mmm.base.metainfo.impl.MetaInfoEmpty;
+import io.github.mmm.base.metainfo.impl.MetaInfoValues;
+
+/**
+ * Interface for meta-information similar to {@link java.util.Properties} but more sophisticated. Implements
+ * {@link Iterable} allowing to {@link #iterator() iterate} the available meta-information keys.
+ * Use {@link #empty()} to get the empty instance and then call any {@code with} method to extended with real data.
+ * Example to show usage:
+ *
+ *
+ * {@literal @}{@link MetaInfoValues}({"key1=value1", "key2=value2"})
+ * public class AnnotatedClass {
+ * public static void main(String[] args) {
+ * {@link MetaInfo} metaInfo = {@link MetaInfo}.{@link #empty()}.{@link #with(AnnotatedElement) with}(AnnotatedClass.class);
+ * metaInfo = metaInfo.{@link #with(Map) with}(Map.of("key2", "two", "key3", "value3"));
+ * for (String key : metaInfo) {
+ * String value = metaInfo.{@link #get(String) get}(key);
+ * System.out.println(key + "=" + value);
+ * }
+ * }
+ * }
+ *
+ *
+ * This will print the following output (order is undefined):
+ *
+ *
+ * key1=value1
+ * key2=two
+ * key3=value3
+ *
+ *
+ * ATTENTION: Typically the {@code with} methods will return a new instance of {@link MetaInfo} with the invoked
+ * {@link MetaInfo} as {@link #getParent() parent}. However, this is not guaranteed. Especially immutable
+ * implementations may return a {@link MetaInfo} with a different {@link #getParent() parent}.
+ *
+ * @since 1.0.0
+ */
+public interface MetaInfo extends Iterable {
+
+ /**
+ * @param key the key of the requested meta-information.
+ * @return the value of the meta-information for the given {@code key}. Will be {@code null} if no value is defined
+ * for the given {@code key}.
+ */
+ default String get(String key) {
+
+ return get(true, key);
+ }
+
+ /**
+ * @param inherit - {@code true} to inherit meta-information from the {@link #getParent() parent}, {@code false} to
+ * only return plain meta-information defined in this {@link MetaInfo} itself.
+ * @param key the key of the requested meta-information.
+ * @return the value of the meta-information for the given {@code key}. Will be {@code null} if no value is defined
+ * for the given {@code key}.
+ */
+ String get(boolean inherit, String key);
+
+ /**
+ * @param key the key of the requested meta-information.
+ * @return the value of the meta-information for the given {@code key} parsed as {@link Long}. Will be {@code null} if
+ * no value is defined for the given {@code key}.
+ * @throws IllegalArgumentException if the value cannot be parsed as {@link Long}.
+ */
+ default Long getAsLong(String key) {
+
+ return getAsLong(true, key);
+ }
+
+ /**
+ * @param inherit - {@code true} to inherit meta-information from the {@link #getParent() parent}, {@code false} to
+ * only return plain meta-information defined in this {@link MetaInfo} itself.
+ * @param key the key of the requested meta-information.
+ * @return the value of the meta-information for the given {@code key} parsed as {@link Long}. Will be {@code null} if
+ * no value is defined for the given {@code key}.
+ * @throws IllegalArgumentException if the value cannot be parsed as {@link Long}.
+ */
+ default Long getAsLong(boolean inherit, String key) {
+
+ String value = get(inherit, key);
+ if (value == null) {
+ return null;
+ }
+ try {
+ return Long.valueOf(value);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Meta-info for key '" + key + "' is no long value", e);
+ }
+ }
+
+ /**
+ * @param key the key of the requested meta-information.
+ * @param defaultValue the default value returned if the actual value is undefined.
+ * @return the value of the meta-information for the given {@code key} parsed as {@link long}. If the actual value is
+ * undefined, the given {@code defaultValue} will be returned.
+ * @throws IllegalArgumentException if the value cannot be parsed as {@link long}.
+ */
+ default long getAsLong(String key, long defaultValue) {
+
+ return getAsLong(true, key, defaultValue);
+ }
+
+ /**
+ * @param inherit - {@code true} to inherit meta-information from the {@link #getParent() parent}, {@code false} to
+ * only return plain meta-information defined in this {@link MetaInfo} itself.
+ * @param key the key of the requested meta-information.
+ * @param defaultValue the default value returned if the actual value is undefined.
+ * @return the value of the meta-information for the given {@code key} parsed as {@link long}. If the actual value is
+ * undefined, the given {@code defaultValue} will be returned.
+ * @throws IllegalArgumentException if the value cannot be parsed as {@link long}.
+ */
+ default long getAsLong(boolean inherit, String key, long defaultValue) {
+
+ Long value = getAsLong(inherit, key);
+ if (value == null) {
+ return defaultValue;
+ }
+ return value.longValue();
+ }
+
+ /**
+ * @param key the key of the requested meta-information.
+ * @return the value of the meta-information for the given {@code key} parsed as {@link Boolean}. Will be {@code null}
+ * if no value is defined for the given {@code key}.
+ * @throws IllegalArgumentException if the value cannot be parsed as {@link Boolean}.
+ */
+ default Boolean getAsBoolean(String key) {
+
+ return getAsBoolean(true, key);
+ }
+
+ /**
+ * @param key the key of the requested meta-information.
+ * @param inherit - {@code true} to inherit meta-information from the {@link #getParent() parent}, {@code false} to
+ * only return plain meta-information defined in this {@link MetaInfo} itself.
+ * @return the value of the meta-information for the given {@code key} parsed as {@link Boolean}. Will be {@code null}
+ * if no value is defined for the given {@code key}.
+ * @throws IllegalArgumentException if the value cannot be parsed as {@link Boolean}.
+ */
+ default Boolean getAsBoolean(boolean inherit, String key) {
+
+ String value = get(inherit, key);
+ if (value == null) {
+ return null;
+ }
+ if ("true".equals(value)) {
+ return Boolean.TRUE;
+ } else if ("false".equals(value)) {
+ return Boolean.FALSE;
+ }
+ throw new IllegalArgumentException("Meta-info for key '" + key + "' is no boolean value: " + value);
+ }
+
+ /**
+ * @param key the key of the requested meta-information.
+ * @param defaultValue the default value returned if the actual value is undefined.
+ * @return the value of the meta-information for the given {@code key} parsed as {@link boolean}. If the actual value
+ * is undefined, the given {@code defaultValue} will be returned.
+ * @throws IllegalArgumentException if the value cannot be parsed as {@link Boolean}.
+ */
+ default boolean getAsBoolean(String key, boolean defaultValue) {
+
+ return getAsBoolean(true, key, defaultValue);
+ }
+
+ /**
+ * @param inherit - {@code true} to inherit meta-information from the {@link #getParent() parent}, {@code false} to
+ * only return plain meta-information defined in this {@link MetaInfo} itself.
+ * @param key the key of the requested meta-information.
+ * @param defaultValue the default value returned if the actual value is undefined.
+ * @return the value of the meta-information for the given {@code key} parsed as {@link boolean}. If the actual value
+ * is undefined, the given {@code defaultValue} will be returned.
+ * @throws IllegalArgumentException if the value cannot be parsed as {@link Boolean}.
+ */
+ default boolean getAsBoolean(boolean inherit, String key, boolean defaultValue) {
+
+ Boolean value = getAsBoolean(inherit, key);
+ if (value == null) {
+ return defaultValue;
+ }
+ return value.booleanValue();
+ }
+
+ /**
+ * @return the number of {@link #get(String) entries} contained in this {@link MetaInfo}.
+ */
+ int size();
+
+ /**
+ * @return {@code true} if empty, {@code false} otherwise.
+ */
+ default boolean isEmpty() {
+
+ return size() == 0;
+ }
+
+ /**
+ * @return the potential parent {@link MetaInfo} to inherit from. Will be {@code null} if nothing is inherited.
+ */
+ default MetaInfo getParent() {
+
+ return null;
+ }
+
+ /**
+ * Adds or updates the specified meta-info.
+ * ATTENTION: Please consider using {@link #with(Map)} and other related factory methods instead of
+ * sequentially using this method that may be inefficient.
+ *
+ * @param key the new key of the meta-info.
+ * @param value the new value of the meta-info.
+ * @return a new {@link MetaInfo} that extends this {@link MetaInfo} with the specified meta-information.
+ */
+ MetaInfo with(String key, String value);
+
+ /**
+ * @param map the {@link Map} with the meta-information to wrap.
+ * @return a new {@link MetaInfo} that {@link #getParent() inherits} from this {@link MetaInfo} and extends it with
+ * the meta-information from the specified parameters.
+ */
+ default MetaInfo with(Map map) {
+
+ return with(map, "");
+ }
+
+ /**
+ * @param map the {@link Map} with the meta-information to wrap.
+ * @param keyPrefix the prefix for the {@link #get(String) keys} as namespace. E.g. when using "spring.datasource."
+ * and you call {@link #get(String)} with "password" it will return the property for the key
+ * "spring.datasource.password" from the given {@link Properties}.
+ * @return a new {@link MetaInfo} that extends this {@link MetaInfo} with the specified meta-information.
+ */
+ MetaInfo with(Map map, String keyPrefix);
+
+ /**
+ * ATTENTION: Due to the design of {@link Properties} (with ability for inheritance but no API to get access to
+ * the parent defaults) the result will be inefficient.
+ *
+ * @param properties the {@link Properties} with the meta-information to wrap.
+ * @return a new {@link MetaInfo} that extends this {@link MetaInfo} with the specified meta-information.
+ */
+ default MetaInfo with(Properties properties) {
+
+ return with(properties, "");
+ }
+
+ /**
+ * ATTENTION: Due to the design of {@link Properties} (with ability for inheritance but no API to get access to
+ * the parent defaults) the result will be inefficient.
+ *
+ * @param properties the {@link Properties} with the meta-information to wrap.
+ * @param keyPrefix the prefix for the {@link #get(String) keys} as namespace. E.g. when using "spring.datasource."
+ * and you call {@link #get(String)} with "password" it will return the property for the key
+ * "spring.datasource.password" from the given {@link Properties}.
+ * @return a new {@link MetaInfo} that extends this {@link MetaInfo} with the specified meta-information.
+ */
+ MetaInfo with(Properties properties, String keyPrefix);
+
+ /**
+ * @param annotatedElement the {@link AnnotatedElement} (e.g. {@link Class}) that is (potentially) annotated with
+ * {@link MetaInfos}.
+ * @return a new {@link MetaInfo} that extends this {@link MetaInfo} with the meta-information from the given
+ * {@link AnnotatedElement}. Will be {@code this} instance itself if the given {@link AnnotatedElement} has no
+ * {@link MetaInfos} annotation.
+ */
+ default MetaInfo with(AnnotatedElement annotatedElement) {
+
+ MetaInfos metaInfos = annotatedElement.getAnnotation(MetaInfos.class);
+ if (metaInfos == null) {
+ return this;
+ } else {
+ return with(metaInfos);
+ }
+ }
+
+ /**
+ * @param metaValues the {@link MetaInfos}.
+ * @return a new {@link MetaInfo} that extends this {@link MetaInfo} with the specified meta-information.
+ */
+ MetaInfo with(MetaInfos metaValues);
+
+ /**
+ * @return an instance of {@link MetaInfo} that is {@link #isEmpty() empty}.
+ */
+ static MetaInfo empty() {
+
+ return MetaInfoEmpty.INSTANCE;
+ }
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/MetaInfos.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/MetaInfos.java
new file mode 100644
index 0000000..5c63c87
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/MetaInfos.java
@@ -0,0 +1,27 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+/**
+ * Annotation to define {@link MetaInfo}.
+ *
+ * @see MetaInfo#get(String)
+ * @since 1.0.0
+ */
+@Documented
+@Retention(RUNTIME)
+public @interface MetaInfos {
+
+ /**
+ * @return the {@link MetaInfo} as {@link String}s in the form {@code «key»=«value»}. Each {@link String} needs to
+ * contain an equals sign. The left-hand side to the (first) equals sign will be the key of the
+ * meta-information and the right-hand side will be the {@link MetaInfo#get(String) value} for that key.
+ */
+ String[] value();
+
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/AbstractMetaInfo.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/AbstractMetaInfo.java
new file mode 100644
index 0000000..be5d180
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/AbstractMetaInfo.java
@@ -0,0 +1,150 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo.impl;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+import io.github.mmm.base.metainfo.MetaInfo;
+import io.github.mmm.base.metainfo.MetaInfos;
+
+/**
+ * Implementation of {@link MetaInfo} as wrapper of {@link Map}.
+ *
+ * @since 1.0.0
+ */
+public abstract class AbstractMetaInfo implements MetaInfo {
+
+ @Override
+ public AbstractMetaInfo getParent() {
+
+ return null;
+ }
+
+ /**
+ * @return {@code this} instance as {@link InheritedMetaInfo} to be used as {@link #getParent() parent} of a new child
+ * (see {@code with} methods) or {@code null} if this is the empty instance.
+ */
+ protected InheritedMetaInfo asParent() {
+
+ return null;
+ }
+
+ /**
+ * @param key the key of the requested meta-information.
+ * @return the value of the meta-information for the given {@code key} without inheritance from {@link #getParent()
+ * parent}. Will be {@code null} if no value is defined for the given {@code key}.
+ * @see #get(boolean, String)
+ */
+ protected abstract String getPlain(String key);
+
+ @Override
+ public String get(boolean inherit, String key) {
+
+ String value = getPlain(getKey(key));
+
+ if ((value == null) && inherit) {
+ AbstractMetaInfo parent = getParent();
+ if (parent != null) {
+ value = parent.get(true, key);
+ }
+ }
+ return value;
+ }
+
+ /**
+ * @return the prefix for the {@link #get(String) keys} as namespace. E.g. when using "spring.datasource." and you
+ * call {@link #get(String)} with "password" it will return the property for the key
+ * "spring.datasource.password" from the given {@link Properties}. The default value is the empty
+ * {@link String}.
+ */
+ public String getKeyPrefix() {
+
+ return null;
+ }
+
+ /**
+ * @param key the raw key.
+ * @return the qualified key with the {@link #getKeyPrefix() key prefix}.
+ */
+ protected String getKey(String key) {
+
+ return key;
+ }
+
+ @Override
+ public MetaInfo with(String key, String value) {
+
+ return new MetaInfoSingle(asParent(), key, value);
+ }
+
+ @Override
+ public MetaInfo with(Map map, String keyPrefix) {
+
+ return new MetaInfoMap(map, asParent(), keyPrefix);
+ }
+
+ @Override
+ public MetaInfo with(Properties properties, String keyPrefix) {
+
+ return new MetaInfoProperties(properties, asParent(), keyPrefix);
+ }
+
+ @Override
+ public MetaInfo with(MetaInfos metaValues) {
+
+ String[] values = metaValues.value();
+ if (values.length == 0) {
+ return this;
+ }
+ MetaInfoValue value = null;
+ // process in reverse order to retain original order...
+ for (int i = values.length - 1; i >= 0; i--) {
+ value = MetaInfoValue.of(values[i], value);
+ }
+ MetaInfoValues result = new MetaInfoValues(value, asParent());
+ assert result.hasUniqueKeys();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+
+ return toString(false);
+ }
+
+ /**
+ * @param sort - {@code true} to sort by keys ensuring deterministic results, {@code false} otherwise.
+ * @return the {@link #toString() string representation}.
+ */
+ public String toString(boolean sort) {
+
+ StringBuilder sb = new StringBuilder();
+ Iterable keys = this;
+ if (sort) {
+ Set keySet = new TreeSet<>();
+ for (String key : this) {
+ keySet.add(key);
+ }
+ keys = keySet;
+ }
+ sb.append('{');
+ boolean first = true;
+ for (String key : keys) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ String value = get(key);
+ sb.append(key);
+ sb.append('=');
+ sb.append(value);
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/InheritedMetaInfo.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/InheritedMetaInfo.java
new file mode 100644
index 0000000..99e5a17
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/InheritedMetaInfo.java
@@ -0,0 +1,132 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo.impl;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import io.github.mmm.base.collection.ReadOnlyIterator;
+import io.github.mmm.base.metainfo.MetaInfo;
+
+/**
+ * Implementation of {@link MetaInfo} as wrapper of {@link Map}.
+ *
+ * @since 1.0.0
+ */
+public abstract class InheritedMetaInfo extends AbstractMetaInfo {
+
+ /** @see #getParent() */
+ protected final InheritedMetaInfo parent;
+
+ /** @see #getKeyPrefix() */
+ protected final String keyPrefix;
+
+ /**
+ * The constructor.
+ *
+ * @param parent the {@link #getParent() parent}.
+ * @param keyPrefix the {@link #getKeyPrefix() key prefix}.
+ */
+ public InheritedMetaInfo(InheritedMetaInfo parent, String keyPrefix) {
+
+ super();
+ Objects.requireNonNull(keyPrefix);
+ this.parent = parent;
+ this.keyPrefix = keyPrefix;
+ }
+
+ @Override
+ public InheritedMetaInfo getParent() {
+
+ return this.parent;
+ }
+
+ @Override
+ protected InheritedMetaInfo asParent() {
+
+ return this;
+ }
+
+ /**
+ * @return a {@link Set} with the plain keys without inheritance from {@link #getParent() parent}.
+ * @see java.util.Map#keySet()
+ * @see #iterator()
+ */
+ protected abstract Set getKeysPlain();
+
+ /**
+ * @return the plain {@link #size()} of this {@link MetaInfo} without inheritance from {@link #getParent() parent}.
+ * @see #size()
+ */
+ protected int getSizePlain() {
+
+ return getKeysPlain().size();
+ }
+
+ boolean containsKeyPlain(String key, InheritedMetaInfo stop) {
+
+ InheritedMetaInfo metaInfo = this;
+ while ((metaInfo != null) && (metaInfo != stop)) {
+ String qualifiedKey = metaInfo.getKey(key);
+ if (metaInfo.getKeysPlain().contains(qualifiedKey)) {
+ return true;
+ }
+ metaInfo = metaInfo.parent;
+ }
+ return false;
+ }
+
+ @Override
+ public String getKeyPrefix() {
+
+ return this.keyPrefix;
+ }
+
+ @Override
+ protected String getKey(String key) {
+
+ if (this.keyPrefix.isEmpty()) {
+ return key;
+ }
+ return this.keyPrefix + key;
+ }
+
+ @Override
+ public Iterator iterator() {
+
+ if (this.keyPrefix.isEmpty() && (this.parent == null)) {
+ return new ReadOnlyIterator<>(getKeysPlain().iterator());
+ }
+ return new InheritedMetaInfoIterator(this);
+ }
+
+ @Override
+ public int size() {
+
+ int size;
+ if (this.keyPrefix.isEmpty()) {
+ if (this.parent != null) {
+ // there is no perfect & most efficient solution to this problem, this seems the best we can do...
+ Set keySet = getKeysPlain();
+ size = keySet.size();
+ for (String key : this.parent) {
+ if (!keySet.contains(key)) {
+ size++;
+ }
+ }
+ } else {
+ size = getSizePlain();
+ }
+ } else {
+ size = 0;
+ for (String key : this) {
+ assert (key != null);
+ size++;
+ }
+ }
+ return size;
+ }
+
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/InheritedMetaInfoIterator.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/InheritedMetaInfoIterator.java
new file mode 100644
index 0000000..d916de6
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/InheritedMetaInfoIterator.java
@@ -0,0 +1,83 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo.impl;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Implementation of {@link Iterator} for {@link AbstractMetaInfo}.
+ */
+public class InheritedMetaInfoIterator implements Iterator {
+
+ private final InheritedMetaInfo metaInfo;
+
+ private String keyPrefix;
+
+ private InheritedMetaInfo currentMetaInfo;
+
+ private Iterator iterator;
+
+ private String next;
+
+ /**
+ * The constructor.
+ *
+ * @param metaInfo the {@link AbstractMetaInfo} to iterate.
+ */
+ public InheritedMetaInfoIterator(InheritedMetaInfo metaInfo) {
+
+ super();
+ this.metaInfo = metaInfo;
+ this.keyPrefix = metaInfo.keyPrefix;
+ this.iterator = metaInfo.getKeysPlain().iterator();
+ this.next = findNext();
+ }
+
+ private String findNext() {
+
+ while (this.iterator != null) {
+ while (this.iterator.hasNext()) {
+ String key = this.iterator.next();
+ if (key != null) {
+ if (!this.keyPrefix.isEmpty()) {
+ key = this.keyPrefix + key;
+ }
+ if ((this.currentMetaInfo == null) || !this.metaInfo.containsKeyPlain(key, this.currentMetaInfo)) {
+ return key;
+ }
+ }
+ }
+ if (this.currentMetaInfo == null) {
+ this.currentMetaInfo = this.metaInfo.getParent();
+ } else {
+ this.currentMetaInfo = this.currentMetaInfo.getParent();
+ }
+ if (this.currentMetaInfo == null) {
+ this.iterator = null;
+ } else {
+ this.iterator = this.currentMetaInfo.iterator();
+ this.keyPrefix = this.currentMetaInfo.keyPrefix;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean hasNext() {
+
+ return this.next != null;
+ }
+
+ @Override
+ public String next() {
+
+ if (this.next == null) {
+ throw new NoSuchElementException();
+ }
+ String key = this.next;
+ this.next = findNext();
+ return key;
+ }
+
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoEmpty.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoEmpty.java
new file mode 100644
index 0000000..de69dca
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoEmpty.java
@@ -0,0 +1,59 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo.impl;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+import io.github.mmm.base.metainfo.MetaInfo;
+
+/**
+ * Empty {@link MetaInfo} implementation.
+ */
+public final class MetaInfoEmpty extends AbstractMetaInfo {
+
+ /** The singleton instance. */
+ public static final MetaInfoEmpty INSTANCE = new MetaInfoEmpty();
+
+ private MetaInfoEmpty() {
+
+ super();
+ }
+
+ @Override
+ public Iterator iterator() {
+
+ return Collections.emptyIterator();
+ }
+
+ @Override
+ public String getPlain(String key) {
+
+ return null;
+ }
+
+ @Override
+ public int size() {
+
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+
+ return true;
+ }
+
+ @Override
+ public MetaInfo with(String key, String value) {
+
+ return new MetaInfoValues(new MetaInfoValue(key, value), null);
+ }
+
+ @Override
+ public String toString(boolean sort) {
+
+ return "{}";
+ }
+
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoMap.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoMap.java
new file mode 100644
index 0000000..aae99e1
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoMap.java
@@ -0,0 +1,63 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo.impl;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import io.github.mmm.base.metainfo.MetaInfo;
+
+/**
+ * Implementation of {@link MetaInfo} as wrapper of {@link Map}.
+ *
+ * @since 1.0.0
+ */
+public final class MetaInfoMap extends InheritedMetaInfo {
+
+ /** @see #getPlain(String) */
+ protected final Map map;
+
+ /**
+ * The constructor.
+ *
+ * @param map the {@link Map} to wrap as {@link MetaInfo}.
+ */
+ public MetaInfoMap(Map map) {
+
+ this(map, null, "");
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param map the {@link Map} to wrap as {@link MetaInfo}.
+ * @param parent the {@link #getParent() parent}.
+ * @param keyPrefix the {@link #getKeyPrefix() key prefix}.
+ */
+ public MetaInfoMap(Map map, InheritedMetaInfo parent, String keyPrefix) {
+
+ super(parent, keyPrefix);
+ Objects.requireNonNull(map);
+ this.map = map;
+ }
+
+ @Override
+ protected Set getKeysPlain() {
+
+ return this.map.keySet();
+ }
+
+ @Override
+ protected String getPlain(String key) {
+
+ return this.map.get(key);
+ }
+
+ @Override
+ protected int getSizePlain() {
+
+ return this.map.size();
+ }
+
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoProperties.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoProperties.java
new file mode 100644
index 0000000..e023cd5
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoProperties.java
@@ -0,0 +1,47 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo.impl;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+
+import io.github.mmm.base.metainfo.MetaInfo;
+
+/**
+ * Implementation of {@link MetaInfo} as wrapper of {@link Map}.
+ *
+ * @since 1.0.0
+ */
+public final class MetaInfoProperties extends InheritedMetaInfo {
+
+ private final Properties properties;
+
+ /**
+ * The constructor.
+ *
+ * @param properties the {@link Properties}.
+ * @param parent the {@link #getParent() parent}.
+ * @param keyPrefix the {@link #getKeyPrefix() key prefix}.
+ */
+ public MetaInfoProperties(Properties properties, InheritedMetaInfo parent, String keyPrefix) {
+
+ super(parent, keyPrefix);
+ Objects.requireNonNull(properties);
+ this.properties = properties;
+ }
+
+ @Override
+ protected Set getKeysPlain() {
+
+ return this.properties.stringPropertyNames();
+ }
+
+ @Override
+ protected String getPlain(String key) {
+
+ return this.properties.getProperty(key);
+ }
+
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoSingle.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoSingle.java
new file mode 100644
index 0000000..71b0695
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoSingle.java
@@ -0,0 +1,73 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo.impl;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import io.github.mmm.base.metainfo.MetaInfo;
+
+/**
+ * Implementation of {@link MetaInfo} as wrapper of {@link Map}.
+ *
+ * @since 1.0.0
+ */
+public final class MetaInfoSingle extends InheritedMetaInfo {
+
+ private final String key;
+
+ private final String value;
+
+ /**
+ * The constructor.
+ *
+ * @param parent the {@link #getParent() parent}.
+ * @param key the {@link #get(String) key}.
+ * @param value the {@link #get(String) value}.
+ */
+ public MetaInfoSingle(InheritedMetaInfo parent, String key, String value) {
+
+ super(parent, "");
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(value);
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ protected String getPlain(String metaKey) {
+
+ if (this.key.equals(metaKey)) {
+ return this.value;
+ }
+ return null;
+ }
+
+ @Override
+ protected Set getKeysPlain() {
+
+ return Collections.singleton(this.key);
+ }
+
+ @Override
+ protected int getSizePlain() {
+
+ return 1;
+ }
+
+ @Override
+ public int size() {
+
+ int size = 1;
+ if (this.parent != null) {
+ size = this.parent.size();
+ if (this.parent.get(this.key) == null) {
+ size++;
+ }
+ }
+ return size;
+ }
+
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoValue.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoValue.java
new file mode 100644
index 0000000..23dc178
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoValue.java
@@ -0,0 +1,99 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo.impl;
+
+import java.util.Objects;
+
+/**
+ * Simple class for a {@link io.github.mmm.base.metainfo.MetaInfos#value() meta-info value} as {@link #getKey()
+ * key}-{@link #getValue() value} pair. Further, it implements a linked list
+ *
+ * @since 1.0.0
+ */
+public final class MetaInfoValue {
+
+ final String key;
+
+ final String value;
+
+ final MetaInfoValue next;
+
+ /**
+ * The constructor.
+ *
+ * @param key the {@link #getKey() key}.
+ * @param value the {#getValue() value}.
+ */
+ public MetaInfoValue(String key, String value) {
+
+ this(key, value, null);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param key the {@link #getKey() key}.
+ * @param value the {#getValue() value}.
+ * @param next the next {@link MetaInfoValue}.
+ */
+ public MetaInfoValue(String key, String value, MetaInfoValue next) {
+
+ super();
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(value);
+ this.key = key;
+ this.value = value;
+ this.next = next;
+ }
+
+ /**
+ * @return the key.
+ * @see java.util.Map.Entry#getKey()
+ */
+ public String getKey() {
+
+ return this.key;
+ }
+
+ /**
+ * @return the value.
+ * @see java.util.Map.Entry#getValue()
+ */
+ public String getValue() {
+
+ return this.value;
+ }
+
+ /**
+ * @return the link to the next {@link MetaInfoValue} or {@code null} if this is the last element of the list.
+ */
+ public MetaInfoValue getNext() {
+
+ return this.next;
+ }
+
+ @Override
+ public String toString() {
+
+ return this.key + "=" + this.value;
+ }
+
+ /**
+ * @param keyValue the key-value pair. See {@link io.github.mmm.base.metainfo.MetaInfos#value()}.
+ * @param next the {@link #getNext() next} {@link MetaInfoValue}.
+ * @return the new {@link MetaInfoValue} with the parsed {@link #getKey() key} + {@link #getValue() value} and linked
+ * to {@code next}.
+ */
+ public static MetaInfoValue of(String keyValue, MetaInfoValue next) {
+
+ int i = keyValue.indexOf('=');
+ if (i < 0) {
+ throw new IllegalArgumentException(
+ "Invalid MetaInfo '" + keyValue + "' - has to be in the form '«key»=«value»'!");
+ }
+ String key = keyValue.substring(0, i);
+ String value = keyValue.substring(i + 1);
+ return new MetaInfoValue(key, value, next);
+ }
+
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoValueIterator.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoValueIterator.java
new file mode 100644
index 0000000..960f5e9
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoValueIterator.java
@@ -0,0 +1,64 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo.impl;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Implementation of {@link Iterator} for {@link MetaInfoValue}.
+ */
+public class MetaInfoValueIterator implements Iterator {
+
+ private MetaInfoValue value;
+
+ private Iterator parentIterator;
+
+ private String next;
+
+ /**
+ * The constructor.
+ *
+ * @param values the {@link MetaInfoValues} to iterate.
+ */
+ public MetaInfoValueIterator(MetaInfoValues values) {
+
+ super();
+ this.value = values.first;
+ if (values.parent != null) {
+ this.parentIterator = values.parent.iterator();
+ }
+ this.next = findNext();
+ }
+
+ private String findNext() {
+
+ if (this.value != null) {
+ String key = this.value.key;
+ this.value = this.value.next;
+ return key;
+ } else if ((this.parentIterator != null) && this.parentIterator.hasNext()) {
+ return this.parentIterator.next();
+ }
+ this.parentIterator = null;
+ return null;
+ }
+
+ @Override
+ public boolean hasNext() {
+
+ return this.next != null;
+ }
+
+ @Override
+ public String next() {
+
+ if (this.next == null) {
+ throw new NoSuchElementException();
+ }
+ String key = this.next;
+ this.next = findNext();
+ return key;
+ }
+
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoValues.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoValues.java
new file mode 100644
index 0000000..8de62b8
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/impl/MetaInfoValues.java
@@ -0,0 +1,125 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+package io.github.mmm.base.metainfo.impl;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import io.github.mmm.base.exception.DuplicateObjectException;
+import io.github.mmm.base.metainfo.MetaInfo;
+import io.github.mmm.base.metainfo.MetaInfos;
+
+/**
+ * Implementation of {@link MetaInfo} based on {@link MetaInfoValue}.
+ *
+ * @since 1.0.0
+ */
+public final class MetaInfoValues extends InheritedMetaInfo {
+
+ final MetaInfoValue first;
+
+ private final int size;
+
+ /**
+ * The constructor.
+ *
+ * @param first the first {@link MetaInfoValue}.
+ * @param parent the {@link #getParent() parent}.
+ */
+ public MetaInfoValues(MetaInfoValue first, InheritedMetaInfo parent) {
+
+ super(parent, "");
+ this.first = first;
+ int i = 0;
+ MetaInfoValue current = first;
+ while (current != null) {
+ i++;
+ current = current.next;
+ }
+ this.size = i;
+ }
+
+ boolean hasUniqueKeys() {
+
+ Set keys = new HashSet<>(this.size);
+ MetaInfoValue current = this.first;
+ while (current != null) {
+ boolean added = keys.add(current.key);
+ if (!added) {
+ throw new DuplicateObjectException("MetaInfoValue", current.key);
+ }
+ current = current.next;
+ }
+ return true;
+ }
+
+ @Override
+ protected String getPlain(String key) {
+
+ MetaInfoValue current = this.first;
+ while (current != null) {
+ if (current.key.equals(key)) {
+ return current.value;
+ }
+ current = current.next;
+ }
+ return null;
+ }
+
+ @Override
+ protected Set getKeysPlain() {
+
+ Set keys = new HashSet<>(this.size);
+ MetaInfoValue current = this.first;
+ while (current != null) {
+ keys.add(current.key);
+ current = current.next;
+ }
+ return keys;
+ }
+
+ @Override
+ protected int getSizePlain() {
+
+ return this.size;
+ }
+
+ @Override
+ public Iterator iterator() {
+
+ return new MetaInfoValueIterator(this);
+ }
+
+ @Override
+ public int size() {
+
+ if (this.parent == null) {
+ return this.size;
+ }
+ return super.size();
+ }
+
+ @Override
+ public MetaInfo with(String key, String value) {
+
+ return new MetaInfoValues(new MetaInfoValue(key, value, this.first), this.parent);
+ }
+
+ @Override
+ public MetaInfo with(MetaInfos metaValues) {
+
+ String[] values = metaValues.value();
+ if (values.length == 0) {
+ return this;
+ }
+ MetaInfoValue value = this.first;
+ // process in reverse order to retain original order...
+ for (int i = values.length - 1; i >= 0; i--) {
+ value = MetaInfoValue.of(values[i], value);
+ }
+ MetaInfoValues result = new MetaInfoValues(value, getParent());
+ return result;
+ }
+
+}
diff --git a/metainfo/src/main/java/io/github/mmm/base/metainfo/package-info.java b/metainfo/src/main/java/io/github/mmm/base/metainfo/package-info.java
new file mode 100644
index 0000000..b6ff4b3
--- /dev/null
+++ b/metainfo/src/main/java/io/github/mmm/base/metainfo/package-info.java
@@ -0,0 +1,7 @@
+/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0 */
+/**
+ * Contains API for {@link io.github.mmm.base.metainfo.MetaInfo} and the annotation
+ * {@link io.github.mmm.base.metainfo.MetaInfos}.
+ */
+package io.github.mmm.base.metainfo;
\ No newline at end of file
diff --git a/metainfo/src/main/java/module-info.java b/metainfo/src/main/java/module-info.java
new file mode 100644
index 0000000..0f304bc
--- /dev/null
+++ b/metainfo/src/main/java/module-info.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * Types for meta-information.
+ */
+module io.github.mmm.base.metainfo {
+
+ exports io.github.mmm.base.metainfo;
+
+ requires transitive io.github.mmm.base;
+
+}
diff --git a/metainfo/src/test/java/io/github/mmm/base/metadata/MetaInfoTest.java b/metainfo/src/test/java/io/github/mmm/base/metadata/MetaInfoTest.java
new file mode 100644
index 0000000..762ac3f
--- /dev/null
+++ b/metainfo/src/test/java/io/github/mmm/base/metadata/MetaInfoTest.java
@@ -0,0 +1,128 @@
+package io.github.mmm.base.metadata;
+
+import java.util.Map;
+import java.util.Properties;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import io.github.mmm.base.metainfo.MetaInfo;
+import io.github.mmm.base.metainfo.MetaInfos;
+import io.github.mmm.base.metainfo.impl.AbstractMetaInfo;
+
+/**
+ * Test of {@link MetaInfo}.
+ */
+@MetaInfos({ "key1=value1", "key2=value2" })
+public class MetaInfoTest extends Assertions {
+
+ private String toString(MetaInfo metaInfo) {
+
+ return ((AbstractMetaInfo) metaInfo).toString(true);
+ }
+
+ /** Test of {@link MetaInfo#empty()}. */
+ @Test
+ public void testEmpty() {
+
+ // arrange
+ // act
+ MetaInfo metaInfo = MetaInfo.empty();
+ // assert
+ assertThat(metaInfo.isEmpty()).isTrue();
+ assertThat(metaInfo.size()).isEqualTo(0);
+ assertThat(metaInfo).isEmpty();
+ assertThat(metaInfo.get("key1")).isEqualTo(null);
+ assertThat(metaInfo.get("key2")).isEqualTo(null);
+ assertThat(metaInfo.getParent()).isNull();
+ assertThat(toString(metaInfo)).isEqualTo("{}");
+ }
+
+ /** Test of {@link MetaInfo#with(String, String)}. */
+ @Test
+ public void testWithSingle() {
+
+ // arrange
+ String key = "key1";
+ String value = "value1";
+ MetaInfo metaInfo = MetaInfo.empty();
+ // act
+ metaInfo = metaInfo.with(key, value);
+ // assert
+ assertThat(metaInfo.isEmpty()).isFalse();
+ assertThat(metaInfo.size()).isEqualTo(1);
+ assertThat(metaInfo).containsExactly(key);
+ assertThat(metaInfo.get("key1")).isEqualTo("value1");
+ assertThat(metaInfo.get("key2")).isEqualTo(null);
+ assertThat(metaInfo.getParent()).isNull();
+ assertThat(toString(metaInfo)).isEqualTo("{key1=value1}");
+ }
+
+ /** Test of {@link MetaInfo#with(java.lang.reflect.AnnotatedElement)}. */
+ @Test
+ public void testWithClass() {
+
+ // arrange
+ Class annotatedClass = MetaInfoTest.class;
+ // act
+ MetaInfo metaInfo = MetaInfo.empty().with(annotatedClass);
+ // assert
+ assertThat(metaInfo.size()).isEqualTo(2);
+ assertThat(metaInfo).containsExactly("key1", "key2");
+ assertThat(metaInfo.get("key1")).isEqualTo("value1");
+ assertThat(metaInfo.get("key2")).isEqualTo("value2");
+ assertThat(toString(metaInfo)).isEqualTo("{key1=value1, key2=value2}");
+
+ // and act
+ MetaInfo metaInfo2 = metaInfo.with(Map.of("key2", "two", "key3", "value3"));
+ // and assert
+ assertThat(metaInfo2.get("key1")).isEqualTo("value1");
+ assertThat(metaInfo2.get(true, "key1")).isEqualTo("value1");
+ assertThat(metaInfo2.get(false, "key1")).isNull();
+ assertThat(metaInfo2.get("key2")).isEqualTo("two");
+ assertThat(metaInfo2.get("key3")).isEqualTo("value3");
+ assertThat(metaInfo2).containsExactlyInAnyOrder("key1", "key2", "key3");
+ assertThat(metaInfo2.getParent()).isSameAs(metaInfo);
+ assertThat(toString(metaInfo2)).isEqualTo("{key1=value1, key2=two, key3=value3}");
+ }
+
+ /** Test of {@link MetaInfo#with(Map)}. */
+ @Test
+ public void testWithMap() {
+
+ // arrange
+ Map map = Map.of("key1", "42", "key2", "true", "key3", "magicValue");
+ // act
+ MetaInfo metaInfo = MetaInfo.empty().with(map);
+ // assert
+ assertThat(metaInfo.size()).isEqualTo(map.size());
+ assertThat(metaInfo).containsExactly(map.keySet().toArray(new String[map.size()]));
+ assertThat(metaInfo.getAsLong(true, "key1", -1)).isEqualTo(42L);
+ assertThat(metaInfo.getAsBoolean(true, "key2", false)).isTrue();
+ assertThat(metaInfo.get("key3")).isEqualTo("magicValue");
+ assertThat(toString(metaInfo)).isEqualTo("{key1=42, key2=true, key3=magicValue}");
+ }
+
+ /** Test of {@link MetaInfo#with(Properties)}. */
+ @Test
+ public void testWithProperties() {
+
+ // arrange
+ Properties root = new Properties();
+ root.setProperty("key2", "false");
+ Properties properties = new Properties(root);
+ properties.setProperty("key3", "magicValue");
+ properties.setProperty("key2", "true");
+ root.setProperty("key1", "42");
+ // act
+ MetaInfo metaInfo = MetaInfo.empty().with(properties);
+ // assert
+ assertThat(metaInfo.size()).isEqualTo(3); // properties.size() returns 2 even though we have key1-3
+ assertThat(metaInfo).containsExactly(properties.stringPropertyNames().toArray(new String[3]));
+ assertThat(metaInfo.getAsLong("key1", -1)).isEqualTo(42L);
+ assertThat(metaInfo.getAsBoolean(true, "key2", false)).isTrue();
+ assertThat(metaInfo.get("key3")).isEqualTo("magicValue");
+ assertThat(toString(metaInfo)).isEqualTo("{key1=42, key2=true, key3=magicValue}");
+ }
+
+}
diff --git a/placement/src/main/java/io/github/mmm/base/placement/Alignment.java b/placement/src/main/java/io/github/mmm/base/placement/Alignment.java
index 79fc521..e6666e8 100644
--- a/placement/src/main/java/io/github/mmm/base/placement/Alignment.java
+++ b/placement/src/main/java/io/github/mmm/base/placement/Alignment.java
@@ -274,7 +274,6 @@ public Alignment toAlignment(Orientation orientation) {
/**
* @return the corresponding {@link Direction} or {@code null} for {@link #CENTER}.
- * @since 4.0.0
*/
public Direction toDirection() {
@@ -307,7 +306,6 @@ public Direction toDirection() {
*
* @param direction is the {@link Direction}. May be {@code null} for {@link #CENTER}.
* @return the corresponding {@link Alignment}.
- * @since 4.0.0
*/
public static Alignment fromDirection(Direction direction) {
diff --git a/pom.xml b/pom.xml
index 96d77d3..93fefaf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,6 +21,7 @@
core
placement
+ metainfo
@@ -36,6 +37,11 @@
mmm-base
${project.version}
+
+ ${project.groupId}
+ mmm-base-metainfo
+ ${project.version}
+
${project.groupId}
mmm-base-placement