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