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