Java tutorial
// ======================================================================== // $Id: PathMap.java,v 1.25 2005/08/13 00:01:24 gregwilkins Exp $ // Copyright 1999-2004 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // 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 org.openqa.jetty.http; import java.io.Externalizable; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.apache.commons.logging.Log; import org.openqa.jetty.log.LogFactory; import org.openqa.jetty.util.LazyList; import org.openqa.jetty.util.SingletonList; import org.openqa.jetty.util.StringMap; /* ------------------------------------------------------------ */ /** URI path map to Object. * This mapping implements the path specification recommended * in the 2.2 Servlet API. * * Path specifications can be of the following forms:<PRE> * /foo/bar - an exact path specification. * /foo/* - a prefix path specification (must end '/*'). * *.ext - a suffix path specification. * / - the default path specification. * </PRE> * Matching is performed in the following order <NL> * <LI>Exact match. * <LI>Longest prefix match. * <LI>Longest suffix match. * <LI>default. * </NL> * Multiple path specifications can be mapped by providing a list of * specifications. The list is separated by the characters specified * in the "org.openqa.jetty.http.PathMap.separators" System property, which * defaults to : * <P> * Note that this is a very different mapping to that provided by PathMap * in Jetty2. * <P> * This class is not synchronized for get's. If concurrent modifications are * possible then it should be synchronized at a higher level. * * @version $Id: PathMap.java,v 1.25 2005/08/13 00:01:24 gregwilkins Exp $ * @author Greg Wilkins (gregw) */ public class PathMap extends HashMap implements Externalizable { private static Log log = LogFactory.getLog(PathMap.class); /* ------------------------------------------------------------ */ private static String __pathSpecSeparators = System.getProperty("org.openqa.jetty.http.PathMap.separators", ":,"); /* ------------------------------------------------------------ */ /** Set the path spec separator. * Multiple path specification may be included in a single string * if they are separated by the characters set in this string. * The default value is ":," or whatever has been set by the * system property org.openqa.jetty.http.PathMap.separators * @param s separators */ public static void setPathSpecSeparators(String s) { __pathSpecSeparators = s; } /* --------------------------------------------------------------- */ StringMap _prefixMap = new StringMap(); StringMap _suffixMap = new StringMap(); StringMap _exactMap = new StringMap(); List _defaultSingletonList = null; Map.Entry _prefixDefault = null; Map.Entry _default = null; Set _entrySet; boolean _nodefault = false; /* --------------------------------------------------------------- */ /** Construct empty PathMap. */ public PathMap() { super(11); _entrySet = entrySet(); } /* --------------------------------------------------------------- */ /** Construct empty PathMap. */ public PathMap(boolean nodefault) { super(11); _entrySet = entrySet(); _nodefault = nodefault; } /* --------------------------------------------------------------- */ /** Construct empty PathMap. */ public PathMap(int capacity) { super(capacity); _entrySet = entrySet(); } /* --------------------------------------------------------------- */ /** Construct from dictionary PathMap. */ public PathMap(Map m) { putAll(m); _entrySet = entrySet(); } /* ------------------------------------------------------------ */ public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException { HashMap map = new HashMap(this); out.writeObject(map); } /* ------------------------------------------------------------ */ public void readExternal(java.io.ObjectInput in) throws java.io.IOException, ClassNotFoundException { HashMap map = (HashMap) in.readObject(); this.putAll(map); } /* --------------------------------------------------------------- */ /** Add a single path match to the PathMap. * @param pathSpec The path specification, or comma separated list of * path specifications. * @param object The object the path maps to */ public synchronized Object put(Object pathSpec, Object object) { StringTokenizer tok = new StringTokenizer(pathSpec.toString(), __pathSpecSeparators); Object old = null; while (tok.hasMoreTokens()) { String spec = tok.nextToken(); if (!spec.startsWith("/") && !spec.startsWith("*.")) { log.warn("PathSpec " + spec + ". must start with '/' or '*.'"); spec = "/" + spec; } old = super.put(spec, object); // Make entry that was just created. Entry entry = new Entry(spec, object); if (entry.getKey().equals(spec)) { if (spec.equals("/*")) _prefixDefault = entry; else if (spec.endsWith("/*")) { _prefixMap.put(spec.substring(0, spec.length() - 2), entry); _exactMap.put(spec.substring(0, spec.length() - 1), entry); _exactMap.put(spec.substring(0, spec.length() - 2), entry); } else if (spec.startsWith("*.")) _suffixMap.put(spec.substring(2), entry); else if (spec.equals("/")) { if (_nodefault) _exactMap.put(spec, entry); else { _default = entry; _defaultSingletonList = SingletonList.newSingletonList(_default); } } else _exactMap.put(spec, entry); } } return old; } /* ------------------------------------------------------------ */ /** Get object matched by the path. * @param path the path. * @return Best matched object or null. */ public Object match(String path) { Map.Entry entry = getMatch(path); if (entry != null) return entry.getValue(); return null; } /* --------------------------------------------------------------- */ /** Get the entry mapped by the best specification. * @param path the path. * @return Map.Entry of the best matched or null. */ public Map.Entry getMatch(String path) { Map.Entry entry; if (path == null) return null; int l = path.indexOf(';'); if (l < 0) { l = path.indexOf('?'); if (l < 0) l = path.length(); } // try exact match entry = _exactMap.getEntry(path, 0, l); if (entry != null) return (Map.Entry) entry.getValue(); // prefix search int i = l; while ((i = path.lastIndexOf('/', i - 1)) >= 0) { entry = _prefixMap.getEntry(path, 0, i); if (entry != null) return (Map.Entry) entry.getValue(); } // Prefix Default if (_prefixDefault != null) return _prefixDefault; // Extension search i = 0; while ((i = path.indexOf('.', i + 1)) > 0) { entry = _suffixMap.getEntry(path, i + 1, l - i - 1); if (entry != null) return (Map.Entry) entry.getValue(); } // Default return _default; } /* --------------------------------------------------------------- */ /** Get all entries matched by the path. * Best match first. * @param path Path to match * @return List of Map.Entry instances key=pathSpec */ public List getMatches(String path) { Map.Entry entry; Object entries = null; if (path == null) return LazyList.getList(entries); int l = path.indexOf(';'); if (l < 0) { l = path.indexOf('?'); if (l < 0) l = path.length(); } // try exact match entry = _exactMap.getEntry(path, 0, l); if (entry != null) entries = LazyList.add(entries, entry.getValue()); // prefix search int i = l - 1; while ((i = path.lastIndexOf('/', i - 1)) >= 0) { entry = _prefixMap.getEntry(path, 0, i); if (entry != null) entries = LazyList.add(entries, entry.getValue()); } // Prefix Default if (_prefixDefault != null) entries = LazyList.add(entries, _prefixDefault); // Extension search i = 0; while ((i = path.indexOf('.', i + 1)) > 0) { entry = _suffixMap.getEntry(path, i + 1, l - i - 1); if (entry != null) entries = LazyList.add(entries, entry.getValue()); } // Default if (_default != null) { // Optimization for just the default if (entries == null) return _defaultSingletonList; entries = LazyList.add(entries, _default); } return LazyList.getList(entries); } /* --------------------------------------------------------------- */ public synchronized Object remove(Object pathSpec) { if (pathSpec != null) { String spec = (String) pathSpec; if (spec.equals("/*")) _prefixDefault = null; else if (spec.endsWith("/*")) { _prefixMap.remove(spec.substring(0, spec.length() - 2)); _exactMap.remove(spec.substring(0, spec.length() - 1)); _exactMap.remove(spec.substring(0, spec.length() - 2)); } else if (spec.startsWith("*.")) _suffixMap.remove(spec.substring(2)); else if (spec.equals("/")) { _default = null; _defaultSingletonList = null; } else _exactMap.remove(spec); } return super.remove(pathSpec); } /* --------------------------------------------------------------- */ public void clear() { _exactMap = new StringMap(); _prefixMap = new StringMap(); _suffixMap = new StringMap(); _default = null; _defaultSingletonList = null; super.clear(); } /* --------------------------------------------------------------- */ /** * @return true if match. */ public static boolean match(String pathSpec, String path) throws IllegalArgumentException { char c = pathSpec.charAt(0); if (c == '/') { if (pathSpec.length() == 1 || pathSpec.equals(path)) return true; if (pathSpec.endsWith("/*") && pathSpec.regionMatches(0, path, 0, pathSpec.length() - 2)) return true; if (path.startsWith(pathSpec) && path.charAt(pathSpec.length()) == ';') return true; } else if (c == '*') return path.regionMatches(path.length() - pathSpec.length() + 1, pathSpec, 1, pathSpec.length() - 1); return false; } /* --------------------------------------------------------------- */ /** * @return true if match. */ public static boolean match(String pathSpec, String path, boolean noDefault) throws IllegalArgumentException { char c = pathSpec.charAt(0); if (c == '/') { if (!noDefault && pathSpec.length() == 1 || pathSpec.equals(path)) return true; if (pathSpec.endsWith("/*") && pathSpec.regionMatches(0, path, 0, pathSpec.length() - 2)) return true; if (path.startsWith(pathSpec) && path.charAt(pathSpec.length()) == ';') return true; } else if (c == '*') return path.regionMatches(path.length() - pathSpec.length() + 1, pathSpec, 1, pathSpec.length() - 1); return false; } /* --------------------------------------------------------------- */ /** Return the portion of a path that matches a path spec. * @return null if no match at all. */ public static String pathMatch(String pathSpec, String path) { char c = pathSpec.charAt(0); if (c == '/') { if (pathSpec.length() == 1) return path; if (pathSpec.equals(path)) return path; if (pathSpec.endsWith("/*") && pathSpec.regionMatches(0, path, 0, pathSpec.length() - 2)) return path.substring(0, pathSpec.length() - 2); if (path.startsWith(pathSpec) && path.charAt(pathSpec.length()) == ';') return path; } else if (c == '*') { if (path.regionMatches(path.length() - (pathSpec.length() - 1), pathSpec, 1, pathSpec.length() - 1)) return path; } return null; } /* --------------------------------------------------------------- */ /** Return the portion of a path that is after a path spec. * @return The path info string */ public static String pathInfo(String pathSpec, String path) { char c = pathSpec.charAt(0); if (c == '/') { if (pathSpec.length() == 1) return null; if (pathSpec.equals(path)) return null; if (pathSpec.endsWith("/*") && pathSpec.regionMatches(0, path, 0, pathSpec.length() - 2)) { if (path.length() == pathSpec.length() - 2) return null; return path.substring(pathSpec.length() - 2); } } return null; } /* ------------------------------------------------------------ */ /** Relative path. * @param base The base the path is relative to. * @param pathSpec The spec of the path segment to ignore. * @param path the additional path * @return base plus path with pathspec removed */ public static String relativePath(String base, String pathSpec, String path) { String info = pathInfo(pathSpec, path); if (info == null) info = path; if (info.startsWith("./")) info = info.substring(2); if (base.endsWith("/")) if (info.startsWith("/")) path = base + info.substring(1); else path = base + info; else if (info.startsWith("/")) path = base + info; else path = base + "/" + info; return path; } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ private static class Entry implements Map.Entry { private Object key; private Object value; private transient String string; Entry(Object key, Object value) { this.key = key; this.value = value; } public Object getKey() { return key; } public Object getValue() { return value; } public Object setValue(Object o) { throw new UnsupportedOperationException(); } public String toString() { if (string == null) string = key + "=" + value; return string; } } }