All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.undertow.util.PathTemplateMatcher Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package io.undertow.util;

import io.undertow.UndertowMessages;

import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;

/**
 * Utility class that provides fast path matching of path templates. Templates are stored in a map based on the stem of the template,
 * and matches longest stem first.
 * 

* TODO: we can probably do this faster using a trie type structure, but I think the current impl should perform ok most of the time * * @author Stuart Douglas */ public class PathTemplateMatcher { /** * Map of path template stem to the path templates that share the same base. */ private Map> pathTemplateMap = new CopyOnWriteMap<>(); /** * lengths of all registered paths */ private volatile int[] lengths = {}; public PathMatchResult match(final String path) { String normalizedPath = "".equals(path) ? "/" : path; if(!normalizedPath.startsWith("/")) normalizedPath = "/"+ normalizedPath; final Map params = new LinkedHashMap<>(); int length = normalizedPath.length(); final int[] lengths = this.lengths; for (int i = 0; i < lengths.length; ++i) { int pathLength = lengths[i]; if (pathLength == length) { Set entry = pathTemplateMap.get(normalizedPath); if (entry != null) { PathMatchResult res = handleStemMatch(entry, normalizedPath, params); if (res != null) { return res; } } } else if (pathLength < length) { String part = normalizedPath.substring(0, pathLength); Set entry = pathTemplateMap.get(part); if (entry != null) { PathMatchResult res = handleStemMatch(entry, normalizedPath, params); if (res != null) { return res; } } } } return null; } private PathMatchResult handleStemMatch(Set entry, final String path, final Map params) { for (PathTemplateHolder val : entry) { if (val.template.matches(path, params)) { return new PathMatchResult<>(params, val.template.getTemplateString(), val.value); } else { params.clear(); } } return null; } public synchronized PathTemplateMatcher add(final PathTemplate template, final T value) { Set values = pathTemplateMap.get(trimBase(template)); Set newValues; if (values == null) { newValues = new TreeSet<>(); } else { newValues = new TreeSet<>(values); } PathTemplateHolder holder = new PathTemplateHolder(value, template); if (newValues.contains(holder)) { PathTemplate equivalent = null; for (PathTemplateHolder item : newValues) { if (item.compareTo(holder) == 0) { equivalent = item.template; break; } } throw UndertowMessages.MESSAGES.matcherAlreadyContainsTemplate(template.getTemplateString(), equivalent.getTemplateString()); } newValues.add(holder); pathTemplateMap.put(trimBase(template), newValues); buildLengths(); return this; } private String trimBase(PathTemplate template) { String retval = template.getBase(); if (template.getBase().endsWith("/") && !template.getParameterNames().isEmpty()) { return retval.substring(0, retval.length() - 1); } if (retval.endsWith("*")) { return retval.substring(0, retval.length() - 1); } return retval; } private void buildLengths() { final Set lengths = new TreeSet<>(new Comparator() { @Override public int compare(Integer o1, Integer o2) { return -o1.compareTo(o2); } }); for (String p : pathTemplateMap.keySet()) { lengths.add(p.length()); } int[] lengthArray = new int[lengths.size()]; int pos = 0; for (int i : lengths) { lengthArray[pos++] = i; //-1 because the base paths end with a / } this.lengths = lengthArray; } public synchronized PathTemplateMatcher add(final String pathTemplate, final T value) { final PathTemplate template = PathTemplate.create(pathTemplate); return add(template, value); } public synchronized PathTemplateMatcher addAll(PathTemplateMatcher pathTemplateMatcher) { for (Entry> entry : pathTemplateMatcher.getPathTemplateMap().entrySet()) { for (PathTemplateHolder pathTemplateHolder : entry.getValue()) { add(pathTemplateHolder.template, pathTemplateHolder.value); } } return this; } Map> getPathTemplateMap() { return pathTemplateMap; } public Set getPathTemplates() { Set templates = new HashSet<>(); for (Set holders : pathTemplateMap.values()) { for (PathTemplateHolder holder: holders) { templates.add(holder.template); } } return templates; } public synchronized PathTemplateMatcher remove(final String pathTemplate) { final PathTemplate template = PathTemplate.create(pathTemplate); return remove(template); } private synchronized PathTemplateMatcher remove(PathTemplate template) { Set values = pathTemplateMap.get(trimBase(template)); Set newValues; if (values == null) { return this; } else { newValues = new TreeSet<>(values); } Iterator it = newValues.iterator(); while (it.hasNext()) { PathTemplateHolder next = it.next(); if (next.template.getTemplateString().equals(template.getTemplateString())) { it.remove(); break; } } if (newValues.size() == 0) { pathTemplateMap.remove(trimBase(template)); } else { pathTemplateMap.put(trimBase(template), newValues); } buildLengths(); return this; } public synchronized T get(String template) { PathTemplate pathTemplate = PathTemplate.create(template); Set values = pathTemplateMap.get(trimBase(pathTemplate)); if(values == null) { return null; } for (PathTemplateHolder next : values) { if (next.template.getTemplateString().equals(template)) { return next.value; } } return null; } public static class PathMatchResult extends PathTemplateMatch { private final T value; public PathMatchResult(Map parameters, String matchedTemplate, T value) { super(matchedTemplate, parameters); this.value = value; } public T getValue() { return value; } } private final class PathTemplateHolder implements Comparable { final T value; final PathTemplate template; private PathTemplateHolder(T value, PathTemplate template) { this.value = value; this.template = template; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if (!PathTemplateHolder.class.equals(o.getClass())) return false; PathTemplateHolder that = (PathTemplateHolder) o; return template.equals(that.template); } @Override public int hashCode() { return template.hashCode(); } @Override public int compareTo(PathTemplateHolder o) { return template.compareTo(o.template); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy