View Javadoc

1   /*
2    * Copyright 2007-2013 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.smartics.exceptions.i18n.message;
17  
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.apache.commons.lang.StringUtils;
25  
26  import de.smartics.exceptions.i18n.app.ParseExceptionCode;
27  
28  /**
29   * This class provides methods to parse message parameter.
30   */
31  public final class MessageParamParser
32  {
33    // ********************************* Fields *********************************
34  
35    // --- constants ------------------------------------------------------------
36  
37    // --- members --------------------------------------------------------------
38  
39    // ****************************** Initializer *******************************
40  
41    // ****************************** Constructors ******************************
42  
43    /**
44     * Utility class.
45     */
46    private MessageParamParser()
47    {
48    }
49  
50    // ****************************** Inner Classes *****************************
51  
52    /**
53     * Contains the placeholderId information for one message parameter.
54     */
55    public static final class MessageParamInfo
56    {
57      // ******************************** Fields ********************************
58  
59      // --- constants ----------------------------------------------------------
60  
61      // --- members ------------------------------------------------------------
62  
63      /**
64       * The identifier of the placeholder (index or property name) the
65       * information should be written to.
66       */
67      private final String placeholderId;
68  
69      /**
70       * The OGNL path to the value inside the value to use.
71       */
72      private final String ognlPath;
73  
74      // ***************************** Initializer ******************************
75  
76      // ***************************** Constructors *****************************
77  
78      /**
79       * Default constructor.
80       *
81       * @param placeholderId the identifier of the placeholder (index or property
82       *          name) the information should be written to.
83       * @param ognlPath the OGNL path to the value inside the value to use.
84       */
85      public MessageParamInfo(final String placeholderId, final String ognlPath)
86      {
87        this.placeholderId = placeholderId;
88        this.ognlPath = ognlPath;
89      }
90  
91      // ***************************** Inner Classes ****************************
92  
93      // ******************************** Methods *******************************
94  
95      // --- init ---------------------------------------------------------------
96  
97      // --- get&set ------------------------------------------------------------
98  
99      /**
100      * Returns the identifier of the placeholder (index or property name) the
101      * information should be written to.
102      *
103      * @return the identifier of the placeholder (index or property name) the
104      *         information should be written to.
105      */
106     public String getPlaceholderId()
107     {
108       return placeholderId;
109     }
110 
111     /**
112      * Returns the OGNL path to the value inside the value to use.
113      *
114      * @return the OGNL path to the value inside the value to use.
115      */
116     public String getOgnlPath()
117     {
118       return ognlPath;
119     }
120 
121     // --- business -----------------------------------------------------------
122 
123     // --- object basics ------------------------------------------------------
124   }
125 
126   // ********************************* Methods ********************************
127 
128   // --- init -----------------------------------------------------------------
129 
130   // --- get&set --------------------------------------------------------------
131 
132   // --- business -------------------------------------------------------------
133 
134   /**
135    * Splits the input string into message param informations.
136    * <p>
137    * The expected format is a comma separated list of
138    * <code>placeholderId:ognlPath</code> strings. The OGNL path may be missing,
139    * in this case the placeholderId alone.
140    * </p>
141    * <p>
142    * Example: <code>0:user.name,4,3:user.id</code>.
143    * </p>
144    * <p>
145    * Spaces between tokens are removed.
146    * </p>
147    * <p>
148    * If the input string is <code>null</code> or empty, the empty list is
149    * returned.
150    * </p>
151    *
152    * @param propertyName the name of the property whose message parameter is to
153    *          be parsed.
154    * @param input the input string to parse.
155    * @return the list of message parameter infos.
156    * @throws ParseException if the input is violating the expected syntax.
157    */
158   public static List<MessageParamInfo> parse(final String propertyName,
159       final String input) throws ParseException
160   {
161     final List<MessageParamInfo> infos = new ArrayList<MessageParamInfo>();
162     if (input == null || "".equals(input.trim()))
163     {
164       final MessageParamInfo info = new MessageParamInfo(propertyName, null);
165       infos.add(info);
166       return infos;
167     }
168 
169     final List<String> tokens = Helper.split(input);
170     for (final String token : tokens)
171     {
172       final String index;
173       final String ognlPath;
174 
175       final int i = token.indexOf(':');
176       if (i >= 0)
177       {
178         final String specifiedIndex = token.substring(0, i).trim();
179         ognlPath = token.substring(i + 1).trim();
180         if ("".equals(ognlPath))
181         {
182           throw new ParseException(ParseExceptionCode.OGNL, token, i + 1);
183         }
184         index =
185             StringUtils.isNotBlank(specifiedIndex) ? specifiedIndex
186                 : calcNameFromPath(ognlPath);
187       }
188       else
189       {
190         index = token;
191         ognlPath = null;
192       }
193 
194       final MessageParamInfo info = new MessageParamInfo(index, ognlPath); // NOPMD
195       infos.add(info);
196     }
197 
198     return infos;
199   }
200 
201   private static String calcNameFromPath(final String ognlPath)
202   {
203     final int index = ognlPath.lastIndexOf('.', ognlPath.length() - 2);
204     if (index == -1)
205     {
206       return ognlPath;
207     }
208     return ognlPath.substring(index + 1);
209   }
210 
211   /**
212    * Parses the content of the parent message parameter. The input must follow
213    * the following syntax:
214    * <p>
215    * <code>property=placeholderId:ognl-path</code> as in
216    * <code>cause=1:message</code>
217    * </p>
218    * <p>
219    * If several properties of a referenced entity are to be displayed at
220    * different indices, separate them by commas and the information for
221    * different attributes separate by colons (<code>;</code>).
222    * </p>
223    * <p>
224    * <code>cause=1:cause.message,3:message,4:localizedMessage; message=5</code>
225    * </p>
226    *
227    * @param input the input string to parse.
228    * @return the parsed information. The key of the map is the name of the
229    *         attribute, the value is the placeholderId information for that
230    *         attribute.
231    * @throws ParseException if the input is violating the expected syntax.
232    */
233   public static Map<String, List<MessageParamInfo>> parseParentMessageParam(
234       final String input) throws ParseException
235   {
236     final String norm = fix(input);
237     if (norm == null || "".equals(norm.trim()))
238     {
239       return Collections.emptyMap();
240     }
241 
242     final Map<String, List<MessageParamInfo>> map =
243         new HashMap<String, List<MessageParamInfo>>(// NOPMD
244             3);
245     final List<String> tokens = Helper.split(norm, ";");
246     int currentParsingPosition = 0; // NOPMD
247     for (final String token : tokens)
248     {
249       parseToken(norm, map, token, currentParsingPosition);
250       currentParsingPosition = currentParsingPosition + token.length();
251     }
252 
253     return map;
254   }
255 
256   private static String fix(final String string)
257   {
258     if (string == null)
259     {
260       return null;
261     }
262     final int length = string.length();
263     if (length > 2 && string.charAt(0) == '"')
264     {
265       return string.substring(1, string.length() - 1);
266     }
267     return string;
268   }
269 
270   /**
271    * Parses the given token and throws an exception if it cannot be parsed.
272    *
273    * @param input the input string is only used as exception information in case
274    *          an exception is thrown.
275    * @param map if the token is successfully parsed, the information is set to
276    *          this map.
277    * @param token the token to be analyzed. There should be a key and a value
278    *          separated by the equal character ("<code>=</code>").
279    * @param currentParsingPosition the current parsing position in the input
280    *          string. This information is only passed to the exception in case
281    *          one is thrown.
282    * @throws ParseException if the token cannot be parsed.
283    */
284   private static void parseToken(final String input,
285       final Map<String, List<MessageParamInfo>> map, final String token,
286       final int currentParsingPosition) throws ParseException
287   {
288     final int index = token.indexOf('=');
289     if (index != -1)
290     {
291       final String attributeName = token.substring(0, index).trim();
292       if ("".equals(attributeName))
293       {
294         throw new ParseException(
295             ParseExceptionCode.MISSING_PARENT_PROPERTY_ATTRIBUTE, input,
296             currentParsingPosition + index);
297       }
298       final String subInput = token.substring(index + 1);
299       if ("".equals(subInput))
300       {
301         throw new ParseException(
302             ParseExceptionCode.MISSING_PARENT_PROPERTY_INDEX, input,
303             currentParsingPosition + index + 1);
304       }
305       final List<MessageParamInfo> info = parse(attributeName, subInput);
306       map.put(attributeName, info);
307     }
308     else
309     {
310       throw new ParseException(
311           ParseExceptionCode.MISSING_PARENT_PROPERTY_SEPARATOR, input,
312           currentParsingPosition);
313     }
314   }
315 
316   // --- object basics --------------------------------------------------------
317 
318 }