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