| /* |
| * Licensed to the Apache Software Foundation (ASF) under one * |
| * or more contributor license agreements. See the NOTICE file * |
| * distributed with this work for additional information * |
| * regarding copyright ownership. The ASF licenses this file * |
| * to you under the Apache License, Version 2.0 (the * |
| * "License"); you may not use this file except in compliance * |
| * with the License. You may obtain a copy of the License at * |
| * * |
| * http://www.apache.org/licenses/LICENSE-2.0 * |
| * * |
| * Unless required by applicable law or agreed to in writing, * |
| * software distributed under the License is distributed on an * |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * |
| * KIND, either express or implied. See the License for the * |
| * specific language governing permissions and limitations * |
| * under the License. * |
| */ |
| package org.apache.rat.annotation; |
| |
| import org.apache.commons.io.IOUtils; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileWriter; |
| import java.io.FilterInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.Writer; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * Add a license header to a document. This appender does not check for the |
| * existence of an existing license header, it is assumed that either a second |
| * license header is intentional or that there is no license header present |
| * already. |
| */ |
| public abstract class AbstractLicenseAppender { |
| private static final String DOT = "."; |
| private static final int TYPE_UNKNOWN = 0; |
| private static final int TYPE_JAVA = 1; |
| private static final int TYPE_XML = 2; |
| private static final int TYPE_HTML = 3; |
| private static final int TYPE_CSS = 4; |
| private static final int TYPE_JAVASCRIPT = 5; |
| private static final int TYPE_APT = 6; |
| private static final int TYPE_PROPERTIES = 7; |
| private static final int TYPE_PYTHON = 8; |
| private static final int TYPE_C = 9; |
| private static final int TYPE_H = 10; |
| private static final int TYPE_SH = 11; |
| private static final int TYPE_BAT = 12; |
| private static final int TYPE_VM = 13; |
| private static final int TYPE_SCALA = 14; |
| private static final int TYPE_RUBY = 15; |
| private static final int TYPE_PERL = 16; |
| private static final int TYPE_TCL = 17; |
| private static final int TYPE_CPP = 18; |
| private static final int TYPE_CSHARP = 19; |
| private static final int TYPE_PHP = 20; |
| private static final int TYPE_GROOVY = 21; |
| private static final int TYPE_VISUAL_STUDIO_SOLUTION = 22; |
| private static final int TYPE_BEANSHELL = 23; |
| private static final int TYPE_JSP = 24; |
| private static final int TYPE_FML = 25; |
| |
| /** |
| * the line separator for this OS |
| */ |
| private static final String LINE_SEP = System.getProperty("line.separator"); |
| |
| private static final int[] FAMILY_C = new int[]{ |
| TYPE_JAVA, TYPE_JAVASCRIPT, TYPE_C, TYPE_H, TYPE_SCALA, |
| TYPE_CSS, TYPE_CPP, TYPE_CSHARP, TYPE_PHP, TYPE_GROOVY, |
| TYPE_BEANSHELL, |
| }; |
| private static final int[] FAMILY_SGML = new int[]{ |
| TYPE_XML, TYPE_HTML, TYPE_JSP, TYPE_FML, |
| }; |
| private static final int[] FAMILY_SH = new int[]{ |
| TYPE_PROPERTIES, TYPE_PYTHON, TYPE_SH, TYPE_RUBY, TYPE_PERL, |
| TYPE_TCL, TYPE_VISUAL_STUDIO_SOLUTION, |
| }; |
| private static final int[] FAMILY_BAT = new int[]{ |
| TYPE_BAT, |
| }; |
| private static final int[] FAMILY_APT = new int[]{ |
| TYPE_APT, |
| }; |
| private static final int[] FAMILY_VELOCITY = new int[]{ |
| TYPE_VM, |
| }; |
| private static final int[] EXPECTS_HASH_PLING = new int[]{ |
| TYPE_PYTHON, TYPE_SH, TYPE_RUBY, TYPE_PERL, TYPE_TCL |
| }; |
| private static final int[] EXPECTS_AT_ECHO = new int[]{ |
| TYPE_BAT, |
| }; |
| private static final int[] EXPECTS_PACKAGE = new int[]{ |
| TYPE_JAVA, |
| }; |
| private static final int[] EXPECTS_XML_DECL = new int[]{ |
| TYPE_XML, |
| }; |
| private static final int[] EXPECTS_PHP_PI = new int[]{ |
| TYPE_PHP, |
| }; |
| private static final int[] EXPECTS_MSVSSF_HEADER = new int[]{ |
| TYPE_VISUAL_STUDIO_SOLUTION, |
| }; |
| |
| private static final Map<String, Integer> EXT2TYPE = new HashMap<String, Integer>(); |
| |
| static { |
| // these arrays are used in Arrays.binarySearch so they must |
| // be sorted |
| Arrays.sort(FAMILY_C); |
| Arrays.sort(FAMILY_SGML); |
| Arrays.sort(FAMILY_SH); |
| Arrays.sort(FAMILY_BAT); |
| Arrays.sort(FAMILY_APT); |
| Arrays.sort(FAMILY_VELOCITY); |
| |
| Arrays.sort(EXPECTS_HASH_PLING); |
| Arrays.sort(EXPECTS_AT_ECHO); |
| Arrays.sort(EXPECTS_PACKAGE); |
| Arrays.sort(EXPECTS_XML_DECL); |
| Arrays.sort(EXPECTS_MSVSSF_HEADER); |
| |
| EXT2TYPE.put("apt", Integer.valueOf(TYPE_APT)); |
| EXT2TYPE.put("asax", Integer.valueOf(TYPE_HTML)); |
| EXT2TYPE.put("ascx", Integer.valueOf(TYPE_HTML)); |
| EXT2TYPE.put("aspx", Integer.valueOf(TYPE_HTML)); |
| EXT2TYPE.put("bat", Integer.valueOf(TYPE_BAT)); |
| EXT2TYPE.put("bsh", Integer.valueOf(TYPE_BEANSHELL)); |
| EXT2TYPE.put("c", Integer.valueOf(TYPE_C)); |
| EXT2TYPE.put("cc", Integer.valueOf(TYPE_CPP)); |
| EXT2TYPE.put("cmd", Integer.valueOf(TYPE_BAT)); |
| EXT2TYPE.put("config", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("cpp", Integer.valueOf(TYPE_CPP)); |
| EXT2TYPE.put("cs", Integer.valueOf(TYPE_CSHARP)); |
| EXT2TYPE.put("csdproj", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("csproj", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("css", Integer.valueOf(TYPE_CSS)); |
| EXT2TYPE.put("fxcop", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("fml", Integer.valueOf(TYPE_FML)); |
| EXT2TYPE.put("groovy", Integer.valueOf(TYPE_GROOVY)); |
| EXT2TYPE.put("h", Integer.valueOf(TYPE_H)); |
| EXT2TYPE.put("hh", Integer.valueOf(TYPE_H)); |
| EXT2TYPE.put("hpp", Integer.valueOf(TYPE_H)); |
| EXT2TYPE.put("htm", Integer.valueOf(TYPE_HTML)); |
| EXT2TYPE.put("html", Integer.valueOf(TYPE_HTML)); |
| EXT2TYPE.put("java", Integer.valueOf(TYPE_JAVA)); |
| EXT2TYPE.put("js", Integer.valueOf(TYPE_JAVASCRIPT)); |
| EXT2TYPE.put("jsp", Integer.valueOf(TYPE_JSP)); |
| EXT2TYPE.put("ndoc", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("nunit", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("php", Integer.valueOf(TYPE_PHP)); |
| EXT2TYPE.put("pl", Integer.valueOf(TYPE_PERL)); |
| EXT2TYPE.put("properties", Integer.valueOf(TYPE_PROPERTIES)); |
| EXT2TYPE.put("py", Integer.valueOf(TYPE_PYTHON)); |
| EXT2TYPE.put("rb", Integer.valueOf(TYPE_RUBY)); |
| EXT2TYPE.put("rdf", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("resx", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("scala", Integer.valueOf(TYPE_SCALA)); |
| EXT2TYPE.put("sh", Integer.valueOf(TYPE_SH)); |
| EXT2TYPE.put("shfbproj", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("sln", Integer.valueOf(TYPE_VISUAL_STUDIO_SOLUTION)); |
| EXT2TYPE.put("stylecop", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("svg", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("tcl", Integer.valueOf(TYPE_TCL)); |
| EXT2TYPE.put("vbdproj", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("vbproj", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("vcproj", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("vm", Integer.valueOf(TYPE_VM)); |
| EXT2TYPE.put("vsdisco", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("webinfo", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("xml", Integer.valueOf(TYPE_XML)); |
| EXT2TYPE.put("xsl", Integer.valueOf(TYPE_XML)); |
| } |
| |
| private boolean isForced; |
| |
| public AbstractLicenseAppender() { |
| super(); |
| } |
| |
| /** |
| * Append the default license header to the supplied document. |
| * |
| * @param document document to append to. |
| * @throws IOException if there is a problem while reading or writing the file |
| */ |
| public void append(File document) throws IOException { |
| int type = getType(document); |
| if (type == TYPE_UNKNOWN) { |
| return; |
| } |
| |
| boolean expectsHashPling = expectsHashPling(type); |
| boolean expectsAtEcho = expectsAtEcho(type); |
| boolean expectsPackage = expectsPackage(type); |
| boolean expectsXMLDecl = expectsXMLDecl(type); |
| boolean expectsPhpPI = expectsPhpPI(type); |
| boolean expectsMSVSSF = expectsMSVisualStudioSolutionFileHeader(type); |
| |
| File newDocument = new File(document.getAbsolutePath() + ".new"); |
| FileWriter writer = new FileWriter(newDocument); |
| try { |
| if (!attachLicense(writer, document, |
| expectsHashPling, expectsAtEcho, expectsPackage, |
| expectsXMLDecl, expectsPhpPI, expectsMSVSSF)) { |
| // Java File without package, XML file without decl or PHP |
| // file without PI |
| // for Java just place the license at the front, for XML add |
| // an XML decl first - don't know how to handle PHP |
| if (expectsPackage || expectsXMLDecl) { |
| writer = new FileWriter(newDocument); |
| if (expectsXMLDecl) { |
| writer.write("<?xml version='1.0'?>"); |
| writer.write(LINE_SEP); |
| } |
| attachLicense(writer, document, |
| false, false, false, false, false, false); |
| } |
| } |
| } finally { |
| IOUtils.closeQuietly(writer); |
| } |
| |
| if (isForced) { |
| boolean deleted = document.delete(); |
| if (!deleted) { |
| System.err.println("Could not delete original file to prepare renaming."); |
| } |
| boolean renamed = newDocument.renameTo(document.getAbsoluteFile()); |
| if (!renamed) { |
| System.err.println("Failed to rename new file, original file remains unchanged."); |
| } |
| } |
| } |
| |
| /** |
| * Write document's content to writer attaching the license using |
| * the given flags as hints for where to put it. |
| * |
| * @return whether the license has actually been written |
| */ |
| private boolean attachLicense(Writer writer, File document, |
| boolean expectsHashPling, |
| boolean expectsAtEcho, |
| boolean expectsPackage, |
| boolean expectsXMLDecl, |
| boolean expectsPhpPI, |
| boolean expectsMSVSSF) |
| throws IOException { |
| boolean written = false; |
| FileInputStream fis = null; |
| BufferedReader br = null; |
| try { |
| fis = new FileInputStream(document); |
| br = new BufferedReader(new InputStreamReader(new BOMInputStream(fis))); |
| |
| if (!expectsHashPling |
| && !expectsAtEcho |
| && !expectsPackage |
| && !expectsXMLDecl |
| && !expectsPhpPI |
| && !expectsMSVSSF) { |
| written = true; |
| writer.write(getLicenseHeader(document)); |
| writer.write(LINE_SEP); |
| } |
| |
| String line; |
| boolean first = true; |
| while ((line = br.readLine()) != null) { |
| if (first && expectsHashPling) { |
| written = true; |
| doFirstLine(document, writer, line, "#!"); |
| } else if (first && expectsAtEcho) { |
| written = true; |
| doFirstLine(document, writer, line, "@echo"); |
| } else if (first && expectsMSVSSF) { |
| written = true; |
| if ("".equals(line)) { |
| line = passThroughReadNext(writer, line, br); |
| } |
| if (line.startsWith("Microsoft Visual Studio Solution" |
| + " File")) { |
| line = passThroughReadNext(writer, line, br); |
| } |
| doFirstLine(document, writer, line, "# Visual "); |
| } else { |
| writer.write(line); |
| writer.write(LINE_SEP); |
| } |
| |
| if (expectsPackage && line.startsWith("package ")) { |
| written = true; |
| writer.write(LINE_SEP); |
| writer.write(getLicenseHeader(document)); |
| writer.write(LINE_SEP); |
| } else if (expectsXMLDecl && line.startsWith("<?xml ")) { |
| written = true; |
| writer.write(LINE_SEP); |
| writer.write(getLicenseHeader(document)); |
| writer.write(LINE_SEP); |
| } else if (expectsPhpPI && line.startsWith("<?php")) { |
| written = true; |
| writer.write(LINE_SEP); |
| writer.write(getLicenseHeader(document)); |
| writer.write(LINE_SEP); |
| } |
| first = false; |
| } |
| } finally { |
| IOUtils.closeQuietly(br); |
| IOUtils.closeQuietly(fis); |
| IOUtils.closeQuietly(writer); |
| } |
| return written; |
| } |
| |
| /** |
| * Check first line for specified text and process. |
| */ |
| private void doFirstLine(File document, Writer writer, String line, String lookfor) throws IOException { |
| if (line.startsWith(lookfor)) { |
| writer.write(line); |
| writer.write(LINE_SEP); |
| writer.write(getLicenseHeader(document)); |
| } else { |
| writer.write(getLicenseHeader(document)); |
| writer.write(line); |
| writer.write(LINE_SEP); |
| } |
| } |
| |
| /** |
| * Detect the type of document. |
| * |
| * @param document to retrieve type from. |
| * @return not null |
| * 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. |
| */ |
| protected int getType(File document) { |
| String path = document.getPath(); |
| int lastDot = path.lastIndexOf(DOT); |
| if (lastDot >= 0 && lastDot < path.length() - 1) { |
| String ext = path.substring(lastDot + 1); |
| Integer type = EXT2TYPE.get(ext); |
| if (type != null) { |
| return type.intValue(); |
| } |
| } |
| return TYPE_UNKNOWN; |
| } |
| |
| /** |
| * Set the force flag on this appender. If this flag is set |
| * to true then files will be modified directly, otherwise |
| * new files will be created alongside the existing files. |
| * |
| * @param force force flag. |
| */ |
| public void setForce(boolean force) { |
| isForced = force; |
| } |
| |
| /** |
| * @param document document to extract from. |
| * @return Get the license header of a document. |
| */ |
| public abstract String getLicenseHeader(File document); |
| |
| /** |
| * Get the first line of the license header formatted |
| * for the given type of file. |
| * |
| * @param type the type of file, see the TYPE_* constants |
| * @return not null |
| */ |
| protected String getFirstLine(int type) { |
| if (isFamilyC(type)) { |
| return "/*" + LINE_SEP; |
| } else if (isFamilySGML(type)) { |
| return "<!--" + LINE_SEP; |
| } |
| return ""; |
| } |
| |
| |
| /** |
| * Get the last line of the license header formatted |
| * for the given type of file. |
| * |
| * @param type the type of file, see the TYPE_* constants |
| * @return not null |
| */ |
| protected String getLastLine(int type) { |
| if (isFamilyC(type)) { |
| return " */" + LINE_SEP; |
| } else if (isFamilySGML(type)) { |
| return "-->" + LINE_SEP; |
| } |
| return ""; |
| } |
| |
| |
| /** |
| * Get a line of the license header formatted |
| * for the given type of file. |
| * |
| * @param type the type of file, see the TYPE_* constants |
| * @param content the content for this line |
| * @return not null |
| */ |
| protected String getLine(int type, String content) { |
| if (isFamilyC(type)) { |
| return " * " + content + LINE_SEP; |
| } else if (isFamilySGML(type)) { |
| return content + LINE_SEP; |
| } else if (isFamilyAPT(type)) { |
| return "~~ " + content + LINE_SEP; |
| } else if (isFamilySH(type)) { |
| return "# " + content + LINE_SEP; |
| } else if (isFamilyBAT(type)) { |
| return "rem " + content + LINE_SEP; |
| } else if (isFamilyVelocity(type)) { |
| return "## " + content + LINE_SEP; |
| } |
| return ""; |
| } |
| |
| private static boolean isFamilyC(int type) { |
| return isIn(FAMILY_C, type); |
| } |
| |
| private static boolean isFamilySGML(int type) { |
| return isIn(FAMILY_SGML, type); |
| } |
| |
| private static boolean isFamilySH(int type) { |
| return isIn(FAMILY_SH, type); |
| } |
| |
| private static boolean isFamilyAPT(int type) { |
| return isIn(FAMILY_APT, type); |
| } |
| |
| private static boolean isFamilyBAT(int type) { |
| return isIn(FAMILY_BAT, type); |
| } |
| |
| private static boolean isFamilyVelocity(int type) { |
| return isIn(FAMILY_VELOCITY, type); |
| } |
| |
| private static boolean expectsHashPling(int type) { |
| return isIn(EXPECTS_HASH_PLING, type); |
| } |
| |
| private static boolean expectsAtEcho(int type) { |
| return isIn(EXPECTS_AT_ECHO, type); |
| } |
| |
| private static boolean expectsPackage(int type) { |
| return isIn(EXPECTS_PACKAGE, type); |
| } |
| |
| private static boolean expectsXMLDecl(int type) { |
| return isIn(EXPECTS_XML_DECL, type); |
| } |
| |
| private static boolean expectsPhpPI(int type) { |
| return isIn(EXPECTS_PHP_PI, type); |
| } |
| |
| private static boolean expectsMSVisualStudioSolutionFileHeader(int type) { |
| return isIn(EXPECTS_MSVSSF_HEADER, type); |
| } |
| |
| private static boolean isIn(int[] arr, int key) { |
| return Arrays.binarySearch(arr, key) >= 0; |
| } |
| |
| private String passThroughReadNext(Writer writer, String line, |
| BufferedReader br) throws IOException { |
| writer.write(line); |
| writer.write(LINE_SEP); |
| String l = br.readLine(); |
| return l == null ? "" : l; |
| } |
| } |
| |
| /** |
| * Stripped down version of Commons IO 2.0's BOMInputStream. |
| */ |
| class BOMInputStream extends FilterInputStream { |
| private int[] firstBytes; |
| private int fbLength, fbIndex, markFbIndex; |
| private boolean markedAtStart; |
| private static final int[][] BOMS = { |
| new int[]{0xEF, 0xBB, 0xBF}, // UTF-8 |
| new int[]{0xFE, 0xFF}, // UTF-16BE |
| new int[]{0xFF, 0xFE}, // UTF-16LE |
| }; |
| |
| BOMInputStream(InputStream s) { |
| super(s); |
| } |
| |
| @Override |
| public int read() throws IOException { |
| int b = readFirstBytes(); |
| return (b >= 0) ? b : in.read(); |
| } |
| |
| @Override |
| public int read(byte[] buf, int off, int len) throws IOException { |
| int firstCount = 0; |
| int b = 0; |
| while ((len > 0) && (b >= 0)) { |
| b = readFirstBytes(); |
| if (b >= 0) { |
| buf[off++] = (byte) (b & 0xFF); |
| len--; |
| firstCount++; |
| } |
| } |
| int secondCount = in.read(buf, off, len); |
| return (secondCount < 0) |
| ? (firstCount > 0 ? firstCount : -1) : firstCount + secondCount; |
| } |
| |
| @Override |
| public int read(byte[] buf) throws IOException { |
| return read(buf, 0, buf.length); |
| } |
| |
| private int readFirstBytes() throws IOException { |
| getBOM(); |
| return (fbIndex < fbLength) ? firstBytes[fbIndex++] : -1; |
| } |
| |
| private void getBOM() throws IOException { |
| if (firstBytes == null) { |
| int max = 0; |
| for (int[] BOM : BOMS) { |
| max = Math.max(max, BOM.length); |
| } |
| firstBytes = new int[max]; |
| for (int i = 0; i < firstBytes.length; i++) { |
| firstBytes[i] = in.read(); |
| fbLength++; |
| if (firstBytes[i] < 0) { |
| break; |
| } |
| |
| boolean found = find(); |
| if (found) { |
| fbLength = 0; |
| break; |
| } |
| } |
| } |
| } |
| |
| @Override |
| public synchronized void mark(int readlimit) { |
| markFbIndex = fbIndex; |
| markedAtStart = (firstBytes == null); |
| in.mark(readlimit); |
| } |
| |
| @Override |
| public synchronized void reset() throws IOException { |
| fbIndex = markFbIndex; |
| if (markedAtStart) { |
| firstBytes = null; |
| } |
| |
| in.reset(); |
| } |
| |
| @Override |
| public long skip(long n) throws IOException { |
| while ((n > 0) && (readFirstBytes() >= 0)) { |
| n--; |
| } |
| return in.skip(n); |
| } |
| |
| private boolean find() { |
| for (int[] BOM : BOMS) { |
| if (matches(BOM)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean matches(int[] bom) { |
| if (bom.length != fbLength) { |
| return false; |
| } |
| for (int i = 0; i < bom.length; i++) { |
| if (bom[i] != firstBytes[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } |