AggregatePersistenceResolver.java

package io.vanillabp.integration.deployment.processservice;

import java.util.HashSet;
import java.util.Set;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.Type;
import org.jboss.jandex.WildcardType;

import io.vanillabp.integration.spi.AggregatePersistenceAware;

/**
 * A utility class used to figure out the right aggregate persistence for a given aggregate type.
 */
public final class AggregatePersistenceResolver {

  private static final DotName APA = DotName.createSimple(AggregatePersistenceAware.class);

  private AggregatePersistenceResolver() {
  }

  /**
   * Computes a specificity distance for an implementation of
   * {@link AggregatePersistenceAware} with respect to the given aggregate type.
   * Smaller values are more specific.
   *
   * @param index The index to use for looking up classes
   * @param implClass The class implementing {@link AggregatePersistenceAware}
   * @param aggregateType The given aggregate type
   * @return Distance of aggregateType to generic argument "implClass"
   */
  public static int distance(
      IndexView index,
      ClassInfo implClass,
      DotName aggregateType) {

    int rawDistance = rawInterfaceDistance(index, implClass, APA, new HashSet<>());
    if (rawDistance == Integer.MAX_VALUE) {
      return Integer.MAX_VALUE;
    }

    int genericDistance = genericArgumentDistance(index, implClass, aggregateType);

    // Raw type distance dominates generic distance
    return rawDistance * 1000 + genericDistance;
  }

  /* ---------------------------------------------------------------------- */
  /* Raw interface distance                                                  */
  /* ---------------------------------------------------------------------- */

  private static int rawInterfaceDistance(
      IndexView index,
      ClassInfo current,
      DotName targetInterface,
      Set<DotName> visited) {

    if (!visited.add(current.name())) {
      return Integer.MAX_VALUE;
    }

    int best = Integer.MAX_VALUE;

    for (Type iface : current.interfaceTypes()) {
      if (iface.name().equals(targetInterface)) {
        return 0;
      }

      ClassInfo ifaceInfo = index.getClassByName(iface.name());
      if (ifaceInfo != null) {
        best = Math.min(
            best,
            1 + rawInterfaceDistance(index, ifaceInfo, targetInterface, visited));
      }
    }

    Type superType = current.superClassType();
    if (superType != null) {
      ClassInfo superInfo = index.getClassByName(superType.name());
      if (superInfo != null) {
        best = Math.min(
            best,
            1 + rawInterfaceDistance(index, superInfo, targetInterface, visited));
      }
    }

    return best;
  }

  /* ---------------------------------------------------------------------- */
  /* Generic argument distance                                               */
  /* ---------------------------------------------------------------------- */

  private static int genericArgumentDistance(
      IndexView index,
      ClassInfo implClass,
      DotName aggregateType) {

    for (Type iface : implClass.interfaceTypes()) {

      if (!iface.name().equals(APA)) {
        continue;
      }

      // Raw type: AggregatePersistenceAware
      if (iface.kind() != Type.Kind.PARAMETERIZED_TYPE) {
        return 1000;
      }

      Type arg = iface.asParameterizedType().arguments().getFirst();
      return argumentDistance(index, arg, aggregateType);
    }

    // recurse into superclass
    Type superType = implClass.superClassType();
    if (superType != null) {
      ClassInfo superInfo = index.getClassByName(superType.name());
      if (superInfo != null) {
        return genericArgumentDistance(index, superInfo, aggregateType);
      }
    }

    return Integer.MAX_VALUE;
  }

  private static int argumentDistance(
      IndexView index,
      Type arg,
      DotName aggregateType) {

    switch (arg.kind()) {

      case CLASS:
        if (arg.name().equals(aggregateType)) {
          return 0;
        }
        return Math.min(
            inheritanceDistance(
                index,
                aggregateType,
                arg.name()),
            inheritanceDistance(
                index,
                arg.name(),
                aggregateType));

      case WILDCARD_TYPE:
        WildcardType wc = arg.asWildcardType();
        if (wc.extendsBound() != null) {
          return 100 + argumentDistance(
              index,
              wc.extendsBound(),
              aggregateType);
        }
        return 1000;

      case TYPE_VARIABLE:
        return 500;

      default:
        return Integer.MAX_VALUE;
    }
  }

  /* ---------------------------------------------------------------------- */
  /* Inheritance distance for classes/interfaces                             */
  /* ---------------------------------------------------------------------- */

  /**
   * The distance of inheritance between two classes.
   *
   * @param index The index to use for looking up classes
   * @param base The less specific class (base class or interface)
   * @param current The more specific class (subclass)
   * @return Number of steps of inheritance between base and current.
   */
  public static int inheritanceDistance(
      IndexView index,
      DotName base,
      DotName current) {

    return inheritanceDistance(index, base, current, new HashSet<>());
  }

  private static int inheritanceDistance(
      IndexView index,
      DotName base,
      DotName current,
      Set<DotName> visited) {

    if (base.equals(current)) {
      return 0;
    }

    if (!visited.add(current)) {
      return Integer.MAX_VALUE;
    }

    ClassInfo info = index.getClassByName(current);
    if (info == null) {
      return Integer.MAX_VALUE;
    }

    int best = Integer.MAX_VALUE;

    for (Type iface : info.interfaceTypes()) {
      int interfaceTypeDistance = inheritanceDistance(
          index, base, iface.name(), visited);
      best = Math.min(
          best,
          interfaceTypeDistance == Integer.MAX_VALUE ? interfaceTypeDistance : 1 + interfaceTypeDistance);
    }

    Type superType = info.superClassType();
    if (superType != null) {
      int superTypeDistance = inheritanceDistance(
          index, base, superType.name(), visited);
      best = Math.min(
          best,
          superTypeDistance == Integer.MAX_VALUE ? superTypeDistance : 1 + superTypeDistance);
    }

    return best;
  }

}