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 int problemsReportedFor(BuilderProblem.Severity... severities);
97
98
99
100
101
102
103 boolean problemsOverflow();
104
105
106
107
108
109
110
111 boolean reportProblem(P problem);
112
113
114
115
116
117 @Nonnull
118 default Stream<P> problems() {
119 Stream<P> result = Stream.empty();
120 for (BuilderProblem.Severity severity : BuilderProblem.Severity.values()) {
121 result = Stream.concat(result, problems(severity));
122 }
123 return result;
124 }
125
126
127
128
129
130 @Nonnull
131 Stream<P> problems(BuilderProblem.Severity severity);
132
133
134
135
136 @Nonnull
137 static <P extends BuilderProblem> ProblemCollector<P> empty() {
138 return new ProblemCollector<>() {
139 @Override
140 public boolean problemsOverflow() {
141 return false;
142 }
143
144 @Override
145 public int problemsReportedFor(BuilderProblem.Severity... severities) {
146 return 0;
147 }
148
149 @Override
150 public boolean reportProblem(P problem) {
151 throw new IllegalStateException("empty problem collector");
152 }
153
154 @Override
155 public Stream<P> problems(BuilderProblem.Severity severity) {
156 return Stream.empty();
157 }
158 };
159 }
160
161
162
163
164 @Nonnull
165 static <P extends BuilderProblem> ProblemCollector<P> create(@Nullable ProtoSession protoSession) {
166 if (protoSession != null
167 && protoSession.getUserProperties().containsKey(Constants.MAVEN_BUILDER_MAX_PROBLEMS)) {
168 return new Impl<>(
169 Integer.parseInt(protoSession.getUserProperties().get(Constants.MAVEN_BUILDER_MAX_PROBLEMS)));
170 } else {
171 return create(100);
172 }
173 }
174
175
176
177
178 @Nonnull
179 static <P extends BuilderProblem> ProblemCollector<P> create(int maxCountLimit) {
180 return new Impl<>(maxCountLimit);
181 }
182
183 class Impl<P extends BuilderProblem> implements ProblemCollector<P> {
184
185 private final int maxCountLimit;
186 private final AtomicInteger totalCount;
187 private final ConcurrentMap<BuilderProblem.Severity, LongAdder> counters;
188 private final ConcurrentMap<BuilderProblem.Severity, List<P>> problems;
189
190 private static final List<BuilderProblem.Severity> REVERSED_ORDER = Arrays.stream(
191 BuilderProblem.Severity.values())
192 .sorted(Comparator.reverseOrder())
193 .toList();
194
195 private Impl(int maxCountLimit) {
196 if (maxCountLimit < 0) {
197 throw new IllegalArgumentException("maxCountLimit must be non-negative");
198 }
199 this.maxCountLimit = maxCountLimit;
200 this.totalCount = new AtomicInteger();
201 this.counters = new ConcurrentHashMap<>();
202 this.problems = new ConcurrentHashMap<>();
203 }
204
205 @Override
206 public int problemsReportedFor(BuilderProblem.Severity... severity) {
207 int result = 0;
208 for (BuilderProblem.Severity s : severity) {
209 result += getCounter(s).intValue();
210 }
211 return result;
212 }
213
214 @Override
215 public boolean problemsOverflow() {
216 return totalCount.get() > maxCountLimit;
217 }
218
219 @Override
220 public boolean reportProblem(P problem) {
221 requireNonNull(problem, "problem");
222 int currentCount = totalCount.incrementAndGet();
223 getCounter(problem.getSeverity()).increment();
224 if (currentCount <= maxCountLimit || dropProblemWithLowerSeverity(problem.getSeverity())) {
225 getProblems(problem.getSeverity()).add(problem);
226 return true;
227 }
228 return false;
229 }
230
231 @Override
232 public Stream<P> problems(BuilderProblem.Severity severity) {
233 requireNonNull(severity, "severity");
234 return getProblems(severity).stream();
235 }
236
237 private LongAdder getCounter(BuilderProblem.Severity severity) {
238 return counters.computeIfAbsent(severity, k -> new LongAdder());
239 }
240
241 private List<P> getProblems(BuilderProblem.Severity severity) {
242 return problems.computeIfAbsent(severity, k -> new CopyOnWriteArrayList<>());
243 }
244
245 private boolean dropProblemWithLowerSeverity(BuilderProblem.Severity severity) {
246 for (BuilderProblem.Severity s : REVERSED_ORDER) {
247 if (s.ordinal() > severity.ordinal()) {
248 List<P> problems = getProblems(s);
249 while (!problems.isEmpty()) {
250 try {
251 return problems.remove(0) != null;
252 } catch (IndexOutOfBoundsException e) {
253
254 }
255 }
256 }
257 }
258 return false;
259 }
260 }
261 }