/** * File Name: fflib_ArrayUtils * @description * * @author architect ir. Wilhelmus G.J. Velzeboer * * Copyright (c), W.G.J. Velzeboer, * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above author notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the author nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ public class fflib_ArrayUtils { /* private constructor to prevent instantiation */ private fflib_ArrayUtils() {} /** * Creates a clone from the provided list and returns the sorted list * * @param unsortedIntegers List of unsorted Integers * * @return Sorted list or Integers */ public static List cloneAndSort(List unsortedIntegers) { return (List) cloneAndSort((List) unsortedIntegers); } /** * Creates a clone from the provided list and returns the sorted list * * @param unsorted List of unsorted Objects * * @return Sorted list */ public static List cloneAndSort(List unsorted) { List result = unsorted.clone(); result.sort(); return result; } /** * Copies the given fields from the a domain to another domain * * @param source The domain holding the values that need to be copied over to the target * @param target The target domain that should be updated * @param targetRelationShipField The relationship field on the target linking to the source record * @param targetBySourceField The target SObjectField by the source SObjectField * * @example * Accounts accounts = Accounts.newInstance(accountRecords); * Contacts contact = Contact.newInstance(contactRecords * Map accountNameById = * fflib_ArrayUtils.copyFields( * accounts, * contacts, * Contact.AccountId, * new Map{ * Account.BillingCity => Contact.MailingCity, * Account.BillingCountry => Contact.MailingCountry * }); */ public static void copyFields( fflib_ISObjects2 source, fflib_ISObjects2 target, SObjectField targetRelationShipField, Map targetBySourceField) { SObjectField sourceIdField = fflib_SObjectDescribe.getDescribe(source.getSObjectType()).getField('Id'); // Retrieve values Map> sourceFieldValueById = new Map>(); for (SObject record : source.getRecords()) { Map valueByField = new Map(); for (SObjectField sourceField : targetBySourceField.keySet()) { valueByField.put(sourceField, record.get(sourceField)); } sourceFieldValueById.put((Id) record.get(sourceIdField), valueByField); } // Set values for (SObject record : target.getRecords()) { Id recordId = (Id) record.get(targetRelationShipField); if (sourceFieldValueById.containsKey(recordId) == false) continue; Map valueByField = sourceFieldValueById.get(recordId); for (SObjectField sourceField : valueByField.keySet()) { if (targetBySourceField.containsKey(sourceField) == false) continue; SObjectField targetField = targetBySourceField.get(sourceField); record.put(targetField, valueByField.get(sourceField)); } } } /** * Gets the first value of the list * * @param objects List of objects * * @return The first value of the list. Returns null, is the provided list is null or empty */ public static Object firstValue(List objects) { if (isEmpty(objects)) return null; return objects.get(0); } /** * Gets all String values for the given Ids * * @param stringsByIds the data map to process * @param idsToRetrieve The keys for which to retrieve their string values * * @return Returns all the values for the provided keys */ public static Set getAllValues(Map stringsByIds, Set idsToRetrieve) { Set result = new Set(); for (Id id : idsToRetrieve) { if (stringsByIds.containsKey(id) == false) continue; result.add(stringsByIds.get(id)); } return result; } /** * Get a map with the values of two fields. * Key fields containing null values are omitted * * @param domain The domain holding the records * @param valueField The field to use as the Value of the Map * @param keyField The Id field to use as the Key of the map * * @return Returns a map with the values of two fields * * @example * Accounts accounts = Accounts.newInstance(records); * Map accountNameById = fflib_ArrayUtils.getValueById(accounts, Account.Name, Account.Id); */ public static Map getValueById(fflib_ISObjects2 domain, SObjectField valueField, SObjectField keyField) { Map result = new Map(); for (SObject record : domain.getRecords()) { if (record.get(keyField) == null) continue; result.put( (Id) record.get(keyField), record.get(valueField) ); } return result; } /** * Validates if a list is empty * * @param objects The list to validate * * @return Returns true of the list is either null or empty */ public static Boolean isEmpty(List objects) { return (null == objects || objects.isEmpty()); } /** * Validates if a list is empty * * @param sObjects The list to validate * * @return Returns true of the list is either null or empty */ public static Boolean isEmpty(List sObjects) { return (null == sObjects || sObjects.isEmpty()); } /** * Validates if a list is not empty * * @param objects The list to validate * * @return Returns true of the list is not null or not empty */ public static Boolean isNotEmpty(List objects) { return !isEmpty(objects); } /** * Validates if a list is not empty * * @param sObjects The list to validate * * @return Returns true of the list is not null or not empty */ public static Boolean isNotEmpty(List sObjects) { return !isEmpty(sObjects); } /** * Get the last value in the list * * @param objects List of objects * * @return The last value of the list. Returns null, is the provided list is null or empty */ public static Object lastValue(List objects) { if (isEmpty(objects)) return null; return objects.get(objects.size() - 1); } /** * Converts all Strings in the list to lowercase, * null values are ignored and removed from the list * * @param strings The List of String to convert * * @return Returns the List with only lowercase values */ public static List lowerCase(List strings) { List result = new List(); if (isEmpty(strings)) return result; for (String str : strings) { if (null == str) continue; result.add(str.toLowerCase()); } return result; } /** * Sorts the list with Datetime values and return the highest value in the list * * @param input List of Datetime values * * @return Highest Datetime value of the given input. Returns null when the input is empty or null. */ public static Datetime maxDatetime(List input) { if (isEmpty(input)) return null; return (Datetime) max((List) input); } /** * Sorts the list and return the highest value in the list * * @param input List of values * * @return Highest value of the given input. Returns null when the input is empty or null. */ public static Object max(List input) { if (isEmpty(input)) return null; return lastValue(cloneAndSort(input)); } /** * Merge the given two maps, without modifying the source. * * @param mapA Map> * @param mapB Map> * * @return Returns the merged map */ public static Map> mergeMaps(Map> mapA, Map> mapB) { Map> result = mapA.clone(); for (Id idValue : mapB.keySet()) { if (result.containsKey(idValue)) { result.get(idValue).addAll(mapB.get(idValue)); } else { result.put(idValue, mapB.get(idValue)); } } return result; } /** * Merge the given two maps, without modifying the source. * When duplicate values found, only TRUE values will be merged. * e.g. * mapA: 1 => true * mapB: 1 => false * result: 1 => true * * @param mapA Map * @param mapB Map * * @return Returns the merged map */ public static Map mergeMaps(Map mapA, Map mapB) { Map result = mapA.clone(); for (Id idValue : mapB.keySet()) { if (result.containsKey(idValue)) { if (mapB.get(idValue)) { result.put(idValue, true); } } else { result.put(idValue, mapB.get(idValue)); } } return result; } /** * Sorts the list with Datetime values and return the oldest value in the list * * @param input List of Datetime values * * @return Oldest Datetime value of the given input. Returns null when the input is empty or null. */ public static Datetime minDatetime(List input) { if (isEmpty(input)) return null; return (Datetime) min((List) input); } /** * Sorts the list and return the lowest value in the list * * @param input List of values * * @return Lowest value of the given input. Returns null when the input is empty or null. */ public static Object min(List input) { if (isEmpty(input)) return null; List result = cloneAndSort(input); return firstValue(result); } /** * Casts a list of objects to a list of their literal string version * * @param objects List of objects to convert * * @return Returns a list of Strings with the literal conversion of the object */ public static List objectToString(List objects) { List result = new List(); if (isEmpty(objects)) return result; for (Object obj : objects) { result.add(String.valueOf(obj)); } return result; } /** * Casts a Set of objects to a list of their literal string version * * @param objects Set of objects to convert * * @return Returns a list of Strings with the literal conversion of the object */ public static List objectToString(Set objects) { return objectToString(new List(objects)); } /** * Casts a Set of Objects to a list of Ids * Non Id objects are ignored * * @param objects Set of objects to convert * * @return Returns a list of Strings with the literal conversion of the object */ public static Set objectToIds(Set objects) { Set results = new Set(); for (Object obj : objects) { if (!(obj instanceof Id)) continue; results.add((Id) obj); } return results; } /** * Takes two maps and replaces the key of the source with the value of the replacement, * where the source key matches the replacement key. * The key will be removed if it does not exists as key in replacement map * * @param source The map to replace its key * @param replacement The map to use its value for replacement * * @return Returns a Map<{Replacement-value} Id, {Source-value} Id> */ public static Map replaceKey(Map source, Map replacement) { Map result = new Map(); for (Id keyId : replacement.keySet()) { if (source.containsKey(keyId) == false) continue; result.put(replacement.get(keyId), source.get(keyId)); } return result; } /** * Takes two maps and replaces the value of the source with the value of the replacement, * where the source value matches the replacement key. * The value will become null if the source value does not exists as key in replacement map * * @param source The map to replace its value * @param replacement The map to use its value for replacement * * @return Returns a Map<{Source} Id, {Replacement} Id> */ public static Map replaceValue(Map source, Map replacement) { Map result = new Map(); for (Id keyId : source.keySet()) { Id sourceValue = source.get(keyId); if (replacement.containsKey(sourceValue)) { result.put(keyId, replacement.get(sourceValue)); } else { // There is no replacement value for this keyId result.put(keyId, null); } } return result; } /** * Takes two maps and replaces the value of the source with the value of the replacement, * where the source value matches the replacement key. * The value will become null if the source value does not exists as key in replacement map * * @param source The map to replace its value * @param replacement The map to use its value for replacement * * @return Returns a Map<{Source} Id, {Replacement} Id> */ public static Map replaceValue(Map source, Map replacement) { Map result = new Map(); for (Id keyId : source.keySet()) { Id sourceValue = source.get(keyId); if (replacement.containsKey(sourceValue)) { result.put(keyId, replacement.get(sourceValue)); } else { // There is no replacement value for this keyId result.put(keyId, null); } } return result; } /** * Takes two maps and replaces the value of the source with the value of the replacement, * where the source value matches the replacement key. * The value will become null if the source value does not exists as key in replacement map * * @param source The map to replace its value * @param replacement The map to use its value for replacement * * @return Returns a Map<{Source-key} Id, {Replacement-value} Id> */ public static Map replaceValue(Map source, Map replacement) { Map result = new Map(); for (Id keyId : source.keySet()) { Id sourceValue = source.get(keyId); if (replacement.containsKey(sourceValue)) { result.put(keyId, replacement.get(sourceValue)); } else { // There is no replacement value for this keyId result.put(keyId, null); } } return result; } /** * Takes two maps and replaces the value of the source with the value of the replacement, * where the source value matches the replacement key. * The value will become null if the source value does not exists as key in replacement map * * @param source The map to replace its value * @param replacement The map to use its value for replacement * * @return Returns a Map<{Source-key} Id, {Replacement-value} Id> */ public static Map replaceValue(Map source, Map replacement) { Map result = new Map(); for (Id keyId : source.keySet()) { Id sourceValue = source.get(keyId); if (replacement.containsKey(sourceValue)) { result.put(keyId, replacement.get(sourceValue)); } else { // There is no replacement value for this keyId result.put(keyId, null); } } return result; } /** * Takes two maps and replaces the value of the source with the value of the replacement, * where the source value matches the replacement key. * The value will become null if the source value does not exists as key in replacement map * * @param source The map to replace its value * @param replacement The map to use its value for replacement * * @return Returns a Map<{Source-key} Id, {Replacement-value} Id> */ public static Map replaceValue(Map source, Map replacement) { Map result = new Map(); for (Id keyId : source.keySet()) { String sourceValue = source.get(keyId); if (replacement.containsKey(sourceValue)) { result.put(keyId, replacement.get(sourceValue)); } else { // There is no replacement value for this keyId result.put(keyId, null); } } return result; } /** * Takes two maps and replaces the value of the source with the value of the replacement, * where the source value matches the replacement key. * The value will become null if the source value does not exists as key in replacement map * * @param source The map to replace its value * @param replacement The map to use its value for replacement * * @return Returns a Map<{Source-key} Id, {Replacement-value} Id> */ public static Map replaceValue(Map source, Map replacement) { Map result = new Map(); for (Id keyId : source.keySet()) { Id sourceValue = source.get(keyId); if (replacement.containsKey(sourceValue)) { result.put(keyId, replacement.get(sourceValue)); } else { // There is no replacement value for this keyId result.put(keyId, null); } } return result; } /** * Takes two maps and replaces the value of the source with the values of the replacement, * where the source value matches the replacement key. * If multiple matches are found the values will merged into one set. * The value will become null if the source value does not exists as key in replacement map * * @param source The map to replace its value * @param replacement The map to use its values for replacement * * @return Returns a Map<{Source} Id, Set<{Replacement} Id>> */ public static Map> replaceValues(Map> source, Map> replacement) { Map> result = new Map>(); for (Id keyId : source.keySet()) { for (Id sourceValue : source.get(keyId)) { if (replacement.containsKey(sourceValue)) { if (result.containsKey(keyId)) { result.get(keyId).addAll(replacement.get(sourceValue)); } else { result.put(keyId, replacement.get(sourceValue)); } } } // There is no replacement value for this keyId if (result.containsKey(keyId) == false ) { result.put(keyId, null); } } return result; } /** * Reverse the order of a list of objects * * @param objects List of Objects * * @return Returns a list of objects in reserve order */ public static List reverse(List objects) { if (isEmpty(objects)) return objects; Integer i = 0; Integer j = objects.size() - 1; Object tmp; while (j > i) { tmp = objects[j]; objects[j] = objects[i]; objects[i] = tmp; j--; i++; } return objects; } /** * Reverse the order of a list of SObjects * * @param sObjects List of SObjects * * @return Returns a list of SObjects in reserve order */ public static List reverse(List sObjects) { if (isEmpty(sObjects)) return sObjects; Integer i = 0; Integer j = sObjects.size() - 1; SObject tmp; while (j > i) { tmp = sObjects[j]; sObjects[j] = sObjects[i]; sObjects[i] = tmp; j--; i++; } return sObjects; } /** * Sets a value to the given field only when key field Id value is provided in the given map * * @param domain The domain containing the records to update * @param sObjectIdFieldToCheck The SObject Id Field to match the key against in the provided map * @param sObjectFieldToUpdate The SObjectField to store the mapped value when the key matches the value in the sObjectFieldToUpdate field * @param values Map of values to store by the sObjectIdFieldToCheck fields value */ public static void setFieldValue( fflib_ISObjects2 domain, Schema.SObjectField sObjectIdFieldToCheck, Schema.SObjectField sObjectFieldToUpdate, Map values) { for (SObject record : domain.getRecords()) { Id keyValue = (Id) record.get(sObjectIdFieldToCheck); if (values.containsKey(keyValue)) { record.put(sObjectFieldToUpdate, values.get(keyValue)); } } } /** * Converts all String of the list into uppercase * null values are ignored * * @param strings The list of Strings to convert * * @return Returns the List with uppercase Strings */ public static List upperCase(List strings) { List result = new List(); if (isEmpty(strings)) return result; for (String str : strings) { if (null == str) continue; result.add(str.toUpperCase()); } return result; } }