1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.rat.analysis.matchers;
20
21 import java.util.Arrays;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24 import java.util.stream.Collectors;
25
26 import org.apache.commons.lang3.StringUtils;
27 import org.apache.rat.ConfigurationException;
28 import org.apache.rat.analysis.IHeaders;
29 import org.apache.rat.config.parameters.ComponentType;
30 import org.apache.rat.config.parameters.ConfigComponent;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 @ConfigComponent(type = ComponentType.MATCHER, name = "copyright",
58 desc = "A matcher that matches Copyright text. " +
59 "Uses regular expressions and so should only be used when looking for copyrights with specific " +
60 "patterns that are not caught by a standard text matcher. This matcher will match \"(C)\", \"copyright\", " +
61 "or \"©\". (text is not case sensitive). It will also match things like Copyright (c) joe 1995 as well " +
62 "as Copyright (C) 1995 joe and Copyright (C) joe 1995.")
63 public final class CopyrightMatcher extends AbstractHeaderMatcher {
64
65 private static final String COPYRIGHT_SYMBOL_DEFN = "\\([Cc]\\)|©|\\&[Cc][Oo][Pp][Yy]\\;";
66
67 private static final String COPYRIGHT_PATTERN_DEFN = "(\\b)?" + COPYRIGHT_SYMBOL_DEFN + "|Copyright\\b";
68
69 private static final Pattern COPYRIGHT_PATTERN = Pattern.compile(COPYRIGHT_PATTERN_DEFN);
70
71 private static final String ONE_PART = "\\W+((" + COPYRIGHT_SYMBOL_DEFN + ")\\W+)?%s";
72
73 private static final String TWO_PART = "\\W+((" + COPYRIGHT_SYMBOL_DEFN + ")\\W+)?%s,?\\W+%s";
74
75 private static final String DOUBLE_DATE_FMT = "%s\\W*-\\W*%s";
76
77
78
79
80
81
82
83 private static final int MAX_COPYRIGHT_MATCH_LENGTH = 1000;
84
85 private static final String ARBITRARY_DATE = "[0-9]{4}";
86
87 private final Pattern dateOwnerPattern;
88
89 private final Pattern ownerDatePattern;
90
91 @ConfigComponent(type = ComponentType.PARAMETER, desc = "The initial year of the copyright if any")
92 private final String start;
93
94 @ConfigComponent(type = ComponentType.PARAMETER, desc = "The last year the copyright. Only valid with 'start'")
95 private final String end;
96
97 @ConfigComponent(type = ComponentType.PARAMETER, desc = "The owner of the copyright")
98 private final String owner;
99
100
101
102
103
104
105
106
107
108
109 public CopyrightMatcher(final String start, final String end, final String owner) {
110 this(null, start, end, owner);
111 }
112
113 private static void assertNumber(final String label, final String value) {
114 try {
115 if (StringUtils.isNotEmpty(value)) {
116 Integer.parseInt(value);
117 }
118 } catch (NumberFormatException e) {
119 throw new ConfigurationException(String.format("'%s' must be numeric (value provided: '%s')", label, value));
120 }
121 }
122
123
124
125
126
127
128
129
130 private String parseOwner(final String owner) {
131 return Arrays.stream(owner.split("\\s+")).map(s -> "\\Q" + s + "\\E").collect(Collectors.joining("\\W+"));
132 }
133
134
135
136
137
138
139
140
141
142
143
144 public CopyrightMatcher(final String id, final String start, final String end, final String owner) {
145 super(id);
146 if (StringUtils.isBlank(start) && !StringUtils.isBlank(end)) {
147 throw new ConfigurationException("'end' may not be set if 'start' is not set.");
148 }
149 assertNumber("start", start);
150 assertNumber("end", end);
151 this.start = start;
152 this.end = end;
153 this.owner = owner;
154 String dateDefn = "";
155 if (StringUtils.isNotEmpty(start)) {
156 if (StringUtils.isNotEmpty(end)) {
157 dateDefn = String.format(DOUBLE_DATE_FMT, this.start, this.end);
158 } else {
159 dateDefn = this.start;
160 }
161 }
162 if (StringUtils.isEmpty(owner)) {
163
164 if (StringUtils.isEmpty(dateDefn)) {
165 dateDefn = ARBITRARY_DATE;
166 }
167 dateOwnerPattern = Pattern.compile(String.format(ONE_PART, dateDefn));
168 ownerDatePattern = null;
169 } else {
170 String formattedOwner = parseOwner(owner);
171 if (StringUtils.isEmpty(dateDefn)) {
172 dateDefn = String.format(DOUBLE_DATE_FMT, "(((" + ARBITRARY_DATE, ")?" + ARBITRARY_DATE + "))?");
173 dateOwnerPattern = Pattern.compile(String.format(TWO_PART, dateDefn, formattedOwner));
174 ownerDatePattern = Pattern.compile(String.format(ONE_PART, formattedOwner));
175 } else {
176 dateOwnerPattern = Pattern.compile(String.format(TWO_PART, dateDefn, formattedOwner));
177 ownerDatePattern = Pattern.compile(String.format(TWO_PART, formattedOwner, dateDefn));
178 }
179 }
180 }
181
182
183
184
185
186 public String getStart() {
187 return start;
188 }
189
190
191
192
193
194 public String getEnd() {
195 return end;
196 }
197
198
199
200
201
202 public String getOwner() {
203 return owner;
204 }
205
206 @Override
207 public boolean matches(final IHeaders headers) {
208 String lowerLine = headers.raw().toLowerCase();
209 if (lowerLine.contains("copyright") || lowerLine.contains("(c)") || lowerLine.contains("©") ||
210 lowerLine.contains("©")) {
211 Matcher matcher = COPYRIGHT_PATTERN.matcher(headers.raw());
212 if (matcher.find()) {
213 String buffer = headers.raw().substring(matcher.end());
214
215
216
217
218
219
220
221 if (buffer.length() > MAX_COPYRIGHT_MATCH_LENGTH) {
222 buffer = buffer.substring(0, MAX_COPYRIGHT_MATCH_LENGTH);
223 }
224 matcher = dateOwnerPattern.matcher(buffer);
225 if (matcher.lookingAt()) {
226 return true;
227 }
228 if (ownerDatePattern != null) {
229 matcher = ownerDatePattern.matcher(buffer);
230 return matcher.lookingAt();
231 }
232 }
233 }
234 return false;
235 }
236 }