All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.jstach.kiwi.kvs.KeyValue Maven / Gradle / Ivy

The newest version!
package io.jstach.kiwi.kvs;

import java.net.URI;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;

import org.jspecify.annotations.Nullable;

/**
 * Represents a key-value pair in the Kiwi configuration system. Unlike a simple
 * {@code Map.Entry}, this record holds additional metadata that provides
 * more context about the key-value pair, such as:
 *
 * 
    *
  • The original raw value (before any interpolation).
  • *
  • The expanded value (after interpolation).
  • *
  • Source information indicating where the key-value pair was loaded from.
  • *
  • Flags indicating special behavior (e.g., sensitive data or disabling * interpolation).
  • *
* * This class is immutable and provides methods for handling sensitive data, * interpolation, and chaining resources for further key-value loading. * *

Example Usage

* * The following example shows how to load key-values using the Kiwi system: * * {@snippet : * * var kvs = KeyValuesSystem.defaults() * .loader() * .add("classpath:/start.properties") * .add("system:///") * .add("env:///") * .load(); * * // Accessing key-value pairs: * for (KeyValue kv : kvs) { * System.out.println("Key: " + kv.key() + ", Value: " + kv.value()); * } * } * * @param key the key associated with this key-value pair. * @param expanded the value after any interpolation. * @param meta additional metadata associated with this key-value pair. */ public record KeyValue(String key, String expanded, Meta meta) { /** * Constructs a new {@code KeyValue} with the given key, expanded value, and * associated metadata. * @param key the key associated with this key-value pair (cannot be {@code null}). * @param expanded the value after any interpolation (cannot be {@code null}). * @param meta additional metadata (cannot be {@code null}). */ public KeyValue { Objects.requireNonNull(key); Objects.requireNonNull(expanded); Objects.requireNonNull(meta); } /** * Constructs a new {@code KeyValue} with a raw value, using the provided key and raw * value. The {@link Meta} information will be initialized with default settings. * @param key the key associated with this key-value pair. * @param raw the raw (unexpanded) value associated with this key. */ public KeyValue(String key, String raw) { this(key, raw, Meta.of(key, raw, Source.EMPTY, Set.of())); } public final static String REDACTED_MESSAGE = "REDACTED"; /** * Gets the expanded value after interpolation. * @return the expanded value. */ public String value() { return this.expanded; } /** * Gets the original raw value before any interpolation. * @return the raw value. */ public String raw() { return meta().raw(); } /** * Gets the set of flags associated with this key-value pair. * @return the flags as an immutable set. */ Set flags() { return meta().flags(); } KeyValue replaceNullSource(URI uri) { var original = this.meta.source(); if (!original.isNullResource()) { return this; } var source = original.withURI(uri); var meta = this.meta.withSource(source); return new KeyValue(key, expanded, meta); } /** * Creates a new KeyValue with an updated key. * @param key the new key. * @return new key value. */ public KeyValue withKey(String key) { return new KeyValue(key, expanded, meta); } /** * Creates a new {@code KeyValue} with an updated expanded value. * @param expanded the new expanded value. * @return a new {@code KeyValue} with the updated expanded value. */ public KeyValue withExpanded(String expanded) { return new KeyValue(key, expanded, meta); } /** * Returns a new {@code KeyValue} instance with its value expanded using the provided * function. The expansion function takes the key as input and returns the expanded * value, or {@code null} if no expansion is necessary. If an expanded value is * provided by the function, a new {@code KeyValue} instance is created with that * expanded value; otherwise, the current instance is returned unchanged. * @param expanded a function that takes the key as input and returns the expanded * value or {@code null} if no expansion is needed * @return a new {@code KeyValue} instance with the expanded value, or the original * instance if no expansion was applied */ public KeyValue withExpanded(@SuppressWarnings("exports") Function expanded) { String n = key(); String exp = expanded.apply(n); if (exp != null) { return withExpanded(exp); } return this; } /** * Adds additional flags to this {@code KeyValue} and returns a new instance. * @param flagsCol a collection of flags to add. * @return a new {@code KeyValue} with the added flags. */ public KeyValue addFlags(Collection flagsCol) { var flags = EnumSet.noneOf(Flag.class); flags.addAll(flags()); flags.addAll(flagsCol); var meta = this.meta.withFlags(flags); return new KeyValue(key, expanded, meta); } /** * Metadata associated with a {@link KeyValue}. This interface encapsulates additional * information about a key-value pair, such as its raw, unexpanded value, its source, * and any flags that modify its behavior. * *

Components:

*
    *
  • {@link #raw()} - The original, unexpanded value associated with the key.
  • *
  • {@link #source()} - The source of the key-value, represented by a * {@link KeyValue.Source}.
  • *
  • {@link #flags()} - A set of {@link KeyValue.Flag} that can modify how the * key-value is processed.
  • *
* *

Immutability:

Implementations of {@code Meta} are immutable, ensuring that * once created, the metadata cannot be altered. However, methods are provided to * create modified copies with updated flags or source information. * */ public sealed interface Meta { /** * The original key that was collected on the source. * @return the original key. */ String originalKey(); /** * Retrieves the raw, unexpanded value associated with the {@link KeyValue}. * @return the original raw value as a {@link String}. */ String raw(); /** * Returns the source from which the key-value pair was loaded. * @return a {@link KeyValue.Source} representing the origin of the key-value. */ Source source(); /** * Retrieves any flags associated with the key-value. These flags can alter how * the key-value is interpreted or displayed. * @return a {@link Set} of {@link KeyValue.Flag} representing the flags. */ Set flags(); /** * Creates a new {@code Meta} instance with the specified flags. * @param flags the collection of flags to include in the new metadata. * @return a new {@code Meta} instance with the updated flags. */ Meta withFlags(Collection flags); /** * Creates a new {@code Meta} instance with the specified source. * @param source the new source to associate with the metadata. * @return a new {@code Meta} instance with the updated source. */ Meta withSource(Source source); /** * Factory method to create a new {@code Meta} instance with the specified raw * value, source, and flags. * @param originalKey original key. * @param raw the raw value associated with the key-value. * @param source the source from which the key-value was loaded. * @param flags the flags associated with the key-value. * @return a new {@code Meta} instance. */ static Meta of(String originalKey, String raw, Source source, Set flags) { return new DefaultMeta(originalKey, raw, source, flags); } } record DefaultMeta(String originalKey, String raw, Source source, Set flags) implements Meta { public DefaultMeta { flags = FlagSet.copyOf(flags, Flag.class); } @Override public Meta withFlags(Collection flags) { var flagsCopy = FlagSet.copyOf(flags, Flag.class); return new DefaultMeta(originalKey, raw, source, flagsCopy); } @Override public Meta withSource(Source source) { return new DefaultMeta(originalKey, raw, source, flags); } @Override public String toString() { return "Meta[raw=" + raw + ", source=" + Source.toString(source) + ", flags=" + flags + "]"; } } /** * Represents the source information for a {@link KeyValue}. A {@code Source} can * indicate where the key-value pair was loaded from, such as a configuration file, * system properties, or environment variables. It also supports linking a key-value * pair to another one through the {@code reference} field. * *

Fields:

*
    *
  • {@code uri} - The URI representing the origin of the key-value pair.
  • *
  • {@code reference} - An optional {@link KeyValue} that this key-value depends on * (e.g., for chained loading).
  • *
  • {@code index} - A numerical identifier that can be used for ordering or * tracking.
  • *
* *

Usage Example:

* * The following example demonstrates creating a source from a URI: * * {@snippet : * URI fileUri = URI.create("file:///config.properties"); * KeyValue.Source source = new KeyValue.Source(fileUri, null, 0); * System.out.println("Source URI: " + source.uri()); * } * * @param uri the URI representing the origin of the key-value pair. * @param reference an optional key-value pair that this one references. * @param index an optional index for ordering. */ public record Source(URI uri, @Nullable KeyValue reference, int index) implements KeyValueReference, KeyValuesSource { /** * A constant representing a "null" URI for cases where the source is not * specified. */ public static URI NULL_URI = URI.create("null:///"); /** * A default, empty source instance used when no specific source information is * available. */ public static Source EMPTY = new Source(); /** * Creates an empty source. * @see #EMPTY */ public Source() { this(NULL_URI, null, 0); } /** * Checks if this {@code Source} is considered a null resource. * @return {@code true} if the source URI is {@link #NULL_URI}, otherwise * {@code false}. */ boolean isNullResource() { return uri.equals(NULL_URI); } /** * Creates a new {@code Source} instance with the specified URI, keeping the other * fields the same. * @param uri the new URI to set. * @return a new {@code Source} with the updated URI. */ Source withURI(URI uri) { return new Source(uri, reference, index); } /** * Redacts the source. * @return redacted source. */ Source redact() { if (!KeyValueReference.isRedacted(this)) { return this; } var uri = URI.create(KeyValueReference.redactURI(this, KeyValue.REDACTED_MESSAGE)); return new Source(uri, reference, index); } @Override public String toString() { return toString(redact()); } private static String toString(Source source) { if (source.isNullResource()) { return "Source[empty]"; } return "Source[uri=" + source.uri + (source.reference == null ? "" : ", reference=" + KeyValueReference.toStringRef(source.reference)) + ", index=" + source.index + "]"; } } /** * Enumeration representing flags that can modify the behavior of a {@link KeyValue}. * These flags provide additional metadata that can influence how key-value pairs are * processed, stored, or displayed. * *

Flags:

*
    *
  • {@link #NO_INTERPOLATION} - Indicates that the key-value should not be * interpolated, meaning its value should not undergo any variable substitution.
  • *
  • {@link #SENSITIVE} - Marks the key-value as containing sensitive information, * such as passwords or secrets, which may be redacted when displayed.
  • *
*/ public enum Flag { /** * Indicates that the key-value pair should not undergo any interpolation or * variable substitution. Useful for cases where the raw value should be preserved * as-is. */ NO_INTERPOLATION, /** * Marks the key-value as containing sensitive information. When this flag is set, * the value may be redacted when displayed to avoid leaking secrets. */ SENSITIVE; /** * Checks if this flag is set for the given {@link KeyValue}. * @param kv the key-value pair to check. * @return {@code true} if the flag is set on the provided key-value, otherwise * {@code false}. */ public boolean isSet(KeyValue kv) { return kv.isFlag(NO_INTERPOLATION); } } /** * Checks if the {@link Flag#NO_INTERPOLATION} flag is set on this {@code KeyValue}. * This indicates that the value should not undergo any interpolation or variable * substitution. * @return {@code true} if the {@code NO_INTERPOLATION} flag is set, otherwise * {@code false}. */ public boolean isNoInterpolation() { return isFlag(Flag.NO_INTERPOLATION); } /** * Checks if the {@link Flag#SENSITIVE} flag is set on this {@code KeyValue}. A * sensitive value is marked to indicate that it should be treated as confidential and * may be redacted when displayed to prevent leaking sensitive information. * @return {@code true} if the {@code SENSITIVE} flag is set, otherwise {@code false}. */ public boolean isSensitive() { return isFlag(Flag.SENSITIVE); } /** * Checks if a specific flag is set on this {@code KeyValue}. * @param flag the flag to check * @return {@code true} if the specified flag is set, otherwise {@code false}. */ public boolean isFlag(Flag flag) { return meta().flags().contains(flag); } /** * Redacts the sensitive value in this key-value pair by replacing it with a default * message. * @return a new {@code KeyValue} with the sensitive data redacted. */ public KeyValue redact() { return redact(REDACTED_MESSAGE); } /** * Redacts the sensitive value in this key-value pair by replacing it with a custom * message. * @param redactMessage the message to use for redaction. * @return a new {@code KeyValue} with the sensitive data redacted. */ public KeyValue redact(String redactMessage) { if (isSensitive()) { String expanded = redactMessage; String raw = redactMessage; var flags = EnumSet.copyOf(this.meta.flags()); flags.remove(Flag.SENSITIVE); var source = meta.source(); var originalKey = meta.originalKey(); Meta meta = new DefaultMeta(originalKey, raw, source, flags); return new KeyValue(key, expanded, meta); } return this; } boolean isOriginalKey() { return key.equals(meta.originalKey()); } @Override public String toString() { return toString(redact()); } private static String toString(KeyValue kv) { return "KeyValue[key='" + kv.key + "'" // + (kv.isOriginalKey() ? "" : ", originalKey='" + kv.meta().originalKey() + "'") // + ", raw='" + kv.raw() + "'" // + ", expanded='" + kv.expanded + "'"// + ", source=" + Source.toString(kv.meta().source())// + (kv.flags().isEmpty() ? "" : ", flags=" + kv.flags()) // + "]"; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy