001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * bstefanescu 018 */ 019package org.nuxeo.ecm.webengine.jaxrs.servlet.mapping; 020 021import java.util.ArrayList; 022import java.util.List; 023 024/** 025 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 026 */ 027public class PathMatcher { 028 029 public static final PathMatcher ANY = new PathMatcher(new SegmentMatcher[0]); 030 031 protected final SegmentMatcher[] matchers; 032 033 public PathMatcher(SegmentMatcher... matchers) { 034 this.matchers = matchers; 035 } 036 037 public boolean matches(String path) { 038 return matches(Path.parse(path)); 039 } 040 041 public boolean matches(Path path) { 042 // if (path.hasTrailingSpace()) { 043 // path.append("**"); 044 // } 045 if (matchers.length == 0) { 046 return true; 047 } 048 return matches(path.segments, 0, 0); 049 } 050 051 public boolean matches(String[] segments, int soff, int moff) { 052 if (soff == segments.length) { 053 // segments consumed 054 if (moff == matchers.length) { 055 // no more matchers => matched 056 return true; 057 } 058 if (moff == matchers.length - 1 && matchers[moff] == SegmentMatcher.ANY) { 059 // it remains one matcher which is any path => matched 060 return true; 061 } 062 return false; 063 } 064 if (moff == matchers.length) { 065 // no more matchers but segments not consumed 066 if (matchers[moff - 1] == SegmentMatcher.ANY) { 067 // last matcher is any path 068 return true; 069 } 070 return false; 071 } 072 SegmentMatcher m = matchers[moff]; 073 074 if (m == SegmentMatcher.ANY) { 075 // if current matcher is any path try all sub-paths until a match is 076 // found 077 for (int i = soff; i < segments.length; i++) { 078 if (matches(segments, i, moff + 1)) { 079 return true; 080 } 081 } 082 return false; 083 } 084 085 // test is the current segment is matching 086 if (!m.matches(segments[soff])) { 087 return false; // not matching 088 } 089 090 // continue iteration on segments and matchers 091 return matches(segments, soff + 1, moff + 1); 092 } 093 094 public static PathMatcher compile(String path) { 095 return compile(Path.parse(path)); 096 } 097 098 public static PathMatcher compile(Path path) { 099 // TODO handle / case 100 ArrayList<SegmentMatcher> matchers = new ArrayList<SegmentMatcher>(); 101 for (String segment : path.segments) { 102 if (segment.length() == 0) { 103 continue; 104 } 105 if ("**".equals(segment)) { 106 addAnyMatcher(matchers, SegmentMatcher.ANY); 107 } else if ("*".equals(segment)) { 108 addAnyMatcher(matchers, SegmentMatcher.ANY_SEGMENT); 109 } else if (segment.charAt(0) == '(' && segment.charAt(segment.length() - 1) == ')') { 110 matchers.add(new RegexSegmentMatcher(segment.substring(1, segment.length() - 1))); 111 } else { 112 matchers.add(createSegmentMatcher(segment)); 113 } 114 } 115 return new PathMatcher(matchers.toArray(new SegmentMatcher[matchers.size()])); 116 } 117 118 private static void addAnyMatcher(List<SegmentMatcher> matchers, SegmentMatcher matcher) { 119 if (!matchers.isEmpty() && matchers.get(matchers.size() - 1) == matcher) { 120 return; 121 } 122 matchers.add(matcher); 123 } 124 125 private static SegmentMatcher createSegmentMatcher(String segment) { 126 if (segment.indexOf('*') == -1 && segment.indexOf('?') == -1) { 127 return new ExactSegmentMatcher(segment); 128 } 129 return new WildcardSegmentMatcher(segment); 130 } 131 132 @Override 133 public String toString() { 134 if (matchers.length == 0) { 135 return "/**"; 136 } 137 StringBuilder buf = new StringBuilder(); 138 for (int i = 0; i < matchers.length; i++) { 139 buf.append("/").append(matchers[i]); 140 } 141 return buf.toString(); 142 } 143}