Skip to content

Package: DiagnosticFrequencyMap$Unlimited$1

DiagnosticFrequencyMap$Unlimited$1

nameinstructionbranchcomplexitylinemethod
hasNext()
M: 0 C: 4
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
itr()
M: 0 C: 18
100%
M: 0 C: 4
100%
M: 0 C: 3
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
next()
M: 0 C: 5
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
{...}
M: 0 C: 36
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 5
100%
M: 0 C: 1
100%

Coverage

1: /*******************************************************************************
2: * Copyright (c) 2019 Christian W. Damus and others.
3: *
4: * All rights reserved. This program and the accompanying materials
5: * are made available under the terms of the Eclipse Public License 2.0
6: * which accompanies this distribution, and is available at
7: * https://www.eclipse.org/legal/epl-2.0/
8: *
9: * SPDX-License-Identifier: EPL-2.0
10: *
11: * Contributors:
12: * Christian W. Damus - initial API and implementation
13: ******************************************************************************/
14: package org.eclipse.emfforms.spi.common.validation;
15:
16: import java.util.ArrayList;
17: import java.util.Arrays;
18: import java.util.Collection;
19: import java.util.Iterator;
20: import java.util.List;
21: import java.util.function.Predicate;
22:
23: import org.eclipse.emf.common.util.Diagnostic;
24:
25: /**
26: * A simple collector of diagnostics that maps them in a histogram by severity.
27: * Collected diagnostics are {@linkplain Iterable#iterator() iterated} in
28: * decreasing severity order.
29: *
30: * @since 1.21
31: */
32: public interface DiagnosticFrequencyMap extends Iterable<Diagnostic> {
33:
34:         /**
35:          * Remove all diagnostics and histogram counts.
36:          */
37:         void clear();
38:
39:         /**
40:          * Queries the number of diagnostics that I contain.
41:          *
42:          * @return my size
43:          */
44:         int size();
45:
46:         /**
47:          * Queries whether I am empty of any diagnostics.
48:          *
49:          * @return {@code true} I have no diagnostics; {@code false}, otherwise
50:          */
51:         boolean isEmpty();
52:
53:         /**
54:          * Queries whether I am full to capacity, unable to accept any more diagnostics.
55:          *
56:          * @return {@code true} if I have reached my limit of diagnostics; {@code false}, otherwise
57:          */
58:         boolean isFull();
59:
60:         /**
61:          * Query the greatest severity of diagnostics that had to be discarded because the
62:          * map {@linkplain #isFull() filled up}.
63:          *
64:          * @return the highest discarded severity, or {@link Diagnostic#OK} if no diagnostics were discarded
65:          */
66:         int getDiscardedSeverity();
67:
68:         /**
69:          * Add a diagnostic.
70:          *
71:          * @param diagnostic a diagnostic to add
72:          * @return {@code true} if it was added; {@code false}, otherwise, for example if I am {@linkplain #isFull() full}
73:          */
74:         boolean add(Diagnostic diagnostic);
75:
76:         /**
77:          * Add a predicate that matches diagnostics that should be collected.
78:          * Diagnostics that do not match the filter are discarded, but their
79:          * {@linkplain #getDiscardedSeverity() severity is retained} for separate
80:          * reporting if the client should so wish. A diagnostic is only accepted
81:          * that satisfies all {@code filter}s (overall, it's a conjunction).
82:          *
83:          * @param filter a predicate matching diagnostics to accept
84:          */
85:         void addDiagnosticFilter(Predicate<? super Diagnostic> filter);
86:
87:         /**
88:          * Add a bunch of diagnostics.
89:          *
90:          * @param diagnostics some diagnostics to add
91:          * @return {@code true} if any of them were added; {@code false} if none
92:          *
93:          * @see #add(Diagnostic)
94:          */
95:         default boolean addAll(Collection<?> diagnostics) {
96:                 boolean result = false;
97:
98:                 for (final Object next : diagnostics) {
99:                         if (next instanceof Diagnostic) {
100:                                 result = add((Diagnostic) next) || result;
101:                                 if (!result) {
102:                                         break;
103:                                 }
104:                         }
105:                 }
106:
107:                 return result;
108:         }
109:
110:         /**
111:          * Append the diagnostics that I have collected, in decreasing severity order,
112:          * to a collection of {@code diagnostics}.
113:          *
114:          * @param diagnostics a collection of diagnostics to append to
115:          */
116:         default void appendTo(Collection<? super Diagnostic> diagnostics) {
117:                 for (final Diagnostic next : this) {
118:                         diagnostics.add(next);
119:                 }
120:         }
121:
122:         /**
123:          * Create a frequency map of unlimited size that keeps all diagnostics added to it.
124:          *
125:          * @return a new unlimited-size diagnostic frequency map
126:          */
127:         static DiagnosticFrequencyMap unlimited() {
128:                 return new Unlimited();
129:         }
130:
131:         /**
132:          * Create a frequency map that retains at most the given number of
133:          * most severe problems.
134:          *
135:          * @param size the maximal size of diagnostic collection
136:          * @return a new limited-{@code size} diagnostic frequency map
137:          *
138:          * @throws IllegalArgumentException if the {@code size} is negative
139:          */
140:         static DiagnosticFrequencyMap limitedTo(int size) {
141:                 return new Limited(size);
142:         }
143:
144:         //
145:         // Nested types
146:         //
147:
148:         /**
149:          * A frequency map of unlimited size that keeps all diagnostics added to it.
150:          *
151:          * @since 1.21
152:          */
153:         class Unlimited implements DiagnosticFrequencyMap {
154:
155:                 // CHECKSTYLE.OFF: VisibilityModifier - only visible to other inner classes
156:                 /** The bucket of error (and cancel) diagnostics. */
157:                 final List<Diagnostic> errors;
158:
159:                 /** The bucket of warning diagnostics. */
160:                 final List<Diagnostic> warnings;
161:
162:                 /** The bucket of informational diagnostics. */
163:                 final List<Diagnostic> infos;
164:                 // CHECKSTYLE.ON: VisibilityModifier
165:
166:                 private Predicate<Diagnostic> filter;
167:
168:                 private int size;
169:
170:                 private int discardedSeverity = Diagnostic.OK;
171:
172:                 /**
173:                  * Initializes me.
174:                  */
175:                 Unlimited() {
176:                         this(10); // The ArrayList default capacity
177:                 }
178:
179:                 /**
180:                  * Initializes me with the initial capacity of my buckets.
181:                  *
182:                  * @param capacity the initial capacity of my diagnostic severity buckets
183:                  */
184:                 Unlimited(int capacity) {
185:                         super();
186:
187:                         errors = new ArrayList<>(capacity);
188:                         warnings = new ArrayList<>(capacity);
189:                         infos = new ArrayList<>(capacity);
190:                 }
191:
192:                 @Override
193:                 public void clear() {
194:                         size = 0;
195:                         discardedSeverity = Diagnostic.OK;
196:                         errors.clear();
197:                         warnings.clear();
198:                         infos.clear();
199:                 }
200:
201:                 @Override
202:                 public int size() {
203:                         return size;
204:                 }
205:
206:                 @Override
207:                 public boolean isEmpty() {
208:                         return size == 0;
209:                 }
210:
211:                 @Override
212:                 public boolean isFull() {
213:                         // I cannot be full
214:                         return false;
215:                 }
216:
217:                 @Override
218:                 public int getDiscardedSeverity() {
219:                         return discardedSeverity;
220:                 }
221:
222:                 @Override
223:                 public boolean add(Diagnostic diagnostic) {
224:                         if (isFull() || !filter(diagnostic)) {
225:                                 // Already full or doesn't pass the filter
226:                                 discarded(diagnostic.getSeverity());
227:                                 return false;
228:                         }
229:
230:                         boolean result = false;
231:
232:                         switch (diagnostic.getSeverity()) {
233:                         case Diagnostic.CANCEL:
234:                         case Diagnostic.ERROR:
235:                                 // No point in tracking more errors than problems we can report in total
236:                                 if (canAdd(Diagnostic.ERROR)) {
237:                                         result = errors.add(diagnostic);
238:                                         if (!discard(Diagnostic.WARNING)) {
239:                                                 size = size + 1;
240:                                         }
241:                                 } else {
242:                                         discarded(diagnostic.getSeverity());
243:                                 }
244:                                 break;
245:                         case Diagnostic.WARNING:
246:                                 // No point in tracking more warnings than problems we can report in total
247:                                 if (canAdd(Diagnostic.ERROR | Diagnostic.WARNING)) {
248:                                         result = warnings.add(diagnostic);
249:                                         if (!discard(Diagnostic.INFO)) {
250:                                                 size = size + 1;
251:                                         }
252:                                 } else {
253:                                         discarded(Diagnostic.WARNING);
254:                                 }
255:                                 break;
256:                         case Diagnostic.INFO:
257:                                 // No point in tracking more infos than problems we can report in total
258:                                 if (canAdd(Diagnostic.ERROR | Diagnostic.WARNING | Diagnostic.INFO)) {
259:                                         result = infos.add(diagnostic);
260:                                         if (!discard(Diagnostic.OK)) {
261:                                                 size = size + 1;
262:                                         }
263:                                 } else {
264:                                         discarded(Diagnostic.INFO);
265:                                 }
266:                                 break;
267:                         default:
268:                                 // We don't track OK diagnostics at all, but we don't need to
269:                                 // tell anyone that
270:                                 result = true;
271:                                 break;
272:                         }
273:
274:                         return result;
275:                 }
276:
277:                 @Override
278:                 public void addDiagnosticFilter(Predicate<? super Diagnostic> filter) {
279:                         if (this.filter == null) {
280:                                 @SuppressWarnings("unchecked") // This is safe because we only filter diagnostics
281:                                 final Predicate<Diagnostic> cast = (Predicate<Diagnostic>) filter;
282:                                 this.filter = cast;
283:                         } else {
284:                                 this.filter = this.filter.and(filter);
285:                         }
286:                 }
287:
288:                 /**
289:                  * Query whether a {@code diagnostic} passes my filter.
290:                  *
291:                  * @param diagnostic a diagnostic
292:                  * @return {@code true} if I have no filter or if the {@code diagnostic} passes it
293:                  */
294:                 private boolean filter(Diagnostic diagnostic) {
295:                         return filter == null || filter.test(diagnostic);
296:                 }
297:
298:                 /**
299:                  * Query whether any diagnostic with a severity matching the given mask
300:                  * can be added.
301:                  *
302:                  * @param severityMask a bitmask of severity values
303:                  * @return {@code true} if I have room for another diagnostic of any of the masked
304:                  * severities; {@code false}, otherwise
305:                  */
306:                 boolean canAdd(int severityMask) {
307:                         return true;
308:                 }
309:
310:                 /**
311:                  * Discard a diagnostic of the given {@code severity}, if appropriate.
312:                  *
313:                  * @param severity severity of a diagnostic to consider discarding
314:                  * @return {@code true} if the diagnostic was discarded; {@code false}, otherwise
315:                  */
316:                 boolean discard(int severity) {
317:                         return false;
318:                 }
319:
320:                 /**
321:                  * Process the successful discarding of a diagnostic of the given {@code severity}.
322:                  *
323:                  * @param severity the severity of a diagnostic that was discarded
324:                  */
325:                 void discarded(int severity) {
326:                         if (discardedSeverity < severity) {
327:                                 discardedSeverity = severity;
328:                         }
329:                 }
330:
331:                 @Override
332:                 public void appendTo(Collection<? super Diagnostic> diagnostics) {
333:                         if (!errors.isEmpty()) {
334:                                 diagnostics.addAll(errors);
335:                         }
336:                         if (!warnings.isEmpty()) {
337:                                 diagnostics.addAll(warnings);
338:                         }
339:                         if (!infos.isEmpty()) {
340:                                 diagnostics.addAll(infos);
341:                         }
342:                 }
343:
344:                 @Override
345:                 public Iterator<Diagnostic> iterator() {
346:                         return new Iterator<Diagnostic>() {
347:                                 private final Iterator<Iterator<Diagnostic>> iitr = Arrays.asList(
348:                                         errors.iterator(), warnings.iterator(), infos.iterator())
349:                                         .iterator();
350:
351:                                 private Iterator<Diagnostic> itr = iitr.next();
352:
353:                                 private Iterator<Diagnostic> itr() {
354:•                                        while (!itr.hasNext() && iitr.hasNext()) {
355:                                                 itr = iitr.next();
356:                                         }
357:                                         return itr;
358:                                 }
359:
360:                                 @Override
361:                                 public boolean hasNext() {
362:                                         return itr().hasNext();
363:                                 }
364:
365:                                 @Override
366:                                 public Diagnostic next() {
367:                                         return itr().next();
368:                                 }
369:                         };
370:                 }
371:
372:         }
373:
374:         /**
375:          * A frequency map of limited size that collects up to and no more than a certain
376:          * number of diagnostics, keeping the most severe of them.
377:          *
378:          * @since 1.21
379:          */
380:         class Limited extends Unlimited {
381:                 private final int limit;
382:
383:                 /**
384:                  * Initializes me with the maximal number of diagnostics that we want to retain.
385:                  *
386:                  * @param limit the maximal number of diagnostics
387:                  *
388:                  * @throws IllegalArgumentException if the {@code limit} is negative
389:                  */
390:                 Limited(int limit) {
391:                         super(checkLimit(limit));
392:
393:                         this.limit = limit;
394:                 }
395:
396:                 private static int checkLimit(int limit) {
397:                         if (limit < 0) {
398:                                 throw new IllegalArgumentException("negative limit"); //$NON-NLS-1$
399:                         }
400:                         return limit;
401:                 }
402:
403:                 @Override
404:                 public boolean isFull() {
405:                         return errors.size() >= limit;
406:                 }
407:
408:                 @Override
409:                 boolean canAdd(int severityMask) {
410:                         int count = 0;
411:
412:                         if ((severityMask & Diagnostic.ERROR) != 0) {
413:                                 count = count + errors.size();
414:                         }
415:                         if ((severityMask & Diagnostic.WARNING) != 0) {
416:                                 count = count + warnings.size();
417:                         }
418:                         if ((severityMask & Diagnostic.INFO) != 0) {
419:                                 count = count + infos.size();
420:                         }
421:
422:                         return count < limit;
423:                 }
424:
425:                 @Override
426:                 boolean discard(int severity) {
427:                         // Do we need to discard anything?
428:                         if (size() < limit) {
429:                                 // Nope
430:                                 return false;
431:                         }
432:
433:                         Diagnostic discarded = null;
434:
435:                         switch (severity) {
436:                         case Diagnostic.CANCEL:
437:                         case Diagnostic.ERROR:
438:                                 // We cannot discard errors because we don't accumulate more than the limit in the first place
439:                                 throw new IllegalArgumentException("Cannot discard errors"); //$NON-NLS-1$
440:                         case Diagnostic.WARNING:
441:                                 // Preferentially kick out infos
442:                                 if (!infos.isEmpty()) {
443:                                         discarded = infos.remove(infos.size() - 1);
444:                                 } else if (!warnings.isEmpty()) {
445:                                         discarded = warnings.remove(warnings.size() - 1);
446:                                 }
447:                                 break;
448:                         case Diagnostic.INFO:
449:                                 if (!infos.isEmpty()) {
450:                                         discarded = infos.remove(infos.size() - 1);
451:                                 }
452:                                 break;
453:                         default:
454:                                 // Cannot discard OK diagnostics because we don't track them
455:                                 break;
456:                         }
457:
458:                         if (discarded != null) {
459:                                 discarded(discarded.getSeverity());
460:                         }
461:
462:                         return discarded != null;
463:                 }
464:
465:         }
466:
467: }