rat/apache-rat-core/xref/org/apache/rat/annotation/AbstractLicenseAppender.html [1:636]: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
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 * Add a license header to a document. This appender does not check for the
38 * existence of an existing license header, it is assumed that either a second
39 * license header is intentional or that there is no license header present
40 * already.
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 private static final int TYPE_GO = 26;
71 private static final int TYPE_PM = 27;
72
73 /**
74 * the line separator for this OS
75 */
76 private static final String LINE_SEP = System.getProperty("line.separator");
77
78 private static final int[] FAMILY_C = new int[]{
79 TYPE_JAVA, TYPE_JAVASCRIPT, TYPE_C, TYPE_H, TYPE_SCALA,
80 TYPE_CSS, TYPE_CPP, TYPE_CSHARP, TYPE_PHP, TYPE_GROOVY,
81 TYPE_BEANSHELL, TYPE_GO,
82 };
83 private static final int[] FAMILY_SGML = new int[]{
84 TYPE_XML, TYPE_HTML, TYPE_JSP, TYPE_FML,
85 };
86 private static final int[] FAMILY_SH = new int[]{
87 TYPE_PROPERTIES, TYPE_PYTHON, TYPE_SH, TYPE_RUBY, TYPE_PERL,
88 TYPE_TCL, TYPE_VISUAL_STUDIO_SOLUTION, TYPE_PM,
89 };
90 private static final int[] FAMILY_BAT = new int[]{
91 TYPE_BAT,
92 };
93 private static final int[] FAMILY_APT = new int[]{
94 TYPE_APT,
95 };
96 private static final int[] FAMILY_VELOCITY = new int[]{
97 TYPE_VM,
98 };
99 private static final int[] EXPECTS_HASH_PLING = new int[]{
100 TYPE_PYTHON, TYPE_SH, TYPE_RUBY, TYPE_PERL, TYPE_TCL,
101 };
102 private static final int[] EXPECTS_AT_ECHO = new int[]{
103 TYPE_BAT,
104 };
105 private static final int[] EXPECTS_PACKAGE = new int[]{
106 TYPE_JAVA, TYPE_GO, TYPE_PM,
107 };
108 private static final int[] EXPECTS_XML_DECL = new int[]{
109 TYPE_XML,
110 };
111 private static final int[] EXPECTS_PHP_PI = new int[]{
112 TYPE_PHP,
113 };
114 private static final int[] EXPECTS_MSVSSF_HEADER = new int[]{
115 TYPE_VISUAL_STUDIO_SOLUTION,
116 };
117
118 private static final Map<String, Integer> EXT2TYPE = new HashMap<>();
119
120 static {
121 // these arrays are used in Arrays.binarySearch so they must
122 // be sorted
123 Arrays.sort(FAMILY_C);
124 Arrays.sort(FAMILY_SGML);
125 Arrays.sort(FAMILY_SH);
126 Arrays.sort(FAMILY_BAT);
127 Arrays.sort(FAMILY_APT);
128 Arrays.sort(FAMILY_VELOCITY);
129
130 Arrays.sort(EXPECTS_HASH_PLING);
131 Arrays.sort(EXPECTS_AT_ECHO);
132 Arrays.sort(EXPECTS_PACKAGE);
133 Arrays.sort(EXPECTS_XML_DECL);
134 Arrays.sort(EXPECTS_MSVSSF_HEADER);
135
136 EXT2TYPE.put("apt", TYPE_APT);
137 EXT2TYPE.put("asax", TYPE_HTML);
138 EXT2TYPE.put("ascx", TYPE_HTML);
139 EXT2TYPE.put("aspx", TYPE_HTML);
140 EXT2TYPE.put("bat", TYPE_BAT);
141 EXT2TYPE.put("bsh", TYPE_BEANSHELL);
142 EXT2TYPE.put("c", TYPE_C);
143 EXT2TYPE.put("cc", TYPE_CPP);
144 EXT2TYPE.put("cmd", TYPE_BAT);
145 EXT2TYPE.put("config", TYPE_XML);
146 EXT2TYPE.put("cpp", TYPE_CPP);
147 EXT2TYPE.put("cs", TYPE_CSHARP);
148 EXT2TYPE.put("csdproj", TYPE_XML);
149 EXT2TYPE.put("csproj", TYPE_XML);
150 EXT2TYPE.put("css", TYPE_CSS);
151 EXT2TYPE.put("fxcop", TYPE_XML);
152 EXT2TYPE.put("fml", TYPE_FML);
153 EXT2TYPE.put("groovy", TYPE_GROOVY);
154 EXT2TYPE.put("go", TYPE_GO);
155 EXT2TYPE.put("h", TYPE_H);
156 EXT2TYPE.put("hh", TYPE_H);
157 EXT2TYPE.put("hpp", TYPE_H);
158 EXT2TYPE.put("htm", TYPE_HTML);
159 EXT2TYPE.put("html", TYPE_HTML);
160 EXT2TYPE.put("java", TYPE_JAVA);
161 EXT2TYPE.put("js", TYPE_JAVASCRIPT);
162 EXT2TYPE.put("jsp", TYPE_JSP);
163 EXT2TYPE.put("ndoc", TYPE_XML);
164 EXT2TYPE.put("nunit", TYPE_XML);
165 EXT2TYPE.put("php", TYPE_PHP);
166 EXT2TYPE.put("pl", TYPE_PERL);
167 EXT2TYPE.put("pm", TYPE_PM);
168 EXT2TYPE.put("properties", TYPE_PROPERTIES);
169 EXT2TYPE.put("py", TYPE_PYTHON);
170 EXT2TYPE.put("rb", TYPE_RUBY);
171 EXT2TYPE.put("rdf", TYPE_XML);
172 EXT2TYPE.put("resx", TYPE_XML);
173 EXT2TYPE.put("scala", TYPE_SCALA);
174 EXT2TYPE.put("sh", TYPE_SH);
175 EXT2TYPE.put("shfbproj", TYPE_XML);
176 EXT2TYPE.put("sln", TYPE_VISUAL_STUDIO_SOLUTION);
177 EXT2TYPE.put("stylecop", TYPE_XML);
178 EXT2TYPE.put("svg", TYPE_XML);
179 EXT2TYPE.put("tcl", TYPE_TCL);
180 EXT2TYPE.put("vbdproj", TYPE_XML);
181 EXT2TYPE.put("vbproj", TYPE_XML);
182 EXT2TYPE.put("vcproj", TYPE_XML);
183 EXT2TYPE.put("vm", TYPE_VM);
184 EXT2TYPE.put("vsdisco", TYPE_XML);
185 EXT2TYPE.put("webinfo", TYPE_XML);
186 EXT2TYPE.put("xml", TYPE_XML);
187 EXT2TYPE.put("xproj", TYPE_XML);
188 EXT2TYPE.put("xsl", TYPE_XML);
189 }
190
191 private boolean isForced;
192
193 public AbstractLicenseAppender() {
194 super();
195 }
196
197 /**
198 * Append the default license header to the supplied document.
199 *
200 * @param document document to append to.
201 * @throws IOException if there is a problem while reading or writing the file
202 */
203 public void append(File document) throws IOException {
204 int type = getType(document);
205 if (type == TYPE_UNKNOWN) {
206 return;
207 }
208
209 boolean expectsHashPling = expectsHashPling(type);
210 boolean expectsAtEcho = expectsAtEcho(type);
211 boolean expectsPackage = expectsPackage(type);
212 boolean expectsXMLDecl = expectsXMLDecl(type);
213 boolean expectsPhpPI = expectsPhpPI(type);
214 boolean expectsMSVSSF = expectsMSVisualStudioSolutionFileHeader(type);
215
216 File newDocument = new File(document.getAbsolutePath() + ".new");
217 FileWriter writer = new FileWriter(newDocument);
218 try {
219 if (!attachLicense(writer, document,
220 expectsHashPling, expectsAtEcho, expectsPackage,
221 expectsXMLDecl, expectsPhpPI, expectsMSVSSF)) {
222 // Java File without package, XML file without decl or PHP
223 // file without PI
224 // for Java just place the license at the front, for XML add
225 // an XML decl first - don't know how to handle PHP
226 if (expectsPackage || expectsXMLDecl) {
227 writer = new FileWriter(newDocument);
228 if (expectsXMLDecl) {
229 writer.write("<?xml version='1.0'?>");
230 writer.write(LINE_SEP);
231 }
232 attachLicense(writer, document,
233 false, false, false, false, false, false);
234 }
235 }
236 } finally {
237 IOUtils.closeQuietly(writer);
238 }
239
240 if (isForced) {
241 boolean deleted = document.delete();
242 if (!deleted) {
243 System.err.println("Could not delete original file to prepare renaming.");
244 }
245 boolean renamed = newDocument.renameTo(document.getAbsoluteFile());
246 if (!renamed) {
247 System.err.println("Failed to rename new file, original file remains unchanged.");
248 }
249 }
250 }
251
252 /**
253 * Write document's content to writer attaching the license using
254 * the given flags as hints for where to put it.
255 *
256 * @return whether the license has actually been written
257 */
258 private boolean attachLicense(Writer writer, File document,
259 boolean expectsHashPling,
260 boolean expectsAtEcho,
261 boolean expectsPackage,
262 boolean expectsXMLDecl,
263 boolean expectsPhpPI,
264 boolean expectsMSVSSF)
265 throws IOException {
266 boolean written = false;
267 FileInputStream fis = null;
268 BufferedReader br = null;
269 try {
270 fis = new FileInputStream(document);
271 br = new BufferedReader(new InputStreamReader(new BOMInputStream(fis)));
272
273 if (!expectsHashPling
274 && !expectsAtEcho
275 && !expectsPackage
276 && !expectsXMLDecl
277 && !expectsPhpPI
278 && !expectsMSVSSF) {
279 written = true;
280 writer.write(getLicenseHeader(document));
281 writer.write(LINE_SEP);
282 }
283
284 String line;
285 boolean first = true;
286 while ((line = br.readLine()) != null) {
287 if (first && expectsHashPling) {
288 written = true;
289 doFirstLine(document, writer, line, "#!");
290 } else if (first && expectsAtEcho) {
291 written = true;
292 doFirstLine(document, writer, line, "@echo");
293 } else if (first && expectsMSVSSF) {
294 written = true;
295 if ("".equals(line)) {
296 line = passThroughReadNext(writer, line, br);
297 }
298 if (line.startsWith("Microsoft Visual Studio Solution"
299 + " File")) {
300 line = passThroughReadNext(writer, line, br);
301 }
302 doFirstLine(document, writer, line, "# Visual ");
303 } else {
304 writer.write(line);
305 writer.write(LINE_SEP);
306 }
307
308 if (expectsPackage && line.startsWith("package ")) {
309 written = true;
310 writer.write(LINE_SEP);
311 writer.write(getLicenseHeader(document));
312 writer.write(LINE_SEP);
313 } else if (expectsXMLDecl && line.startsWith("<?xml ")) {
314 written = true;
315 writer.write(LINE_SEP);
316 writer.write(getLicenseHeader(document));
317 writer.write(LINE_SEP);
318 } else if (expectsPhpPI && line.startsWith("<?php")) {
319 written = true;
320 writer.write(LINE_SEP);
321 writer.write(getLicenseHeader(document));
322 writer.write(LINE_SEP);
323 }
324 first = false;
325 }
326 } finally {
327 IOUtils.closeQuietly(br);
328 IOUtils.closeQuietly(fis);
329 IOUtils.closeQuietly(writer);
330 }
331 return written;
332 }
333
334 /**
335 * Check first line for specified text and process.
336 */
337 private void doFirstLine(File document, Writer writer, String line, String lookfor) throws IOException {
338 if (line.startsWith(lookfor)) {
339 writer.write(line);
340 writer.write(LINE_SEP);
341 writer.write(getLicenseHeader(document));
342 } else {
343 writer.write(getLicenseHeader(document));
344 writer.write(line);
345 writer.write(LINE_SEP);
346 }
347 }
348
349 /**
350 * Detect the type of document.
351 *
352 * @param document to retrieve type from.
353 * @return not null
354 * TODO use existing mechanism to detect the type of a file and record it in the report output, thus we will not need this duplication here.
355 */
356 protected int getType(File document) {
357 String path = document.getPath();
358 int lastDot = path.lastIndexOf(DOT);
359 if (lastDot >= 0 && lastDot < path.length() - 1) {
360 String ext = path.substring(lastDot + 1);
361 Integer type = EXT2TYPE.get(ext);
362 if (type != null) {
363 return type;
364 }
365 }
366 return TYPE_UNKNOWN;
367 }
368
369 /**
370 * Set the force flag on this appender. If this flag is set
371 * to true then files will be modified directly, otherwise
372 * new files will be created alongside the existing files.
373 *
374 * @param force force flag.
375 */
376 public void setForce(boolean force) {
377 isForced = force;
378 }
379
380 /**
381 * @param document document to extract from.
382 * @return Get the license header of a document.
383 */
384 public abstract String getLicenseHeader(File document);
385
386 /**
387 * Get the first line of the license header formatted
388 * for the given type of file.
389 *
390 * @param type the type of file, see the TYPE_* constants
391 * @return not null
392 */
393 protected String getFirstLine(int type) {
394 if (isFamilyC(type)) {
395 return "/*" + LINE_SEP;
396 } else if (isFamilySGML(type)) {
397 return "<!--" + LINE_SEP;
398 }
399 return "";
400 }
401
402
403 /**
404 * Get the last line of the license header formatted
405 * for the given type of file.
406 *
407 * @param type the type of file, see the TYPE_* constants
408 * @return not null
409 */
410 protected String getLastLine(int type) {
411 if (isFamilyC(type)) {
412 return " */" + LINE_SEP;
413 } else if (isFamilySGML(type)) {
414 return "-->" + LINE_SEP;
415 }
416 return "";
417 }
418
419
420 /**
421 * Get a line of the license header formatted
422 * for the given type of file.
423 *
424 * @param type the type of file, see the TYPE_* constants
425 * @param content the content for this line
426 * @return not null
427 */
428 protected String getLine(int type, String content) {
429 if (isFamilyC(type)) {
430 return " * " + content + LINE_SEP;
431 } else if (isFamilySGML(type)) {
432 return content + LINE_SEP;
433 } else if (isFamilyAPT(type)) {
434 return "~~ " + content + LINE_SEP;
435 } else if (isFamilySH(type)) {
436 return "# " + content + LINE_SEP;
437 } else if (isFamilyBAT(type)) {
438 return "rem " + content + LINE_SEP;
439 } else if (isFamilyVelocity(type)) {
440 return "## " + content + LINE_SEP;
441 }
442 return "";
443 }
444
445 private static boolean isFamilyC(int type) {
446 return isIn(FAMILY_C, type);
447 }
448
449 private static boolean isFamilySGML(int type) {
450 return isIn(FAMILY_SGML, type);
451 }
452
453 private static boolean isFamilySH(int type) {
454 return isIn(FAMILY_SH, type);
455 }
456
457 private static boolean isFamilyAPT(int type) {
458 return isIn(FAMILY_APT, type);
459 }
460
461 private static boolean isFamilyBAT(int type) {
462 return isIn(FAMILY_BAT, type);
463 }
464
465 private static boolean isFamilyVelocity(int type) {
466 return isIn(FAMILY_VELOCITY, type);
467 }
468
469 private static boolean expectsHashPling(int type) {
470 return isIn(EXPECTS_HASH_PLING, type);
471 }
472
473 private static boolean expectsAtEcho(int type) {
474 return isIn(EXPECTS_AT_ECHO, type);
475 }
476
477 private static boolean expectsPackage(int type) {
478 return isIn(EXPECTS_PACKAGE, type);
479 }
480
481 private static boolean expectsXMLDecl(int type) {
482 return isIn(EXPECTS_XML_DECL, type);
483 }
484
485 private static boolean expectsPhpPI(int type) {
486 return isIn(EXPECTS_PHP_PI, type);
487 }
488
489 private static boolean expectsMSVisualStudioSolutionFileHeader(int type) {
490 return isIn(EXPECTS_MSVSSF_HEADER, type);
491 }
492
493 private static boolean isIn(int[] arr, int key) {
494 return Arrays.binarySearch(arr, key) >= 0;
495 }
496
497 private String passThroughReadNext(Writer writer, String line,
498 BufferedReader br) throws IOException {
499 writer.write(line);
500 writer.write(LINE_SEP);
501 String l = br.readLine();
502 return l == null ? "" : l;
503 }
504 }
505
506 /**
507 * Stripped down version of Commons IO 2.0's BOMInputStream.
508 */
509 class BOMInputStream extends FilterInputStream {
510 private int[] firstBytes;
511 private int fbLength, fbIndex, markFbIndex;
512 private boolean markedAtStart;
513 private static final int[][] BOMS = {
514 new int[]{0xEF, 0xBB, 0xBF}, // UTF-8
515 new int[]{0xFE, 0xFF}, // UTF-16BE
516 new int[]{0xFF, 0xFE}, // UTF-16LE
517 };
518
519 BOMInputStream(InputStream s) {
520 super(s);
521 }
522
523 @Override
524 public int read() throws IOException {
525 int b = readFirstBytes();
526 return (b >= 0) ? b : in.read();
527 }
528
529 @Override
530 public int read(byte[] buf, int off, int len) throws IOException {
531 int firstCount = 0;
532 int b = 0;
533 while ((len > 0) && (b >= 0)) {
534 b = readFirstBytes();
535 if (b >= 0) {
536 buf[off++] = (byte) (b & 0xFF);
537 len--;
538 firstCount++;
539 }
540 }
541 int secondCount = in.read(buf, off, len);
542 return (secondCount < 0)
543 ? (firstCount > 0 ? firstCount : -1) : firstCount + secondCount;
544 }
545
546 @Override
547 public int read(byte[] buf) throws IOException {
548 return read(buf, 0, buf.length);
549 }
550
551 private int readFirstBytes() throws IOException {
552 getBOM();
553 return (fbIndex < fbLength) ? firstBytes[fbIndex++] : -1;
554 }
555
556 private void getBOM() throws IOException {
557 if (firstBytes == null) {
558 int max = 0;
559 for (int[] BOM : BOMS) {
560 max = Math.max(max, BOM.length);
561 }
562 firstBytes = new int[max];
563 for (int i = 0; i < firstBytes.length; i++) {
564 firstBytes[i] = in.read();
565 fbLength++;
566 if (firstBytes[i] < 0) {
567 break;
568 }
569
570 boolean found = find();
571 if (found) {
572 fbLength = 0;
573 break;
574 }
575 }
576 }
577 }
578
579 @Override
580 public synchronized void mark(int readlimit) {
581 markFbIndex = fbIndex;
582 markedAtStart = (firstBytes == null);
583 in.mark(readlimit);
584 }
585
586 @Override
587 public synchronized void reset() throws IOException {
588 fbIndex = markFbIndex;
589 if (markedAtStart) {
590 firstBytes = null;
591 }
592
593 in.reset();
594 }
595
596 @Override
597 public long skip(long n) throws IOException {
598 while ((n > 0) && (readFirstBytes() >= 0)) {
599 n--;
600 }
601 return in.skip(n);
602 }
603
604 private boolean find() {
605 for (int[] BOM : BOMS) {
606 if (matches(BOM)) {
607 return true;
608 }
609 }
610 return false;
611 }
612
613 private boolean matches(int[] bom) {
614 if (bom.length != fbLength) {
615 return false;
616 }
617 for (int i = 0; i < bom.length; i++) {
618 if (bom[i] != firstBytes[i]) {
619 return false;
620 }
621 }
622 return true;
623 }
624
625 }
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
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 * Add a license header to a document. This appender does not check for the
38 * existence of an existing license header, it is assumed that either a second
39 * license header is intentional or that there is no license header present
40 * already.
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 private static final int TYPE_GO = 26;
71 private static final int TYPE_PM = 27;
72
73 /**
74 * the line separator for this OS
75 */
76 private static final String LINE_SEP = System.getProperty("line.separator");
77
78 private static final int[] FAMILY_C = new int[]{
79 TYPE_JAVA, TYPE_JAVASCRIPT, TYPE_C, TYPE_H, TYPE_SCALA,
80 TYPE_CSS, TYPE_CPP, TYPE_CSHARP, TYPE_PHP, TYPE_GROOVY,
81 TYPE_BEANSHELL, TYPE_GO,
82 };
83 private static final int[] FAMILY_SGML = new int[]{
84 TYPE_XML, TYPE_HTML, TYPE_JSP, TYPE_FML,
85 };
86 private static final int[] FAMILY_SH = new int[]{
87 TYPE_PROPERTIES, TYPE_PYTHON, TYPE_SH, TYPE_RUBY, TYPE_PERL,
88 TYPE_TCL, TYPE_VISUAL_STUDIO_SOLUTION, TYPE_PM,
89 };
90 private static final int[] FAMILY_BAT = new int[]{
91 TYPE_BAT,
92 };
93 private static final int[] FAMILY_APT = new int[]{
94 TYPE_APT,
95 };
96 private static final int[] FAMILY_VELOCITY = new int[]{
97 TYPE_VM,
98 };
99 private static final int[] EXPECTS_HASH_PLING = new int[]{
100 TYPE_PYTHON, TYPE_SH, TYPE_RUBY, TYPE_PERL, TYPE_TCL,
101 };
102 private static final int[] EXPECTS_AT_ECHO = new int[]{
103 TYPE_BAT,
104 };
105 private static final int[] EXPECTS_PACKAGE = new int[]{
106 TYPE_JAVA, TYPE_GO, TYPE_PM,
107 };
108 private static final int[] EXPECTS_XML_DECL = new int[]{
109 TYPE_XML,
110 };
111 private static final int[] EXPECTS_PHP_PI = new int[]{
112 TYPE_PHP,
113 };
114 private static final int[] EXPECTS_MSVSSF_HEADER = new int[]{
115 TYPE_VISUAL_STUDIO_SOLUTION,
116 };
117
118 private static final Map<String, Integer> EXT2TYPE = new HashMap<>();
119
120 static {
121 // these arrays are used in Arrays.binarySearch so they must
122 // be sorted
123 Arrays.sort(FAMILY_C);
124 Arrays.sort(FAMILY_SGML);
125 Arrays.sort(FAMILY_SH);
126 Arrays.sort(FAMILY_BAT);
127 Arrays.sort(FAMILY_APT);
128 Arrays.sort(FAMILY_VELOCITY);
129
130 Arrays.sort(EXPECTS_HASH_PLING);
131 Arrays.sort(EXPECTS_AT_ECHO);
132 Arrays.sort(EXPECTS_PACKAGE);
133 Arrays.sort(EXPECTS_XML_DECL);
134 Arrays.sort(EXPECTS_MSVSSF_HEADER);
135
136 EXT2TYPE.put("apt", TYPE_APT);
137 EXT2TYPE.put("asax", TYPE_HTML);
138 EXT2TYPE.put("ascx", TYPE_HTML);
139 EXT2TYPE.put("aspx", TYPE_HTML);
140 EXT2TYPE.put("bat", TYPE_BAT);
141 EXT2TYPE.put("bsh", TYPE_BEANSHELL);
142 EXT2TYPE.put("c", TYPE_C);
143 EXT2TYPE.put("cc", TYPE_CPP);
144 EXT2TYPE.put("cmd", TYPE_BAT);
145 EXT2TYPE.put("config", TYPE_XML);
146 EXT2TYPE.put("cpp", TYPE_CPP);
147 EXT2TYPE.put("cs", TYPE_CSHARP);
148 EXT2TYPE.put("csdproj", TYPE_XML);
149 EXT2TYPE.put("csproj", TYPE_XML);
150 EXT2TYPE.put("css", TYPE_CSS);
151 EXT2TYPE.put("fxcop", TYPE_XML);
152 EXT2TYPE.put("fml", TYPE_FML);
153 EXT2TYPE.put("groovy", TYPE_GROOVY);
154 EXT2TYPE.put("go", TYPE_GO);
155 EXT2TYPE.put("h", TYPE_H);
156 EXT2TYPE.put("hh", TYPE_H);
157 EXT2TYPE.put("hpp", TYPE_H);
158 EXT2TYPE.put("htm", TYPE_HTML);
159 EXT2TYPE.put("html", TYPE_HTML);
160 EXT2TYPE.put("java", TYPE_JAVA);
161 EXT2TYPE.put("js", TYPE_JAVASCRIPT);
162 EXT2TYPE.put("jsp", TYPE_JSP);
163 EXT2TYPE.put("ndoc", TYPE_XML);
164 EXT2TYPE.put("nunit", TYPE_XML);
165 EXT2TYPE.put("php", TYPE_PHP);
166 EXT2TYPE.put("pl", TYPE_PERL);
167 EXT2TYPE.put("pm", TYPE_PM);
168 EXT2TYPE.put("properties", TYPE_PROPERTIES);
169 EXT2TYPE.put("py", TYPE_PYTHON);
170 EXT2TYPE.put("rb", TYPE_RUBY);
171 EXT2TYPE.put("rdf", TYPE_XML);
172 EXT2TYPE.put("resx", TYPE_XML);
173 EXT2TYPE.put("scala", TYPE_SCALA);
174 EXT2TYPE.put("sh", TYPE_SH);
175 EXT2TYPE.put("shfbproj", TYPE_XML);
176 EXT2TYPE.put("sln", TYPE_VISUAL_STUDIO_SOLUTION);
177 EXT2TYPE.put("stylecop", TYPE_XML);
178 EXT2TYPE.put("svg", TYPE_XML);
179 EXT2TYPE.put("tcl", TYPE_TCL);
180 EXT2TYPE.put("vbdproj", TYPE_XML);
181 EXT2TYPE.put("vbproj", TYPE_XML);
182 EXT2TYPE.put("vcproj", TYPE_XML);
183 EXT2TYPE.put("vm", TYPE_VM);
184 EXT2TYPE.put("vsdisco", TYPE_XML);
185 EXT2TYPE.put("webinfo", TYPE_XML);
186 EXT2TYPE.put("xml", TYPE_XML);
187 EXT2TYPE.put("xproj", TYPE_XML);
188 EXT2TYPE.put("xsl", TYPE_XML);
189 }
190
191 private boolean isForced;
192
193 public AbstractLicenseAppender() {
194 super();
195 }
196
197 /**
198 * Append the default license header to the supplied document.
199 *
200 * @param document document to append to.
201 * @throws IOException if there is a problem while reading or writing the file
202 */
203 public void append(File document) throws IOException {
204 int type = getType(document);
205 if (type == TYPE_UNKNOWN) {
206 return;
207 }
208
209 boolean expectsHashPling = expectsHashPling(type);
210 boolean expectsAtEcho = expectsAtEcho(type);
211 boolean expectsPackage = expectsPackage(type);
212 boolean expectsXMLDecl = expectsXMLDecl(type);
213 boolean expectsPhpPI = expectsPhpPI(type);
214 boolean expectsMSVSSF = expectsMSVisualStudioSolutionFileHeader(type);
215
216 File newDocument = new File(document.getAbsolutePath() + ".new");
217 FileWriter writer = new FileWriter(newDocument);
218 try {
219 if (!attachLicense(writer, document,
220 expectsHashPling, expectsAtEcho, expectsPackage,
221 expectsXMLDecl, expectsPhpPI, expectsMSVSSF)) {
222 // Java File without package, XML file without decl or PHP
223 // file without PI
224 // for Java just place the license at the front, for XML add
225 // an XML decl first - don't know how to handle PHP
226 if (expectsPackage || expectsXMLDecl) {
227 writer = new FileWriter(newDocument);
228 if (expectsXMLDecl) {
229 writer.write("<?xml version='1.0'?>");
230 writer.write(LINE_SEP);
231 }
232 attachLicense(writer, document,
233 false, false, false, false, false, false);
234 }
235 }
236 } finally {
237 IOUtils.closeQuietly(writer);
238 }
239
240 if (isForced) {
241 boolean deleted = document.delete();
242 if (!deleted) {
243 System.err.println("Could not delete original file to prepare renaming.");
244 }
245 boolean renamed = newDocument.renameTo(document.getAbsoluteFile());
246 if (!renamed) {
247 System.err.println("Failed to rename new file, original file remains unchanged.");
248 }
249 }
250 }
251
252 /**
253 * Write document's content to writer attaching the license using
254 * the given flags as hints for where to put it.
255 *
256 * @return whether the license has actually been written
257 */
258 private boolean attachLicense(Writer writer, File document,
259 boolean expectsHashPling,
260 boolean expectsAtEcho,
261 boolean expectsPackage,
262 boolean expectsXMLDecl,
263 boolean expectsPhpPI,
264 boolean expectsMSVSSF)
265 throws IOException {
266 boolean written = false;
267 FileInputStream fis = null;
268 BufferedReader br = null;
269 try {
270 fis = new FileInputStream(document);
271 br = new BufferedReader(new InputStreamReader(new BOMInputStream(fis)));
272
273 if (!expectsHashPling
274 && !expectsAtEcho
275 && !expectsPackage
276 && !expectsXMLDecl
277 && !expectsPhpPI
278 && !expectsMSVSSF) {
279 written = true;
280 writer.write(getLicenseHeader(document));
281 writer.write(LINE_SEP);
282 }
283
284 String line;
285 boolean first = true;
286 while ((line = br.readLine()) != null) {
287 if (first && expectsHashPling) {
288 written = true;
289 doFirstLine(document, writer, line, "#!");
290 } else if (first && expectsAtEcho) {
291 written = true;
292 doFirstLine(document, writer, line, "@echo");
293 } else if (first && expectsMSVSSF) {
294 written = true;
295 if ("".equals(line)) {
296 line = passThroughReadNext(writer, line, br);
297 }
298 if (line.startsWith("Microsoft Visual Studio Solution"
299 + " File")) {
300 line = passThroughReadNext(writer, line, br);
301 }
302 doFirstLine(document, writer, line, "# Visual ");
303 } else {
304 writer.write(line);
305 writer.write(LINE_SEP);
306 }
307
308 if (expectsPackage && line.startsWith("package ")) {
309 written = true;
310 writer.write(LINE_SEP);
311 writer.write(getLicenseHeader(document));
312 writer.write(LINE_SEP);
313 } else if (expectsXMLDecl && line.startsWith("<?xml ")) {
314 written = true;
315 writer.write(LINE_SEP);
316 writer.write(getLicenseHeader(document));
317 writer.write(LINE_SEP);
318 } else if (expectsPhpPI && line.startsWith("<?php")) {
319 written = true;
320 writer.write(LINE_SEP);
321 writer.write(getLicenseHeader(document));
322 writer.write(LINE_SEP);
323 }
324 first = false;
325 }
326 } finally {
327 IOUtils.closeQuietly(br);
328 IOUtils.closeQuietly(fis);
329 IOUtils.closeQuietly(writer);
330 }
331 return written;
332 }
333
334 /**
335 * Check first line for specified text and process.
336 */
337 private void doFirstLine(File document, Writer writer, String line, String lookfor) throws IOException {
338 if (line.startsWith(lookfor)) {
339 writer.write(line);
340 writer.write(LINE_SEP);
341 writer.write(getLicenseHeader(document));
342 } else {
343 writer.write(getLicenseHeader(document));
344 writer.write(line);
345 writer.write(LINE_SEP);
346 }
347 }
348
349 /**
350 * Detect the type of document.
351 *
352 * @param document to retrieve type from.
353 * @return not null
354 * TODO use existing mechanism to detect the type of a file and record it in the report output, thus we will not need this duplication here.
355 */
356 protected int getType(File document) {
357 String path = document.getPath();
358 int lastDot = path.lastIndexOf(DOT);
359 if (lastDot >= 0 && lastDot < path.length() - 1) {
360 String ext = path.substring(lastDot + 1);
361 Integer type = EXT2TYPE.get(ext);
362 if (type != null) {
363 return type;
364 }
365 }
366 return TYPE_UNKNOWN;
367 }
368
369 /**
370 * Set the force flag on this appender. If this flag is set
371 * to true then files will be modified directly, otherwise
372 * new files will be created alongside the existing files.
373 *
374 * @param force force flag.
375 */
376 public void setForce(boolean force) {
377 isForced = force;
378 }
379
380 /**
381 * @param document document to extract from.
382 * @return Get the license header of a document.
383 */
384 public abstract String getLicenseHeader(File document);
385
386 /**
387 * Get the first line of the license header formatted
388 * for the given type of file.
389 *
390 * @param type the type of file, see the TYPE_* constants
391 * @return not null
392 */
393 protected String getFirstLine(int type) {
394 if (isFamilyC(type)) {
395 return "/*" + LINE_SEP;
396 } else if (isFamilySGML(type)) {
397 return "<!--" + LINE_SEP;
398 }
399 return "";
400 }
401
402
403 /**
404 * Get the last line of the license header formatted
405 * for the given type of file.
406 *
407 * @param type the type of file, see the TYPE_* constants
408 * @return not null
409 */
410 protected String getLastLine(int type) {
411 if (isFamilyC(type)) {
412 return " */" + LINE_SEP;
413 } else if (isFamilySGML(type)) {
414 return "-->" + LINE_SEP;
415 }
416 return "";
417 }
418
419
420 /**
421 * Get a line of the license header formatted
422 * for the given type of file.
423 *
424 * @param type the type of file, see the TYPE_* constants
425 * @param content the content for this line
426 * @return not null
427 */
428 protected String getLine(int type, String content) {
429 if (isFamilyC(type)) {
430 return " * " + content + LINE_SEP;
431 } else if (isFamilySGML(type)) {
432 return content + LINE_SEP;
433 } else if (isFamilyAPT(type)) {
434 return "~~ " + content + LINE_SEP;
435 } else if (isFamilySH(type)) {
436 return "# " + content + LINE_SEP;
437 } else if (isFamilyBAT(type)) {
438 return "rem " + content + LINE_SEP;
439 } else if (isFamilyVelocity(type)) {
440 return "## " + content + LINE_SEP;
441 }
442 return "";
443 }
444
445 private static boolean isFamilyC(int type) {
446 return isIn(FAMILY_C, type);
447 }
448
449 private static boolean isFamilySGML(int type) {
450 return isIn(FAMILY_SGML, type);
451 }
452
453 private static boolean isFamilySH(int type) {
454 return isIn(FAMILY_SH, type);
455 }
456
457 private static boolean isFamilyAPT(int type) {
458 return isIn(FAMILY_APT, type);
459 }
460
461 private static boolean isFamilyBAT(int type) {
462 return isIn(FAMILY_BAT, type);
463 }
464
465 private static boolean isFamilyVelocity(int type) {
466 return isIn(FAMILY_VELOCITY, type);
467 }
468
469 private static boolean expectsHashPling(int type) {
470 return isIn(EXPECTS_HASH_PLING, type);
471 }
472
473 private static boolean expectsAtEcho(int type) {
474 return isIn(EXPECTS_AT_ECHO, type);
475 }
476
477 private static boolean expectsPackage(int type) {
478 return isIn(EXPECTS_PACKAGE, type);
479 }
480
481 private static boolean expectsXMLDecl(int type) {
482 return isIn(EXPECTS_XML_DECL, type);
483 }
484
485 private static boolean expectsPhpPI(int type) {
486 return isIn(EXPECTS_PHP_PI, type);
487 }
488
489 private static boolean expectsMSVisualStudioSolutionFileHeader(int type) {
490 return isIn(EXPECTS_MSVSSF_HEADER, type);
491 }
492
493 private static boolean isIn(int[] arr, int key) {
494 return Arrays.binarySearch(arr, key) >= 0;
495 }
496
497 private String passThroughReadNext(Writer writer, String line,
498 BufferedReader br) throws IOException {
499 writer.write(line);
500 writer.write(LINE_SEP);
501 String l = br.readLine();
502 return l == null ? "" : l;
503 }
504 }
505
506 /**
507 * Stripped down version of Commons IO 2.0's BOMInputStream.
508 */
509 class BOMInputStream extends FilterInputStream {
510 private int[] firstBytes;
511 private int fbLength, fbIndex, markFbIndex;
512 private boolean markedAtStart;
513 private static final int[][] BOMS = {
514 new int[]{0xEF, 0xBB, 0xBF}, // UTF-8
515 new int[]{0xFE, 0xFF}, // UTF-16BE
516 new int[]{0xFF, 0xFE}, // UTF-16LE
517 };
518
519 BOMInputStream(InputStream s) {
520 super(s);
521 }
522
523 @Override
524 public int read() throws IOException {
525 int b = readFirstBytes();
526 return (b >= 0) ? b : in.read();
527 }
528
529 @Override
530 public int read(byte[] buf, int off, int len) throws IOException {
531 int firstCount = 0;
532 int b = 0;
533 while ((len > 0) && (b >= 0)) {
534 b = readFirstBytes();
535 if (b >= 0) {
536 buf[off++] = (byte) (b & 0xFF);
537 len--;
538 firstCount++;
539 }
540 }
541 int secondCount = in.read(buf, off, len);
542 return (secondCount < 0)
543 ? (firstCount > 0 ? firstCount : -1) : firstCount + secondCount;
544 }
545
546 @Override
547 public int read(byte[] buf) throws IOException {
548 return read(buf, 0, buf.length);
549 }
550
551 private int readFirstBytes() throws IOException {
552 getBOM();
553 return (fbIndex < fbLength) ? firstBytes[fbIndex++] : -1;
554 }
555
556 private void getBOM() throws IOException {
557 if (firstBytes == null) {
558 int max = 0;
559 for (int[] BOM : BOMS) {
560 max = Math.max(max, BOM.length);
561 }
562 firstBytes = new int[max];
563 for (int i = 0; i < firstBytes.length; i++) {
564 firstBytes[i] = in.read();
565 fbLength++;
566 if (firstBytes[i] < 0) {
567 break;
568 }
569
570 boolean found = find();
571 if (found) {
572 fbLength = 0;
573 break;
574 }
575 }
576 }
577 }
578
579 @Override
580 public synchronized void mark(int readlimit) {
581 markFbIndex = fbIndex;
582 markedAtStart = (firstBytes == null);
583 in.mark(readlimit);
584 }
585
586 @Override
587 public synchronized void reset() throws IOException {
588 fbIndex = markFbIndex;
589 if (markedAtStart) {
590 firstBytes = null;
591 }
592
593 in.reset();
594 }
595
596 @Override
597 public long skip(long n) throws IOException {
598 while ((n > 0) && (readFirstBytes() >= 0)) {
599 n--;
600 }
601 return in.skip(n);
602 }
603
604 private boolean find() {
605 for (int[] BOM : BOMS) {
606 if (matches(BOM)) {
607 return true;
608 }
609 }
610 return false;
611 }
612
613 private boolean matches(int[] bom) {
614 if (bom.length != fbLength) {
615 return false;
616 }
617 for (int i = 0; i < bom.length; i++) {
618 if (bom[i] != firstBytes[i]) {
619 return false;
620 }
621 }
622 return true;
623 }
624
625 }