View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.http.protocol;
29  
30  import java.util.HashSet;
31  import java.util.LinkedHashMap;
32  import java.util.Map;
33  import java.util.Map.Entry;
34  import java.util.Set;
35  
36  import org.apache.hc.core5.annotation.Contract;
37  import org.apache.hc.core5.annotation.ThreadingBehavior;
38  import org.apache.hc.core5.util.Args;
39  
40  /**
41   * Maintains a map of objects keyed by a request URI pattern.
42   * <p>
43   * Patterns may have three formats:
44   * </p>
45   * <ul>
46   * <li>{@code *}</li>
47   * <li>{@code *<uri>}</li>
48   * <li>{@code <uri>*}</li>
49   * </ul>
50   * <p>
51   * This class can be used to resolve an object matching a particular request
52   * URI.
53   * </p>
54   *
55   * @param <T> The type of registered objects.
56   * @since 4.0
57   */
58  @Contract(threading = ThreadingBehavior.SAFE)
59  public class UriPatternMatcher<T> implements LookupRegistry<T> {
60  
61      private final Map<String, T> map;
62  
63      public UriPatternMatcher() {
64          super();
65          this.map = new LinkedHashMap<>();
66      }
67  
68      /**
69       * Returns a {@link Set} view of the mappings contained in this matcher.
70       *
71       * @return  a set view of the mappings contained in this matcher.
72       *
73       * @see Map#entrySet()
74       * @since 4.4.9
75       */
76      public synchronized Set<Entry<String, T>> entrySet() {
77          return new HashSet<>(map.entrySet());
78      }
79  
80      /**
81       * Registers the given object for URIs matching the given pattern.
82       *
83       * @param pattern
84       *            the pattern to register the handler for.
85       * @param obj
86       *            the object.
87       */
88      @Override
89      public synchronized void register(final String pattern, final T obj) {
90          Args.notNull(pattern, "URI request pattern");
91          this.map.put(pattern, obj);
92      }
93  
94      /**
95       * Removes registered object, if exists, for the given pattern.
96       *
97       * @param pattern
98       *            the pattern to unregister.
99       */
100     @Override
101     public synchronized void unregister(final String pattern) {
102         if (pattern == null) {
103             return;
104         }
105         this.map.remove(pattern);
106     }
107 
108     /**
109      * Looks up an object matching the given request path.
110      *
111      * @param path
112      *            the request path
113      * @return object or {@code null} if no match is found.
114      */
115     @Override
116     public synchronized T lookup(final String path) {
117         Args.notNull(path, "Request path");
118         // direct match?
119         T obj = this.map.get(path);
120         if (obj == null) {
121             // pattern match?
122             String bestMatch = null;
123             for (final String pattern : this.map.keySet()) {
124                 if (matchUriRequestPattern(pattern, path)) {
125                     // we have a match. is it any better?
126                     if (bestMatch == null || (bestMatch.length() < pattern.length())
127                             || (bestMatch.length() == pattern.length() && pattern.endsWith("*"))) {
128                         obj = this.map.get(pattern);
129                         bestMatch = pattern;
130                     }
131                 }
132             }
133         }
134         return obj;
135     }
136 
137     /**
138      * Tests if the given request path matches the given pattern.
139      *
140      * @param pattern
141      *            the pattern
142      * @param path
143      *            the request path
144      * @return {@code true} if the request URI matches the pattern, {@code false} otherwise.
145      */
146     protected boolean matchUriRequestPattern(final String pattern, final String path) {
147         if (pattern.equals("*")) {
148             return true;
149         }
150         return (pattern.endsWith("*") && path.startsWith(pattern.substring(0, pattern.length() - 1)))
151                 || (pattern.startsWith("*") && path.endsWith(pattern.substring(1)));
152     }
153 
154     @Override
155     public String toString() {
156         return this.map.toString();
157     }
158 
159 }