Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/main/java/net/kyori/option/Option.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package net.kyori.option;

import net.kyori.option.value.ValueType;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -37,7 +38,6 @@
*/
@ApiStatus.NonExtendable
public interface Option<V> {

/**
* Create an option with a boolean value type.
*
Expand Down Expand Up @@ -87,8 +87,20 @@ static <E extends Enum<E>> Option<E> enumOption(final String id, final Class<E>
*
* @return the value type
* @since 1.0.0
* @deprecated for removal since 1.1.0, use {@link #valueType()} instead
*/
@Deprecated
default @NotNull Class<V> type() {
return this.valueType().type();
}

/**
* Get information about the option's value type.
*
* @return the value type
* @since 1.0.0
*/
@NotNull Class<V> type();
@NotNull ValueType<V> valueType();

/**
* Get a default value for the option, if any is present.
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/net/kyori/option/OptionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@
package net.kyori.option;

import java.util.Objects;
import net.kyori.option.value.ValueType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

final class OptionImpl<V> implements Option<V> {

private final String id;
private final Class<V> type;
private final ValueType<V> type;
private final @Nullable V defaultValue; // excluded from equality comparisons, it does not form part of the option identity

OptionImpl(final @NotNull String id, final @NotNull Class<V> type, final @Nullable V defaultValue) {
OptionImpl(final @NotNull String id, final @NotNull ValueType<V> type, final @Nullable V defaultValue) {
this.id = id;
this.type = type;
this.defaultValue = defaultValue;
Expand All @@ -45,7 +46,7 @@ final class OptionImpl<V> implements Option<V> {
}

@Override
public @NotNull Class<V> type() {
public @NotNull ValueType<V> valueType() {
return this.type;
}

Expand Down
13 changes: 7 additions & 6 deletions src/main/java/net/kyori/option/OptionSchemaImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.kyori.option.value.ValueType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -83,7 +84,7 @@ static final class Instances {
}

final class MutableImpl implements OptionSchema.Mutable {
<T> Option<T> register(final String id, final Class<T> type, final @Nullable T defaultValue) {
<T> Option<T> register(final String id, final ValueType<T> type, final @Nullable T defaultValue) {
final Option<T> ret = new OptionImpl<>(
requireNonNull(id, "id"),
requireNonNull(type, "type"),
Expand All @@ -99,27 +100,27 @@ <T> Option<T> register(final String id, final Class<T> type, final @Nullable T d

@Override
public @NotNull Option<String> stringOption(final @NotNull String id, final @Nullable String defaultValue) {
return this.register(id, String.class, defaultValue);
return this.register(id, ValueType.stringType(), defaultValue);
}

@Override
public @NotNull Option<Boolean> booleanOption(final @NotNull String id, final boolean defaultValue) {
return this.register(id, Boolean.class, defaultValue);
return this.register(id, ValueType.booleanType(), defaultValue);
}

@Override
public @NotNull Option<Integer> intOption(final @NotNull String id, final int defaultValue) {
return this.register(id, Integer.class, defaultValue);
return this.register(id, ValueType.integerType(), defaultValue);
}

@Override
public @NotNull Option<Double> doubleOption(final @NotNull String id, final double defaultValue) {
return this.register(id, Double.class, defaultValue);
return this.register(id, ValueType.doubleType(), defaultValue);
}

@Override
public @NotNull <E extends Enum<E>> Option<E> enumOption(final @NotNull String id, final @NotNull Class<E> enumClazz, final @Nullable E defaultValue) {
return this.register(id, enumClazz, defaultValue);
return this.register(id, ValueType.enumType(enumClazz), defaultValue);
}

@Override
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/net/kyori/option/OptionState.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import java.util.Map;
import java.util.function.Consumer;
import net.kyori.option.value.ValueSource;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

Expand Down Expand Up @@ -152,6 +153,15 @@ interface Builder {
*/
@NotNull Builder values(final @NotNull OptionState existing);

/**
* Set a value for all options within the {@link #schema()} where a value is provided by the {@code source}.
*
* @param source a source to populate values
* @return this builder
* @since 1.1.0
*/
@NotNull Builder values(final @NotNull ValueSource source);

/**
* Create a completed option state.
*
Expand Down
15 changes: 14 additions & 1 deletion src/main/java/net/kyori/option/OptionStateImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Consumer;
import net.kyori.option.value.ValueSource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -56,7 +57,7 @@ public boolean has(final @NotNull Option<?> option) {

@Override
public <V> V value(final @NotNull Option<V> option) {
final V value = option.type().cast(this.values.get(requireNonNull(option, "flag")));
final V value = option.valueType().type().cast(this.values.get(requireNonNull(option, "flag")));
return value == null ? option.defaultValue() : value;
}

Expand Down Expand Up @@ -209,6 +210,18 @@ private void putAll(final Map<Option<?>, Object> values) {
}
return this;
}

@Override
public @NotNull Builder values(final @NotNull ValueSource source) {
for (final Option<?> opt : this.schema.knownOptions()) {
final Object value = source.value(opt);
if (value != null) {
this.values.put(opt, value);
}
}

return this;
}
}

static final class VersionedBuilderImpl implements OptionState.VersionedBuilder {
Expand Down
98 changes: 98 additions & 0 deletions src/main/java/net/kyori/option/value/ValueSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* This file is part of option, licensed under the MIT License.
*
* Copyright (c) 2025 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.option.value;

import net.kyori.option.Option;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* A source for external option values.
*
* @since 1.1.0
*/
@FunctionalInterface
public interface ValueSource {
/**
* A value source that will extract option values from environment variables.
*
* <p>Any of the characters {@code :}, {@code /}, and {@code -} will be replaced with {@code _}.</p>
*
* @return an environment variable-backed value source with no prefix
* @since 1.1.0
*/
static @NotNull ValueSource environmentVariable() {
return environmentVariable("");
}

/**
* A value source that will extract option values from environment variables.
*
* <p>Any of the characters {@code :}, {@code /}, and {@code -} will be replaced with {@code _}.</p>
*
* @param prefix the prefix for environment lookup, which will be prepended to keys followed by a {@code _},
* or the empty string for no prefix
* @return an environment variable-backed value source
* @since 1.1.0
*/
static @NotNull ValueSource environmentVariable(final @NotNull String prefix) {
return new ValueSources.EnvironmentVariable(prefix);
}

/**
* A value source that will extract option values from system properties.
*
* <p>Any of the characters {@code :} and {@code /} will be replaced with {@code .}.</p>
*
* @return a system property-backed value source with no prefix
* @since 1.1.0
*/
static @NotNull ValueSource systemProperty() {
return systemProperty("");
}

/**
* A value source that will extract option values from system properties.
*
* <p>Any of the characters {@code :} and {@code /} will be replaced with {@code .}.</p>
*
* @param prefix the prefix for property lookup, which will be prepended to properties followed by a {@code .},
* or the empty string for no prefix
* @return a system property-backed value source
* @since 1.1.0
*/
static @NotNull ValueSource systemProperty(final @NotNull String prefix) {
return new ValueSources.SystemProperty(prefix);
}

/**
* Provide a value for the specified option, if any is set.
*
* @param option the option
* @return a value, if set
* @param <T> the value type
* @since 1.1.0
*/
<T> @Nullable T value(final @NotNull Option<T> option);
}
97 changes: 97 additions & 0 deletions src/main/java/net/kyori/option/value/ValueSources.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* This file is part of option, licensed under the MIT License.
*
* Copyright (c) 2025 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.option.value;

import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.kyori.option.Option;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

final class ValueSources {
static final ValueSource ENVIRONMENT = new EnvironmentVariable("");
static final ValueSource SYSTEM_PROPERTIES = new SystemProperty("");

private ValueSources() {
}

static final class EnvironmentVariable implements ValueSource {
private static final Pattern ENVIRONMENT_SUBST_PATTERN = Pattern.compile("[:\\-/]");
private static final String ENVIRONMENT_VAR_SEPARATOR = "_";

private final String prefix;

EnvironmentVariable(final String prefix) {
this.prefix = prefix.isEmpty() ? "" : prefix.toUpperCase(Locale.ROOT) + ENVIRONMENT_VAR_SEPARATOR;
}

@Override
public <T> @Nullable T value(final @NotNull Option<T> option) {
final StringBuffer buf = new StringBuffer(option.id().length() + this.prefix.length());
buf.append(this.prefix);
final Matcher match = ENVIRONMENT_SUBST_PATTERN.matcher(option.id());
while (match.find()) {
match.appendReplacement(buf, ENVIRONMENT_VAR_SEPARATOR);
}
match.appendTail(buf);

final String value = System.getenv(buf.toString().toUpperCase(Locale.ROOT));
if (value == null) {
return null;
}

return option.valueType().parse(value);
}
}

static final class SystemProperty implements ValueSource {
private static final Pattern SYSTEM_PROP_SUBST_PATTERN = Pattern.compile("[:/]");
private static final String SYSTEM_PROPERTY_SEPARATOR = ".";

private final String prefix;

SystemProperty(final String prefix) {
this.prefix = prefix.isEmpty() ? "" : prefix + SYSTEM_PROPERTY_SEPARATOR;
}

@Override
public <T> @Nullable T value(final @NotNull Option<T> option) {
final StringBuffer buf = new StringBuffer(option.id().length() + this.prefix.length());
buf.append(this.prefix);
final Matcher match = SYSTEM_PROP_SUBST_PATTERN.matcher(option.id());
while (match.find()) {
match.appendReplacement(buf, SYSTEM_PROPERTY_SEPARATOR);
}
match.appendTail(buf);

final String value = System.getProperty(buf.toString());
if (value == null) {
return null;
}

return option.valueType().parse(value);
}
}
}
Loading