1 package org.wikimedia.search.extra.superdetectnoop;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Iterator;
6 import java.util.LinkedHashSet;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Optional;
10 import java.util.Set;
11 import java.util.stream.Collectors;
12
13 import javax.annotation.Nullable;
14
15 import com.google.common.collect.ImmutableSet;
16
17
18
19
20 public class SetHandler implements ChangeHandler<Object> {
21
22
23
24
25
26
27 private static final ChangeHandler<Object> INSTANCE = new SetHandler(150, Integer.MAX_VALUE, 20);
28
29 private static final String PARAM_ADD = "add";
30 private static final String PARAM_REMOVE = "remove";
31 private static final String PARAM_MAX_SIZE = "max_size";
32
33 private static final Set<String> VALID_PARAMS = ImmutableSet.of(PARAM_ADD, PARAM_REMOVE, PARAM_MAX_SIZE);
34
35 public static class Recognizer implements ChangeHandler.Recognizer {
36 @Override
37 public ChangeHandler<Object> build(String description) {
38 if (description.equals("set")) {
39 return INSTANCE;
40 }
41 return null;
42 }
43 }
44
45 private final int minConvert;
46 private final int maxConvert;
47 private final int maxKeepAsList;
48
49 public SetHandler(int minConvert, int maxConvert, int maxKeepAsList) {
50 this.minConvert = minConvert;
51 this.maxConvert = maxConvert;
52 this.maxKeepAsList = maxKeepAsList;
53 }
54
55 @Override
56 @SuppressWarnings({"unchecked", "CyclomaticComplexity", "NPathComplexity"})
57 public ChangeHandler.Result handle(@Nullable Object oldValue, @Nullable Object newValue) {
58 if (newValue == null) {
59 return Changed.forBoolean(oldValue == null, null);
60 }
61
62
63
64
65 Collection<Object> value = listify(oldValue);
66 Map<String, Object> params;
67 try {
68 params = ((Map<String, Object>) newValue);
69 final String excessiveParams = params.keySet().stream()
70 .filter(key -> !VALID_PARAMS.contains(key)).collect(Collectors.joining(", "));
71 if (!excessiveParams.isEmpty()) {
72 throw new IllegalArgumentException("Unexpected parameter(s) "
73 + excessiveParams + "; expected "
74 + String.join(", ", VALID_PARAMS));
75 }
76 } catch (ClassCastException e) {
77 throw new IllegalArgumentException("Expected parameters to be a map containing "
78 + String.join(", ", VALID_PARAMS), e);
79 }
80 List<Object> remove = listify(params.get(PARAM_REMOVE));
81 List<Object> add = listify(params.get(PARAM_ADD))
82 .stream().filter(toAdd -> !remove.contains(toAdd))
83 .collect(Collectors.toList());
84
85 int maxSize = Optional.ofNullable((Number) params.get(PARAM_MAX_SIZE)).map(Number::intValue)
86 .orElse(Integer.MAX_VALUE);
87
88 if (add.size() + remove.size() > maxKeepAsList && minConvert < value.size() && value.size() < maxConvert) {
89 value = new LinkedHashSet<>(value);
90 }
91 boolean changed = value.removeAll(remove);
92 long remainingAddCount = Math.min(Math.max(0, maxSize - value.size()), add.size());
93
94 final Iterator<Object> adderator = add.iterator();
95 while (remainingAddCount > 0 && adderator.hasNext()) {
96 final Object toAdd = adderator.next();
97 if (!value.contains(toAdd)) {
98 value.add(toAdd);
99 changed = true;
100 --remainingAddCount;
101 }
102 }
103 if (!changed) {
104 return CloseEnough.INSTANCE;
105 }
106 return new Changed(value instanceof List ? value : new ArrayList<>(value));
107 }
108
109
110
111
112
113
114 @SuppressWarnings("unchecked")
115 private List<Object> listify(@Nullable Object value) {
116 if (value == null) {
117 return new ArrayList<>();
118 }
119 if (value instanceof List) {
120 return (List<Object>) value;
121 }
122 List<Object> result = new ArrayList<>();
123 result.add(value);
124 return result;
125 }
126 }