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.server.util; 018 019import static java.util.Collections.*; 020import static org.apache.juneau.commons.utils.CollectionUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022import static org.apache.juneau.commons.utils.StringUtils.isEmpty; 023 024import java.util.*; 025 026import org.apache.juneau.commons.collections.*; 027import org.apache.juneau.commons.utils.*; 028 029/** 030 * Represents a URL path pattern match. 031 * 032 * For example, given the pattern <js>"/foo/{bar}/*"</js> and the path <js>"/foo/123/baz/qux"</js>, this match gives 033 * you a map containing <js>"{bar:123}"</js> and a remainder string containing <js>"baz/qux"</js>. 034 * 035 */ 036@SuppressWarnings({ 037 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 038}) 039public class UrlPathMatch { 040 041 private final int matchedParts; 042 private final String path; 043 private final Map<String,String> vars; 044 045 /** 046 * Constructor. 047 * 048 * @param path The path being matched against. Must not be <jk>null</jk> (a <jk>null</jk> argument causes {@link #getPrefix()}/{@link #getSuffix()} to throw {@link NullPointerException}). 049 * @param matchedParts The number of parts that were matched against the path. 050 * @param keys The variable keys. Can be <jk>null</jk> (no path variables are recorded; the variable map resolves to empty). 051 * @param values The variable values. Can be <jk>null</jk> if <c>keys</c> is also <jk>null</jk>; otherwise must align positionally with <c>keys</c>. 052 */ 053 protected UrlPathMatch(String path, int matchedParts, String[] keys, String[] values) { 054 this.path = path; 055 this.matchedParts = matchedParts; 056 this.vars = keys == null ? emptyMap() : new SimpleMap<>(keys, values); 057 } 058 059 /** 060 * Returns the part of the URL that the pattern matched against. 061 * 062 * @return 063 * The part of the URL that the pattern matched against. 064 * <br>Can be <jk>null</jk> if nothing matched. 065 * <br>Otherwise, always starts with <js>'/'</js>. 066 */ 067 public String getPrefix() { 068 var c = 0; 069 for (var j = 0; j < matchedParts; j++) { 070 c = path.indexOf('/', c + 1); 071 if (c == -1) 072 c = path.length(); 073 } 074 return StringUtils.nullIfEmpty(path.substring(0, c)); 075 } 076 077 /** 078 * Returns the remainder of the path after the pattern match has been made. 079 * 080 * <p> 081 * Same as {#link {@link #getSuffix()} but trims the leading slash if there is one. 082 * 083 * @return The remainder of the path after the pattern match has been made. 084 */ 085 public String getRemainder() { 086 var suffix = getSuffix(); 087 if (ine(suffix) && suffix.charAt(0) == '/') 088 suffix = suffix.substring(1); 089 return suffix; 090 } 091 092 /** 093 * Returns the remainder of the URL after the pattern was matched. 094 * 095 * @return 096 * The remainder of the URL after the pattern was matched. 097 * <br>Can be <jk>null</jk> if nothing remains to be matched. 098 * <br>Otherwise, always starts with <js>'/'</js>. 099 */ 100 public String getSuffix() { 101 var s = path; 102 for (var j = 0; j < matchedParts; j++) { 103 var k = s.indexOf('/', 1); 104 if (k == -1) 105 return null; 106 s = s.substring(k); 107 } 108 return s; 109 } 110 111 /** 112 * Returns a map of the path variables and values. 113 * 114 * @return 115 * An unmodifiable map of variable keys/values. 116 * <br>Returns an empty map if no variables were found in the path. 117 */ 118 public Map<String,String> getVars() { return vars; } 119 120 /** 121 * Returns <jk>true</jk> if any of the variable values are blank. 122 * 123 * @return <jk>true</jk> if any of the variable values are blank. 124 */ 125 public boolean hasEmptyVars() { 126 for (var v : vars.values()) 127 if (isEmpty(v)) 128 return true; 129 return false; 130 } 131 132 /** 133 * Returns <jk>true</jk> if this match contains one or more variables. 134 * 135 * @return <jk>true</jk> if this match contains one or more variables. 136 */ 137 public boolean hasVars() { 138 return ! vars.isEmpty(); 139 } 140 141 protected FluentMap<String,Object> properties() { 142 // @formatter:off 143 return filteredBeanPropertyMap() 144 .a("r", getRemainder()) 145 .a("v", getVars()); 146 // @formatter:on 147 } 148 149 @Override /* Overridden from Object */ 150 public String toString() { 151 return r(properties()); 152 } 153}