1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.api.services;
20
21 import java.util.Arrays;
22 import java.util.Comparator;
23 import java.util.List;
24 import java.util.concurrent.ConcurrentHashMap;
25 import java.util.concurrent.ConcurrentMap;
26 import java.util.concurrent.CopyOnWriteArrayList;
27 import java.util.concurrent.atomic.AtomicInteger;
28 import java.util.concurrent.atomic.LongAdder;
29 import java.util.function.Predicate;
30 import java.util.stream.Stream;
31
32 import org.apache.maven.api.Constants;
33 import org.apache.maven.api.ProtoSession;
34 import org.apache.maven.api.annotations.Experimental;
35 import org.apache.maven.api.annotations.Nonnull;
36 import org.apache.maven.api.annotations.Nullable;
37
38 import static java.util.Objects.requireNonNull;
39
40
41
42
43
44
45
46 @Experimental
47 public interface ProblemCollector<P extends BuilderProblem> {
48
49
50
51
52
53 default boolean hasWarningProblems() {
54 return hasProblemsFor(BuilderProblem.Severity.WARNING);
55 }
56
57
58
59
60
61 default boolean hasErrorProblems() {
62 return hasProblemsFor(BuilderProblem.Severity.ERROR);
63 }
64
65
66
67
68
69 default boolean hasFatalProblems() {
70 return hasProblemsFor(BuilderProblem.Severity.FATAL);
71 }
72
73
74
75
76
77 default boolean hasProblemsFor(BuilderProblem.Severity severity) {
78 requireNonNull(severity, "severity");
79 for (BuilderProblem.Severity s : BuilderProblem.Severity.values()) {
80 if (s.ordinal() <= severity.ordinal() && problemsReportedFor(s) > 0) {
81 return true;
82 }
83 }
84 return false;
85 }
86
87
88
89
90 default int totalProblemsReported() {
91 return problemsReportedFor(BuilderProblem.Severity.values());
92 }
93
94
95
96
97
98
99
100 int problemsReportedFor(BuilderProblem.Severity... severities);
101
102
103
104
105
106
107
108
109 boolean problemsOverflow();
110
111
112
113
114
115
116
117
118 boolean reportProblem(P problem);
119
120
121
122
123
124 @Nonnull
125 default Stream<P> problems() {
126 Stream<P> result = Stream.empty();
127 for (BuilderProblem.Severity severity : BuilderProblem.Severity.values()) {
128 result = Stream.concat(result, problems(severity));
129 }
130 return result;
131 }
132
133
134
135
136
137
138
139
140 @Nonnull
141 Stream<P> problems(BuilderProblem.Severity severity);
142
143
144
145
146
147
148
149 @Nonnull
150 static <P extends BuilderProblem> ProblemCollector<P> empty() {
151 return new ProblemCollector<>() {
152 @Override
153 public boolean problemsOverflow() {
154 return false;
155 }
156
157 @Override
158 public int problemsReportedFor(BuilderProblem.Severity... severities) {
159 return 0;
160 }
161
162 @Override
163 public boolean reportProblem(P problem) {
164 throw new IllegalStateException("empty problem collector");
165 }
166
167 @Override
168 public Stream<P> problems(BuilderProblem.Severity severity) {
169 return Stream.empty();
170 }
171 };
172 }
173
174
175
176
177
178
179
180
181 @Nonnull
182 static <P extends BuilderProblem> ProblemCollector<P> create(@Nullable ProtoSession protoSession) {
183 if (protoSession != null
184 && protoSession.getUserProperties().containsKey(Constants.MAVEN_BUILDER_MAX_PROBLEMS)) {
185 int limit = Integer.parseInt(protoSession.getUserProperties().get(Constants.MAVEN_BUILDER_MAX_PROBLEMS));
186 return create(limit, p -> true);
187 } else {
188 return create(100);
189 }
190 }
191
192
193
194
195
196
197
198
199
200
201 @Nonnull
202 static <P extends BuilderProblem> ProblemCollector<P> create(int maxCountLimit, Predicate<? super P> filter) {
203 return new Impl<>(maxCountLimit, filter);
204 }
205
206
207
208
209
210
211
212
213
214 @Nonnull
215 static <P extends BuilderProblem> ProblemCollector<P> create(int maxCountLimit) {
216 return create(maxCountLimit, p -> true);
217 }
218
219
220
221
222
223
224 class Impl<P extends BuilderProblem> implements ProblemCollector<P> {
225
226 private final int maxCountLimit;
227 private final AtomicInteger totalCount;
228 private final ConcurrentMap<BuilderProblem.Severity, LongAdder> counters;
229 private final ConcurrentMap<BuilderProblem.Severity, List<P>> problems;
230 private final Predicate<? super P> filter;
231
232 private static final List<BuilderProblem.Severity> REVERSED_ORDER = Arrays.stream(
233 BuilderProblem.Severity.values())
234 .sorted(Comparator.reverseOrder())
235 .toList();
236
237 private Impl(int maxCountLimit, Predicate<? super P> filter) {
238 if (maxCountLimit < 0) {
239 throw new IllegalArgumentException("maxCountLimit must be non-negative");
240 }
241 this.maxCountLimit = maxCountLimit;
242 this.totalCount = new AtomicInteger();
243 this.counters = new ConcurrentHashMap<>();
244 this.problems = new ConcurrentHashMap<>();
245 this.filter = requireNonNull(filter, "filter");
246 }
247
248 @Override
249 public int problemsReportedFor(BuilderProblem.Severity... severity) {
250 int result = 0;
251 for (BuilderProblem.Severity s : severity) {
252 result += getCounter(s).intValue();
253 }
254 return result;
255 }
256
257 @Override
258 public boolean problemsOverflow() {
259 return totalCount.get() > maxCountLimit;
260 }
261
262 @Override
263 public boolean reportProblem(P problem) {
264 requireNonNull(problem, "problem");
265
266 if (!filter.test(problem)) {
267
268 return false;
269 }
270 int currentCount = totalCount.incrementAndGet();
271 getCounter(problem.getSeverity()).increment();
272 if (currentCount <= maxCountLimit || dropProblemWithLowerSeverity(problem.getSeverity())) {
273 getProblems(problem.getSeverity()).add(problem);
274 return true;
275 }
276 return false;
277 }
278
279 @Override
280 public Stream<P> problems(BuilderProblem.Severity severity) {
281 requireNonNull(severity, "severity");
282 return getProblems(severity).stream();
283 }
284
285 private LongAdder getCounter(BuilderProblem.Severity severity) {
286 return counters.computeIfAbsent(severity, k -> new LongAdder());
287 }
288
289 private List<P> getProblems(BuilderProblem.Severity severity) {
290 return problems.computeIfAbsent(severity, k -> new CopyOnWriteArrayList<>());
291 }
292
293 private boolean dropProblemWithLowerSeverity(BuilderProblem.Severity severity) {
294 for (BuilderProblem.Severity s : REVERSED_ORDER) {
295 if (s.ordinal() > severity.ordinal()) {
296 List<P> problems = getProblems(s);
297 while (!problems.isEmpty()) {
298 try {
299 return problems.remove(0) != null;
300 } catch (IndexOutOfBoundsException e) {
301
302 }
303 }
304 }
305 }
306 return false;
307 }
308 }
309 }