private void mergeInheritedOptions(
      SourceMethod method,
      MapperConfiguration mapperConfig,
      List<SourceMethod> availableMethods,
      List<SourceMethod> initializingMethods) {
    if (initializingMethods.contains(method)) {
      // cycle detected

      initializingMethods.add(method);

      messager.printMessage(
          method.getExecutable(),
          Message.INHERITCONFIGURATION_CYCLE,
          Strings.join(initializingMethods, " -> "));
      return;
    }

    initializingMethods.add(method);

    MappingOptions mappingOptions = method.getMappingOptions();
    List<SourceMethod> applicablePrototypeMethods = method.getApplicablePrototypeMethods();

    MappingOptions inverseMappingOptions =
        getInverseMappingOptions(availableMethods, method, initializingMethods, mapperConfig);

    MappingOptions templateMappingOptions =
        getTemplateMappingOptions(
            join(availableMethods, applicablePrototypeMethods),
            method,
            initializingMethods,
            mapperConfig);

    if (templateMappingOptions != null) {
      mappingOptions.applyInheritedOptions(
          templateMappingOptions, false, method, messager, typeFactory);
    } else if (inverseMappingOptions != null) {
      mappingOptions.applyInheritedOptions(
          inverseMappingOptions, true, method, messager, typeFactory);
    } else if (mapperConfig.getMappingInheritanceStrategy() == AUTO_INHERIT_FROM_CONFIG) {
      if (applicablePrototypeMethods.size() == 1) {
        mappingOptions.applyInheritedOptions(
            first(applicablePrototypeMethods).getMappingOptions(),
            false,
            method,
            messager,
            typeFactory);
      } else if (applicablePrototypeMethods.size() > 1) {
        messager.printMessage(
            method.getExecutable(),
            Message.INHERITCONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH,
            Strings.join(applicablePrototypeMethods, ", "));
      }
    }

    mappingOptions.markAsFullyInitialized();
  }
  private List<MappingMethod> getMappingMethods(
      MapperConfiguration mapperConfig, List<SourceMethod> methods) {
    List<MappingMethod> mappingMethods = new ArrayList<MappingMethod>();

    for (SourceMethod method : methods) {
      if (!method.overridesMethod()) {
        continue;
      }

      mergeInheritedOptions(method, mapperConfig, methods, new ArrayList<SourceMethod>());

      MappingOptions mappingOptions = method.getMappingOptions();

      boolean hasFactoryMethod = false;

      if (method.isIterableMapping()) {

        IterableMappingMethod.Builder builder = new IterableMappingMethod.Builder();

        String dateFormat = null;
        List<TypeMirror> qualifiers = null;
        TypeMirror qualifyingElementTargetType = null;
        NullValueMappingStrategyPrism nullValueMappingStrategy = null;

        if (mappingOptions.getIterableMapping() != null) {
          dateFormat = mappingOptions.getIterableMapping().getDateFormat();
          qualifiers = mappingOptions.getIterableMapping().getQualifiers();
          qualifyingElementTargetType =
              mappingOptions.getIterableMapping().getQualifyingElementTargetType();
          nullValueMappingStrategy =
              mappingOptions.getIterableMapping().getNullValueMappingStrategy();
        }

        IterableMappingMethod iterableMappingMethod =
            builder
                .mappingContext(mappingContext)
                .method(method)
                .dateFormat(dateFormat)
                .qualifiers(qualifiers)
                .qualifyingElementTargetType(qualifyingElementTargetType)
                .nullValueMappingStrategy(nullValueMappingStrategy)
                .build();

        hasFactoryMethod = iterableMappingMethod.getFactoryMethod() != null;
        mappingMethods.add(iterableMappingMethod);
      } else if (method.isMapMapping()) {

        MapMappingMethod.Builder builder = new MapMappingMethod.Builder();

        String keyDateFormat = null;
        String valueDateFormat = null;
        List<TypeMirror> keyQualifiers = null;
        List<TypeMirror> valueQualifiers = null;
        TypeMirror keyQualifyingTargetType = null;
        TypeMirror valueQualifyingTargetType = null;
        NullValueMappingStrategyPrism nullValueMappingStrategy = null;

        if (mappingOptions.getMapMapping() != null) {
          keyDateFormat = mappingOptions.getMapMapping().getKeyFormat();
          valueDateFormat = mappingOptions.getMapMapping().getValueFormat();
          keyQualifiers = mappingOptions.getMapMapping().getKeyQualifiers();
          valueQualifiers = mappingOptions.getMapMapping().getValueQualifiers();
          keyQualifyingTargetType = mappingOptions.getMapMapping().getKeyQualifyingTargetType();
          valueQualifyingTargetType = mappingOptions.getMapMapping().getValueQualifyingTargetType();
          nullValueMappingStrategy = mappingOptions.getMapMapping().getNullValueMappingStrategy();
        }

        MapMappingMethod mapMappingMethod =
            builder
                .mappingContext(mappingContext)
                .method(method)
                .keyDateFormat(keyDateFormat)
                .valueDateFormat(valueDateFormat)
                .keyQualifiers(keyQualifiers)
                .valueQualifiers(valueQualifiers)
                .keyQualifyingTargetType(keyQualifyingTargetType)
                .valueQualifyingTargetType(valueQualifyingTargetType)
                .nullValueMappingStrategy(nullValueMappingStrategy)
                .build();

        hasFactoryMethod = mapMappingMethod.getFactoryMethod() != null;
        mappingMethods.add(mapMappingMethod);
      } else if (method.isEnumMapping()) {

        EnumMappingMethod.Builder builder = new EnumMappingMethod.Builder();
        MappingMethod enumMappingMethod =
            builder.mappingContext(mappingContext).souceMethod(method).build();

        if (enumMappingMethod != null) {
          mappingMethods.add(enumMappingMethod);
        }
      } else {

        NullValueMappingStrategyPrism nullValueMappingStrategy = null;
        TypeMirror resultType = null;
        List<TypeMirror> qualifiers = null;

        if (mappingOptions.getBeanMapping() != null) {
          nullValueMappingStrategy = mappingOptions.getBeanMapping().getNullValueMappingStrategy();
          resultType = mappingOptions.getBeanMapping().getResultType();
          qualifiers = mappingOptions.getBeanMapping().getQualifiers();
        }
        BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder();
        BeanMappingMethod beanMappingMethod =
            builder
                .mappingContext(mappingContext)
                .souceMethod(method)
                .nullValueMappingStrategy(nullValueMappingStrategy)
                .qualifiers(qualifiers)
                .resultType(resultType)
                .build();

        if (beanMappingMethod != null) {
          hasFactoryMethod = beanMappingMethod.getFactoryMethod() != null;
          mappingMethods.add(beanMappingMethod);
        }
      }

      if (!hasFactoryMethod) {
        // A factory method  is allowed to return an interface type and hence, the generated
        // implementation as well. The check below must only be executed if there's no factory
        // method that could be responsible.
        reportErrorIfNoImplementationTypeIsRegisteredForInterfaceReturnType(method);
      }
    }
    return mappingMethods;
  }
  private List<MappingMethod> getMappingMethods(
      MapperConfiguration mapperConfig, List<SourceMethod> methods) {
    List<MappingMethod> mappingMethods = new ArrayList<MappingMethod>();

    for (SourceMethod method : methods) {
      if (!method.overridesMethod()) {
        continue;
      }

      mergeInheritedOptions(method, mapperConfig, methods, new ArrayList<SourceMethod>());

      MappingOptions mappingOptions = method.getMappingOptions();

      boolean hasFactoryMethod = false;

      if (method.isIterableMapping()) {

        IterableMappingMethod.Builder builder = new IterableMappingMethod.Builder();

        FormattingParameters formattingParameters = null;
        SelectionParameters selectionParameters = null;
        NullValueMappingStrategyPrism nullValueMappingStrategy = null;

        if (mappingOptions.getIterableMapping() != null) {
          formattingParameters = mappingOptions.getIterableMapping().getFormattingParameters();
          selectionParameters = mappingOptions.getIterableMapping().getSelectionParameters();
          nullValueMappingStrategy =
              mappingOptions.getIterableMapping().getNullValueMappingStrategy();
        }

        IterableMappingMethod iterableMappingMethod =
            builder
                .mappingContext(mappingContext)
                .method(method)
                .formattingParameters(formattingParameters)
                .selectionParameters(selectionParameters)
                .nullValueMappingStrategy(nullValueMappingStrategy)
                .build();

        hasFactoryMethod = iterableMappingMethod.getFactoryMethod() != null;
        mappingMethods.add(iterableMappingMethod);
      } else if (method.isMapMapping()) {

        MapMappingMethod.Builder builder = new MapMappingMethod.Builder();

        SelectionParameters keySelectionParameters = null;
        FormattingParameters keyFormattingParameters = null;
        SelectionParameters valueSelectionParameters = null;
        FormattingParameters valueFormattingParameters = null;
        NullValueMappingStrategyPrism nullValueMappingStrategy = null;

        if (mappingOptions.getMapMapping() != null) {
          keySelectionParameters = mappingOptions.getMapMapping().getKeySelectionParameters();
          keyFormattingParameters = mappingOptions.getMapMapping().getKeyFormattingParameters();
          valueSelectionParameters = mappingOptions.getMapMapping().getValueSelectionParameters();
          valueFormattingParameters = mappingOptions.getMapMapping().getValueFormattingParameters();
          nullValueMappingStrategy = mappingOptions.getMapMapping().getNullValueMappingStrategy();
        }

        MapMappingMethod mapMappingMethod =
            builder
                .mappingContext(mappingContext)
                .method(method)
                .keyFormattingParameters(keyFormattingParameters)
                .keySelectionParameters(keySelectionParameters)
                .valueFormattingParameters(valueFormattingParameters)
                .valueSelectionParameters(valueSelectionParameters)
                .nullValueMappingStrategy(nullValueMappingStrategy)
                .build();

        hasFactoryMethod = mapMappingMethod.getFactoryMethod() != null;
        mappingMethods.add(mapMappingMethod);
      } else if (method.isValueMapping()) {
        // prefer value mappings over enum mapping
        ValueMappingMethod valueMappingMethod =
            new ValueMappingMethod.Builder()
                .mappingContext(mappingContext)
                .souceMethod(method)
                .valueMappings(mappingOptions.getValueMappings())
                .build();
        mappingMethods.add(valueMappingMethod);
      } else if (method.isEnumMapping()) {

        messager.printMessage(method.getExecutable(), Message.ENUMMAPPING_DEPRECATED);

        EnumMappingMethod.Builder builder = new EnumMappingMethod.Builder();
        MappingMethod enumMappingMethod =
            builder.mappingContext(mappingContext).souceMethod(method).build();

        if (enumMappingMethod != null) {
          mappingMethods.add(enumMappingMethod);
        }
      } else {

        NullValueMappingStrategyPrism nullValueMappingStrategy = null;
        SelectionParameters selectionParameters = null;

        if (mappingOptions.getBeanMapping() != null) {
          nullValueMappingStrategy = mappingOptions.getBeanMapping().getNullValueMappingStrategy();
          selectionParameters = mappingOptions.getBeanMapping().getSelectionParameters();
        }
        BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder();
        BeanMappingMethod beanMappingMethod =
            builder
                .mappingContext(mappingContext)
                .souceMethod(method)
                .nullValueMappingStrategy(nullValueMappingStrategy)
                .selectionParameters(selectionParameters)
                .build();

        if (beanMappingMethod != null) {
          hasFactoryMethod = beanMappingMethod.getFactoryMethod() != null;
          mappingMethods.add(beanMappingMethod);
        }
      }

      if (!hasFactoryMethod) {
        // A factory method  is allowed to return an interface type and hence, the generated
        // implementation as well. The check below must only be executed if there's no factory
        // method that could be responsible.
        reportErrorIfNoImplementationTypeIsRegisteredForInterfaceReturnType(method);
      }
    }
    return mappingMethods;
  }