gt:                     if (responseContext.getMediaType() == null
context: /*
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
   *
   * Copyright (c) 2012-2014 Oracle and/or its affiliates. All rights reserved.
   *
   * The contents of this file are subject to the terms of either the GNU
   * General Public License Version 2 only ("GPL") or the Common Development
   * and Distribution License("CDDL") (collectively, the "License").  You
   * may not use this file except in compliance with the License.  You can
   * obtain a copy of the License at
   * http://glassfish.java.net/public/CDDL+GPL_1_1.html
   * or packager/legal/LICENSE.txt.  See the License for the specific
   * language governing permissions and limitations under the License.
   *
   * When distributing the software, include this License Header Notice in each
   * file and include the License file at packager/legal/LICENSE.txt.
   *
   * GPL Classpath Exception:
   * Oracle designates this particular file as subject to the "Classpath"
   * exception as provided by Oracle in the GPL Version 2 section of the License
   * file that accompanied this code.
   *
   * Modifications:
   * If applicable, add the following below the License Header, with the fields
   * enclosed by brackets [] replaced by your own identifying information:
   * "Portions Copyright [year] [name of copyright owner]"
   *
   * Contributor(s):
   * If you wish your version of this file to be governed by only the CDDL or
   * only the GPL Version 2, indicate your decision by adding "[Contributor]
   * elects to include this software in this distribution under the [CDDL or GPL
   * Version 2] license."  If you don't indicate a single choice of license, a
   * recipient has the option to distribute your version of this file under
   * either the CDDL, the GPL Version 2 or to extend the choice of license to
   * its licensees as provided above.  However, if you add GPL Version 2 code
   * and therefore, elected the GPL Version 2 license, then the option applies
   * only if the new code is made subject to such option by the copyright
   * holder.
   */
  package org.glassfish.jersey.server.internal.routing;
  
  import java.lang.reflect.Method;
  import java.lang.reflect.Type;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.LinkedHashSet;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
  import javax.ws.rs.HttpMethod;
  import javax.ws.rs.NotAcceptableException;
  import javax.ws.rs.NotAllowedException;
  import javax.ws.rs.NotSupportedException;
  import javax.ws.rs.Produces;
  import javax.ws.rs.container.ContainerRequestContext;
  import javax.ws.rs.core.GenericType;
  import javax.ws.rs.core.MediaType;
  import javax.ws.rs.core.Response;
  import javax.ws.rs.core.Response.Status;
  import javax.ws.rs.ext.MessageBodyReader;
  import javax.ws.rs.ext.MessageBodyWriter;
  
  import org.glassfish.jersey.message.MessageBodyWorkers;
  import org.glassfish.jersey.message.internal.MediaTypes;
  import org.glassfish.jersey.server.ContainerRequest;
  import org.glassfish.jersey.server.ContainerResponse;
  import org.glassfish.jersey.server.internal.LocalizationMessages;
  import org.glassfish.jersey.server.internal.process.RequestProcessingContext;
  import org.glassfish.jersey.server.model.Invocable;
  import org.glassfish.jersey.server.model.Parameter;
  import org.glassfish.jersey.server.model.ResourceMethod;
  
  import com.google.common.base.Function;
  import com.google.common.collect.Lists;
  import com.google.common.collect.Sets;
  import com.google.common.primitives.Primitives;
  
  /**
   * A single router responsible for selecting a single method from all the methods
   * bound to the same routed request path.
   *
   * The method selection algorithm selects the handling method based on the HTTP request
   * method name, requested media type as well as defined resource method media type
   * capabilities.
   *
   * @author Jakub Podlesak (jakub.podlesak at oracle.com)
   * @author Marek Potociar (marek.potociar at oracle.com)
   */
  final class MethodSelectingRouter implements Router {
  
      private static final Logger LOGGER = Logger.getLogger(MethodSelectingRouter.class.getName());
  
      private final MessageBodyWorkers workers;
  
      private final Map<String, List<ConsumesProducesAcceptor>> consumesProducesAcceptors;
      private final Router router;
  
      /**
       * Injectable builder of a {@link MethodSelectingRouter} instance.
       */
      static class Builder {
  
          /**
           * Create a new {@link MethodSelectingRouter} for all the methods on the same path.
           *
           * The router selects the method that best matches the request based on
           * produce/consume information from the resource method models.
           *
           * @param workers             message body workers.
           * @param methodAcceptorPairs [method model, method methodAcceptorPair] pairs.
           * @return new {@link MethodSelectingRouter}
           */
          public MethodSelectingRouter build(
                  final MessageBodyWorkers workers, final List<MethodAcceptorPair> methodAcceptorPairs) {
  
              return new MethodSelectingRouter(workers,
                      methodAcceptorPairs);
          }
      }
  
      /**
       * Create a new {@code MethodSelectingRouter} for all the methods on the same path.
       *
       * The router selects the method that best matches the request based on
       * produce/consume information from the resource method models.
       *
       * @param workers             message body workers.
       * @param methodAcceptorPairs [method model, method methodAcceptorPair] pairs.
       * @return new {@link MethodSelectingRouter}
       */
      MethodSelectingRouter(MessageBodyWorkers workers, List<MethodAcceptorPair> methodAcceptorPairs) {
          this.workers = workers;
  
          this.consumesProducesAcceptors = new HashMap<>();
  
          final Set<String> httpMethods = Sets.newHashSet();
          for (final MethodAcceptorPair methodAcceptorPair : methodAcceptorPairs) {
              final String httpMethod = methodAcceptorPair.model.getHttpMethod();
              httpMethods.add(httpMethod);
  
              List<ConsumesProducesAcceptor> httpMethodBoundAcceptors = consumesProducesAcceptors.get(httpMethod);
              if (httpMethodBoundAcceptors == null) {
                  httpMethodBoundAcceptors = new LinkedList<>();
                  consumesProducesAcceptors.put(httpMethod, httpMethodBoundAcceptors);
              }
  
              addAllConsumesProducesCombinations(httpMethodBoundAcceptors, methodAcceptorPair);
          }
  
          // Sort acceptors for added HTTP methods - primary based on @Consumes, @Produces present on method, secondary on consumes,
          // produces values of the acceptor.
          for (final String httpMethod : httpMethods) {
              Collections.sort(consumesProducesAcceptors.get(httpMethod), new Comparator<ConsumesProducesAcceptor>() {
  
                  @Override
                  public int compare(final ConsumesProducesAcceptor o1, final ConsumesProducesAcceptor o2) {
                      // Make sure that annotated (@Consumes, @Produces) goes first.
                      final ResourceMethod model1 = o1.methodAcceptorPair.model;
                      final ResourceMethod model2 = o2.methodAcceptorPair.model;
  
                      // @Consumes on method.
                      int compared = compare(model2.getConsumedTypes(), model1.getConsumedTypes());
  
                      if (compared == 0) {
                          // @Produces on method.
                          compared = compare(model2.getProducedTypes(), model1.getProducedTypes());
  
                          if (compared == 0) {
                              // Consumes media type of the acceptor.
                              compared = MediaTypes.MEDIA_TYPE_COMPARATOR.compare(o1.consumes.getMediaType(),
                                      o2.consumes.getMediaType());
  
                              if (compared == 0) {
                                  // Produces media type of the acceptor.
                                  compared = MediaTypes.MEDIA_TYPE_COMPARATOR.compare(o1.produces.getMediaType(),
                                          o2.produces.getMediaType());
                              }
                          }
                      }
  
                      return compared;
                  }
  
                  private int compare(List<MediaType> mediaTypeList1, List<MediaType> mediaTypeList2) {
                      mediaTypeList1 = mediaTypeList1.isEmpty() ? MediaTypes.GENERAL_MEDIA_TYPE_LIST : mediaTypeList1;
                      mediaTypeList2 = mediaTypeList2.isEmpty() ? MediaTypes.GENERAL_MEDIA_TYPE_LIST : mediaTypeList2;
  
                      return MediaTypes.MEDIA_TYPE_LIST_COMPARATOR.compare(mediaTypeList2, mediaTypeList1);
                  }
              });
          }
  
          if (!consumesProducesAcceptors.containsKey(HttpMethod.HEAD)) {
              this.router = createHeadEnrichedRouter();
          } else {
              this.router = createInternalRouter();
          }
      }
  
      /**
       * Represents a 1-1-1 relation between input and output media type and an methodAcceptorPair.
       * <p>E.g. for a single resource method
       * <pre>
       *   &#064;Consumes("&#042;/&#042;")
       *   &#064;Produces("text/plain","text/html")
       *   &#064;GET
       *   public String myGetMethod() {
       *     return "S";
       *   }
       * </pre>
       * the following two relations would be generated:
       * <table>
       * <tr>
       * <th>consumes</th>
       * <th>produces</th>
       * <th>method</th>
       * </tr>
       * <tr>
       * <td>&#042;/&#042;</td>
       * <td>text/plain</td>
       * <td>myGetMethod</td>
       * </td>
       * </tr>
       * <tr>
       * <td>&#042;/&#042;</td>
       * <td>text/html</td>
       * <td>myGetMethod</td>
       * </td>
       * </tr>
       * </table>
       */
      private static class ConsumesProducesAcceptor {
  
          private final CombinedClientServerMediaType.EffectiveMediaType consumes;
          private final CombinedClientServerMediaType.EffectiveMediaType produces;
          private final MethodAcceptorPair methodAcceptorPair;
  
          private ConsumesProducesAcceptor(
                  CombinedClientServerMediaType.EffectiveMediaType consumes,
                  CombinedClientServerMediaType.EffectiveMediaType produces,
                  MethodAcceptorPair methodAcceptorPair) {
              this.methodAcceptorPair = methodAcceptorPair;
              this.consumes = consumes;
              this.produces = produces;
          }
  
          /**
           * Returns the {@link CombinedClientServerMediaType.EffectiveMediaType extended media type} which can be
           * consumed by {@link ResourceMethod resource method} of this {@code ConsumesProducesAcceptor} router.
           *
           * @return Consumed type.
           */
          public CombinedClientServerMediaType.EffectiveMediaType getConsumes() {
              return consumes;
          }
  
          /**
           * Returns the {@link CombinedClientServerMediaType.EffectiveMediaType extended media type} which can be
           * produced by {@link ResourceMethod resource method} of this {@code ConsumesProducesAcceptor} router.
           *
           * @return Produced type.
           */
          public CombinedClientServerMediaType.EffectiveMediaType getProduces() {
              return produces;
          }
  
  
          /**
           * Determines whether this {@code ConsumesProducesAcceptor} router can process the {@code request}.
           *
           * @param requestContext The request to be tested.
           * @return True if the {@code request} can be processed by this router, false otherwise.
           */
          boolean isConsumable(ContainerRequest requestContext) {
              MediaType contentType = requestContext.getMediaType();
              return contentType == null || consumes.getMediaType().isCompatible(contentType);
          }
  
          @Override
          public String toString() {
              return String.format("%s->%s:%s", consumes.getMediaType(), produces.getMediaType(), methodAcceptorPair);
          }
  
          @Override
          public boolean equals(final Object o) {
              if (this == o) {
                  return true;
              }
              if (!(o instanceof ConsumesProducesAcceptor)) {
                  return false;
              }
  
              final ConsumesProducesAcceptor that = (ConsumesProducesAcceptor) o;
  
              if (consumes != null ? !consumes.equals(that.consumes) : that.consumes != null) {
                  return false;
              }
              if (methodAcceptorPair != null ? !methodAcceptorPair.equals(that.methodAcceptorPair) : that.methodAcceptorPair !=
                      null) {
                  return false;
              }
              if (produces != null ? !produces.equals(that.produces) : that.produces != null) {
                  return false;
              }
  
              return true;
          }
  
          @Override
          public int hashCode() {
              int result = consumes != null ? consumes.hashCode() : 0;
              result = 31 * result + (produces != null ? produces.hashCode() : 0);
              result = 31 * result + (methodAcceptorPair != null ? methodAcceptorPair.hashCode() : 0);
              return result;
          }
      }
  
      /**
       * The same as above ConsumesProducesAcceptor,
       * only concrete request content-type and accept header info is included in addition.
       *
       * @see org.glassfish.jersey.server.internal.routing.CombinedClientServerMediaType
       */
      private static class RequestSpecificConsumesProducesAcceptor implements Comparable {
  
          CombinedClientServerMediaType consumes;
          CombinedClientServerMediaType produces;
          MethodAcceptorPair methodAcceptorPair;
  
          boolean consumesFromProviders;
          boolean producesFromProviders;
  
          RequestSpecificConsumesProducesAcceptor(CombinedClientServerMediaType consumes,
                                                  boolean consumesFromProviders,
                                                  CombinedClientServerMediaType produces,
                                                  boolean producesFromProviders,
                                                  MethodAcceptorPair methodAcceptorPair) {
  
              this.methodAcceptorPair = methodAcceptorPair;
  
              this.consumes = consumes;
              this.produces = produces;
  
              this.consumesFromProviders = consumesFromProviders;
              this.producesFromProviders = producesFromProviders;
          }
  
          @Override
          public String toString() {
              return String.format("%s->%s:%s", consumes, produces, methodAcceptorPair);
          }
  
          @Override
          public int compareTo(Object o) {
              if (o == null) {
                  return 1;
              }
              if (!(o instanceof RequestSpecificConsumesProducesAcceptor)) {
                  return 1;
              }
              RequestSpecificConsumesProducesAcceptor other = (RequestSpecificConsumesProducesAcceptor) o;
              final int consumedComparison = CombinedClientServerMediaType.COMPARATOR.compare(consumes, other.consumes);
              return (consumedComparison != 0) ? consumedComparison : CombinedClientServerMediaType.COMPARATOR.compare(produces,
                      other.produces);
          }
      }
  
      /**
       * Helper class to select matching resource method to be invoked.
       */
      private static class MethodSelector {
  
          RequestSpecificConsumesProducesAcceptor selected;
          List<RequestSpecificConsumesProducesAcceptor> sameFitnessAcceptors;
  
          MethodSelector(RequestSpecificConsumesProducesAcceptor i) {
              selected = i;
              sameFitnessAcceptors = null;
          }
  
          void consider(RequestSpecificConsumesProducesAcceptor i) {
              final int theGreaterTheBetter = i.compareTo(selected);
              if (theGreaterTheBetter > 0) {
                  selected = i;
                  sameFitnessAcceptors = null;
              } else {
                  if (theGreaterTheBetter == 0 && (selected.methodAcceptorPair != i.methodAcceptorPair)) {
                      getSameFitnessList().add(i);
                  }
              }
          }
  
          List<RequestSpecificConsumesProducesAcceptor> getSameFitnessList() {
              if (sameFitnessAcceptors == null) {
                  sameFitnessAcceptors = new LinkedList<>();
              }
              return sameFitnessAcceptors;
          }
      }
  
  
      private Router createInternalRouter() {
          return new Router() {
  
              @Override
              public Continuation apply(RequestProcessingContext requestContext) {
                  return Continuation.of(requestContext, getMethodRouter(requestContext));
              }
          };
      }
  
      @Override
      public Continuation apply(RequestProcessingContext requestContext) {
          return router.apply(requestContext);
      }
  
      private void addAllConsumesProducesCombinations(final List<ConsumesProducesAcceptor> acceptors,
                                                      final MethodAcceptorPair methodAcceptorPair) {
          final ResourceMethod resourceMethod = methodAcceptorPair.model;
  
          final Set<MediaType> effectiveInputTypes = new LinkedHashSet<>();
          boolean consumesFromWorkers = fillMediaTypes(effectiveInputTypes, resourceMethod,
                  resourceMethod.getConsumedTypes(), true);
  
          final Set<MediaType> effectiveOutputTypes = new LinkedHashSet<>();
          boolean producesFromWorkers = fillMediaTypes(effectiveOutputTypes, resourceMethod,
                  resourceMethod.getProducedTypes(), false);
  
          final Set<ConsumesProducesAcceptor> acceptorSet = Sets.newHashSet();
          for (MediaType consumes : effectiveInputTypes) {
              for (MediaType produces : effectiveOutputTypes) {
  
                  acceptorSet.add(new ConsumesProducesAcceptor(
                          new CombinedClientServerMediaType.EffectiveMediaType(consumes, consumesFromWorkers),
                          new CombinedClientServerMediaType.EffectiveMediaType(produces, producesFromWorkers),
                          methodAcceptorPair));
              }
          }
          acceptors.addAll(acceptorSet);
      }
  
      private boolean fillMediaTypes(final Set<MediaType> effectiveTypes,
                                     final ResourceMethod resourceMethod,
                                     final List<MediaType> methodTypes,
                                     final boolean inputTypes) {
  
          // Add method types to the resulting list iff there is more than just */*
          if (methodTypes.size() > 1 || !methodTypes.contains(MediaType.WILDCARD_TYPE)) {
              effectiveTypes.addAll(methodTypes);
          }
  
          boolean mediaTypesFromWorkers = effectiveTypes.isEmpty();
          if (workers != null && mediaTypesFromWorkers) {
              final Invocable invocableMethod = resourceMethod.getInvocable();
  
              // If not predefined from method - get it from workers.
              if (inputTypes) {
                  fillInputTypesFromWorkers(effectiveTypes, invocableMethod);
              } else {
                  fillOutputTypesFromWorkers(effectiveTypes, invocableMethod.getRawResponseType());
              }
              mediaTypesFromWorkers = !effectiveTypes.isEmpty();
  
              // If still empty - get all available.
              if (!mediaTypesFromWorkers) {
                  if (inputTypes) {
                      effectiveTypes.addAll(workers.getMessageBodyReaderMediaTypesByType(Object.class));
                  } else {
                      effectiveTypes.addAll(workers.getMessageBodyWriterMediaTypesByType(Object.class));
                  }
                  mediaTypesFromWorkers = true;
              }
          }
  
          return mediaTypesFromWorkers;
      }
  
      private void fillOutputTypesFromWorkers(final Set<MediaType> effectiveOutputTypes, final Class<?> returnEntityType) {
          effectiveOutputTypes.addAll(workers.getMessageBodyWriterMediaTypesByType(returnEntityType));
      }
  
      private void fillInputTypesFromWorkers(final Set<MediaType> effectiveInputTypes, final Invocable invocableMethod) {
          for (Parameter p : invocableMethod.getParameters()) {
              if (p.getSource() == Parameter.Source.ENTITY) {
                  effectiveInputTypes.addAll(workers.getMessageBodyReaderMediaTypesByType(p.getRawType()));
  
                  // there's at most one entity parameter
                  break;
              }
          }
      }
  
      private Parameter getEntityParam(final Invocable invocable) {
          for (final Parameter parameter : invocable.getParameters()) {
              if (parameter.getSource() == Parameter.Source.ENTITY
                      && !ContainerRequestContext.class.isAssignableFrom(parameter.getRawType())) {
                  // there's at most one entity parameter
                  return parameter;
              }
          }
          return null;
      }
  
      private List<Router> getMethodRouter(final RequestProcessingContext context) {
          final ContainerRequest request = context.request();
          final List<ConsumesProducesAcceptor> acceptors = consumesProducesAcceptors.get(request.getMethod());
          if (acceptors == null) {
              throw new NotAllowedException(
                      Response.status(Status.METHOD_NOT_ALLOWED).allow(consumesProducesAcceptors.keySet()).build());
          }
  
          final List<ConsumesProducesAcceptor> satisfyingAcceptors = new LinkedList<>();
          final Set<ResourceMethod> differentInvokableMethods = Sets.newIdentityHashSet();
          for (ConsumesProducesAcceptor cpi : acceptors) {
              if (cpi.isConsumable(request)) {
                  satisfyingAcceptors.add(cpi);
                  differentInvokableMethods.add(cpi.methodAcceptorPair.model);
              }
          }
          if (satisfyingAcceptors.isEmpty()) {
              throw new NotSupportedException();
          }
  
          final List<MediaType> acceptableMediaTypes = request.getAcceptableMediaTypes();
  
          final MediaType requestContentType = request.getMediaType();
          final MediaType effectiveContentType = requestContentType == null ? MediaType.WILDCARD_TYPE : requestContentType;
  
          final MethodSelector methodSelector = selectMethod(acceptableMediaTypes, satisfyingAcceptors, effectiveContentType,
                  differentInvokableMethods.size() == 1);
  
          if (methodSelector.selected != null) {
              final RequestSpecificConsumesProducesAcceptor selected = methodSelector.selected;
  
              if (methodSelector.sameFitnessAcceptors != null) {
                  reportMethodSelectionAmbiguity(acceptableMediaTypes, methodSelector.selected, methodSelector.sameFitnessAcceptors);
              }
  
              context.push(new Function<ContainerResponse, ContainerResponse>() {
                  @Override
                  public ContainerResponse apply(final ContainerResponse responseContext) {
                      // we only need to compute and set the effective media type if it hasn't been set already
                      // and either there is an entity, or we are responding to a HEAD request
  