001package com.identityworksllc.iiq.common;
002
003import sailpoint.object.SailPointObject;
004import sailpoint.tools.Util;
005
006import java.util.Collections;
007import java.util.Comparator;
008import java.util.Date;
009import java.util.List;
010
011/**
012 * A {@link Comparator} to sort {@link SailPointObject}s in a reliable way: first by date, and if the dates are
013 * identical, by ID. SPOs may have a null modified date, in which case the created
014 * date is checked.
015 *
016 * IMPORTANT: Before a new SPO is saved, the values of both 'created' and 'id' will be
017 * null. Take care that this class is not used in that context.
018 */
019public final class SailPointObjectDateSorter implements Comparator<SailPointObject> {
020
021    /**
022     * Returns the latest date for the given SPO, including modified if the flag
023     * is set to true. If the object has no created or modified date (which will
024     * happen if it's not yet saved), returns null.
025     *
026     * @param sailPointObject The SPO to get the date
027     * @param includeModified If true, use the modified date if it is set; if false, use create only
028     * @return The modified or created date, or null if none
029     */
030    public static Date latestDate(SailPointObject sailPointObject, boolean includeModified) {
031        if (includeModified && sailPointObject.getModified() != null) {
032            return sailPointObject.getModified();
033        } else if (sailPointObject.getCreated() != null) {
034            return sailPointObject.getCreated();
035        } else {
036            return null;
037        }
038    }
039
040    /**
041     * Sorts the given list of SPOs using this comparator
042     * @param list The list to sort
043     */
044    public static void sort(List<? extends SailPointObject> list) {
045        list.sort(new SailPointObjectDateSorter());
046    }
047    /**
048     * If true, the modified date will be considered in addition to create
049     */
050    private final boolean includeModified;
051
052    /**
053     * SPO date sorter defaulting to checking modified dates
054     */
055    public SailPointObjectDateSorter() {
056        this(true);
057    }
058
059    /**
060     * SPO date sorter allowing you to specify whether you want to include modified dates
061     *
062     * @param includeModified If true, use the modified date if it is set; if false, use create only
063     */
064    public SailPointObjectDateSorter(boolean includeModified) {
065        this.includeModified = includeModified;
066    }
067
068    /**
069     * @see Comparator#compare(Object, Object)
070     */
071    @Override
072    public int compare(SailPointObject o1, SailPointObject o2) {
073        // Ensures that nulls move to the start of the sorted list
074        if (o1 == null && o2 == null) {
075            return 0;
076        } else if (o1 == null) {
077            return 1;
078        } else if (o2 == null) {
079            return -1;
080        }
081
082        Date d1 = latestDate(o1, includeModified);
083        Date d2 = latestDate(o2, includeModified);
084
085        int compare = 0;
086
087        // This is convoluted to account for the weird chance that one or both of the dates is null.
088        // A null date will always go last in the list.
089        if (!Util.nullSafeEq(d1, d2, true)) {
090            if (d1 == null || d1.after(d2)) {
091                compare = 1;
092            } else if (d2 == null || d2.after(d1)) {
093                compare = -1;
094            }
095        }
096
097        if (compare == 0 && Util.isNotNullOrEmpty(o1.getId()) && Util.isNotNullOrEmpty(o2.getId())) {
098            compare = o1.getId().compareTo(o2.getId());
099        }
100
101        return compare;
102    }
103
104}