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