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