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}