001 /** 002 * Copyright (c) 2000-2013 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 * <p> 062 * <pre> 063 * <code> 064 * Hi {name}! How are you? 065 * </code> 066 * </pre> 067 * </p> 068 * 069 * <p> 070 * This pattern would match the string "Hi Tom! How are you?". The 071 * format of a fragment may optionally be specified by inserting a colon 072 * followed by a regular expression after the fragment name. For instance, 073 * <code>name</code> could be set to match only lower case letters with the 074 * following: 075 * </p> 076 * 077 * <p> 078 * <pre> 079 * <code> 080 * Hi {name:[a-z]+}! How are you? 081 * </code> 082 * </pre> 083 * </p> 084 * 085 * <p> 086 * By default, a fragment will match anything except a forward slash or a 087 * period. 088 * </p> 089 * 090 * <p> 091 * If a string parser is set to encode fragments using a {@link 092 * StringEncoder}, an individual fragment can be specified as raw by 093 * prefixing its name with a percent sign, as shown below: 094 * </p> 095 * 096 * <p> 097 * <pre> 098 * <code> 099 * /view_page/{%path:.*} 100 * </code> 101 * </pre> 102 * </p> 103 * 104 * <p> 105 * The format of the path fragment has also been specified to match anything 106 * using the pattern ".*". This pattern could be used to parse the 107 * string: 108 * </p> 109 * 110 * <p> 111 * <pre> 112 * <code> 113 * /view_page/root/home/mysite/pages/index.htm 114 * </code> 115 * </pre> 116 * </p> 117 * 118 * <p> 119 * <code>path</code> would be set to 120 * "root/home/mysite/pages/index.htm", even if {@link 121 * URLStringEncoder} had been set as the string encoder. 122 * </p> 123 * 124 * <p> 125 * <b>Do not include capturing subgroups in the pattern.</b> 126 * </p> 127 * 128 * @param pattern the pattern string 129 */ 130 public StringParser(String pattern) { 131 _builder = pattern; 132 133 String regex = escapeRegex(pattern); 134 135 Matcher matcher = _fragmentPattern.matcher(pattern); 136 137 while (matcher.find()) { 138 String chunk = matcher.group(); 139 140 StringParserFragment stringParserFragment = 141 new StringParserFragment(chunk); 142 143 _stringParserFragments.add(stringParserFragment); 144 145 _builder = _builder.replace(chunk, stringParserFragment.getToken()); 146 147 regex = regex.replace( 148 escapeRegex(chunk), 149 StringPool.OPEN_PARENTHESIS.concat( 150 stringParserFragment.getPattern().concat( 151 StringPool.CLOSE_PARENTHESIS))); 152 } 153 154 _pattern = Pattern.compile(regex); 155 } 156 157 /** 158 * Builds a string from the parameter map if this parser is appropriate. 159 * 160 * <p> 161 * A parser is appropriate if each parameter matches the format of its 162 * accompanying fragment. 163 * </p> 164 * 165 * <p> 166 * If this parser is appropriate, all the parameters used in the pattern 167 * will be removed from the parameter map. If this parser is not 168 * appropriate, the parameter map will not be modified. 169 * </p> 170 * 171 * @param parameters the parameter map to build the string from 172 * @return the string, or <code>null</code> if this parser is not 173 * appropriate 174 */ 175 public String build(Map<String, String> parameters) { 176 String s = _builder; 177 178 for (StringParserFragment stringParserFragment : 179 _stringParserFragments) { 180 181 String value = parameters.get(stringParserFragment.getName()); 182 183 if (value == null) { 184 return null; 185 } 186 187 if ((_stringEncoder != null) && !stringParserFragment.isRaw()) { 188 value = _stringEncoder.encode(value); 189 } 190 191 if (!stringParserFragment.matches(value)) { 192 return null; 193 } 194 195 s = s.replace(stringParserFragment.getToken(), value); 196 } 197 198 for (StringParserFragment stringParserFragment : 199 _stringParserFragments) { 200 201 parameters.remove(stringParserFragment.getName()); 202 } 203 204 return s; 205 } 206 207 /** 208 * Populates the parameter map with values parsed from the string if this 209 * parser matches. 210 * 211 * @param s the string to parse 212 * @param parameters the parameter map to populate if this parser matches 213 * the string 214 * @return <code>true</code> if this parser matches; <code>false</code> 215 * otherwise 216 */ 217 public boolean parse(String s, Map<String, String> parameters) { 218 Matcher matcher = _pattern.matcher(s); 219 220 if (!matcher.matches()) { 221 return false; 222 } 223 224 for (int i = 1; i <= _stringParserFragments.size(); i++) { 225 StringParserFragment stringParserFragment = 226 _stringParserFragments.get(i - 1); 227 228 String value = matcher.group(i); 229 230 if ((_stringEncoder != null) && !stringParserFragment.isRaw()) { 231 value = _stringEncoder.decode(value); 232 } 233 234 parameters.put(stringParserFragment.getName(), value); 235 } 236 237 return true; 238 } 239 240 /** 241 * Sets the string encoder to use for parsing or building a string. 242 * 243 * <p> 244 * The string encoder will not be used for fragments marked as raw. A 245 * fragment can be marked as raw by prefixing its name with a percent sign. 246 * </p> 247 * 248 * @param stringEncoder the string encoder to use for parsing or building a 249 * string 250 * @see StringEncoder 251 */ 252 public void setStringEncoder(StringEncoder stringEncoder) { 253 _stringEncoder = stringEncoder; 254 } 255 256 private static Pattern _escapeRegexPattern = Pattern.compile( 257 "[\\{\\}\\(\\)\\[\\]\\*\\+\\?\\$\\^\\.\\#\\\\]"); 258 private static Pattern _fragmentPattern = Pattern.compile("\\{.+?\\}"); 259 260 private String _builder; 261 private Pattern _pattern; 262 private StringEncoder _stringEncoder; 263 private List<StringParserFragment> _stringParserFragments = 264 new ArrayList<StringParserFragment>(); 265 266 }