001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2010–2011 ApexIdentity Inc. 015 * Portions Copyright 2011-2014 ForgeRock AS. 016 */ 017 018package org.forgerock.openig.el; 019 020import static org.forgerock.openig.util.StringUtil.asString; 021import static org.forgerock.util.Utils.closeSilently; 022 023import java.io.File; 024import java.io.FileInputStream; 025import java.io.IOException; 026import java.io.UnsupportedEncodingException; 027import java.lang.reflect.Array; 028import java.lang.reflect.Method; 029import java.lang.reflect.Modifier; 030import java.net.URLDecoder; 031import java.net.URLEncoder; 032import java.nio.charset.Charset; 033import java.util.Collection; 034import java.util.HashMap; 035import java.util.Map; 036import java.util.Properties; 037import java.util.regex.Matcher; 038import java.util.regex.Pattern; 039import java.util.regex.PatternSyntaxException; 040 041import javax.el.FunctionMapper; 042 043import org.forgerock.openig.util.StringUtil; 044import org.forgerock.util.encode.Base64; 045 046/** 047 * Maps between EL function names and methods. In this implementation all public 048 * static methods are automatically exposed as functions. 049 */ 050public class Functions extends FunctionMapper { 051 052 /** A mapping of function names with methods to return. */ 053 private static final Map<String, Method> METHODS; 054 static { 055 METHODS = new HashMap<String, Method>(); 056 for (Method method : Functions.class.getMethods()) { 057 if (Modifier.isStatic(method.getModifiers())) { 058 METHODS.put(method.getName(), method); 059 } 060 } 061 } 062 063 /** 064 * Resolves the specified prefix and local name into a method. In this 065 * implementation, the only supported supported prefix is none ({@code ""}). 066 * 067 * @param prefix the prefix of the function, or {@code ""} if no prefix. 068 * @param localName the short name of the function. 069 * @return the static method to invoke, or {@code null} if no match was 070 * found. 071 */ 072 @Override 073 public Method resolveFunction(String prefix, String localName) { 074 if (prefix != null && localName != null && prefix.length() == 0) { 075 return METHODS.get(localName); 076 } 077 // no match was found 078 return null; 079 } 080 081 /** 082 * Returns {@code true} if the object contains the value. 083 * 084 * @param object the object to be searched. 085 * @param value the value to find. 086 * @return the length of the object, or {@code 0} if length could not be 087 * determined. 088 */ 089 public static boolean contains(Object object, Object value) { 090 if (object == null || value == null) { 091 return false; 092 } else if (object instanceof CharSequence && value instanceof CharSequence) { 093 return (object.toString().contains(value.toString())); 094 } else if (object instanceof Collection) { 095 return ((Collection<?>) object).contains(value); 096 } else if (object instanceof Object[]) { 097 // doesn't handle primitives (but is cheap) 098 for (Object o : (Object[]) object) { 099 if (o.equals(value)) { 100 return true; 101 } 102 } 103 } else if (object.getClass().isArray()) { 104 // handles primitives (slightly more expensive) 105 int length = Array.getLength(object); 106 for (int n = 0; n < length; n++) { 107 if (Array.get(object, n).equals(value)) { 108 return true; 109 } 110 } 111 } 112 // value not contained in object 113 return false; 114 } 115 116 /** 117 * Returns the index within a string of the first occurrence of a specified 118 * substring. 119 * 120 * @param value the string to be searched. 121 * @param substring the value to search for within the string 122 * @return the index of the first instance of substring, or {@code -1} if 123 * not found. 124 */ 125 public static int indexOf(String value, String substring) { 126 return (value != null && substring != null ? value.indexOf(substring) : null); 127 } 128 129 /** 130 * Joins an array of strings into a single string value, with a specified 131 * separator. 132 * 133 * @param separator the separator to place between joined elements. 134 * @param values the array of strings to be joined. 135 * @return the string containing the joined strings. 136 */ 137 public static String join(String[] values, String separator) { 138 return (values != null ? StringUtil.join(separator, (Object[]) values) : null); 139 } 140 141 /** 142 * Returns the first key found in a map that matches the specified regular 143 * expression pattern, or {@code null} if no such match is found. 144 * 145 * @param map the map whose keys are to be searched. 146 * @param pattern a string containing the regular expression pattern to match. 147 * @return the first matching key, or {@code null} if no match found. 148 */ 149 public static String keyMatch(Object map, String pattern) { 150 if (map instanceof Map) { 151 // avoid unnecessary proxying via duck typing 152 Pattern p = null; 153 try { 154 // TODO: cache oft-used patterns? 155 p = Pattern.compile(pattern); 156 } catch (PatternSyntaxException pse) { 157 // invalid pattern results in no match 158 return null; 159 } 160 for (Object key : ((Map<?, ?>) map).keySet()) { 161 if (key instanceof String) { 162 if (p.matcher((String) key).matches()) { 163 return (String) key; 164 } 165 } 166 } 167 } 168 // no match 169 return null; 170 } 171 172 /** 173 * Returns the number of items in a collection, or the number of characters 174 * in a string. 175 * 176 * @param value the object whose length is to be determined. 177 * @return the length of the object, or {@code 0} if length could not be 178 * determined. 179 */ 180 public static int length(Object value) { 181 if (value == null) { 182 return 0; 183 } else if (value instanceof CharSequence) { 184 return ((CharSequence) value).length(); 185 } else if (value instanceof Collection) { 186 return ((Collection<?>) value).size(); 187 } else if (value instanceof Map) { 188 return ((Map<?, ?>) value).size(); 189 } else if (value instanceof Object[]) { 190 // doesn't handle primitives (but is cheap) 191 return ((Object[]) value).length; 192 } else if (value.getClass().isArray()) { 193 // handles primitives (slightly more expensive) 194 return Array.getLength(value); 195 } 196 // no items 197 return 0; 198 } 199 200 /** 201 * Returns {@code true} if the string contains the specified regular 202 * expression pattern. 203 * 204 * @param value 205 * the string to be searched. 206 * @param pattern 207 * a string containing the regular expression pattern to find. 208 * @return {@code true} if the string contains the specified regular 209 * expression pattern. 210 */ 211 public static boolean matches(String value, String pattern) { 212 try { 213 return Pattern.compile(pattern).matcher(value).find(); 214 } catch (PatternSyntaxException pse) { 215 // ignore invalid pattern 216 } 217 return false; 218 } 219 220 /** 221 * Returns an array containing the matches of a regular expression pattern 222 * against a string, or {@code null} if no match is found. The first element 223 * of the array is the entire match, and each subsequent element correlates 224 * to any capture group specified within the regular expression. 225 * 226 * @param value 227 * the string to be searched. 228 * @param pattern 229 * a string containing the regular expression pattern to match. 230 * @return an array of matches, or {@code null} if no match found. 231 */ 232 public static String[] matchingGroups(String value, String pattern) { 233 try { 234 Pattern p = Pattern.compile(pattern); 235 Matcher m = p.matcher(value); 236 if (m.find()) { 237 int count = m.groupCount(); 238 String[] matches = new String[count + 1]; 239 matches[0] = m.group(0); 240 for (int n = 1; n <= count; n++) { 241 matches[n] = m.group(n); 242 } 243 return matches; 244 } 245 } catch (PatternSyntaxException pse) { 246 // ignore invalid pattern 247 } 248 return null; 249 } 250 251 /** 252 * Splits a string into an array of substrings around matches of the given 253 * regular expression. 254 * 255 * @param value 256 * the string to be split. 257 * @param regex 258 * the regular expression to split substrings around. 259 * @return the resulting array of split substrings. 260 */ 261 public static String[] split(String value, String regex) { 262 return (value != null ? value.split(regex) : null); 263 } 264 265 /** 266 * Converts all of the characters in a string to lower case. 267 * 268 * @param value the string whose characters are to be converted. 269 * @return the string with characters converted to lower case. 270 */ 271 public static String toLowerCase(String value) { 272 return (value != null ? value.toLowerCase() : null); 273 } 274 275 /** 276 * Returns the string value of an arbitrary object. 277 * 278 * @param value 279 * the object whose string value is to be returned. 280 * @return the string value of the object. 281 */ 282 public static String toString(Object value) { 283 return (value != null ? value.toString() : null); 284 } 285 286 /** 287 * Converts all of the characters in a string to upper case. 288 * 289 * @param value the string whose characters are to be converted. 290 * @return the string with characters converted to upper case. 291 */ 292 public static String toUpperCase(String value) { 293 return (value != null ? value.toUpperCase() : null); 294 } 295 296 /** 297 * Returns a copy of a string with leading and trailing whitespace omitted. 298 * 299 * @param value the string whose white space is to be omitted. 300 * @return the string with leading and trailing white space omitted. 301 */ 302 public static String trim(String value) { 303 return (value != null ? value.trim() : null); 304 } 305 306 /** 307 * Returns the URL encoding of the provided string. 308 * 309 * @param value 310 * the string to be URL encoded, which may be {@code null}. 311 * @return the URL encoding of the provided string, or {@code null} if 312 * {@code string} was {@code null}. 313 */ 314 public static String urlEncode(String value) { 315 try { 316 return value != null ? URLEncoder.encode(value, "UTF-8") : null; 317 } catch (UnsupportedEncodingException e) { 318 return value; 319 } 320 } 321 322 /** 323 * Returns the URL decoding of the provided string. 324 * 325 * @param value 326 * the string to be URL decoded, which may be {@code null}. 327 * @return the URL decoding of the provided string, or {@code null} if 328 * {@code string} was {@code null}. 329 */ 330 public static String urlDecode(String value) { 331 try { 332 return value != null ? URLDecoder.decode(value, "UTF-8") : null; 333 } catch (UnsupportedEncodingException e) { 334 return value; 335 } 336 } 337 338 /** 339 * Encode the given String input into Base 64. 340 * 341 * @param value 342 * the string to be Base64 encoded, which may be {@code null}. 343 * @return the Base64 encoding of the provided string, or {@code null} if 344 * {@code string} was {@code null}. 345 */ 346 public static String encodeBase64(final String value) { 347 if (value == null) { 348 return null; 349 } 350 return Base64.encode(value.getBytes()); 351 } 352 353 /** 354 * Decode the given Base64 String input. 355 * 356 * @param value 357 * the string to be Base64 decoded, which may be {@code null}. 358 * @return the decoding of the provided string, or {@code null} if 359 * {@code string} was {@code null} or if the input was not a Base64 valid input. 360 */ 361 public static String decodeBase64(final String value) { 362 if (value == null) { 363 return null; 364 } 365 return new String(Base64.decode(value)); 366 } 367 368 /** 369 * Returns the content of the given file as a plain String. 370 * 371 * @param filename 372 * file to be read 373 * @return the file content as a String or {@literal null} if here was an error (missing file, ...) 374 */ 375 public static String read(final String filename) { 376 try { 377 return asString(new FileInputStream(new File(filename)), Charset.defaultCharset()); 378 } catch (IOException e) { 379 return null; 380 } 381 } 382 383 /** 384 * Returns the content of the given file as a {@link Properties}. 385 * 386 * @param filename 387 * file to be read 388 * @return the file content as {@link Properties} or {@literal null} if here was an error (missing file, ...) 389 */ 390 public static Properties readProperties(final String filename) { 391 FileInputStream fis = null; 392 try { 393 fis = new FileInputStream(new File(filename)); 394 Properties properties = new Properties(); 395 properties.load(fis); 396 return properties; 397 } catch (IOException e) { 398 return null; 399 } finally { 400 closeSilently(fis); 401 } 402 } 403}