mirror of
https://github.com/nosqlbench/nosqlbench.git
synced 2025-02-25 18:55:28 -06:00
improve type-safe intention mapping
This commit is contained in:
@@ -1,49 +0,0 @@
|
||||
package io.nosqlbench.engine.api.templating;
|
||||
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
* <p>The result type from calling {@link ParsedOp#getRequiredTypeFromEnum(Class)}, which
|
||||
* captures the matching enum type as well as the field name and a value function.</p>
|
||||
*
|
||||
* <p>The <em>enumId</em> is type-safe enum value from the provided enum to the above method.
|
||||
* The <em>field</em> is the field name which was passed. The <em>targetFunction</em> is
|
||||
* a {@link LongFunction} of Object which can be called to return an associated target value.</p>
|
||||
*
|
||||
* For example, with an emum like <pre>{@code
|
||||
* public enum LandMovers {
|
||||
* BullDozer,
|
||||
* DumpTruck
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* and a parsed op like <pre>{@code
|
||||
* (json)
|
||||
* {
|
||||
* "op": {
|
||||
* "bulldozer": "{dozerid}"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* (yaml)
|
||||
* op:
|
||||
* bulldozer: "{dozerid}
|
||||
* }</pre>
|
||||
* the result will be returned with the following: <pre>{@code
|
||||
* enumId: BullDozer
|
||||
* field: bulldozer
|
||||
* targetFunction: (long l) -> ...
|
||||
* }</pre>
|
||||
* @param <E>
|
||||
*/
|
||||
public class NamedTarget<E extends Enum<E>> {
|
||||
public final E enumId;
|
||||
public final String field;
|
||||
public final LongFunction<?> targetFunction;
|
||||
|
||||
public NamedTarget(E enumId, String matchingOpFieldName, LongFunction<?> value) {
|
||||
this.enumId = enumId;
|
||||
this.field = matchingOpFieldName;
|
||||
this.targetFunction = value;
|
||||
}
|
||||
}
|
||||
@@ -367,6 +367,11 @@ public class ParsedTemplateMap implements LongFunction<Map<String, ?>>, StaticFi
|
||||
return getAsRequiredFunction(name, String.class);
|
||||
}
|
||||
|
||||
public <V extends Enum<V>> Optional<LongFunction<V>> getAsOptionalEnumFunction(String name, Class<V> type) {
|
||||
Optional<LongFunction<String>> nameFunc = this.getAsOptionalFunction(name, String.class);
|
||||
return nameFunc.map((f) -> (l) -> Enum.valueOf(type, f.apply(l)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the op field as a {@link LongFunction}
|
||||
*
|
||||
@@ -378,11 +383,23 @@ public class ParsedTemplateMap implements LongFunction<Map<String, ?>>, StaticFi
|
||||
public <V> Optional<LongFunction<V>> getAsOptionalFunction(String name, Class<? extends V> type) {
|
||||
if (isStatic(name)) {
|
||||
V value = getStaticValue(name);
|
||||
return Optional.of((cycle) -> value);
|
||||
} else if (isDefinedDynamic(name)) {
|
||||
if (type.isAssignableFrom(value.getClass())) {
|
||||
return Optional.of((cycle) -> value);
|
||||
} else if (NBTypeConverter.canConvert(value, type)) {
|
||||
V converted = NBTypeConverter.convert(value, type);
|
||||
return Optional.of((cycle) -> converted);
|
||||
} else {
|
||||
throw new OpConfigError(
|
||||
"function for '" + name + "' yielded a " + value.getClass().getCanonicalName()
|
||||
+ " type, which is not assignable to a " + type.getCanonicalName() + "'"
|
||||
);
|
||||
}
|
||||
} else if (isDynamic(name)) {
|
||||
Object testValue = dynamics.get(name).apply(0L);
|
||||
if (type.isAssignableFrom(testValue.getClass())) {
|
||||
return Optional.of((LongFunction<V>) dynamics.get(name));
|
||||
} else if (NBTypeConverter.canConvert(testValue, type)) {
|
||||
return Optional.of(l -> NBTypeConverter.convert(dynamics.get(name).apply(l), type));
|
||||
} else {
|
||||
throw new OpConfigError(
|
||||
"function for '" + name + "' yielded a " + testValue.getClass().getCanonicalName()
|
||||
@@ -393,8 +410,8 @@ public class ParsedTemplateMap implements LongFunction<Map<String, ?>>, StaticFi
|
||||
}
|
||||
}
|
||||
|
||||
public <V> LongFunction<? extends V> getAsRequiredFunction(String name, Class<? extends V> type) {
|
||||
Optional<? extends LongFunction<? extends V>> sf = getAsOptionalFunction(name, type);
|
||||
public <V> LongFunction<V> getAsRequiredFunction(String name, Class<? extends V> type) {
|
||||
Optional<? extends LongFunction<V>> sf = getAsOptionalFunction(name, type);
|
||||
return sf.orElseThrow(() -> new OpConfigError("The op field '" + name + "' is required, but it wasn't found in the op template."));
|
||||
}
|
||||
|
||||
@@ -439,7 +456,7 @@ public class ParsedTemplateMap implements LongFunction<Map<String, ?>>, StaticFi
|
||||
} else {
|
||||
throw new OpConfigError("Unable to compose string to object cache with non-String value of type " + defaultValue.getClass().getCanonicalName());
|
||||
}
|
||||
} else if (isDefinedDynamic(fieldname)) {
|
||||
} else if (isDynamic(fieldname)) {
|
||||
LongFunction<V> f = l -> get(fieldname, l);
|
||||
V testValue = f.apply(0);
|
||||
if (testValue instanceof String) {
|
||||
@@ -608,6 +625,55 @@ public class ParsedTemplateMap implements LongFunction<Map<String, ?>>, StaticFi
|
||||
|
||||
}
|
||||
|
||||
|
||||
public <E extends Enum<E>, V> Optional<TypeAndTarget<E, V>> getOptionalTargetEnum(
|
||||
Class<E> enumclass,
|
||||
String typeFieldName,
|
||||
String valueFieldName,
|
||||
Class<V> valueClass
|
||||
) {
|
||||
if (isStatic(typeFieldName)) {
|
||||
String enumValue = statics.get(typeFieldName).toString();
|
||||
E verifiedEnumValue;
|
||||
|
||||
try {
|
||||
verifiedEnumValue = Enum.valueOf(enumclass, enumValue);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new OpConfigError("type designator field '" + typeFieldName + "' had value of '" + enumValue + ", but this failed to match " +
|
||||
"any of known types in " + EnumSet.allOf(enumclass));
|
||||
}
|
||||
|
||||
if (isDefined(valueFieldName)) {
|
||||
if (isStatic(typeFieldName)) {
|
||||
return Optional.of(
|
||||
new TypeAndTarget<E, V>(verifiedEnumValue, typeFieldName, l -> NBTypeConverter.convert(statics.get(valueFieldName), valueClass))
|
||||
);
|
||||
} else if (isDynamic(valueFieldName)) {
|
||||
return Optional.of(
|
||||
new TypeAndTarget<E, V>(verifiedEnumValue, typeFieldName, getAsRequiredFunction(valueFieldName, valueClass))
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (isDynamic(typeFieldName)) {
|
||||
throw new OpConfigError("The op template field '" + typeFieldName + "' must be a static value. You can not vary it by cycle.");
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public <E extends Enum<E>, V> Optional<TypeAndTarget<E, V>> getOptionalTargetEnum(
|
||||
Class<E> enumclass,
|
||||
Class<V> valueClass,
|
||||
String alternateTypeField,
|
||||
String alternateValueField
|
||||
) {
|
||||
Optional<TypeAndTarget<E, V>> result = getOptionalTargetEnum(enumclass, valueClass);
|
||||
if (result.isPresent()) {
|
||||
return result;
|
||||
}
|
||||
return getOptionalTargetEnum(enumclass,alternateTypeField,alternateValueField,valueClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an enum of any type, return the enum value which is found in any of the field names of the op template,
|
||||
* ignoring case and any non-word characters. This is useful for matching op templates to op types where the presence
|
||||
@@ -619,37 +685,58 @@ public class ParsedTemplateMap implements LongFunction<Map<String, ?>>, StaticFi
|
||||
* @return Optionally, an enum value which matches, or {@link Optional#empty()}
|
||||
* @throws OpConfigError if more than one field matches
|
||||
*/
|
||||
public <E extends Enum<E>> Optional<NamedTarget<E>> getOptionalTypeFromEnum(Class<E> enumclass) {
|
||||
List<NamedTarget<E>> matched = new ArrayList<>();
|
||||
public <E extends Enum<E>, V> Optional<TypeAndTarget<E, V>> getOptionalTargetEnum(
|
||||
Class<E> enumclass,
|
||||
Class<? extends V> valueClass
|
||||
) {
|
||||
List<TypeAndTarget<E, V>> matched = new ArrayList<>();
|
||||
for (E e : EnumSet.allOf(enumclass)) {
|
||||
String lowerenum = e.name().toLowerCase(Locale.ROOT).replaceAll("[^\\w]", "");
|
||||
for (String s : statics.keySet()) {
|
||||
String lowerkey = s.toLowerCase(Locale.ROOT).replaceAll("[^\\w]", "");
|
||||
if (lowerkey.equals(lowerenum)) {
|
||||
matched.add(new NamedTarget<>(e, s, null));
|
||||
matched.add(new TypeAndTarget<>(e, s, null));
|
||||
}
|
||||
}
|
||||
for (String s : dynamics.keySet()) {
|
||||
String lowerkey = s.toLowerCase(Locale.ROOT).replaceAll("[^\\w]", "");
|
||||
if (lowerkey.equals(lowerenum)) {
|
||||
matched.add(new NamedTarget<>(e, s, null));
|
||||
matched.add(new TypeAndTarget<>(e, s, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (matched.size() == 1) {
|
||||
NamedTarget<E> prototype = matched.get(0);
|
||||
LongFunction<? extends String> asFunction = getAsRequiredFunction(prototype.field);
|
||||
return Optional.of(new NamedTarget<>(prototype.enumId, prototype.field, asFunction));
|
||||
}
|
||||
if (matched.size() > 1) {
|
||||
TypeAndTarget<E, V> prototype = matched.get(0);
|
||||
LongFunction<V> asFunction = getAsRequiredFunction(prototype.field, valueClass);
|
||||
return Optional.of(new TypeAndTarget<E, V>(prototype.enumId, prototype.field, asFunction));
|
||||
} else if (matched.size() > 1) {
|
||||
throw new OpConfigError("Multiple matches were found from op template fieldnames ["
|
||||
+ getDefinedNames() + "] to possible enums: [" + EnumSet.allOf(enumclass) + "]");
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public <E extends Enum<E>> NamedTarget<E> getRequiredTypeFromEnum(Class<E> enumclass) {
|
||||
Optional<NamedTarget<E>> typeFromEnum = getOptionalTypeFromEnum(enumclass);
|
||||
public <E extends Enum<E>,V> TypeAndTarget<E,V> getTargetEnum(
|
||||
Class<E> enumclass,
|
||||
Class<V> valueClass,
|
||||
String tname,
|
||||
String vname
|
||||
) {
|
||||
Optional<TypeAndTarget<E, V>> optionalMappedEnum = getOptionalTargetEnum(enumclass, valueClass);
|
||||
if (optionalMappedEnum.isPresent()) {
|
||||
return optionalMappedEnum.get();
|
||||
}
|
||||
Optional<TypeAndTarget<E, V>> optionalSpecifiedEnum = getOptionalTargetEnum(enumclass, tname, vname, valueClass);
|
||||
if (optionalSpecifiedEnum.isPresent()) {
|
||||
return optionalSpecifiedEnum.get();
|
||||
}
|
||||
throw new OpConfigError("Unable to map the type and target for possible values " + EnumSet.allOf(enumclass) +" either by key or by fields " + tname + " and " + vname + ". " +
|
||||
"Fields considered: static:" + statics.keySet() + " dynamic:" + dynamics.keySet());
|
||||
}
|
||||
|
||||
public <E extends Enum<E>, V> TypeAndTarget<E, V> getTargetEnum(Class<E> enumclass, Class<V> valueClass) {
|
||||
Optional<TypeAndTarget<E, V>> typeFromEnum = getOptionalTargetEnum(enumclass, valueClass);
|
||||
|
||||
return typeFromEnum.orElseThrow(
|
||||
() -> {
|
||||
@@ -666,12 +753,12 @@ public class ParsedTemplateMap implements LongFunction<Map<String, ?>>, StaticFi
|
||||
/**
|
||||
* Map a named op field to an enum
|
||||
*
|
||||
* @param enumclass The type of enum to look within
|
||||
* @param fieldname The field name to look for
|
||||
* @param <E> The generic type of the enum
|
||||
* @param enumclass The type of enum to look within
|
||||
* @param fieldname The field name to look for
|
||||
* @param <E> The generic type of the enum
|
||||
* @return An optional enum value
|
||||
*/
|
||||
public <E extends Enum<E>> Optional<E> getOptionalEnumFromField(Class<E> enumclass,String fieldname) {
|
||||
public <E extends Enum<E>> Optional<E> getOptionalEnumFromField(Class<E> enumclass, String fieldname) {
|
||||
|
||||
Optional<String> enumField = getOptionalStaticConfig(fieldname, String.class);
|
||||
if (enumField.isEmpty()) {
|
||||
@@ -696,4 +783,5 @@ public class ParsedTemplateMap implements LongFunction<Map<String, ?>>, StaticFi
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package io.nosqlbench.engine.api.templating;
|
||||
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
* <p>A convenient pattern for users to specify a command is that of <em>type and target</em>. This
|
||||
* emphasizes that users often need to specify what kind of action to take, and what subject to
|
||||
* take the action on. This concept pervades programming, exmplified by a simple <pre>{@code function(object) } call</pre>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* To facilitate this pattern in op templates with the help of type safety, this helper type allows
|
||||
* for the scanning of a map for a matching enum field. If any map key matches one of the possible
|
||||
* enum values, case-insensitively, and with only a single match, then the matching enum value is
|
||||
* taken as a specific type of action, and the matching value in the map is taken as the intended target.
|
||||
*</p>
|
||||
*
|
||||
* <p>Further, the target may be indirect, such as a binding, rather than a specific literal or structured
|
||||
* value. In such cases, only a lambda-style function may be available. The provided <em>targetFunction</em> is
|
||||
* a {@link LongFunction} of Object which can be called to return an associated target value once the
|
||||
* cycle value is known.</p>
|
||||
*
|
||||
* For example, with an enum like <pre>{@code
|
||||
* public enum LandMovers {
|
||||
* BullDozer,
|
||||
* DumpTruck
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* and a parsed op like <pre>{@code
|
||||
* (json)
|
||||
* {
|
||||
* "op": {
|
||||
* "bulldozer": "{dozerid}"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* (yaml)
|
||||
* op:
|
||||
* bulldozer: "{dozerid}
|
||||
* }</pre>
|
||||
* the result will be returned with the following: <pre>{@code
|
||||
* enumId: (Enum field) BullDozer
|
||||
* field: (String) bulldozer
|
||||
* targetFunction: (long l) -> template function for {dozerid}
|
||||
* }</pre>
|
||||
* @param <E>
|
||||
*/
|
||||
public class TypeAndTarget<E extends Enum<E>,T> {
|
||||
public final E enumId;
|
||||
public final String field;
|
||||
public final LongFunction<T> targetFunction;
|
||||
|
||||
public TypeAndTarget(E enumId, String matchingOpFieldName, LongFunction<T> value) {
|
||||
this.enumId = enumId;
|
||||
this.field = matchingOpFieldName;
|
||||
this.targetFunction = value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user