@NotNull
  public Instantiator<?> findInstantiator(@NotNull Type type, @NotNull NamedTypeList types) {
    // First check if we have an immediate conversion registered. If so, we'll just use that.
    if (types.size() == 1) {
      TypeConversion conversion = findConversionFromDbValue(types.getType(0), type).orElse(null);
      if (conversion != null) return args -> conversion.convert(args.getSingleValue());
    }

    Class<?> cl = rawType(type);

    Instantiator<?> instantiator = findExplicitInstantiatorFor(cl, types).orElse(null);
    if (instantiator != null) return instantiator;

    if (!isPublic(cl.getModifiers()))
      throw new InstantiationFailureException(
          type
              + " can't be instantiated reflectively because it is not public or missing a @DalesbredConstructor-annotation");

    return candidateConstructorsSortedByDescendingParameterCount(cl)
        .map(ctor -> implicitInstantiatorFrom(ctor, types).orElse(null))
        .filter(Objects::nonNull)
        .findFirst()
        .orElseThrow(
            () ->
                new InstantiationFailureException(
                    "could not find a way to instantiate " + type + " with parameters " + types));
  }
  /**
   * Returns the list of conversions that need to be performed to convert sourceTypes to
   * targetTypes, or empty if conversions can't be done.
   */
  @NotNull
  private Optional<List<TypeConversion>> resolveConversions(
      @NotNull NamedTypeList sourceTypes, @NotNull List<Type> targetTypes) {
    if (targetTypes.size() != sourceTypes.size()) return Optional.empty();

    ArrayList<TypeConversion> conversions = new ArrayList<>(targetTypes.size());

    for (int i = 0, len = targetTypes.size(); i < len; i++) {
      TypeConversion conversion =
          findConversionFromDbValue(sourceTypes.getType(i), targetTypes.get(i)).orElse(null);
      if (conversion != null) conversions.add(conversion);
      else return Optional.empty();
    }

    return Optional.of(conversions);
  }
  /**
   * Returns an instantiator that uses given constructor and given types to create instances, or
   * empty if there are no conversions that can be made to instantiate the type.
   */
  @NotNull
  private <T> Optional<Instantiator<T>> implicitInstantiatorFrom(
      @NotNull Constructor<T> constructor, @NotNull NamedTypeList types) {
    if (!isPublic(constructor.getModifiers())) return Optional.empty();

    List<String> columnNames = types.getNames();
    return findTargetTypes(constructor, columnNames)
        .flatMap(
            targetTypes ->
                resolveConversions(types, targetTypes)
                    .map(
                        conversions ->
                            new ReflectionInstantiator<>(
                                constructor,
                                conversions,
                                createPropertyAccessorsForValuesNotCoveredByConstructor(
                                    constructor, columnNames))));
  }
  @NotNull
  private Optional<Instantiator<?>> findExplicitInstantiatorFor(
      Class<?> cl, @NotNull NamedTypeList types) throws InstantiationFailureException {
    Constructor<?> constructor = dalesbredConstructor(cl).orElse(null);

    if (constructor == null) return Optional.empty();

    try {
      ReflectionUtils.makeAccessible(constructor);
    } catch (SecurityException e) {
      throw new InstantiationFailureException(
          "Cannot instantiate "
              + constructor.getDeclaringClass().getName()
              + " using non-public constructor due to Security exception",
          e);
    }

    List<String> columnNames = types.getNames();
    List<Type> constructorParameterTypes = asList(constructor.getGenericParameterTypes());

    if (constructorParameterTypes.size() != columnNames.size())
      throw new InstantiationFailureException(
          String.format(
              "Cannot instantiate %s, constructor takes %d arguments, but result set has %d",
              constructor.getDeclaringClass().getName(),
              constructorParameterTypes.size(),
              columnNames.size()));

    ReflectionInstantiator<?> instantiator =
        resolveConversions(types, constructorParameterTypes)
            .map(
                conversions ->
                    new ReflectionInstantiator<>(constructor, conversions, Collections.emptyList()))
            .orElseThrow(
                () ->
                    new InstantiationFailureException(
                        "could not find a way to instantiate "
                            + constructor.getDeclaringClass().getName()
                            + " with parameters "
                            + types));

    return Optional.of(instantiator);
  }