View Javadoc
1   /*
2   * Licensed to the Apache Software Foundation (ASF) under one   *
3   * or more contributor license agreements.  See the NOTICE file *
4   * distributed with this work for additional information        *
5   * regarding copyright ownership.  The ASF licenses this file   *
6   * to you under the Apache License, Version 2.0 (the            *
7   * "License"); you may not use this file except in compliance   *
8   * with the License.  You may obtain a copy of the License at   *
9   *                                                              *
10  *   http://www.apache.org/licenses/LICENSE-2.0                 *
11  *                                                              *
12  * Unless required by applicable law or agreed to in writing,   *
13  * software distributed under the License is distributed on an  *
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15  * KIND, either express or implied.  See the License for the    *
16  * specific language governing permissions and limitations      *
17  * under the License.                                           *
18  */
19  
20  package org.apache.rat.report.claim;
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.util.ArrayList;
25  import java.util.Comparator;
26  import java.util.List;
27  import java.util.Locale;
28  import java.util.Map;
29  import java.util.concurrent.ConcurrentHashMap;
30  
31  import javax.xml.parsers.DocumentBuilder;
32  
33  import org.apache.commons.io.function.IOSupplier;
34  import org.apache.commons.lang3.StringUtils;
35  import org.apache.rat.api.Document;
36  import org.apache.rat.configuration.XMLConfigurationReader;
37  import org.apache.rat.report.xml.writer.XmlWriter;
38  import org.apache.rat.utils.StandardXmlFactory;
39  import org.xml.sax.SAXException;
40  
41  /**
42   * This class provides a numerical overview about
43   * the report.
44   */
45  public class ClaimStatistic {
46      // keep the counter types in alphabetical order
47      /** The counter types */
48      public enum Counter {
49          /** count of approved files */
50          APPROVED("A count of approved licenses.", -1, 0),
51          /** count of archive files */
52          ARCHIVES("A count of archive files.", -1, 0),
53          /** count of binary  files */
54          BINARIES("A count of binary files.", -1, 0),
55          /** count of distinct document types */
56          DOCUMENT_TYPES("A count of distinct document types.", -1, 1),
57          /** count of generated/ignored files */
58          IGNORED("A count of ignored files.", -1, 0),
59          /** count of license categories */
60          LICENSE_CATEGORIES("A count of distinct license categories.", -1, 1),
61          /** count of distinct license names */
62          LICENSE_NAMES("A count of distinct license names.", -1, 1),
63          /** count of note files */
64          NOTICES("A count of notice files.", -1, 0),
65          /** count of standard files */
66          STANDARDS("A count of standard files.", -1, 1),
67          /** count of unapproved files */
68          UNAPPROVED("A count of unapproved licenses.", 0, 0),
69          /** count of unknown files */
70          UNKNOWN("A count of unknown file types.", -1, 0);
71  
72          /** The description of the counter */
73          private final String description;
74          /** The default max value for the counter */
75          private final int defaultMaxValue;
76          /** The default minimum value for the counter */
77          private final int defaultMinValue;
78  
79          Counter(final String description, final int defaultMaxValue, final int defaultMinValue) {
80              this.description = description;
81              this.defaultMaxValue = defaultMaxValue;
82              this.defaultMinValue = defaultMinValue;
83          }
84  
85          /**
86           * Gets the description of the counter.
87           * @return The description of the counter.
88           */
89          public String getDescription() {
90              return description;
91          }
92  
93          /**
94           * Gets the default maximum value for the counter.
95           * @return the default maximum value for the counter.
96           */
97          public int getDefaultMaxValue() {
98              return defaultMaxValue;
99          }
100         /**
101          * Gets the default minimum value for the counter.
102          * @return the default maximum value for the counter.
103          */
104         public int getDefaultMinValue() {
105             return defaultMinValue;
106         }
107 
108         /**
109          * Display name is capitalized and any underscores are replaced by spaces.
110          * @return displayName of the counter, capitalized and without underscores.
111          */
112         public String displayName() {
113             return StringUtils.capitalize(name().replace("_", " ").toLowerCase(Locale.ROOT));
114         }
115     }
116 
117     /** Count of license family name to counter */
118     private final ConcurrentHashMap<String, IntCounter> licenseNameMap = new ConcurrentHashMap<>();
119     /** Map of license family category to counter */
120     private final ConcurrentHashMap<String, IntCounter> licenseFamilyCategoryMap = new ConcurrentHashMap<>();
121     /** Map of document type to counter */
122     private final ConcurrentHashMap<Document.Type, IntCounter> documentTypeMap = new ConcurrentHashMap<>();
123     /** Map of counter type to value */
124     private final ConcurrentHashMap<ClaimStatistic.Counter, IntCounter> counterMap = new ConcurrentHashMap<>();
125 
126     public SerDes serDes() {
127         return new SerDes();
128     }
129     /**
130      * Converts {@code null} counter to 0.
131      *
132      * @param counter the Counter to retrieve the value from.
133      * @return 0 if counter is {@code null} or counter value otherwise.
134      */
135     private int getValue(final IntCounter counter) {
136         return counter == null ? 0 : counter.value();
137     }
138 
139     /**
140      * Returns the counts for the counter.
141      * @param counter the counter to get the value for.
142      * @return the number times the counter type was seen.
143      */
144     public int getCounter(final Counter counter) {
145         return getValue(counterMap.get(counter));
146     }
147 
148     /**
149      * Increments the counts for the counter.
150      * @param counter the counter to increment.
151      * @param value the value to increment the counter by.
152      */
153     public void incCounter(final Counter counter, final int value) {
154         counterMap.compute(counter, (k, v) -> v == null ? new IntCounter().increment(value) : v.increment(value));
155     }
156 
157     /**
158      * Increments the counts for the counter.
159      * @param counter the counter to increment.
160      * @param value the value to increment the counter by.
161      */
162     public void setCounter(final Counter counter, final int value) {
163         counterMap.put(counter, new IntCounter().increment(value));
164     }
165 
166     /**
167      * Gets the counts for the Document.Type.
168      * @param documentType the Document.Type to get the counter for.
169      * @return the number times the Document.Type was seen.
170      */
171     public int getCounter(final Document.Type documentType) {
172         return getValue(documentTypeMap.get(documentType));
173     }
174 
175     /**
176      * Gets the list of Document.Types seen in the run.
177      * @return the list of Document.Types seen in the run.
178      */
179     public List<Document.Type> getDocumentTypes() {
180         List<Document.Type> result = new ArrayList<>(documentTypeMap.keySet());
181         result.sort(Comparator.comparing(Enum::name));
182         return result;
183     }
184 
185     /**
186      * Increments the number of times the Document.Type was seen.
187      * @param documentType the Document.Type to increment.
188      * @param value the value to increment the counter by.
189      */
190     public void incCounter(final Document.Type documentType, final int value) {
191         documentTypeMap.compute(documentType, (k, v) -> updateCounter(Counter.DOCUMENT_TYPES, v, value));
192         switch (documentType) {
193             case STANDARD -> incCounter(Counter.STANDARDS, value);
194             case ARCHIVE -> incCounter(Counter.ARCHIVES, value);
195             case BINARY -> incCounter(Counter.BINARIES, value);
196             case NOTICE -> incCounter(Counter.NOTICES, value);
197             case UNKNOWN -> incCounter(Counter.UNKNOWN, value);
198             case IGNORED -> incCounter(Counter.IGNORED, value);
199         }
200     }
201 
202     /**
203      * Gets the counts for the license category.
204      * @param licenseFamilyCategory the license family category to get the count for.
205      * @return the number of times the license family category was seen.
206      */
207     public int getLicenseCategoryCount(final String licenseFamilyCategory) {
208         return getValue(licenseFamilyCategoryMap.get(licenseFamilyCategory));
209     }
210 
211     /**
212      * Gets the counts for the license name.
213      * @param licenseName the license name to get the count for.
214      * @return the number of times the license family category was seen.
215      */
216     public int getLicenseNameCount(final String licenseName) {
217         return getValue(licenseNameMap.get(licenseName));
218     }
219 
220     /**
221      * Updates the intCounter with the value and if the intCounter was {@code null} creates a new one and registers the
222      * creation as a counter type.
223      * @param counter the Type of the counter.
224      * @param intCounter the IntCounter to update. May be {@code null}.
225      * @param value the value to add to the int counter.
226      * @return the intCounter if it was not {@code null}, a new IntCounter otherwise.
227      */
228     private IntCounter updateCounter(final Counter counter, final IntCounter intCounter, final int value) {
229         if (intCounter == null) {
230             incCounter(counter, 1);
231             return new IntCounter().increment(value);
232         } else {
233             return intCounter.increment(value);
234         }
235     }
236 
237     /**
238      * Increments the number of times a license family category was seen.
239      * @param licenseFamilyCategory the License family category to increment.
240      * @param value the value to increment the count by.
241      */
242     public void incLicenseCategoryCount(final String licenseFamilyCategory, final int value) {
243         licenseFamilyCategoryMap.compute(licenseFamilyCategory, (k, v) -> updateCounter(Counter.LICENSE_CATEGORIES, v, value));
244     }
245 
246     /**
247      * Gets the set of license family categories that were seen.
248      * @return A set of license family categories.
249      */
250     public List<String> getLicenseFamilyCategories() {
251         List<String> result = new ArrayList<>(licenseFamilyCategoryMap.keySet());
252         result.sort(String::compareTo);
253         return result;
254     }
255 
256     /**
257      * Gets the license names sorted by name.
258      * @return sorted list of license names.
259      */
260     public List<String> getLicenseNames() {
261         List<String> result = new ArrayList<>(licenseNameMap.keySet());
262         result.sort(String::compareTo);
263         return result;
264     }
265 
266     /**
267      * Increments the license family name count.
268      * @param licenseName the license name to increment.
269      * @param value the value to increment the count by.
270      */
271     public void incLicenseNameCount(final String licenseName, final int value) {
272         licenseNameMap.compute(licenseName, (k, v) -> updateCounter(Counter.LICENSE_NAMES, v, value));
273     }
274 
275     /**
276      * A class that wraps an int and allows easy increment and retrieval.
277      */
278     static class IntCounter {
279         /**
280          * The value of the counter
281          */
282         private int value;
283 
284         /**
285          * Increment the count.
286          * @param count the count to increment by (can be negative).
287          * @return this.
288          */
289         public IntCounter increment(final int count) {
290             value += count;
291             return this;
292         }
293 
294         /**
295          * Retrieves the count.
296          * @return the count contained by this counter.
297          */
298         public int value() {
299             return value;
300         }
301 
302         @Override
303         public String toString() {
304             return String.valueOf(value);
305         }
306     }
307 
308     /**
309      * Serialize and deserialize the claim Statistic.
310      */
311     public class SerDes {
312         /** The count attribute string. */
313         private static final String COUNT = "count";
314         /** The name attribute string. */
315         private static final String NAME = "name";
316 
317         /**
318          * Serializes the claim statistic into an appendable.
319          * @param appendable the appendable to write to.
320          * @throws IOException on error.
321          */
322         public void serialize(final Appendable appendable) throws IOException {
323             try (XmlWriter writer = new XmlWriter(appendable)) {
324                 writer.startDocument().startElement("ClaimStatistic")
325                         .startElement("licenseNameMap");
326                 for (Map.Entry<String, IntCounter> entry : licenseNameMap.entrySet()) {
327                     if (entry.getValue().value > 0) {
328                         writer.startElement("licenseName")
329                                 .attribute(COUNT, entry.getValue().toString())
330                                 .attribute(NAME, entry.getKey()).closeElement();
331                     }
332                 }
333                 writer.closeElement()
334                         .startElement("licenseFamilyCategoryMap");
335                 for (Map.Entry<String, IntCounter> entry : licenseFamilyCategoryMap.entrySet()) {
336                     if (entry.getValue().value > 0) {
337                         writer.startElement("familyCategory")
338                                 .attribute(COUNT, entry.getValue().toString())
339                                 .attribute(NAME, entry.getKey()).closeElement();
340                     }
341                 }
342                 writer.closeElement()
343                         .startElement("documentTypeMap");
344                 for (Map.Entry<Document.Type, IntCounter> entry : documentTypeMap.entrySet()) {
345                     if (entry.getValue().value > 0) {
346                         writer.startElement("documentType")
347                                 .attribute(COUNT, entry.getValue().toString())
348                                 .attribute(NAME, entry.getKey().name()).closeElement();
349                     }
350                 }
351                 writer.closeElement()
352                         .startElement("counterMap");
353                 for (Map.Entry<ClaimStatistic.Counter, IntCounter> entry : counterMap.entrySet()) {
354                     if (entry.getValue().value > 0) {
355                         writer.startElement("counter")
356                                 .attribute(COUNT, entry.getValue().toString())
357                                 .attribute(NAME, entry.getKey().name()).closeElement();
358                     }
359                 }
360                 writer.closeElement();
361             }
362         }
363 
364         /**
365          * Deserializes a ClaimStatistic from an input stream.
366          * @param inputStreamSupplier the supplier of the input stream to deserialize from.
367          * @throws IOException on error.
368          */
369         public void deserialize(final IOSupplier<InputStream> inputStreamSupplier) throws IOException {
370             DocumentBuilder builder = StandardXmlFactory.documentBuilder();
371             org.w3c.dom.Document document;
372 
373             try (InputStream stream = inputStreamSupplier.get()) {
374                 document = builder.parse(stream);
375             } catch (SAXException e) {
376                 throw new IOException("Unable to read input", e);
377             }
378 
379             XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("licenseName"), node -> {
380                 Map<String, String> attributes = XMLConfigurationReader.attributes(node);
381                 incLicenseNameCount(attributes.get(NAME), Integer.parseInt(attributes.get(COUNT)));
382             });
383 
384             XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("familyCategory"), node -> {
385                 Map<String, String> attributes = XMLConfigurationReader.attributes(node);
386                 incLicenseCategoryCount(attributes.get(NAME), Integer.parseInt(attributes.get(COUNT)));
387             });
388 
389             XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("documentType"), node -> {
390                 Map<String, String> attributes = XMLConfigurationReader.attributes(node);
391                 Document.Type type = Document.Type.valueOf(attributes.get(NAME));
392                 incCounter(type, Integer.parseInt(attributes.get(COUNT)));
393             });
394 
395             XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("counter"), node -> {
396                 Map<String, String> attributes = XMLConfigurationReader.attributes(node);
397                 Counter type = Counter.valueOf(attributes.get(NAME));
398                 setCounter(type, Integer.parseInt(attributes.get(COUNT)));
399             });
400         }
401     }
402 }