1
+ package com .optimizely .ab .config ;
2
+
3
+ import javax .annotation .Nonnull ;
4
+ import javax .annotation .Nullable ;
5
+ import java .util .*;
6
+ import java .util .concurrent .ConcurrentHashMap ;
7
+
8
+ /**
9
+ * HoldoutConfig manages collections of Holdout objects and their relationships to flags.
10
+ */
11
+ public class HoldoutConfig {
12
+ private List <Holdout > allHoldouts ;
13
+ private List <Holdout > global ;
14
+ private Map <String , Holdout > holdoutIdMap ;
15
+ private Map <String , List <Holdout >> flagHoldoutsMap ;
16
+ private Map <String , List <Holdout >> includedHoldouts ;
17
+ private Map <String , List <Holdout >> excludedHoldouts ;
18
+
19
+ /**
20
+ * Initializes a new HoldoutConfig with an empty list of holdouts.
21
+ */
22
+ public HoldoutConfig () {
23
+ this (Collections .emptyList ());
24
+ }
25
+
26
+ /**
27
+ * Initializes a new HoldoutConfig with the specified holdouts.
28
+ *
29
+ * @param allHoldouts The list of holdouts to manage
30
+ */
31
+ public HoldoutConfig (@ Nonnull List <Holdout > allHoldouts ) {
32
+ this .allHoldouts = new ArrayList <>(allHoldouts );
33
+ this .global = new ArrayList <>();
34
+ this .holdoutIdMap = new HashMap <>();
35
+ this .flagHoldoutsMap = new ConcurrentHashMap <>();
36
+ this .includedHoldouts = new HashMap <>();
37
+ this .excludedHoldouts = new HashMap <>();
38
+ updateHoldoutMapping ();
39
+ }
40
+
41
+ /**
42
+ * Updates internal mappings of holdouts including the id map, global list,
43
+ * and per-flag inclusion/exclusion maps.
44
+ */
45
+ public void updateHoldoutMapping () {
46
+ holdoutIdMap .clear ();
47
+ for (Holdout holdout : allHoldouts ) {
48
+ holdoutIdMap .put (holdout .getId (), holdout );
49
+ }
50
+
51
+ flagHoldoutsMap .clear ();
52
+ global .clear ();
53
+ includedHoldouts .clear ();
54
+ excludedHoldouts .clear ();
55
+
56
+ for (Holdout holdout : allHoldouts ) {
57
+ boolean hasIncludedFlags = !holdout .getIncludedFlags ().isEmpty ();
58
+ boolean hasExcludedFlags = !holdout .getExcludedFlags ().isEmpty ();
59
+
60
+ if (!hasIncludedFlags && !hasExcludedFlags ) {
61
+ // Global holdout (applies to all flags)
62
+ global .add (holdout );
63
+ } else if (hasIncludedFlags ) {
64
+ // Holdout only applies to specific included flags
65
+ for (String flagId : holdout .getIncludedFlags ()) {
66
+ includedHoldouts .computeIfAbsent (flagId , k -> new ArrayList <>()).add (holdout );
67
+ }
68
+ } else {
69
+ // Global holdout with specific exclusions
70
+ global .add (holdout );
71
+
72
+ for (String flagId : holdout .getExcludedFlags ()) {
73
+ excludedHoldouts .computeIfAbsent (flagId , k -> new ArrayList <>()).add (holdout );
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Returns the applicable holdouts for the given flag ID by combining global holdouts
81
+ * (excluding any specified) and included holdouts, in that order.
82
+ * Caches the result for future calls.
83
+ *
84
+ * @param id The flag identifier
85
+ * @return A list of Holdout objects relevant to the given flag
86
+ */
87
+ public List <Holdout > getHoldoutForFlag (@ Nonnull String id ) {
88
+ if (allHoldouts .isEmpty ()) {
89
+ return Collections .emptyList ();
90
+ }
91
+
92
+ // Check cache and return persistent holdouts
93
+ if (flagHoldoutsMap .containsKey (id )) {
94
+ return flagHoldoutsMap .get (id );
95
+ }
96
+
97
+ // Prioritize global holdouts first
98
+ List <Holdout > activeHoldouts = new ArrayList <>();
99
+ List <Holdout > excluded = excludedHoldouts .getOrDefault (id , Collections .emptyList ());
100
+
101
+ if (!excluded .isEmpty ()) {
102
+ for (Holdout holdout : global ) {
103
+ if (!excluded .contains (holdout )) {
104
+ activeHoldouts .add (holdout );
105
+ }
106
+ }
107
+ } else {
108
+ activeHoldouts .addAll (global );
109
+ }
110
+
111
+ // Add included holdouts
112
+ activeHoldouts .addAll (includedHoldouts .getOrDefault (id , Collections .emptyList ()));
113
+
114
+ // Cache the result
115
+ flagHoldoutsMap .put (id , activeHoldouts );
116
+
117
+ return activeHoldouts ;
118
+ }
119
+
120
+ /**
121
+ * Get a Holdout object for an Id.
122
+ *
123
+ * @param id The holdout identifier
124
+ * @return The Holdout object if found, null otherwise
125
+ */
126
+ @ Nullable
127
+ public Holdout getHoldout (@ Nonnull String id ) {
128
+ return holdoutIdMap .get (id );
129
+ }
130
+
131
+ /**
132
+ * Returns all holdouts managed by this config.
133
+ *
134
+ * @return An unmodifiable list of all holdouts
135
+ */
136
+ public List <Holdout > getAllHoldouts () {
137
+ return Collections .unmodifiableList (allHoldouts );
138
+ }
139
+
140
+ /**
141
+ * Returns the global holdouts (those that apply to all flags by default).
142
+ *
143
+ * @return An unmodifiable list of global holdouts
144
+ */
145
+ public List <Holdout > getGlobal () {
146
+ return Collections .unmodifiableList (global );
147
+ }
148
+
149
+ }
0 commit comments