content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.javaagent.bootstrap.servlet;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  import javax.annotation.Nullable;
  
  /**
   * Helper class for finding a mapping that matches current request from a collection of mappings.
   */
  public final class MappingResolver {
    private final Set<String> exactMatches;
    private final List<WildcardMatcher> wildcardMatchers;
    private final boolean hasDefault;
  
    private MappingResolver(
        Set<String> exactMatches, List<WildcardMatcher> wildcardMatchers, boolean hasDefault) {
      this.exactMatches = exactMatches.isEmpty() ? Collections.emptySet() : exactMatches;
      this.wildcardMatchers = wildcardMatchers.isEmpty() ? Collections.emptyList() : wildcardMatchers;
      this.hasDefault = hasDefault;
    }
  
    public static MappingResolver build(Collection<String> mappings) {
      List<WildcardMatcher> wildcardMatchers = new ArrayList<>();
      Set<String> exactMatches = new HashSet<>();
      boolean hasDefault = false;
      for (String mapping : mappings) {
        if (mapping.equals("")) {
          exactMatches.add("/");
        } else if (mapping.equals("/") || mapping.equals("/*")) {
          hasDefault = true;
        } else if (mapping.startsWith("*.") && mapping.length() > 2) {
          wildcardMatchers.add(new SuffixMatcher("/" + mapping, mapping.substring(1)));
        } else if (mapping.endsWith("/*")) {
          wildcardMatchers.add(
              new PrefixMatcher(mapping, mapping.substring(0, mapping.length() - 2)));
        } else {
          exactMatches.add(mapping);
        }
      }
  
      // wildfly has empty mappings for default servlet
      if (mappings.isEmpty()) {
        hasDefault = true;
      }
  
      return new MappingResolver(exactMatches, wildcardMatchers, hasDefault);
    }
  
    /** Find mapping for requested path. */
    @Nullable
    public String resolve(@Nullable String servletPath, @Nullable String pathInfo) {
      if (servletPath == null) {
        return null;
      }
  
      // get full path inside context
      String path = servletPath;
      if (pathInfo != null) {
        path += pathInfo;
      }
      // trim trailing /
      if (path.endsWith("/") && !path.equals("/")) {
        path = path.substring(0, path.length() - 1);
      }
  
      if (exactMatches.contains(path)) {
        return path;
      }
  
      for (WildcardMatcher matcher : wildcardMatchers) {
        if (matcher.match(path)) {
          String mapping = matcher.getMapping();
          // for jsp return servlet path
          if ("/*.jsp".equals(mapping) || "/*.jspx".equals(mapping)) {
            return servletPath;
          }
          return mapping;
        }
      }
  
      if (hasDefault) {
        return path.equals("/") ? "/" : "/*";
      }
  
      return null;
    }
  
    private interface WildcardMatcher {
      boolean match(String path);
  
      String getMapping();
    }
  
    private static class PrefixMatcher implements WildcardMatcher {
      private final String mapping;
      private final String prefix;
  
      private PrefixMatcher(String mapping, String prefix) {
        this.mapping = mapping;
        this.prefix = prefix;
      }
  
      @Override
      public boolean match(String path) {
        return path.equals(prefix) || path.startsWith(prefix + "/");
      }
  
      @Override
      public String getMapping() {
        return mapping;
      }
    }
  
    private static class SuffixMatcher implements WildcardMatcher {
      private final String mapping;
      private final String suffix;
  
      private SuffixMatcher(String mapping, String suffix) {
        this.mapping = mapping;
        this.suffix = suffix;
      }
  
      @Override
      public boolean match(String path) {
        return path.endsWith(suffix);
      }
  
      @Override
      public String getMapping() {
        return mapping;
      }
    }
  
    /**
     * Factory interface for creating {@link MappingResolver} instances. The main reason this class is
     * here is that we need to ensure that the class used for {@code VirtualField} lookup is always
     * the same. If we would use an injected class it could be different in different class loaders.
     */
    public interface Factory {
  
      @Nullable
      MappingResolver get();
    }
  }
  