Skip to content

Commit f7c0b62

Browse files
committed
simplify
1 parent 51a1215 commit f7c0b62

File tree

6 files changed

+160
-73
lines changed

6 files changed

+160
-73
lines changed

api/shadow.api

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,6 @@ public class com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesF
439439
public fun <init> (Lorg/gradle/api/model/ObjectFactory;)V
440440
public fun canTransformResource (Lorg/gradle/api/file/FileTreeElement;)Z
441441
public fun getCharsetName ()Lorg/gradle/api/provider/Property;
442-
public fun getEscapeUnicode ()Lorg/gradle/api/provider/Property;
443442
public fun getKeyTransformer ()Lkotlin/jvm/functions/Function1;
444443
public fun getMappings ()Lorg/gradle/api/provider/MapProperty;
445444
public fun getMergeSeparator ()Lorg/gradle/api/provider/Property;

src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,31 @@ class PropertiesFileTransformerTest : BaseTransformerTest() {
5151
runWithSuccess(shadowJarPath)
5252

5353
val expected = when (strategy) {
54-
MergeStrategy.First -> arrayOf("key1=one", "key2=one", "key3=two")
55-
MergeStrategy.Latest -> arrayOf("key1=one", "key2=two", "key3=two")
56-
MergeStrategy.Append -> arrayOf("key1=one", "key2=one;two", "key3=two")
54+
MergeStrategy.First ->
55+
"""
56+
key1=one
57+
key2=one
58+
key3=two
59+
60+
"""
61+
MergeStrategy.Latest ->
62+
"""
63+
key1=one
64+
key2=two
65+
key3=two
66+
67+
"""
68+
MergeStrategy.Append ->
69+
"""
70+
key1=one
71+
key2=one;two
72+
key3=two
73+
74+
"""
5775
else -> fail("Unexpected strategy: $strategy")
58-
}
59-
val content = outputShadowedJar.use { it.getContent("META-INF/test.properties") }
60-
assertThat(content).contains(*expected)
76+
}.trimIndent()
77+
val content = outputShadowedJar.use { it.getContent("META-INF/test.properties") }.invariantEolString
78+
assertThat(content).isEqualTo(expected)
6179
}
6280
}
6381

Lines changed: 33 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,47 @@
11
package com.github.jengelman.gradle.plugins.shadow.internal
22

33
import java.io.OutputStream
4+
import java.io.StringWriter
5+
import java.io.Writer
46
import java.nio.charset.Charset
5-
import java.util.HexFormat
6-
import java.util.SortedMap
7+
import java.util.Properties
8+
import java.util.TreeMap
79

810
/**
9-
* Maintains a map of properties sorted by key, which can be written out to a text file with a consistent
10-
* ordering to satisfy the requirements of reproducible builds.
11+
* Provides functionality for reproducible serialization.
1112
*/
12-
internal class ReproducibleProperties {
13-
internal val props: SortedMap<String, String> = sortedMapOf()
13+
internal class ReproducibleProperties : Properties() {
14+
// Just to prevent accidental misuse.
15+
override fun store(writer: Writer?, comments: String?) {
16+
throw UnsupportedOperationException("use writeWithoutComments()")
17+
}
1418

15-
fun writeProperties(charset: Charset, os: OutputStream, escape: Boolean) {
16-
val zipWriter = os.writer(charset)
17-
props.forEach { (key, value) ->
18-
zipWriter.write(convertString(key, isKey = true, escape))
19-
zipWriter.write("=")
20-
zipWriter.write(convertString(value, isKey = false, escape))
21-
zipWriter.write("\n")
22-
}
23-
zipWriter.flush()
19+
// Just to prevent accidental misuse.
20+
override fun store(out: OutputStream?, comments: String?) {
21+
throw UnsupportedOperationException("use writeWithoutComments()")
2422
}
2523

26-
private fun convertString(
27-
str: String,
28-
isKey: Boolean,
29-
escape: Boolean,
30-
): String {
31-
val len = str.length
32-
val out = StringBuilder()
33-
val hex = HexFormat.of().withUpperCase()
34-
for (x in 0..<len) {
35-
val aChar = str[x]
36-
// Handle the common case first, avoid more expensive special cases
37-
if ((aChar.code > 61) && (aChar.code < 127)) {
38-
out.append(if (aChar == '\\') "\\\\" else aChar)
39-
continue
40-
}
41-
when (aChar) {
42-
' ' -> {
43-
if (x == 0 || isKey) out.append('\\')
44-
out.append(' ')
45-
}
46-
'\t' -> out.append("\\t")
47-
'\n' -> out.append("\\n")
48-
'\r' -> out.append("\\r")
49-
'\u000c' -> out.append("\\f")
50-
'=', ':', '#', '!' -> out.append('\\').append(aChar)
51-
else -> if (escape && ((aChar.code < 0x0020) || (aChar.code > 0x007e))) {
52-
out.append("\\u").append(hex.toHexDigits(aChar))
53-
} else {
54-
out.append(aChar)
24+
fun writeWithoutComments(charset: Charset, os: OutputStream) {
25+
val bufferedReader = StringWriter().apply {
26+
super.store(this, null)
27+
}.toString().reader().buffered()
28+
29+
os.bufferedWriter(charset).apply {
30+
var line: String? = null
31+
while (bufferedReader.readLine().also { line = it } != null) {
32+
if (!line!!.startsWith("#")) {
33+
write(line)
34+
newLine()
5535
}
5636
}
57-
}
58-
return out.toString()
37+
}.flush()
5938
}
39+
40+
// yields the entries for Properties.store0() in sorted order
41+
override val entries: MutableSet<MutableMap.MutableEntry<Any, Any>>
42+
get() {
43+
val sorted = TreeMap<Any, Any>()
44+
super.entries.forEach { sorted[it.key] = it.value }
45+
return sorted.entries
46+
}
6047
}

src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -122,21 +122,9 @@ public open class PropertiesFileTransformer @Inject constructor(
122122
@get:Input
123123
public open val mergeSeparator: Property<String> = objectFactory.property(",")
124124

125-
/**
126-
* Properties files are written without escaping Unicode characters using the character set
127-
* configured by [charsetName].
128-
*
129-
* Set this property to `true` to escape all Unicode characters in the properties file, producing
130-
* ASCII compatible files.
131-
*/
132-
@get:Input
133-
public open val escapeUnicode: Property<Boolean> = objectFactory.property(false)
134-
135125
/**
136126
* The character set to use when reading and writing property files.
137127
* Defaults to `ISO-8859-1`.
138-
*
139-
* See also [escapeUnicode].
140128
*/
141129
@get:Input
142130
public open val charsetName: Property<String> = objectFactory.property(Charsets.ISO_8859_1.name())
@@ -161,7 +149,7 @@ public open class PropertiesFileTransformer @Inject constructor(
161149
}
162150

163151
override fun transform(context: TransformerContext) {
164-
val props = propertiesEntries.computeIfAbsent(context.path) { ReproducibleProperties() }.props
152+
val props = propertiesEntries.computeIfAbsent(context.path) { ReproducibleProperties() }
165153
val mergeStrategy = MergeStrategy.from(mergeStrategyFor(context.path))
166154
val mergeSeparator = if (mergeStrategy == MergeStrategy.Append) mergeSeparatorFor(context.path) else ""
167155
loadAndTransformKeys(context.inputStream) { key, value ->
@@ -171,7 +159,7 @@ public open class PropertiesFileTransformer @Inject constructor(
171159
props[key] = value
172160
}
173161
MergeStrategy.Append -> {
174-
props[key] = props[key] + mergeSeparator + value
162+
props[key] = props[key] as String + mergeSeparator + value
175163
}
176164
MergeStrategy.First -> Unit
177165
MergeStrategy.Fail -> {
@@ -237,7 +225,7 @@ public open class PropertiesFileTransformer @Inject constructor(
237225
// Cannot close the writer as the OutputStream needs to remain open.
238226
propertiesEntries.forEach { (path, props) ->
239227
os.putNextEntry(zipEntry(path, preserveFileTimestamps))
240-
props.writeProperties(charset, os, escapeUnicode.get())
228+
props.writeWithoutComments(charset, os)
241229
os.closeEntry()
242230
}
243231
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.github.jengelman.gradle.plugins.shadow.internal
2+
3+
import assertk.assertThat
4+
import assertk.assertions.isEqualTo
5+
import java.io.ByteArrayOutputStream
6+
import java.nio.charset.Charset
7+
import java.nio.charset.StandardCharsets
8+
import org.junit.jupiter.params.ParameterizedTest
9+
import org.junit.jupiter.params.provider.MethodSource
10+
11+
class ReproduciblePropertiesTest {
12+
@ParameterizedTest
13+
@MethodSource("charsets")
14+
fun emptyProperties(charset: Charset) {
15+
val props = ReproducibleProperties()
16+
17+
val str = props.writeToString(charset)
18+
19+
assertThat(str).isEqualTo("")
20+
}
21+
22+
@ParameterizedTest
23+
@MethodSource("charsets")
24+
fun someProperties(charset: Charset) {
25+
val props = ReproducibleProperties()
26+
props["key"] = "value"
27+
props["key2"] = "value2"
28+
props["a"] = "b"
29+
props["d"] = "e"
30+
props["0"] = "1"
31+
props["b"] = "c"
32+
props["c"] = "d"
33+
props["e"] = "f"
34+
35+
val str = props.writeToString(charset)
36+
37+
assertThat(str).isEqualTo(
38+
"""
39+
0=1
40+
a=b
41+
b=c
42+
c=d
43+
d=e
44+
e=f
45+
key=value
46+
key2=value2
47+
48+
""".trimIndent(),
49+
)
50+
}
51+
52+
@ParameterizedTest
53+
@MethodSource("charsetsUtf")
54+
fun utfChars(charset: Charset) {
55+
val props = ReproducibleProperties()
56+
props["äöüß"] = "aouss"
57+
props["áèô"] = "aeo"
58+
props["€²³"] = "x"
59+
props["传傳磨宿说説"] = "b"
60+
61+
val str = props.writeToString(charset)
62+
63+
assertThat(str).isEqualTo(
64+
"""
65+
áèô=aeo
66+
äöüß=aouss
67+
€²³=x
68+
传傳磨宿说説=b
69+
70+
""".trimIndent(),
71+
)
72+
}
73+
74+
internal fun ReproducibleProperties.writeToString(charset: Charset): String {
75+
val buffer = ByteArrayOutputStream()
76+
writeWithoutComments(charset, buffer)
77+
return buffer.toString(charset.name()).replace(System.lineSeparator(), "\n")
78+
}
79+
80+
private companion object {
81+
@JvmStatic
82+
fun charsets() = listOf(
83+
StandardCharsets.ISO_8859_1,
84+
StandardCharsets.UTF_8,
85+
StandardCharsets.US_ASCII,
86+
StandardCharsets.UTF_16,
87+
)
88+
89+
@JvmStatic
90+
fun charsetsUtf() = listOf(
91+
StandardCharsets.UTF_8,
92+
StandardCharsets.UTF_16,
93+
)
94+
}
95+
}

src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformerTest.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest<PropertiesFileTransfor
7474
transformer.transform(context(path, input2))
7575
}
7676

77-
assertThat(transformer.propertiesEntries[path]?.props.orEmpty()).isEqualTo(expectedOutput)
77+
assertThat(transformer.propertiesEntries[path].orEmpty()).isEqualTo(expectedOutput)
7878
assertThat(transformer.conflicts).isEqualTo(expectedConflicts)
7979
}
8080

@@ -95,7 +95,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest<PropertiesFileTransfor
9595
transformer.transform(context(path, input2))
9696
}
9797

98-
assertThat(transformer.propertiesEntries[path]?.props.orEmpty()).isEqualTo(expectedOutput)
98+
assertThat(transformer.propertiesEntries[path].orEmpty()).isEqualTo(expectedOutput)
9999
}
100100

101101
@ParameterizedTest
@@ -115,7 +115,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest<PropertiesFileTransfor
115115
transformer.transform(context(path, input2))
116116
}
117117

118-
assertThat(transformer.propertiesEntries[path]?.props.orEmpty()).isEqualTo(expectedOutput)
118+
assertThat(transformer.propertiesEntries[path].orEmpty()).isEqualTo(expectedOutput)
119119
}
120120

121121
@ParameterizedTest
@@ -135,7 +135,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest<PropertiesFileTransfor
135135
transformer.transform(context(path, input2))
136136
}
137137

138-
assertThat(transformer.propertiesEntries[path]?.props.orEmpty()).isEqualTo(expectedOutput)
138+
assertThat(transformer.propertiesEntries[path].orEmpty()).isEqualTo(expectedOutput)
139139
}
140140

141141
@ParameterizedTest
@@ -152,7 +152,7 @@ class PropertiesFileTransformerTest : BaseTransformerTest<PropertiesFileTransfor
152152
transformer.transform(context(path, input, Charset.forName(charset)))
153153
}
154154

155-
assertThat(transformer.propertiesEntries[path]?.props.orEmpty()).isEqualTo(expectedOutput)
155+
assertThat(transformer.propertiesEntries[path].orEmpty()).isEqualTo(expectedOutput)
156156
}
157157

158158
private companion object {

0 commit comments

Comments
 (0)