Skip to content

Commit

Permalink
rework a bit ConfigurationParameterCollector to make it easier to int…
Browse files Browse the repository at this point in the history
…egrate
  • Loading branch information
rmannibucau committed Jan 17, 2022
1 parent 039980a commit 6436523
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright (c) 2021 - Yupiik SAS - https://www.yupiik.com
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package io.yupiik.batch.runtime.configuration.documentation;

import io.yupiik.batch.runtime.configuration.Binder;
import io.yupiik.batch.runtime.configuration.Param;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static java.util.Locale.ROOT;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;

public class ConfigurationParameterCollector implements Supplier<Map<String, ConfigurationParameterCollector.Parameter>> {
private final List<Class<?>> classes;
private final BiPredicate<Object, String> isNested;
private final Function<String, Binder> binderFactory;

protected ConfigurationParameterCollector(final List<Class<?>> classes, final BiPredicate<Object, String> isNested,
final Function<String, Binder> binderFactory) {
this.classes = classes;
this.isNested = isNested;
this.binderFactory = binderFactory;
}

public ConfigurationParameterCollector(final Class<?>... batchClasses) {
this(List.of(batchClasses));
}

public ConfigurationParameterCollector(final List<Class<?>> batchClasses) {
this(batchClasses, null, null);
}

public ConfigurationParameterCollector(final List<Class<?>> batchClasses,
final BiPredicate<Object, String> isNested) {
this(batchClasses, isNested, null);
}

@Override
public Map<String, Parameter> get() {
return getWithPrefix(batchType -> batchType.getSimpleName().toLowerCase(Locale.ROOT));
}

public Map<String, Parameter> getWithPrefix(final Function<Class<?>, String> prefix) {
return classes.stream()
.flatMap(batchType -> {
final var doc = new HashMap<String, Parameter>();
visitor(prefix.apply(batchType), doc).bind(batchType);
return doc.entrySet().stream();
})
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
}

protected Binder visitor(final String prefix, final Map<String, Parameter> doc) {
return new Binder(prefix, List.of()) {
@Override
protected void doBind(final Object instance, final Field param, final Param conf, final String paramName) {
onParam(instance, param, conf, paramName, this::isList, this::isNestedModel, this::newNestedBinder, doc);
}

@Override
protected boolean isNestedModel(final Object instance, final String fieldType) {
return super.isNestedModel(instance, fieldType) || (isNested != null && isNested.test(instance, fieldType));
}
};
}

public String toAsciidoc() {
return "[options=\"header\",cols=\"a,a,2\"]\n" +
"|===\n" +
"|Name|Env Variable|Description\n" +
getWithPrefix(c -> "").entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(e -> "" +
"| `" + e.getKey() + "` " + (e.getValue().param().required() ? "*" : "") +
"| `" + e.getKey().replaceAll("[^A-Za-z0-9]", "_").toUpperCase(ROOT) + "` " +
"| " + e.getValue().param().description() + "\n")
.collect(joining()) + "\n" +
"|===\n";
}

protected void onParam(final Object instance, final Field param, final Param conf, final String paramName,
final Predicate<Field> isList, final BiPredicate<Object, String> isNestedModel,
final BiFunction<String, List<String>, Binder> newNestedBinder, final Map<String, Parameter> doc) {
if (isList.test(param) || !isNestedModel.test(instance, param.getType().getTypeName())) {
if (!param.canAccess(instance)) {
param.setAccessible(true);
}
try {
final var defValue = param.get(instance);
doc.put(paramName, new Parameter(
conf, defValue == null ? null : String.valueOf(defValue), param.getGenericType(),
paramName));
} catch (final IllegalAccessException e) {
throw new IllegalStateException(e);
}
} else { // recursive call
newNestedBinder.apply(paramName, List.of()).bind(param.getType());
}
}

public record Parameter(Param param, String defaultValue, Type type, String name) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,92 +17,73 @@

import io.yupiik.batch.runtime.batch.Batch;
import io.yupiik.batch.runtime.batch.Batches;
import io.yupiik.batch.runtime.batch.Binder;
import io.yupiik.batch.runtime.configuration.Binder;
import io.yupiik.batch.runtime.configuration.Param;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Supplier;

import static java.util.Locale.ROOT;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;

@Deprecated // use simple-configuration one
public record ConfigurationParameterCollector(
List<Class<Batch<?>>> batchClasses,
List<Class<?>> batchClasses,
BiPredicate<Object, String> isNested) implements Supplier<Map<String, ConfigurationParameterCollector.Parameter>> {
public ConfigurationParameterCollector(final List<Class<Batch<?>>> batchClasses) {
public ConfigurationParameterCollector(final List<Class<?>> batchClasses) {
this(batchClasses, null);
}

@Override
public Map<String, Parameter> get() {
return getWithPrefix(batchType -> batchType.getSimpleName().toLowerCase(Locale.ROOT));
return mapParams(collector().get());
}

public String toAsciidoc() {
return collector().toAsciidoc();
}

public Map<String, Parameter> getWithPrefix(final Function<Class<?>, String> prefix) {
return batchClasses.stream()
.flatMap(batchType -> {
final var doc = new HashMap<String, Parameter>();
new Binder(prefix.apply(batchType), List.of()) {
@Override
protected void doBind(final Object instance, final Field param, final Param conf, final String paramName) {
onParam(instance, param, conf, paramName);
}
return mapParams(collector().getWithPrefix(prefix));
}

private void onParam(final Object instance, final Field param, final Param conf, final String paramName) {
if (isList(param) || !isNestedModel(instance, param.getType().getTypeName())) {
if (!param.canAccess(instance)) {
param.setAccessible(true);
}
try {
final var defValue = param.get(instance);
doc.put(paramName, new Parameter(
conf, defValue == null ? null : String.valueOf(defValue), param.getGenericType(),
paramName));
} catch (final IllegalAccessException e) {
throw new IllegalStateException(e);
}
} else { // recursive call
new Binder(paramName, List.of()) {
@Override
protected void doBind(final Object instance, final Field param, final Param conf, final String paramName) {
onParam(instance, param, conf, paramName);
}
}.bind(param.getType());
}
}
private io.yupiik.batch.runtime.configuration.documentation.ConfigurationParameterCollector collector() {
return new io.yupiik.batch.runtime.configuration.documentation.ConfigurationParameterCollector(mapClasses()) {
@Override
protected Binder visitor(final String prefix, final Map<String, Parameter> doc) {
return new io.yupiik.batch.runtime.batch.Binder(prefix, List.of()) { // handle both @Param
@Override
protected void doBind(final Object instance, final Field param, final Param conf, final String paramName) {
onParam(instance, param, conf, paramName, this::isList, this::isNestedModel, this::newNestedBinder, doc);
}

@Override
protected boolean isNestedModel(final Object instance, final String fieldType) {
return super.isNestedModel(instance, fieldType) || (isNested != null && isNested.test(instance, fieldType));
}
}.bind(Batches.findConfType(batchType));
return doc.entrySet().stream();
})
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
@Override
protected boolean isNestedModel(final Object instance, final String fieldType) {
return super.isNestedModel(instance, fieldType) || (isNested != null && isNested.test(instance, fieldType));
}
};
}
};
}

public String toAsciidoc() {
return "[options=\"header\",cols=\"a,a,2\"]\n" +
"|===\n" +
"|Name|Env Variable|Description\n" +
getWithPrefix(c -> "").entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(e -> "" +
"| `" + e.getKey() + "` " + (e.getValue().param().required() ? "*" : "") +
"| `" + e.getKey().replaceAll("[^A-Za-z0-9]", "_").toUpperCase(ROOT) + "` " +
"| " + e.getValue().param().description() + "\n")
.collect(joining()) + "\n" +
"|===\n";
private Map<String, Parameter> mapParams(final Map<String, io.yupiik.batch.runtime.configuration.documentation.ConfigurationParameterCollector.Parameter> params) {
return params.entrySet().stream()
.collect(toMap(Map.Entry::getKey, p -> new Parameter(
p.getValue().param(), p.getValue().defaultValue(), p.getValue().type(), p.getValue().name())));
}

private List<Class<?>> mapClasses() {
return List.of(
batchClasses.stream()
.map(batchType -> (Class<?>) (Batch.class.isAssignableFrom(batchType) ?
Batches.findConfType(Class.class.cast(batchType)) : batchType))
.toArray(Class[]::new));
}

public static record Parameter(Param param, String defaultValue, Type type, String name) {
public record Parameter(Param param, String defaultValue, Type type, String name) {
}
}

0 comments on commit 6436523

Please sign in to comment.