diff --git a/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.kt b/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.kt new file mode 100644 index 000000000..2fa195e77 --- /dev/null +++ b/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.kt @@ -0,0 +1,178 @@ +package com.github.jengelman.gradle.plugins.shadow.transformers + +import java.io.PrintWriter +import java.nio.charset.Charset +import java.text.SimpleDateFormat +import java.util.Date +import java.util.TreeSet +import org.apache.tools.zip.ZipEntry +import org.apache.tools.zip.ZipOutputStream +import org.gradle.api.file.FileTreeElement +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional + +/** + * Merges `META-INF/NOTICE.TXT` files. + * + * Modified from `org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer.java` + * + * @author John Engelman + */ +open class ApacheNoticeResourceTransformer : Transformer { + private val entries = mutableSetOf() + private val organizationEntries = mutableMapOf>() + private val charset get() = if (encoding.isNullOrEmpty()) Charsets.UTF_8 else Charset.forName(encoding) + + /** + * MSHADE-101 :: NullPointerException when projectName is missing + */ + @get:Input + var projectName: String = "" + + @get:Input + var addHeader: Boolean = true + + @get:Input + var preamble1: String = """ + // ------------------------------------------------------------------ + // NOTICE file corresponding to the section 4d of The Apache License, + // Version 2.0, in this case for + """.trimIndent() + + @get:Input + var preamble2: String = "\n// ------------------------------------------------------------------\n" + + @get:Input + var preamble3: String = "This product includes software developed at\n" + + @get:Input + var organizationName: String = "The Apache Software Foundation" + + @get:Input + var organizationURL: String = "http://www.apache.org/" + + @get:Input + var inceptionYear: String = "2006" + + @get:Optional + @get:Input + var copyright: String? = null + + /** + * The file encoding of the `NOTICE` file. + */ + @get:Optional + @get:Input + var encoding: String? = null + + override fun canTransformResource(element: FileTreeElement): Boolean { + val path = element.relativePath.pathString + return NOTICE_PATH.equals(path, ignoreCase = true) || NOTICE_TXT_PATH.equals(path, ignoreCase = true) + } + + override fun transform(context: TransformerContext) { + if (entries.isEmpty()) { + val year = SimpleDateFormat("yyyy").format(Date()).let { + if (inceptionYear != it) "$inceptionYear-$it" else it + } + // add headers + if (addHeader) { + entries.add("$preamble1$projectName$preamble2") + } else { + entries.add("") + } + // fake second entry, we'll look for a real one later + entries.add("$projectName\nCopyright $year $organizationName\n") + entries.add("$preamble3$organizationName ($organizationURL).\n") + } + + val reader = context.inputStream.bufferedReader(charset) + var line = reader.readLine() + val sb = StringBuffer() + var currentOrg: MutableSet? = null + var lineCount = 0 + while (line != null) { + val trimmedLine = line.trim() + if (!trimmedLine.startsWith("//")) { + if (trimmedLine.isNotEmpty()) { + if (trimmedLine.startsWith("- ")) { + // resource-bundle 1.3 mode + if (lineCount == 1 && sb.toString().contains("This product includes/uses software(s) developed by")) { + currentOrg = organizationEntries.getOrPut(sb.toString().trim()) { TreeSet() } + sb.setLength(0) + } else if (sb.isNotEmpty() && currentOrg != null) { + currentOrg.add(sb.toString()) + sb.setLength(0) + } + } + sb.append(line).append("\n") + lineCount++ + } else { + val entry = sb.toString() + if (entry.startsWith(projectName) && entry.contains("Copyright ")) { + copyright = entry + } + if (currentOrg == null) { + entries.add(entry) + } else { + currentOrg.add(entry) + } + sb.setLength(0) + lineCount = 0 + currentOrg = null + } + } + + line = reader.readLine() + } + if (sb.isNotEmpty()) { + if (currentOrg == null) { + entries.add(sb.toString()) + } else { + currentOrg.add(sb.toString()) + } + } + } + + override fun hasTransformedResource(): Boolean = true + + override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) { + val zipEntry = ZipEntry(NOTICE_PATH) + zipEntry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, zipEntry.time) + os.putNextEntry(zipEntry) + + val writer = PrintWriter(os.writer(charset)) + + var count = 0 + for (line in entries) { + count++ + if (line == copyright && count != 2) continue + if (count == 2 && copyright != null) { + writer.print(copyright) + writer.print('\n') + } else { + writer.print(line) + writer.print('\n') + } + if (count == 3) { + // do org stuff + for ((key, value) in organizationEntries) { + writer.print(key) + writer.print('\n') + for (l in value) { + writer.print(l) + } + writer.print('\n') + } + } + } + + writer.flush() + entries.clear() + } + + private companion object { + private const val NOTICE_PATH = "META-INF/NOTICE" + private const val NOTICE_TXT_PATH = "META-INF/NOTICE.txt" + } +} diff --git a/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt b/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt new file mode 100644 index 000000000..ba5a2632b --- /dev/null +++ b/api/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt @@ -0,0 +1,227 @@ +package com.github.jengelman.gradle.plugins.shadow.transformers + +import com.github.jengelman.gradle.plugins.shadow.internal.CleanProperties +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.InputStreamReader +import java.nio.charset.Charset +import java.util.Properties +import java.util.function.Function +import org.apache.tools.zip.ZipEntry +import org.apache.tools.zip.ZipOutputStream +import org.gradle.api.file.FileTreeElement +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal + +/** + * Resources transformer that merges Properties files. + * + * The default merge strategy discards duplicate values coming from additional + * resources. This behavior can be changed by setting a value for the `mergeStrategy` + * property, such as 'first' (default), 'latest' or 'append'. If the merge strategy is + * 'latest' then the last value of a matching property entry will be used. If the + * merge strategy is 'append' then the property values will be combined, using a + * merge separator (default value is ','). The merge separator can be changed by + * setting a value for the `mergeSeparator` property. + * + * Say there are two properties files A and B with the + * following entries: + * + * **A** + * - key1 = value1 + * - key2 = value2 + * + * **B** + * - key2 = balue2 + * - key3 = value3 + * + * With `mergeStrategy = first` you get + * + * **C** + * - key1 = value1 + * - key2 = value2 + * - key3 = value3 + * + * With `mergeStrategy = latest` you get + * + * **C** + * - key1 = value1 + * - key2 = balue2 + * - key3 = value3 + * + * With `mergeStrategy = append` and `mergeSeparator = ;` you get + * + * **C** + * - key1 = value1 + * - key2 = value2;balue2 + * - key3 = value3 + * + * There are three additional properties that can be set: `paths`, `mappings`, + * and `keyTransformer`. + * The first contains a list of strings or regexes that will be used to determine if + * a path should be transformed or not. The merge strategy and merge separator are + * taken from the global settings. + * + * The `mappings` property allows you to define merge strategy and separator per + * path. If either `paths` or `mappings` is defined then no other path + * entries will be merged. `mappings` has precedence over `paths` if both + * are defined. + * + * If you need to transform keys in properties files, e.g. because they contain class + * names about to be relocated, you can set the `keyTransformer` property to a + * closure that receives the original key and returns the key name to be used. + * + * Example: + * ```groovy + * import org.codehaus.griffon.gradle.shadow.transformers.* + * tasks.named('shadowJar', ShadowJar) { + * transform(PropertiesFileTransformer) { + * paths = [ + * 'META-INF/editors/java.beans.PropertyEditor' + * ] + * keyTransformer = { key -> + * key.replaceAll('^(orig\.package\..*)$', 'new.prefix.$1') + * } + * } + * } + * ``` + * + * @author Andres Almiray + * @author Marc Philipp + */ +open class PropertiesFileTransformer : Transformer { + private val propertiesEntries = mutableMapOf() + private val _charset get() = Charset.forName(charset) + + @get:Input + var paths: List = listOf() + + @get:Input + var mappings: Map> = mapOf() + + /** + * Optional values: first, latest, append. + */ + @get:Input + var mergeStrategy: String = "first" + + @get:Input + var mergeSeparator: String = "," + + @get:Input + var charset: String = "ISO_8859_1" + + /** + * Use [java.util.function.Function] here for compatibility with Groovy and Java. + */ + @get:Internal + var keyTransformer: Function = IDENTITY + + override fun canTransformResource(element: FileTreeElement): Boolean { + val path = element.relativePath.pathString + if (path in mappings) return true + for (key in mappings.keys) { + if (key.toRegex().containsMatchIn(path)) return true + } + if (path in paths) return true + for (p in paths) { + if (p.toRegex().containsMatchIn(path)) return true + } + return mappings.isEmpty() && paths.isEmpty() && path.endsWith(PROPERTIES_SUFFIX) + } + + override fun transform(context: TransformerContext) { + val props = propertiesEntries[context.path] + val incoming = loadAndTransformKeys(context.inputStream) + if (props == null) { + propertiesEntries[context.path] = incoming + } else { + for ((key, value) in incoming) { + if (props.containsKey(key)) { + when (mergeStrategyFor(context.path).lowercase()) { + "latest" -> props[key] = value + "append" -> props[key] = props.getProperty(key as String) + mergeSeparatorFor(context.path) + value + "first" -> Unit + else -> Unit + } + } else { + props[key] = value + } + } + } + } + + private fun loadAndTransformKeys(inputStream: InputStream): CleanProperties { + val props = CleanProperties() + // InputStream closed by caller, so we don't do it here. + props.load(inputStream.reader(_charset)) + return transformKeys(props) + } + + private fun transformKeys(properties: Properties): CleanProperties { + if (keyTransformer === IDENTITY) { + return properties as CleanProperties + } + val result = CleanProperties() + properties.forEach { (key, value) -> + result[keyTransformer.apply(key as String)] = value + } + return result + } + + private fun mergeStrategyFor(path: String): String { + mappings[path]?.let { + return it["mergeStrategy"] ?: mergeStrategy + } + for (key in mappings.keys) { + if (key.toRegex().containsMatchIn(path)) { + return mappings[key]?.get("mergeStrategy") ?: mergeStrategy + } + } + return mergeStrategy + } + + private fun mergeSeparatorFor(path: String): String { + mappings[path]?.let { + return it["mergeSeparator"] ?: mergeSeparator + } + for (key in mappings.keys) { + if (key.toRegex().containsMatchIn(path)) { + return mappings[key]?.get("mergeSeparator") ?: mergeSeparator + } + } + return mergeSeparator + } + + override fun hasTransformedResource(): Boolean { + return propertiesEntries.isNotEmpty() + } + + override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) { + // cannot close the writer as the OutputStream needs to remain open + val zipWriter = os.writer(_charset) + propertiesEntries.forEach { (path, props) -> + val entry = ZipEntry(path) + entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) + os.putNextEntry(entry) + props.toReader().use { + it.copyTo(zipWriter) + } + zipWriter.flush() + os.closeEntry() + } + } + + private fun Properties.toReader(): InputStreamReader { + val os = ByteArrayOutputStream() + os.writer(Charset.forName(charset)).use { writer -> + store(writer, "") + } + return os.toByteArray().inputStream().reader(_charset) + } + + private companion object { + private const val PROPERTIES_SUFFIX = ".properties" + private val IDENTITY = Function { it } + } +} diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.groovy deleted file mode 100644 index 3f894ce7a..000000000 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.groovy +++ /dev/null @@ -1,225 +0,0 @@ -/* - * 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 com.github.jengelman.gradle.plugins.shadow.transformers - -import org.apache.tools.zip.ZipEntry -import org.apache.tools.zip.ZipOutputStream -import org.codehaus.plexus.util.StringUtils -import org.gradle.api.file.FileTreeElement -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Optional - -import java.text.SimpleDateFormat - -/** - * Merges META-INF/NOTICE.TXT files. - *

- * Modified from org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer.java - * - * @author John Engelman - */ -class ApacheNoticeResourceTransformer implements Transformer { - - private Set entries = new LinkedHashSet() - - private Map> organizationEntries = new LinkedHashMap>() - - @Input - String projectName = "" // MSHADE-101 :: NullPointerException when projectName is missing - - @Input - boolean addHeader = true - - @Input - String preamble1 = "// ------------------------------------------------------------------\n" + - "// NOTICE file corresponding to the section 4d of The Apache License,\n" + - "// Version 2.0, in this case for " - - @Input - String preamble2 = "\n// ------------------------------------------------------------------\n" - - @Input - String preamble3 = "This product includes software developed at\n" - - @Input - String organizationName = "The Apache Software Foundation" - - @Input - String organizationURL = "http://www.apache.org/" - - @Input - String inceptionYear = "2006" - - @Optional - @Input - String copyright - - /** - * The file encoding of the NOTICE file. - */ - @Optional - @Input - String encoding - - private static final String NOTICE_PATH = "META-INF/NOTICE" - - private static final String NOTICE_TXT_PATH = "META-INF/NOTICE.txt" - - @Override - boolean canTransformResource(FileTreeElement element) { - def path = element.relativePath.pathString - if (NOTICE_PATH.equalsIgnoreCase(path) || NOTICE_TXT_PATH.equalsIgnoreCase(path)) { - return true - } - - return false - } - - @Override - void transform(TransformerContext context) { - if (entries.isEmpty()) { - String year = new SimpleDateFormat("yyyy").format(new Date()) - if (inceptionYear != year) { - year = inceptionYear + "-" + year - } - - //add headers - if (addHeader) { - entries.add(preamble1 + projectName + preamble2) - } else { - entries.add("") - } - //fake second entry, we'll look for a real one later - entries.add(projectName + "\nCopyright " + year + " " + organizationName + "\n") - entries.add(preamble3 + organizationName + " (" + organizationURL + ").\n") - } - - BufferedReader reader - if (StringUtils.isNotEmpty(encoding)) { - reader = new BufferedReader(new InputStreamReader(context.inputStream, encoding)) - } else { - reader = new BufferedReader(new InputStreamReader(context.inputStream)) - } - - String line = reader.readLine() - StringBuffer sb = new StringBuffer() - Set currentOrg = null - int lineCount = 0 - while (line != null) { - String trimedLine = line.trim() - - if (!trimedLine.startsWith("//")) { - if (trimedLine.length() > 0) { - if (trimedLine.startsWith("- ")) { - //resource-bundle 1.3 mode - if (lineCount == 1 - && sb.toString().indexOf("This product includes/uses software(s) developed by") != -1) { - currentOrg = organizationEntries.get(sb.toString().trim()) - if (currentOrg == null) { - currentOrg = new TreeSet() - organizationEntries.put(sb.toString().trim(), currentOrg) - } - sb = new StringBuffer() - } else if (sb.length() > 0 && currentOrg != null) { - currentOrg.add(sb.toString()) - sb = new StringBuffer() - } - - } - sb.append(line).append("\n") - lineCount++ - } else { - String ent = sb.toString() - if (ent.startsWith(projectName) && ent.indexOf("Copyright ") != -1) { - copyright = ent - } - if (currentOrg == null) { - entries.add(ent) - } else { - currentOrg.add(ent) - } - sb = new StringBuffer() - lineCount = 0 - currentOrg = null - } - } - - line = reader.readLine() - } - if (sb.length() > 0) { - if (currentOrg == null) { - entries.add(sb.toString()) - } else { - currentOrg.add(sb.toString()) - } - } - } - - @Override - boolean hasTransformedResource() { - return true - } - - @Override - void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { - ZipEntry zipEntry = new ZipEntry(NOTICE_PATH) - zipEntry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, zipEntry.time) - os.putNextEntry(zipEntry) - - Writer pow - if (StringUtils.isNotEmpty(encoding)) { - pow = new OutputStreamWriter(os, encoding) - } else { - pow = new OutputStreamWriter(os) - } - PrintWriter writer = new PrintWriter(pow) - - int count = 0 - for (String line : entries) { - ++count - if (line == copyright && count != 2) { - continue - } - - if (count == 2 && copyright != null) { - writer.print(copyright) - writer.print('\n') - } else { - writer.print(line) - writer.print('\n') - } - if (count == 3) { - //do org stuff - for (Map.Entry> entry : organizationEntries.entrySet()) { - writer.print(entry.getKey()) - writer.print('\n') - for (String l : entry.getValue()) { - writer.print(l) - } - writer.print('\n') - } - } - } - - writer.flush() - - entries.clear() - } -} diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy deleted file mode 100644 index 64c230db3..000000000 --- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.groovy +++ /dev/null @@ -1,257 +0,0 @@ -/* - * 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 com.github.jengelman.gradle.plugins.shadow.transformers - -import com.github.jengelman.gradle.plugins.shadow.internal.CleanProperties -import org.apache.tools.zip.ZipEntry -import org.apache.tools.zip.ZipOutputStream -import org.codehaus.plexus.util.IOUtil -import org.gradle.api.file.FileTreeElement -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal - -import static groovy.lang.Closure.IDENTITY - -/** - * Resources transformer that merges Properties files. - * - *

The default merge strategy discards duplicate values coming from additional - * resources. This behavior can be changed by setting a value for the mergeStrategy - * property, such as 'first' (default), 'latest' or 'append'. If the merge strategy is - * 'latest' then the last value of a matching property entry will be used. If the - * merge strategy is 'append' then the property values will be combined, using a - * merge separator (default value is ','). The merge separator can be changed by - * setting a value for the mergeSeparator property.

- * - * Say there are two properties files A and B with the - * following entries: - * - * A - *
    - *
  • key1 = value1
  • - *
  • key2 = value2
  • - *
- * - * B - *
    - *
  • key2 = balue2
  • - *
  • key3 = value3
  • - *
- * - * With mergeStrategy = first you get - * - * C - *
    - *
  • key1 = value1
  • - *
  • key2 = value2
  • - *
  • key3 = value3
  • - *
- * - * With mergeStrategy = latest you get - * - * C - *
    - *
  • key1 = value1
  • - *
  • key2 = balue2
  • - *
  • key3 = value3
  • - *
- * - * With mergeStrategy = append and mergeSparator = ; you get - * - * C - *
    - *
  • key1 = value1
  • - *
  • key2 = value2;balue2
  • - *
  • key3 = value3
  • - *
- * - *

There are three additional properties that can be set: paths, mappings, - * and keyTransformer. - * The first contains a list of strings or regexes that will be used to determine if - * a path should be transformed or not. The merge strategy and merge separator are - * taken from the global settings.

- * - *

The mappings property allows you to define merge strategy and separator per - * path

. If either paths or mappings is defined then no other path - * entries will be merged. mappings has precedence over paths if both - * are defined.

- * - *

If you need to transform keys in properties files, e.g. because they contain class - * names about to be relocated, you can set the keyTransformer property to a - * closure that receives the original key and returns the key name to be used.

- * - *

Example:

- *
- * import org.codehaus.griffon.gradle.shadow.transformers.*
- * tasks.named('shadowJar', ShadowJar) {
- *     transform(PropertiesFileTransformer) {
- *         paths = [
- *             'META-INF/editors/java.beans.PropertyEditor'
- *         ]
- *         keyTransformer = { key ->
- *             key.replaceAll('^(orig\.package\..*)$', 'new.prefix.$1')
- *         }
- *     }
- * }
- * 
- * - * @author Andres Almiray - * @author Marc Philipp - */ -class PropertiesFileTransformer implements Transformer { - private static final String PROPERTIES_SUFFIX = '.properties' - - private Map propertiesEntries = [:] - - @Input - List paths = [] - - @Input - Map> mappings = [:] - - @Input - String mergeStrategy = 'first' // latest, append - - @Input - String mergeSeparator = ',' - - @Input - String charset = 'ISO_8859_1' - - @Internal - Closure keyTransformer = IDENTITY - - @Override - boolean canTransformResource(FileTreeElement element) { - def path = element.relativePath.pathString - if (mappings.containsKey(path)) return true - for (key in mappings.keySet()) { - if (path =~ /$key/) return true - } - - if (path in paths) return true - for (p in paths) { - if (path =~ /$p/) return true - } - - !mappings && !paths && path.endsWith(PROPERTIES_SUFFIX) - } - - @Override - void transform(TransformerContext context) { - Properties props = propertiesEntries[context.path] - Properties incoming = loadAndTransformKeys(context.inputStream) - if (props == null) { - propertiesEntries[context.path] = incoming - } else { - incoming.each { key, value -> - if (props.containsKey(key)) { - switch (mergeStrategyFor(context.path).toLowerCase()) { - case 'latest': - props.put(key, value) - break - case 'append': - props.put(key, props.getProperty(key) + mergeSeparatorFor(context.path) + value) - break - case 'first': - default: - // continue - break - } - } else { - props.put(key, value) - } - } - } - } - - private Properties loadAndTransformKeys(InputStream is) { - Properties props = new CleanProperties() - // InputStream closed by caller, so we don't do it here. - if (is != null) { - props.load(new InputStreamReader(is, charset)) - } - return transformKeys(props) - } - - private Properties transformKeys(Properties properties) { - if (keyTransformer == IDENTITY) { - return properties - } - def result = new CleanProperties() - properties.each { key, value -> - result.put(keyTransformer.call(key), value) - } - return result - } - - private String mergeStrategyFor(String path) { - if (mappings.containsKey(path)) { - return mappings.get(path).mergeStrategy ?: mergeStrategy - } - for (key in mappings.keySet()) { - if (path =~ /$key/) { - return mappings.get(key).mergeStrategy ?: mergeStrategy - } - } - - return mergeStrategy - } - - private String mergeSeparatorFor(String path) { - if (mappings.containsKey(path)) { - return mappings.get(path).mergeSeparator ?: mergeSeparator - } - for (key in mappings.keySet()) { - if (path =~ /$key/) { - return mappings.get(key).mergeSeparator ?: mergeSeparator - } - } - - return mergeSeparator - } - - @Override - boolean hasTransformedResource() { - propertiesEntries.size() > 0 - } - - @Override - void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { - // cannot close the writer as the OutputStream needs to remain open - def zipWriter = new OutputStreamWriter(os, charset) - propertiesEntries.each { String path, Properties props -> - ZipEntry entry = new ZipEntry(path) - entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) - os.putNextEntry(entry) - IOUtil.copy(readerFor(props, charset), zipWriter) - zipWriter.flush() - os.closeEntry() - } - } - - private static InputStreamReader readerFor(Properties props, String charset) { - ByteArrayOutputStream baos = new ByteArrayOutputStream() - baos.withWriter(charset) { w -> - props.store(w, '') - } - new InputStreamReader(new ByteArrayInputStream(baos.toByteArray()), charset) - } -}