Skip to content

Commit

Permalink
Fix adding license header to files larger than 2048 bytes
Browse files Browse the repository at this point in the history
The `BufferedReader` in `PreparedCommentHeader#update()` is initialized with a read-ahead limit of 2048 bytes.

If a file is larger than that, the mark will be invalidated while the `update()` method skips through the file line-by-line and when no valid existing license header was detected, the `BufferedReader#reset()` method will fail.

```text
java.io.IOException: Mark invalid
	at java.base/java.io.BufferedReader.reset(BufferedReader.java:517)
	at java_io_BufferedReader$reset$1.call(Unknown Source)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:119)
	at org.cadixdev.gradle.licenser.header.PreparedCommentHeader$_update_closure2.doCall(PreparedCommentHeader.groovy:84)
```
  • Loading branch information
joschi committed Jul 29, 2021
1 parent 6e73b6b commit 0d8612e
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,54 @@ class LicenserPluginFunctionalTest extends Specification {
where:
[gradleVersion, _, extraArgs] << testMatrix
}
@Unroll
def "updates headers in updateLicenses task for files larger than 2048 bytes (gradle #gradleVersion)"() {
given:
def projectDir = temporaryFolder.newFolder()
def sourceDir = projectDir.toPath().resolve(Paths.get("src", "main", "java", "com", "example")).toFile()
sourceDir.mkdirs()
new File(projectDir, "header.txt") << "New copyright header"
new File(projectDir, "settings.gradle") << ""
new File(projectDir, "build.gradle") << """
plugins {
id('java')
id('org.cadixdev.licenser')
}

license {
lineEnding = '\\n'
header = project.file('header.txt')
skipExistingHeaders = true
}
""".stripIndent()
def sourceFileContent = """\
package com.example;

class MyClass {
}
""".stripIndent() + "// Trailing data\n" * 256
def sourceFile = new File(sourceDir, "MyClass.java") << sourceFileContent
when:
def result = runner(projectDir, gradleVersion, extraArgs + "updateLicenses").build()
then:
result.task(":updateLicenses").outcome == TaskOutcome.SUCCESS
sourceFile.text.startsWith("""\
/*
* New copyright header
*/

package com.example;

class MyClass {
}
""".stripIndent().trim())
where:
[gradleVersion, _, extraArgs] << testMatrix
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@

package org.cadixdev.gradle.licenser.header

import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.cadixdev.gradle.licenser.util.HeaderHelper

@CompileStatic
@PackageScope
class PreparedCommentHeader implements PreparedHeader {

Expand All @@ -42,12 +44,12 @@ class PreparedCommentHeader implements PreparedHeader {

@Override
boolean check(File file, String charset, boolean skipExistingHeaders) throws IOException {
return file.withReader(charset) { BufferedReader reader ->
return new RandomAccessFile(file, "r").with {
boolean result = skipExistingHeaders ?
HeaderHelper.contentStartsWithValidHeaderFormat(reader, format) :
HeaderHelper.contentStartsWith(reader, this.lines.iterator(), format.skipLine)
HeaderHelper.contentStartsWithValidHeaderFormat(it, format) :
HeaderHelper.contentStartsWith(it, this.lines.iterator(), format.skipLine)
if (result) {
def line = reader.readLine()
def line = it.readLine()
if (header.newLine.get()) {
result = line != null && line.isEmpty()
} else if (line != null) {
Expand All @@ -73,22 +75,21 @@ class PreparedCommentHeader implements PreparedHeader {

// Open file for verifying the license header and reading the text we
// need to append after it
file.withReader(charset) { BufferedReader reader ->
if (skipExistingHeaders) {
reader.mark(2048)
def startsWithValidHeader = HeaderHelper.contentStartsWithValidHeaderFormat(reader, format)
if (startsWithValidHeader) {
valid = true
return
} else {
reader.reset()
}
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
if (skipExistingHeaders) {
def startsWithValidHeader = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, format)
if (startsWithValidHeader) {
return false
}
}

// Rewind back to the start of the file
randomAccessFile.seek(0L)
randomAccessFile.with {
String line
while (true) {
// Find first non-empty line
line = HeaderHelper.skipEmptyLines(reader)
line = HeaderHelper.skipEmptyLines(it)
if (line == null) {
return // EOF, invalid and done
}
Expand All @@ -108,7 +109,9 @@ class PreparedCommentHeader implements PreparedHeader {
// and the file doesn't have a license header yet
if (!(line =~ format.start) || (format.end && line =~ format.end)) {
last = line
text = reader.text
byte[] bytes = new byte[it.length() - it.getFilePointer()]
it.readFully(bytes)
text = new String(bytes, charset)
return
}

Expand All @@ -133,7 +136,7 @@ class PreparedCommentHeader implements PreparedHeader {
}

// Read the next line from the file
line = reader.readLine()
line = it.readLine()
if (line == null) {
// EOF, but the end comment was yet found
if (format.end) {
Expand Down Expand Up @@ -186,7 +189,7 @@ class PreparedCommentHeader implements PreparedHeader {
}

// Read one more line so we can check for new lines
last = reader.readLine()
last = it.readLine()
break
}
} else if (!(line =~ format.start)) {
Expand Down Expand Up @@ -224,15 +227,17 @@ class PreparedCommentHeader implements PreparedHeader {

if (last != null && HeaderHelper.isBlank(last)) {
// Skip empty lines
while ((last = reader.readLine()) != null && HeaderHelper.isBlank(last)) {
while ((last = it.readLine()) != null && HeaderHelper.isBlank(last)) {
// Duplicate new lines, NEVER valid
valid = false
}
}

if (last != null) {
// Read the remaining text from the file so we can add it back later
text = reader.text
byte[] bytes = new byte[it.length() - it.getFilePointer()]
it.readFully(bytes)
text = new String(bytes, charset)
}
return
}
Expand Down Expand Up @@ -289,5 +294,4 @@ class PreparedCommentHeader implements PreparedHeader {

return true
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@

package org.cadixdev.gradle.licenser.util;

import groovy.transform.CompileStatic;
import org.cadixdev.gradle.licenser.header.CommentHeaderFormat;

import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Iterator;
import java.util.regex.Pattern;

@CompileStatic
public final class HeaderHelper {

private HeaderHelper() {
Expand Down Expand Up @@ -72,9 +75,9 @@ public static String stripTrailingIndent(String s) {
return "";
}

public static boolean contentStartsWith(BufferedReader reader, Iterator<String> itr, Pattern ignored) throws IOException {
public static boolean contentStartsWith(RandomAccessFile file, Iterator<String> itr, Pattern ignored) throws IOException {
String line;
while (itr.hasNext() && (line = reader.readLine()) != null) {
while (itr.hasNext() && (line = file.readLine()) != null) {
if (ignored != null && ignored.matcher(line).find()) {
continue;
}
Expand All @@ -87,9 +90,9 @@ public static boolean contentStartsWith(BufferedReader reader, Iterator<String>
return !itr.hasNext();
}

public static boolean contentStartsWithValidHeaderFormat(BufferedReader reader, CommentHeaderFormat format) throws IOException {
public static boolean contentStartsWithValidHeaderFormat(RandomAccessFile file, CommentHeaderFormat format) throws IOException {
String firstLine;
while ((firstLine = skipEmptyLines(reader)) != null && findPattern(firstLine, format.getSkipLine())) {
while ((firstLine = skipEmptyLines(file)) != null && findPattern(firstLine, format.getSkipLine())) {
// skip ignored lines
}
if (firstLine == null) {
Expand All @@ -100,7 +103,7 @@ public static boolean contentStartsWithValidHeaderFormat(BufferedReader reader,
boolean contentLinesMatch = true;

String line;
while ((line = reader.readLine()) != null) {
while ((line = file.readLine()) != null) {
// skip ignored lines
if (findPattern(line, format.getSkipLine())) {
continue;
Expand Down Expand Up @@ -131,15 +134,15 @@ public static boolean isBlank(String s) {
return stripIndent(s).isEmpty();
}

public static String skipEmptyLines(BufferedReader reader) throws IOException {
@Nullable
public static String skipEmptyLines(RandomAccessFile file) throws IOException {
String line;
while ((line = reader.readLine()) != null) {
while ((line = file.readLine()) != null) {
if (!isBlank(line)) {
return line;
}
}

return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@ package org.cadixdev.gradle.licenser.util


import org.cadixdev.gradle.licenser.header.HeaderStyle
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification

class HeaderHelperTest extends Specification {
@Rule
TemporaryFolder temporaryFolder = new TemporaryFolder()

def "contentStartsWithValidHeaderFormat returns false with empty input"() {
given:
def inputString = ""
def stringReader = new StringReader(inputString)
def reader = new BufferedReader(stringReader)
def file = new RandomAccessFile(temporaryFolder.newFile(), "r")

when:
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
def result = HeaderHelper.contentStartsWithValidHeaderFormat(file, HeaderStyle.BLOCK_COMMENT.format)

then:
!result
Expand All @@ -45,11 +48,11 @@ class HeaderHelperTest extends Specification {
def "contentStartsWithValidHeaderFormat returns false with non-matching input"() {
given:
def inputString = "Not a copyright header"
def stringReader = new StringReader(inputString)
def reader = new BufferedReader(stringReader)
def file = temporaryFolder.newFile() << inputString
def randomAccessFile = new RandomAccessFile(file, "r")

when:
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.BLOCK_COMMENT.format)

then:
!result
Expand All @@ -63,11 +66,11 @@ class HeaderHelperTest extends Specification {
*/
My Content
""".stripIndent()
def stringReader = new StringReader(inputString)
def reader = new BufferedReader(stringReader)
def file = temporaryFolder.newFile() << inputString
def randomAccessFile = new RandomAccessFile(file, "r")

when:
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.BLOCK_COMMENT.format)

then:
result
Expand All @@ -81,11 +84,11 @@ class HeaderHelperTest extends Specification {
*/
My Content
""".stripIndent()
def stringReader = new StringReader(inputString)
def reader = new BufferedReader(stringReader)
def file = temporaryFolder.newFile() << inputString
def randomAccessFile = new RandomAccessFile(file, "r")

when:
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.BLOCK_COMMENT.format)

then:
!result
Expand All @@ -98,11 +101,11 @@ class HeaderHelperTest extends Specification {
*/
My Content
""".stripIndent()
def stringReader = new StringReader(inputString)
def reader = new BufferedReader(stringReader)
def file = temporaryFolder.newFile() << inputString
def randomAccessFile = new RandomAccessFile(file, "r")

when:
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.BLOCK_COMMENT.format)

then:
result
Expand All @@ -115,11 +118,11 @@ class HeaderHelperTest extends Specification {
* Incomplete copyright header
My Content
""".stripIndent()
def stringReader = new StringReader(inputString)
def reader = new BufferedReader(stringReader)
def file = temporaryFolder.newFile() << inputString
def randomAccessFile = new RandomAccessFile(file, "r")

when:
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.BLOCK_COMMENT.format)

then:
!result
Expand All @@ -132,11 +135,11 @@ class HeaderHelperTest extends Specification {
# Some header
My Content
""".stripIndent()
def stringReader = new StringReader(inputString)
def reader = new BufferedReader(stringReader)
def file = temporaryFolder.newFile() << inputString
def randomAccessFile = new RandomAccessFile(file, "r")

when:
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.HASH.format)
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.HASH.format)

then:
result
Expand All @@ -152,11 +155,11 @@ class HeaderHelperTest extends Specification {
<document>
</document>
""".stripIndent()
def stringReader = new StringReader(inputString)
def reader = new BufferedReader(stringReader)
def file = temporaryFolder.newFile() << inputString
def randomAccessFile = new RandomAccessFile(file, "r")

when:
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.XML.format)
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.XML.format)

then:
result
Expand Down

0 comments on commit 0d8612e

Please sign in to comment.