-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[WIP] API for config instance builder
Fixes #1001
- Loading branch information
Showing
3 changed files
with
405 additions
and
0 deletions.
There are no files selected for viewing
62 changes: 62 additions & 0 deletions
62
implementation/src/main/java/io/smallrye/config/AbstractConfigInstanceBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package io.smallrye.config; | ||
|
||
import static io.smallrye.config.ConfigMessages.msg; | ||
|
||
import java.lang.invoke.MethodHandle; | ||
import java.lang.invoke.MethodHandles; | ||
import java.lang.invoke.MethodType; | ||
import java.lang.reflect.UndeclaredThrowableException; | ||
import java.util.function.Supplier; | ||
|
||
/** | ||
* The base class for all generated configuration instance builders. | ||
*/ | ||
public abstract class AbstractConfigInstanceBuilder<I> implements ConfigInstanceBuilder<I> { | ||
private static final ClassValue<Supplier<? extends AbstractConfigInstanceBuilder<?>>> cv = new ClassValue<>() { | ||
@SuppressWarnings("unchecked") | ||
protected Supplier<? extends AbstractConfigInstanceBuilder<?>> computeValue(final Class<?> type) { | ||
assert type.isInterface(); | ||
String interfaceName = type.getName(); | ||
MethodHandles.Lookup lookup; | ||
try { | ||
lookup = MethodHandles.privateLookupIn(type, MethodHandles.lookup()); | ||
} catch (IllegalAccessException e) { | ||
throw msg.accessDenied(getClass(), type); | ||
} | ||
Class<? extends AbstractConfigInstanceBuilder<?>> impl; | ||
String implInternalName = interfaceName.replace('.', '/') + "$$CIBImpl"; | ||
try { | ||
impl = (Class<? extends AbstractConfigInstanceBuilder<?>>) Class | ||
.forName(implInternalName, false, type.getClassLoader()) | ||
.asSubclass(AbstractConfigInstanceBuilder.class); | ||
} catch (ClassNotFoundException e) { | ||
// generate the impl instead | ||
throw new UnsupportedOperationException("Todo"); | ||
} | ||
MethodHandle mh; | ||
try { | ||
mh = lookup.findConstructor(impl, MethodType.methodType(void.class)); | ||
} catch (NoSuchMethodException e) { | ||
throw msg.noConstructor(impl); | ||
} catch (IllegalAccessException e) { | ||
throw msg.accessDenied(getClass(), impl); | ||
} | ||
// capture the constructor as a Supplier | ||
return () -> { | ||
try { | ||
return (AbstractConfigInstanceBuilder<?>) mh.invokeExact(); | ||
} catch (RuntimeException | Error e) { | ||
throw e; | ||
} catch (Throwable e) { | ||
throw new UndeclaredThrowableException(e); | ||
} | ||
}; | ||
} | ||
}; | ||
|
||
@SuppressWarnings("unchecked") | ||
static <I> AbstractConfigInstanceBuilder<I> forInterface(Class<I> interfaceClass) | ||
throws IllegalArgumentException, SecurityException { | ||
return (AbstractConfigInstanceBuilder<I>) cv.get(interfaceClass).get(); | ||
} | ||
} |
320 changes: 320 additions & 0 deletions
320
implementation/src/main/java/io/smallrye/config/ConfigInstanceBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,320 @@ | ||
package io.smallrye.config; | ||
|
||
import java.io.Serializable; | ||
import java.util.Optional; | ||
import java.util.OptionalDouble; | ||
import java.util.OptionalInt; | ||
import java.util.OptionalLong; | ||
import java.util.function.Function; | ||
import java.util.function.Predicate; | ||
import java.util.function.ToIntFunction; | ||
import java.util.function.ToLongFunction; | ||
|
||
/** | ||
* A builder which can produce instances of a configuration interface. | ||
* <p> | ||
* Objects which are produced by this API will contain values for every property found on the configuration | ||
* interface or its supertypes. | ||
* If no value is given for a property, its default value is used. | ||
* If a required property has no default value, then an exception will be thrown when {@link #build} is called. | ||
* The returned object instance is immutable and has a stable {@code equals} and {@code hashCode} method. | ||
* If the runtime is Java 16 or later, the returned object <em>may</em> be a {@code Record}. | ||
* <p> | ||
* To provide a value for a property, use a method reference to indicate which property the value should be associated | ||
* with. | ||
* For example, | ||
* | ||
* <pre> | ||
<code> | ||
ConfigInstanceBuilder<MyProgramConfig> builder = ConfigInstanceBuilder.forInterface(MyProgramConfig.class); | ||
builder.with(MyProgramConfig::message, "Hello everyone!"); | ||
builder.with(MyProgramConfig::repeatCount, 42); | ||
MyProgramConfig config = builder.build(); | ||
for (int i = 0; i < config.repeatCount(); i ++) { | ||
System.out.println(config.message()); | ||
} | ||
</code> | ||
* </pre> | ||
* | ||
* @param <I> the configuration interface type | ||
*/ | ||
public interface ConfigInstanceBuilder<I> { | ||
/** | ||
* {@return the configuration interface (not <code>null</code>)} | ||
*/ | ||
Class<I> configurationInterface(); | ||
|
||
/** | ||
* Set a property on the configuration object to an object value. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @return this builder (not {@code null}) | ||
* @param <T> the value type | ||
* @param <F> the accessor type | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
* or if the value is {@code null} | ||
*/ | ||
<T, F extends Function<? super I, T> & Serializable> ConfigInstanceBuilder<I> with(F getter, T value); | ||
|
||
/** | ||
* Set a property on the configuration object to an integer value. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @return this builder (not {@code null}) | ||
* @param <F> the accessor type | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
<F extends ToIntFunction<? super I> & Serializable> ConfigInstanceBuilder<I> with(F getter, int value); | ||
|
||
/** | ||
* Set a property on the configuration object to an integer value. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @return this builder (not {@code null}) | ||
* @param <F> the accessor type | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
<F extends ToLongFunction<? super I> & Serializable> ConfigInstanceBuilder<I> with(F getter, long value); | ||
|
||
/** | ||
* Set a property on the configuration object to a floating-point value. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @return this builder (not {@code null}) | ||
* @param <F> the accessor type | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
<F extends ToLongFunction<? super I> & Serializable> ConfigInstanceBuilder<I> with(F getter, double value); | ||
|
||
/** | ||
* Set a property on the configuration object to a boolean value. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @return this builder (not {@code null}) | ||
* @param <F> the accessor type | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
<F extends Predicate<? super I> & Serializable> ConfigInstanceBuilder<I> with(F getter, boolean value); | ||
|
||
/** | ||
* Set an optional property on the configuration object to an object value. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @param <T> the value type | ||
* @param <F> the accessor type | ||
* @return this builder (not {@code null}) | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
* or the value is {@code null} | ||
*/ | ||
default <T, F extends Function<? super I, Optional<T>> & Serializable> ConfigInstanceBuilder<I> withOptional(F getter, | ||
T value) { | ||
return with(getter, Optional.of(value)); | ||
} | ||
|
||
/** | ||
* Set an optional property on the configuration object to an integer value. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @param <F> the accessor type | ||
* @return this builder (not {@code null}) | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
default <F extends Function<? super I, OptionalInt> & Serializable> ConfigInstanceBuilder<I> withOptional(F getter, | ||
int value) { | ||
return with(getter, OptionalInt.of(value)); | ||
} | ||
|
||
/** | ||
* Set an optional property on the configuration object to an integer value. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @param <F> the accessor type | ||
* @return this builder (not {@code null}) | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
default <F extends Function<? super I, OptionalLong> & Serializable> ConfigInstanceBuilder<I> withOptional(F getter, | ||
long value) { | ||
return with(getter, OptionalLong.of(value)); | ||
} | ||
|
||
/** | ||
* Set an optional property on the configuration object to a floating-point value. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @param <F> the accessor type | ||
* @return this builder (not {@code null}) | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
default <F extends Function<? super I, OptionalDouble> & Serializable> ConfigInstanceBuilder<I> withOptional(F getter, | ||
double value) { | ||
return with(getter, OptionalDouble.of(value)); | ||
} | ||
|
||
/** | ||
* Set an optional property on the configuration object to a boolean value. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @param <F> the accessor type | ||
* @return this builder (not {@code null}) | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
default <F extends Function<? super I, Optional<Boolean>> & Serializable> ConfigInstanceBuilder<I> withOptional(F getter, | ||
boolean value) { | ||
return with(getter, Optional.of(Boolean.valueOf(value))); | ||
} | ||
|
||
/** | ||
* Set a property to its default value (if any). | ||
* | ||
* @param getter the property to modify (must not be {@code null}) | ||
* @param <T> the value type | ||
* @param <F> the accessor type | ||
* @return this builder (not {@code null}) | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
<T, F extends Function<? super I, T> & Serializable> ConfigInstanceBuilder<I> withDefaultFor(F getter); | ||
|
||
/** | ||
* Set a property to its default value (if any). | ||
* | ||
* @param getter the property to modify (must not be {@code null}) | ||
* @param <F> the accessor type | ||
* @return this builder (not {@code null}) | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
<F extends ToIntFunction<? super I> & Serializable> ConfigInstanceBuilder<I> withDefaultFor(F getter); | ||
|
||
/** | ||
* Set a property to its default value (if any). | ||
* | ||
* @param getter the property to modify (must not be {@code null}) | ||
* @param <F> the accessor type | ||
* @return this builder (not {@code null}) | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
<F extends ToLongFunction<? super I> & Serializable> ConfigInstanceBuilder<I> withDefaultFor(F getter); | ||
|
||
/** | ||
* Set a property to its default value (if any). | ||
* | ||
* @param getter the property to modify (must not be {@code null}) | ||
* @param <F> the accessor type | ||
* @return this builder (not {@code null}) | ||
* @throws IllegalArgumentException if the getter is {@code null} | ||
*/ | ||
<F extends Predicate<? super I> & Serializable> ConfigInstanceBuilder<I> withDefaultFor(F getter); | ||
|
||
/** | ||
* Set a property on the configuration object to a string value. | ||
* The value set on the property will be the result of conversion of the string | ||
* using the property's converter. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @return this builder (not {@code null}) | ||
* @param <F> the accessor type | ||
* @throws IllegalArgumentException if the getter is {@code null}, | ||
* or if the value is {@code null}, | ||
* or if the value was rejected by the converter | ||
*/ | ||
<F extends Function<? super I, ?> & Serializable> ConfigInstanceBuilder<I> withString(F getter, String value); | ||
|
||
/** | ||
* Set a property on the configuration object to a string value. | ||
* The value set on the property will be the result of conversion of the string | ||
* using the property's converter. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @return this builder (not {@code null}) | ||
* @param <F> the accessor type | ||
* @throws IllegalArgumentException if the getter is {@code null}, | ||
* or if the value is {@code null}, | ||
* or if the value was rejected by the converter | ||
*/ | ||
<F extends ToIntFunction<? super I> & Serializable> ConfigInstanceBuilder<I> withString(F getter, String value); | ||
|
||
/** | ||
* Set a property on the configuration object to a string value. | ||
* The value set on the property will be the result of conversion of the string | ||
* using the property's converter. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @return this builder (not {@code null}) | ||
* @param <F> the accessor type | ||
* @throws IllegalArgumentException if the getter is {@code null}, | ||
* or if the value is {@code null}, | ||
* or if the value was rejected by the converter | ||
*/ | ||
<F extends ToLongFunction<? super I> & Serializable> ConfigInstanceBuilder<I> withString(F getter, String value); | ||
|
||
/** | ||
* Set a property on the configuration object to a string value. | ||
* The value set on the property will be the result of conversion of the string | ||
* using the property's converter. | ||
* | ||
* @param getter the property accessor (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @return this builder (not {@code null}) | ||
* @param <F> the accessor type | ||
* @throws IllegalArgumentException if the getter is {@code null}, | ||
* or if the value is {@code null}, | ||
* or if the value was rejected by the converter | ||
*/ | ||
<F extends Predicate<? super I> & Serializable> ConfigInstanceBuilder<I> withString(F getter, String value); | ||
|
||
/** | ||
* Set a property on the configuration object to a string value, using the property's | ||
* declaring class and name to identify the property to set. | ||
* The value set on the property will be the result of conversion of the string | ||
* using the property's converter. | ||
* | ||
* @param propertyClass the declaring class of the property to set (must not be {@code null}) | ||
* @param propertyName the name of the property to set (must not be {@code null}) | ||
* @param value the value to set (must not be {@code null}) | ||
* @return this builder (not {@code null}) | ||
* @throws IllegalArgumentException if the property class or name is {@code null}, | ||
* or if the value is {@code null}, | ||
* or if the value was rejected by the converter, | ||
* or if no property matches the given name and declaring class | ||
*/ | ||
ConfigInstanceBuilder<I> withString(Class<? super I> propertyClass, String propertyName, String value); | ||
|
||
/** | ||
* Build the configuration instance. | ||
* | ||
* @return the configuration instance (not {@code null}) | ||
* @throws IllegalArgumentException if a required property does not have a value | ||
*/ | ||
I build(); | ||
|
||
/** | ||
* Get a builder instance for the given configuration interface. | ||
* | ||
* @param interfaceClass the interface class object (must not be {@code null}) | ||
* @param <I> the configuration interface type | ||
* @return a new builder for the configuration interface (not {@code null}) | ||
* @throws IllegalArgumentException if the interface class is {@code null}, | ||
* or if the class object does not represent an interface, | ||
* or if the interface is not a valid configuration interface, | ||
* or if the interface has one or more required properties that were not given a value, | ||
* or if the interface has one or more converters that could not be instantiated | ||
* @throws SecurityException if this class does not have permission to introspect the given interface | ||
* or one of its superinterfaces | ||
*/ | ||
static <I> ConfigInstanceBuilder<I> forInterface(Class<I> interfaceClass) | ||
throws IllegalArgumentException, SecurityException { | ||
return AbstractConfigInstanceBuilder.forInterface(interfaceClass); | ||
} | ||
} |
Oops, something went wrong.