1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.rat.annotation;
20
21 import java.io.BufferedReader;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileWriter;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.io.Writer;
28 import java.nio.charset.StandardCharsets;
29 import java.nio.file.Files;
30 import java.nio.file.InvalidPathException;
31 import java.nio.file.Path;
32 import java.nio.file.StandardCopyOption;
33 import java.util.Arrays;
34 import java.util.HashMap;
35 import java.util.Map;
36
37 import org.apache.commons.io.IOUtils;
38 import org.apache.commons.io.input.BOMInputStream;
39 import org.apache.rat.utils.DefaultLog;
40
41
42
43
44
45
46
47 public abstract class AbstractLicenseAppender {
48
49 private static final String DOT = ".";
50
51 private static final int TYPE_UNKNOWN = 0;
52
53 private static final int TYPE_JAVA = 1;
54
55 private static final int TYPE_XML = 2;
56
57 private static final int TYPE_HTML = 3;
58
59 private static final int TYPE_CSS = 4;
60
61 private static final int TYPE_JAVASCRIPT = 5;
62
63 private static final int TYPE_APT = 6;
64
65 private static final int TYPE_PROPERTIES = 7;
66
67 private static final int TYPE_PYTHON = 8;
68
69 private static final int TYPE_C = 9;
70
71 private static final int TYPE_H = 10;
72
73 private static final int TYPE_SH = 11;
74
75 private static final int TYPE_BAT = 12;
76
77 private static final int TYPE_VM = 13;
78
79 private static final int TYPE_SCALA = 14;
80
81 private static final int TYPE_RUBY = 15;
82
83 private static final int TYPE_PERL = 16;
84
85 private static final int TYPE_TCL = 17;
86
87 private static final int TYPE_CPP = 18;
88
89 private static final int TYPE_CSHARP = 19;
90
91 private static final int TYPE_PHP = 20;
92
93 private static final int TYPE_GROOVY = 21;
94
95 private static final int TYPE_VISUAL_STUDIO_SOLUTION = 22;
96
97 private static final int TYPE_BEANSHELL = 23;
98
99 private static final int TYPE_JSP = 24;
100
101 private static final int TYPE_FML = 25;
102
103 private static final int TYPE_GO = 26;
104
105 private static final int TYPE_PM = 27;
106
107 private static final int TYPE_MD = 28;
108
109 private static final int TYPE_YAML = 29;
110
111
112
113
114 private static final String LINE_SEP = System.lineSeparator();
115
116
117
118 private static final int[] FAMILY_C = new int[]{
119 TYPE_JAVA, TYPE_JAVASCRIPT, TYPE_C, TYPE_H, TYPE_SCALA,
120 TYPE_CSS, TYPE_CPP, TYPE_CSHARP, TYPE_PHP, TYPE_GROOVY,
121 TYPE_BEANSHELL, TYPE_GO,
122 };
123
124
125
126 private static final int[] FAMILY_SGML = new int[]{
127 TYPE_XML, TYPE_HTML, TYPE_JSP, TYPE_FML, TYPE_MD,
128 };
129
130
131
132 private static final int[] FAMILY_SH = new int[]{
133 TYPE_PROPERTIES, TYPE_PYTHON, TYPE_SH, TYPE_RUBY, TYPE_PERL,
134 TYPE_TCL, TYPE_VISUAL_STUDIO_SOLUTION, TYPE_PM, TYPE_YAML,
135 };
136
137
138
139 private static final int[] FAMILY_BAT = new int[] {
140 TYPE_BAT,
141 };
142
143
144
145 private static final int[] FAMILY_APT = new int[] {
146 TYPE_APT,
147 };
148
149
150
151 private static final int[] FAMILY_VELOCITY = new int[] {
152 TYPE_VM,
153 };
154
155
156
157 private static final int[] EXPECTS_HASH_PLING = new int[] {
158 TYPE_PYTHON, TYPE_SH, TYPE_RUBY, TYPE_PERL, TYPE_TCL,
159 };
160
161
162
163 private static final int[] EXPECTS_AT_ECHO = new int[]{
164 TYPE_BAT,
165 };
166
167
168
169 private static final int[] EXPECTS_PACKAGE = new int[]{
170 TYPE_JAVA, TYPE_GO, TYPE_PM,
171 };
172
173
174
175 private static final int[] EXPECTS_XML_DECL = new int[]{
176 TYPE_XML,
177 };
178
179
180
181 private static final int[] EXPECTS_PHP_PI = new int[] {
182 TYPE_PHP,
183 };
184
185
186
187 private static final int[] EXPECTS_MSVSSF_HEADER = new int[] {
188 TYPE_VISUAL_STUDIO_SOLUTION,
189 };
190
191
192
193
194 private static final Map<String, Integer> EXT2TYPE = new HashMap<>();
195
196 static {
197
198
199 Arrays.sort(FAMILY_C);
200 Arrays.sort(FAMILY_SGML);
201 Arrays.sort(FAMILY_SH);
202 Arrays.sort(FAMILY_BAT);
203 Arrays.sort(FAMILY_APT);
204 Arrays.sort(FAMILY_VELOCITY);
205
206 Arrays.sort(EXPECTS_HASH_PLING);
207 Arrays.sort(EXPECTS_AT_ECHO);
208 Arrays.sort(EXPECTS_PACKAGE);
209 Arrays.sort(EXPECTS_XML_DECL);
210 Arrays.sort(EXPECTS_MSVSSF_HEADER);
211
212 EXT2TYPE.put("apt", TYPE_APT);
213 EXT2TYPE.put("asax", TYPE_HTML);
214 EXT2TYPE.put("ascx", TYPE_HTML);
215 EXT2TYPE.put("aspx", TYPE_HTML);
216 EXT2TYPE.put("bat", TYPE_BAT);
217 EXT2TYPE.put("bsh", TYPE_BEANSHELL);
218 EXT2TYPE.put("c", TYPE_C);
219 EXT2TYPE.put("cc", TYPE_CPP);
220 EXT2TYPE.put("cmd", TYPE_BAT);
221 EXT2TYPE.put("config", TYPE_XML);
222 EXT2TYPE.put("cpp", TYPE_CPP);
223 EXT2TYPE.put("cs", TYPE_CSHARP);
224 EXT2TYPE.put("csdproj", TYPE_XML);
225 EXT2TYPE.put("csproj", TYPE_XML);
226 EXT2TYPE.put("css", TYPE_CSS);
227 EXT2TYPE.put("fxcop", TYPE_XML);
228 EXT2TYPE.put("fml", TYPE_FML);
229 EXT2TYPE.put("groovy", TYPE_GROOVY);
230 EXT2TYPE.put("go", TYPE_GO);
231 EXT2TYPE.put("h", TYPE_H);
232 EXT2TYPE.put("hh", TYPE_H);
233 EXT2TYPE.put("hpp", TYPE_H);
234 EXT2TYPE.put("htm", TYPE_HTML);
235 EXT2TYPE.put("html", TYPE_HTML);
236 EXT2TYPE.put("java", TYPE_JAVA);
237 EXT2TYPE.put("js", TYPE_JAVASCRIPT);
238 EXT2TYPE.put("jsp", TYPE_JSP);
239 EXT2TYPE.put("md", TYPE_MD);
240 EXT2TYPE.put("ndoc", TYPE_XML);
241 EXT2TYPE.put("nunit", TYPE_XML);
242 EXT2TYPE.put("php", TYPE_PHP);
243 EXT2TYPE.put("pl", TYPE_PERL);
244 EXT2TYPE.put("pm", TYPE_PM);
245 EXT2TYPE.put("properties", TYPE_PROPERTIES);
246 EXT2TYPE.put("py", TYPE_PYTHON);
247 EXT2TYPE.put("rb", TYPE_RUBY);
248 EXT2TYPE.put("rdf", TYPE_XML);
249 EXT2TYPE.put("resx", TYPE_XML);
250 EXT2TYPE.put("scala", TYPE_SCALA);
251 EXT2TYPE.put("sh", TYPE_SH);
252 EXT2TYPE.put("shfbproj", TYPE_XML);
253 EXT2TYPE.put("sln", TYPE_VISUAL_STUDIO_SOLUTION);
254 EXT2TYPE.put("stylecop", TYPE_XML);
255 EXT2TYPE.put("svg", TYPE_XML);
256 EXT2TYPE.put("tcl", TYPE_TCL);
257 EXT2TYPE.put("vbdproj", TYPE_XML);
258 EXT2TYPE.put("vbproj", TYPE_XML);
259 EXT2TYPE.put("vcproj", TYPE_XML);
260 EXT2TYPE.put("vm", TYPE_VM);
261 EXT2TYPE.put("vsdisco", TYPE_XML);
262 EXT2TYPE.put("webinfo", TYPE_XML);
263 EXT2TYPE.put("xml", TYPE_XML);
264 EXT2TYPE.put("xproj", TYPE_XML);
265 EXT2TYPE.put("xsl", TYPE_XML);
266 EXT2TYPE.put("yaml", TYPE_YAML);
267 EXT2TYPE.put("yml", TYPE_YAML);
268 }
269
270
271
272
273 private boolean isOverwrite;
274
275
276
277
278 public AbstractLicenseAppender() {
279 super();
280 }
281
282
283
284
285
286
287
288 public void append(final File document) throws IOException {
289 int type = getType(document);
290 if (type == TYPE_UNKNOWN) {
291 return;
292 }
293
294 boolean expectsHashPling = expectsHashPling(type);
295 boolean expectsAtEcho = expectsAtEcho(type);
296 boolean expectsPackage = expectsPackage(type);
297 boolean expectsXMLDecl = expectsXMLDecl(type);
298 boolean expectsPhpPI = expectsPhpPI(type);
299 boolean expectsMSVSSF = expectsMSVisualStudioSolutionFileHeader(type);
300
301 File newDocument = new File(document.getAbsolutePath() + ".new");
302 try (FileWriter writer = new FileWriter(newDocument)) {
303 if (!attachLicense(writer, document,
304 expectsHashPling, expectsAtEcho, expectsPackage,
305 expectsXMLDecl, expectsPhpPI, expectsMSVSSF)) {
306
307
308
309
310 if (expectsPackage || expectsXMLDecl) {
311 try (FileWriter writer2 = new FileWriter(newDocument)) {
312 if (expectsXMLDecl) {
313 writer2.write("<?xml version='1.0'?>");
314 writer2.write(LINE_SEP);
315 }
316 attachLicense(writer2, document,
317 false, false, false, false, false, false);
318 }
319 }
320 }
321 }
322
323 if (isOverwrite) {
324 try {
325 Path docPath = document.toPath();
326 boolean isExecutable = Files.isExecutable(docPath);
327 Files.move(newDocument.toPath(), docPath, StandardCopyOption.REPLACE_EXISTING);
328 if (isExecutable && !document.setExecutable(true)) {
329 DefaultLog.getInstance().warn(String.format("Could not set %s as executable.", document));
330 }
331 } catch (InvalidPathException | IOException e) {
332 DefaultLog.getInstance().error(String.format("Failed to rename new file to %s, Original file is unchanged.", document), e);
333 }
334 }
335 }
336
337
338
339
340
341
342
343 private boolean attachLicense(final Writer writer, final File document,
344 final boolean expectsHashPling,
345 final boolean expectsAtEcho,
346 final boolean expectsPackage,
347 final boolean expectsXMLDecl,
348 final boolean expectsPhpPI,
349 final boolean expectsMSVSSF)
350 throws IOException {
351 boolean written = false;
352 FileInputStream fis = null;
353 BufferedReader br = null;
354 try {
355 fis = new FileInputStream(document);
356 BOMInputStream bos = BOMInputStream.builder().setInputStream(fis).get();
357 br = new BufferedReader(new InputStreamReader(bos, StandardCharsets.UTF_8));
358
359 if (!expectsHashPling
360 && !expectsAtEcho
361 && !expectsPackage
362 && !expectsXMLDecl
363 && !expectsPhpPI
364 && !expectsMSVSSF) {
365 written = true;
366 writer.write(getLicenseHeader(document));
367 writer.write(LINE_SEP);
368 }
369
370 String line;
371 boolean first = true;
372 while ((line = br.readLine()) != null) {
373 if (first && expectsHashPling) {
374 written = true;
375 doFirstLine(document, writer, line, "#!");
376 } else if (first && expectsAtEcho) {
377 written = true;
378 doFirstLine(document, writer, line, "@echo");
379 } else if (first && expectsMSVSSF) {
380 written = true;
381 if (line.isEmpty()) {
382 line = passThroughReadNext(writer, line, br);
383 }
384 if (line.startsWith("Microsoft Visual Studio Solution"
385 + " File")) {
386 line = passThroughReadNext(writer, line, br);
387 }
388 doFirstLine(document, writer, line, "# Visual ");
389 } else {
390 writer.write(line);
391 writer.write(LINE_SEP);
392 }
393
394 if (expectsPackage && line.startsWith("package ")) {
395 written = true;
396 writer.write(LINE_SEP);
397 writer.write(getLicenseHeader(document));
398 writer.write(LINE_SEP);
399 } else if (expectsXMLDecl && line.startsWith("<?xml ")) {
400 written = true;
401 writer.write(LINE_SEP);
402 writer.write(getLicenseHeader(document));
403 writer.write(LINE_SEP);
404 } else if (expectsPhpPI && line.startsWith("<?php")) {
405 written = true;
406 writer.write(LINE_SEP);
407 writer.write(getLicenseHeader(document));
408 writer.write(LINE_SEP);
409 }
410 first = false;
411 }
412 } finally {
413 IOUtils.closeQuietly(br);
414 IOUtils.closeQuietly(fis);
415 IOUtils.closeQuietly(writer);
416 }
417 return written;
418 }
419
420
421
422
423 private void doFirstLine(final File document, final Writer writer, final String line, final String lookfor) throws IOException {
424 if (line.startsWith(lookfor)) {
425 writer.write(line);
426 writer.write(LINE_SEP);
427 writer.write(getLicenseHeader(document));
428 } else {
429 writer.write(getLicenseHeader(document));
430 writer.write(line);
431 writer.write(LINE_SEP);
432 }
433 }
434
435
436
437
438
439
440
441
442 protected int getType(final File document) {
443 String path = document.getPath();
444 int lastDot = path.lastIndexOf(DOT);
445 if (lastDot >= 0 && lastDot < path.length() - 1) {
446 String ext = path.substring(lastDot + 1);
447 Integer type = EXT2TYPE.get(ext);
448 if (type != null) {
449 return type;
450 }
451 }
452 return TYPE_UNKNOWN;
453 }
454
455
456
457
458
459
460
461
462 public void setOverwrite(final boolean overwrite) {
463 isOverwrite = overwrite;
464 }
465
466
467
468
469
470
471 public abstract String getLicenseHeader(File document);
472
473
474
475
476
477
478
479
480 protected String getFirstLine(final int type) {
481 if (isFamilyC(type)) {
482 return "/*" + LINE_SEP;
483 } else if (isFamilySGML(type)) {
484 return "<!--" + LINE_SEP;
485 }
486 return "";
487 }
488
489
490
491
492
493
494
495
496
497 protected String getLastLine(final int type) {
498 if (isFamilyC(type)) {
499 return " */" + LINE_SEP;
500 } else if (isFamilySGML(type)) {
501 return "-->" + LINE_SEP;
502 }
503 return "";
504 }
505
506
507
508
509
510
511
512
513
514
515 protected String getLine(final int type, final String content) {
516 if (isFamilyC(type)) {
517 return " * " + content + LINE_SEP;
518 } else if (isFamilySGML(type)) {
519 return content + LINE_SEP;
520 } else if (isFamilyAPT(type)) {
521 return "~~ " + content + LINE_SEP;
522 } else if (isFamilySH(type)) {
523 return "# " + content + LINE_SEP;
524 } else if (isFamilyBAT(type)) {
525 return "rem " + content + LINE_SEP;
526 } else if (isFamilyVelocity(type)) {
527 return "## " + content + LINE_SEP;
528 }
529 return "";
530 }
531
532 private static boolean isFamilyC(final int type) {
533 return isIn(FAMILY_C, type);
534 }
535
536 private static boolean isFamilySGML(final int type) {
537 return isIn(FAMILY_SGML, type);
538 }
539
540 private static boolean isFamilySH(final int type) {
541 return isIn(FAMILY_SH, type);
542 }
543
544 private static boolean isFamilyAPT(final int type) {
545 return isIn(FAMILY_APT, type);
546 }
547
548 private static boolean isFamilyBAT(final int type) {
549 return isIn(FAMILY_BAT, type);
550 }
551
552 private static boolean isFamilyVelocity(final int type) {
553 return isIn(FAMILY_VELOCITY, type);
554 }
555
556 private static boolean expectsHashPling(final int type) {
557 return isIn(EXPECTS_HASH_PLING, type);
558 }
559
560 private static boolean expectsAtEcho(final int type) {
561 return isIn(EXPECTS_AT_ECHO, type);
562 }
563
564 private static boolean expectsPackage(final int type) {
565 return isIn(EXPECTS_PACKAGE, type);
566 }
567
568 private static boolean expectsXMLDecl(final int type) {
569 return isIn(EXPECTS_XML_DECL, type);
570 }
571
572 private static boolean expectsPhpPI(final int type) {
573 return isIn(EXPECTS_PHP_PI, type);
574 }
575
576 private static boolean expectsMSVisualStudioSolutionFileHeader(final int type) {
577 return isIn(EXPECTS_MSVSSF_HEADER, type);
578 }
579
580 private static boolean isIn(final int[] arr, final int key) {
581 return Arrays.binarySearch(arr, key) >= 0;
582 }
583
584 private String passThroughReadNext(final Writer writer, final String line,
585 final BufferedReader br) throws IOException {
586 writer.write(line);
587 writer.write(LINE_SEP);
588 String l = br.readLine();
589 return l == null ? "" : l;
590 }
591 }