diff --git a/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/NamedTarget.java b/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/NamedTarget.java index d3d35a2ea..195e5eed5 100644 --- a/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/NamedTarget.java +++ b/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/NamedTarget.java @@ -2,6 +2,40 @@ package io.nosqlbench.engine.api.templating; import java.util.function.LongFunction; +/** + *

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.

+ * + *

The enumId is type-safe enum value from the provided enum to the above method. + * The field is the field name which was passed. The targetFunction is + * a {@link LongFunction} of Object which can be called to return an associated target value.

+ * + * For example, with an emum like
{@code
+ * public enum LandMovers {
+ *     BullDozer,
+ *     DumpTruck
+ * }
+ * }
+ * + * and a parsed op like
{@code
+ * (json)
+ * {
+ *  "op": {
+ *   "bulldozer": "{dozerid}"
+ *   }
+ * }
+ *
+ * (yaml)
+ * op:
+ *  bulldozer: "{dozerid}
+ * }
+ * the result will be returned with the following:
{@code
+ *  enumId: BullDozer
+ *  field: bulldozer
+ *  targetFunction: (long l) -> ...
+ * }
+ * @param + */ public class NamedTarget> { public final E enumId; public final String field; diff --git a/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/ParsedOp.java b/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/ParsedOp.java index 60a4568a6..7a3715a1a 100644 --- a/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/ParsedOp.java +++ b/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/ParsedOp.java @@ -67,7 +67,8 @@ public class ParsedOp implements LongFunction>, StaticFieldReader this._opTemplate = opTemplate; this.activityCfg = activityCfg; - Map map = opTemplate.getOp().orElseThrow(); + Map map = opTemplate.getOp().orElseThrow(() -> + new OpConfigError("ParsedOp constructor requires a non-null value for the op field, but it was missing.")); for (Function, Map> preprocessor : preprocessors) { map = preprocessor.apply(map); } @@ -259,6 +260,10 @@ public class ParsedOp implements LongFunction>, StaticFieldReader return tmap.getAsOptionalFunction(name, type); } + public Optional> getAsOptionalFunction(String name) { + return this.getAsOptionalFunction(name, String.class); + } + public LongFunction getAsRequiredFunction(String name, Class type) { return tmap.getAsRequiredFunction(name, type); } @@ -411,11 +416,18 @@ public class ParsedOp implements LongFunction>, StaticFieldReader * @throws OpConfigError if more than one field matches */ public > Optional> getTypeFromEnum(Class enumclass) { - return tmap.getTypeFromEnum(enumclass); + return tmap.getOptionalTypeFromEnum(enumclass); } public > NamedTarget getRequiredTypeFromEnum(Class enumclass) { return tmap.getRequiredTypeFromEnum(enumclass); } + public > Optional getOptionalEnumFromField(Class enumclass, String fieldName) { + return tmap.getOptionalEnumFromField(enumclass,fieldName); + } + + public > E getEnumFromFieldOr(Class enumClass, E defaultEnum, String fieldName) { + return getOptionalEnumFromField(enumClass,fieldName).orElse(defaultEnum); + } } diff --git a/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/ParsedTemplateMap.java b/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/ParsedTemplateMap.java index c44ee5eb5..ee8514e59 100644 --- a/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/ParsedTemplateMap.java +++ b/adapters-api/src/main/java/io/nosqlbench/engine/api/templating/ParsedTemplateMap.java @@ -28,10 +28,10 @@ import java.util.function.LongFunction; * * The provided map is taken as a map of string to object templates using these rules: *
    - *
  1. If the value is a String and contains no binding points, it is taken as a literal
  2. - *
  3. If the value is a String and contains only a binding point with no leading nor trailing text, it is taken as an object binding
  4. - *
  5. If the value is a String and contains a binding point with any leading or trailing text, it is taken as a String template binding
  6. - *
  7. If the value is a map, list, or set, then each element is interpreted as above
  8. + *
  9. If the value is a String and contains no binding points, it is taken as a literal
  10. + *
  11. If the value is a String and contains only a binding point with no leading nor trailing text, it is taken as an object binding
  12. + *
  13. If the value is a String and contains a binding point with any leading or trailing text, it is taken as a String template binding
  14. + *
  15. If the value is a map, list, or set, then each element is interpreted as above
  16. *
*/ public class ParsedTemplateMap implements LongFunction>, StaticFieldReader, DynamicFieldReader { @@ -65,7 +65,7 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi private final LinkedHashMap protomap = new LinkedHashMap<>(); private final List> cfgsources; - public ParsedTemplateMap(Map map, Map bindings, List> cfgsources) { + public ParsedTemplateMap(Map map, Map bindings, List> cfgsources) { this.cfgsources = cfgsources; applyTemplateFields(map, bindings); mapsize = statics.size() + dynamics.size(); @@ -86,7 +86,7 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi break; case bindref: String spec = pt.asBinding().orElseThrow().getBindspec(); - if (spec==null) { + if (spec == null) { throw new OpConfigError("Empty binding spec for '" + k + "'"); } Optional> mapper = VirtData.getOptionalMapper(spec); @@ -100,19 +100,19 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi break; } } else if (v instanceof Map) { - ((Map)v).keySet().forEach(smk -> { + ((Map) v).keySet().forEach(smk -> { if (!CharSequence.class.isAssignableFrom(smk.getClass())) { throw new OpConfigError("Only string keys are allowed in submaps."); } }); - Map submap = (Map) v; - ParsedTemplateMap subtpl = new ParsedTemplateMap(submap,bindings,cfgsources); + Map submap = (Map) v; + ParsedTemplateMap subtpl = new ParsedTemplateMap(submap, bindings, cfgsources); if (subtpl.isStatic()) { statics.put(k, submap); protomap.put(k, submap); } else { dynamics.put(k, subtpl); - protomap.put(k,null); + protomap.put(k, null); } } else { @@ -130,10 +130,11 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi * @return true if any field of this template map is dynamic */ public boolean isDynamic() { - return (dynamics.size()>0); + return (dynamics.size() > 0); } + public boolean isStatic() { - return (dynamics.size()==0); + return (dynamics.size() == 0); } public Map getStaticPrototype() { @@ -147,9 +148,7 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi @Override public Map apply(long value) { LinkedHashMap map = new LinkedHashMap<>(protomap); - dynamics.forEach((k, v) -> { - map.put(k, v.apply(value)); - }); + dynamics.forEach((k, v) -> map.put(k, v.apply(value))); return map; } @@ -207,6 +206,7 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi * @param The parameter type of the return type. used at compile time only to quality return type. * @return A value of type T, or null */ + @SuppressWarnings("unchecked") @Override public T getStaticValue(String field) { return (T) statics.get(field); @@ -222,6 +222,7 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi * @return The value * @throws RuntimeException if the field name is only present in the dynamic fields. */ + @SuppressWarnings("unchecked") @Override public T getStaticValueOr(String name, T defaultValue) { if (statics.containsKey(name)) { @@ -245,7 +246,7 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi * @param The type of the value to return * @return A configuration value * @throws NBConfigError if the named field is defined dynamically, - * as in this case, it is presumed that the parameter is not supported unless it is defined statically. + * as in this case, it is presumed that the parameter is not supported unless it is defined statically. */ public T getStaticConfigOr(String name, T defaultValue) { if (statics.containsKey(name)) { @@ -253,11 +254,11 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi } for (Map cfgsource : cfgsources) { if (cfgsource.containsKey(name)) { - return NBTypeConverter.convertOr(cfgsource.get(name),defaultValue); + return NBTypeConverter.convertOr(cfgsource.get(name), defaultValue); } } if (dynamics.containsKey(name)) { - throw new NBConfigError("static config field '" + name + "' was defined dynamically. This may be supportable if the driver developer" + + throw new OpConfigError("static config field '" + name + "' was defined dynamically. This may be supportable if the driver developer" + "updates the op mapper to support this field as a dynamic field, but it is not yet supported."); } else { return defaultValue; @@ -274,7 +275,7 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi } } if (dynamics.containsKey("name")) { - throw new NBConfigError("static config field '" + name + "' was defined dynamically. This may be supportable if the driver developer" + + throw new OpConfigError("static config field '" + name + "' was defined dynamically. This may be supportable if the driver developer" + "updates the op mapper to support this field as a dynamic field, but it is not yet supported."); } else { return Optional.empty(); @@ -328,6 +329,7 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi * @param The parameter type of the returned value. Inferred from usage context. * @return The value. */ + @SuppressWarnings("unchecked") @Override public T get(String field, long input) { if (statics.containsKey(field)) { @@ -366,6 +368,7 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi * @param type The value type which the field must be assignable to * @return A function which can provide a value for the given name and type */ + @SuppressWarnings("unchecked") public Optional> getAsOptionalFunction(String name, Class type) { if (isStatic(name)) { V value = getStaticValue(name); @@ -381,8 +384,6 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi } } else { return Optional.empty(); -// throw new OpConfigError("No op field named '" + name + "' was found. If this field has a reasonable" + -// " default value, consider using getAsFunctionOr(...) and documenting the default."); } } @@ -392,7 +393,6 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi } - /** * Get a LongFunction which returns either the static value, the dynamic value, or the default value, * in that order, depending on where it is found first. @@ -415,7 +415,7 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi } /** - * Get a LongFunction that first creates a LongFunction of String as in {@link #getAsFunction(String, Class)}, but then + * Get a LongFunction that first creates a LongFunction of String as in {@link #getAsFunctionOr(String, Object)} )}, but then * applies the result and cached it for subsequent access. This relies on {@link ObjectCache} internally. * * @param fieldname The name of the field which could contain a static or dynamic value @@ -510,25 +510,6 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi return true; } -// /** -// * @param fields field names which must be defined as static -// * @throws {@link OpConfigError} if any specified field is not defined static. All fields are checked and one exception is thrown for all of them together. -// */ -// @Override -// public void assertDefinedStatic(String... fields) { -// for (String field : fields) { -// if (!statics.containsKey(field)) { -// Set missing = new HashSet<>(); -// for (String readoutfield : fields) { -// if (!statics.containsKey(readoutfield)) { -// missing.add(readoutfield); -// } -// } -// throw new OpConfigError("Fields " + missing + " are required to be defined with static values for this type of operation."); -// } -// } -// } - /** * @param fields The ordered field names for which the {@link ListBinder} will be created * @return a new {@link ListBinder} which can produce a {@link List} of Objects from a long input. @@ -620,27 +601,27 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi * @return Optionally, an enum value which matches, or {@link Optional#empty()} * @throws OpConfigError if more than one field matches */ - public > Optional> getTypeFromEnum(Class enumclass) { + public > Optional> getOptionalTypeFromEnum(Class enumclass) { List> 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 NamedTarget<>(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 NamedTarget<>(e, s, null)); } } } if (matched.size() == 1) { NamedTarget prototype = matched.get(0); LongFunction asFunction = getAsRequiredFunction(prototype.field); - return Optional.of(new NamedTarget(prototype.enumId, prototype.field, asFunction)); + return Optional.of(new NamedTarget<>(prototype.enumId, prototype.field, asFunction)); } if (matched.size() > 1) { throw new OpConfigError("Multiple matches were found from op template fieldnames [" @@ -650,14 +631,49 @@ public class ParsedTemplateMap implements LongFunction>, StaticFi } public > NamedTarget getRequiredTypeFromEnum(Class enumclass) { - Optional> typeFromEnum = getTypeFromEnum(enumclass); - String values = EnumSet.allOf(enumclass).toString(); - Set definedNames = getDefinedNames(); + Optional> typeFromEnum = getOptionalTypeFromEnum(enumclass); + return typeFromEnum.orElseThrow( - () -> new OpConfigError("Unable to match op template fields [" + definedNames + "] with " + - "possible op types [" + values + "]") + () -> { + String values = EnumSet.allOf(enumclass).toString(); + Set definedNames = getDefinedNames(); + return new OpConfigError("Unable to match op template fields [" + definedNames + "] with " + + "possible op types [" + values + "]"); + } ); } + /** + * 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 The generic type of the enum + * @return An optional enum value + */ + public > Optional getOptionalEnumFromField(Class enumclass,String fieldname) { + + Optional enumField = getOptionalStaticConfig(fieldname, String.class); + if (enumField.isEmpty()) { + return Optional.empty(); + } + String lowerv = enumField.get().toLowerCase(Locale.ROOT).replaceAll("[^\\w]", ""); + + List matched = new ArrayList<>(); + for (E e : EnumSet.allOf(enumclass)) { + String lowerenum = e.name().toLowerCase(Locale.ROOT).replaceAll("[^\\w]", ""); + if (lowerv.equals(lowerenum)) { + matched.add(e); + } + } + if (matched.size() == 1) { + return Optional.of(matched.get(0)); + } + 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(); + } }