001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.protocol;
029
030import java.util.HashMap;
031import java.util.Map;
032
033import org.apache.http.annotation.ThreadingBehavior;
034import org.apache.http.annotation.Contract;
035import org.apache.http.util.Args;
036
037/**
038 * Maintains a map of objects keyed by a request URI pattern.
039 * <br>
040 * Patterns may have three formats:
041 * <ul>
042 *   <li>{@code *}</li>
043 *   <li>{@code *&lt;uri&gt;}</li>
044 *   <li>{@code &lt;uri&gt;*}</li>
045 * </ul>
046 * <br>
047 * This class can be used to resolve an object matching a particular request
048 * URI.
049 *
050 * @since 4.0
051 */
052@Contract(threading = ThreadingBehavior.SAFE)
053public class UriPatternMatcher<T> {
054
055    private final Map<String, T> map;
056
057    public UriPatternMatcher() {
058        super();
059        this.map = new HashMap<String, T>();
060    }
061
062    /**
063     * Registers the given object for URIs matching the given pattern.
064     *
065     * @param pattern the pattern to register the handler for.
066     * @param obj the object.
067     */
068    public synchronized void register(final String pattern, final T obj) {
069        Args.notNull(pattern, "URI request pattern");
070        this.map.put(pattern, obj);
071    }
072
073    /**
074     * Removes registered object, if exists, for the given pattern.
075     *
076     * @param pattern the pattern to unregister.
077     */
078    public synchronized void unregister(final String pattern) {
079        if (pattern == null) {
080            return;
081        }
082        this.map.remove(pattern);
083    }
084
085    /**
086     * @deprecated (4.1) do not use
087     */
088    @Deprecated
089    public synchronized void setHandlers(final Map<String, T> map) {
090        Args.notNull(map, "Map of handlers");
091        this.map.clear();
092        this.map.putAll(map);
093    }
094
095    /**
096     * @deprecated (4.1) do not use
097     */
098    @Deprecated
099    public synchronized void setObjects(final Map<String, T> map) {
100        Args.notNull(map, "Map of handlers");
101        this.map.clear();
102        this.map.putAll(map);
103    }
104
105    /**
106     * @deprecated (4.1) do not use
107     */
108    @Deprecated
109    public synchronized Map<String, T> getObjects() {
110        return this.map;
111    }
112
113    /**
114     * Looks up an object matching the given request path.
115     *
116     * @param path the request path
117     * @return object or {@code null} if no match is found.
118     */
119    public synchronized T lookup(final String path) {
120        Args.notNull(path, "Request path");
121        // direct match?
122        T obj = this.map.get(path);
123        if (obj == null) {
124            // pattern match?
125            String bestMatch = null;
126            for (final String pattern : this.map.keySet()) {
127                if (matchUriRequestPattern(pattern, path)) {
128                    // we have a match. is it any better?
129                    if (bestMatch == null
130                            || (bestMatch.length() < pattern.length())
131                            || (bestMatch.length() == pattern.length() && pattern.endsWith("*"))) {
132                        obj = this.map.get(pattern);
133                        bestMatch = pattern;
134                    }
135                }
136            }
137        }
138        return obj;
139    }
140
141    /**
142     * Tests if the given request path matches the given pattern.
143     *
144     * @param pattern the pattern
145     * @param path the request path
146     * @return {@code true} if the request URI matches the pattern,
147     *   {@code false} otherwise.
148     */
149    protected boolean matchUriRequestPattern(final String pattern, final String path) {
150        if (pattern.equals("*")) {
151            return true;
152        } else {
153            return
154            (pattern.endsWith("*") && path.startsWith(pattern.substring(0, pattern.length() - 1))) ||
155            (pattern.startsWith("*") && path.endsWith(pattern.substring(1, pattern.length())));
156        }
157    }
158
159    @Override
160    public String toString() {
161        return this.map.toString();
162    }
163
164}