001/* 002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * bstefanescu 011 */ 012package org.nuxeo.ecm.webengine.jaxrs.servlet.mapping; 013 014import java.util.ArrayList; 015import java.util.List; 016 017/** 018 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 019 */ 020public class PathMatcher { 021 022 public static final PathMatcher ANY = new PathMatcher(new SegmentMatcher[0]); 023 024 protected final SegmentMatcher[] matchers; 025 026 public PathMatcher(SegmentMatcher... matchers) { 027 this.matchers = matchers; 028 } 029 030 public boolean matches(String path) { 031 return matches(Path.parse(path)); 032 } 033 034 public boolean matches(Path path) { 035 // if (path.hasTrailingSpace()) { 036 // path.append("**"); 037 // } 038 if (matchers.length == 0) { 039 return true; 040 } 041 return matches(path.segments, 0, 0); 042 } 043 044 public boolean matches(String[] segments, int soff, int moff) { 045 if (soff == segments.length) { 046 // segments consumed 047 if (moff == matchers.length) { 048 // no more matchers => matched 049 return true; 050 } 051 if (moff == matchers.length - 1 && matchers[moff] == SegmentMatcher.ANY) { 052 // it remains one matcher which is any path => matched 053 return true; 054 } 055 return false; 056 } 057 if (moff == matchers.length) { 058 // no more matchers but segments not consumed 059 if (matchers[moff - 1] == SegmentMatcher.ANY) { 060 // last matcher is any path 061 return true; 062 } 063 return false; 064 } 065 SegmentMatcher m = matchers[moff]; 066 067 if (m == SegmentMatcher.ANY) { 068 // if current matcher is any path try all sub-paths until a match is 069 // found 070 for (int i = soff; i < segments.length; i++) { 071 if (matches(segments, i, moff + 1)) { 072 return true; 073 } 074 } 075 return false; 076 } 077 078 // test is the current segment is matching 079 if (!m.matches(segments[soff])) { 080 return false; // not matching 081 } 082 083 // continue iteration on segments and matchers 084 return matches(segments, soff + 1, moff + 1); 085 } 086 087 public static PathMatcher compile(String path) { 088 return compile(Path.parse(path)); 089 } 090 091 public static PathMatcher compile(Path path) { 092 // TODO handle / case 093 ArrayList<SegmentMatcher> matchers = new ArrayList<SegmentMatcher>(); 094 for (String segment : path.segments) { 095 if (segment.length() == 0) { 096 continue; 097 } 098 if ("**".equals(segment)) { 099 addAnyMatcher(matchers, SegmentMatcher.ANY); 100 } else if ("*".equals(segment)) { 101 addAnyMatcher(matchers, SegmentMatcher.ANY_SEGMENT); 102 } else if (segment.charAt(0) == '(' && segment.charAt(segment.length() - 1) == ')') { 103 matchers.add(new RegexSegmentMatcher(segment.substring(1, segment.length() - 1))); 104 } else { 105 matchers.add(createSegmentMatcher(segment)); 106 } 107 } 108 return new PathMatcher(matchers.toArray(new SegmentMatcher[matchers.size()])); 109 } 110 111 private static void addAnyMatcher(List<SegmentMatcher> matchers, SegmentMatcher matcher) { 112 if (!matchers.isEmpty() && matchers.get(matchers.size() - 1) == matcher) { 113 return; 114 } 115 matchers.add(matcher); 116 } 117 118 private static SegmentMatcher createSegmentMatcher(String segment) { 119 if (segment.indexOf('*') == -1 && segment.indexOf('?') == -1) { 120 return new ExactSegmentMatcher(segment); 121 } 122 return new WildcardSegmentMatcher(segment); 123 } 124 125 @Override 126 public String toString() { 127 if (matchers.length == 0) { 128 return "/**"; 129 } 130 StringBuilder buf = new StringBuilder(); 131 for (int i = 0; i < matchers.length; i++) { 132 buf.append("/").append(matchers[i]); 133 } 134 return buf.toString(); 135 } 136}