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