Skip to content

Commit

Permalink
#1306 replace anonymous lambdas (Supplier) by named classes (ValueRes…
Browse files Browse the repository at this point in the history
…olver)

it allows to log what objects were returned when debugging flaky test failures.
  • Loading branch information
asolntsev committed Jul 21, 2024
1 parent 1aebeec commit a0c66fd
Showing 1 changed file with 91 additions and 48 deletions.
139 changes: 91 additions & 48 deletions src/main/java/net/datafaker/service/FakeValuesService.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public class FakeValuesService {

private static final Map<String, String[]> EXPRESSION_2_SPLITTED = new CopyOnWriteMap<>(WeakHashMap::new);

private final Map<RegExpContext, Supplier<?>> REGEXP2SUPPLIER_MAP = new CopyOnWriteMap<>(HashMap::new);
private final Map<RegExpContext, ValueResolver> REGEXP2SUPPLIER_MAP = new CopyOnWriteMap<>(HashMap::new);

public FakeValuesService() {
}
Expand Down Expand Up @@ -165,6 +165,26 @@ public String fetchString(String key, FakerContext context) {
return (String) fetch(key, context);
}

private class SafeFetchResolver implements ValueResolver {
private final String simpleDirective;
private final FakerContext context;

private SafeFetchResolver(String simpleDirective, FakerContext context) {
this.simpleDirective = simpleDirective;
this.context = context;
}

@Override
public Object resolve() {
return safeFetch(simpleDirective, context, null);
}

@Override
public String toString() {
return "%s[simpleDirective=%s, context=%s]".formatted(getClass().getSimpleName(), simpleDirective, context);
}
}

/**
* Safely fetches a key.
* <p>
Expand Down Expand Up @@ -644,10 +664,10 @@ protected String resolveExpression(String expression, Object current, ProviderRe
continue;
}
final RegExpContext regExpContext = new RegExpContext(expr, root, context);
final Supplier<?> val = REGEXP2SUPPLIER_MAP.get(regExpContext);
final ValueResolver val = REGEXP2SUPPLIER_MAP.get(regExpContext);
final Object resolved;
if (val != null) {
resolved = val.get();
resolved = val.resolve();
} else {
int j = 0;
final int length = expr.length();
Expand Down Expand Up @@ -744,21 +764,21 @@ private Object resExp(String directive, String[] args, Object current, ProviderR
Object res = resolveExpression(directive, args, current, root, context);
if (res instanceof CharSequence) {
if (((CharSequence) res).isEmpty()) {
REGEXP2SUPPLIER_MAP.put(regExpContext, () -> "");
REGEXP2SUPPLIER_MAP.put(regExpContext, EMPTY_STRING);
}
return res;
}
if (res instanceof List) {
Iterator it = ((List) res).iterator();
Iterator<ValueResolver> it = ((List) res).iterator();
while (it.hasNext()) {
Object supplier = it.next();
Object valueResolver = it.next();
Object value;
if (supplier instanceof Supplier<?>) {
value = ((Supplier<?>) supplier).get();
if (valueResolver instanceof ValueResolver resolver) {
value = resolver.resolve();
if (value == null) {
it.remove();
} else {
REGEXP2SUPPLIER_MAP.put(regExpContext, (Supplier<?>) supplier);
REGEXP2SUPPLIER_MAP.put(regExpContext, resolver);
return value;
}
}
Expand All @@ -783,22 +803,15 @@ private Object resolveExpression(String directive, String[] args, Object current
}
final int dotIndex = getDotIndex(directive);

List<Supplier<Object>> res = new ArrayList<>();
List<ValueResolver> res = new ArrayList<>();
if (args.length == 0) {
// resolve method references on CURRENT object like #{number_between '1','10'} on Number or
// #{ssn_valid} on IdNumber
if (dotIndex == -1) {
if (current instanceof AbstractProvider) {
final Method method = BaseFaker.getMethod((AbstractProvider<?>) current, directive);
if (method != null) {
res.add(() -> {
try {
return method.invoke(current);
} catch (Exception e) {
throw new RuntimeException("Failed to call method %s.%s() on %s (args: %s)".formatted(
method.getDeclaringClass().getName(), method.getName(), current, Arrays.toString(args)), e);
}
});
res.add(new MethodResolver(method, current, args));
return res;
}
}
Expand All @@ -810,14 +823,7 @@ private Object resolveExpression(String directive, String[] args, Object current
AbstractProvider<?> ap = root.getProvider(providerClassName);
Method method = ap == null ? null : ObjectMethods.getMethodByName(ap, methodName);
if (method != null) {
res.add(() -> {
try {
return method.invoke(ap);
} catch (Exception e) {
throw new RuntimeException("Failed to call method %s.%s() on %s (args: %s)".formatted(
method.getDeclaringClass().getName(), method.getName(), ap, Arrays.toString(args)), e);
}
});
res.add(new MethodResolver(method, ap, args));
return res;
}
}
Expand All @@ -830,7 +836,7 @@ private Object resolveExpression(String directive, String[] args, Object current
// car.wheel will be looked up in the YAML file.
// It's only "simple" if there aren't args
if (args.length == 0) {
res.add(() -> safeFetch(simpleDirective, context, null));
res.add(new SafeFetchResolver(simpleDirective, context));
}

// resolve method references on faker object like #{regexify '[a-z]'}
Expand All @@ -850,7 +856,7 @@ private Object resolveExpression(String directive, String[] args, Object current
// class.method_name (lowercase)
if (dotIndex >= 0) {
final String key = javaNameToYamlName(simpleDirective);
res.add(() -> safeFetch(key, context, null));
res.add(new SafeFetchResolver(key, context));
}

return res;
Expand Down Expand Up @@ -937,18 +943,18 @@ private String javaNameToYamlName(String expression) {
* {@link Name} then this method would return {@link Name#firstName()}. Returns null if the directive is nested
* (i.e. has a '.') or the method doesn't exist on the <em>obj</em> object.
*/
private Supplier<Object> resolveFromMethodOn(Object obj, String directive, String[] args) {
private ValueResolver resolveFromMethodOn(Object obj, String directive, String[] args) {
if (obj == null) {
return null;
}
try {
final MethodAndCoercedArgs accessor = retrieveMethodAccessor(obj, directive, args);
return (accessor == null)
? () -> null
: () -> invokeAndToString(accessor, obj);
? NULL_VALUE
: new MethodAndCoercedArgsResolver(accessor, obj);
} catch (Exception e) {
LOG.log(Level.FINE, "Can't call " + directive + " on " + obj, e);
return () -> null;
return NULL_VALUE;
}
}

Expand All @@ -958,7 +964,7 @@ private Supplier<Object> resolveFromMethodOn(Object obj, String directive, Strin
*
* @throws RuntimeException if there's a problem invoking the method or it doesn't exist.
*/
private Supplier<Object> resolveFakerObjectAndMethod(ProviderRegistration faker, String key, int dotIndex, String[] args) {
private ValueResolver resolveFakerObjectAndMethod(ProviderRegistration faker, String key, int dotIndex, String[] args) {
final String[] classAndMethod;
if (dotIndex == -1) {
classAndMethod = new String[]{key};
Expand All @@ -977,13 +983,12 @@ private Supplier<Object> resolveFakerObjectAndMethod(ProviderRegistration faker,
String nestedMethodName = removeUnderscoreChars(classAndMethod[1]);
final MethodAndCoercedArgs accessor = retrieveMethodAccessor(objectWithMethodToInvoke, nestedMethodName, args);
if (accessor == null) {
return () -> null;
return NULL_VALUE;
}

return () -> invokeAndToString(accessor, objectWithMethodToInvoke);
return new MethodAndCoercedArgsResolver(accessor, objectWithMethodToInvoke);
} catch (Exception e) {
LOG.fine(e.getMessage());
return () -> null;
LOG.log(Level.SEVERE, e.getMessage(), e);
return NULL_VALUE;
}
}

Expand All @@ -1010,16 +1015,6 @@ private MethodAndCoercedArgs retrieveMethodAccessor(Object object, String method
return accessor;
}

private Object invokeAndToString(MethodAndCoercedArgs accessor, Object objectWithMethodToInvoke) {
try {
return accessor.invoke(objectWithMethodToInvoke);
} catch (Exception e) {
LOG.fine(e.getMessage());
return null;
}
}


/**
* Find an accessor by name ignoring case.
*/
Expand Down Expand Up @@ -1206,4 +1201,52 @@ private Object invoke(Object on) throws InvocationTargetException, IllegalAccess

private record RegExpContext(String exp, ProviderRegistration root, FakerContext context) {
}

private interface ValueResolver {
Object resolve();
}

private record ConstantResolver(String value) implements ValueResolver {
@Override
public Object resolve() {
return value;
}
}

private static final ConstantResolver EMPTY_STRING = new ConstantResolver("");
private static final ConstantResolver NULL_VALUE = new ConstantResolver(null);

private record MethodResolver(Method method, Object current, Object[] args) implements ValueResolver {
@Override
public Object resolve() {
try {
return method.invoke(current);
} catch (Exception e) {
throw new RuntimeException("Failed to call method %s.%s() on %s (args: %s)".formatted(
method.getDeclaringClass().getName(), method.getName(), current, Arrays.toString(args)), e);
}
}

@Override
public String toString() {
return "%s[method=%s.%s(), current=%s, args=%s]".formatted(getClass().getSimpleName(),
method.getDeclaringClass().getSimpleName(), method.getName(), current, Arrays.toString(args));
}
}

private record MethodAndCoercedArgsResolver(MethodAndCoercedArgs accessor, Object obj) implements ValueResolver {
@Override
public Object resolve() {
return invokeAndToString(accessor, obj);
}

private static Object invokeAndToString(MethodAndCoercedArgs accessor, Object objectWithMethodToInvoke) {
try {
return accessor.invoke(objectWithMethodToInvoke);
} catch (Exception e) {
LOG.log(Level.FINE, e.getMessage(), e);
return null;
}
}
}
}

0 comments on commit a0c66fd

Please sign in to comment.