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.rat.analysis.matchers;
20  
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Map;
24  import java.util.Objects;
25  import java.util.Set;
26  import java.util.regex.Matcher;
27  import java.util.regex.Pattern;
28  
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.rat.ConfigurationException;
31  import org.apache.rat.analysis.IHeaders;
32  import org.apache.rat.config.parameters.ComponentType;
33  import org.apache.rat.config.parameters.ConfigComponent;
34  
35  /**
36   * Defines a factory to produce matchers for an SPDX tag. SPDX tag is of the
37   * format {@code SPDX-License-Identifier: short-name} where {@code short-name}
38   * matches the regex pattern [A-Za-z0-9\.\-]+
39   * <p>
40   * SPDX identifiers are specified by the Software Package Data Exchange(R) also
41   * known as SPDX(R) project from the Linux foundation.
42   * </p>
43   *
44   * @see <a href="https://spdx.dev/ids/">List of Ids at spdx.dev</a>
45   */
46  public class SPDXMatcherFactory {
47  
48      /**
49       * The collection of all matchers produced by this factory.
50       */
51      private static final Map<String, SPDXMatcherFactory.Match> matchers = new HashMap<>();
52  
53      /**
54       * The instance of this factory.
55       */
56      public static final SPDXMatcherFactory INSTANCE = new SPDXMatcherFactory();
57  
58      static final String LICENSE_IDENTIFIER = "SPDX-License-Identifier:";
59  
60      /**
61       * The regular expression to locate the SPDX license identifier in the text
62       * stream
63       */
64      private static Pattern groupSelector = Pattern.compile(".*"+LICENSE_IDENTIFIER+"\\s([A-Za-z0-9\\.\\-]+)");
65  
66      /**
67       * The last matcer to match the line.
68       */
69      private Set<String> lastMatch;
70  
71      private boolean checked;
72  
73      private SPDXMatcherFactory() {
74          lastMatch = new HashSet<>();
75      }
76  
77      private void reset() {
78          lastMatch.clear();
79          checked = false;
80      }
81  
82      /**
83       * Creates the SPDX matcher.
84       *
85       * @param spdxId the SPDX name to match.
86       * @return a SPDX matcher.
87       */
88      public Match create(String spdxId) {
89          if (StringUtils.isBlank(spdxId)) {
90              throw new ConfigurationException("'SPDX' type matcher requires a name");
91          }
92          Match matcher = matchers.get(spdxId);
93          if (matcher == null) {
94              matcher = new Match(spdxId);
95              matchers.put(spdxId, matcher);
96          }
97          return matcher;
98      }
99  
100     /**
101      * Each matcher calls this method to present the line it is working on.
102      *
103      * @param line The line the caller is looking at.
104      * @param caller the Match that is calling this method.
105      * @return true if the caller matches the text.
106      */
107     private boolean check(String line, Match caller) {
108         /*
109         If the line has not been seen yet see if we can extract the SPDX id from the line.
110         If so then see for each match extract and add the name to lastMatch
111         */
112         if (!checked) {
113             checked = true;
114             if (line.contains(LICENSE_IDENTIFIER)) {
115                 Matcher matcher = groupSelector.matcher(line);
116                 while (matcher.find()) {
117                     lastMatch.add(matcher.group(1));
118                 }
119             }
120         }
121         // see if the caller is in the lastMatch.
122         return lastMatch.contains(caller.spdxId);
123     }
124 
125     @ConfigComponent(type = ComponentType.MATCHER, name = "spdx", desc = "Matches SPDX enclosed license identifier.")
126     public class Match extends AbstractHeaderMatcher {
127         @ConfigComponent(type = ComponentType.PARAMETER, name = "name", desc = "The SPDX identifier string")
128         String spdxId;
129 
130         public String getName() {
131             return spdxId;
132         }
133 
134         /**
135          * Constructor.
136          *
137          * @param spdxId A regular expression that matches the @{short-name} of the SPDX
138          * Identifier.
139          */
140         Match(final String spdxId) {
141             super("SPDX:" + spdxId);
142             Objects.requireNonNull(spdxId, "SpdxId is required");
143             this.spdxId = spdxId;
144         }
145 
146         @Override
147         public boolean matches(IHeaders headers) {
148             return SPDXMatcherFactory.this.check(headers.raw(), this);
149         }
150 
151         @Override
152         public void reset() {
153             super.reset();
154             SPDXMatcherFactory.this.reset();
155         }
156     }
157 }