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-2015 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.lang.reflect.Array; 027import java.nio.charset.Charset; 028import java.util.Collection; 029import java.util.Map; 030import java.util.Properties; 031import java.util.regex.Matcher; 032import java.util.regex.Pattern; 033import java.util.regex.PatternSyntaxException; 034 035import org.forgerock.http.util.Uris; 036import org.forgerock.openig.util.StringUtil; 037import org.forgerock.util.encode.Base64; 038 039/** 040 * Methods exposed for EL usage. 041 */ 042public final class Functions { 043 044 private Functions() { } 045 046 /** 047 * Create an array of String based on the strings given as parameters. 048 * @param values the strings to put in the array. 049 * @return the array of strings. 050 */ 051 public static String[] array(String... values) { 052 return values; 053 } 054 055 /** 056 * Returns {@code true} if the object contains the value. 057 * 058 * @param object the object to be searched. 059 * @param value the value to find. 060 * @return the length of the object, or {@code 0} if length could not be 061 * determined. 062 */ 063 public static boolean contains(Object object, Object value) { 064 if (object == null || value == null) { 065 return false; 066 } else if (object instanceof CharSequence && value instanceof CharSequence) { 067 return object.toString().contains(value.toString()); 068 } else if (object instanceof Collection) { 069 return ((Collection<?>) object).contains(value); 070 } else if (object instanceof Object[]) { 071 // doesn't handle primitives (but is cheap) 072 for (Object o : (Object[]) object) { 073 if (o.equals(value)) { 074 return true; 075 } 076 } 077 } else if (object.getClass().isArray()) { 078 // handles primitives (slightly more expensive) 079 int length = Array.getLength(object); 080 for (int n = 0; n < length; n++) { 081 if (Array.get(object, n).equals(value)) { 082 return true; 083 } 084 } 085 } 086 // value not contained in object 087 return false; 088 } 089 090 /** 091 * Returns the index within a string of the first occurrence of a specified 092 * substring. 093 * 094 * @param value the string to be searched. 095 * @param substring the value to search for within the string 096 * @return the index of the first instance of substring, or {@code -1} if 097 * not found. 098 */ 099 public static int indexOf(String value, String substring) { 100 return value != null && substring != null ? value.indexOf(substring) : null; 101 } 102 103 /** 104 * Joins an array of strings into a single string value, with a specified separator. 105 * 106 * @param separator the separator to place between joined elements. 107 * @param values the array of strings to be joined. You can use the array() function to construct this argument. 108 * @return the string containing the joined strings. 109 */ 110 public static String join(String[] values, String separator) { 111 return values != null ? StringUtil.join(separator, (Object[]) values) : null; 112 } 113 114 /** 115 * Returns the first key found in a map that matches the specified regular 116 * expression pattern, or {@code null} if no such match is found. 117 * 118 * @param map the map whose keys are to be searched. 119 * @param pattern a string containing the regular expression pattern to match. 120 * @return the first matching key, or {@code null} if no match found. 121 */ 122 public static String keyMatch(Object map, String pattern) { 123 if (map instanceof Map) { 124 // avoid unnecessary proxying via duck typing 125 Pattern p = null; 126 try { 127 // TODO: cache oft-used patterns? 128 p = Pattern.compile(pattern); 129 } catch (PatternSyntaxException pse) { 130 // invalid pattern results in no match 131 return null; 132 } 133 for (Object key : ((Map<?, ?>) map).keySet()) { 134 if (key instanceof String && p.matcher((String) key).matches()) { 135 return (String) key; 136 } 137 } 138 } 139 // no match 140 return null; 141 } 142 143 /** 144 * Returns the number of items in a collection, or the number of characters 145 * in a string. 146 * 147 * @param value the object whose length is to be determined. 148 * @return the length of the object, or {@code 0} if length could not be 149 * determined. 150 */ 151 public static int length(Object value) { 152 if (value == null) { 153 return 0; 154 } else if (value instanceof CharSequence) { 155 return ((CharSequence) value).length(); 156 } else if (value instanceof Collection) { 157 return ((Collection<?>) value).size(); 158 } else if (value instanceof Map) { 159 return ((Map<?, ?>) value).size(); 160 } else if (value instanceof Object[]) { 161 // doesn't handle primitives (but is cheap) 162 return ((Object[]) value).length; 163 } else if (value.getClass().isArray()) { 164 // handles primitives (slightly more expensive) 165 return Array.getLength(value); 166 } 167 // no items 168 return 0; 169 } 170 171 /** 172 * Returns {@code true} if the string contains the specified regular 173 * expression pattern. 174 * 175 * @param value 176 * the string to be searched. 177 * @param pattern 178 * a string containing the regular expression pattern to find. 179 * @return {@code true} if the string contains the specified regular 180 * expression pattern. 181 */ 182 public static boolean matches(String value, String pattern) { 183 try { 184 return Pattern.compile(pattern).matcher(value).find(); 185 } catch (PatternSyntaxException pse) { 186 // ignore invalid pattern 187 } 188 return false; 189 } 190 191 /** 192 * Returns an array containing the matches of a regular expression pattern 193 * against a string, or {@code null} if no match is found. The first element 194 * of the array is the entire match, and each subsequent element correlates 195 * to any capture group specified within the regular expression. 196 * 197 * @param value 198 * the string to be searched. 199 * @param pattern 200 * a string containing the regular expression pattern to match. 201 * @return an array of matches, or {@code null} if no match found. 202 */ 203 public static String[] matchingGroups(String value, String pattern) { 204 try { 205 Pattern p = Pattern.compile(pattern); 206 Matcher m = p.matcher(value); 207 if (m.find()) { 208 int count = m.groupCount(); 209 String[] matches = new String[count + 1]; 210 matches[0] = m.group(0); 211 for (int n = 1; n <= count; n++) { 212 matches[n] = m.group(n); 213 } 214 return matches; 215 } 216 } catch (PatternSyntaxException pse) { 217 // ignore invalid pattern 218 } 219 return null; 220 } 221 222 /** 223 * Splits a string into an array of substrings around matches of the given 224 * regular expression. 225 * 226 * @param value 227 * the string to be split. 228 * @param regex 229 * the regular expression to split substrings around. 230 * @return the resulting array of split substrings. 231 */ 232 public static String[] split(String value, String regex) { 233 return value != null ? value.split(regex) : null; 234 } 235 236 /** 237 * Converts all of the characters in a string to lower case. 238 * 239 * @param value the string whose characters are to be converted. 240 * @return the string with characters converted to lower case. 241 */ 242 public static String toLowerCase(String value) { 243 return value != null ? value.toLowerCase() : null; 244 } 245 246 /** 247 * Returns the string value of an arbitrary object. 248 * 249 * @param value 250 * the object whose string value is to be returned. 251 * @return the string value of the object. 252 */ 253 public static String toString(Object value) { 254 return value != null ? value.toString() : null; 255 } 256 257 /** 258 * Converts all of the characters in a string to upper case. 259 * 260 * @param value the string whose characters are to be converted. 261 * @return the string with characters converted to upper case. 262 */ 263 public static String toUpperCase(String value) { 264 return value != null ? value.toUpperCase() : null; 265 } 266 267 /** 268 * Returns a copy of a string with leading and trailing whitespace omitted. 269 * 270 * @param value the string whose white space is to be omitted. 271 * @return the string with leading and trailing white space omitted. 272 */ 273 public static String trim(String value) { 274 return value != null ? value.trim() : null; 275 } 276 277 /** 278 * Returns the URL encoding of the provided string. 279 * 280 * @param value 281 * the string to be URL encoded, which may be {@code null}. 282 * @return the URL encoding of the provided string, or {@code null} if 283 * {@code string} was {@code null}. 284 */ 285 public static String urlEncode(String value) { 286 return Uris.formEncodeParameterNameOrValue(value); 287 } 288 289 /** 290 * Returns the URL decoding of the provided string. 291 * 292 * @param value 293 * the string to be URL decoded, which may be {@code null}. 294 * @return the URL decoding of the provided string, or {@code null} if 295 * {@code string} was {@code null}. 296 */ 297 public static String urlDecode(String value) { 298 return Uris.formDecodeParameterNameOrValue(value); 299 } 300 301 /** 302 * Encode the given String input into Base 64. 303 * 304 * @param value 305 * the string to be Base64 encoded, which may be {@code null}. 306 * @return the Base64 encoding of the provided string, or {@code null} if 307 * {@code string} was {@code null}. 308 */ 309 public static String encodeBase64(final String value) { 310 if (value != null) { 311 return Base64.encode(value.getBytes()); 312 } 313 return null; 314 } 315 316 /** 317 * Decode the given Base64 String input. 318 * 319 * @param value 320 * the string to be Base64 decoded, which may be {@code null}. 321 * @return the decoding of the provided string, or {@code null} if 322 * {@code string} was {@code null} or if the input was not a Base64 valid input. 323 */ 324 public static String decodeBase64(final String value) { 325 if (value != null) { 326 return new String(Base64.decode(value)); 327 } 328 return null; 329 } 330 331 /** 332 * Returns the content of the given file as a plain String. 333 * 334 * @param filename 335 * file to be read 336 * @return the file content as a String or {@literal null} if here was an error (missing file, ...) 337 */ 338 public static String read(final String filename) { 339 try { 340 return asString(new FileInputStream(new File(filename)), Charset.defaultCharset()); 341 } catch (IOException e) { 342 return null; 343 } 344 } 345 346 /** 347 * Returns the content of the given file as a {@link Properties}. 348 * 349 * @param filename 350 * file to be read 351 * @return the file content as {@link Properties} or {@literal null} if here was an error (missing file, ...) 352 */ 353 public static Properties readProperties(final String filename) { 354 FileInputStream fis = null; 355 try { 356 fis = new FileInputStream(new File(filename)); 357 Properties properties = new Properties(); 358 properties.load(fis); 359 return properties; 360 } catch (IOException e) { 361 return null; 362 } finally { 363 closeSilently(fis); 364 } 365 } 366 367}