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  package org.apache.creadur.whisker.app.analysis;
20  
21  import java.util.Collection;
22  import java.util.HashMap;
23  import java.util.Map;
24  import java.util.TreeSet;
25  
26  
27  import org.apache.commons.lang3.tuple.Pair;
28  import org.apache.creadur.whisker.model.Resource;
29  import org.apache.creadur.whisker.model.ResourceNamesCollator;
30  import org.apache.creadur.whisker.model.Descriptor;
31  import org.apache.creadur.whisker.scan.Directory;
32  
33  /**
34   * Analyses licenses.
35   */
36  public class LicenseAnalyst {
37  
38      /**
39       * Maps issues.
40       * @return not null
41       */
42      private static Map<ResourceDefinitionError,
43                          Collection<ResourceDescription>> buildIssueMap() {
44          final Map<ResourceDefinitionError,
45                      Collection<ResourceDescription>> results =
46                      new HashMap<ResourceDefinitionError,
47                          Collection<ResourceDescription>>();
48          for (final ResourceDefinitionError error:
49                  ResourceDefinitionError.values()) {
50              initIssues(results, error);
51          }
52          return results;
53      }
54  
55      /**
56       * Builds an initial map.
57       * @param results not null
58       * @param error not null
59       */
60      private static void initIssues(
61              final Map<
62                  ResourceDefinitionError,
63                  Collection<ResourceDescription>> results,
64              ResourceDefinitionError error) {
65          results.put(error, new TreeSet<ResourceDescription>());
66      }
67  
68      /** Directories analysed. */
69      private final Collection<Directory> directories;
70      /** Maps resource errors to resources. */
71      private final Map<ResourceDefinitionError,
72                      Collection<ResourceDescription>> issues;
73  
74      /**
75       * Constructs empty analyst.
76       */
77      public LicenseAnalyst() {
78          this(null);
79      }
80  
81      /**
82       * Analyse the given directories.
83       * @param directories not null
84       */
85      public LicenseAnalyst(final Collection<Directory> directories) {
86          super();
87          this.directories = directories;
88          issues = buildIssueMap();
89      }
90  
91      /**
92       * Discover discrepancies between meta-data and source directories.
93       * @param work not null
94       * @return this, not null
95       */
96      public LicenseAnalyst analyse(final Descriptor work) {
97          if (directories == null) {
98              final ResourceNamesCollator collator =
99                      new ResourceNamesCollator();
100             work.traverse(collator);
101             analyseDuplicates(collator);
102 
103             final ResourceSourceAuditor sourceAuditor =
104                     new ResourceSourceAuditor();
105             work.traverse(sourceAuditor);
106             analyse(sourceAuditor);
107         } else {
108             for (final Directory directory: directories) {
109                 final ResourceNamesCollator collator =
110                         new ResourceNamesCollator();
111                 work.traverseDirectory(collator, directory.getName());
112                 analyseLicenses(directory, collator);
113                 analyseDuplicates(collator);
114 
115                 final ResourceSourceAuditor sourceAuditor = new
116                         ResourceSourceAuditor();
117                 work.traverseDirectory(sourceAuditor, directory.getName());
118                 analyse(sourceAuditor);
119             }
120         }
121         return this;
122     }
123 
124     /**
125      * Analyse the directories with this auditor.
126      * @param sourceAuditor not null
127      */
128     private void analyse(final ResourceSourceAuditor sourceAuditor) {
129         addIssues(sourceAuditor.getResourcesMissingSource(),
130                 ResourceDefinitionError.MISSING_SOURCE);
131     }
132 
133     /**
134      * Were any errors found?
135      * @return true when the meta-data is valid,
136      * false otherwise
137      */
138     public boolean isValid() {
139         for (final ResourceDefinitionError error:
140                 ResourceDefinitionError.values()) {
141             if (!getIssues(error).isEmpty()) {
142                 return false;
143             }
144         }
145         return true;
146     }
147 
148     /**
149      * Checks the descriptor against the source directories.
150      * @param work not null
151      * @return valid meta-data
152      * @throws ResourceDefinitionException when issues are found
153      */
154     public Descriptor validate(final Descriptor work)
155             throws ResourceDefinitionException {
156         analyse(work);
157         if (isValid()) {
158             return work;
159         } else {
160             throw new ResourceDefinitionException(issues);
161         }
162 
163     }
164 
165     /**
166      * Discovers duplicates.
167      * @param collator not null
168      */
169     private void analyseDuplicates(
170             final ResourceNamesCollator collator) {
171         addIssues(collator.getDuplicates(),
172                 ResourceDefinitionError.DUPLICATE);
173     }
174 
175     /**
176      * Adds the issues to the store.
177      * @param resources not null
178      * @param error not null
179      */
180     private void addIssues(
181             final Collection<Pair<
182                 org.apache.creadur.whisker.model.WithinDirectory,
183                 Resource>> resources,
184             final ResourceDefinitionError error) {
185         for (Pair<
186                 org.apache.creadur.whisker.model.WithinDirectory,
187                 Resource> duplicate: resources) {
188             getIssues(error).add(
189                     new ResourceDescription(
190                             duplicate.getLeft().getName(),
191                             duplicate.getRight().getName()));
192         }
193     }
194 
195     /**
196      * Checks for too many or few licenses.
197      * @param directory not null
198      * @param collator not null
199      */
200     private void analyseLicenses(final Directory directory,
201             final ResourceNamesCollator collator) {
202         analyseExtraLicenses(directory, collator);
203         analyseMissingLicenses(directory, collator);
204     }
205 
206 
207     /**
208      * Checks for to many licenses.
209      * @param directory not null
210      * @param collator not null
211      */
212     private void analyseExtraLicenses(final Directory directory,
213             final ResourceNamesCollator collator) {
214         final Collection<String> actualResources = directory.getContents();
215         for (final String resourceLicense: collator.getNames()) {
216             if (!actualResources.contains(resourceLicense)) {
217                 getExtraLicenses().add(
218                         new ResourceDescription(
219                                 directory.getName(),
220                                 resourceLicense));
221             }
222         }
223     }
224 
225     /**
226      * Checks for too few licenses.
227      * @param directory not null
228      * @param collator not null
229      */
230     private void analyseMissingLicenses(final Directory directory,
231             final ResourceNamesCollator collator) {
232         final Collection<String> licensedResources = collator.getNames();
233         for (final String actualResource: directory.getContents()) {
234             if (!licensedResources.contains(actualResource)) {
235                 getMissingLicenses().add(
236                         new ResourceDescription(
237                                 directory.getName(),
238                                 actualResource));
239             }
240         }
241     }
242 
243     /**
244      * Gets resources whose sources are missing.
245      * @return not null, possibly empty
246      */
247     public Collection<ResourceDescription> getResourcesMissingSources() {
248         return getIssues(ResourceDefinitionError.MISSING_SOURCE);
249     }
250 
251     /**
252      * Gets surplus licenses.
253      * @return not null, possibly empty
254      */
255     public Collection<ResourceDescription> getExtraLicenses() {
256         return getIssues(ResourceDefinitionError.EXTRA_LICENSE);
257     }
258 
259     /**
260      * Gets missing license.
261      * @return not null, possibly empty
262      */
263     public Collection<ResourceDescription> getMissingLicenses() {
264         return getIssues(ResourceDefinitionError.MISSING_LICENSE);
265     }
266 
267     /**
268      * Gets duplicate resources.
269      * @return the duplicates
270      */
271     public Collection<ResourceDescription> getDuplicates() {
272         return getIssues(ResourceDefinitionError.DUPLICATE);
273     }
274 
275     /**
276      * Gets issues by type.
277      * @param ofType not null
278      * @return issues of given type, not null, possibly empty
279      */
280     public Collection<ResourceDescription> getIssues(
281             ResourceDefinitionError ofType) {
282         return issues.get(ofType);
283     }
284 
285     /**
286      * Describes suitably for logging.
287      * @return something suitable for logging
288      * @see java.lang.Object#toString()
289      */
290     @Override
291     public String toString() {
292         return "LicenseAnalyst [directories=" + directories + "]";
293     }
294 }