/**
   * Takes collection of all filters/interceptors (either request/reader or response/writer) and
   * separates out all name-bound filters/interceptors, returns them as a separate MultivaluedMap,
   * mapping the name-bound annotation to the list of name-bound filters/interceptors.
   *
   * <p>Note, the name-bound filters/interceptors are removed from the original filters/interceptors
   * collection. If non-null collection is passed in the postMatching parameter (applicable for
   * filters only), this method also removes all the global postMatching filters from the original
   * collection and adds them to the collection passed in the postMatching parameter.
   *
   * @param all Collection of all filters to be processed.
   * @param applicationNameBindings collection of name binding annotations attached to the JAX-RS
   *     application.
   * @return {@link MultivaluedMap} of all name-bound filters.
   */
  private static <T> MultivaluedMap<Class<? extends Annotation>, RankedProvider<T>> filterNameBound(
      final Iterable<RankedProvider<T>> all,
      final Collection<RankedProvider<ContainerRequestFilter>> preMatching,
      final ComponentBag componentBag,
      final Collection<Class<? extends Annotation>> applicationNameBindings) {

    final MultivaluedMap<Class<? extends Annotation>, RankedProvider<T>> result =
        new MultivaluedHashMap<Class<? extends Annotation>, RankedProvider<T>>();

    for (Iterator<RankedProvider<T>> it = all.iterator(); it.hasNext(); ) {
      RankedProvider<T> provider = it.next();
      final Class<?> providerClass = provider.getProvider().getClass();

      ContractProvider model = componentBag.getModel(providerClass);
      if (model == null) {
        // the provider was (most likely) bound in HK2 externally
        model = ComponentBag.modelFor(providerClass);
      }

      if (preMatching != null && providerClass.getAnnotation(PreMatching.class) != null) {
        it.remove();
        preMatching.add(
            new RankedProvider<ContainerRequestFilter>(
                (ContainerRequestFilter) provider.getProvider(),
                model.getPriority(ContainerRequestFilter.class)));
      }

      boolean nameBound = model.isNameBound();
      if (nameBound && !applicationNameBindings.isEmpty()) {
        for (Class<? extends Annotation> binding : model.getNameBindings()) {
          if (applicationNameBindings.contains(binding)) {
            // override the name-bound flag
            nameBound = false;
            break;
          }
        }
      }
      if (nameBound) { // not application-bound
        it.remove();
        for (Class<? extends Annotation> binding : model.getNameBindings()) {
          result.add(binding, provider);
        }
      }
    }

    return result;
  }
  private void bindEnhancingResourceClasses(
      ResourceModel resourceModel,
      ResourceBag resourceBag,
      Set<ComponentProvider> componentProviders) {
    Set<Class<?>> newClasses = Sets.newHashSet();
    Set<Object> newInstances = Sets.newHashSet();
    for (Resource res : resourceModel.getRootResources()) {
      newClasses.addAll(res.getHandlerClasses());
      newInstances.addAll(res.getHandlerInstances());
    }
    newClasses.removeAll(resourceBag.classes);
    newInstances.removeAll(resourceBag.instances);

    ComponentBag emptyComponentBag =
        ComponentBag.newInstance(
            new Predicate<ContractProvider>() {
              @Override
              public boolean apply(ContractProvider input) {
                return false;
              }
            });
    bindProvidersAndResources(componentProviders, emptyComponentBag, newClasses, newInstances);
  }
  private void bindProvidersAndResources(
      final Set<ComponentProvider> componentProviders,
      final ComponentBag componentBag,
      final Set<Class<?>> resourceClasses,
      final Set<Object> resourceInstances) {

    final JerseyResourceContext resourceContext = locator.getService(JerseyResourceContext.class);
    final DynamicConfiguration dc = Injections.getConfiguration(locator);
    final Set<Class<?>> registeredClasses = runtimeConfig.getRegisteredClasses();

    // Merge programmatic resource classes with component classes.
    Set<Class<?>> classes = Sets.newIdentityHashSet();
    classes.addAll(
        Sets.filter(
            componentBag.getClasses(ComponentBag.EXCLUDE_META_PROVIDERS),
            new Predicate<Class<?>>() {
              @Override
              public boolean apply(Class<?> componentClass) {
                return Providers.checkProviderRuntime(
                    componentClass,
                    componentBag.getModel(componentClass),
                    RuntimeType.SERVER,
                    !registeredClasses.contains(componentClass),
                    resourceClasses.contains(componentClass));
              }
            }));
    classes.addAll(resourceClasses);

    // Bind classes.
    for (Class<?> componentClass : classes) {
      ContractProvider model = componentBag.getModel(componentClass);
      if (resourceClasses.contains(componentClass)) {
        if (bindWithComponentProvider(componentClass, model, componentProviders)) {
          continue;
        }

        if (!Resource.isAcceptable(componentClass)) {
          LOGGER.warning(LocalizationMessages.NON_INSTANTIABLE_COMPONENT(componentClass));
          continue;
        }

        if (model != null
            && !Providers.checkProviderRuntime(
                componentClass,
                model,
                RuntimeType.SERVER,
                !registeredClasses.contains(componentClass),
                true)) {
          model = null;
        }
        resourceContext.unsafeBindResource(componentClass, model, dc);
      } else {
        ProviderBinder.bindProvider(componentClass, model, dc);
      }
    }

    // Merge programmatic resource instances with other component instances.
    Set<Object> instances = Sets.newHashSet();
    instances.addAll(
        Sets.filter(
            componentBag.getInstances(ComponentBag.EXCLUDE_META_PROVIDERS),
            new Predicate<Object>() {
              @Override
              public boolean apply(Object component) {
                final Class<?> componentClass = component.getClass();
                return Providers.checkProviderRuntime(
                    componentClass,
                    componentBag.getModel(componentClass),
                    RuntimeType.SERVER,
                    !registeredClasses.contains(componentClass),
                    resourceInstances.contains(component));
              }
            }));
    instances.addAll(resourceInstances);

    // Bind instances.
    for (Object component : instances) {
      ContractProvider model = componentBag.getModel(component.getClass());
      if (resourceInstances.contains(component)) {
        if (model != null
            && !Providers.checkProviderRuntime(
                component.getClass(),
                model,
                RuntimeType.SERVER,
                !registeredClasses.contains(component.getClass()),
                true)) {
          model = null;
        }
        resourceContext.unsafeBindResource(component, model, dc);
      } else {
        ProviderBinder.bindProvider(component, model, dc);
      }
    }

    dc.commit();
  }
  /** Assumes the configuration field is initialized with a valid ResourceConfig. */
  private void initialize() {
    LOGGER.info(LocalizationMessages.INIT_MSG(Version.getBuildId()));

    // Lock original ResourceConfig.
    if (application instanceof ResourceConfig) {
      ((ResourceConfig) application).lock();
    }

    // add WADL support
    runtimeConfig.register(WadlModelProcessorFeature.class);

    // Configure binders and features.
    runtimeConfig.configureMetaProviders(locator);

    // Introspecting classes & instances
    final ResourceBag.Builder resourceBagBuilder = new ResourceBag.Builder();
    for (Class<?> c : runtimeConfig.getClasses()) {
      try {
        Resource resource = Resource.from(c);
        if (resource != null) {
          resourceBagBuilder.registerResource(c, resource);
        }
      } catch (IllegalArgumentException ex) {
        LOGGER.warning(ex.getMessage());
      }
    }

    for (Object o : runtimeConfig.getSingletons()) {
      try {
        Resource resource = Resource.from(o.getClass());
        if (resource != null) {
          resourceBagBuilder.registerResource(o, resource);
        }
      } catch (IllegalArgumentException ex) {
        LOGGER.warning(ex.getMessage());
      }
    }

    // Adding programmatic resource models
    for (Resource programmaticResource : runtimeConfig.getResources()) {
      resourceBagBuilder.registerProgrammaticResource(programmaticResource);
    }

    final ResourceBag resourceBag = resourceBagBuilder.build();

    runtimeConfig.lock();

    // Registering Injection Bindings
    final Set<ComponentProvider> componentProviders = new HashSet<ComponentProvider>();

    for (ComponentProvider provider : ServiceFinder.find(ComponentProvider.class)) {
      provider.initialize(locator);
      componentProviders.add(provider);
    }

    final ComponentBag componentBag = runtimeConfig.getComponentBag();
    bindProvidersAndResources(
        componentProviders, componentBag, resourceBag.classes, resourceBag.instances);
    for (ComponentProvider componentProvider : componentProviders) {
      componentProvider.done();
    }

    // scan for NameBinding annotations attached to the application class
    Collection<Class<? extends Annotation>> applicationNameBindings =
        ReflectionHelper.getAnnotationTypes(application.getClass(), NameBinding.class);

    // find all filters, interceptors and dynamic features
    final Iterable<RankedProvider<ContainerResponseFilter>> responseFilters =
        Providers.getAllRankedProviders(locator, ContainerResponseFilter.class);
    final MultivaluedMap<Class<? extends Annotation>, RankedProvider<ContainerResponseFilter>>
        nameBoundResponseFilters =
            filterNameBound(responseFilters, null, componentBag, applicationNameBindings);

    final Iterable<RankedProvider<ContainerRequestFilter>> requestFilters =
        Providers.getAllRankedProviders(locator, ContainerRequestFilter.class);
    final List<RankedProvider<ContainerRequestFilter>> preMatchFilters = Lists.newArrayList();
    final MultivaluedMap<Class<? extends Annotation>, RankedProvider<ContainerRequestFilter>>
        nameBoundRequestFilters =
            filterNameBound(requestFilters, preMatchFilters, componentBag, applicationNameBindings);

    final Iterable<RankedProvider<ReaderInterceptor>> readerInterceptors =
        Providers.getAllRankedProviders(locator, ReaderInterceptor.class);
    final MultivaluedMap<Class<? extends Annotation>, RankedProvider<ReaderInterceptor>>
        nameBoundReaderInterceptors =
            filterNameBound(readerInterceptors, null, componentBag, applicationNameBindings);

    final Iterable<RankedProvider<WriterInterceptor>> writerInterceptors =
        Providers.getAllRankedProviders(locator, WriterInterceptor.class);
    final MultivaluedMap<Class<? extends Annotation>, RankedProvider<WriterInterceptor>>
        nameBoundWriterInterceptors =
            filterNameBound(writerInterceptors, null, componentBag, applicationNameBindings);
    final Iterable<DynamicFeature> dynamicFeatures =
        Providers.getAllProviders(locator, DynamicFeature.class);

    ResourceModel resourceModel =
        new ResourceModel.Builder(resourceBag.getRootResources(), false).build();

    resourceModel = processResourceModel(resourceModel);
    // validate the models
    validate(resourceModel);

    bindEnhancingResourceClasses(resourceModel, resourceBag, componentProviders);

    final RuntimeModelBuilder runtimeModelBuilder = locator.getService(RuntimeModelBuilder.class);
    runtimeModelBuilder.setGlobalInterceptors(readerInterceptors, writerInterceptors);
    runtimeModelBuilder.setBoundProviders(
        nameBoundRequestFilters,
        nameBoundResponseFilters,
        nameBoundReaderInterceptors,
        nameBoundWriterInterceptors,
        dynamicFeatures);

    // assembly request processing chain
    /**
     * Root hierarchical request matching acceptor. Invoked in a single linear stage as part of the
     * main linear accepting chain.
     */
    final Router resourceRoutingRoot =
        runtimeModelBuilder.buildModel(resourceModel.getRuntimeResourceModel(), false);

    final ContainerFilteringStage preMatchRequestFilteringStage =
        locator
            .createAndInitialize(ContainerFilteringStage.Builder.class)
            .build(preMatchFilters, responseFilters);
    final RoutingStage routingStage =
        locator.createAndInitialize(RoutingStage.Builder.class).build(resourceRoutingRoot);
    final ContainerFilteringStage resourceFilteringStage =
        locator
            .createAndInitialize(ContainerFilteringStage.Builder.class)
            .build(requestFilters, null);
    final RoutedInflectorExtractorStage routedInflectorExtractorStage =
        locator.createAndInitialize(RoutedInflectorExtractorStage.class);
    /**
     * Root linear request acceptor. This is the main entry point for the whole request processing.
     */
    final Stage<ContainerRequest> rootStage =
        Stages.chain(locator.createAndInitialize(ReferencesInitializer.class))
            .to(locator.createAndInitialize(ContainerMessageBodyWorkersInitializer.class))
            .to(preMatchRequestFilteringStage)
            .to(routingStage)
            .to(resourceFilteringStage)
            .build(routedInflectorExtractorStage);

    // Inject instances.
    for (Object instance : componentBag.getInstances(ComponentBag.EXCLUDE_META_PROVIDERS)) {
      locator.inject(instance);
    }
    for (Object instance : resourceBag.instances) {
      locator.inject(instance);
    }

    // initiate resource model into JerseyResourceContext
    JerseyResourceContext jerseyResourceContext = locator.getService(JerseyResourceContext.class);
    jerseyResourceContext.setResourceModel(resourceModel);

    this.runtime = locator.createAndInitialize(ServerRuntime.Builder.class).build(rootStage);

    // inject self
    locator.inject(this);
  }