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}