diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/ConnectionProcessorTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/ConnectionProcessorTests.java index 03cf9137e..35aec523b 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ConnectionProcessorTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ConnectionProcessorTests.java @@ -30,7 +30,10 @@ of this software and associated documentation files (the "Software"), to deal import java.net.URL; import java.net.URLConnection; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -130,6 +133,26 @@ public void setUp() { @Override public int getRequestTimeoutDurationMillis() { return 30_000; } + + @Override public int getUserPropertyCacheLimit() { + return 100; + } + + @Override public FilterList> getEventFilterList() { + return new FilterList<>(new HashSet<>(), false); + } + + @Override public FilterList> getUserPropertyFilterList() { + return new FilterList<>(new HashSet<>(), false); + } + + @Override public FilterList> getSegmentationFilterList() { + return new FilterList<>(new HashSet<>(), false); + } + + @Override public FilterList>> getEventSegmentationFilterList() { + return new FilterList<>(new ConcurrentHashMap<>(), false); + } }; Countly.sharedInstance().setLoggingEnabled(true); diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleConfigurationTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleConfigurationTests.java index 625d43649..5a44cbe9a 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleConfigurationTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleConfigurationTests.java @@ -4,6 +4,7 @@ import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -644,7 +645,7 @@ public void invalidConfigResponses_AreRejected() { */ @Test public void configurationParameterCount() { - int configParameterCount = 31; // plus config, timestamp and version parameters + int configParameterCount = 40; // plus config, timestamp and version parameters, UPDATE: list filters, and user property cache limit int count = 0; for (Field field : ModuleConfiguration.class.getDeclaredFields()) { if (field.getName().startsWith("keyR")) { @@ -1123,7 +1124,14 @@ private void initServerConfigWithValues(BiConsumer config .segmentationValuesLimit(25) .breadcrumbLimit(90) .traceLengthLimit(78) - .traceLinesLimit(89); + .traceLinesLimit(89) + .userPropertyCacheLimit(67) + + // Filters + .eventFilterList(new HashSet<>(), false) + .userPropertyFilterList(new HashSet<>(), false) + .segmentationFilterList(new HashSet<>(), false) + .eventSegmentationFilterMap(new ConcurrentHashMap<>(), false); String serverConfig = builder.build(); CountlyConfig countlyConfig = TestUtils.createBaseConfig().setLoggingEnabled(false); diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/ServerConfigBuilder.java b/sdk/src/androidTest/java/ly/count/android/sdk/ServerConfigBuilder.java index 003c68726..b6c8bdd20 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ServerConfigBuilder.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ServerConfigBuilder.java @@ -1,7 +1,11 @@ package ly.count.android.sdk; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.json.JSONException; import org.json.JSONObject; import org.junit.Assert; @@ -13,7 +17,11 @@ import static ly.count.android.sdk.ModuleConfiguration.keyRCustomEventTracking; import static ly.count.android.sdk.ModuleConfiguration.keyRDropOldRequestTime; import static ly.count.android.sdk.ModuleConfiguration.keyREnterContentZone; +import static ly.count.android.sdk.ModuleConfiguration.keyREventBlacklist; import static ly.count.android.sdk.ModuleConfiguration.keyREventQueueSize; +import static ly.count.android.sdk.ModuleConfiguration.keyREventSegmentationBlacklist; +import static ly.count.android.sdk.ModuleConfiguration.keyREventSegmentationWhitelist; +import static ly.count.android.sdk.ModuleConfiguration.keyREventWhitelist; import static ly.count.android.sdk.ModuleConfiguration.keyRLimitBreadcrumb; import static ly.count.android.sdk.ModuleConfiguration.keyRLimitKeyLength; import static ly.count.android.sdk.ModuleConfiguration.keyRLimitSegValues; @@ -25,11 +33,16 @@ import static ly.count.android.sdk.ModuleConfiguration.keyRNetworking; import static ly.count.android.sdk.ModuleConfiguration.keyRRefreshContentZone; import static ly.count.android.sdk.ModuleConfiguration.keyRReqQueueSize; +import static ly.count.android.sdk.ModuleConfiguration.keyRSegmentationBlacklist; +import static ly.count.android.sdk.ModuleConfiguration.keyRSegmentationWhitelist; import static ly.count.android.sdk.ModuleConfiguration.keyRServerConfigUpdateInterval; import static ly.count.android.sdk.ModuleConfiguration.keyRSessionTracking; import static ly.count.android.sdk.ModuleConfiguration.keyRSessionUpdateInterval; import static ly.count.android.sdk.ModuleConfiguration.keyRTimestamp; import static ly.count.android.sdk.ModuleConfiguration.keyRTracking; +import static ly.count.android.sdk.ModuleConfiguration.keyRUserPropertyBlacklist; +import static ly.count.android.sdk.ModuleConfiguration.keyRUserPropertyCacheLimit; +import static ly.count.android.sdk.ModuleConfiguration.keyRUserPropertyWhitelist; import static ly.count.android.sdk.ModuleConfiguration.keyRVersion; import static ly.count.android.sdk.ModuleConfiguration.keyRViewTracking; @@ -169,6 +182,47 @@ ServerConfigBuilder traceLinesLimit(int limit) { return this; } + ServerConfigBuilder userPropertyCacheLimit(int limit) { + config.put(keyRUserPropertyCacheLimit, limit); + return this; + } + + ServerConfigBuilder eventFilterList(Set filterList, boolean isWhitelist) { + if (isWhitelist) { + config.put(keyREventWhitelist, filterList); + } else { + config.put(keyREventBlacklist, filterList); + } + return this; + } + + ServerConfigBuilder userPropertyFilterList(Set filterList, boolean isWhitelist) { + if (isWhitelist) { + config.put(keyRUserPropertyWhitelist, filterList); + } else { + config.put(keyRUserPropertyBlacklist, filterList); + } + return this; + } + + ServerConfigBuilder segmentationFilterList(Set filterList, boolean isWhitelist) { + if (isWhitelist) { + config.put(keyRSegmentationWhitelist, filterList); + } else { + config.put(keyRSegmentationBlacklist, filterList); + } + return this; + } + + ServerConfigBuilder eventSegmentationFilterMap(Map> filterMap, boolean isWhitelist) { + if (isWhitelist) { + config.put(keyREventSegmentationWhitelist, filterMap); + } else { + config.put(keyREventSegmentationBlacklist, filterMap); + } + return this; + } + ServerConfigBuilder defaults() { // Feature flags tracking(true); @@ -198,6 +252,12 @@ ServerConfigBuilder defaults() { breadcrumbLimit(Countly.maxBreadcrumbCountDefault); traceLengthLimit(Countly.maxStackTraceLineLengthDefault); traceLinesLimit(Countly.maxStackTraceLinesPerThreadDefault); + userPropertyCacheLimit(100); + + eventFilterList(new HashSet<>(), false); + userPropertyFilterList(new HashSet<>(), false); + segmentationFilterList(new HashSet<>(), false); + eventSegmentationFilterMap(new ConcurrentHashMap<>(), false); return this; } @@ -221,6 +281,7 @@ void validateAgainst(Countly countly) { validateFeatureFlags(countly); validateIntervalsAndSizes(countly); validateLimits(countly); + validateFilterSettings(countly); } private void validateFeatureFlags(Countly countly) { @@ -260,5 +321,32 @@ private void validateLimits(Countly countly) { Assert.assertEquals(config.get(keyRLimitBreadcrumb), countly.config_.sdkInternalLimits.maxBreadcrumbCount); Assert.assertEquals(config.get(keyRLimitTraceLength), countly.config_.sdkInternalLimits.maxStackTraceLineLength); Assert.assertEquals(config.get(keyRLimitTraceLine), countly.config_.sdkInternalLimits.maxStackTraceLinesPerThread); + Assert.assertEquals(config.get(keyRUserPropertyCacheLimit), countly.moduleConfiguration.getUserPropertyCacheLimit()); + } + + private void validateFilterSettings(Countly countly) { + Set eventFilterList = (Set) config.get(keyREventBlacklist); + if (eventFilterList == null) { + eventFilterList = (Set) config.get(keyREventWhitelist); + } + Assert.assertEquals(Objects.requireNonNull(eventFilterList).toString(), countly.moduleConfiguration.getEventFilterList().filterList.toString()); + + Set userPropertyFilterList = (Set) config.get(keyRUserPropertyBlacklist); + if (userPropertyFilterList == null) { + userPropertyFilterList = (Set) config.get(keyRUserPropertyWhitelist); + } + Assert.assertEquals(Objects.requireNonNull(userPropertyFilterList).toString(), countly.moduleConfiguration.getUserPropertyFilterList().filterList.toString()); + + Set segmentationFilterList = (Set) config.get(keyRSegmentationBlacklist); + if (segmentationFilterList == null) { + segmentationFilterList = (Set) config.get(keyRSegmentationWhitelist); + } + Assert.assertEquals(Objects.requireNonNull(segmentationFilterList).toString(), countly.moduleConfiguration.getSegmentationFilterList().filterList.toString()); + + Map> eventSegmentationFilterMap = (Map>) config.get(keyREventSegmentationBlacklist); + if (eventSegmentationFilterMap == null) { + eventSegmentationFilterMap = (Map>) config.get(keyREventSegmentationWhitelist); + } + Assert.assertEquals(Objects.requireNonNull(eventSegmentationFilterMap).toString(), countly.moduleConfiguration.getEventSegmentationFilterList().filterList.toString()); } } \ No newline at end of file diff --git a/sdk/src/main/java/ly/count/android/sdk/ConfigurationProvider.java b/sdk/src/main/java/ly/count/android/sdk/ConfigurationProvider.java index ec42ae165..74e78a8dd 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ConfigurationProvider.java +++ b/sdk/src/main/java/ly/count/android/sdk/ConfigurationProvider.java @@ -1,5 +1,8 @@ package ly.count.android.sdk; +import java.util.Map; +import java.util.Set; + interface ConfigurationProvider { boolean getNetworkingEnabled(); @@ -31,4 +34,26 @@ interface ConfigurationProvider { int getBOMDuration(); int getRequestTimeoutDurationMillis(); + + int getUserPropertyCacheLimit(); + + // LISTING FILTERS + + FilterList> getEventFilterList(); + + FilterList> getUserPropertyFilterList(); + + FilterList> getSegmentationFilterList(); + + FilterList>> getEventSegmentationFilterList(); + + class FilterList { + T filterList; + boolean isWhitelist; + + FilterList(T filterList, boolean isWhitelist) { + this.filterList = filterList; + this.isWhitelist = isWhitelist; + } + } } diff --git a/sdk/src/main/java/ly/count/android/sdk/ModuleConfiguration.java b/sdk/src/main/java/ly/count/android/sdk/ModuleConfiguration.java index df7abc8cc..2e2046e71 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleConfiguration.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleConfiguration.java @@ -2,7 +2,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.HashSet; import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -47,6 +52,16 @@ class ModuleConfiguration extends ModuleBase implements ConfigurationProvider { final static String keyRBOMRQPercentage = "bom_rqp"; final static String keyRBOMRequestAge = "bom_ra"; final static String keyRBOMDuration = "bom_d"; + final static String keyRUserPropertyCacheLimit = "upcl"; + final static String keyREventBlacklist = "eb"; + final static String keyRUserPropertyBlacklist = "upb"; + final static String keyRSegmentationBlacklist = "sb"; + final static String keyREventSegmentationBlacklist = "esb"; // json + final static String keyREventWhitelist = "ew"; + final static String keyRUserPropertyWhitelist = "upw"; + final static String keyRSegmentationWhitelist = "sw"; + final static String keyREventSegmentationWhitelist = "esw"; // json + // FLAGS boolean currentVTracking = true; boolean currentVNetworking = true; @@ -64,6 +79,13 @@ class ModuleConfiguration extends ModuleBase implements ConfigurationProvider { double currentVBOMRQPercentage = 0.5; int currentVBOMRequestAge = 24; // in hours int currentVBOMDuration = 60; // in seconds + int currentVUserPropertyCacheLimit = 100; + + // FILTERS + FilterList> currentVEventFilterList = new FilterList<>(new HashSet<>(), false); + FilterList> currentVUserPropertyFilterList = new FilterList<>(new HashSet<>(), false); + FilterList> currentVSegmentationFilterList = new FilterList<>(new HashSet<>(), false); + FilterList>> currentVEventSegmentationFilterList = new FilterList<>(new ConcurrentHashMap<>(), false); // SERVER CONFIGURATION PARAMS Integer serverConfigUpdateInterval; // in hours @@ -207,6 +229,7 @@ private void updateConfigVariables(@NonNull final CountlyConfig clyConfig) { currentVBOMRQPercentage = extractValue(keyRBOMRQPercentage, sb, currentVBOMRQPercentage, currentVBOMRQPercentage, Double.class, (Double value) -> value > 0.0 && value < 1.0); currentVBOMRequestAge = extractValue(keyRBOMRequestAge, sb, currentVBOMRequestAge, currentVBOMRequestAge, Integer.class, (Integer value) -> value > 0); currentVBOMDuration = extractValue(keyRBOMDuration, sb, currentVBOMDuration, currentVBOMDuration, Integer.class, (Integer value) -> value > 0); + currentVUserPropertyCacheLimit = extractValue(keyRUserPropertyCacheLimit, sb, currentVUserPropertyCacheLimit, currentVUserPropertyCacheLimit, Integer.class, (Integer value) -> value > 0); clyConfig.setMaxRequestQueueSize(extractValue(keyRReqQueueSize, sb, clyConfig.maxRequestQueueSize, clyConfig.maxRequestQueueSize, Integer.class, (Integer value) -> value > 0)); clyConfig.setEventQueueSizeToSend(extractValue(keyREventQueueSize, sb, clyConfig.eventQueueSizeThreshold, Countly.sharedInstance().EVENT_QUEUE_SIZE_THRESHOLD, Integer.class, (Integer value) -> value > 0)); @@ -222,6 +245,8 @@ private void updateConfigVariables(@NonNull final CountlyConfig clyConfig) { clyConfig.setRequiresConsent(extractValue(keyRConsentRequired, sb, clyConfig.shouldRequireConsent, clyConfig.shouldRequireConsent)); clyConfig.setRequestDropAgeHours(extractValue(keyRDropOldRequestTime, sb, clyConfig.dropAgeHours, clyConfig.dropAgeHours, Integer.class, (Integer value) -> value >= 0)); + updateListingFilters(); + String updatedValues = sb.toString(); if (!updatedValues.isEmpty()) { L.i("[ModuleConfiguration] updateConfigVariables, SDK configuration has changed, notifying the SDK, new values: [" + updatedValues + "]"); @@ -229,6 +254,93 @@ private void updateConfigVariables(@NonNull final CountlyConfig clyConfig) { } } + private void updateListingFilters() { + L.d("[ModuleConfiguration] updateListingFilters, current listing filters before updating: \n" + + "Event Filter List: " + currentVEventFilterList.filterList + ", isWhitelist: " + currentVEventFilterList.isWhitelist + "\n" + + "User Property Filter List: " + currentVUserPropertyFilterList.filterList + ", isWhitelist: " + currentVUserPropertyFilterList.isWhitelist + "\n" + + "Segmentation Filter List: " + currentVSegmentationFilterList.filterList + ", isWhitelist: " + currentVSegmentationFilterList.isWhitelist + "\n" + + "Event Segmentation Filter List: " + currentVEventSegmentationFilterList.filterList + ", isWhitelist: " + currentVEventSegmentationFilterList.isWhitelist); + JSONArray eventBlacklistJSARR = latestRetrievedConfiguration.optJSONArray(keyREventBlacklist); + JSONArray eventWhitelistJSARR = latestRetrievedConfiguration.optJSONArray(keyREventWhitelist); + JSONArray userPropertyBlacklistJSARR = latestRetrievedConfiguration.optJSONArray(keyRUserPropertyBlacklist); + JSONArray userPropertyWhitelistJSARR = latestRetrievedConfiguration.optJSONArray(keyRUserPropertyWhitelist); + JSONArray segmentationBlacklistJSARR = latestRetrievedConfiguration.optJSONArray(keyRSegmentationBlacklist); + JSONArray segmentationWhitelistJSARR = latestRetrievedConfiguration.optJSONArray(keyRSegmentationWhitelist); + JSONObject eventSegmentationBlacklistJSOBJ = latestRetrievedConfiguration.optJSONObject(keyREventSegmentationBlacklist); + JSONObject eventSegmentationWhitelistJSOBJ = latestRetrievedConfiguration.optJSONObject(keyREventSegmentationWhitelist); + + if (eventBlacklistJSARR != null) { + extractFilterSetFromJSONArray(eventBlacklistJSARR, currentVEventFilterList.filterList); + currentVEventFilterList.isWhitelist = false; + } else if (eventWhitelistJSARR != null) { + extractFilterSetFromJSONArray(eventWhitelistJSARR, currentVEventFilterList.filterList); + currentVEventFilterList.isWhitelist = true; + } + + if (userPropertyBlacklistJSARR != null) { + extractFilterSetFromJSONArray(userPropertyBlacklistJSARR, currentVUserPropertyFilterList.filterList); + currentVUserPropertyFilterList.isWhitelist = false; + } else if (userPropertyWhitelistJSARR != null) { + extractFilterSetFromJSONArray(userPropertyWhitelistJSARR, currentVUserPropertyFilterList.filterList); + currentVUserPropertyFilterList.isWhitelist = true; + } + + if (segmentationBlacklistJSARR != null) { + extractFilterSetFromJSONArray(segmentationBlacklistJSARR, currentVSegmentationFilterList.filterList); + currentVSegmentationFilterList.isWhitelist = false; + } else if (segmentationWhitelistJSARR != null) { + extractFilterSetFromJSONArray(segmentationWhitelistJSARR, currentVSegmentationFilterList.filterList); + currentVSegmentationFilterList.isWhitelist = true; + } + + if (eventSegmentationBlacklistJSOBJ != null) { + currentVEventSegmentationFilterList.filterList.clear(); + currentVEventSegmentationFilterList.isWhitelist = false; + Iterator keys = eventSegmentationBlacklistJSOBJ.keys(); + while (keys.hasNext()) { + String key = keys.next(); + JSONArray jsonArray = eventSegmentationBlacklistJSOBJ.optJSONArray(key); + if (jsonArray != null) { + Set filterSet = new HashSet<>(); + extractFilterSetFromJSONArray(jsonArray, filterSet); + currentVEventSegmentationFilterList.filterList.put(key, filterSet); + } + } + } else if (eventSegmentationWhitelistJSOBJ != null) { + currentVEventSegmentationFilterList.filterList.clear(); + currentVEventSegmentationFilterList.isWhitelist = true; + Iterator keys = eventSegmentationWhitelistJSOBJ.keys(); + while (keys.hasNext()) { + String key = keys.next(); + JSONArray jsonArray = eventSegmentationWhitelistJSOBJ.optJSONArray(key); + if (jsonArray != null) { + Set filterSet = new HashSet<>(); + extractFilterSetFromJSONArray(jsonArray, filterSet); + currentVEventSegmentationFilterList.filterList.put(key, filterSet); + } + } + } + + L.d("[ModuleConfiguration] updateListingFilters, current listing filters after updating: \n" + + "Event Filter List: " + currentVEventFilterList.filterList + ", isWhitelist: " + currentVEventFilterList.isWhitelist + "\n" + + "User Property Filter List: " + currentVUserPropertyFilterList.filterList + ", isWhitelist: " + currentVUserPropertyFilterList.isWhitelist + "\n" + + "Segmentation Filter List: " + currentVSegmentationFilterList.filterList + ", isWhitelist: " + currentVSegmentationFilterList.isWhitelist + "\n" + + "Event Segmentation Filter List: " + currentVEventSegmentationFilterList.filterList + ", isWhitelist: " + currentVEventSegmentationFilterList.isWhitelist); + } + + private void extractFilterSetFromJSONArray(@Nullable JSONArray jsonArray, @NonNull Set targetSet) { + if (jsonArray == null) { + return; + } + targetSet.clear(); + for (int i = 0; i < jsonArray.length(); i++) { + String item = jsonArray.optString(i, null); + if (item != null) { + targetSet.add(item); + } + } + } + boolean validateServerConfig(@NonNull JSONObject config) { JSONObject newInner = config.optJSONObject(keyRConfig); @@ -293,6 +405,7 @@ private void removeUnsupportedKeys(@NonNull JSONObject newInner) { case keyRLimitBreadcrumb: case keyRLimitTraceLine: case keyRLimitTraceLength: + case keyRUserPropertyCacheLimit: isValid = value instanceof Integer && ((Integer) value) > 0; break; @@ -310,6 +423,20 @@ private void removeUnsupportedKeys(@NonNull JSONObject newInner) { case keyRBOMRQPercentage: isValid = value instanceof Double && ((Double) value > 0.0 && (Double) value < 1.0); break; + + // --- Filtering keys --- + case keyREventBlacklist: + case keyRSegmentationBlacklist: + case keyRUserPropertyBlacklist: + case keyREventWhitelist: + case keyRSegmentationWhitelist: + case keyRUserPropertyWhitelist: + isValid = value instanceof JSONArray; + break; + case keyREventSegmentationBlacklist: + case keyREventSegmentationWhitelist: + isValid = value instanceof JSONObject; + break; // --- Unknown keys --- default: L.w("[ModuleConfiguration] removeUnsupportedKeys, Unknown key: [" + key + "], removing it. value: [" + value + "]"); @@ -498,4 +625,24 @@ public boolean getTrackingEnabled() { @Override public int getRequestTimeoutDurationMillis() { return _cly.config_.requestTimeoutDuration * 1000; } + + @Override public int getUserPropertyCacheLimit() { + return currentVUserPropertyCacheLimit; + } + + @Override public FilterList> getEventFilterList() { + return currentVEventFilterList; + } + + @Override public FilterList> getUserPropertyFilterList() { + return currentVUserPropertyFilterList; + } + + @Override public FilterList> getSegmentationFilterList() { + return currentVSegmentationFilterList; + } + + @Override public FilterList>> getEventSegmentationFilterList() { + return currentVEventSegmentationFilterList; + } } diff --git a/sdk/src/main/java/ly/count/android/sdk/UtilsListingFilters.java b/sdk/src/main/java/ly/count/android/sdk/UtilsListingFilters.java new file mode 100644 index 000000000..53bebb174 --- /dev/null +++ b/sdk/src/main/java/ly/count/android/sdk/UtilsListingFilters.java @@ -0,0 +1,75 @@ +package ly.count.android.sdk; + +import androidx.annotation.NonNull; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class UtilsListingFilters { + + private UtilsListingFilters() { + } + + static boolean applyEventFilter(@NonNull String eventName, @NonNull ConfigurationProvider configProvider) { + ConfigurationProvider.FilterList> eventFilterList = configProvider.getEventFilterList(); + return applyListFilter(eventName, eventFilterList.filterList, eventFilterList.isWhitelist); + } + + static boolean applyUserPropertyFilter(@NonNull String propertyName, @NonNull ConfigurationProvider configProvider) { + ConfigurationProvider.FilterList> userPropertyFilterList = configProvider.getUserPropertyFilterList(); + return applyListFilter(propertyName, userPropertyFilterList.filterList, userPropertyFilterList.isWhitelist); + } + + static void applySegmentationFilter(@NonNull Map segmentation, @NonNull ConfigurationProvider configProvider, @NonNull ModuleLog L) { + if (segmentation.isEmpty()) { + return; + } + + applyMapFilter(segmentation, configProvider.getSegmentationFilterList().filterList, configProvider.getSegmentationFilterList().isWhitelist, L); + } + + static void applyEventSegmentationFilter(@NonNull String eventName, @NonNull Map segmentation, + @NonNull ConfigurationProvider configProvider, @NonNull ModuleLog L) { + ConfigurationProvider.FilterList>> eventSegmentationFilterList = configProvider.getEventSegmentationFilterList(); + if (segmentation.isEmpty() || eventSegmentationFilterList.filterList.isEmpty()) { + return; + } + + Set segmentationSet = eventSegmentationFilterList.filterList.get(eventName); + if (segmentationSet == null || segmentationSet.isEmpty()) { + // No rules defined for this event so allow everything + return; + } + applyMapFilter(segmentation, segmentationSet, eventSegmentationFilterList.isWhitelist, L); + } + + private static void applyMapFilter(@NonNull Map map, @NonNull Set filterSet, boolean isWhitelist, @NonNull ModuleLog L) { + if (filterSet.isEmpty()) { + // No rules defined so allow everything + return; + } + + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + String key = entry.getKey(); + + boolean contains = filterSet.contains(key); + + // Whitelist: remove if NOT in list + // Blacklist: remove if IN list + if ((isWhitelist && !contains) || (!isWhitelist && contains)) { + iterator.remove(); + L.d("[UtilsListingFilters] applyMapFilter, removed key: " + key + (isWhitelist ? "not in whitelist" : "blacklisted")); + } + } + } + + private static boolean applyListFilter(String item, @NonNull Set filterSet, boolean isWhitelist) { + if (filterSet.isEmpty()) { + // No rules defined so allow everything + return true; + } + return isWhitelist == filterSet.contains(item); + } +}