001package com.identityworksllc.iiq.common;
002
003import bsh.EvalError;
004import bsh.This;
005import com.fasterxml.jackson.core.JsonProcessingException;
006import com.fasterxml.jackson.databind.ObjectMapper;
007import org.apache.commons.beanutils.PropertyUtils;
008import org.apache.commons.logging.Log;
009import org.apache.commons.logging.LogFactory;
010import sailpoint.api.IdentityService;
011import sailpoint.api.IncrementalObjectIterator;
012import sailpoint.api.IncrementalProjectionIterator;
013import sailpoint.api.SailPointContext;
014import sailpoint.api.SailPointFactory;
015import sailpoint.object.*;
016import sailpoint.search.MapMatcher;
017import sailpoint.tools.GeneralException;
018import sailpoint.tools.MapUtil;
019import sailpoint.tools.Util;
020import sailpoint.tools.xml.AbstractXmlObject;
021
022import java.lang.reflect.Method;
023import java.lang.reflect.Modifier;
024import java.lang.reflect.Parameter;
025import java.sql.Connection;
026import java.sql.ResultSet;
027import java.sql.SQLException;
028import java.text.ParseException;
029import java.text.SimpleDateFormat;
030import java.util.*;
031import java.util.function.BiConsumer;
032import java.util.function.BiFunction;
033import java.util.function.Consumer;
034import java.util.function.Function;
035import java.util.function.Predicate;
036import java.util.function.Supplier;
037import java.util.regex.Matcher;
038import java.util.regex.Pattern;
039import java.util.stream.Collectors;
040import java.util.stream.Stream;
041import java.util.stream.StreamSupport;
042
043import static com.identityworksllc.iiq.common.Utilities.getProperty;
044
045/**
046 * This class implements a whole slew of java.util.function implementations, which
047 * can be used as a hack to get streams working in Beanshell. This can simplify code
048 * enormously.
049 *
050 * For example, you could do something like:
051 *
052 * ```
053 * someMethod(Object item) {
054 *     // Beanshell function that does something interesting with the item
055 * }
056 *
057 * // Creates a {@link Consumer} that invokes the specified Beanshell method
058 * someMethodFunction = Functions.c(this, "someMethod");
059 *
060 * list.forEach(someMethodFunction);
061 * ```
062 *
063 * @author Devin Rosenbauer
064 * @author Instrumental Identity
065 */
066@SuppressWarnings({"rawtypes", "unused"})
067public class Functions {
068    /**
069     * A generic callback for doing connection things
070     */
071    @FunctionalInterface
072    public interface ConnectionHandler {
073        /**
074         * Does something with the connection
075         * @param connection The connection
076         * @throws SQLException on SQL errors
077         * @throws GeneralException on IIQ errors
078         */
079        void accept(Connection connection) throws SQLException, GeneralException;
080    }
081
082    /**
083     * A generic callback for doing result set row handling things
084     */
085    @FunctionalInterface
086    public interface RowHandler {
087        /**
088         * Does something with the current row of the ResultSet provided
089         *
090         * @param resultSet The ResultSet from which the current row should be extracted
091         * @throws SQLException on JDBC errors
092         * @throws GeneralException on IIQ errors
093         */
094        void accept(ResultSet resultSet) throws SQLException, GeneralException;
095    }
096
097    /**
098     * A generic callback implementation, essentially a runnable with an exception. This
099     * class also implements ConsumerWithError, making it easier to use via Beanshell.
100     */
101    @FunctionalInterface
102    public interface GenericCallback extends ConsumerWithError<SailPointContext> {
103        @Override
104        default void acceptWithError(SailPointContext context) throws GeneralException {
105            run(context);
106        }
107
108        void run(SailPointContext context) throws GeneralException;
109    }
110
111    /**
112     * An extension of Predicate that allows predicate code to throw
113     * an exception. If used in a context not expecting this class,
114     * the error will be caught, logged, and re-thrown.
115     */
116    public interface PredicateWithError<A> extends Predicate<A> {
117        default PredicateWithError<A> and(Predicate<? super A> other) {
118            return (a) -> {
119                boolean result = testWithError(a);
120                if (result) {
121                    if (other instanceof PredicateWithError) {
122                        @SuppressWarnings("unchecked")
123                        PredicateWithError<? super A> errorHandler = (PredicateWithError<? super A>) other;
124                        result = errorHandler.testWithError(a);
125                    } else {
126                        result = other.test(a);
127                    }
128                }
129                return result;
130            };
131        }
132
133        default PredicateWithError<A> negate() {
134            return (a) -> !testWithError(a);
135        }
136
137        default PredicateWithError<A> or(Predicate<? super A> other) {
138            return (a) -> {
139                boolean result = testWithError(a);
140                if (!result) {
141                    if (other instanceof PredicateWithError) {
142                        @SuppressWarnings("unchecked")
143                        PredicateWithError<? super A> errorHandler = (PredicateWithError<? super A>) other;
144                        result = errorHandler.testWithError(a);
145                    } else {
146                        result = other.test(a);
147                    }
148                }
149                return result;
150            };
151        }
152
153        @Override
154        default boolean test(A a) {
155            try {
156                return testWithError(a);
157            } catch (Throwable t) {
158                log.error("Caught an error in a function");
159                if (t instanceof RuntimeException) {
160                    throw ((RuntimeException) t);
161                }
162                throw new IllegalStateException(t);
163            }
164        }
165
166        boolean testWithError(A object) throws Throwable;
167    }
168
169    /**
170     * An extension of BiFunction that allows functional code to throw
171     * an exception. If used in a context not expecting this class,
172     * the error will be caught, logged, and re-thrown.
173     */
174    public interface BiFunctionWithError<A, B, R> extends BiFunction<A, B, R> {
175        @SuppressWarnings("unchecked")
176        default <V> BiFunctionWithError<A, B, V> andThen(Function<? super R, ? extends V> after) {
177            return (a, b) -> {
178                R result = applyWithError(a, b);
179                if (after instanceof FunctionWithError) {
180                    return ((FunctionWithError<? super R, ? extends V>) after).applyWithError(result);
181                }
182                return after.apply(result);
183            };
184        }
185
186        @Override
187        default R apply(A a, B b) {
188            try {
189                return applyWithError(a, b);
190            } catch (Throwable t) {
191                log.error("Caught an error in a function");
192                if (t instanceof RuntimeException) {
193                    throw ((RuntimeException) t);
194                }
195                throw new IllegalStateException(t);
196            }
197        }
198
199        R applyWithError(A obj1, B obj2) throws Throwable;
200    }
201
202    /**
203     * An extension of Consumer that allows functional code to throw
204     * an exception. If used in a context not expecting this class,
205     * the error will be caught, logged, and re-thrown.
206     */
207    public interface ConsumerWithError<T> extends Consumer<T> {
208        @Override
209        default void accept(T a) {
210            try {
211                acceptWithError(a);
212            } catch (Throwable t) {
213                log.error("Caught an error in a function");
214                if (t instanceof RuntimeException) {
215                    throw ((RuntimeException) t);
216                }
217                throw new IllegalStateException(t);
218            }
219        }
220
221        void acceptWithError(T t) throws Throwable;
222
223        @Override
224        default ConsumerWithError<T> andThen(Consumer<? super T> after) {
225            return (a) -> {
226                acceptWithError(a);
227                if (after instanceof ConsumerWithError) {
228                    @SuppressWarnings("unchecked")
229                    ConsumerWithError<T> errorHandler = (ConsumerWithError<T>) after;
230                    errorHandler.acceptWithError(a);
231                } else {
232                    after.accept(a);
233                }
234            };
235        }
236    }
237
238    /**
239     * An extension of Function that allows functional code to throw
240     * an exception. If used in a context not expecting this class,
241     * the error will be caught, logged, and re-thrown.
242     */
243    @SuppressWarnings("unchecked")
244    public interface FunctionWithError<A, B> extends Function<A, B> {
245        default <V> FunctionWithError<A, V> andThen(Function<? super B, ? extends V> after) {
246            return o -> {
247                B result = applyWithError(o);
248                if (after instanceof FunctionWithError) {
249                    return ((FunctionWithError<? super B, ? extends V>) after).applyWithError(result);
250                }
251                return after.apply(result);
252            };
253        }
254
255        /**
256         * Handles the case where this object is used in a regular Stream API, which
257         * cannot handle errors. Logs the error and throws a runtime exception.
258         * @param a The input object of type A
259         * @return The output object of type B
260         */
261        @Override
262        default B apply(A a) {
263            try {
264                return applyWithError(a);
265            } catch (Throwable t) {
266                log.error("Caught an error in a function");
267                if (t instanceof RuntimeException) {
268                    throw ((RuntimeException) t);
269                }
270                throw new IllegalStateException(t);
271            }
272        }
273
274        /**
275         * Implements a function transforming an object of type A to an object of
276         * type B. This function can throw an arbitrary error.
277         *
278         * @param object The input object
279         * @return The output object
280         * @throws Throwable if any errors occur
281         */
282        B applyWithError(A object) throws Throwable;
283
284        /**
285         * Creates an error-friendly function composition that first translates from
286         * input type V to intermediate type A, then translates from A to B.
287         *
288         * B = f2(f1(V))
289         *
290         * @param before The function to invoke before invoking this function
291         * @param <V> The input type of the 'before' function
292         * @return The composed FunctionWithError
293         */
294        @Override
295        default <V> FunctionWithError<V, B> compose(Function<? super V, ? extends A> before) {
296            if (before instanceof FunctionWithError) {
297                return (v) -> applyWithError(((FunctionWithError<? super V, ? extends A>) before).applyWithError(v));
298            } else {
299                return (v) -> applyWithError(before.apply(v));
300            }
301        }
302
303    }
304
305    /**
306     * An extension of Supplier that allows functional code to throw
307     * an exception. If used in a context not expecting this class,
308     * the error will be caught, logged, and re-thrown.
309     */
310    public interface SupplierWithError<T> extends Supplier<T> {
311        @Override
312        default T get() {
313            try {
314                return getWithError();
315            } catch (Throwable t) {
316                log.error("Caught an error in a function");
317                if (t instanceof RuntimeException) {
318                    throw ((RuntimeException) t);
319                }
320                throw new IllegalStateException(t);
321            }
322        }
323
324        T getWithError() throws Throwable;
325    }
326
327    /**
328     * A dual Consumer and BiConsumer that just does nothing, eating
329     * the object
330     */
331    public static class NullConsumer implements Consumer<Object>, BiConsumer<Object, Object> {
332
333        @Override
334        public void accept(Object o, Object o2) {
335
336        }
337
338        @Override
339        public void accept(Object o) {
340
341        }
342    }
343
344    /**
345     * Wrapper class so that Functions.otob() can be used as both a
346     * function and a predicate
347     */
348    public static class OtobWrapper implements Function<Object, Boolean>, Predicate<Object> {
349
350        /**
351         * A holder object causing the singleton to be initialized on first use.
352         * The JVM automatically synchronizes it.
353         */
354        private static final class OtobWrapperSingletonHolder {
355            /**
356             * The singleton OtobWrapper object
357             */
358            static final OtobWrapper _singleton = new OtobWrapper();
359        }
360
361        /**
362         * Gets an instance of OtobWrapper. At this time, this is a Singleton
363         * object, but you should not depend on that behavior.
364         *
365         * @return an {@link OtobWrapper} object
366         */
367        public static OtobWrapper get() {
368            return OtobWrapperSingletonHolder._singleton;
369        }
370
371        private OtobWrapper() {
372            /* do not instantiate */
373        }
374
375        @Override
376        public Boolean apply(Object o) {
377            return Util.otob(o);
378        }
379
380        @Override
381        public boolean test(Object o) {
382            return Util.otob(o);
383        }
384    }
385
386    /**
387     * Logger shared among various functions
388     */
389    private static final Log log = LogFactory.getLog(Functions.class);
390
391    /**
392     * A flatMap() function to extract the account requests from a plan
393     */
394    public static Function<ProvisioningPlan, Stream<ProvisioningPlan.AccountRequest>> accountRequests() {
395        return plan -> plan == null ? Stream.empty() : Utilities.safeStream(plan.getAccountRequests());
396    }
397
398    /**
399     * Returns a predicate that is always false
400     * @param <T> The arbitrary type
401     * @return The predicate
402     */
403    public static <T> Predicate<T> alwaysFalse() {
404        return (t) -> false;
405    }
406
407    /**
408     * Returns a predicate that is always true
409     * @param <T> The arbitrary type
410     * @return The predicate
411     */
412    public static <T> Predicate<T> alwaysTrue() {
413        return (t) -> true;
414    }
415
416    /**
417     * Returns a Consumer that will append all inputs to the given List.
418     * If the list is not modifiable, the error will occur at runtime.
419     *
420     * @param values The List to which things should be added
421     * @return The function to add items to that list
422     * @param <T> The type of the items
423     */
424    public static <T> Consumer<T> appendTo(List<? super T> values) {
425        return values::add;
426    }
427
428    /**
429     * Returns a Consumer that will append all inputs to the given List.
430     * If the set is not modifiable, the error will occur at runtime.
431     *
432     * @param values The Set to which things should be added
433     * @return The function to add items to that set
434     * @param <T> The type of the items
435     */
436    public static <T> Consumer<T> appendTo(Set<? super T> values) {
437        return values::add;
438    }
439
440    /**
441     * Creates a Predicate that resolves to true if the given attribute on the argument
442     * resolves to the given value.
443     */
444    public static Predicate<? extends SailPointObject> attributeEquals(final String attributeName, final Object testValue) {
445        return spo -> {
446            Attributes<String, Object> attributes = Utilities.getAttributes(spo);
447            if (attributes != null) {
448                Object val = attributes.get(attributeName);
449                return Util.nullSafeEq(val, testValue);
450            }
451            return (testValue == null);
452        };
453    }
454
455    /**
456     * Creates a flatMap() stream to extract attribute requests from an account request
457     */
458    public static Function<ProvisioningPlan.AccountRequest, Stream<ProvisioningPlan.AttributeRequest>> attributeRequests() {
459        return accountRequest -> accountRequest == null ? Stream.empty() : Utilities.safeStream(accountRequest.getAttributeRequests());
460    }
461
462    /**
463     * Create a Predicate that resolves to true if the given attribute value is the same
464     * (per Sameness) as the given test value.
465     */
466    public static Predicate<? extends SailPointObject> attributeSame(final String attributeName, final Object testValue) {
467        return spo -> {
468            Attributes<String, Object> attributes = Utilities.getAttributes(spo);
469            if (attributes != null) {
470                Object val = attributes.get(attributeName);
471                return Sameness.isSame(val, testValue, false);
472            }
473            return (testValue == null);
474        };
475    }
476
477    /**
478     * Creates a BiConsumer that invokes the given Beanshell method,
479     * passing the inputs to the consumer.
480     *
481     * @param bshThis The Beanshell 'this' context
482     * @param methodName The method name to invoke
483     * @return The BiConsumer
484     */
485    public static BiConsumer<?, ?> bc(bsh.This bshThis, String methodName) {
486        return (a, b) -> {
487            Object[] params = new Object[]{a, b};
488            try {
489                bshThis.invokeMethod(methodName, params);
490            } catch (EvalError evalError) {
491                throw new RuntimeException(evalError);
492            }
493        };
494    }
495
496    /**
497     * Returns the 'boxed' class corresponding to the given primitive.
498     *
499     * @param prim The primitive class, e.g. Long.TYPE
500     * @return The boxed class, e.g., java.lang.Long
501     */
502    public static Class<?> box(Class<?> prim) {
503        Objects.requireNonNull(prim, "The class to box must not be null");
504        if (prim.equals(Long.TYPE)) {
505            return Long.class;
506        } else if (prim.equals(Integer.TYPE)) {
507            return Integer.class;
508        } else if (prim.equals(Short.TYPE)) {
509            return Short.class;
510        } else if (prim.equals(Character.TYPE)) {
511            return Character.class;
512        } else if (prim.equals(Byte.TYPE)) {
513            return Byte.class;
514        } else if (prim.equals(Boolean.TYPE)) {
515            return Boolean.class;
516        } else if (prim.equals(Float.TYPE)) {
517            return Float.class;
518        } else if (prim.equals(Double.TYPE)) {
519            return Double.class;
520        }
521        throw new IllegalArgumentException("Unrecognized primitive type: " + prim.getName());
522    }
523
524    /**
525     * Creates a Consumer that passes each input to the given beanshell method in the
526     * given namespace.
527     */
528    public static ConsumerWithError<Object> c(bsh.This bshThis, String methodName) {
529        return object -> {
530            Object[] params = new Object[]{object};
531            bshThis.invokeMethod(methodName, params);
532        };
533    }
534
535    /**
536     * Creates a Consumer to invoke a method on each input.
537     */
538    public static ConsumerWithError<Object> c(String methodName) {
539        return object -> {
540            Method method = object.getClass().getMethod(methodName);
541            method.invoke(object);
542        };
543    }
544
545    /**
546     * Creates a Consumer to invoke a method on an object passed to it. The
547     * remaining inputs arguments will be provided as arguments to the method.
548     */
549    public static ConsumerWithError<Object> c(String methodName, Object... inputs) {
550        return object -> {
551            Method method = findMethod(object.getClass(), methodName, false, inputs);
552            if (method != null) {
553                method.invoke(object, inputs);
554            }
555        };
556    }
557
558    /**
559     * Returns a function that will cast the input object to the given type
560     * or throw a ClassCastException.
561     */
562    public static <T> Function<Object, T> cast(Class<T> target) {
563        return target::cast;
564    }
565
566    /**
567     * Returns a Comparator that extracts the given property (by path) from the two
568     * input objects, then compares them. The property values must be Comparable to
569     * each other.
570     */
571    @SuppressWarnings("rawtypes")
572    public static Comparator<Object> comparator(String property) {
573        return (a, b) -> {
574            try {
575                Object o1 = Utilities.getProperty(a, property);
576                Object o2 = Utilities.getProperty(b, property);
577                if (o1 instanceof Comparable && o2 instanceof Comparable) {
578                    return Util.nullSafeCompareTo((Comparable) o1, (Comparable) o2);
579                } else {
580                    throw new IllegalArgumentException("Property values must both implement Comparable; these are " + safeType(o1) + " and " + safeType(o2));
581                }
582            } catch (Exception e) {
583                throw new IllegalStateException(e);
584            }
585        };
586    }
587
588    /**
589     * Creates a Comparator that extracts the given property from the two input objects,
590     * then passes them both to the given Beanshell method, which must return an appropriate
591     * Comparator integer result.
592     */
593    public static Comparator<Object> comparator(String property, bsh.This bshThis, String methodName) {
594        return (a, b) -> {
595            try {
596                Object o1 = PropertyUtils.getProperty(a, property);
597                Object o2 = PropertyUtils.getProperty(b, property);
598                Object[] params = new Object[]{o1, o2};
599                return Util.otoi(bshThis.invokeMethod(methodName, params));
600            } catch (Exception e) {
601                throw new IllegalStateException(e);
602            }
603        };
604    }
605
606    /**
607     * Creates a Comparator that directly passes the two input objects to the given
608     * Beanshell method. The method must return an appropriate Comparator integer
609     * result.
610     */
611    public static Comparator<Object> comparator(bsh.This bshThis, String methodName) {
612        return (a, b) -> {
613            Object[] params = new Object[]{a, b};
614            try {
615                return Util.otoi(bshThis.invokeMethod(methodName, params));
616            } catch (EvalError evalError) {
617                throw new IllegalStateException(evalError);
618            }
619        };
620    }
621
622    /**
623     * Creates a Consumer that invokes the given static method on the given
624     * class for each input object. (The "cs" stands for consumer static.)
625     */
626    public static ConsumerWithError<Object> cs(Class<?> sourceType, String methodName) {
627        return object -> {
628            Object[] params = new Object[]{object};
629            Method method = findMethod(sourceType, methodName, true, params);
630            if (method != null) {
631                method.invoke(null, params);
632            }
633        };
634    }
635
636    /**
637     * Creates a Consumer that invokes the given static method on the given
638     * class for each input object, passing the object as the first method
639     * parameter and the given param1 as the second. (The "cs" stands for
640     * consumer static.)
641     */
642    public static ConsumerWithError<Object> cs(Class<?> sourceType, String methodName, Object param1) {
643        return object -> {
644            Object[] params = new Object[]{object, param1};
645            Method method = findMethod(sourceType, methodName, true, params);
646            if (method != null) {
647                method.invoke(null, params);
648            }
649        };
650    }
651
652    /**
653     * Creates a Consumer that invokes the given static method on the given
654     * class for each input object, passing the object as the first method
655     * parameter, the given param1 as the second, and the given param2 as the
656     * third.
657     */
658    public static ConsumerWithError<Object> cs(Class<?> sourceType, String methodName, Object param1, Object param2) {
659        return object -> {
660            Object[] params = new Object[]{object, param1, param2};
661            Method method = findMethod(sourceType, methodName, true, params);
662            if (method != null) {
663                method.invoke(null, params);
664            }
665        };
666    }
667
668    /**
669     * Logs whatever is passed in at debug level, if the logger has debug enabled
670     * @param <T> The type of the object
671     * @return The consumer
672     */
673    public static <T> Consumer<T> debug() {
674        return debug(log);
675    }
676
677    /**
678     * Logs whatever is passed in at debug level, if the logger has debug enabled
679     * @param logger The logger to which the value should be logged
680     * @param <T> The type of the object
681     * @return The consumer
682     */
683    public static <T> Consumer<T> debug(Log logger) {
684        return obj -> {
685            if (logger.isDebugEnabled()) {
686                logger.debug(obj);
687            }
688        };
689    }
690
691    /**
692     * Logs the input as XML, useful with peek() in a stream. Since no logger
693     * is specified, uses this class's own logger.
694     *
695     * @param <T> the input type
696     * @return The consumer
697     */
698    public static <T extends AbstractXmlObject> Consumer<T> debugXml() {
699        return debugXml(log);
700    }
701
702    /**
703     * Logs the input as XML, useful with peek() in a stream
704     * @param logger The logger to which the object should be logged
705     * @param <T> the input type
706     * @return The consumer
707     */
708    public static <T extends AbstractXmlObject> Consumer<T> debugXml(Log logger) {
709        return obj -> {
710            try {
711                if (logger.isDebugEnabled() && obj instanceof AbstractXmlObject) {
712                    logger.debug(obj.toXml());
713                }
714            } catch (GeneralException e) {
715                logger.error("Caught an error attempting to debug-log an object XML", e);
716            }
717        };
718    }
719
720    /**
721     * Creates a Predicate that returns true if the input string ends with the given suffix
722     * @param suffix The suffix
723     * @return True if the input string ends with the suffix
724     */
725    public static Predicate<String> endsWith(String suffix) {
726        return (s) -> s.endsWith(suffix);
727    }
728
729    /**
730     * Returns a Predicate that returns true if the two values are null-safe equals.
731     * Two null values will be considered equal.
732     *
733     * {@link Util#nullSafeEq(Object, Object)} is used under the hood.
734     *
735     * @param value The value to which each input should be compared
736     * @param <T> The type of the input expected
737     * @return The predicate
738     */
739    public static <T> Predicate<? extends T> eq(final T value) {
740        return o -> Util.nullSafeEq(o, value, true);
741    }
742
743    /**
744     * Returns a Predicate that returns true if the extracted value from the input is equal
745     * (in a null-safe way) to the test value. If your test value is itself a Predicate,
746     * it will be invoked to test the extracted value.
747     *
748     * @param valueExtractor A function to extract a value for comparison from the actual input object
749     * @param testValue The text value
750     */
751    public static <K, T> Predicate<K> eq(Function<K, T> valueExtractor, Object testValue) {
752        return (input) -> {
753            T value = valueExtractor.apply(input);
754            if (testValue instanceof Predicate) {
755                return ((Predicate<T>) testValue).test(value);
756            } else {
757                return Util.nullSafeEq(value, testValue, true);
758            }
759        };
760    }
761
762    /**
763     * Returns a Predicate that resolves to true when tested against an object that is
764     * equal to the input to this method.
765     *
766     * @param value The value to test for equality with the Predicate input
767     * @return The predicate
768     */
769    public static Predicate<String> eqIgnoreCase(final String value) {
770        return o -> Util.nullSafeCaseInsensitiveEq(o, value);
771    }
772
773    /**
774     * Returns a function that extracts the Nth matching group from applying the
775     * regular expression to the input string.
776     *
777     * The return value will be an empty Optional if the input does not match the
778     * regular expression or if the group was not matched. Otherwise, it will be
779     * an Optional containing the contents of the matched group.
780     *
781     * @param regex The regular expression
782     * @param matchGroup Which match group to return (starting with 1)
783     * @return A function with the above behavior
784     */
785    public static Function<String, Optional<String>> extractRegexMatchGroup(final String regex, final int matchGroup) {
786        Pattern pattern = Pattern.compile(regex);
787
788        return string -> {
789            Matcher matcher = pattern.matcher(string);
790            if (matcher.find()) {
791                String group = matcher.group(matchGroup);
792                return Optional.ofNullable(group);
793            } else {
794                return Optional.empty();
795            }
796        };
797    }
798
799    /**
800     * Type-free version of {@link Functions#f(String, Class)}
801     */
802    public static <K> Function<K, Object> f(String methodName) {
803        return f(methodName, Object.class);
804    }
805
806    /**
807     * Type-free version of {@link Functions#f(String, Class, Object...)}
808     */
809    public static <K> Function<K, Object> f(String methodName, Object... parameters) {
810        return f(methodName, Object.class, parameters);
811    }
812
813    /**
814     * Creates a Function that invokes the given method on the input object, and
815     * returns the output of that method. The given parameters will be passed to
816     * the method.
817     *
818     * NOTE: Beanshell will be confused if you don't pass at least one argument.
819     *
820     * @param methodName The method name to invoke
821     * @param expectedType The expected type of the output
822     * @param parameters The remaining parameters to pass to the method
823     * @param <T> The value
824     * @return The function
825     */
826    public static <K, T> Function<K, T> f(String methodName, Class<T> expectedType, Object... parameters) {
827        return object -> {
828            try {
829                Class<?> cls = object.getClass();
830                Method method = findMethod(cls, methodName, false, parameters);
831                if (method != null) {
832                    return expectedType.cast(method.invoke(object, parameters));
833                }
834            } catch (Exception e) {
835                /* Ignore */
836            }
837            return null;
838        };
839    }
840
841    /**
842     * Creates a Function that invokes the named Beanshell method, passing the input
843     * object as its parameter, and returning the method's return value.
844     *
845     * A simplified type-free version of {@link Functions#f(This, String, Class, Object...)}
846     *
847     * @param bshThis The object on which the method should be invoked
848     * @param methodName The method name to invoke
849     * @return The function
850     */
851    public static Function<Object, Object> f(bsh.This bshThis, String methodName) {
852        return f(bshThis, methodName, Object.class);
853    }
854
855    /**
856     * Creates a Function that invokes the named Beanshell method, passing the input
857     * object as its parameter, and returning the method's return value.
858     *
859     * This is roughly equivalent to the class method syntax, ClassName::method.
860     *
861     * @param bshThis The object on which the method should be invoked
862     * @param methodName The method name to invoke
863     * @param expectedResult The expected type of the output
864     * @param parameters The other parameters to pass to the method
865     * @param <B> The type of the output
866     * @return The function
867     */
868    public static <B> Function<Object, B> f(bsh.This bshThis, String methodName, Class<B> expectedResult, Object... parameters) {
869        return object -> {
870            try {
871                List<Object> params = new ArrayList<>();
872                params.add(object);
873                if (parameters != null) {
874                    params.addAll(Arrays.asList(parameters));
875                }
876                return expectedResult.cast(bshThis.invokeMethod(methodName, params.toArray()));
877            } catch (Exception e) {
878                // TODO Handle this with an Either
879                throw new RuntimeException(e);
880            }
881        };
882    }
883
884    /**
885     * Creates a Function that invokes the named method on the given target object,
886     * passing the input item as its parameter.
887     *
888     * This is roughly equivalent to the instance method syntax, obj::method, and can
889     * be used to do stuff equivalent to:
890     *
891     *  list.stream().filter(map::containsKey)...
892     *
893     * @param target The object on which the method should be invoked
894     * @param methodName The method name to invoke
895     * @param expectedType The expected type of the output
896     * @param <T> The type of the output
897     * @return The function
898     */
899    public static <T> Function<Object, T> f(Object target, String methodName, Class<T> expectedType) {
900        if (target instanceof bsh.This) {
901            // Beanshell is bad at calling this method; help it out
902            return f((bsh.This) target, methodName, expectedType, new Object[0]);
903        }
904        return object -> {
905            try {
906                Object[] params = new Object[]{object};
907                Class<?> sourceType = target.getClass();
908                Method method = findMethod(sourceType, methodName, false, params);
909                if (method != null) {
910                    return expectedType.cast(method.invoke(target, object));
911                } else {
912                    log.warn("Could not find matching method " + methodName + " in target class " + target.getClass());
913                }
914            } catch (Exception e) {
915                /* Ignore */
916            }
917            return null;
918        };
919    }
920
921    /**
922     * Creates a Function that invokes the given method on the input object, and
923     * returns the output of that method. This is essentially equivalent to the
924     * class method reference syntax Class::method.
925     *
926     * @param methodName The method name to invoke
927     * @param expectedType The expected type of the output
928     * @param <T> The value
929     * @return The function
930     * @param <K> The input type
931     */
932    public static <K, T> Function<K, T> f(String methodName, Class<T> expectedType) {
933        return object -> {
934            try {
935                Class<?> cls = object.getClass();
936                Method method = cls.getMethod(methodName);
937                return expectedType.cast(method.invoke(object));
938            } catch (Exception e) {
939                /* Ignore */
940            }
941            return null;
942        };
943    }
944
945    /**
946     * Finds the most specific accessible Method on the given Class that accepts
947     * the parameters provided. If two methods are equally specific, which would
948     * usually result in an "ambiguous method call" compiler error, an arbitrary
949     * one will be returned.
950     *
951     * @param toSearch   The class to search for the method
952     * @param name       The name of the method to locate
953     * @param findStatic True if we should look at static methods, false if we should look at instance methods
954     * @param params     The intended parameters to the method
955     * @return The Method discovered, or null if none match
956     */
957    public static Method findMethod(Class<?> toSearch, String name, boolean findStatic, Object... params) {
958        boolean hasNull = false;
959        for(Object in : params) {
960            if (in == null) {
961                hasNull = true;
962            }
963        }
964        List<Method> foundMethods = new ArrayList<>();
965        method:
966        for (Method m : toSearch.getMethods()) {
967            if (Modifier.isStatic(m.getModifiers()) != findStatic) {
968                continue;
969            }
970            if (m.getName().equals(name)) {
971                Parameter[] paramTypes = m.getParameters();
972                boolean isVarArgs = m.isVarArgs();
973                // Quick and easy case
974                if (params.length == 0 && paramTypes.length == 0) {
975                    foundMethods.add(m);
976                } else if (params.length == paramTypes.length) {
977                    int index = 0;
978                    for (Parameter p : paramTypes) {
979                        Object param = params[index];
980                        if (param == null && p.getType().isPrimitive()) {
981                            // Can't pass a 'null' to a primitive input, will be an NPE
982                            continue method;
983                        } else if (param != null && !isAssignableFrom(p.getType(), params[index].getClass())) {
984                            continue method;
985                        }
986                        index++;
987                    }
988                    foundMethods.add(m);
989                } else if (params.length == paramTypes.length - 1 && isVarArgs) {
990                    for (int index = 0; index < paramTypes.length - 1; index++) {
991                        Parameter p = paramTypes[index];
992                        if (params.length > index) {
993                            Object param = params[index];
994                            if (param == null && p.getType().isPrimitive()) {
995                                // Can't pass a 'null' to a primitive input, will be an NPE
996                                continue method;
997                            } else if (param != null && !isAssignableFrom(p.getType(), params[index].getClass())) {
998                                continue method;
999                            }
1000                        }
1001                    }
1002                    foundMethods.add(m);
1003                }
1004            }
1005        }
1006        if (foundMethods.size() == 0) {
1007            return null;
1008        } else if (foundMethods.size() == 1) {
1009            return foundMethods.get(0);
1010        } else if (hasNull) {
1011            // We can't proceed here because we can't narrow down what the
1012            // caller meant by a null. The compiler could do it if we cast
1013            // the null explicitly, but we can't do it at runtime.
1014            return null;
1015        }
1016
1017        Comparator<Method> methodComparator = (m1, m2) -> {
1018            int paramCount = m1.getParameterCount();
1019            int weight = 0;
1020            for (int p = 0; p < paramCount; p++) {
1021                Class<?> c1 = m1.getParameterTypes()[p];
1022                Class<?> c2 = m2.getParameterTypes()[p];
1023
1024                if (!c1.equals(c2)) {
1025                    if (isAssignableFrom(c2, c1)) {
1026                        weight--;
1027                    } else {
1028                        weight++;
1029                    }
1030                }
1031            }
1032            if (weight == 0) {
1033                Class<?> r1 = m1.getReturnType();
1034                Class<?> r2 = m2.getReturnType();
1035
1036                if (!r1.equals(r2)) {
1037                    if (isAssignableFrom(r2, r1)) {
1038                        weight--;
1039                    } else {
1040                        weight++;
1041                    }
1042                }
1043            }
1044            return weight;
1045        };
1046
1047        foundMethods.sort(methodComparator);
1048
1049        return foundMethods.get(0);
1050    }
1051
1052    /**
1053     * Transforms a provisioning plan into a list of requests for the given application
1054     * @param application The application name
1055     * @return The list of account requests
1056     */
1057    public static Function<ProvisioningPlan, List<ProvisioningPlan.AccountRequest>> findRequests(String application) {
1058        return plan -> plan.getAccountRequests(application);
1059    }
1060
1061    /**
1062     * Transforms a provisioning plan into a list of requests with the given operation
1063     * @param operation The operation to look for
1064     * @return The list of account requests
1065     */
1066    public static Function<ProvisioningPlan, List<ProvisioningPlan.AccountRequest>> findRequests(ProvisioningPlan.AccountRequest.Operation operation) {
1067        return plan -> Utilities.safeStream(plan.getAccountRequests()).filter(ar -> Util.nullSafeEq(ar.getOperation(), operation)).collect(Collectors.toList());
1068    }
1069
1070    /**
1071     * Runs the given consumer for each item in the collection
1072     * @param values The values to iterate
1073     * @param consumer The consumer to apply to each
1074     * @param <T> The type of the collection
1075     */
1076    public static <T> void forEach(Collection<T> values, Consumer<T> consumer) {
1077        values.forEach(consumer);
1078    }
1079
1080    /**
1081     * Runs the given consumer for each entry in the map
1082     * @param values The map of values
1083     * @param consumer The consumer of the map entries
1084     * @param <A> The key type
1085     * @param <B> The value type
1086     */
1087    public static <A, B> void forEach(Map<A, B> values, Consumer<Map.Entry<A, B>> consumer) {
1088        values.entrySet().forEach(consumer);
1089    }
1090
1091    /**
1092     * Runs the given bi-consumer for all of the values in the map
1093     * @param values The values
1094     * @param consumer The bi-consumer to be passed the key and value
1095     * @param <A> The key type
1096     * @param <B> The value type
1097     */
1098    public static <A, B> void forEach(Map<A, B> values, BiConsumer<A, B> consumer) {
1099        values.forEach(consumer);
1100    }
1101
1102    /**
1103     * Runs the given consumer for each remaining item returned by the Iterator
1104     * @param values The values to iterate
1105     * @param consumer The consumer to apply to each
1106     * @param <A> The type of the values
1107     */
1108    public static <A> void forEach(Iterator<A> values, Consumer<A> consumer) {
1109        values.forEachRemaining(consumer);
1110    }
1111
1112    /**
1113     * Invokes the named static function on the given class, passing the input object to it.
1114     * The output will be cast to the expected type and returned.
1115     */
1116    public static <T> Function<Object, T> fs(Class<?> targetClass, String methodName, Class<T> expectedType) {
1117        return object -> {
1118            try {
1119                Class<?> cls = object.getClass();
1120                Method method = findMethod(targetClass, methodName, true, object);
1121                if (method == null) {
1122                    log.warn("Could not find matching static method " + methodName + " in target class " + targetClass);
1123                } else {
1124                    return expectedType.cast(method.invoke(null, object));
1125                }
1126            } catch (Exception e) {
1127                /* Ignore */
1128            }
1129            return null;
1130        };
1131    }
1132
1133    /**
1134     * Constructs a Supplier that 'curries' the given Function, applying
1135     * it to the input object and returning the result. This allows you to
1136     * do logger-related structures like this in Beanshell:
1137     *
1138     * ftoc(someObject, getStringAttribute("hi"))
1139     *
1140     * @param inputObject The input object to curry
1141     * @param function The function to apply to the input object
1142     * @return A supplier wrapping the object and function call
1143     * @param <In> The input object type
1144     * @param <Out> The output object type
1145     */
1146    public static <In, Out> Supplier<Out> ftoc(In inputObject, Function<? super In, ? extends Out> function) {
1147        return () -> function.apply(inputObject);
1148    }
1149
1150    /**
1151     * Functionally implements the {@link Utilities#getProperty(Object, String)} method
1152     * @param beanPath The path to the property to retrieve from the object
1153     */
1154    public static Function<Object, Object> get(String beanPath) {
1155        return get(Object.class, beanPath);
1156    }
1157
1158    /**
1159     * Functionally implements the {@link Utilities#getProperty(Object, String)} method.
1160     * If the value is not of the expected type, returns null silently.
1161     *
1162     * @param beanPath The path to the property to retrieve from the object
1163     */
1164    public static <T> Function<Object, T> get(Class<T> expectedType, String beanPath) {
1165        return obj -> {
1166            try {
1167                return expectedType.cast(getProperty(obj, beanPath, true));
1168            } catch (Exception e) {
1169                return null;
1170            }
1171        };
1172    }
1173
1174    /**
1175     * If the input object has Attributes, retrieves the attribute with the given name,
1176     * otherwise returns null.
1177     *
1178     * @param attributeName The attribute name to retrieve
1179     * @return The function
1180     */
1181    public static Function<? extends SailPointObject, Object> getAttribute(final String attributeName) {
1182        return spo -> {
1183            Attributes<String, Object> attributes = Utilities.getAttributes(spo);
1184            if (attributes != null) {
1185                return attributes.get(attributeName);
1186            }
1187            return null;
1188        };
1189    }
1190
1191    /**
1192     * Extracts a boolean attribute from the input object, or false if the
1193     * attribute is not present.
1194     *
1195     * @param attributeName The attribute name
1196     * @return The function
1197     */
1198    public static Function<? extends SailPointObject, Boolean> getBooleanAttribute(final String attributeName) {
1199        return getBooleanAttribute(attributeName, false);
1200    }
1201
1202    /**
1203     * Extracts a boolean attribute from the input object, or the default value if the
1204     * attribute is not present.
1205     *
1206     * @param attributeName The attribute name
1207     * @param defaultValue The default value
1208     * @return The function
1209     */
1210    public static Function<? extends SailPointObject, Boolean> getBooleanAttribute(final String attributeName, final boolean defaultValue) {
1211        return spo -> {
1212            Attributes<String, Object> attributes = Utilities.getAttributes(spo);
1213            if (attributes != null) {
1214                return attributes.getBoolean(attributeName, defaultValue);
1215            }
1216            return defaultValue;
1217        };
1218    }
1219
1220    /**
1221     * Extracts the list of entitlements from the given Identity object, optionally
1222     * filtering them with the filter if provided. This is a direct query against the
1223     * database.
1224     *
1225     * @param context The context
1226     * @param optionalFilter An optional filter which will be used to decide which entitlements to return
1227     * @return The function
1228     */
1229    public static Function<Identity, List<IdentityEntitlement>> getEntitlements(SailPointContext context, Filter optionalFilter) {
1230        return identity -> {
1231            try {
1232                QueryOptions qo = new QueryOptions();
1233                qo.add(Filter.eq("identity.id", identity.getId()));
1234                if (optionalFilter != null) {
1235                    qo.add(optionalFilter);
1236                }
1237                List<IdentityEntitlement> entitlements = context.getObjects(IdentityEntitlement.class, qo);
1238                if (entitlements == null) {
1239                    entitlements = new ArrayList<>();
1240                }
1241                return entitlements;
1242            } catch (GeneralException e) {
1243                return new ArrayList<>();
1244            }
1245        };
1246    }
1247
1248    /**
1249     * Gets all links associated with the given Identity. This will use IdentityService
1250     * to do a database query rather than pulling the links from the Identity object,
1251     * because the Identity object's list can be unreliable in long-running DB sessions.
1252     */
1253    public static Function<Identity, List<Link>> getLinks() {
1254        return (identity) -> {
1255            List<Link> output = new ArrayList<>();
1256            try {
1257                SailPointContext context = SailPointFactory.getCurrentContext();
1258                IdentityService service = new IdentityService(context);
1259                List<Link> links = service.getLinks(identity, 0, 0);
1260                if (links != null) {
1261                    output.addAll(links);
1262                }
1263            } catch (GeneralException e) {
1264                log.error("Caught an exception getting links for identity " + identity.getName() + " in a function");
1265            }
1266            return output;
1267        };
1268    }
1269
1270    /**
1271     * Gets all links associated with the given Identity on the given Application(s).
1272     */
1273    public static Function<Identity, List<Link>> getLinks(final String applicationName, final String... moreApplicationNames) {
1274        if (applicationName == null) {
1275            throw new IllegalArgumentException("Application name must not be null");
1276        }
1277
1278        final List<String> names = new ArrayList<>();
1279        names.add(applicationName);
1280        if (moreApplicationNames != null) {
1281            Collections.addAll(names, moreApplicationNames);
1282        }
1283
1284        return (identity) -> {
1285            List<Link> output = new ArrayList<>();
1286            try {
1287                SailPointContext context = SailPointFactory.getCurrentContext();
1288                for (String name : names) {
1289                    Application application = context.getObjectByName(Application.class, name);
1290                    IdentityService service = new IdentityService(context);
1291                    List<Link> links = service.getLinks(identity, application);
1292                    if (links != null) {
1293                        output.addAll(links);
1294                    }
1295                }
1296            } catch (GeneralException e) {
1297                log.error("Caught an exception getting links for identity " + identity.getName() + " in a function");
1298            }
1299            return output;
1300        };
1301    }
1302
1303    /**
1304     * Gets all links associated with the given Identity on the given Application
1305     */
1306    public static Function<Identity, List<Link>> getLinks(final Application application) {
1307        if (application == null) {
1308            throw new IllegalArgumentException("Application must not be null");
1309        }
1310        return getLinks(application.getName());
1311    }
1312
1313    /**
1314     * Gets the given attribute as a string from the input object
1315     */
1316    public static Function<? extends SailPointObject, String> getStringAttribute(final String attributeName) {
1317        return spo -> {
1318            Attributes<String, Object> attributes = Utilities.getAttributes(spo);
1319            if (attributes != null) {
1320                return attributes.getString(attributeName);
1321            }
1322            return null;
1323        };
1324    }
1325
1326    /**
1327     * Gets the given attribute as a string list from the input object
1328     */
1329    public static Function<? extends SailPointObject, List<String>> getStringListAttribute(final String attributeName) {
1330        return spo -> {
1331            Attributes<String, Object> attributes = Utilities.getAttributes(spo);
1332            if (attributes != null) {
1333                return attributes.getStringList(attributeName);
1334            }
1335            return null;
1336        };
1337    }
1338
1339    /**
1340     * Returns a Predicate that resolves to true if the input AccountRequest has
1341     * at least one AttributeRequest corresponding to every one of the attribute names
1342     * given
1343     *
1344     * @param attributeName The list of names to match against
1345     * @return The predicate
1346     */
1347    public static Predicate<ProvisioningPlan.AccountRequest> hasAllAttributeRequest(final String... attributeName) {
1348        return hasAttributeRequest(true, attributeName);
1349    }
1350
1351    /**
1352     * Returns a Predicate that resolves to true if the input AccountRequest has
1353     * at least one AttributeRequest corresponding to any of the attribute names given
1354     * @param attributeName The list of names to match against
1355     * @return The predicate
1356     */
1357    public static Predicate<ProvisioningPlan.AccountRequest> hasAnyAttributeRequest(final String... attributeName) {
1358        return hasAttributeRequest(false, attributeName);
1359    }
1360
1361    /**
1362     * Resolves to true if the input object has a non-null value for the given attribute
1363     */
1364    public static Predicate<? extends SailPointObject> hasAttribute(final String attributeName) {
1365        return spo -> {
1366            Attributes<String, Object> attributes = Utilities.getAttributes(spo);
1367            if (attributes != null) {
1368                return attributes.get(attributeName) != null;
1369            }
1370            return false;
1371        };
1372    }
1373
1374    /**
1375     * Returns a predicate that resolves to true if the account request has the attribute(s)
1376     * in question, controlled by the 'useAnd' parameter.
1377     *
1378     * @param useAnd        If true, all names must match an AttributeRequest. If false, only one match will resolve in a true.
1379     * @param attributeName The list of attribute names
1380     */
1381    public static Predicate<ProvisioningPlan.AccountRequest> hasAttributeRequest(boolean useAnd, final String... attributeName) {
1382        List<String> attributeNames = new ArrayList<>();
1383        if (attributeName != null) {
1384            Collections.addAll(attributeNames, attributeName);
1385        }
1386        return spo -> {
1387            Set<String> fieldNames = new HashSet<>();
1388            for (ProvisioningPlan.AttributeRequest attributeRequest : Util.safeIterable(spo.getAttributeRequests())) {
1389                if (attributeNames.isEmpty() || attributeNames.contains(attributeRequest.getName())) {
1390                    if (useAnd) {
1391                        fieldNames.add(attributeRequest.getName());
1392                    } else {
1393                        return true;
1394                    }
1395                }
1396            }
1397            return useAnd && fieldNames.size() > attributeNames.size();
1398        };
1399    }
1400
1401    /**
1402     * Resolves to the ID of the input SailPointObject
1403     */
1404    public static Function<? extends SailPointObject, String> id() {
1405        return SailPointObject::getId;
1406    }
1407
1408    /**
1409     * Resolves to true if the input object is in the given list
1410     */
1411    public static Predicate<?> in(final List<String> list) {
1412        return o -> Util.nullSafeContains(list, o);
1413    }
1414
1415    /**
1416     * Resolves to true if the input object is in the given set
1417     */
1418    public static Predicate<?> in(final Set<String> list) {
1419        return in(new ArrayList<>(list));
1420    }
1421
1422    /**
1423     * Returns true if targetType is assignable from otherType, e.g. if the following code
1424     * would not fail to compile:
1425     *
1426     * OtherType ot = new OtherType();
1427     * TargetType tt = ot;
1428     *
1429     * This is also equivalent to 'b instanceof A' or 'otherType extends targetType'.
1430     *
1431     * Primitive types and their boxed equivalents have special handling.
1432     *
1433     * @param targetType The first (parent-ish) class
1434     * @param otherType  The second (child-ish) class
1435     * @return True if cls1 is assignable from cls2
1436     */
1437    public static boolean isAssignableFrom(Class<?> targetType, Class<?> otherType) {
1438        return Utilities.isAssignableFrom(targetType, otherType);
1439    }
1440
1441    /**
1442     * A predicate that resolves to true if the boolean attribute of the input object
1443     * is true according to {@link Attributes#getBoolean(String)}.
1444     *
1445     * @param attributeName The attribute name to query
1446     * @return The predicate
1447     */
1448    public static Predicate<? extends SailPointObject> isBooleanAttribute(final String attributeName) {
1449        return isBooleanAttribute(attributeName, false);
1450    }
1451
1452    /**
1453     * A predicate that resolves to true if the boolean attribute of the input object
1454     * is true according to {@link Attributes#getBoolean(String, boolean)}.
1455     *
1456     * @param attributeName The attribute name to query
1457     * @param defaultValue The default to use if the attribute is empty
1458     * @return The predicate
1459     */
1460    public static Predicate<? extends SailPointObject> isBooleanAttribute(final String attributeName, boolean defaultValue) {
1461        return spo -> {
1462            Attributes<String, Object> attributes = Utilities.getAttributes(spo);
1463            if (attributes != null) {
1464                return attributes.getBoolean(attributeName, defaultValue);
1465            }
1466            return defaultValue;
1467        };
1468    }
1469
1470    /**
1471     * A predicate that resolves to true if the boolean property of the input object
1472     * is true according to {@link Util#otob(Object)}
1473     *
1474     * @param attributeName The property name to query
1475     * @return The predicate
1476     */
1477    public static Predicate<? extends SailPointObject> isBooleanProperty(final String attributeName) {
1478        return isBooleanProperty(attributeName, false);
1479    }
1480
1481    /**
1482     * A predicate that resolves to true if the boolean property of the input object
1483     * is true according to {@link Util#otob(Object)}
1484     *
1485     * @param attributeName The property name to query
1486     * @param defaultValue The default to use if the property is empty
1487     * @return The predicate
1488     */
1489    public static Predicate<? extends SailPointObject> isBooleanProperty(final String attributeName, boolean defaultValue) {
1490        return spo -> {
1491            try {
1492                Object property = getProperty(spo, attributeName);
1493                return Util.otob(property);
1494            } catch (GeneralException e) {
1495                /* Ignore */
1496            }
1497            return false;
1498        };
1499    }
1500
1501    /**
1502     * A predicate that resolves to true if the input is disabled. If the input object is of type Identity,
1503     * its {@link Identity#isInactive()} will be called. Otherwise, {@link SailPointObject#isDisabled()}.
1504     * @param <T> The type of the object
1505     * @return The predicate
1506     */
1507    public static <T extends SailPointObject> Predicate<T> isDisabled() {
1508        return spo -> {
1509            if (spo instanceof Identity) {
1510                return ((Identity) spo).isInactive();
1511            } else {
1512                return spo.isDisabled();
1513            }
1514        };
1515    }
1516
1517    /**
1518     * Returns a predicate that resolves to true if the input is empty according to Sameness
1519     * @param <T> The type of the object
1520     * @return The preidcate
1521     */
1522    public static <T> Predicate<T> isEmpty() {
1523        return Sameness::isEmpty;
1524    }
1525
1526    /**
1527     * Resolves to true if the input object is an instance of the target
1528     * class. For specific common classes, this uses a quicker instanceof,
1529     * and for everything else, it passes off to isAssignableFrom.
1530     * <p>
1531     * Null inputs will always be false.
1532     */
1533    public static <T> Predicate<T> isInstanceOf(Class<?> target) {
1534        Objects.requireNonNull(target);
1535        // Faster versions for specific common classes
1536        if (target.equals(String.class)) {
1537            return obj -> obj instanceof String;
1538        } else if (target.equals(List.class)) {
1539            return obj -> obj instanceof List;
1540        } else if (target.equals(Map.class)) {
1541            return obj -> obj instanceof Map;
1542        } else if (target.equals(SailPointObject.class)) {
1543            return obj -> obj instanceof SailPointObject;
1544        } else if (target.equals(Identity.class)) {
1545            return obj -> obj instanceof Identity;
1546        } else if (target.equals(Link.class)) {
1547            return obj -> obj instanceof Link;
1548        }
1549        return obj -> (obj != null && isAssignableFrom(target, obj.getClass()));
1550    }
1551
1552    /**
1553     * Returns a Predicate that resolves to true if the input Link's native identity is equal to the
1554     * comparison value.
1555     */
1556    public static Predicate<Link> isNativeIdentity(final String nativeIdentity) {
1557        return link -> Util.nullSafeEq(link.getNativeIdentity(), nativeIdentity);
1558    }
1559
1560    public static Predicate<String> isNotNullOrEmpty() {
1561        return Util::isNotNullOrEmpty;
1562    }
1563
1564    /**
1565     * Resolves to true if the object is not null
1566     */
1567    public static <T> Predicate<T> isNull() {
1568        return Objects::isNull;
1569    }
1570
1571    /**
1572     * Resolves to true if the given property on the given object is
1573     * not null or empty. Emptiness is defined by this class's isEmpty().
1574     */
1575    public static <T> Predicate<T> isNullOrEmpty(String propertyPath) {
1576        return o -> {
1577            try {
1578                Object property = getProperty(o, propertyPath);
1579                if (property == null) {
1580                    return true;
1581                }
1582                return isEmpty().test(property);
1583            } catch (GeneralException e) {
1584                /* Ignore this */
1585            }
1586            return true;
1587        };
1588    }
1589
1590    /**
1591     * Functional equivalent to Util.isNullOrEmpty
1592     */
1593    public static Predicate<String> isNullOrEmpty() {
1594        return Util::isNullOrEmpty;
1595    }
1596
1597    /**
1598     * Extracts the key from the given map entry
1599     */
1600    public static <T> Function<Map.Entry<T, ?>, T> key() {
1601        return Map.Entry::getKey;
1602    }
1603
1604    /**
1605     * Returns a Predicate that resolves to true if the input string matches the given pattern, as
1606     * equivalent to the Filter like() method
1607     */
1608    public static Predicate<? extends String> like(final String pattern, final Filter.MatchMode matchMode) {
1609        if (Util.isNullOrEmpty(pattern)) {
1610            throw new IllegalArgumentException("Pattern must not be null or empty");
1611        }
1612        if (matchMode.equals(Filter.MatchMode.EXACT)) {
1613            return eq(pattern);
1614        }
1615        return s -> {
1616            if (s == null || s.isEmpty()) {
1617                return false;
1618            }
1619            if (matchMode.equals(Filter.MatchMode.START)) {
1620                return s.startsWith(pattern);
1621            } else if (matchMode.equals(Filter.MatchMode.END)) {
1622                return s.endsWith(pattern);
1623            } else if (matchMode.equals(Filter.MatchMode.ANYWHERE)) {
1624                return s.contains(pattern);
1625            } else {
1626                return false;
1627            }
1628        };
1629    }
1630
1631    /**
1632     * Returns the Identity for the given Link object
1633     * @return Gets the Link identity
1634     */
1635    public static Function<Link, Identity> linkGetIdentity() {
1636        return Link::getIdentity;
1637    }
1638
1639    /**
1640     * Creates a predicate that tests whether the Link has the given application name
1641     * @param applicationName The application name
1642     * @return The predicate
1643     */
1644    public static Predicate<Link> linkIsApplication(String applicationName) {
1645        return (link) -> Util.nullSafeEq(link.getApplicationName(), applicationName);
1646    }
1647
1648    /**
1649     * Creates a predicate that tests whether the Link has the given application name.
1650     * The ID will be cached in the closure, so you do not need to keep the Application
1651     * object in the same Hibernate scope.
1652     *
1653     * @param application The application object
1654     * @return The predicate
1655     */
1656    public static Predicate<Link> linkIsApplication(Application application) {
1657        final String applicationId = application.getId();
1658        return (link) -> Util.nullSafeEq(link.getApplicationId(), applicationId);
1659    }
1660
1661    /**
1662     * Returns a functional getter for a map of a given type
1663     * @param index The index to get
1664     * @param <T> The type stored in the list
1665     * @return The output
1666     */
1667    public static <T> Function<List<T>, T> listSafeGet(int index) {
1668        return (list) -> {
1669            if (list.size() <= index) {
1670                return null;
1671            } else if (index < 0) {
1672                return null;
1673            } else {
1674                return list.get(index);
1675            }
1676        };
1677    }
1678
1679    /**
1680     * Logs the input object at warn() level in the default Functions logger.
1681     * This is intended for use with peek() in the middle of a stream.
1682     */
1683    public static <T> Consumer<T> log() {
1684        return (obj) -> {
1685            if (log.isWarnEnabled()) {
1686                log.warn(obj);
1687            }
1688        };
1689    }
1690
1691    /**
1692     * Logs the input object at warn() level in the provided logger
1693     * This is intended for use with peek() in the middle of a stream.
1694     */
1695    public static <T> Consumer<T> log(Log logger) {
1696        return (obj) -> {
1697            if (logger.isWarnEnabled()) {
1698                logger.warn(obj);
1699            }
1700        };
1701    }
1702
1703    /**
1704     * Converts the given object to XML and logs it to the default logger as a warning
1705     * @param <T> The object type
1706     * @return A consumer that will log the object
1707     */
1708    public static <T extends AbstractXmlObject> Consumer<T> logXml() {
1709        return logXml(log);
1710    }
1711
1712    /**
1713     * Converts the given object to XML and logs it to the given logger
1714     * @param logger The logger
1715     * @param <T> The object type
1716     * @return A consumer that will log the object
1717     */
1718    public static <T extends AbstractXmlObject> Consumer<T> logXml(Log logger) {
1719        return obj -> {
1720            try {
1721                if (logger.isWarnEnabled() && obj instanceof AbstractXmlObject) {
1722                    logger.warn(obj.toXml());
1723                }
1724            } catch (GeneralException e) {
1725                logger.error("Caught an error attempting to log an object XML", e);
1726            }
1727        };
1728    }
1729
1730    /**
1731     * For the given input key, returns the value of that key in the given map.
1732     *
1733     * @param map Returns the result of {@link MapUtil#get(Map, String)}
1734     * @return A function that takes a string and returns the proper value from the Map
1735     */
1736    public static Function<String, Object> lookup(final Map<String, Object> map) {
1737        return key -> {
1738            try {
1739                return MapUtil.get(map, key);
1740            } catch (GeneralException e) {
1741                /* Ignore */
1742                return null;
1743            }
1744        };
1745    }
1746
1747    /**
1748     * For the given input map, returns the value at the key. The key is a
1749     * MapUtil path.
1750     *
1751     * @param key Returns the result of {@link MapUtil#get(Map, String)}
1752     * @return A function that takes a Map and returns the value of the given key in that map
1753     */
1754    public static Function<Map<String, Object>, Object> lookup(final String key) {
1755        return map -> {
1756            try {
1757                return MapUtil.get(map, key);
1758            } catch (GeneralException e) {
1759                /* Ignore */
1760                return null;
1761            }
1762        };
1763    }
1764
1765    /**
1766     * Same as lookup(String), but casts the output to the expected type
1767     */
1768    public static <B> Function<Map<String, B>, B> lookup(final String key, final Class<B> expectedType) {
1769        return map -> {
1770            try {
1771                return expectedType.cast(MapUtil.get((Map<String, Object>) map, key));
1772            } catch (GeneralException e) {
1773                return null;
1774            }
1775        };
1776    }
1777
1778    /**
1779     * Returns a Function converting a string to an object of the given type,
1780     * looked up using the given SailPointContext.
1781     *
1782     * In 8.4, ensure that the SailPointContext is the correct one for the object
1783     * type. Certain objects are now in the Access History context.
1784     *
1785     * @param <T> The type of SailPointObject to look up
1786     * @param context The context to use to look up the object
1787     * @param sailpointClass The object type to read
1788     * @return A function to look up objects of the given type by ID or name
1789     */
1790    public static <T extends SailPointObject> Function<String, T> lookup(final SailPointContext context, final Class<T> sailpointClass) {
1791        return name -> {
1792            try {
1793                return context.getObject(sailpointClass, name);
1794            } catch (GeneralException e) {
1795                return null;
1796            }
1797        };
1798    }
1799
1800    /**
1801     * Returns a functional getter for a map of a given type
1802     * @param key The key to get
1803     * @param <T> The key type of the map
1804     * @param <U> The value type of the map
1805     * @return The output
1806     */
1807    public static <T, U> Function<Map<T, ? extends U>, U> mapGet(T key) {
1808        return (map) -> map.get(key);
1809    }
1810
1811    /**
1812     * Returns a functional predicate to compare a Map to the filter provided.
1813     *
1814     * @param filter The filter to execute against the Map
1815     * @param <T> The key type of the map
1816     * @param <U> The value type of the map
1817     * @return The output
1818     */
1819    public static <T, U> PredicateWithError<Map<T, U>> mapMatches(Filter filter) {
1820        return (map) -> {
1821            MapMatcher matcher = new MapMatcher(filter);
1822            return matcher.matches(map);
1823        };
1824    }
1825
1826    /**
1827     * Creates a map comparator for sorting a map against the given key(s). Additional
1828     * keys are implemented using recursive calls back to this method.
1829     *
1830     * @param key The first key for comparing
1831     * @param keys A list of additional keys for comparing
1832     * @return The comparator
1833     */
1834    @SafeVarargs
1835    public static <K> Comparator<Map<K, Object>> mapValueComparator(K key, K... keys) {
1836        Comparator<Map<K, Object>> mapComparator = Comparator.comparing(mapGet(key).andThen(otoa()));
1837        if (keys != null) {
1838            for(K otherKey : keys) {
1839                if (otherKey != null) {
1840                    mapComparator = mapComparator.thenComparing(mapValueComparator(otherKey));
1841                }
1842            }
1843        }
1844        return mapComparator;
1845    }
1846
1847    /**
1848     * Creates a Predicate that retrieves the key of the given name from the input
1849     * Map and returns true if the value equals the given value.
1850     *
1851     * @param key The key to query in the map
1852     * @param value The value to query in the map
1853     */
1854    public static <T, U> Predicate<Map<T, ? extends U>> mapValueEquals(T key, U value) {
1855        return eq(Functions.mapGet(key), value);
1856    }
1857
1858    /**
1859     * Resolves to true if the input object matches the filter. This ought to be thread-safe
1860     * if the SailPointFactory's current context is correct for the thread.
1861     *
1862     * {@link HybridObjectMatcher} is used to do the matching.
1863     *
1864     * @param filter The Filter to evaluate against the input object
1865     * @return A predicate returning true when the filter matches the input
1866     */
1867    public static PredicateWithError<? extends SailPointObject> matches(Filter filter) {
1868        return spo -> {
1869            HybridObjectMatcher matcher = new HybridObjectMatcher(SailPointFactory.getCurrentContext(), filter, false);
1870            return matcher.matches(spo);
1871        };
1872    }
1873
1874    /**
1875     * Resolves to true if the input object matches the filter. This ought to be thread-safe
1876     * if the SailPointFactory's current context is correct for the thread.
1877     *
1878     * {@link HybridObjectMatcher} is used to do the matching.
1879     *
1880     * @param filter The Filter to evaluate against the input object
1881     * @param matchType The class to match, which does not need to be a SailPointObject
1882     * @return A predicate returning true when the filter matches the input
1883     */
1884    public static <T> PredicateWithError<T> matches(Filter filter, Class<T> matchType) {
1885        return object -> {
1886            HybridObjectMatcher matcher = new HybridObjectMatcher(SailPointFactory.getCurrentContext(), filter, false);
1887            return matcher.matches(object);
1888        };
1889    }
1890
1891    /**
1892     * Resolves to true if the input object matches the filter. The filter will be
1893     * compiled when this method is called, and then the remainder is a simple
1894     * forward to {@link #matches(Filter)}.
1895     *
1896     * @param filterString The filter string to evaluate against the input object
1897     * @return A predicate returning true when the filter matches the input
1898     */
1899    public static PredicateWithError<? extends SailPointObject> matches(String filterString) {
1900        Filter filter = Filter.compile(filterString);
1901        return matches(filter);
1902    }
1903
1904    /**
1905     * Maps to the name of the input object
1906     */
1907    public static Function<? extends SailPointObject, String> name() {
1908        return SailPointObject::getName;
1909    }
1910
1911    /**
1912     * Resolves to true if the input object does not equal the value
1913     */
1914    public static <T> Predicate<? extends T> ne(final T value) {
1915        return eq(value).negate();
1916    }
1917
1918    /**
1919     * Returns a supplier returning a new, writeable list. This can be passed to {@link Collectors#toCollection(Supplier)} for example.
1920     * @param <T> The type of the list
1921     * @return the new ArrayList supplier
1922     */
1923    public static <T> Supplier<List<T>> newList() {
1924        return ArrayList::new;
1925    }
1926
1927    /**
1928     * Returns a supplier returning a new, writeable set. This can be passed to {@link Collectors#toCollection(Supplier)} for example.
1929     * @param <T> The type of the list
1930     * @return the new HashSet supplier
1931     */
1932    public static <T> Supplier<Set<T>> newSet() {
1933        return HashSet::new;
1934    }
1935
1936    /**
1937     * Returns a predicate that resolves to true if the input item is not null
1938     * @return The predicate
1939     */
1940    public static Predicate<?> nonNull() {
1941        return Objects::nonNull;
1942    }
1943
1944    /**
1945     * A very basic normalizer function that trims spaces and lowercases the
1946     * input string, then removes all non-ASCII characters. This should be replaced
1947     * with a better normalization algorithm in most cases.
1948     *
1949     * @return The function
1950     */
1951    public static Function<String, String> normalize() {
1952        return (s) -> s == null ? "" : s.trim().toLowerCase(Locale.getDefault()).replaceAll("[^A-Za-z0-9_]+", "");
1953    }
1954
1955    /**
1956     * Implements a generic "black hole" or "dev null" consumer that swallows
1957     * the object. This can be used as a sane default where another consumer
1958     * is required.
1959     */
1960    public static NullConsumer nothing() {
1961        return new NullConsumer();
1962    }
1963
1964    /**
1965     * Returns a function that transforms a value into an Optional that will
1966     * be empty if the value matches {@link Utilities#isNothing(Object)}.
1967     *
1968     * @return A function that transforms a value into an Optional
1969     * @param <T> The type of the object
1970     */
1971    public static <T> Function<T, Optional<T>> nothingToOptional() {
1972        return (item) -> {
1973            if (Utilities.isNothing(item)) {
1974                return Optional.empty();
1975            } else {
1976                return Optional.of(item);
1977            }
1978        };
1979    }
1980
1981    /**
1982     * Returns a function that transforms a value into a Stream that will
1983     * be empty if the value matches {@link Utilities#isNothing(Object)}.
1984     *
1985     * @return A function that transforms a value into an Optional
1986     * @param <T> The type of the object
1987     */
1988    public static <T> Function<T, Stream<T>> nothingToStream() {
1989        return (item) -> {
1990            if (Utilities.isNothing(item)) {
1991                return Stream.empty();
1992            } else {
1993                return Stream.of(item);
1994            }
1995        };
1996    }
1997
1998    /**
1999     * Creates a BiFunction that resolves to Boolean true if the two input objects are equal, ignoring case
2000     *
2001     * @return the BiFunction
2002     */
2003    public static BiFunction<Object, Object, Boolean> nullSafeEq() {
2004        return Util::nullSafeEq;
2005    }
2006
2007    /**
2008     * Transforms the input object into a non-null string
2009     */
2010    public static Function<Object, String> nullToEmpty() {
2011        return Utilities::safeString;
2012    }
2013
2014    /**
2015     * Resolves to true if the given AccountRequest's operation equals the given value
2016     */
2017    public static Predicate<ProvisioningPlan.AccountRequest> operationEquals(ProvisioningPlan.AccountRequest.Operation operation) {
2018        return accountRequest -> Util.nullSafeEq(accountRequest.getOperation(), operation);
2019    }
2020
2021    /**
2022     * Resolves to true if the given provisioning plan's operation equals the given value
2023     */
2024    public static Predicate<ProvisioningPlan.AttributeRequest> operationEquals(ProvisioningPlan.Operation operation) {
2025        return attributeRequest -> Util.nullSafeEq(attributeRequest.getOperation(), operation);
2026    }
2027
2028    /**
2029     * Returns a predicate that resolves to true if the input Optional is empty
2030     * @return The predicate
2031     */
2032    public static Predicate<Optional<?>> optionalEmpty() {
2033        return (o) -> !o.isPresent();
2034    }
2035
2036    /**
2037     * Returns a predicate that resolves to true if the input Optional is present
2038     * @return The predicate
2039     */
2040    public static Predicate<Optional<?>> optionalPresent() {
2041        return Optional::isPresent;
2042    }
2043
2044    /**
2045     * Returns a Beanshell-friendly equivalent to the JDK 9 Optional::stream
2046     * function. The stream will have zero or one elements and is intended
2047     * for use with {@link Stream#flatMap(Function)}.
2048     *
2049     * @return A function from an Optional to a Stream
2050     * @param <T> The type of the object
2051     */
2052    public static <T> Function<Optional<T>, Stream<T>> optionalToStream() {
2053        return o -> o.map(Stream::of).orElseGet(Stream::empty);
2054    }
2055
2056    /**
2057     * Creates a function equivalent to Util::otoa
2058     * @return The function
2059     */
2060    public static <T> Function<T, String> otoa() {
2061        return Util::otoa;
2062    }
2063
2064    /**
2065     * Returns an object that implements both Function and Predicate and returns
2066     * the result of Util::otob on the input object
2067     * @return The function/predicate
2068     */
2069    public static OtobWrapper otob() {
2070        return OtobWrapper.get();
2071    }
2072
2073    /**
2074     * Creates a function equivalent to Util::otoi
2075     * @return The function
2076     */
2077    public static <T> Function<T, Integer> otoi() {
2078        return Util::otoi;
2079    }
2080
2081    /**
2082     * Creates a function equivalent to Util::otol
2083     * @return The function
2084     */
2085    public static <T> Function<T, List<String>> otol() {
2086        return Util::otol;
2087    }
2088
2089    /**
2090     * Transforms a Function that returns a boolean into a predicate
2091     * @param f The function to transform
2092     * @return The predicate
2093     */
2094    public static <T> Predicate<T> p(Function<T, Boolean> f) {
2095        return f::apply;
2096    }
2097
2098    /**
2099     * Invokes the given method on the input object, resolving to the
2100     * otob truthy conversion of its output.
2101     */
2102    @SuppressWarnings("unchecked")
2103    public static <T> Predicate<T> p(String methodName) {
2104        return (Predicate<T>) p(f(methodName).andThen(Util::otob));
2105    }
2106
2107    /**
2108     * Invokes the given method against the target object, passing the
2109     * input object as its only parameter, then resolves to true if the
2110     * output is otob truthy.
2111     */
2112    public static Predicate<?> p(Object target, String methodName) {
2113        return p(f(target, methodName, Object.class).andThen(Util::otob));
2114    }
2115
2116    /**
2117     * Constructs a Predicate that invokes the given method against the
2118     * input object, providing the additional inputs as needed, then returns true
2119     * if the result is 'otob' truthy.
2120     */
2121    public static Predicate<?> p(String methodName, Object... inputs) {
2122        return p(f(methodName, inputs).andThen(Util::otob));
2123    }
2124
2125    /**
2126     * Invokes the given Beanshell method, which will receive the input
2127     * object as its sole parameter, and then resolves to true if the
2128     * method returns an otob truthy value.
2129     */
2130    public static Predicate<?> p(bsh.This bshThis, String methodName) {
2131        return p(f(bshThis, methodName).andThen(Util::otob));
2132    }
2133
2134    /**
2135     * Parses each incoming string as a date according to the provided format,
2136     * returning null if there is a parse exception
2137     */
2138    public static Function<String, Date> parseDate(String format) {
2139        if (Util.isNotNullOrEmpty(format)) {
2140            throw new IllegalArgumentException("The format string " + format + " is not valid");
2141        }
2142        // Also throws an exception if it's a bad format
2143        SimpleDateFormat preParse = new SimpleDateFormat(format);
2144        return date -> {
2145            final SimpleDateFormat formatter = new SimpleDateFormat(format);
2146            try {
2147                return formatter.parse(date);
2148            } catch (ParseException e) {
2149                if (log.isDebugEnabled()) {
2150                    log.debug("Could not parse input string " + date + " according to formatter " + format);
2151                }
2152                return null;
2153            }
2154        };
2155    }
2156
2157    /**
2158     * Returns a Predicate that resolves to true if the given plan has the given attribute on the given
2159     * application. This can be done in a more fluent way using Plans.find
2160     * if desired.
2161     */
2162    public static Predicate<ProvisioningPlan> planHasAttribute(String application, String attribute) {
2163        return plan -> (
2164                Utilities.safeStream(plan.getAccountRequests())
2165                        .filter(ar -> Util.nullSafeEq(ar.getApplicationName(), application))
2166                        .flatMap(ar -> Utilities.safeStream(ar.getAttributeRequests()))
2167                        .anyMatch(attr -> Util.nullSafeEq(attr.getName(), attribute)));
2168    }
2169
2170    /**
2171     * Creates a Predicate that returns true if provided a ProvisioningPlan that contains
2172     * an AccountRequest with the given Operation
2173     *
2174     * @param operation The operation to check for
2175     * @return The predicate
2176     */
2177    public static Predicate<ProvisioningPlan> planHasOperation(ProvisioningPlan.AccountRequest.Operation operation) {
2178        return plan -> (Utilities.safeStream(plan.getAccountRequests()).anyMatch(ar -> Util.nullSafeEq(ar.getOperation(), operation)));
2179    }
2180
2181    /**
2182     * Creates a Predicate that returns true if provided a ProvisioningPlan that contains
2183     * an AccountRequest with the given application name and request operation
2184     *
2185     * @param application The name of the application
2186     * @param operation The operation to check for
2187     * @return The predicate
2188     */
2189    public static Predicate<ProvisioningPlan> planHasOperation(String application, ProvisioningPlan.AccountRequest.Operation operation) {
2190        return plan -> (Utilities.safeStream(plan.getAccountRequests()).anyMatch(ar -> Util.nullSafeEq(ar.getApplicationName(), application) && Util.nullSafeEq(ar.getOperation(), operation)));
2191    }
2192
2193    /**
2194     * Maps to a Stream of provisioning plans (for use with flatMap) in the given project
2195     */
2196    public static Function<ProvisioningProject, Stream<ProvisioningPlan>> plans() {
2197        return (project) -> project == null ? Stream.empty() : Utilities.safeStream(project.getPlans());
2198    }
2199
2200    /**
2201     * Resolves to true if the given property value on the input object can be
2202     * coerced to a Date and is after the given Date.
2203     */
2204    public static Predicate<Object> propertyAfter(final String propertyPath, final Date test) {
2205        return source -> {
2206            try {
2207                Object propertyValue = Utilities.getProperty(source, propertyPath);
2208                if (propertyValue == null) {
2209                    return false;
2210                }
2211                if (propertyValue instanceof Date) {
2212                    Date d = (Date) propertyValue;
2213                    return d.after(test);
2214                } else if (propertyValue instanceof Number) {
2215                    long date = ((Number) propertyValue).longValue();
2216                    Date d = new Date(date);
2217                    return d.after(test);
2218                } else if (propertyValue instanceof String) {
2219                    long date = Long.parseLong((String) propertyValue);
2220                    Date d = new Date(date);
2221                    return d.after(test);
2222                }
2223            } catch (GeneralException ignored) {
2224                /* Nothing */
2225            }
2226            return false;
2227        };
2228    }
2229
2230    /**
2231     * Resolves to true if the given property value on the input object can be
2232     * coerced to a Date and is before the given Date.
2233     */
2234    public static Predicate<Object> propertyBefore(final String propertyPath, final Date test) {
2235        return source -> {
2236            try {
2237                Object propertyValue = Utilities.getProperty(source, propertyPath);
2238                if (propertyValue == null) {
2239                    return false;
2240                }
2241                if (propertyValue instanceof Date) {
2242                    Date d = (Date) propertyValue;
2243                    return d.before(test);
2244                } else if (propertyValue instanceof Number) {
2245                    long date = ((Number) propertyValue).longValue();
2246                    Date d = new Date(date);
2247                    return d.before(test);
2248                } else if (propertyValue instanceof String) {
2249                    long date = Long.parseLong((String) propertyValue);
2250                    Date d = new Date(date);
2251                    return d.before(test);
2252                }
2253            } catch (GeneralException ignored) {
2254                /* Nothing */
2255            }
2256            return false;
2257        };
2258    }
2259
2260    /**
2261     * Returns a Predicate that resolves to true if the given property on the input object equals the test value
2262     */
2263    public static Predicate<Object> propertyEquals(final String propertyPath, final Object test) {
2264        return source -> {
2265            try {
2266                Object propertyValue = Utilities.getProperty(source, propertyPath);
2267                return Util.nullSafeEq(propertyValue, test);
2268            } catch (GeneralException e) {
2269                return false;
2270            }
2271        };
2272    }
2273
2274    /**
2275     * Returns a Predicate that resolves to true if the property at the given path matches the given regex. If the
2276     * property is a string it will be used directly. If the property is a List containing
2277     * only one string, that string will be extracted and used.
2278     *
2279     * In all other cases, including parse errors, false will be returned.
2280     *
2281     * @param propertyPath The path to the property via {@link Utilities#getProperty(Object, String)}
2282     * @param regexString The regular expression string
2283     * @return A predicate that returns true if the property extracted matches the
2284     */
2285    public static Predicate<Object> propertyMatchesRegex(final String propertyPath, final String regexString) {
2286        Pattern regex = Pattern.compile(regexString);
2287
2288        return object -> {
2289            try {
2290                Object propertyValue = getProperty(object, propertyPath, true);
2291                if (propertyValue instanceof String) {
2292                    String stringProperty = (String) propertyValue;
2293                    Matcher regexMatcher = regex.matcher(stringProperty);
2294                    return regexMatcher.find();
2295                } else if (propertyValue instanceof List) {
2296                    List<?> listProperty = (List<?>) propertyValue;
2297                    if (listProperty.size() == 1) {
2298                        Object onlyValue = listProperty.get(0);
2299                        if (onlyValue instanceof String) {
2300                            String stringProperty = (String) onlyValue;
2301                            Matcher regexMatcher = regex.matcher(stringProperty);
2302                            return regexMatcher.find();
2303                        }
2304                    }
2305                }
2306            } catch(GeneralException e) {
2307                return false;
2308            }
2309            return false;
2310        };
2311    }
2312
2313    /**
2314     * Returns a Predicate that resolves to true if the given property on the input object is the same as the
2315     * test value, per Sameness rules
2316     */
2317    public static Predicate<Object> propertySame(final String propertyPath, final Object test) {
2318        return source -> {
2319            try {
2320                Object propertyValue = Utilities.getProperty(source, propertyPath);
2321                return Sameness.isSame(propertyValue, test, false);
2322            } catch (GeneralException e) {
2323                return false;
2324            }
2325        };
2326    }
2327
2328    /**
2329     * Returns a Predicate that evaluates to true if the predicate's input
2330     * string matches the given regular expression.
2331     *
2332     * @param regex The regex
2333     * @return A predicate that matches the regex
2334     */
2335    public static Predicate<String> regexMatches(final String regex) {
2336        return string -> string.matches(regex);
2337    }
2338
2339    /**
2340     * Returns a Predicate that evaluates to true if the predicate's input
2341     * string matches the given regular expression.
2342     *
2343     * Unlike the version of {@link #regexMatches(String)} taking a String,
2344     * this one uses {@link Matcher#find()}, meaning it will match partial
2345     * strings.
2346     *
2347     * @param regex The regex
2348     * @return A predicate that matches the regex
2349     */
2350    public static Predicate<String> regexMatches(final Pattern regex) {
2351        return string -> regex.matcher(string).find();
2352    }
2353
2354    /**
2355     * Returns a Predicate that evaluates to true if the predicate's input
2356     * string matches the given regular expression.
2357     *
2358     * @param regex The regex
2359     * @return A predicate that matches the regex
2360     */
2361    public static Predicate<String> regexMatchesPartial(final String regex) {
2362        Pattern pattern = Pattern.compile(regex);
2363
2364        return string -> pattern.matcher(string).find();
2365    }
2366
2367    /**
2368     * Safely casts the value to the given class, returning null if it can't be
2369     * cast to that value.
2370     *
2371     * @param expectedClass The expected class
2372     * @param <T> The expected class type
2373     * @return The value cast to the given type, or null if not that type
2374     */
2375    public static <T> Function<Object, T> safeCast(Class<T> expectedClass) {
2376        return o -> Utilities.safeCast(o, expectedClass);
2377    }
2378
2379    /**
2380     * Safely retrieves the given property from the input object, returning
2381     * the default value if the result is null or throws an exception.
2382     */
2383    public static <T> Function<Object, T> safeGet(String propertyName, T defaultValue, Class<T> expectedClass) {
2384        return o -> {
2385            try {
2386                Object result = Utilities.getProperty(o, propertyName, true);
2387                if (result == null) {
2388                    return defaultValue;
2389                }
2390                return expectedClass.cast(result);
2391            } catch (Exception e) {
2392                return defaultValue;
2393            }
2394        };
2395    }
2396
2397    /**
2398     * Safely retrieves the given property from the input object, returning
2399     * null if the property value is not of the expected type.
2400     */
2401    public static <T> Function<Object, T> safeGet(String propertyName, Class<T> expectedClass) {
2402        return o -> {
2403            try {
2404                return expectedClass.cast(Utilities.getProperty(o, propertyName));
2405            } catch (Exception e) {
2406                return null;
2407            }
2408        };
2409    }
2410
2411    public static Function<Object, List<String>> safeListify() {
2412        return Utilities::safeListify;
2413    }
2414
2415    private static Class<?> safeType(Object obj) {
2416        if (obj == null) {
2417            return null;
2418        }
2419        return obj.getClass();
2420    }
2421
2422    /**
2423     * Returns a Predicate that resolves to true if the input value is the same as the given test value,
2424     * per Sameness rules
2425     */
2426    public static Predicate<?> sameAs(final Object value) {
2427        return sameAs(value, false);
2428    }
2429
2430    /**
2431     * Returns a Predicate that resolves to true if the input value is the same as the given test value,
2432     * ignoring case, per Sameness rules
2433     */
2434    public static Predicate<?> sameAs(final Object value, boolean ignoreCase) {
2435        return o -> Sameness.isSame(o, value, ignoreCase);
2436    }
2437
2438    /**
2439     * Supplies a value by invoking the given Beanshell method with the
2440     * given parameters. On error, returns null.
2441     */
2442    public static SupplierWithError<Boolean> sb(final bsh.This bsh, final String methodName, final Object... params) {
2443        return () -> Util.otob(bsh.invokeMethod(methodName, params));
2444    }
2445
2446    /**
2447     * Creates a comparator that sorts in the order specified, leaving the
2448     * input objects alone. Equivalent to calling sortListOrder and passing
2449     * {@link Function#identity()} as the translator.
2450     */
2451    public static <ListItem> Comparator<ListItem> sortListOrder(List<ListItem> order) {
2452        return sortListOrder(order, Function.identity());
2453    }
2454
2455    /**
2456     * Creates a comparator that sorts in the order specified, translating input
2457     * values into sort keys first via the provided translator. Values not in the
2458     * order list will be sorted to the end of the list.
2459     *
2460     * For example, you might sort a list of Links into a specific order by
2461     * application to create a precedence structure.
2462     *
2463     * If no order is specified, the resulting Comparator will laboriously
2464     * leave the list in the original order.
2465     *
2466     * @param <ListItem> The type of the item being sorted
2467     * @param <SortType> The type of item in the ordering list
2468     * @param order The ordering to apply
2469     * @param keyExtractor The translator
2470     * @return The comparator
2471     */
2472    public static <ListItem, SortType> Comparator<ListItem> sortListOrder(List<SortType> order, Function<ListItem, SortType> keyExtractor) {
2473        Map<SortType, Integer> orderMap = new HashMap<>();
2474        if (order != null) {
2475            int index = 0;
2476            for (SortType v : order) {
2477                orderMap.put(v, index++);
2478            }
2479        }
2480
2481        return (v1, v2) -> {
2482            SortType key1 = keyExtractor.apply(v1);
2483            SortType key2 = keyExtractor.apply(v2);
2484
2485            int o1 = orderMap.getOrDefault(key1, Integer.MAX_VALUE);
2486            int o2 = orderMap.getOrDefault(key2, Integer.MAX_VALUE);
2487
2488            return (o1 - o2);
2489        };
2490    }
2491
2492    /**
2493     * Returns a Predicate that resolves to true if the input string starts with the given prefix
2494     */
2495    public static Predicate<String> startsWith(String prefix) {
2496        return (s) -> s.startsWith(prefix);
2497    }
2498
2499    /**
2500     * Creates a stream out of the given SailPoint search
2501     * @param context The context
2502     * @param spoClass The SailPointObject to search
2503     * @param qo The QueryOptions
2504     * @param <A> The object type
2505     * @return The stream
2506     * @throws GeneralException if the query fails
2507     */
2508    public static <A extends SailPointObject> Stream<A> stream(SailPointContext context, Class<A> spoClass, QueryOptions qo) throws GeneralException {
2509        IncrementalObjectIterator<A> objects = new IncrementalObjectIterator<>(context, spoClass, qo);
2510        return StreamSupport.stream(Spliterators.spliterator(objects, objects.getSize(), Spliterator.ORDERED), false).onClose(() -> {
2511            Util.flushIterator(objects);
2512        });
2513    }
2514
2515    /**
2516     * Creates a stream out of the given SailPoint projection search
2517     * @param context The context
2518     * @param spoClass The SailPoint class
2519     * @param qo The query filters
2520     * @param props The query properties to query
2521     * @param <A> The object type to query
2522     * @return The stream
2523     * @throws GeneralException if the query fails
2524     */
2525    public static <A extends SailPointObject> Stream<Object[]> stream(SailPointContext context, Class<A> spoClass, QueryOptions qo, List<String> props) throws GeneralException {
2526        IncrementalProjectionIterator objects = new IncrementalProjectionIterator(context, spoClass, qo, props);
2527        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(objects, Spliterator.ORDERED), false).onClose(() -> {
2528            Util.flushIterator(objects);
2529        });
2530    }
2531
2532
2533    /**
2534     * Equivalent to {@link #stream(Function, Class)} if passed Object.class
2535     */
2536    public static <A> Function<A, Stream<Object>> stream(Function<A, ? extends Object> input) {
2537        return stream(input, Object.class);
2538    }
2539
2540    /**
2541     * Applies the given function to the input object and then makes a Stream
2542     * out of it. When used with other functions, this is handy for flatMap.
2543     *
2544     * If the result of the function is not of the expected type, or is not a
2545     * List, Map, or Set of them, returns an empty Stream.
2546     *
2547     * @param input A function to transform the input object
2548     * @param expectedType The expected type of the output
2549     * @return A function from an input object to a Stream of output objects
2550     */
2551    @SuppressWarnings("unchecked")
2552    public static <A, T> Function<A, Stream<T>> stream(Function<A, ?> input, Class<T> expectedType) {
2553        return object -> {
2554            Object result = input.apply(object);
2555            if (result == null) {
2556                return Stream.empty();
2557            }
2558            if (result instanceof List) {
2559                return Utilities.safeStream((List)result);
2560            } else if (result instanceof Set) {
2561                return Utilities.safeStream((Set)result);
2562            } else if (result instanceof Map) {
2563                return Utilities.safeStream(((Map)result).entrySet());
2564            } else if (expectedType.isAssignableFrom(result.getClass())) {
2565                return (Stream<T>)Stream.of(result);
2566            } else {
2567                return Stream.empty();
2568            }
2569        };
2570    }
2571
2572    /**
2573     * Returns a supplier that serializes the given object to JSON. The JSON
2574     * text is lazily determined only on supplier invocation.
2575     *
2576     * If an error occurs during JSON invocation, a warning will be logged and
2577     * an empty string will be returned.
2578     *
2579     * @param obj The object to serialize when the supplier is called
2580     * @return A supplier to JSON
2581     */
2582    public static Supplier<String> toJson(Object obj) {
2583        return () -> {
2584            ObjectMapper mapper = Utilities.getJacksonObjectMapper();
2585            try {
2586                return mapper.writeValueAsString(obj);
2587            } catch (JsonProcessingException e) {
2588                log.warn("Error converting object to JSON", e);
2589                return "";
2590            }
2591        };
2592    }
2593
2594    /**
2595     * Returns a Supplier that translates the given AbstractXmlObject to XML.
2596     * This can be used with modern log4j2 invocations, notably. The call
2597     * to {@link AbstractXmlObject#toXml()} happens on invocation. The
2598     * result is not cached, so if the object is changed, subsequent
2599     * invocations of the Supplier may produce different output.
2600     *
2601     * @param spo The SailPointObject to serialize
2602     * @return A supplier that translates the SPO to XML when invoked
2603     */
2604    public static Supplier<String> toXml(AbstractXmlObject spo) {
2605        if (spo == null) {
2606            return () -> "";
2607        }
2608
2609        return () -> {
2610            try {
2611                return spo.toXml();
2612            } catch (GeneralException e) {
2613                log.debug("Unable to translate object to XML", e);
2614                return e.toString();
2615            }
2616        };
2617    }
2618
2619    /**
2620     * Returns a mapping function producing the value of the input map entry
2621     * @param <T> The map entry's value type
2622     * @return A function producing the resulting value
2623     */
2624    public static <T> Function<Map.Entry<?, T>, T> value() {
2625        return Map.Entry::getValue;
2626    }
2627
2628    /**
2629     * Private utility constructor
2630     */
2631    private Functions() {
2632
2633    }
2634}