Skip to content

Commit

Permalink
Kotlin migration part 7 (#1026)
Browse files Browse the repository at this point in the history
* Convert ServiceFileTransformer

* Convert ComponentsXmlResourceTransformer

* Simplify ComponentsXmlResourceTransformer
  • Loading branch information
Goooler authored Nov 20, 2024
1 parent 48a3482 commit 67ea28b
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 419 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.github.jengelman.gradle.plugins.shadow.transformers

import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext.Companion.getEntryTimestamp
import java.io.BufferedInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import org.apache.tools.zip.ZipEntry
import org.apache.tools.zip.ZipOutputStream
import org.codehaus.plexus.util.xml.XmlStreamReader
import org.codehaus.plexus.util.xml.XmlStreamWriter
import org.codehaus.plexus.util.xml.Xpp3Dom
import org.codehaus.plexus.util.xml.Xpp3DomBuilder
import org.codehaus.plexus.util.xml.Xpp3DomWriter
import org.gradle.api.file.FileTreeElement

/**
* A resource processor that aggregates plexus `components.xml` files.
*
* Modified from `org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer.java`
*
* @author John Engelman
*/
open class ComponentsXmlResourceTransformer : Transformer {
private val components = mutableMapOf<String, Xpp3Dom>()

override fun canTransformResource(element: FileTreeElement): Boolean {
return COMPONENTS_XML_PATH == element.relativePath.pathString
}

override fun transform(context: TransformerContext) {
val newDom = try {
val bis = object : BufferedInputStream(context.inputStream) {
@Throws(IOException::class)
override fun close() {
// leave ZIP open
}
}
Xpp3DomBuilder.build(XmlStreamReader(bis))
} catch (e: Exception) {
throw IOException("Error parsing components.xml in ${context.inputStream}", e)
}

// Only try to merge in components if there are some elements in the component-set
if (newDom.getChild("components") == null) return

val children = newDom.getChild("components").getChildren("component")
for (component in children) {
var role: String? = getValue(component, "role")
role = getRelocatedClass(role, context)
setValue(component, "role", role)

val roleHint = getValue(component, "role-hint")

var impl: String? = getValue(component, "implementation")
impl = getRelocatedClass(impl, context)
setValue(component, "implementation", impl)

val key = "$role:$roleHint"
// TODO: use the tools in Plexus to merge these properly. For now, I just need an all-or-nothing
// configuration carry over
components[key]?.getChild("configuration")?.let {
component.addChild(it)
}

val requirements = component.getChild("requirements")
if (requirements != null && requirements.childCount > 0) {
for (r in requirements.childCount - 1 downTo 0) {
val requirement = requirements.getChild(r)
var requiredRole: String? = getValue(requirement, "role")
requiredRole = getRelocatedClass(requiredRole, context)
setValue(requirement, "role", requiredRole)
}
}
components[key] = component
}
}

override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) {
val entry = ZipEntry(COMPONENTS_XML_PATH)
entry.time = getEntryTimestamp(preserveFileTimestamps, entry.time)
os.putNextEntry(entry)

transformedResource.inputStream().use {
it.copyTo(os)
}
components.clear()
}

override fun hasTransformedResource(): Boolean = components.isNotEmpty()

@get:Throws(IOException::class)
private val transformedResource: ByteArray
get() {
val os = ByteArrayOutputStream(1024 * 4)
XmlStreamWriter(os).use { writer ->
val dom = Xpp3Dom("component-set")
val componentDom = Xpp3Dom("components")
dom.addChild(componentDom)
for (component in components.values) {
componentDom.addChild(component)
}
Xpp3DomWriter.write(writer, dom)
}
return os.toByteArray()
}

companion object {
const val COMPONENTS_XML_PATH: String = "META-INF/plexus/components.xml"

private fun getRelocatedClass(className: String?, context: TransformerContext): String? {
val stats = context.stats
if (!className.isNullOrEmpty()) {
for (relocator in context.relocators) {
if (relocator.canRelocateClass(className)) {
val relocateClassContext = RelocateClassContext(className, stats)
return relocator.relocateClass(relocateClassContext)
}
}
}
return className
}

private fun getValue(dom: Xpp3Dom, element: String): String {
return dom.getChild(element).value.orEmpty()
}

private fun setValue(dom: Xpp3Dom, element: String, value: String?) {
val child = dom.getChild(element)
if (child == null || value.isNullOrEmpty()) return
child.value = value
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.github.jengelman.gradle.plugins.shadow.transformers

import com.github.jengelman.gradle.plugins.shadow.relocation.RelocateClassContext
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowCopyAction
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
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.util.PatternFilterable
import org.gradle.api.tasks.util.PatternSet

/**
* Modified from `org.apache.maven.plugins.shade.resource.ServiceResourceTransformer.java`
*
* Resources transformer that appends entries in `META-INF/services` resources into
* a single resource. For example, if there are several `META-INF/services/org.apache.maven.project.ProjectBuilder`
* resources spread across many JARs the individual entries will all be concatenated into a single
* `META-INF/services/org.apache.maven.project.ProjectBuilder` resource packaged into the resultant JAR produced
* by the shading process.
*
* @author jvanzyl
* @author Charlie Knudsen
* @author John Engelman
*/
@CacheableTransformer
open class ServiceFileTransformer(
private val patternSet: PatternSet = PatternSet()
.include(SERVICES_PATTERN)
.exclude(GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATTERN),
) : Transformer,
PatternFilterable by patternSet {
private val serviceEntries = mutableMapOf<String, ServiceStream>()

override fun canTransformResource(element: FileTreeElement): Boolean {
val target = if (element is ShadowCopyAction.ArchiveFileTreeElement) element.asFileTreeElement() else element
return patternSet.asSpec.isSatisfiedBy(target)
}

override fun transform(context: TransformerContext) {
val lines = context.inputStream.bufferedReader().readLines().toMutableList()
var targetPath = context.path
context.relocators.forEach { rel ->
if (rel.canRelocateClass(File(targetPath).name)) {
val classContext = RelocateClassContext.builder().className(targetPath).stats(context.stats).build()
targetPath = rel.relocateClass(classContext)
}
lines.forEachIndexed { i, line ->
if (rel.canRelocateClass(line)) {
val lineContext = RelocateClassContext.builder().className(line).stats(context.stats).build()
lines[i] = rel.relocateClass(lineContext)
}
}
}
lines.forEach { line ->
serviceEntries[targetPath] = serviceEntries.getOrDefault(targetPath, ServiceStream()).apply {
append(ByteArrayInputStream(line.toByteArray()))
}
}
}

override fun hasTransformedResource(): Boolean = serviceEntries.isNotEmpty()

override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) {
serviceEntries.forEach { (path, stream) ->
val entry = ZipEntry(path)
entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time)
os.putNextEntry(entry)
stream.toInputStream().use {
it.copyTo(os)
}
os.closeEntry()
}
}

/**
* {@inheritDoc}
*/
@Input
override fun getIncludes(): Set<String> = patternSet.includes

/**
* {@inheritDoc}
*/
@Input
override fun getExcludes(): Set<String> = patternSet.excludes

open fun setPath(path: String): PatternFilterable = apply {
patternSet.setIncludes(listOf("$path/**"))
}

open class ServiceStream : ByteArrayOutputStream(1024) {
@Throws(IOException::class)
open fun append(inputStream: InputStream) {
if (count > 0 && buf[count - 1] != '\n'.code.toByte() && buf[count - 1] != '\r'.code.toByte()) {
val newline = "\n".toByteArray()
write(newline, 0, newline.size)
}
inputStream.use {
it.copyTo(this)
}
}

open fun toInputStream(): InputStream = ByteArrayInputStream(buf, 0, count)
}

private companion object {
private const val SERVICES_PATTERN = "META-INF/services/**"
private const val GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATTERN =
"META-INF/services/org.codehaus.groovy.runtime.ExtensionModule"
}
}
Loading

0 comments on commit 67ea28b

Please sign in to comment.