All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jp.co.moneyforward.autotest.framework.action.ResolverBundle Maven / Gradle / Ivy

The newest version!
package jp.co.moneyforward.autotest.framework.action;

import com.github.dakusui.actionunit.core.Context;
import jp.co.moneyforward.autotest.framework.annotations.*;
import jp.co.moneyforward.autotest.framework.internal.InternalUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;

import static java.util.Collections.emptyList;
import static jp.co.moneyforward.autotest.framework.internal.InternalUtils.findMethodByName;

/// A bundle of variable resolvers.
///
/// A variable resolver figures out a value of a variable specified by a variable name.
/// This class bundles a set of such resolvers and associated each resolver with a variable name which the resolver
/// should figure out the value.
public class ResolverBundle extends HashMap> {
  /// Creates an instance of this class.
  ///
  /// @param resolvers A map of resolvers from variable names to resolvers.
  public ResolverBundle(Map> resolvers) {
    super(resolvers);
  }
  
  /// Creates an instance of this class.
  ///
  /// @param resolvers A list of resolvers from which an object of this class will be created.
  public ResolverBundle(List resolvers) {
    this(resolversToMap(resolvers));
  }
  
  /// Returns a resolver bundle object which figures out both input and output variable values for a given `Scene`.
  /// The variables are looked up from a variable store specified by the argument `variableStoreName`.
  ///
  /// For each input or output variable, a resolver will be created, and it will be an element of the returned bundle.
  ///
  /// @param scene             A scene for which resolver bundle is created.
  /// @param variableStoreName A name of variable store, where variable values are looked up.
  /// @return A new resolver bundle.
  public static ResolverBundle resolverBundleFor(Scene scene, String variableStoreName) {
    return new ResolverBundle(createVariableResolversFor(scene, variableStoreName));
  }
  
  /// Returns a resolver bundle which is necessary for creating a new `SceneCall` call from a `Scene` defined by
  /// `targetMethod`.
  ///
  /// @param targetMethod     A method which creates a `Scene`.
  /// @param accessModelClass A class to which `targetMethod` belongs and from which dependencies of `target` method are searched.
  /// @return A resolver bundle for a `Scene` created by `targetMethod`.
  public static ResolverBundle resolverBundleFromDependenciesOf(Method targetMethod, Class accessModelClass) {
    //assert Scene.class.isAssignableFrom(targetMethod.getReturnType());
    return new ResolverBundle(resolversFromDependenciesOf(targetMethod, accessModelClass));
  }
  
  /// Returns a variable resolvers of a given `scene` based on its children's input and output variable names.
  /// The returned variable resolvers figure out the value of a given variable name from a Map context variable.
  /// The context variable is specified by a `variableStoreName`.
  ///
  /// `variableStoreName` is a name of a context variable that stores a map. From the map,
  /// the returned resolvers figure out the values of given variable names, which are used by the scene.
  ///
  /// This is "de facto"-based method to create variable resolvers, so to say.
  ///
  /// @param scene             A scene for which variable resolvers are created and returned.
  /// @param variableStoreName A name of a context variable that stores a map.
  /// @return A list of variable resolvers.
  private static List createVariableResolversFor(Scene scene, String variableStoreName) {
    return Resolver.resolversFor(Stream.concat(scene.inputVariableNames().stream(),
                                               scene.outputVariableNames().stream())
                                       .distinct()
                                       .toList(), variableStoreName
    );
  }
  
  /// Creates resolvers (`Resolver`) for a scene call associated with a scene returned by `method`.
  ///
  /// Either `@DependsOn` or `@When` annotations attached to `method` tells the framework that methods which it depends on.
  /// This method scans `@Export` attached to those methods to figure out variables available to the `method`.
  ///
  /// This is "de-juro" based method to create variable resolvers, so to say.
  ///
  /// @param method           A method that returns a `Scene` object.
  /// @param accessModelClass An access model class to which method belongs.
  /// @return A list of resolvers that a scene returned by `method` requires.
  private static List resolversFromDependenciesOf(Method method, Class accessModelClass) {
    //noinspection removal
    return InternalUtils.concat(variableResolversFor(annotationValuesOf(method, PreparedBy.class, PreparedBy::value), accessModelClass).stream(),
                                variableResolversFor(annotationValuesOf(method, Given.class, Given::value), accessModelClass).stream(),
                                variableResolversFor(annotationValuesOf(method, DependsOn.class, DependsOn::value), accessModelClass).stream(),
                                variableResolversFor(annotationValuesOf(method, When.class, When::value), accessModelClass).stream()).toList();
  }
  
  private static  String[] annotationValuesOf(Method method, Class annotationClass, Function func) {
    A[] annotationsByType = method.getAnnotationsByType(annotationClass);
    if (annotationsByType.length == 0)
      return new String[0];
    return Arrays.stream(annotationsByType).flatMap(a -> Arrays.stream(func.apply(a))).toArray(String[]::new);
  }
  
  /// Returns a list of variable resolvers (`Resolver`) for scenes specified by their names (`sceneNames).
  ///
  /// Creates variable resolvers for a scene created from a method `m`.
  ///
  /// The scene created by `m` will be called "scene `m`" in this description, hereafter.
  /// Variable resolvers created based on annotations specified by `dependencyAnnotationClass`, which is usually `@DependsOn`.
  ///
  /// @param sceneNames       Scenes for which variable resolvers are searched, created, and returned.
  /// @param accessModelClass An access model class that defines a set of scene creating methods, on which `m` potentially depends.
  /// @return Resolvers for a scene created by `m`.
  /// @see Resolver
  private static List variableResolversFor(String[] sceneNames, Class accessModelClass) {
    if (sceneNames.length == 0)
      return emptyList();
    return variableResolversFor(sceneNames,
                                dependencySceneName -> exportedVariablesOf(methodForName(dependencySceneName, accessModelClass)));
  }
  
  /// Returns `Resolver`s for variables exported by scenes specified by `sceneNames`.
  /// A resolver in the list returns a value of a variable defined in a scene that exports it with the same name.
  ///
  /// @param sceneNames                    Names of `Scene`s.
  /// @param exportedVariableNamesResolver A function that returns a list of exported variable names by a scene specified as a parameter.
  /// @return `Resolver`s for variables exported by specified scenes.
  private static List variableResolversFor(String[] sceneNames,
                                                     Function> exportedVariableNamesResolver) {
    return Arrays.stream(sceneNames)
                 .flatMap((String sceneName) -> exportedVariablesResolversOf(sceneName, exportedVariableNamesResolver).stream())
                 .toList();
  }
  
  private static List exportedVariablesResolversOf(String sceneName, Function> exportedVariablesNamesResolver) {
    return exportedVariablesNamesResolver.apply(sceneName)
                                         .stream()
                                         .map((String variableNane) -> Resolver.resolverFor(variableNane, sceneName))
                                         .toList();
  }
  
  private static List exportedVariablesOf(Method method) {
    return Stream.concat(Optional.ofNullable(method.getAnnotation(To.class))
                                 .map(To::value)
                                 .stream(),
                         Arrays.stream(Optional.ofNullable(method.getAnnotation(Export.class))
                                               .map(Export::value)
                                               .orElse(new String[]{Scene.DEFAULT_DEFAULT_VARIABLE_NAME})))
                 .toList();
  }
  
  private static Map> resolversToMap(List resolvers) {
    Map> resolverMap = new HashMap<>();
    for (Resolver resolver : resolvers) {
      resolverMap.put(resolver.variableName(), resolver.resolverFunction());
    }
    resolverMap.put("*ALL*", context -> {
      // As Collectors.toMap doesn't accept `null` value, we do normal "for" loop.
      Map ret = new LinkedHashMap<>();
      for (var r : resolvers) {
        ret.put(r.variableName(), r.resolverFunction().apply(context));
      }
      return ret;
    });
    return resolverMap;
  }
  
  private static Method methodForName(String methodName, Class classObject) {
    return findMethodByName(methodName,
                            classObject).orElseThrow(() -> messageForNoSuchMethod(classObject, methodName));
  }
  
  
  private static NoSuchElementException messageForNoSuchMethod(Class accessModelClass, String dependencySceneName) {
    return new NoSuchElementException(String.format("A method named:'%s' was not found in class:'%s'",
                                                    dependencySceneName,
                                                    accessModelClass.getCanonicalName()));
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy