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