001 /** 002 * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved. 003 * 004 * This library is free software; you can redistribute it and/or modify it under 005 * the terms of the GNU Lesser General Public License as published by the Free 006 * Software Foundation; either version 2.1 of the License, or (at your option) 007 * any later version. 008 * 009 * This library is distributed in the hope that it will be useful, but WITHOUT 010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 011 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 012 * details. 013 */ 014 015 package com.liferay.portal.kernel.util; 016 017 import java.util.ArrayList; 018 import java.util.List; 019 import java.util.Map; 020 import java.util.regex.Matcher; 021 import java.util.regex.Pattern; 022 023 /** 024 * Parses strings into parameter maps and vice versa. 025 * 026 * @author Connor McKay 027 * @author Brian Wing Shun Chan 028 * @see com.liferay.portal.kernel.portlet.Route 029 * @see Pattern 030 */ 031 public class StringParser { 032 033 /** 034 * Escapes the special characters in the string so that they will have no 035 * special meaning in a regular expression. 036 * 037 * <p> 038 * This method differs from {@link Pattern#quote(String)} by escaping each 039 * special character with a backslash, rather than enclosing the entire 040 * string in special quote tags. This allows the escaped string to be 041 * manipulated or have sections replaced with non-literal sequences. 042 * </p> 043 * 044 * @param s the string to escape 045 * @return the escaped string 046 */ 047 public static String escapeRegex(String s) { 048 Matcher matcher = _escapeRegexPattern.matcher(s); 049 050 return matcher.replaceAll("\\\\$0"); 051 } 052 053 /** 054 * Constructs a new string parser from the pattern. 055 * 056 * <p> 057 * The pattern can be any string containing named fragments in brackets. The 058 * following is a valid pattern for greeting: 059 * </p> 060 * 061 * <pre> 062 * <code> 063 * Hi {name}! How are you? 064 * </code> 065 * </pre> 066 * 067 * <p> 068 * This pattern would match the string "Hi Tom! How are you?". The 069 * format of a fragment may optionally be specified by inserting a colon 070 * followed by a regular expression after the fragment name. For instance, 071 * <code>name</code> could be set to match only lower case letters with the 072 * following: 073 * </p> 074 * 075 * <pre> 076 * <code> 077 * Hi {name:[a-z]+}! How are you? 078 * </code> 079 * </pre> 080 * 081 * <p> 082 * By default, a fragment will match anything except a forward slash or a 083 * period. 084 * </p> 085 * 086 * <p> 087 * If a string parser is set to encode fragments using a {@link 088 * StringEncoder}, an individual fragment can be specified as raw by 089 * prefixing its name with a percent sign, as shown below: 090 * </p> 091 * 092 * <pre> 093 * <code> 094 * /view_page/{%path:.*} 095 * </code> 096 * </pre> 097 * 098 * <p> 099 * The format of the path fragment has also been specified to match anything 100 * using the pattern ".*". This pattern could be used to parse the 101 * string: 102 * </p> 103 * 104 * <pre> 105 * <code> 106 * /view_page/root/home/mysite/pages/index.htm 107 * </code> 108 * </pre> 109 * 110 * <p> 111 * <code>path</code> would be set to 112 * "root/home/mysite/pages/index.htm", even if {@link 113 * URLStringEncoder} had been set as the string encoder. 114 * </p> 115 * 116 * <p> 117 * <b>Do not include capturing subgroups in the pattern.</b> 118 * </p> 119 * 120 * 121 * @param pattern the pattern string 122 */ 123 public StringParser(String pattern) { 124 _builder = pattern; 125 126 String regex = escapeRegex(pattern); 127 128 Matcher matcher = _fragmentPattern.matcher(pattern); 129 130 while (matcher.find()) { 131 String chunk = matcher.group(); 132 133 StringParserFragment stringParserFragment = 134 new StringParserFragment(chunk); 135 136 _stringParserFragments.add(stringParserFragment); 137 138 _builder = _builder.replace(chunk, stringParserFragment.getToken()); 139 140 regex = regex.replace( 141 escapeRegex(chunk), 142 StringPool.OPEN_PARENTHESIS.concat( 143 stringParserFragment.getPattern().concat( 144 StringPool.CLOSE_PARENTHESIS))); 145 } 146 147 _pattern = Pattern.compile(regex); 148 } 149 150 /** 151 * Builds a string from the parameter map if this parser is appropriate. 152 * 153 * <p> 154 * A parser is appropriate if each parameter matches the format of its 155 * accompanying fragment. 156 * </p> 157 * 158 * <p> 159 * If this parser is appropriate, all the parameters used in the pattern 160 * will be removed from the parameter map. If this parser is not 161 * appropriate, the parameter map will not be modified. 162 * </p> 163 * 164 * @param parameters the parameter map to build the string from 165 * @return the string, or <code>null</code> if this parser is not 166 * appropriate 167 */ 168 public String build(Map<String, String> parameters) { 169 String s = _builder; 170 171 for (StringParserFragment stringParserFragment : 172 _stringParserFragments) { 173 174 String value = parameters.get(stringParserFragment.getName()); 175 176 if (value == null) { 177 return null; 178 } 179 180 if ((_stringEncoder != null) && !stringParserFragment.isRaw()) { 181 value = _stringEncoder.encode(value); 182 } 183 184 if (!stringParserFragment.matches(value)) { 185 return null; 186 } 187 188 s = s.replace(stringParserFragment.getToken(), value); 189 } 190 191 for (StringParserFragment stringParserFragment : 192 _stringParserFragments) { 193 194 parameters.remove(stringParserFragment.getName()); 195 } 196 197 return s; 198 } 199 200 /** 201 * Populates the parameter map with values parsed from the string if this 202 * parser matches. 203 * 204 * @param s the string to parse 205 * @param parameters the parameter map to populate if this parser matches 206 * the string 207 * @return <code>true</code> if this parser matches; <code>false</code> 208 * otherwise 209 */ 210 public boolean parse(String s, Map<String, String> parameters) { 211 Matcher matcher = _pattern.matcher(s); 212 213 if (!matcher.matches()) { 214 return false; 215 } 216 217 for (int i = 1; i <= _stringParserFragments.size(); i++) { 218 StringParserFragment stringParserFragment = 219 _stringParserFragments.get(i - 1); 220 221 String value = matcher.group(i); 222 223 if ((_stringEncoder != null) && !stringParserFragment.isRaw()) { 224 value = _stringEncoder.decode(value); 225 } 226 227 parameters.put(stringParserFragment.getName(), value); 228 } 229 230 return true; 231 } 232 233 /** 234 * Sets the string encoder to use for parsing or building a string. 235 * 236 * <p> 237 * The string encoder will not be used for fragments marked as raw. A 238 * fragment can be marked as raw by prefixing its name with a percent sign. 239 * </p> 240 * 241 * @param stringEncoder the string encoder to use for parsing or building a 242 * string 243 * @see StringEncoder 244 */ 245 public void setStringEncoder(StringEncoder stringEncoder) { 246 _stringEncoder = stringEncoder; 247 } 248 249 private static Pattern _escapeRegexPattern = Pattern.compile( 250 "[\\{\\}\\(\\)\\[\\]\\*\\+\\?\\$\\^\\.\\#\\\\]"); 251 private static Pattern _fragmentPattern = Pattern.compile("\\{.+?\\}"); 252 253 private String _builder; 254 private Pattern _pattern; 255 private StringEncoder _stringEncoder; 256 private List<StringParserFragment> _stringParserFragments = 257 new ArrayList<StringParserFragment>(); 258 259 }