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 }