001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.util;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.StringUtils.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022
023import java.util.*;
024
025import org.apache.juneau.commons.collections.*;
026import org.apache.juneau.commons.utils.*;
027
028/**
029 * Represents a URL path pattern match.
030 *
031 * For example, given the pattern <js>"/foo/{bar}/*"</js> and the path <js>"/foo/123/baz/qux"</js>, this match gives
032 * you a map containing <js>"{bar:123}"</js> and a remainder string containing <js>"baz/qux"</js>.
033 *
034 */
035public class UrlPathMatch {
036
037   private final int matchedParts;
038   private final String path;
039   private final Map<String,String> vars;
040
041   /**
042    * Constructor.
043    *
044    * @param path The path being matched against.  Can be <jk>null</jk>.
045    * @param matchedParts The number of parts that were matched against the path.
046    * @param keys The variable keys.  Can be <jk>null</jk>.
047    * @param values The variable values.  Can be <jk>null</jk>.
048    */
049   protected UrlPathMatch(String path, int matchedParts, String[] keys, String[] values) {
050      this.path = path;
051      this.matchedParts = matchedParts;
052      this.vars = keys == null ? mape() : new SimpleMap<>(keys, values);
053   }
054
055   /**
056    * Returns the part of the URL that the pattern matched against.
057    *
058    * @return
059    * The part of the URL that the pattern matched against.
060    * <br>Can be <jk>null</jk> if nothing matched.
061    * <br>Otherwise, always starts with <js>'/'</js>.
062    */
063   public String getPrefix() {
064      var c = 0;
065      for (var j = 0; j < matchedParts; j++) {
066         c = path.indexOf('/', c + 1);
067         if (c == -1)
068            c = path.length();
069      }
070      return Utils.nullIfEmpty(path.substring(0, c));
071   }
072
073   /**
074    * Returns the remainder of the path after the pattern match has been made.
075    *
076    * <p>
077    * Same as {#link {@link #getSuffix()} but trims the leading slash if there is one.
078    *
079    * @return The remainder of the path after the pattern match has been made.
080    */
081   public String getRemainder() {
082      var suffix = getSuffix();
083      if (ne(suffix) && suffix.charAt(0) == '/')
084         suffix = suffix.substring(1);
085      return suffix;
086   }
087
088   /**
089    * Returns the remainder of the URL after the pattern was matched.
090    *
091    * @return
092    * The remainder of the URL after the pattern was matched.
093    * <br>Can be <jk>null</jk> if nothing remains to be matched.
094    * <br>Otherwise, always starts with <js>'/'</js>.
095    */
096   public String getSuffix() {
097      var s = path;
098      for (var j = 0; j < matchedParts; j++) {
099         var k = s.indexOf('/', 1);
100         if (k == -1)
101            return null;
102         s = s.substring(k);
103      }
104      return s;
105   }
106
107   /**
108    * Returns a map of the path variables and values.
109    *
110    * @return
111    *    An unmodifiable map of variable keys/values.
112    *    <br>Returns an empty map if no variables were found in the path.
113    */
114   public Map<String,String> getVars() { return vars; }
115
116   /**
117    * Returns <jk>true</jk> if any of the variable values are blank.
118    *
119    * @return <jk>true</jk> if any of the variable values are blank.
120    */
121   public boolean hasEmptyVars() {
122      for (var v : vars.values())
123         if (isEmpty(v))
124            return true;
125      return false;
126   }
127
128   /**
129    * Returns <jk>true</jk> if this match contains one or more variables.
130    *
131    * @return <jk>true</jk> if this match contains one or more variables.
132    */
133   public boolean hasVars() {
134      return ! vars.isEmpty();
135   }
136
137   protected FluentMap<String,Object> properties() {
138      // @formatter:off
139      return filteredBeanPropertyMap()
140         .a("r", getRemainder())
141         .a("v", getVars());
142      // @formatter:on
143   }
144
145   @Override /* Overridden from Object */
146   public String toString() {
147      return r(properties());
148   }
149}