private Mapper getMapper(
      TypeElement element, MapperConfiguration mapperConfig, List<SourceMethod> methods) {
    List<MapperReference> mapperReferences = mappingContext.getMapperReferences();
    List<MappingMethod> mappingMethods = getMappingMethods(mapperConfig, methods);
    mappingMethods.addAll(mappingContext.getUsedVirtualMappings());
    mappingMethods.addAll(mappingContext.getMappingsToGenerate());

    Mapper mapper =
        new Mapper.Builder()
            .element(element)
            .mappingMethods(mappingMethods)
            .mapperReferences(mapperReferences)
            .options(options)
            .versionInformation(versionInformation)
            .decorator(
                getDecorator(
                    element,
                    methods,
                    mapperConfig.implementationName(),
                    mapperConfig.implementationPackage()))
            .typeFactory(typeFactory)
            .elementUtils(elementUtils)
            .extraImports(getExtraImports(element))
            .implName(mapperConfig.implementationName())
            .implPackage(mapperConfig.implementationPackage())
            .build();

    return mapper;
  }
  @Override
  public Mapper process(
      ProcessorContext context, TypeElement mapperTypeElement, List<SourceMethod> sourceModel) {
    this.elementUtils = context.getElementUtils();
    this.typeUtils = context.getTypeUtils();
    this.messager = context.getMessager();
    this.options = context.getOptions();
    this.versionInformation = context.getVersionInformation();
    this.typeFactory = context.getTypeFactory();

    MapperConfiguration mapperConfig = MapperConfiguration.getInstanceOn(mapperTypeElement);
    List<MapperReference> mapperReferences = initReferencedMappers(mapperTypeElement, mapperConfig);

    MappingBuilderContext ctx =
        new MappingBuilderContext(
            typeFactory,
            elementUtils,
            typeUtils,
            messager,
            options,
            new MappingResolverImpl(
                messager, elementUtils, typeUtils, typeFactory, sourceModel, mapperReferences),
            mapperTypeElement,
            sourceModel,
            mapperReferences);
    this.mappingContext = ctx;
    return getMapper(mapperTypeElement, mapperConfig, sourceModel);
  }
  private SortedSet<Type> getExtraImports(TypeElement element) {
    SortedSet<Type> extraImports = new TreeSet<Type>();

    MapperConfiguration mapperConfiguration = MapperConfiguration.getInstanceOn(element);

    for (TypeMirror extraImport : mapperConfiguration.imports()) {
      Type type = typeFactory.getType(extraImport);
      extraImports.add(type);
    }

    // Add original package if a dest package has been set
    if (!"default".equals(mapperConfiguration.implementationPackage())) {
      extraImports.add(typeFactory.getType(element));
    }

    return extraImports;
  }
  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<MapperReference> initReferencedMappers(
      TypeElement element, MapperConfiguration mapperConfig) {
    List<MapperReference> result = new LinkedList<MapperReference>();
    List<String> variableNames = new LinkedList<String>();

    for (TypeMirror usedMapper : mapperConfig.uses()) {
      DefaultMapperReference mapperReference =
          DefaultMapperReference.getInstance(
              typeFactory.getType(usedMapper),
              MapperPrism.getInstanceOn(typeUtils.asElement(usedMapper)) != null,
              typeFactory,
              variableNames);

      result.add(mapperReference);
      variableNames.add(mapperReference.getVariableName());
    }

    return result;
  }