View Javadoc
1   package org.apache.rat.analysis;
2   /*
3    * Licensed to the Apache Software Foundation (ASF) under one   *
4    * or more contributor license agreements.  See the NOTICE file *
5    * distributed with this work for additional information        *
6    * regarding copyright ownership.  The ASF licenses this file   *
7    * to you under the Apache License, Version 2.0 (the            *
8    * "License"); you may not use this file except in compliance   *
9    * with the License.  You may obtain a copy of the License at   *
10   *                                                              *
11   *   http://www.apache.org/licenses/LICENSE-2.0                 *
12   *                                                              *
13   * Unless required by applicable law or agreed to in writing,   *
14   * software distributed under the License is distributed on an  *
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
16   * KIND, either express or implied.  See the License for the    *
17   * specific language governing permissions and limitations      *
18   * under the License.                                           *
19   */
20  
21  import java.io.BufferedReader;
22  import java.io.IOException;
23  import java.io.Reader;
24  import java.util.Locale;
25  import java.util.Objects;
26  
27  import org.apache.commons.collections4.set.UnmodifiableSortedSet;
28  import org.apache.rat.ConfigurationException;
29  import org.apache.rat.analysis.matchers.FullTextMatcher;
30  import org.apache.rat.api.Document;
31  import org.apache.rat.license.ILicense;
32  
33  /**
34   * Reads from a stream to check for a license.
35   * <p>
36   * <strong>Note</strong> that this class is not thread safe.
37   * </p>
38   */
39  public final class HeaderCheckWorker {
40  
41      /*
42       * TODO revisit this class. It is only used in one place and can be moved inline
43       * as the DocumentHeaderAnalyser states. However, it may also be possible to
44       * make the entire set thread safe so that multiple files can be checked
45       * simultaneously.
46       */
47      /**
48       * The default number of header lines to read while looking for the license
49       * information.
50       */
51      public static final int DEFAULT_NUMBER_OF_RETAINED_HEADER_LINES = 50;
52      /** The number of header lines to retain for processing */
53      private final int numberOfRetainedHeaderLines;
54      /** The BufferedReader used to read the lines */
55      private final BufferedReader reader;
56      /** The licenses to check for match */
57      private final UnmodifiableSortedSet<ILicense> licenses;
58      /** The document being processed */
59      private final Document document;
60      /**  The matcher for generated headers */
61      private final IHeaderMatcher generatedMatcher;
62  
63      /**
64       * Read the input and perform the header check.
65       * <p>
66       * The number of lines indicates how many lines from the top of the file will be read for processing.
67       *
68       * @param reader the reader for the document.
69       * @param numberOfLines the number of lines to read from the header.
70       * @return the IHeaders instance for the header.
71       * @throws IOException on input failure
72       */
73      public static IHeaders readHeader(final BufferedReader reader, final int numberOfLines) throws IOException {
74          final StringBuilder headers = new StringBuilder();
75          int headerLinesRead = 0;
76          String line;
77  
78          while (headerLinesRead < numberOfLines && (line = reader.readLine()) != null) {
79              headers.append(line).append(System.lineSeparator());
80          }
81          final String raw = headers.toString();
82          final String pruned = FullTextMatcher.prune(raw).toLowerCase(Locale.ENGLISH);
83          return new IHeaders() {
84              @Override
85              public String raw() {
86                  return raw;
87              }
88  
89              @Override
90              public String pruned() {
91                  return pruned;
92              }
93  
94              @Override
95              public String toString() {
96                  return this.getClass().getSimpleName();
97              }
98          };
99      }
100 
101     /**
102      * Convenience constructor wraps given <code>Reader</code> in a
103      * <code>BufferedReader</code>.
104      *
105      * @param generatedMatcher the matcher for generated headers.
106      * @param reader the reader on the document. Not null.
107      * @param licenses the licenses to check against. Not null.
108      * @param name the document that is being checked. Possibly null.
109      */
110     public HeaderCheckWorker(final IHeaderMatcher generatedMatcher, final Reader reader, final UnmodifiableSortedSet<ILicense> licenses, final Document name) {
111         this(generatedMatcher, reader, DEFAULT_NUMBER_OF_RETAINED_HEADER_LINES, licenses, name);
112     }
113 
114     /**
115      * Constructs a check worker for the license against the specified document.
116      *
117      * @param generatedMatcher The matcher for generated headers.
118      * @param reader The reader on the document. Not null.
119      * @param numberOfRetainedHeaderLine the maximum number of lines to read to find
120      * the license information.
121      * @param licenses The licenses to check against. Not null.
122      * @param document The document that is being checked. Possibly null.
123      */
124     public HeaderCheckWorker(final IHeaderMatcher generatedMatcher, final Reader reader,
125                              final int numberOfRetainedHeaderLine, final UnmodifiableSortedSet<ILicense> licenses,
126                              final Document document) {
127         Objects.requireNonNull(reader, "Reader may not be null");
128         Objects.requireNonNull(licenses, "Licenses may not be null");
129         if (numberOfRetainedHeaderLine < 0) {
130             throw new ConfigurationException("numberOfRetainedHeaderLine may not be less than zero");
131         }
132         this.reader = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);
133         this.numberOfRetainedHeaderLines = numberOfRetainedHeaderLine;
134         this.licenses = licenses;
135         this.document = document;
136         this.generatedMatcher = generatedMatcher;
137     }
138 
139     /**
140      * Read the input and perform the header check.
141      *
142      * @throws RatHeaderAnalysisException in case of I/O exceptions.
143      */
144     public void read() throws RatHeaderAnalysisException {
145         try {
146             final IHeaders headers = readHeader(reader, numberOfRetainedHeaderLines);
147             if (generatedMatcher.matches(headers)) {
148                 document.getMetaData().setDocumentType(Document.Type.IGNORED);
149             } else {
150                 licenses.stream().filter(lic -> lic.matches(headers)).forEach(document.getMetaData()::reportOnLicense);
151                 if (!document.getMetaData().detectedLicense()) {
152                     document.getMetaData().reportOnLicense(UnknownLicense.INSTANCE);
153                 }
154             }
155         } catch (IOException e) {
156             throw new RatHeaderAnalysisException("Cannot read header for " + document, e);
157         } finally {
158             licenses.forEach(ILicense::reset);
159         }
160     }
161 }