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 }