Skip to content

Commit 8e0101d

Browse files
authored
Merge pull request #31 from KyoriPowered/feat/value-source
feat: Allow mass-populating options from a value source
2 parents 81b05e8 + d005096 commit 8e0101d

File tree

12 files changed

+548
-12
lines changed

12 files changed

+548
-12
lines changed

src/main/java/net/kyori/option/Option.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424
package net.kyori.option;
2525

26+
import net.kyori.option.value.ValueType;
2627
import org.jetbrains.annotations.ApiStatus;
2728
import org.jetbrains.annotations.NotNull;
2829
import org.jetbrains.annotations.Nullable;
@@ -37,7 +38,6 @@
3738
*/
3839
@ApiStatus.NonExtendable
3940
public interface Option<V> {
40-
4141
/**
4242
* Create an option with a boolean value type.
4343
*
@@ -87,8 +87,20 @@ static <E extends Enum<E>> Option<E> enumOption(final String id, final Class<E>
8787
*
8888
* @return the value type
8989
* @since 1.0.0
90+
* @deprecated for removal since 1.1.0, use {@link #valueType()} instead
91+
*/
92+
@Deprecated
93+
default @NotNull Class<V> type() {
94+
return this.valueType().type();
95+
}
96+
97+
/**
98+
* Get information about the option's value type.
99+
*
100+
* @return the value type
101+
* @since 1.0.0
90102
*/
91-
@NotNull Class<V> type();
103+
@NotNull ValueType<V> valueType();
92104

93105
/**
94106
* Get a default value for the option, if any is present.

src/main/java/net/kyori/option/OptionImpl.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,17 @@
2424
package net.kyori.option;
2525

2626
import java.util.Objects;
27+
import net.kyori.option.value.ValueType;
2728
import org.jetbrains.annotations.NotNull;
2829
import org.jetbrains.annotations.Nullable;
2930

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

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

36-
OptionImpl(final @NotNull String id, final @NotNull Class<V> type, final @Nullable V defaultValue) {
37+
OptionImpl(final @NotNull String id, final @NotNull ValueType<V> type, final @Nullable V defaultValue) {
3738
this.id = id;
3839
this.type = type;
3940
this.defaultValue = defaultValue;
@@ -45,7 +46,7 @@ final class OptionImpl<V> implements Option<V> {
4546
}
4647

4748
@Override
48-
public @NotNull Class<V> type() {
49+
public @NotNull ValueType<V> valueType() {
4950
return this.type;
5051
}
5152

src/main/java/net/kyori/option/OptionSchemaImpl.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Set;
3030
import java.util.concurrent.ConcurrentHashMap;
3131
import java.util.concurrent.ConcurrentMap;
32+
import net.kyori.option.value.ValueType;
3233
import org.jetbrains.annotations.NotNull;
3334
import org.jetbrains.annotations.Nullable;
3435

@@ -83,7 +84,7 @@ static final class Instances {
8384
}
8485

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

100101
@Override
101102
public @NotNull Option<String> stringOption(final @NotNull String id, final @Nullable String defaultValue) {
102-
return this.register(id, String.class, defaultValue);
103+
return this.register(id, ValueType.stringType(), defaultValue);
103104
}
104105

105106
@Override
106107
public @NotNull Option<Boolean> booleanOption(final @NotNull String id, final boolean defaultValue) {
107-
return this.register(id, Boolean.class, defaultValue);
108+
return this.register(id, ValueType.booleanType(), defaultValue);
108109
}
109110

110111
@Override
111112
public @NotNull Option<Integer> intOption(final @NotNull String id, final int defaultValue) {
112-
return this.register(id, Integer.class, defaultValue);
113+
return this.register(id, ValueType.integerType(), defaultValue);
113114
}
114115

115116
@Override
116117
public @NotNull Option<Double> doubleOption(final @NotNull String id, final double defaultValue) {
117-
return this.register(id, Double.class, defaultValue);
118+
return this.register(id, ValueType.doubleType(), defaultValue);
118119
}
119120

120121
@Override
121122
public @NotNull <E extends Enum<E>> Option<E> enumOption(final @NotNull String id, final @NotNull Class<E> enumClazz, final @Nullable E defaultValue) {
122-
return this.register(id, enumClazz, defaultValue);
123+
return this.register(id, ValueType.enumType(enumClazz), defaultValue);
123124
}
124125

125126
@Override

src/main/java/net/kyori/option/OptionState.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import java.util.Map;
2727
import java.util.function.Consumer;
28+
import net.kyori.option.value.ValueSource;
2829
import org.jetbrains.annotations.ApiStatus;
2930
import org.jetbrains.annotations.NotNull;
3031

@@ -152,6 +153,15 @@ interface Builder {
152153
*/
153154
@NotNull Builder values(final @NotNull OptionState existing);
154155

156+
/**
157+
* Set a value for all options within the {@link #schema()} where a value is provided by the {@code source}.
158+
*
159+
* @param source a source to populate values
160+
* @return this builder
161+
* @since 1.1.0
162+
*/
163+
@NotNull Builder values(final @NotNull ValueSource source);
164+
155165
/**
156166
* Create a completed option state.
157167
*

src/main/java/net/kyori/option/OptionStateImpl.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.SortedMap;
3131
import java.util.TreeMap;
3232
import java.util.function.Consumer;
33+
import net.kyori.option.value.ValueSource;
3334
import org.jetbrains.annotations.NotNull;
3435
import org.jetbrains.annotations.Nullable;
3536

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

5758
@Override
5859
public <V> V value(final @NotNull Option<V> option) {
59-
final V value = option.type().cast(this.values.get(requireNonNull(option, "flag")));
60+
final V value = option.valueType().type().cast(this.values.get(requireNonNull(option, "flag")));
6061
return value == null ? option.defaultValue() : value;
6162
}
6263

@@ -209,6 +210,18 @@ private void putAll(final Map<Option<?>, Object> values) {
209210
}
210211
return this;
211212
}
213+
214+
@Override
215+
public @NotNull Builder values(final @NotNull ValueSource source) {
216+
for (final Option<?> opt : this.schema.knownOptions()) {
217+
final Object value = source.value(opt);
218+
if (value != null) {
219+
this.values.put(opt, value);
220+
}
221+
}
222+
223+
return this;
224+
}
212225
}
213226

214227
static final class VersionedBuilderImpl implements OptionState.VersionedBuilder {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* This file is part of option, licensed under the MIT License.
3+
*
4+
* Copyright (c) 2025 KyoriPowered
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package net.kyori.option.value;
25+
26+
import net.kyori.option.Option;
27+
import org.jetbrains.annotations.NotNull;
28+
import org.jetbrains.annotations.Nullable;
29+
30+
/**
31+
* A source for external option values.
32+
*
33+
* @since 1.1.0
34+
*/
35+
@FunctionalInterface
36+
public interface ValueSource {
37+
/**
38+
* A value source that will extract option values from environment variables.
39+
*
40+
* <p>Any of the characters {@code :}, {@code /}, and {@code -} will be replaced with {@code _}.</p>
41+
*
42+
* @return an environment variable-backed value source with no prefix
43+
* @since 1.1.0
44+
*/
45+
static @NotNull ValueSource environmentVariable() {
46+
return environmentVariable("");
47+
}
48+
49+
/**
50+
* A value source that will extract option values from environment variables.
51+
*
52+
* <p>Any of the characters {@code :}, {@code /}, and {@code -} will be replaced with {@code _}.</p>
53+
*
54+
* @param prefix the prefix for environment lookup, which will be prepended to keys followed by a {@code _},
55+
* or the empty string for no prefix
56+
* @return an environment variable-backed value source
57+
* @since 1.1.0
58+
*/
59+
static @NotNull ValueSource environmentVariable(final @NotNull String prefix) {
60+
return new ValueSources.EnvironmentVariable(prefix);
61+
}
62+
63+
/**
64+
* A value source that will extract option values from system properties.
65+
*
66+
* <p>Any of the characters {@code :} and {@code /} will be replaced with {@code .}.</p>
67+
*
68+
* @return a system property-backed value source with no prefix
69+
* @since 1.1.0
70+
*/
71+
static @NotNull ValueSource systemProperty() {
72+
return systemProperty("");
73+
}
74+
75+
/**
76+
* A value source that will extract option values from system properties.
77+
*
78+
* <p>Any of the characters {@code :} and {@code /} will be replaced with {@code .}.</p>
79+
*
80+
* @param prefix the prefix for property lookup, which will be prepended to properties followed by a {@code .},
81+
* or the empty string for no prefix
82+
* @return a system property-backed value source
83+
* @since 1.1.0
84+
*/
85+
static @NotNull ValueSource systemProperty(final @NotNull String prefix) {
86+
return new ValueSources.SystemProperty(prefix);
87+
}
88+
89+
/**
90+
* Provide a value for the specified option, if any is set.
91+
*
92+
* @param option the option
93+
* @return a value, if set
94+
* @param <T> the value type
95+
* @since 1.1.0
96+
*/
97+
<T> @Nullable T value(final @NotNull Option<T> option);
98+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* This file is part of option, licensed under the MIT License.
3+
*
4+
* Copyright (c) 2025 KyoriPowered
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package net.kyori.option.value;
25+
26+
import java.util.Locale;
27+
import java.util.regex.Matcher;
28+
import java.util.regex.Pattern;
29+
import net.kyori.option.Option;
30+
import org.jetbrains.annotations.NotNull;
31+
import org.jetbrains.annotations.Nullable;
32+
33+
final class ValueSources {
34+
static final ValueSource ENVIRONMENT = new EnvironmentVariable("");
35+
static final ValueSource SYSTEM_PROPERTIES = new SystemProperty("");
36+
37+
private ValueSources() {
38+
}
39+
40+
static final class EnvironmentVariable implements ValueSource {
41+
private static final Pattern ENVIRONMENT_SUBST_PATTERN = Pattern.compile("[:\\-/]");
42+
private static final String ENVIRONMENT_VAR_SEPARATOR = "_";
43+
44+
private final String prefix;
45+
46+
EnvironmentVariable(final String prefix) {
47+
this.prefix = prefix.isEmpty() ? "" : prefix.toUpperCase(Locale.ROOT) + ENVIRONMENT_VAR_SEPARATOR;
48+
}
49+
50+
@Override
51+
public <T> @Nullable T value(final @NotNull Option<T> option) {
52+
final StringBuffer buf = new StringBuffer(option.id().length() + this.prefix.length());
53+
buf.append(this.prefix);
54+
final Matcher match = ENVIRONMENT_SUBST_PATTERN.matcher(option.id());
55+
while (match.find()) {
56+
match.appendReplacement(buf, ENVIRONMENT_VAR_SEPARATOR);
57+
}
58+
match.appendTail(buf);
59+
60+
final String value = System.getenv(buf.toString().toUpperCase(Locale.ROOT));
61+
if (value == null) {
62+
return null;
63+
}
64+
65+
return option.valueType().parse(value);
66+
}
67+
}
68+
69+
static final class SystemProperty implements ValueSource {
70+
private static final Pattern SYSTEM_PROP_SUBST_PATTERN = Pattern.compile("[:/]");
71+
private static final String SYSTEM_PROPERTY_SEPARATOR = ".";
72+
73+
private final String prefix;
74+
75+
SystemProperty(final String prefix) {
76+
this.prefix = prefix.isEmpty() ? "" : prefix + SYSTEM_PROPERTY_SEPARATOR;
77+
}
78+
79+
@Override
80+
public <T> @Nullable T value(final @NotNull Option<T> option) {
81+
final StringBuffer buf = new StringBuffer(option.id().length() + this.prefix.length());
82+
buf.append(this.prefix);
83+
final Matcher match = SYSTEM_PROP_SUBST_PATTERN.matcher(option.id());
84+
while (match.find()) {
85+
match.appendReplacement(buf, SYSTEM_PROPERTY_SEPARATOR);
86+
}
87+
match.appendTail(buf);
88+
89+
final String value = System.getProperty(buf.toString());
90+
if (value == null) {
91+
return null;
92+
}
93+
94+
return option.valueType().parse(value);
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)