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