Skip to content

Commit 0d8612e

Browse files
committed
Fix adding license header to files larger than 2048 bytes
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) ```
1 parent 6e73b6b commit 0d8612e

4 files changed

Lines changed: 112 additions & 54 deletions

File tree

src/functionalTest/groovy/org/cadixdev/gradle/licenser/LicenserPluginFunctionalTest.groovy

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,54 @@ class LicenserPluginFunctionalTest extends Specification {
444444
445445
where:
446446
[gradleVersion, _, extraArgs] << testMatrix
447+
}
447448
449+
@Unroll
450+
def "updates headers in updateLicenses task for files larger than 2048 bytes (gradle #gradleVersion)"() {
451+
given:
452+
def projectDir = temporaryFolder.newFolder()
453+
def sourceDir = projectDir.toPath().resolve(Paths.get("src", "main", "java", "com", "example")).toFile()
454+
sourceDir.mkdirs()
455+
new File(projectDir, "header.txt") << "New copyright header"
456+
new File(projectDir, "settings.gradle") << ""
457+
new File(projectDir, "build.gradle") << """
458+
plugins {
459+
id('java')
460+
id('org.cadixdev.licenser')
461+
}
462+
463+
license {
464+
lineEnding = '\\n'
465+
header = project.file('header.txt')
466+
skipExistingHeaders = true
467+
}
468+
""".stripIndent()
469+
def sourceFileContent = """\
470+
package com.example;
471+
472+
class MyClass {
473+
}
474+
""".stripIndent() + "// Trailing data\n" * 256
475+
476+
def sourceFile = new File(sourceDir, "MyClass.java") << sourceFileContent
477+
478+
when:
479+
def result = runner(projectDir, gradleVersion, extraArgs + "updateLicenses").build()
480+
481+
then:
482+
result.task(":updateLicenses").outcome == TaskOutcome.SUCCESS
483+
sourceFile.text.startsWith("""\
484+
/*
485+
* New copyright header
486+
*/
487+
488+
package com.example;
489+
490+
class MyClass {
491+
}
492+
""".stripIndent().trim())
493+
494+
where:
495+
[gradleVersion, _, extraArgs] << testMatrix
448496
}
449497
}

src/main/groovy/org/cadixdev/gradle/licenser/header/PreparedCommentHeader.groovy

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424

2525
package org.cadixdev.gradle.licenser.header
2626

27+
import groovy.transform.CompileStatic
2728
import groovy.transform.PackageScope
2829
import org.cadixdev.gradle.licenser.util.HeaderHelper
2930

31+
@CompileStatic
3032
@PackageScope
3133
class PreparedCommentHeader implements PreparedHeader {
3234

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

4345
@Override
4446
boolean check(File file, String charset, boolean skipExistingHeaders) throws IOException {
45-
return file.withReader(charset) { BufferedReader reader ->
47+
return new RandomAccessFile(file, "r").with {
4648
boolean result = skipExistingHeaders ?
47-
HeaderHelper.contentStartsWithValidHeaderFormat(reader, format) :
48-
HeaderHelper.contentStartsWith(reader, this.lines.iterator(), format.skipLine)
49+
HeaderHelper.contentStartsWithValidHeaderFormat(it, format) :
50+
HeaderHelper.contentStartsWith(it, this.lines.iterator(), format.skipLine)
4951
if (result) {
50-
def line = reader.readLine()
52+
def line = it.readLine()
5153
if (header.newLine.get()) {
5254
result = line != null && line.isEmpty()
5355
} else if (line != null) {
@@ -73,22 +75,21 @@ class PreparedCommentHeader implements PreparedHeader {
7375

7476
// Open file for verifying the license header and reading the text we
7577
// need to append after it
76-
file.withReader(charset) { BufferedReader reader ->
77-
if (skipExistingHeaders) {
78-
reader.mark(2048)
79-
def startsWithValidHeader = HeaderHelper.contentStartsWithValidHeaderFormat(reader, format)
80-
if (startsWithValidHeader) {
81-
valid = true
82-
return
83-
} else {
84-
reader.reset()
85-
}
78+
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
79+
if (skipExistingHeaders) {
80+
def startsWithValidHeader = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, format)
81+
if (startsWithValidHeader) {
82+
return false
8683
}
84+
}
8785

86+
// Rewind back to the start of the file
87+
randomAccessFile.seek(0L)
88+
randomAccessFile.with {
8889
String line
8990
while (true) {
9091
// Find first non-empty line
91-
line = HeaderHelper.skipEmptyLines(reader)
92+
line = HeaderHelper.skipEmptyLines(it)
9293
if (line == null) {
9394
return // EOF, invalid and done
9495
}
@@ -108,7 +109,9 @@ class PreparedCommentHeader implements PreparedHeader {
108109
// and the file doesn't have a license header yet
109110
if (!(line =~ format.start) || (format.end && line =~ format.end)) {
110111
last = line
111-
text = reader.text
112+
byte[] bytes = new byte[it.length() - it.getFilePointer()]
113+
it.readFully(bytes)
114+
text = new String(bytes, charset)
112115
return
113116
}
114117

@@ -133,7 +136,7 @@ class PreparedCommentHeader implements PreparedHeader {
133136
}
134137

135138
// Read the next line from the file
136-
line = reader.readLine()
139+
line = it.readLine()
137140
if (line == null) {
138141
// EOF, but the end comment was yet found
139142
if (format.end) {
@@ -186,7 +189,7 @@ class PreparedCommentHeader implements PreparedHeader {
186189
}
187190

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

225228
if (last != null && HeaderHelper.isBlank(last)) {
226229
// Skip empty lines
227-
while ((last = reader.readLine()) != null && HeaderHelper.isBlank(last)) {
230+
while ((last = it.readLine()) != null && HeaderHelper.isBlank(last)) {
228231
// Duplicate new lines, NEVER valid
229232
valid = false
230233
}
231234
}
232235

233236
if (last != null) {
234237
// Read the remaining text from the file so we can add it back later
235-
text = reader.text
238+
byte[] bytes = new byte[it.length() - it.getFilePointer()]
239+
it.readFully(bytes)
240+
text = new String(bytes, charset)
236241
}
237242
return
238243
}
@@ -289,5 +294,4 @@ class PreparedCommentHeader implements PreparedHeader {
289294

290295
return true
291296
}
292-
293297
}

src/main/groovy/org/cadixdev/gradle/licenser/util/HeaderHelper.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,17 @@
2424

2525
package org.cadixdev.gradle.licenser.util;
2626

27+
import groovy.transform.CompileStatic;
2728
import org.cadixdev.gradle.licenser.header.CommentHeaderFormat;
2829

2930
import javax.annotation.Nullable;
3031
import java.io.BufferedReader;
3132
import java.io.IOException;
33+
import java.io.RandomAccessFile;
3234
import java.util.Iterator;
3335
import java.util.regex.Pattern;
3436

37+
@CompileStatic
3538
public final class HeaderHelper {
3639

3740
private HeaderHelper() {
@@ -72,9 +75,9 @@ public static String stripTrailingIndent(String s) {
7275
return "";
7376
}
7477

75-
public static boolean contentStartsWith(BufferedReader reader, Iterator<String> itr, Pattern ignored) throws IOException {
78+
public static boolean contentStartsWith(RandomAccessFile file, Iterator<String> itr, Pattern ignored) throws IOException {
7679
String line;
77-
while (itr.hasNext() && (line = reader.readLine()) != null) {
80+
while (itr.hasNext() && (line = file.readLine()) != null) {
7881
if (ignored != null && ignored.matcher(line).find()) {
7982
continue;
8083
}
@@ -87,9 +90,9 @@ public static boolean contentStartsWith(BufferedReader reader, Iterator<String>
8790
return !itr.hasNext();
8891
}
8992

90-
public static boolean contentStartsWithValidHeaderFormat(BufferedReader reader, CommentHeaderFormat format) throws IOException {
93+
public static boolean contentStartsWithValidHeaderFormat(RandomAccessFile file, CommentHeaderFormat format) throws IOException {
9194
String firstLine;
92-
while ((firstLine = skipEmptyLines(reader)) != null && findPattern(firstLine, format.getSkipLine())) {
95+
while ((firstLine = skipEmptyLines(file)) != null && findPattern(firstLine, format.getSkipLine())) {
9396
// skip ignored lines
9497
}
9598
if (firstLine == null) {
@@ -100,7 +103,7 @@ public static boolean contentStartsWithValidHeaderFormat(BufferedReader reader,
100103
boolean contentLinesMatch = true;
101104

102105
String line;
103-
while ((line = reader.readLine()) != null) {
106+
while ((line = file.readLine()) != null) {
104107
// skip ignored lines
105108
if (findPattern(line, format.getSkipLine())) {
106109
continue;
@@ -131,15 +134,15 @@ public static boolean isBlank(String s) {
131134
return stripIndent(s).isEmpty();
132135
}
133136

134-
public static String skipEmptyLines(BufferedReader reader) throws IOException {
137+
@Nullable
138+
public static String skipEmptyLines(RandomAccessFile file) throws IOException {
135139
String line;
136-
while ((line = reader.readLine()) != null) {
140+
while ((line = file.readLine()) != null) {
137141
if (!isBlank(line)) {
138142
return line;
139143
}
140144
}
141145

142146
return null;
143147
}
144-
145148
}

src/test/groovy/org/cadixdev/gradle/licenser/util/HeaderHelperTest.groovy

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,20 @@ package org.cadixdev.gradle.licenser.util
2626

2727

2828
import org.cadixdev.gradle.licenser.header.HeaderStyle
29+
import org.junit.Rule
30+
import org.junit.rules.TemporaryFolder
2931
import spock.lang.Specification
3032

3133
class HeaderHelperTest extends Specification {
34+
@Rule
35+
TemporaryFolder temporaryFolder = new TemporaryFolder()
36+
3237
def "contentStartsWithValidHeaderFormat returns false with empty input"() {
3338
given:
34-
def inputString = ""
35-
def stringReader = new StringReader(inputString)
36-
def reader = new BufferedReader(stringReader)
39+
def file = new RandomAccessFile(temporaryFolder.newFile(), "r")
3740

3841
when:
39-
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
42+
def result = HeaderHelper.contentStartsWithValidHeaderFormat(file, HeaderStyle.BLOCK_COMMENT.format)
4043

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

5154
when:
52-
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
55+
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.BLOCK_COMMENT.format)
5356

5457
then:
5558
!result
@@ -63,11 +66,11 @@ class HeaderHelperTest extends Specification {
6366
*/
6467
My Content
6568
""".stripIndent()
66-
def stringReader = new StringReader(inputString)
67-
def reader = new BufferedReader(stringReader)
69+
def file = temporaryFolder.newFile() << inputString
70+
def randomAccessFile = new RandomAccessFile(file, "r")
6871

6972
when:
70-
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
73+
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.BLOCK_COMMENT.format)
7174

7275
then:
7376
result
@@ -81,11 +84,11 @@ class HeaderHelperTest extends Specification {
8184
*/
8285
My Content
8386
""".stripIndent()
84-
def stringReader = new StringReader(inputString)
85-
def reader = new BufferedReader(stringReader)
87+
def file = temporaryFolder.newFile() << inputString
88+
def randomAccessFile = new RandomAccessFile(file, "r")
8689

8790
when:
88-
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
91+
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.BLOCK_COMMENT.format)
8992

9093
then:
9194
!result
@@ -98,11 +101,11 @@ class HeaderHelperTest extends Specification {
98101
*/
99102
My Content
100103
""".stripIndent()
101-
def stringReader = new StringReader(inputString)
102-
def reader = new BufferedReader(stringReader)
104+
def file = temporaryFolder.newFile() << inputString
105+
def randomAccessFile = new RandomAccessFile(file, "r")
103106

104107
when:
105-
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
108+
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.BLOCK_COMMENT.format)
106109

107110
then:
108111
result
@@ -115,11 +118,11 @@ class HeaderHelperTest extends Specification {
115118
* Incomplete copyright header
116119
My Content
117120
""".stripIndent()
118-
def stringReader = new StringReader(inputString)
119-
def reader = new BufferedReader(stringReader)
121+
def file = temporaryFolder.newFile() << inputString
122+
def randomAccessFile = new RandomAccessFile(file, "r")
120123

121124
when:
122-
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.BLOCK_COMMENT.format)
125+
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.BLOCK_COMMENT.format)
123126

124127
then:
125128
!result
@@ -132,11 +135,11 @@ class HeaderHelperTest extends Specification {
132135
# Some header
133136
My Content
134137
""".stripIndent()
135-
def stringReader = new StringReader(inputString)
136-
def reader = new BufferedReader(stringReader)
138+
def file = temporaryFolder.newFile() << inputString
139+
def randomAccessFile = new RandomAccessFile(file, "r")
137140

138141
when:
139-
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.HASH.format)
142+
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.HASH.format)
140143

141144
then:
142145
result
@@ -152,11 +155,11 @@ class HeaderHelperTest extends Specification {
152155
<document>
153156
</document>
154157
""".stripIndent()
155-
def stringReader = new StringReader(inputString)
156-
def reader = new BufferedReader(stringReader)
158+
def file = temporaryFolder.newFile() << inputString
159+
def randomAccessFile = new RandomAccessFile(file, "r")
157160

158161
when:
159-
def result = HeaderHelper.contentStartsWithValidHeaderFormat(reader, HeaderStyle.XML.format)
162+
def result = HeaderHelper.contentStartsWithValidHeaderFormat(randomAccessFile, HeaderStyle.XML.format)
160163

161164
then:
162165
result

0 commit comments

Comments
 (0)