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