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 &quot;Hi Tom! How are you?&quot;. 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 &quot;.*&quot;. 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             * &quot;root/home/mysite/pages/index.htm&quot;, 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    }