/***********************************************************************************************************
 * Name         : FormulaUtils
 * Purpose      : Util class for Formula related methods
 **********************************************************************************************************/
public with sharing class FormulaUtils {
    /**
     * Recalculates formulas for given SObjects,
     *  preserving the values of all Address CountryCode__s and StateCode__s
     *  subfields found on the records.
     * @param sobjects List of SObjects to recalculate formulas for.
     */
    public static void recalcFormulasPreservingAddressCodes(List<SObject> sObjects) {
        Schema.SObjectType sType = sObjects[0].getSObjectType();
        Map<String, Schema.SObjectField> fieldMap = sType.getDescribe().fields.getMap();

        List<String> fieldsToBackup = new List<String>();
        for (Schema.SObjectField f : fieldMap.values()) {
            Schema.DescribeFieldResult fd = f.getDescribe();
            if (fd.getType() == Schema.DisplayType.Address) {
                String base = fd.getName();
                String prefix = base.substring(0, base.length() - 1);
                fieldsToBackup.add(prefix + 'CountryCode__s');
                fieldsToBackup.add(prefix + 'StateCode__s');
            }
        }

        if (fieldsToBackup.isEmpty()) {
            Formula.recalculateFormulas(sObjects);
        } else {
            List<SObject> backupRecords = new List<SObject>();

            for (SObject sObj : sObjects) {
                SObject backup = sType.newSObject();
                backup.Id = sObj.Id;
                for (String fieldName : fieldsToBackup) {
                    backup.put(fieldName, sObj.get(fieldName));
                    sObj.put(fieldName, null);
                }
                backupRecords.add(backup);
            }

            Formula.recalculateFormulas(sObjects);

            for (Integer i = 0; i < sObjects.size(); i++) {
                SObject sObj = sObjects[i];
                SObject backup = backupRecords[i];
                for (String fieldName : fieldsToBackup) {
                    sObj.put(fieldName, backup.get(fieldName));
                }
            }
        }
    }
}
