mirror of
https://github.com/corda/corda.git
synced 2025-02-20 09:26:41 +00:00
CORDA-1816: Migrate JarFilter plugin to corda-gradle-plugins. (#3651)
This commit is contained in:
parent
7466463b89
commit
fe9b89369f
@ -99,6 +99,7 @@ buildscript {
|
||||
classpath "net.corda.plugins:cordformation:$gradle_plugins_version"
|
||||
classpath "net.corda.plugins:cordapp:$gradle_plugins_version"
|
||||
classpath "net.corda.plugins:api-scanner:$gradle_plugins_version"
|
||||
classpath "net.corda.plugins:jar-filter:$gradle_plugins_version"
|
||||
classpath "net.sf.proguard:proguard-gradle:$proguard_version"
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
|
||||
|
@ -4,21 +4,8 @@ buildscript {
|
||||
|
||||
ext {
|
||||
guava_version = constants.getProperty("guavaVersion")
|
||||
kotlin_version = constants.getProperty("kotlinVersion")
|
||||
proguard_version = constants.getProperty("proguardVersion")
|
||||
assertj_version = '3.9.1'
|
||||
junit_version = '4.12'
|
||||
asm_version = '6.2'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "net.sf.proguard:proguard-gradle:$proguard_version"
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,23 +15,9 @@ repositories {
|
||||
}
|
||||
|
||||
allprojects {
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
languageVersion = "1.2"
|
||||
apiVersion = "1.2"
|
||||
jvmTarget = "1.8"
|
||||
javaParameters = true // Useful for reflection.
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
// Prevent the project from creating temporary files outside of the build directory.
|
||||
systemProperty 'java.io.tmpdir', buildDir.absolutePath
|
||||
|
||||
// Tell the tests where Gradle's current module cache is.
|
||||
// We need the tests to share this module cache to prevent the
|
||||
// Gradle Test-Kit from downloading its own copy of Kotlin etc.
|
||||
systemProperty 'test.gradle.user.home', project.gradle.gradleUserHomeDir
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,218 +0,0 @@
|
||||
# JarFilter
|
||||
|
||||
Deletes annotated elements at the byte-code level from a JAR of Java/Kotlin code. In the case of Kotlin
|
||||
code, it also modifies the `@kotlin.Metadata` annotations not to contain any functions, properties or
|
||||
type aliases that have been deleted. This prevents the Kotlin compiler from successfully compiling against
|
||||
any elements which no longer exist.
|
||||
|
||||
We use this plugin together with ProGuard to generate Corda's `core-deterministic` and `serialization-deterministic`
|
||||
modules. See [here](../../docs/source/deterministic-modules.rst) for more information.
|
||||
|
||||
## Usage
|
||||
This plugin is automatically available on Gradle's classpath since it lives in Corda's `buildSrc` directory.
|
||||
You need only `import` the plugin's task classes in the `build.gradle` file and then use them to declare
|
||||
tasks.
|
||||
|
||||
You can enable the tasks' logging output using Gradle's `--info` or `--debug` command-line options.
|
||||
|
||||
### The `JarFilter` task
|
||||
The `JarFilter` task removes unwanted elements from `class` files, namely:
|
||||
- Deleting both Java methods/fields and Kotlin functions/properties/type aliases.
|
||||
- Stubbing out methods by replacing the byte-code of their implementations.
|
||||
- Removing annotations from classes/methods/fields.
|
||||
|
||||
It supports the following configuration options:
|
||||
```gradle
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
// Task(s) whose JAR outputs should be filtered.
|
||||
jars jar
|
||||
|
||||
// The annotations assigned to each filtering role. For example:
|
||||
annotations {
|
||||
forDelete = [
|
||||
"org.testing.DeleteMe"
|
||||
]
|
||||
forStub = [
|
||||
"org.testing.StubMeOut"
|
||||
]
|
||||
forRemove = [
|
||||
"org.testing.RemoveMe"
|
||||
]
|
||||
}
|
||||
|
||||
// Location for filtered JARs. Defaults to "$buildDir/filtered-libs".
|
||||
outputDir file(...)
|
||||
|
||||
// Whether the timestamps on the JARs' entries should be preserved "as is"
|
||||
// or set to a platform-independent constant value (1st February 1980).
|
||||
preserveTimestamps = {true|false}
|
||||
|
||||
// The maximum number of times (>= 1) to pass the JAR through the filter.
|
||||
maxPasses = 5
|
||||
|
||||
// Writes more information about each pass of the filter.
|
||||
verbose = {true|false}
|
||||
}
|
||||
```
|
||||
|
||||
You can specify as many annotations for each role as you like. The only constraint is that a given
|
||||
annotation cannot be assigned to more than one role.
|
||||
|
||||
#### Removing unwanted default parameter values
|
||||
It is possible to assign non-deterministic expressions as default values for Kotlin constructors and functions. For
|
||||
example:
|
||||
```kotlin
|
||||
data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID())
|
||||
```
|
||||
|
||||
The Kotlin compiler will generate _two_ constructors in this case:
|
||||
```
|
||||
UniqueIdentifier(String?, UUID)
|
||||
UniqueIdentifier(String?, UUID, Int, DefaultConstructorMarker)
|
||||
```
|
||||
|
||||
The first constructor is the primary constructor that we would expect (and which we'd like to keep), whereas the
|
||||
second is a public synthetic constructor that Kotlin applications invoke to handle the different combinations of
|
||||
default parameter values. Unfortunately, this synthetic constructor is therefore also part of the Kotlin ABI and
|
||||
so we _cannot_ rewrite the class like this to remove the default values:
|
||||
```kotlin
|
||||
// THIS REFACTOR WOULD BREAK THE KOTLIN ABI!
|
||||
data class UniqueIdentifier(val externalId: String?, val id: UUID) {
|
||||
constructor(externalId: String?) : this(externalId, UUID.randomUUID())
|
||||
constructor() : this(null)
|
||||
}
|
||||
```
|
||||
|
||||
The refactored class would have the following constructors, and would require client applications to be recompiled:
|
||||
```
|
||||
UniqueIdentifier(String?, UUID)
|
||||
UniqueIdentifier(String?)
|
||||
UniqueIdentifier()
|
||||
```
|
||||
|
||||
We therefore need to keep the default constructor parameters in order to preserve the ABI for the unfiltered code,
|
||||
which in turn means that `JarFilter` will need to delete only the synthetic constructor and leave the primary
|
||||
constructor intact. However, Kotlin does not currently allow us to annotate _specific_ constructors - see
|
||||
[KT-22524](https://youtrack.jetbrains.com/issue/KT-22524). Until it does, `JarFilter` will perform an initial
|
||||
"sanitising" pass over the JAR file to remove any unwanted annotations from the primary constructors. These unwanted
|
||||
annotations are configured in the `JarFilter` task definition:
|
||||
```gradle
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
...
|
||||
annotations {
|
||||
...
|
||||
forSanitise = [
|
||||
"org.testing.DeleteMe"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows us to annotate the `UniqueIdentifier` class like this:
|
||||
```kotlin
|
||||
data class UniqueIdentifier @DeleteMe constructor(val externalId: String? = null, val id: UUID = UUID.randomUUID())
|
||||
```
|
||||
|
||||
to generate these constructors:
|
||||
```
|
||||
UniqueIdentifier(String?, UUID)
|
||||
@DeleteMe UniqueIdentifier(String?, UUID, Int, DefaultConstructorMarker)
|
||||
```
|
||||
|
||||
We currently **do not** sanitise annotations from functions with default parameter values, although (in theory) these
|
||||
may also be non-deterministic. We will need to extend the sanitation pass to include such functions if/when the need
|
||||
arises. At the moment, deleting such functions _entirely_ is enough, whereas also deleting a primary constructor means
|
||||
that we can no longer create instances of that class either.
|
||||
|
||||
### The `MetaFixer` task
|
||||
The `MetaFixer` task updates the `@kotlin.Metadata` annotations by removing references to any functions,
|
||||
constructors, properties or nested classes that no longer exist in the byte-code. This is primarily to
|
||||
"repair" Kotlin library code that has been processed by ProGuard.
|
||||
|
||||
Kotlin type aliases exist only inside `@Metadata` and so are unaffected by this task. Similarly, the
|
||||
constructors for Kotlin's annotation classes don't exist in the byte-code either because Java annotations
|
||||
are interfaces really. The `MetaFixer` task will therefore ignore annotations' constructors too.
|
||||
|
||||
It supports these configuration options:
|
||||
```gradle
|
||||
import net.corda.gradle.jarfilter.MetaFixerTask
|
||||
task metafix(type: MetaFixerTask) {
|
||||
// Task(s) whose JAR outputs should be fixed.
|
||||
jars jar
|
||||
|
||||
// Location for fixed JARs. Defaults to "$buildDir/metafixed-libs"
|
||||
outputDir file(...)
|
||||
|
||||
// Tag to be appended to the JAR name. Defaults to "-metafixed".
|
||||
suffix = "..."
|
||||
|
||||
// Whether the timestamps on the JARs' entries should be preserved "as is"
|
||||
// or set to a platform-independent constant value (1st February 1980).
|
||||
preserveTimestamps = {true|false}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Code Coverage
|
||||
You can generate a JaCoCo code coverage report for the unit tests using:
|
||||
```bash
|
||||
$ cd buildSrc
|
||||
$ ../gradlew jarfilter:jacocoTestReport
|
||||
```
|
||||
|
||||
### Kotlin Metadata
|
||||
The Kotlin compiler encodes information about each class inside its `@kotlin.Metadata` annotation.
|
||||
|
||||
```kotlin
|
||||
import kotlin.annotation.AnnotationRetention.*
|
||||
|
||||
@Retention(RUNTIME)
|
||||
annotation class Metadata {
|
||||
val k: Int = 1
|
||||
val d1: Array<String> = []
|
||||
val d2: Array<String> = []
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This is an internal feature of Kotlin which is read by Kotlin Reflection. There is no public API
|
||||
for writing this information, and the content format of arrays `d1` and `d2` depends upon the
|
||||
"class kind" `k`. For the kinds that we are interested in, `d1` contains a buffer of ProtoBuf
|
||||
data and `d2` contains an array of `String` identifiers which the ProtoBuf data refers to by index.
|
||||
|
||||
Although ProtoBuf generates functions for both reading and writing the data buffer, the
|
||||
Kotlin Reflection artifact only contains the functions for reading. This is almost certainly
|
||||
because the writing functionality has been removed from the `kotlin-reflect` JAR using
|
||||
ProGuard. However, the complete set of generated ProtoBuf classes is still available in the
|
||||
`kotlin-compiler-embeddable` JAR. The `jarfilter:kotlin-metadata` module uses ProGuard to
|
||||
extracts these classes into a new `kotlin-metdata` JAR, discarding any classes that the
|
||||
ProtoBuf ones do not need and obfuscating any other ones that they do.
|
||||
|
||||
The custom `kotlin-metadata` object was originally created as a workaround for
|
||||
[KT-18621](https://youtrack.jetbrains.com/issue/KT-18621). However, reducing the number of unwanted
|
||||
classes on the classpath anyway can only be a Good Thing<sup>(TM)</sup>.
|
||||
|
||||
At runtime, `JarFilter` decompiles the ProtoBuf buffer into POJOs, deletes the elements that
|
||||
no longer exist in the byte-code and then recompiles the POJOs into a new ProtoBuf buffer. The
|
||||
`@Metadata` annotation is then rewritten using this new buffer for `d1` and the _original_ `String`
|
||||
identifiers for `d2`. While some of these identifiers are very likely no longer used after this,
|
||||
removing them would also require re-indexing the ProtoBuf data. It is therefore simpler just to
|
||||
leave them as harmless cruft in the byte-code's constant pool.
|
||||
|
||||
The majority of `JarFilter`'s unit tests use Kotlin and Java reflection and so should not be
|
||||
brittle as Kotlin evolves because `kotlin-reflect` is public API. Also, Kotlin's requirement that
|
||||
it remain backwards-compatible with itself should imply that the ProtoBuf logic shouldn't change
|
||||
(much). However, the ProtoBuf classes are still internal to Kotlin and so it _is_ possible that they
|
||||
will occasionally move between packages. This has already happened for Kotlin 1.2.3x -> 1.2.4x, but
|
||||
I am hoping this means that they will not move again for a while.
|
||||
|
||||
### JARs vs ZIPs
|
||||
The `JarFilter` and `MetaFixer` tasks _deliberately_ use `ZipFile` and `ZipOutputStream` rather
|
||||
than `JarInputStream` and `JarOutputStream` when reading and writing their JAR files. This is to
|
||||
ensure that the original `META-INF/MANIFEST.MF` files are passed through unaltered. Note also that
|
||||
there is no `ZipInputStream.getComment()` method, and so we need to use `ZipFile` in order to
|
||||
preserve any JAR comments.
|
||||
|
||||
Neither `JarFilter` nor `MetaFixer` should change the order of the entries inside the JAR files.
|
@ -1,46 +0,0 @@
|
||||
plugins {
|
||||
id 'java-gradle-plugin'
|
||||
id 'jacoco'
|
||||
}
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
jarFilterPlugin {
|
||||
id = 'net.corda.plugins.jar-filter'
|
||||
implementationClass = 'net.corda.gradle.jarfilter.JarFilterPlugin'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
jacocoRuntime
|
||||
}
|
||||
|
||||
processTestResources {
|
||||
filesMatching('**/build.gradle') {
|
||||
expand(['kotlin_version': kotlin_version])
|
||||
}
|
||||
filesMatching('gradle.properties') {
|
||||
expand(['jacocoAgent': configurations.jacocoRuntime.asPath.replace('\\', '/'),
|
||||
'buildDir': buildDir])
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation project(':jarfilter:kotlin-metadata')
|
||||
implementation "org.ow2.asm:asm:$asm_version"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
testImplementation "org.assertj:assertj-core:$assertj_version"
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation project(':jarfilter:unwanteds')
|
||||
jacocoRuntime "org.jacoco:org.jacoco.agent:${jacoco.toolVersion}:runtime"
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
plugins {
|
||||
id 'base'
|
||||
}
|
||||
|
||||
description "Kotlin's metadata-handling classes"
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
configurations {
|
||||
proguard
|
||||
runtime
|
||||
configurations.default.extendsFrom runtime
|
||||
}
|
||||
|
||||
dependencies {
|
||||
proguard "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlin_version"
|
||||
proguard "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
runtime "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
}
|
||||
|
||||
def javaHome = System.getProperty('java.home')
|
||||
def originalJar = configurations.proguard.files.find { it.name.startsWith("kotlin-compiler-embeddable") }
|
||||
|
||||
import proguard.gradle.ProGuardTask
|
||||
task metadata(type: ProGuardTask) {
|
||||
injars originalJar, filter: 'META-INF/MANIFEST.MF,META-INF/metadata*.kotlin_module,**.class'
|
||||
outjars "$buildDir/libs/${project.name}-${kotlin_version}.jar"
|
||||
|
||||
libraryjars "$javaHome/lib/rt.jar"
|
||||
libraryjars "$javaHome/../lib/tools.jar"
|
||||
configurations.proguard.forEach {
|
||||
if (originalJar != it) {
|
||||
libraryjars it.path, filter: '!META-INF/versions/**'
|
||||
}
|
||||
}
|
||||
|
||||
keepattributes '*'
|
||||
dontoptimize
|
||||
verbose
|
||||
|
||||
dontwarn 'com.sun.jna.**'
|
||||
dontwarn 'org.jetbrains.annotations.**'
|
||||
dontwarn 'org.jetbrains.kotlin.com.intellij.**'
|
||||
dontwarn 'org.jetbrains.kotlin.com.google.j2objc.annotations.**'
|
||||
dontwarn 'org.jetbrains.kotlin.com.google.errorprone.annotations.**'
|
||||
dontnote
|
||||
|
||||
keep 'class org.jetbrains.kotlin.load.java.JvmAnnotationNames { *; }'
|
||||
keep 'class org.jetbrains.kotlin.metadata.ProtoBuf { *; }', includedescriptorclasses: true
|
||||
keep 'class org.jetbrains.kotlin.metadata.ProtoBuf$* { *; }', includedescriptorclasses: true
|
||||
keep 'class org.jetbrains.kotlin.metadata.deserialization.** { *; }', includedescriptorclasses: true
|
||||
keep 'class org.jetbrains.kotlin.metadata.jvm.** { *; }', includedescriptorclasses: true
|
||||
keep 'class org.jetbrains.kotlin.protobuf.** { *; }', includedescriptorclasses: true
|
||||
}
|
||||
def metadataJar = metadata.outputs.files.singleFile
|
||||
|
||||
task validate(type: ProGuardTask) {
|
||||
injars metadataJar
|
||||
libraryjars "$javaHome/lib/rt.jar"
|
||||
configurations.runtime.forEach {
|
||||
libraryjars it.path, filter: '!META-INF/versions/**'
|
||||
}
|
||||
|
||||
keepattributes '*'
|
||||
dontpreverify
|
||||
dontobfuscate
|
||||
dontoptimize
|
||||
verbose
|
||||
|
||||
dontwarn 'org.jetbrains.kotlin.com.google.errorprone.annotations.**'
|
||||
dontnote
|
||||
|
||||
keep 'class *'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
'default' file: metadataJar, name: project.name, type: 'jar', extension: 'jar', builtBy: metadata
|
||||
}
|
||||
|
||||
defaultTasks "metadata"
|
||||
metadata.finalizedBy validate
|
@ -1,181 +0,0 @@
|
||||
@file:JvmName("Elements")
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.deserialization.Flags.*
|
||||
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
|
||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||
import org.jetbrains.kotlin.metadata.deserialization.returnType
|
||||
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.ClassMapperLite
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
||||
import org.objectweb.asm.Opcodes.ACC_SYNTHETIC
|
||||
import java.util.*
|
||||
|
||||
private const val DEFAULT_CONSTRUCTOR_MARKER = "ILkotlin/jvm/internal/DefaultConstructorMarker;"
|
||||
private const val DEFAULT_FUNCTION_MARKER = "ILjava/lang/Object;"
|
||||
private const val DUMMY_PASSES = 1
|
||||
|
||||
private val DECLARES_DEFAULT_VALUE_MASK: Int = DECLARES_DEFAULT_VALUE.toFlags(true).inv()
|
||||
|
||||
abstract class Element(val name: String, val descriptor: String) {
|
||||
private var lifetime: Int = DUMMY_PASSES
|
||||
|
||||
open val isExpired: Boolean get() = --lifetime < 0
|
||||
}
|
||||
|
||||
|
||||
class MethodElement(name: String, descriptor: String, val access: Int = 0) : Element(name, descriptor) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
other as MethodElement
|
||||
return other.name == name && other.descriptor == descriptor
|
||||
}
|
||||
override fun hashCode(): Int = Objects.hash(name, descriptor)
|
||||
override fun toString(): String = "MethodElement[name=$name, descriptor=$descriptor, access=$access]"
|
||||
override val isExpired: Boolean get() = access == 0 && super.isExpired
|
||||
val isConstructor: Boolean get() = isObjectConstructor || isClassConstructor
|
||||
val isClassConstructor: Boolean get() = name == "<clinit>"
|
||||
val isObjectConstructor: Boolean get() = name == "<init>"
|
||||
val isVoidFunction: Boolean get() = !isConstructor && descriptor.endsWith(")V")
|
||||
|
||||
private val suffix: String
|
||||
val visibleName: String
|
||||
val signature: String = name + descriptor
|
||||
|
||||
init {
|
||||
val idx = name.indexOf('$')
|
||||
visibleName = if (idx == -1) name else name.substring(0, idx)
|
||||
suffix = if (idx == -1) "" else name.drop(idx + 1)
|
||||
}
|
||||
|
||||
fun isKotlinSynthetic(vararg tags: String): Boolean = (access and ACC_SYNTHETIC) != 0 && tags.contains(suffix)
|
||||
fun asKotlinNonDefaultConstructor(): MethodElement? {
|
||||
val markerIdx = descriptor.indexOf(DEFAULT_CONSTRUCTOR_MARKER)
|
||||
return if (markerIdx >= 0) {
|
||||
MethodElement(name, descriptor.removeRange(markerIdx, markerIdx + DEFAULT_CONSTRUCTOR_MARKER.length))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A class cannot have two fields with the same name but different types. However,
|
||||
* it can define extension functions and properties.
|
||||
*/
|
||||
class FieldElement(name: String, descriptor: String = "?", val extension: String = "()") : Element(name, descriptor) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
other as FieldElement
|
||||
return other.name == name && other.extension == extension
|
||||
}
|
||||
override fun hashCode(): Int = Objects.hash(name, extension)
|
||||
override fun toString(): String = "FieldElement[name=$name, descriptor=$descriptor, extension=$extension]"
|
||||
override val isExpired: Boolean get() = descriptor == "?" && super.isExpired
|
||||
}
|
||||
|
||||
val String.extensionType: String get() = substring(0, 1 + indexOf(')'))
|
||||
|
||||
/**
|
||||
* Returns a fully-qualified class name as it would exist
|
||||
* in the byte-code, e.g. as "a/b/c/ClassName$Nested".
|
||||
*/
|
||||
fun NameResolver.getClassInternalName(idx: Int): String
|
||||
= getQualifiedClassName(idx).replace('.', '$')
|
||||
|
||||
/**
|
||||
* Construct the signatures of the synthetic methods that
|
||||
* Kotlin would create to handle default parameter values.
|
||||
*/
|
||||
fun String.toKotlinDefaultConstructor(): String {
|
||||
val closer = lastIndexOf(')')
|
||||
return substring(0, closer) + DEFAULT_CONSTRUCTOR_MARKER + substring(closer)
|
||||
}
|
||||
|
||||
fun String.toKotlinDefaultFunction(classDescriptor: String): String {
|
||||
val opener = indexOf('(')
|
||||
val closer = lastIndexOf(')')
|
||||
return (substring(0, opener) + "\$default("
|
||||
+ classDescriptor + substring(opener + 1, closer) + DEFAULT_FUNCTION_MARKER
|
||||
+ substring(closer))
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Kotlin getter/setter method data to [MethodElement] objects.
|
||||
*/
|
||||
internal fun JvmProtoBuf.JvmPropertySignature.toGetter(nameResolver: NameResolver): MethodElement? {
|
||||
return if (hasGetter()) { getter?.toMethodElement(nameResolver) } else { null }
|
||||
}
|
||||
|
||||
internal fun JvmProtoBuf.JvmPropertySignature.toSetter(nameResolver: NameResolver): MethodElement? {
|
||||
return if (hasSetter()) { setter?.toMethodElement(nameResolver) } else { null }
|
||||
}
|
||||
|
||||
internal fun JvmProtoBuf.JvmMethodSignature.toMethodElement(nameResolver: NameResolver)
|
||||
= MethodElement(nameResolver.getString(name), nameResolver.getString(desc))
|
||||
|
||||
/**
|
||||
* This logic is based heavily on [JvmProtoBufUtil.getJvmFieldSignature].
|
||||
*/
|
||||
internal fun JvmProtoBuf.JvmPropertySignature.toFieldElement(property: ProtoBuf.Property, nameResolver: NameResolver, typeTable: TypeTable): FieldElement {
|
||||
var nameId = property.name
|
||||
var descId = -1
|
||||
|
||||
if (hasField()) {
|
||||
if (field.hasName()) {
|
||||
nameId = field.name
|
||||
}
|
||||
if (field.hasDesc()) {
|
||||
descId = field.desc
|
||||
}
|
||||
}
|
||||
|
||||
val descriptor = if (descId == -1) {
|
||||
val returnType = property.returnType(typeTable)
|
||||
if (returnType.hasClassName()) {
|
||||
ClassMapperLite.mapClass(nameResolver.getQualifiedClassName(returnType.className))
|
||||
} else {
|
||||
"?"
|
||||
}
|
||||
} else {
|
||||
nameResolver.getString(descId)
|
||||
}
|
||||
|
||||
return FieldElement(nameResolver.getString(nameId), descriptor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites metadata for function and constructor parameters.
|
||||
*/
|
||||
internal fun ProtoBuf.Constructor.Builder.updateValueParameters(
|
||||
updater: (ProtoBuf.ValueParameter) -> ProtoBuf.ValueParameter
|
||||
): ProtoBuf.Constructor.Builder {
|
||||
for (idx in 0 until valueParameterList.size) {
|
||||
setValueParameter(idx, updater(valueParameterList[idx]))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun ProtoBuf.Function.Builder.updateValueParameters(
|
||||
updater: (ProtoBuf.ValueParameter) -> ProtoBuf.ValueParameter
|
||||
): ProtoBuf.Function.Builder {
|
||||
for (idx in 0 until valueParameterList.size) {
|
||||
setValueParameter(idx, updater(valueParameterList[idx]))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun ProtoBuf.ValueParameter.clearDeclaresDefaultValue(): ProtoBuf.ValueParameter {
|
||||
return if (DECLARES_DEFAULT_VALUE.get(flags)) {
|
||||
toBuilder().setFlags(flags and DECLARES_DEFAULT_VALUE_MASK).build()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
internal val List<ProtoBuf.ValueParameter>.hasAnyDefaultValues
|
||||
get() = any { DECLARES_DEFAULT_VALUE.get(it.flags) }
|
@ -1,356 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.InvalidUserDataException
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.objectweb.asm.*
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
|
||||
/**
|
||||
* ASM [ClassVisitor] for the JarFilter task that deletes unwanted class elements.
|
||||
* The unwanted elements have been annotated in advance. Elements that reference
|
||||
* unwanted elements are also removed to keep the byte-code consistent. Finally,
|
||||
* the deleted elements are passed to the [MetadataTransformer] so that they can
|
||||
* be removed from the [kotlin.Metadata] annotation.
|
||||
*
|
||||
* This Visitor is applied to the byte-code repeatedly until it has removed
|
||||
* everything that is no longer wanted.
|
||||
*/
|
||||
class FilterTransformer private constructor (
|
||||
visitor: ClassVisitor,
|
||||
logger: Logger,
|
||||
kotlinMetadata: MutableMap<String, List<String>>,
|
||||
private val removeAnnotations: Set<String>,
|
||||
private val deleteAnnotations: Set<String>,
|
||||
private val stubAnnotations: Set<String>,
|
||||
private val unwantedElements: UnwantedCache,
|
||||
private val unwantedFields: MutableSet<FieldElement>,
|
||||
private val deletedMethods: MutableSet<MethodElement>,
|
||||
private val stubbedMethods: MutableSet<MethodElement>
|
||||
) : KotlinAfterProcessor(ASM6, visitor, logger, kotlinMetadata), Repeatable<FilterTransformer> {
|
||||
constructor(
|
||||
visitor: ClassVisitor,
|
||||
logger: Logger,
|
||||
removeAnnotations: Set<String>,
|
||||
deleteAnnotations: Set<String>,
|
||||
stubAnnotations: Set<String>,
|
||||
unwantedElements: UnwantedCache
|
||||
) : this(
|
||||
visitor = visitor,
|
||||
logger = logger,
|
||||
kotlinMetadata = mutableMapOf(),
|
||||
removeAnnotations = removeAnnotations,
|
||||
deleteAnnotations = deleteAnnotations,
|
||||
stubAnnotations = stubAnnotations,
|
||||
unwantedElements = unwantedElements,
|
||||
unwantedFields = mutableSetOf(),
|
||||
deletedMethods = mutableSetOf(),
|
||||
stubbedMethods = mutableSetOf()
|
||||
)
|
||||
|
||||
var className: String = "(unknown)"
|
||||
private set
|
||||
|
||||
val isUnwantedClass: Boolean get() = isUnwantedClass(className)
|
||||
override val hasUnwantedElements: Boolean
|
||||
get() = unwantedFields.isNotEmpty()
|
||||
|| deletedMethods.isNotEmpty()
|
||||
|| stubbedMethods.isNotEmpty()
|
||||
|| super.hasUnwantedElements
|
||||
|
||||
private fun isUnwantedClass(name: String): Boolean = unwantedElements.containsClass(name)
|
||||
private fun hasDeletedSyntheticMethod(name: String): Boolean = deletedMethods.any { method ->
|
||||
name.startsWith("$className\$${method.visibleName}\$")
|
||||
}
|
||||
|
||||
override fun recreate(visitor: ClassVisitor) = FilterTransformer(
|
||||
visitor = visitor,
|
||||
logger = logger,
|
||||
kotlinMetadata = kotlinMetadata,
|
||||
removeAnnotations = removeAnnotations,
|
||||
deleteAnnotations = deleteAnnotations,
|
||||
stubAnnotations = stubAnnotations,
|
||||
unwantedElements = unwantedElements,
|
||||
unwantedFields = unwantedFields,
|
||||
deletedMethods = deletedMethods,
|
||||
stubbedMethods = stubbedMethods
|
||||
)
|
||||
|
||||
override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array<String>?) {
|
||||
className = clsName
|
||||
logger.info("Class {}", clsName)
|
||||
super.visit(version, access, clsName, signature, superName, interfaces)
|
||||
}
|
||||
|
||||
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
|
||||
if (removeAnnotations.contains(descriptor)) {
|
||||
logger.info("- Removing annotation {}", descriptor)
|
||||
return null
|
||||
} else if (deleteAnnotations.contains(descriptor)) {
|
||||
if (unwantedElements.addClass(className)) {
|
||||
logger.info("- Identified class {} as unwanted", className)
|
||||
}
|
||||
}
|
||||
return super.visitAnnotation(descriptor, visible)
|
||||
}
|
||||
|
||||
override fun visitField(access: Int, fieldName: String, descriptor: String, signature: String?, value: Any?): FieldVisitor? {
|
||||
val field = FieldElement(fieldName, descriptor)
|
||||
logger.debug("--- field ---> {}", field)
|
||||
if (unwantedFields.contains(field)) {
|
||||
logger.info("- Deleted field {},{}", field.name, field.descriptor)
|
||||
unwantedFields.remove(field)
|
||||
return null
|
||||
}
|
||||
val fv = super.visitField(access, fieldName, descriptor, signature, value) ?: return null
|
||||
return UnwantedFieldAdapter(fv, field)
|
||||
}
|
||||
|
||||
override fun visitMethod(access: Int, methodName: String, descriptor: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
|
||||
val method = MethodElement(methodName, descriptor, access)
|
||||
logger.debug("--- method ---> {}", method)
|
||||
if (deletedMethods.contains(method)) {
|
||||
logger.info("- Deleted method {}{}", method.name, method.descriptor)
|
||||
unwantedElements.addMethod(className, method)
|
||||
deletedMethods.remove(method)
|
||||
return null
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the byte-code for the method's prototype, then check whether
|
||||
* we need to replace the method's body with our "stub" code.
|
||||
*/
|
||||
val mv = super.visitMethod(access, methodName, descriptor, signature, exceptions) ?: return null
|
||||
if (stubbedMethods.contains(method)) {
|
||||
logger.info("- Stubbed out method {}{}", method.name, method.descriptor)
|
||||
stubbedMethods.remove(method)
|
||||
return if (method.isVoidFunction) VoidStubMethodAdapter(mv) else ThrowingStubMethodAdapter(mv)
|
||||
}
|
||||
|
||||
return UnwantedMethodAdapter(mv, method)
|
||||
}
|
||||
|
||||
override fun visitInnerClass(clsName: String, outerName: String?, innerName: String?, access: Int) {
|
||||
logger.debug("--- inner class {} [outer: {}, inner: {}]", clsName, outerName, innerName)
|
||||
if (isUnwantedClass || hasDeletedSyntheticMethod(clsName)) {
|
||||
if (unwantedElements.addClass(clsName)) {
|
||||
logger.info("- Deleted inner class {}", clsName)
|
||||
}
|
||||
} else if (isUnwantedClass(clsName)) {
|
||||
logger.info("- Deleted reference to inner class: {}", clsName)
|
||||
} else {
|
||||
super.visitInnerClass(clsName, outerName, innerName, access)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitOuterClass(outerName: String, methodName: String?, methodDescriptor: String?) {
|
||||
logger.debug("--- outer class {} [enclosing method {},{}]", outerName, methodName, methodDescriptor)
|
||||
if (unwantedElements.containsMethod(outerName, methodName, methodDescriptor)) {
|
||||
if (unwantedElements.addClass(className)) {
|
||||
logger.info("- Identified class {} as unwanted by its outer class", className)
|
||||
}
|
||||
} else {
|
||||
super.visitOuterClass(outerName, methodName, methodDescriptor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
if (isUnwantedClass) {
|
||||
/*
|
||||
* Optimisation: Don't rewrite the Kotlin @Metadata
|
||||
* annotation if we're going to delete this class.
|
||||
*/
|
||||
kotlinMetadata.clear()
|
||||
}
|
||||
super.visitEnd()
|
||||
/*
|
||||
* Some elements were created based on unreliable information,
|
||||
* such as Kotlin @Metadata annotations. We cannot rely on
|
||||
* these actually existing in the bytecode, and so we expire
|
||||
* them after a fixed number of passes.
|
||||
*/
|
||||
deletedMethods.removeIf(MethodElement::isExpired)
|
||||
unwantedFields.removeIf(FieldElement::isExpired)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the deleted methods and fields from the Kotlin Class metadata.
|
||||
*/
|
||||
override fun processClassMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
val partitioned = deletedMethods.groupBy(MethodElement::isConstructor)
|
||||
val prefix = "$className$"
|
||||
return ClassMetadataTransformer(
|
||||
logger = logger,
|
||||
deletedFields = unwantedFields,
|
||||
deletedFunctions = partitioned[false] ?: emptyList(),
|
||||
deletedConstructors = partitioned[true] ?: emptyList(),
|
||||
deletedNestedClasses = unwantedElements.classes.filter { it.startsWith(prefix) }.map { it.drop(prefix.length) },
|
||||
deletedClasses = unwantedElements.classes,
|
||||
handleExtraMethod = ::delete,
|
||||
d1 = d1,
|
||||
d2 = d2)
|
||||
.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the deleted methods and fields from the Kotlin Package metadata.
|
||||
*/
|
||||
override fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
return PackageMetadataTransformer(
|
||||
logger = logger,
|
||||
deletedFields = unwantedFields,
|
||||
deletedFunctions = deletedMethods,
|
||||
handleExtraMethod = ::delete,
|
||||
d1 = d1,
|
||||
d2 = d2)
|
||||
.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function to mark extra methods for deletion.
|
||||
* This will override a request for stubbing.
|
||||
*/
|
||||
private fun delete(method: MethodElement) {
|
||||
if (deletedMethods.add(method) && stubbedMethods.remove(method)) {
|
||||
logger.warn("-- method {}{} will be deleted instead of stubbed out",
|
||||
method.name, method.descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyses the field to decide whether it should be deleted.
|
||||
*/
|
||||
private inner class UnwantedFieldAdapter(fv: FieldVisitor, private val field: FieldElement) : FieldVisitor(api, fv) {
|
||||
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
|
||||
if (removeAnnotations.contains(descriptor)) {
|
||||
logger.info("- Removing annotation {} from field {},{}", descriptor, field.name, field.descriptor)
|
||||
return null
|
||||
} else if (deleteAnnotations.contains(descriptor)) {
|
||||
if (unwantedFields.add(field)) {
|
||||
logger.info("- Identified field {},{} as unwanted", field.name, field.descriptor)
|
||||
}
|
||||
}
|
||||
return super.visitAnnotation(descriptor, visible)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyses the method to decide whether it should be deleted.
|
||||
*/
|
||||
private inner class UnwantedMethodAdapter(mv: MethodVisitor, private val method: MethodElement) : MethodVisitor(api, mv) {
|
||||
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
|
||||
if (removeAnnotations.contains(descriptor)) {
|
||||
logger.info("- Removing annotation {} from method {}{}", descriptor, method.name, method.descriptor)
|
||||
return null
|
||||
} else if (deleteAnnotations.contains(descriptor)) {
|
||||
if (deletedMethods.add(method)) {
|
||||
logger.info("- Identified method {}{} for deletion", method.name, method.descriptor)
|
||||
}
|
||||
if (method.isKotlinSynthetic("annotations")) {
|
||||
val extensionType = method.descriptor.extensionType
|
||||
if (unwantedFields.add(FieldElement(name = method.visibleName, extension = extensionType))) {
|
||||
logger.info("-- also identified property or typealias {},{} for deletion", method.visibleName, extensionType)
|
||||
}
|
||||
}
|
||||
} else if (stubAnnotations.contains(descriptor) && (method.access and ACC_ABSTRACT) == 0) {
|
||||
if (stubbedMethods.add(method)) {
|
||||
logger.info("- Identified method {}{} for stubbing out", method.name, method.descriptor)
|
||||
}
|
||||
}
|
||||
return super.visitAnnotation(descriptor, visible)
|
||||
}
|
||||
|
||||
override fun visitMethodInsn(opcode: Int, ownerName: String, methodName: String, descriptor: String, isInterface: Boolean) {
|
||||
if ((isUnwantedClass(ownerName) || (ownerName == className && deletedMethods.contains(MethodElement(methodName, descriptor))))
|
||||
&& !stubbedMethods.contains(method)) {
|
||||
if (deletedMethods.add(method)) {
|
||||
logger.info("- Unwanted invocation of method {},{}{} from method {}{}", ownerName, methodName, descriptor, method.name, method.descriptor)
|
||||
}
|
||||
}
|
||||
super.visitMethodInsn(opcode, ownerName, methodName, descriptor, isInterface)
|
||||
}
|
||||
|
||||
override fun visitFieldInsn(opcode: Int, ownerName: String, fieldName: String, descriptor: String) {
|
||||
if ((isUnwantedClass(ownerName) || (ownerName == className && unwantedFields.contains(FieldElement(fieldName, descriptor))))
|
||||
&& !stubbedMethods.contains(method)) {
|
||||
if (method.isConstructor) {
|
||||
when (opcode) {
|
||||
GETFIELD, GETSTATIC -> {
|
||||
when (descriptor) {
|
||||
"I", "S", "B", "C", "Z" -> visitIntInsn(BIPUSH, 0)
|
||||
"J" -> visitInsn(LCONST_0)
|
||||
"F" -> visitInsn(FCONST_0)
|
||||
"D" -> visitInsn(DCONST_0)
|
||||
else -> visitInsn(ACONST_NULL)
|
||||
}
|
||||
}
|
||||
PUTFIELD, PUTSTATIC -> {
|
||||
when (descriptor) {
|
||||
"J", "D" -> visitInsn(POP2)
|
||||
else -> visitInsn(POP)
|
||||
}
|
||||
}
|
||||
else -> throw InvalidUserDataException("Unexpected opcode $opcode")
|
||||
}
|
||||
logger.info("- Unwanted reference to field {},{},{} REMOVED from constructor {}{}",
|
||||
ownerName, fieldName, descriptor, method.name, method.descriptor)
|
||||
return
|
||||
} else if (deletedMethods.add(method)) {
|
||||
logger.info("- Unwanted reference to field {},{},{} from method {}{}",
|
||||
ownerName, fieldName, descriptor, method.name, method.descriptor)
|
||||
}
|
||||
}
|
||||
super.visitFieldInsn(opcode, ownerName, fieldName, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write "stub" byte-code for this method, preserving its other annotations.
|
||||
* The method's original byte-code is discarded.
|
||||
*/
|
||||
private abstract inner class StubbingMethodAdapter(mv: MethodVisitor) : MethodVisitor(api, mv) {
|
||||
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
|
||||
return if (stubAnnotations.contains(descriptor)) null else mv.visitAnnotation(descriptor, visible)
|
||||
}
|
||||
|
||||
protected abstract fun writeStubCode()
|
||||
|
||||
final override fun visitCode() {
|
||||
with (mv) {
|
||||
visitCode()
|
||||
writeStubCode()
|
||||
visitMaxs(-1, -1) // Trigger computation of the max values.
|
||||
visitEnd()
|
||||
}
|
||||
|
||||
// Prevent this visitor from writing any more byte-code.
|
||||
mv = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a method that throws [UnsupportedOperationException] with message "Method has been deleted".
|
||||
*/
|
||||
private inner class ThrowingStubMethodAdapter(mv: MethodVisitor) : StubbingMethodAdapter(mv) {
|
||||
override fun writeStubCode() {
|
||||
with (mv) {
|
||||
val throwEx = Label()
|
||||
visitLabel(throwEx)
|
||||
visitLineNumber(0, throwEx)
|
||||
visitTypeInsn(NEW, "java/lang/UnsupportedOperationException")
|
||||
visitInsn(DUP)
|
||||
visitLdcInsn("Method has been deleted")
|
||||
visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "(Ljava/lang/String;)V", false)
|
||||
visitInsn(ATHROW)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an empty method. Can only be applied to methods that return void.
|
||||
*/
|
||||
private inner class VoidStubMethodAdapter(mv: MethodVisitor) : StubbingMethodAdapter(mv) {
|
||||
override fun writeStubCode() {
|
||||
mv.visitInsn(RETURN)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
|
||||
/**
|
||||
* This plugin definition is only needed by the tests.
|
||||
*/
|
||||
class JarFilterPlugin : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
project.logger.info("Applying JarFilter plugin")
|
||||
}
|
||||
}
|
@ -1,288 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.InvalidUserDataException
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.tasks.*
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.*
|
||||
import java.nio.file.StandardCopyOption.*
|
||||
import java.util.zip.Deflater.BEST_COMPRESSION
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlin.math.max
|
||||
|
||||
@Suppress("Unused", "MemberVisibilityCanBePrivate")
|
||||
open class JarFilterTask : DefaultTask() {
|
||||
private companion object {
|
||||
private const val DEFAULT_MAX_PASSES = 5
|
||||
}
|
||||
|
||||
private val _jars: ConfigurableFileCollection = project.files()
|
||||
@get:SkipWhenEmpty
|
||||
@get:InputFiles
|
||||
val jars: FileCollection get() = _jars
|
||||
|
||||
fun setJars(inputs: Any?) {
|
||||
val files = inputs ?: return
|
||||
_jars.setFrom(files)
|
||||
}
|
||||
|
||||
fun jars(inputs: Any?) = setJars(inputs)
|
||||
|
||||
@get:Input
|
||||
protected var forDelete: Set<String> = emptySet()
|
||||
|
||||
@get:Input
|
||||
protected var forStub: Set<String> = emptySet()
|
||||
|
||||
@get:Input
|
||||
protected var forRemove: Set<String> = emptySet()
|
||||
|
||||
@get:Input
|
||||
protected var forSanitise: Set<String> = emptySet()
|
||||
|
||||
fun annotations(assign: Closure<List<String>>) {
|
||||
assign.call()
|
||||
}
|
||||
|
||||
@get:Console
|
||||
var verbose: Boolean = false
|
||||
|
||||
@get:Input
|
||||
var maxPasses: Int = DEFAULT_MAX_PASSES
|
||||
set(value) {
|
||||
field = max(value, 1)
|
||||
}
|
||||
|
||||
@get:Input
|
||||
var preserveTimestamps: Boolean = true
|
||||
|
||||
private var _outputDir = project.buildDir.resolve("filtered-libs")
|
||||
@get:Internal
|
||||
val outputDir: File get() = _outputDir
|
||||
|
||||
fun setOutputDir(d: File?) {
|
||||
val dir = d ?: return
|
||||
_outputDir = dir
|
||||
}
|
||||
|
||||
fun outputDir(dir: File?) = setOutputDir(dir)
|
||||
|
||||
@get:OutputFiles
|
||||
val filtered: FileCollection get() = project.files(jars.files.map(this::toFiltered))
|
||||
|
||||
private fun toFiltered(source: File) = File(outputDir, source.name.replace(JAR_PATTERN, "-filtered\$1"))
|
||||
|
||||
@TaskAction
|
||||
fun filterJars() {
|
||||
logger.info("JarFiltering:")
|
||||
if (forDelete.isNotEmpty()) {
|
||||
logger.info("- Elements annotated with one of '{}' will be deleted", forDelete.joinToString())
|
||||
}
|
||||
if (forStub.isNotEmpty()) {
|
||||
logger.info("- Methods annotated with one of '{}' will be stubbed out", forStub.joinToString())
|
||||
}
|
||||
if (forRemove.isNotEmpty()) {
|
||||
logger.info("- Annotations '{}' will be removed entirely", forRemove.joinToString())
|
||||
}
|
||||
if (forSanitise.isNotEmpty()) {
|
||||
logger.info("- Annotations '{}' will be removed from primary constructors", forSanitise.joinToString())
|
||||
}
|
||||
checkDistinctAnnotations()
|
||||
try {
|
||||
jars.forEach { jar ->
|
||||
logger.info("Filtering {}", jar)
|
||||
Filter(jar).run()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
rethrowAsUncheckedException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDistinctAnnotations() {
|
||||
logger.info("Checking that all annotations are distinct.")
|
||||
val annotations = forRemove.toHashSet().apply {
|
||||
addAll(forDelete)
|
||||
addAll(forStub)
|
||||
removeAll(forRemove)
|
||||
}
|
||||
forDelete.forEach {
|
||||
if (!annotations.remove(it)) {
|
||||
failWith("Annotation '$it' also appears in JarFilter 'forDelete' section")
|
||||
}
|
||||
}
|
||||
forStub.forEach {
|
||||
if (!annotations.remove(it)) {
|
||||
failWith("Annotation '$it' also appears in JarFilter 'forStub' section")
|
||||
}
|
||||
}
|
||||
if (!annotations.isEmpty()) {
|
||||
failWith("SHOULDN'T HAPPEN - Martian annotations! '${annotations.joinToString()}'")
|
||||
}
|
||||
}
|
||||
|
||||
private fun failWith(message: String): Nothing = throw InvalidUserDataException(message)
|
||||
|
||||
private fun verbose(format: String, vararg objects: Any) {
|
||||
if (verbose) {
|
||||
logger.info(format, *objects)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class Filter(inFile: File) {
|
||||
private val unwantedElements = UnwantedCache()
|
||||
private val source: Path = inFile.toPath()
|
||||
private val target: Path = toFiltered(inFile).toPath()
|
||||
|
||||
private val descriptorsForRemove = toDescriptors(forRemove)
|
||||
private val descriptorsForDelete = toDescriptors(forDelete)
|
||||
private val descriptorsForStub = toDescriptors(forStub)
|
||||
private val descriptorsForSanitising = toDescriptors(forSanitise)
|
||||
|
||||
init {
|
||||
Files.deleteIfExists(target)
|
||||
}
|
||||
|
||||
fun run() {
|
||||
logger.info("Filtering to: {}", target)
|
||||
var input = source
|
||||
|
||||
try {
|
||||
if (descriptorsForSanitising.isNotEmpty() && SanitisingPass(input).use { it.run() }) {
|
||||
input = target.moveToInput()
|
||||
}
|
||||
|
||||
var passes = 1
|
||||
while (true) {
|
||||
verbose("Pass {}", passes)
|
||||
val isModified = FilterPass(input).use { it.run() }
|
||||
|
||||
if (!isModified) {
|
||||
logger.info("No changes after latest pass - exiting.")
|
||||
break
|
||||
} else if (++passes > maxPasses) {
|
||||
break
|
||||
}
|
||||
|
||||
input = target.moveToInput()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("Error filtering '{}' elements from {}", ArrayList(forRemove).apply { addAll(forDelete); addAll(forStub) }, input)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun Path.moveToInput(): Path {
|
||||
return Files.move(this, Files.createTempFile(parent, "filter-", ".tmp"), REPLACE_EXISTING).also {
|
||||
verbose("New input JAR: {}", it)
|
||||
}
|
||||
}
|
||||
|
||||
private abstract inner class Pass(input: Path): Closeable {
|
||||
/**
|
||||
* Use [ZipFile] instead of [java.util.jar.JarInputStream] because
|
||||
* JarInputStream consumes MANIFEST.MF when it's the first or second entry.
|
||||
*/
|
||||
protected val inJar = ZipFile(input.toFile())
|
||||
protected val outJar = ZipOutputStream(Files.newOutputStream(target))
|
||||
protected var isModified = false
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
inJar.use {
|
||||
outJar.close()
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun transform(inBytes: ByteArray): ByteArray
|
||||
|
||||
fun run(): Boolean {
|
||||
outJar.setLevel(BEST_COMPRESSION)
|
||||
outJar.setComment(inJar.comment)
|
||||
|
||||
for (entry in inJar.entries()) {
|
||||
val entryData = inJar.getInputStream(entry)
|
||||
|
||||
if (entry.isDirectory || !entry.name.endsWith(".class")) {
|
||||
// This entry's byte contents have not changed,
|
||||
// but may still need to be recompressed.
|
||||
outJar.putNextEntry(entry.copy().withFileTimestamps(preserveTimestamps))
|
||||
entryData.copyTo(outJar)
|
||||
} else {
|
||||
val classData = transform(entryData.readBytes())
|
||||
if (classData.isNotEmpty()) {
|
||||
// This entry's byte contents have almost certainly
|
||||
// changed, and will be stored compressed.
|
||||
outJar.putNextEntry(entry.asCompressed().withFileTimestamps(preserveTimestamps))
|
||||
outJar.write(classData)
|
||||
}
|
||||
}
|
||||
}
|
||||
return isModified
|
||||
}
|
||||
}
|
||||
|
||||
private inner class SanitisingPass(input: Path) : Pass(input) {
|
||||
override fun transform(inBytes: ByteArray): ByteArray {
|
||||
return ClassWriter(0).let { writer ->
|
||||
val transformer = SanitisingTransformer(writer, logger, descriptorsForSanitising)
|
||||
ClassReader(inBytes).accept(transformer, 0)
|
||||
isModified = isModified or transformer.isModified
|
||||
writer.toByteArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class FilterPass(input: Path) : Pass(input) {
|
||||
override fun transform(inBytes: ByteArray): ByteArray {
|
||||
var reader = ClassReader(inBytes)
|
||||
var writer = ClassWriter(COMPUTE_MAXS)
|
||||
var transformer = FilterTransformer(
|
||||
visitor = writer,
|
||||
logger = logger,
|
||||
removeAnnotations = descriptorsForRemove,
|
||||
deleteAnnotations = descriptorsForDelete,
|
||||
stubAnnotations = descriptorsForStub,
|
||||
unwantedElements = unwantedElements
|
||||
)
|
||||
|
||||
/*
|
||||
* First pass: This might not find anything to remove!
|
||||
*/
|
||||
reader.accept(transformer, 0)
|
||||
|
||||
if (transformer.isUnwantedClass || transformer.hasUnwantedElements) {
|
||||
isModified = true
|
||||
|
||||
do {
|
||||
/*
|
||||
* Rewrite the class without any of the unwanted elements.
|
||||
* If we're deleting the class then make sure we identify all of
|
||||
* its inner classes too, for the next filter pass to delete.
|
||||
*/
|
||||
reader = ClassReader(writer.toByteArray())
|
||||
writer = ClassWriter(COMPUTE_MAXS)
|
||||
transformer = transformer.recreate(writer)
|
||||
reader.accept(transformer, 0)
|
||||
} while (!transformer.isUnwantedClass && transformer.hasUnwantedElements)
|
||||
}
|
||||
|
||||
return if (transformer.isUnwantedClass) {
|
||||
// The entire class is unwanted, so don't write it out.
|
||||
logger.info("Deleting class {}", transformer.className)
|
||||
byteArrayOf()
|
||||
} else {
|
||||
writer.toByteArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.*
|
||||
import org.objectweb.asm.AnnotationVisitor
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
|
||||
/**
|
||||
* Kotlin support: Loads the ProtoBuf data from the [kotlin.Metadata] annotation.
|
||||
*/
|
||||
abstract class KotlinAwareVisitor(
|
||||
api: Int,
|
||||
visitor: ClassVisitor,
|
||||
protected val logger: Logger,
|
||||
protected val kotlinMetadata: MutableMap<String, List<String>>
|
||||
) : ClassVisitor(api, visitor) {
|
||||
|
||||
private companion object {
|
||||
/** See [org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind]. */
|
||||
private const val KOTLIN_CLASS = 1
|
||||
private const val KOTLIN_FILE = 2
|
||||
private const val KOTLIN_SYNTHETIC = 3
|
||||
private const val KOTLIN_MULTIFILE_PART = 5
|
||||
}
|
||||
|
||||
private var classKind: Int = 0
|
||||
|
||||
open val hasUnwantedElements: Boolean get() = kotlinMetadata.isNotEmpty()
|
||||
protected open val level: LogLevel = LogLevel.INFO
|
||||
|
||||
protected abstract fun processClassMetadata(d1: List<String>, d2: List<String>): List<String>
|
||||
protected abstract fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String>
|
||||
protected abstract fun processKotlinAnnotation()
|
||||
|
||||
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
|
||||
val av = super.visitAnnotation(descriptor, visible) ?: return null
|
||||
return if (descriptor == METADATA_DESC) KotlinMetadataAdaptor(av) else av
|
||||
}
|
||||
|
||||
protected fun processMetadata() {
|
||||
if (kotlinMetadata.isNotEmpty()) {
|
||||
logger.log(level, "- Examining Kotlin @Metadata[k={}]", classKind)
|
||||
val d1 = kotlinMetadata.remove(METADATA_DATA_FIELD_NAME)
|
||||
val d2 = kotlinMetadata.remove(METADATA_STRINGS_FIELD_NAME)
|
||||
if (d1 != null && d1.isNotEmpty() && d2 != null) {
|
||||
processMetadata(d1, d2).apply {
|
||||
if (isNotEmpty()) {
|
||||
kotlinMetadata[METADATA_DATA_FIELD_NAME] = this
|
||||
kotlinMetadata[METADATA_STRINGS_FIELD_NAME] = d2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
return when (classKind) {
|
||||
KOTLIN_CLASS -> processClassMetadata(d1, d2)
|
||||
KOTLIN_FILE, KOTLIN_MULTIFILE_PART -> processPackageMetadata(d1, d2)
|
||||
KOTLIN_SYNTHETIC -> {
|
||||
logger.log(level,"-- synthetic class ignored")
|
||||
emptyList()
|
||||
}
|
||||
else -> {
|
||||
/*
|
||||
* For class-kind=4 (i.e. "multi-file"), we currently
|
||||
* expect d1=[list of multi-file-part classes], d2=null.
|
||||
*/
|
||||
logger.log(level,"-- unsupported class-kind {}", classKind)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class KotlinMetadataAdaptor(av: AnnotationVisitor): AnnotationVisitor(api, av) {
|
||||
override fun visit(name: String?, value: Any?) {
|
||||
if (name == KIND_FIELD_NAME) {
|
||||
classKind = value as Int
|
||||
}
|
||||
super.visit(name, value)
|
||||
}
|
||||
|
||||
override fun visitArray(name: String): AnnotationVisitor? {
|
||||
val av = super.visitArray(name)
|
||||
if (av != null) {
|
||||
val data = kotlinMetadata.remove(name) ?: return ArrayAccumulator(av, name)
|
||||
logger.debug("-- rewrote @Metadata.{}[{}]", name, data.size)
|
||||
data.forEach { av.visit(null, it) }
|
||||
av.visitEnd()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
super.visitEnd()
|
||||
processKotlinAnnotation()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ArrayAccumulator(av: AnnotationVisitor, private val name: String) : AnnotationVisitor(api, av) {
|
||||
private val data: MutableList<String> = mutableListOf()
|
||||
|
||||
override fun visit(name: String?, value: Any?) {
|
||||
super.visit(name, value)
|
||||
data.add(value as String)
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
super.visitEnd()
|
||||
kotlinMetadata[name] = data
|
||||
logger.debug("-- read @Metadata.{}[{}]", name, data.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the ProtoBuf data from the [kotlin.Metadata] annotation, or
|
||||
* writes new ProtoBuf data that was created during a previous pass.
|
||||
*/
|
||||
abstract class KotlinAfterProcessor(
|
||||
api: Int,
|
||||
visitor: ClassVisitor,
|
||||
logger: Logger,
|
||||
kotlinMetadata: MutableMap<String, List<String>>
|
||||
) : KotlinAwareVisitor(api, visitor, logger, kotlinMetadata) {
|
||||
|
||||
/**
|
||||
* Process the metadata once we have finished visiting the class.
|
||||
* This will allow us to rewrite the [kotlin.Metadata] annotation
|
||||
* in the next visit.
|
||||
*/
|
||||
override fun visitEnd() {
|
||||
super.visitEnd()
|
||||
processMetadata()
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing immediately after we have parsed [kotlin.Metadata].
|
||||
*/
|
||||
final override fun processKotlinAnnotation() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the ProtoBuf data from the [kotlin.Metadata] annotation
|
||||
* and then processes it before visiting the rest of the class.
|
||||
*/
|
||||
abstract class KotlinBeforeProcessor(
|
||||
api: Int,
|
||||
visitor: ClassVisitor,
|
||||
logger: Logger,
|
||||
kotlinMetadata: MutableMap<String, List<String>>
|
||||
) : KotlinAwareVisitor(api, visitor, logger, kotlinMetadata) {
|
||||
|
||||
/**
|
||||
* Process the ProtoBuf data as soon as we have parsed [kotlin.Metadata].
|
||||
*/
|
||||
final override fun processKotlinAnnotation() = processMetadata()
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.gradle.api.tasks.*
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.*
|
||||
import java.util.zip.Deflater.BEST_COMPRESSION
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
@Suppress("Unused", "MemberVisibilityCanBePrivate")
|
||||
open class MetaFixerTask : DefaultTask() {
|
||||
private val _jars: ConfigurableFileCollection = project.files()
|
||||
@get:SkipWhenEmpty
|
||||
@get:InputFiles
|
||||
val jars: FileCollection
|
||||
get() = _jars
|
||||
|
||||
fun setJars(inputs: Any?) {
|
||||
val files = inputs ?: return
|
||||
_jars.setFrom(files)
|
||||
}
|
||||
|
||||
fun jars(inputs: Any?) = setJars(inputs)
|
||||
|
||||
private var _outputDir = project.buildDir.resolve("metafixer-libs")
|
||||
@get:Internal
|
||||
val outputDir: File
|
||||
get() = _outputDir
|
||||
|
||||
fun setOutputDir(d: File?) {
|
||||
val dir = d ?: return
|
||||
_outputDir = dir
|
||||
}
|
||||
|
||||
fun outputDir(dir: File?) = setOutputDir(dir)
|
||||
|
||||
private var _suffix: String = "-metafixed"
|
||||
@get:Input
|
||||
val suffix: String get() = _suffix
|
||||
|
||||
fun setSuffix(input: String?) {
|
||||
_suffix = input ?: return
|
||||
}
|
||||
|
||||
fun suffix(suffix: String?) = setSuffix(suffix)
|
||||
|
||||
@get:Input
|
||||
var preserveTimestamps: Boolean = true
|
||||
|
||||
@TaskAction
|
||||
fun fixMetadata() {
|
||||
logger.info("Fixing Kotlin @Metadata")
|
||||
try {
|
||||
jars.forEach { jar ->
|
||||
logger.info("Reading from {}", jar)
|
||||
MetaFix(jar).use { it.run() }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
rethrowAsUncheckedException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@get:OutputFiles
|
||||
val metafixed: FileCollection get() = project.files(jars.files.map(this::toMetaFixed))
|
||||
|
||||
private fun toMetaFixed(source: File) = File(outputDir, source.name.replace(JAR_PATTERN, "$suffix\$1"))
|
||||
|
||||
private inner class MetaFix(inFile: File) : Closeable {
|
||||
/**
|
||||
* Use [ZipFile] instead of [java.util.jar.JarInputStream] because
|
||||
* JarInputStream consumes MANIFEST.MF when it's the first or second entry.
|
||||
*/
|
||||
private val target: Path = toMetaFixed(inFile).toPath()
|
||||
private val inJar = ZipFile(inFile)
|
||||
private val outJar: ZipOutputStream
|
||||
|
||||
init {
|
||||
// Default options for newOutputStream() are CREATE, TRUNCATE_EXISTING.
|
||||
outJar = ZipOutputStream(Files.newOutputStream(target)).apply {
|
||||
setLevel(BEST_COMPRESSION)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
inJar.use {
|
||||
outJar.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun run() {
|
||||
logger.info("Writing to {}", target)
|
||||
outJar.setComment(inJar.comment)
|
||||
|
||||
val classNames = inJar.entries().asSequence().namesEndingWith(".class")
|
||||
for (entry in inJar.entries()) {
|
||||
val entryData = inJar.getInputStream(entry)
|
||||
|
||||
if (entry.isDirectory || !entry.name.endsWith(".class")) {
|
||||
// This entry's byte contents have not changed,
|
||||
// but may still need to be recompressed.
|
||||
outJar.putNextEntry(entry.copy().withFileTimestamps(preserveTimestamps))
|
||||
entryData.copyTo(outJar)
|
||||
} else {
|
||||
// This entry's byte contents have almost certainly
|
||||
// changed, and will be stored compressed.
|
||||
val classData = entryData.readBytes().fixMetadata(logger, classNames)
|
||||
outJar.putNextEntry(entry.asCompressed().withFileTimestamps(preserveTimestamps))
|
||||
outJar.write(classData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Sequence<ZipEntry>.namesEndingWith(suffix: String): Set<String> {
|
||||
return filter { it.name.endsWith(suffix) }.map { it.name.dropLast(suffix.length) }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteArray.fixMetadata(logger: Logger, classNames: Set<String>): ByteArray
|
||||
= execute({ writer -> MetaFixerVisitor(writer, logger, classNames) })
|
@ -1,278 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.ProtoBuf.Class.Kind.*
|
||||
import org.jetbrains.kotlin.metadata.deserialization.Flags.*
|
||||
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
|
||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||
import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull
|
||||
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf.*
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmNameResolver
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.EXTENSION_REGISTRY
|
||||
import org.jetbrains.kotlin.protobuf.ExtensionRegistryLite
|
||||
import org.jetbrains.kotlin.protobuf.MessageLite
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Base class for aligning the contents of [kotlin.Metadata] annotations
|
||||
* with the contents of the host byte-code.
|
||||
* This is used by [MetaFixerVisitor] for [MetaFixerTask].
|
||||
*/
|
||||
internal abstract class MetaFixerTransformer<out T : MessageLite>(
|
||||
private val logger: Logger,
|
||||
private val actualFields: Collection<FieldElement>,
|
||||
private val actualMethods: Collection<String>,
|
||||
private val actualNestedClasses: Collection<String>,
|
||||
private val actualClasses: Collection<String>,
|
||||
d1: List<String>,
|
||||
d2: List<String>,
|
||||
parser: (InputStream, ExtensionRegistryLite) -> T
|
||||
) {
|
||||
private val stringTableTypes: StringTableTypes
|
||||
protected val nameResolver: NameResolver
|
||||
protected val message: T
|
||||
|
||||
protected abstract val typeTable: TypeTable
|
||||
protected open val classDescriptor: String = ""
|
||||
protected open val classKind: ProtoBuf.Class.Kind? = null
|
||||
protected abstract val properties: MutableList<ProtoBuf.Property>
|
||||
protected abstract val functions: MutableList<ProtoBuf.Function>
|
||||
protected abstract val constructors: MutableList<ProtoBuf.Constructor>
|
||||
protected open val nestedClassNames: MutableList<Int> get() = throw UnsupportedOperationException("No nestedClassNames")
|
||||
protected open val sealedSubclassNames: MutableList<Int> get() = throw UnsupportedOperationException("No sealedSubclassNames")
|
||||
|
||||
init {
|
||||
val input = ByteArrayInputStream(BitEncoding.decodeBytes(d1.toTypedArray()))
|
||||
stringTableTypes = StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY)
|
||||
nameResolver = JvmNameResolver(stringTableTypes, d2.toTypedArray())
|
||||
message = parser(input, EXTENSION_REGISTRY)
|
||||
}
|
||||
|
||||
abstract fun rebuild(): T
|
||||
|
||||
private fun filterNestedClasses(): Int {
|
||||
if (classKind == null) return 0
|
||||
|
||||
var count = 0
|
||||
var idx = 0
|
||||
while (idx < nestedClassNames.size) {
|
||||
val nestedClassName = nameResolver.getString(nestedClassNames[idx])
|
||||
if (actualNestedClasses.contains(nestedClassName)) {
|
||||
++idx
|
||||
} else {
|
||||
logger.info("-- removing nested class: {}", nestedClassName)
|
||||
nestedClassNames.removeAt(idx)
|
||||
++count
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
private fun filterSealedSubclassNames(): Int {
|
||||
if (classKind == null) return 0
|
||||
|
||||
var count = 0
|
||||
var idx = 0
|
||||
while (idx < sealedSubclassNames.size) {
|
||||
val sealedSubclassName = nameResolver.getClassInternalName(sealedSubclassNames[idx])
|
||||
if (actualClasses.contains(sealedSubclassName)) {
|
||||
++idx
|
||||
} else {
|
||||
logger.info("-- removing sealed subclass: {}", sealedSubclassName)
|
||||
sealedSubclassNames.removeAt(idx)
|
||||
++count
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
private fun filterFunctions(): Int {
|
||||
var count = 0
|
||||
var idx = 0
|
||||
removed@ while (idx < functions.size) {
|
||||
val function = functions[idx]
|
||||
val signature = JvmProtoBufUtil.getJvmMethodSignature(function, nameResolver, typeTable)
|
||||
if (signature != null) {
|
||||
if (!actualMethods.contains(signature)) {
|
||||
logger.info("-- removing method: {}", signature)
|
||||
functions.removeAt(idx)
|
||||
++count
|
||||
continue@removed
|
||||
} else if (function.valueParameterList.hasAnyDefaultValues
|
||||
&& !actualMethods.contains(signature.toKotlinDefaultFunction(classDescriptor))) {
|
||||
logger.info("-- removing default parameter values: {}", signature)
|
||||
functions[idx] = function.toBuilder()
|
||||
.updateValueParameters(ProtoBuf.ValueParameter::clearDeclaresDefaultValue)
|
||||
.build()
|
||||
++count
|
||||
}
|
||||
}
|
||||
++idx
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
private fun filterConstructors(): Int {
|
||||
var count = 0
|
||||
var idx = 0
|
||||
removed@ while (idx < constructors.size) {
|
||||
val constructor = constructors[idx]
|
||||
val signature = JvmProtoBufUtil.getJvmConstructorSignature(constructor, nameResolver, typeTable)
|
||||
if (signature != null) {
|
||||
if (!actualMethods.contains(signature)) {
|
||||
logger.info("-- removing constructor: {}", signature)
|
||||
constructors.removeAt(idx)
|
||||
++count
|
||||
continue@removed
|
||||
} else if (constructor.valueParameterList.hasAnyDefaultValues
|
||||
&& !actualMethods.contains(signature.toKotlinDefaultConstructor())) {
|
||||
logger.info("-- removing default parameter values: {}", signature)
|
||||
constructors[idx] = constructor.toBuilder()
|
||||
.updateValueParameters(ProtoBuf.ValueParameter::clearDeclaresDefaultValue)
|
||||
.build()
|
||||
++count
|
||||
}
|
||||
}
|
||||
++idx
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
private fun filterProperties(): Int {
|
||||
var count = 0
|
||||
var idx = 0
|
||||
removed@ while (idx < properties.size) {
|
||||
val property = properties[idx]
|
||||
val signature = property.getExtensionOrNull(propertySignature)
|
||||
if (signature != null) {
|
||||
val field = signature.toFieldElement(property, nameResolver, typeTable)
|
||||
val getterMethod = signature.toGetter(nameResolver)
|
||||
|
||||
/**
|
||||
* A property annotated with [JvmField] will use a field instead of a getter method.
|
||||
* But properties without [JvmField] will also usually have a backing field. So we only
|
||||
* remove a property that has either lost its getter method, or never had a getter method
|
||||
* and has lost its field.
|
||||
*
|
||||
* Having said that, we cannot remove [JvmField] properties from a companion object class
|
||||
* because these properties are implemented as static fields on the companion's host class.
|
||||
*/
|
||||
val isValidProperty = if (getterMethod == null) {
|
||||
actualFields.contains(field) || classKind == COMPANION_OBJECT
|
||||
} else {
|
||||
actualMethods.contains(getterMethod.signature)
|
||||
}
|
||||
|
||||
if (!isValidProperty) {
|
||||
logger.info("-- removing property: {},{}", field.name, field.descriptor)
|
||||
properties.removeAt(idx)
|
||||
++count
|
||||
continue@removed
|
||||
}
|
||||
}
|
||||
++idx
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
fun transform(): List<String> {
|
||||
var count = filterProperties() + filterFunctions() + filterNestedClasses() + filterSealedSubclassNames()
|
||||
if (classKind != ANNOTATION_CLASS) {
|
||||
count += filterConstructors()
|
||||
}
|
||||
if (count == 0) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val bytes = ByteArrayOutputStream()
|
||||
stringTableTypes.writeDelimitedTo(bytes)
|
||||
rebuild().writeTo(bytes)
|
||||
return BitEncoding.encodeBytes(bytes.toByteArray()).toList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns a [kotlin.Metadata] annotation containing a [ProtoBuf.Class] object
|
||||
* in its [d1][kotlin.Metadata.d1] field with the byte-code of its host class.
|
||||
*/
|
||||
internal class ClassMetaFixerTransformer(
|
||||
logger: Logger,
|
||||
actualFields: Collection<FieldElement>,
|
||||
actualMethods: Collection<String>,
|
||||
actualNestedClasses: Collection<String>,
|
||||
actualClasses: Collection<String>,
|
||||
d1: List<String>,
|
||||
d2: List<String>
|
||||
) : MetaFixerTransformer<ProtoBuf.Class>(
|
||||
logger,
|
||||
actualFields,
|
||||
actualMethods,
|
||||
actualNestedClasses,
|
||||
actualClasses,
|
||||
d1,
|
||||
d2,
|
||||
ProtoBuf.Class::parseFrom
|
||||
) {
|
||||
override val typeTable = TypeTable(message.typeTable)
|
||||
override val classDescriptor = "L${nameResolver.getClassInternalName(message.fqName)};"
|
||||
override val classKind: ProtoBuf.Class.Kind = CLASS_KIND.get(message.flags)
|
||||
override val properties = mutableList(message.propertyList)
|
||||
override val functions = mutableList(message.functionList)
|
||||
override val constructors = mutableList(message.constructorList)
|
||||
override val nestedClassNames = mutableList(message.nestedClassNameList)
|
||||
override val sealedSubclassNames= mutableList(message.sealedSubclassFqNameList)
|
||||
|
||||
override fun rebuild(): ProtoBuf.Class = message.toBuilder().apply {
|
||||
clearConstructor().addAllConstructor(constructors)
|
||||
clearFunction().addAllFunction(functions)
|
||||
|
||||
if (nestedClassNames.size != nestedClassNameCount) {
|
||||
clearNestedClassName().addAllNestedClassName(nestedClassNames)
|
||||
}
|
||||
if (sealedSubclassNames.size != sealedSubclassFqNameCount) {
|
||||
clearSealedSubclassFqName().addAllSealedSubclassFqName(sealedSubclassNames)
|
||||
}
|
||||
if (properties.size != propertyCount) {
|
||||
clearProperty().addAllProperty(properties)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns a [kotlin.Metadata] annotation containing a [ProtoBuf.Package] object
|
||||
* in its [d1][kotlin.Metadata.d1] field with the byte-code of its host class.
|
||||
*/
|
||||
internal class PackageMetaFixerTransformer(
|
||||
logger: Logger,
|
||||
actualFields: Collection<FieldElement>,
|
||||
actualMethods: Collection<String>,
|
||||
d1: List<String>,
|
||||
d2: List<String>
|
||||
) : MetaFixerTransformer<ProtoBuf.Package>(
|
||||
logger,
|
||||
actualFields,
|
||||
actualMethods,
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
d1,
|
||||
d2,
|
||||
ProtoBuf.Package::parseFrom
|
||||
) {
|
||||
override val typeTable = TypeTable(message.typeTable)
|
||||
override val properties = mutableList(message.propertyList)
|
||||
override val functions = mutableList(message.functionList)
|
||||
override val constructors = mutableListOf<ProtoBuf.Constructor>()
|
||||
|
||||
override fun rebuild(): ProtoBuf.Package = message.toBuilder().apply {
|
||||
clearFunction().addAllFunction(functions)
|
||||
|
||||
if (properties.size != propertyCount) {
|
||||
clearProperty().addAllProperty(properties)
|
||||
}
|
||||
}.build()
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.objectweb.asm.*
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
|
||||
/**
|
||||
* ASM [ClassVisitor] for the MetaFixer task. This visitor inventories every function,
|
||||
* property and inner class within the byte-code and then passes this information to
|
||||
* the [MetaFixerTransformer].
|
||||
*/
|
||||
class MetaFixerVisitor private constructor(
|
||||
visitor: ClassVisitor,
|
||||
logger: Logger,
|
||||
kotlinMetadata: MutableMap<String, List<String>>,
|
||||
private val classNames: Set<String>,
|
||||
private val fields: MutableSet<FieldElement>,
|
||||
private val methods: MutableSet<String>,
|
||||
private val nestedClasses: MutableSet<String>
|
||||
) : KotlinAfterProcessor(ASM6, visitor, logger, kotlinMetadata), Repeatable<MetaFixerVisitor> {
|
||||
constructor(visitor: ClassVisitor, logger: Logger, classNames: Set<String>)
|
||||
: this(visitor, logger, mutableMapOf(), classNames, mutableSetOf(), mutableSetOf(), mutableSetOf())
|
||||
|
||||
override fun recreate(visitor: ClassVisitor) = MetaFixerVisitor(visitor, logger, kotlinMetadata, classNames, fields, methods, nestedClasses)
|
||||
|
||||
private var className: String = "(unknown)"
|
||||
|
||||
override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array<String>?) {
|
||||
className = clsName
|
||||
logger.info("Class {}", clsName)
|
||||
super.visit(version, access, clsName, signature, superName, interfaces)
|
||||
}
|
||||
|
||||
override fun visitField(access: Int, fieldName: String, descriptor: String, signature: String?, value: Any?): FieldVisitor? {
|
||||
if (fields.add(FieldElement(fieldName, descriptor))) {
|
||||
logger.info("- field {},{}", fieldName, descriptor)
|
||||
}
|
||||
return super.visitField(access, fieldName, descriptor, signature, value)
|
||||
}
|
||||
|
||||
override fun visitMethod(access: Int, methodName: String, descriptor: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
|
||||
if (methods.add(methodName + descriptor)) {
|
||||
logger.info("- method {}{}", methodName, descriptor)
|
||||
}
|
||||
return super.visitMethod(access, methodName, descriptor, signature, exceptions)
|
||||
}
|
||||
|
||||
override fun visitInnerClass(clsName: String, outerName: String?, innerName: String?, access: Int) {
|
||||
if (outerName == className && innerName != null && nestedClasses.add(innerName)) {
|
||||
logger.info("- inner class {}", clsName)
|
||||
}
|
||||
return super.visitInnerClass(clsName, outerName, innerName, access)
|
||||
}
|
||||
|
||||
override fun processClassMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
return ClassMetaFixerTransformer(
|
||||
logger = logger,
|
||||
actualFields = fields,
|
||||
actualMethods = methods,
|
||||
actualNestedClasses = nestedClasses,
|
||||
actualClasses = classNames,
|
||||
d1 = d1,
|
||||
d2 = d2)
|
||||
.transform()
|
||||
}
|
||||
|
||||
override fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
return PackageMetaFixerTransformer(
|
||||
logger = logger,
|
||||
actualFields = fields,
|
||||
actualMethods = methods,
|
||||
d1 = d1,
|
||||
d2 = d2)
|
||||
.transform()
|
||||
}
|
||||
}
|
@ -1,319 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.deserialization.Flags.*
|
||||
import org.jetbrains.kotlin.metadata.deserialization.NameResolver
|
||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||
import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull
|
||||
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf.*
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmNameResolver
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.EXTENSION_REGISTRY
|
||||
import org.jetbrains.kotlin.protobuf.ExtensionRegistryLite
|
||||
import org.jetbrains.kotlin.protobuf.MessageLite
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Base class for removing unwanted elements from [kotlin.Metadata] annotations.
|
||||
* This is used by [FilterTransformer] for [JarFilterTask].
|
||||
*/
|
||||
internal abstract class MetadataTransformer<out T : MessageLite>(
|
||||
private val logger: Logger,
|
||||
private val deletedFields: Collection<FieldElement>,
|
||||
private val deletedFunctions: Collection<MethodElement>,
|
||||
private val deletedConstructors: Collection<MethodElement>,
|
||||
private val deletedNestedClasses: Collection<String>,
|
||||
private val deletedClasses: Collection<String>,
|
||||
private val handleExtraMethod: (MethodElement) -> Unit,
|
||||
d1: List<String>,
|
||||
d2: List<String>,
|
||||
parser: (InputStream, ExtensionRegistryLite) -> T
|
||||
) {
|
||||
private val stringTableTypes: StringTableTypes
|
||||
protected val nameResolver: NameResolver
|
||||
protected val message: T
|
||||
|
||||
protected abstract val typeTable: TypeTable
|
||||
protected open val className: String get() = throw UnsupportedOperationException("No className")
|
||||
protected open val nestedClassNames: MutableList<Int> get() = throw UnsupportedOperationException("No nestedClassNames")
|
||||
protected open val sealedSubclassNames: MutableList<Int> get() = throw UnsupportedOperationException("No sealedSubclassNames")
|
||||
protected abstract val properties: MutableList<ProtoBuf.Property>
|
||||
protected abstract val functions: MutableList<ProtoBuf.Function>
|
||||
protected open val constructors: MutableList<ProtoBuf.Constructor> get() = throw UnsupportedOperationException("No constructors")
|
||||
protected abstract val typeAliases: MutableList<ProtoBuf.TypeAlias>
|
||||
|
||||
init {
|
||||
val input = ByteArrayInputStream(BitEncoding.decodeBytes(d1.toTypedArray()))
|
||||
stringTableTypes = StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY)
|
||||
nameResolver = JvmNameResolver(stringTableTypes, d2.toTypedArray())
|
||||
message = parser(input, EXTENSION_REGISTRY)
|
||||
}
|
||||
|
||||
abstract fun rebuild(): T
|
||||
|
||||
fun transform(): List<String> {
|
||||
val count = (
|
||||
filterProperties()
|
||||
+ filterFunctions()
|
||||
+ filterConstructors()
|
||||
+ filterNestedClasses()
|
||||
+ filterTypeAliases()
|
||||
+ filterSealedSubclasses()
|
||||
)
|
||||
if (count == 0) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val bytes = ByteArrayOutputStream()
|
||||
stringTableTypes.writeDelimitedTo(bytes)
|
||||
rebuild().writeTo(bytes)
|
||||
return BitEncoding.encodeBytes(bytes.toByteArray()).toList()
|
||||
}
|
||||
|
||||
private fun filterNestedClasses(): Int {
|
||||
if (deletedNestedClasses.isEmpty()) return 0
|
||||
|
||||
var count = 0
|
||||
var idx = 0
|
||||
while (idx < nestedClassNames.size) {
|
||||
val nestedClassName = nameResolver.getString(nestedClassNames[idx])
|
||||
if (deletedNestedClasses.contains(nestedClassName)) {
|
||||
logger.info("-- removing nested class: {}", nestedClassName)
|
||||
nestedClassNames.removeAt(idx)
|
||||
++count
|
||||
} else {
|
||||
++idx
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
private fun filterConstructors(): Int = deletedConstructors.count(::filterConstructor)
|
||||
|
||||
private fun filterConstructor(deleted: MethodElement): Boolean {
|
||||
/*
|
||||
* Constructors with the default parameter marker are synthetic and DO NOT have
|
||||
* entries in the metadata. So we construct an element for the "primary" one
|
||||
* that it was synthesised for, and which we DO expect to find.
|
||||
*/
|
||||
val deletedPrimary = deleted.asKotlinNonDefaultConstructor()
|
||||
|
||||
for (idx in 0 until constructors.size) {
|
||||
val constructor = constructors[idx]
|
||||
val signature = JvmProtoBufUtil.getJvmConstructorSignature(constructor, nameResolver, typeTable)
|
||||
if (signature == deleted.signature) {
|
||||
if (IS_SECONDARY.get(constructor.flags)) {
|
||||
logger.info("-- removing constructor: {}", deleted.signature)
|
||||
} else {
|
||||
logger.warn("Removing primary constructor: {}{}", className, deleted.descriptor)
|
||||
}
|
||||
constructors.removeAt(idx)
|
||||
return true
|
||||
} else if (signature == deletedPrimary?.signature) {
|
||||
constructors[idx] = constructor.toBuilder()
|
||||
.updateValueParameters(ProtoBuf.ValueParameter::clearDeclaresDefaultValue)
|
||||
.build()
|
||||
logger.info("-- removing default parameter values: {}", signature)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun filterFunctions(): Int = deletedFunctions.count(::filterFunction)
|
||||
|
||||
private fun filterFunction(deleted: MethodElement): Boolean {
|
||||
for (idx in 0 until functions.size) {
|
||||
val function = functions[idx]
|
||||
if (nameResolver.getString(function.name) == deleted.name) {
|
||||
val signature = JvmProtoBufUtil.getJvmMethodSignature(function, nameResolver, typeTable)
|
||||
if (signature == deleted.signature) {
|
||||
logger.info("-- removing function: {}", deleted.signature)
|
||||
functions.removeAt(idx)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun filterProperties(): Int = deletedFields.count(::filterProperty)
|
||||
|
||||
private fun filterProperty(deleted: FieldElement): Boolean {
|
||||
for (idx in 0 until properties.size) {
|
||||
val property = properties[idx]
|
||||
val signature = property.getExtensionOrNull(propertySignature) ?: continue
|
||||
val field = signature.toFieldElement(property, nameResolver, typeTable)
|
||||
if (field.name.toVisible() == deleted.name) {
|
||||
// Check that this property's getter has the correct descriptor.
|
||||
// If it doesn't then we have the wrong property here.
|
||||
val getter = signature.toGetter(nameResolver)
|
||||
if (getter != null) {
|
||||
if (!getter.descriptor.startsWith(deleted.extension)) {
|
||||
continue
|
||||
}
|
||||
deleteExtra(getter)
|
||||
}
|
||||
signature.toSetter(nameResolver)?.apply(::deleteExtra)
|
||||
|
||||
logger.info("-- removing property: {},{}", field.name, field.descriptor)
|
||||
properties.removeAt(idx)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun deleteExtra(func: MethodElement) {
|
||||
if (!deletedFunctions.contains(func)) {
|
||||
logger.info("-- identified extra method {} for deletion", func.signature)
|
||||
handleExtraMethod(func)
|
||||
filterFunction(func)
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterTypeAliases(): Int {
|
||||
if (deletedFields.isEmpty()) return 0
|
||||
|
||||
var count = 0
|
||||
var idx = 0
|
||||
while (idx < typeAliases.size) {
|
||||
val aliasName = nameResolver.getString(typeAliases[idx].name)
|
||||
if (deletedFields.any { it.name == aliasName && it.extension == "()" }) {
|
||||
logger.info("-- removing typealias: {}", aliasName)
|
||||
typeAliases.removeAt(idx)
|
||||
++count
|
||||
} else {
|
||||
++idx
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
private fun filterSealedSubclasses(): Int {
|
||||
if (deletedClasses.isEmpty()) return 0
|
||||
|
||||
var count = 0
|
||||
var idx = 0
|
||||
while (idx < sealedSubclassNames.size) {
|
||||
val subclassName = nameResolver.getClassInternalName(sealedSubclassNames[idx])
|
||||
if (deletedClasses.contains(subclassName)) {
|
||||
logger.info("-- removing sealed subclass: {}", subclassName)
|
||||
sealedSubclassNames.removeAt(idx)
|
||||
++count
|
||||
} else {
|
||||
++idx
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any Kotlin suffix, e.g. "$delegate" or "$annotations".
|
||||
*/
|
||||
private fun String.toVisible(): String {
|
||||
val idx = indexOf('$')
|
||||
return if (idx == -1) this else substring(0, idx)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes elements from a [kotlin.Metadata] annotation that contains
|
||||
* a [ProtoBuf.Class] object in its [d1][kotlin.Metadata.d1] field.
|
||||
*/
|
||||
internal class ClassMetadataTransformer(
|
||||
logger: Logger,
|
||||
deletedFields: Collection<FieldElement>,
|
||||
deletedFunctions: Collection<MethodElement>,
|
||||
deletedConstructors: Collection<MethodElement>,
|
||||
deletedNestedClasses: Collection<String>,
|
||||
deletedClasses: Collection<String>,
|
||||
handleExtraMethod: (MethodElement) -> Unit,
|
||||
d1: List<String>,
|
||||
d2: List<String>
|
||||
) : MetadataTransformer<ProtoBuf.Class>(
|
||||
logger,
|
||||
deletedFields,
|
||||
deletedFunctions,
|
||||
deletedConstructors,
|
||||
deletedNestedClasses,
|
||||
deletedClasses,
|
||||
handleExtraMethod,
|
||||
d1,
|
||||
d2,
|
||||
ProtoBuf.Class::parseFrom
|
||||
) {
|
||||
override val typeTable = TypeTable(message.typeTable)
|
||||
override val className = nameResolver.getClassInternalName(message.fqName)
|
||||
override val nestedClassNames = mutableList(message.nestedClassNameList)
|
||||
override val sealedSubclassNames = mutableList(message.sealedSubclassFqNameList)
|
||||
override val properties = mutableList(message.propertyList)
|
||||
override val functions = mutableList(message.functionList)
|
||||
override val constructors = mutableList(message.constructorList)
|
||||
override val typeAliases = mutableList(message.typeAliasList)
|
||||
|
||||
override fun rebuild(): ProtoBuf.Class = message.toBuilder().apply {
|
||||
clearConstructor().addAllConstructor(constructors)
|
||||
|
||||
if (nestedClassNames.size != nestedClassNameCount) {
|
||||
clearNestedClassName().addAllNestedClassName(nestedClassNames)
|
||||
}
|
||||
if (functions.size != functionCount) {
|
||||
clearFunction().addAllFunction(functions)
|
||||
}
|
||||
if (properties.size != propertyCount) {
|
||||
clearProperty().addAllProperty(properties)
|
||||
}
|
||||
if (typeAliases.size != typeAliasCount) {
|
||||
clearTypeAlias().addAllTypeAlias(typeAliases)
|
||||
}
|
||||
if (sealedSubclassNames.size != sealedSubclassFqNameCount) {
|
||||
clearSealedSubclassFqName().addAllSealedSubclassFqName(sealedSubclassNames)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes elements from a [kotlin.Metadata] annotation that contains
|
||||
* a [ProtoBuf.Package] object in its [d1][kotlin.Metadata.d1] field.
|
||||
*/
|
||||
internal class PackageMetadataTransformer(
|
||||
logger: Logger,
|
||||
deletedFields: Collection<FieldElement>,
|
||||
deletedFunctions: Collection<MethodElement>,
|
||||
handleExtraMethod: (MethodElement) -> Unit,
|
||||
d1: List<String>,
|
||||
d2: List<String>
|
||||
) : MetadataTransformer<ProtoBuf.Package>(
|
||||
logger,
|
||||
deletedFields,
|
||||
deletedFunctions,
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
handleExtraMethod,
|
||||
d1,
|
||||
d2,
|
||||
ProtoBuf.Package::parseFrom
|
||||
) {
|
||||
override val typeTable = TypeTable(message.typeTable)
|
||||
override val properties = mutableList(message.propertyList)
|
||||
override val functions = mutableList(message.functionList)
|
||||
override val typeAliases = mutableList(message.typeAliasList)
|
||||
|
||||
override fun rebuild(): ProtoBuf.Package = message.toBuilder().apply {
|
||||
if (functions.size != functionCount) {
|
||||
clearFunction().addAllFunction(functions)
|
||||
}
|
||||
if (properties.size != propertyCount) {
|
||||
clearProperty().addAllProperty(properties)
|
||||
}
|
||||
if (typeAliases.size != typeAliasCount) {
|
||||
clearTypeAlias().addAllTypeAlias(typeAliases)
|
||||
}
|
||||
}.build()
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
|
||||
interface Repeatable<T : ClassVisitor> {
|
||||
fun recreate(visitor: ClassVisitor): T
|
||||
val hasUnwantedElements: Boolean
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.deserialization.Flags.*
|
||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf.*
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmNameResolver
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.EXTENSION_REGISTRY
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.getJvmConstructorSignature
|
||||
import org.objectweb.asm.AnnotationVisitor
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
/**
|
||||
* This is (hopefully?!) a temporary solution for classes with [JvmOverloads] constructors.
|
||||
* We need to be able to annotate ONLY the secondary constructors for such classes, but Kotlin
|
||||
* will apply any annotation to all constructors equally. Nor can we replace the overloaded
|
||||
* constructor with individual constructors because this will break ABI compatibility. (Kotlin
|
||||
* generates a synthetic public constructor to handle default parameter values.)
|
||||
*
|
||||
* This transformer identifies a class's primary constructor and removes all of its unwanted annotations.
|
||||
* It will become superfluous when Kotlin allows us to target only the secondary constructors with our
|
||||
* filtering annotations in the first place.
|
||||
*/
|
||||
class SanitisingTransformer(visitor: ClassVisitor, logger: Logger, private val unwantedAnnotations: Set<String>)
|
||||
: KotlinBeforeProcessor(ASM6, visitor, logger, mutableMapOf()) {
|
||||
|
||||
var isModified: Boolean = false
|
||||
private set
|
||||
override val level: LogLevel = LogLevel.DEBUG
|
||||
|
||||
private var className: String = "(unknown)"
|
||||
private var primaryConstructor: MethodElement? = null
|
||||
|
||||
override fun processPackageMetadata(d1: List<String>, d2: List<String>): List<String> = emptyList()
|
||||
|
||||
override fun processClassMetadata(d1: List<String>, d2: List<String>): List<String> {
|
||||
val input = ByteArrayInputStream(BitEncoding.decodeBytes(d1.toTypedArray()))
|
||||
val stringTableTypes = StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY)
|
||||
val nameResolver = JvmNameResolver(stringTableTypes, d2.toTypedArray())
|
||||
val message = ProtoBuf.Class.parseFrom(input, EXTENSION_REGISTRY)
|
||||
val typeTable = TypeTable(message.typeTable)
|
||||
|
||||
for (constructor in message.constructorList) {
|
||||
if (!IS_SECONDARY.get(constructor.flags)) {
|
||||
val signature = getJvmConstructorSignature(constructor, nameResolver, typeTable) ?: break
|
||||
primaryConstructor = MethodElement("<init>", signature.drop("<init>".length))
|
||||
logger.log(level, "Class {} has primary constructor {}", className, signature)
|
||||
break
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array<String>?) {
|
||||
className = clsName
|
||||
super.visit(version, access, clsName, signature, superName, interfaces)
|
||||
}
|
||||
|
||||
override fun visitMethod(access: Int, methodName: String, descriptor: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
|
||||
val method = MethodElement(methodName, descriptor, access)
|
||||
val mv = super.visitMethod(access, methodName, descriptor, signature, exceptions) ?: return null
|
||||
return if (method == primaryConstructor) SanitisingMethodAdapter(mv, method) else mv
|
||||
}
|
||||
|
||||
private inner class SanitisingMethodAdapter(mv: MethodVisitor, private val method: MethodElement) : MethodVisitor(api, mv) {
|
||||
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
|
||||
if (unwantedAnnotations.contains(descriptor)) {
|
||||
logger.info("Sanitising annotation {} from method {}.{}{}", descriptor, className, method.name, method.descriptor)
|
||||
isModified = true
|
||||
return null
|
||||
}
|
||||
return super.visitAnnotation(descriptor, visible)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import java.util.Collections.unmodifiableMap
|
||||
|
||||
/**
|
||||
* A persistent cache of all of the classes and methods that JarFilter has
|
||||
* removed. This cache belongs to the Gradle task itself and so is shared
|
||||
* by successive filter passes.
|
||||
*
|
||||
* The internal method cache is only required for those classes which are
|
||||
* being kept. When an entire class is declared as "unwanted", any entry
|
||||
* it may have in the method cache is removed.
|
||||
*/
|
||||
class UnwantedCache {
|
||||
private val _classes: MutableSet<String> = mutableSetOf()
|
||||
private val _classMethods: MutableMap<String, MutableSet<MethodElement>> = mutableMapOf()
|
||||
|
||||
val classes: Set<String> get() = _classes
|
||||
val classMethods: Map<String, Set<MethodElement>> get() = unmodifiableMap(_classMethods)
|
||||
|
||||
fun containsClass(className: String): Boolean = _classes.contains(className)
|
||||
|
||||
fun addClass(className: String): Boolean {
|
||||
return _classes.add(className).also { isAdded ->
|
||||
if (isAdded) {
|
||||
_classMethods.remove(className)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addMethod(className: String, method: MethodElement) {
|
||||
if (!containsClass(className)) {
|
||||
_classMethods.getOrPut(className) { mutableSetOf() }.add(method)
|
||||
}
|
||||
}
|
||||
|
||||
private fun containsMethod(className: String, method: MethodElement): Boolean {
|
||||
return _classMethods[className]?.contains(method) ?: false
|
||||
}
|
||||
|
||||
fun containsMethod(className: String, methodName: String?, methodDescriptor: String?): Boolean {
|
||||
return containsClass(className) ||
|
||||
(methodName != null && methodDescriptor != null && containsMethod(className, MethodElement(methodName, methodDescriptor)))
|
||||
}
|
||||
}
|
||||
|
@ -1,92 +0,0 @@
|
||||
@file:JvmName("Utils")
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.GradleException
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import java.nio.file.attribute.FileTime
|
||||
import java.util.*
|
||||
import java.util.Calendar.FEBRUARY
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipEntry.DEFLATED
|
||||
import java.util.zip.ZipEntry.STORED
|
||||
import kotlin.math.max
|
||||
import kotlin.text.RegexOption.*
|
||||
|
||||
internal val JAR_PATTERN = "(\\.jar)$".toRegex(IGNORE_CASE)
|
||||
|
||||
// Use the same constant file timestamp as Gradle.
|
||||
private val CONSTANT_TIME: FileTime = FileTime.fromMillis(
|
||||
GregorianCalendar(1980, FEBRUARY, 1).apply { timeZone = TimeZone.getTimeZone("UTC") }.timeInMillis
|
||||
)
|
||||
|
||||
internal fun rethrowAsUncheckedException(e: Exception): Nothing
|
||||
= throw (e as? RuntimeException) ?: GradleException(e.message ?: "", e)
|
||||
|
||||
/**
|
||||
* Recreates a [ZipEntry] object. The entry's byte contents
|
||||
* will be compressed automatically, and its CRC, size and
|
||||
* compressed size fields populated.
|
||||
*/
|
||||
internal fun ZipEntry.asCompressed(): ZipEntry {
|
||||
return ZipEntry(name).also { entry ->
|
||||
entry.lastModifiedTime = lastModifiedTime
|
||||
lastAccessTime?.also { at -> entry.lastAccessTime = at }
|
||||
creationTime?.also { ct -> entry.creationTime = ct }
|
||||
entry.comment = comment
|
||||
entry.method = DEFLATED
|
||||
entry.extra = extra
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ZipEntry.copy(): ZipEntry {
|
||||
return if (method == STORED) ZipEntry(this) else asCompressed()
|
||||
}
|
||||
|
||||
internal fun ZipEntry.withFileTimestamps(preserveTimestamps: Boolean): ZipEntry {
|
||||
if (!preserveTimestamps) {
|
||||
lastModifiedTime = CONSTANT_TIME
|
||||
lastAccessTime?.apply { lastAccessTime = CONSTANT_TIME }
|
||||
creationTime?.apply { creationTime = CONSTANT_TIME }
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun <T : Any> mutableList(c: Collection<T>): MutableList<T> = ArrayList(c)
|
||||
|
||||
/**
|
||||
* Converts Java class names to Java descriptors.
|
||||
*/
|
||||
internal fun toDescriptors(classNames: Iterable<String>): Set<String> {
|
||||
return classNames.map(String::descriptor).toSet()
|
||||
}
|
||||
|
||||
internal val String.toPathFormat: String get() = replace('.', '/')
|
||||
internal val String.descriptor: String get() = "L$toPathFormat;"
|
||||
|
||||
|
||||
/**
|
||||
* Performs the given number of passes of the repeatable visitor over the byte-code.
|
||||
* Used by [MetaFixerVisitor], but also by some of the test visitors.
|
||||
*/
|
||||
internal fun <T> ByteArray.execute(visitor: (ClassVisitor) -> T, flags: Int = 0, passes: Int = 2): ByteArray
|
||||
where T : ClassVisitor,
|
||||
T : Repeatable<T> {
|
||||
var bytecode = this
|
||||
var writer = ClassWriter(flags)
|
||||
var transformer = visitor(writer)
|
||||
var count = max(passes, 1)
|
||||
|
||||
while (--count >= 0) {
|
||||
ClassReader(bytecode).accept(transformer, 0)
|
||||
bytecode = writer.toByteArray()
|
||||
|
||||
if (!transformer.hasUnwantedElements) break
|
||||
|
||||
writer = ClassWriter(flags)
|
||||
transformer = transformer.recreate(writer)
|
||||
}
|
||||
|
||||
return bytecode
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.hamcrest.core.IsNot.not
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import java.lang.reflect.Modifier.*
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class AbstractFunctionTest {
|
||||
companion object {
|
||||
private const val FUNCTION_CLASS = "net.corda.gradle.AbstractFunctions"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "abstract-function")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteAbstractFunction() {
|
||||
val longFunction = isFunction("toDelete", Long::class, Long::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getMethod("toDelete", Long::class.java).also { method ->
|
||||
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
|
||||
}
|
||||
assertThat("toDelete(J) not found", kotlin.declaredFunctions, hasItem(longFunction))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getMethod("toDelete", Long::class.java) }
|
||||
assertThat("toDelete(J) still exists", kotlin.declaredFunctions, not(hasItem(longFunction)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cannotStubAbstractFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getMethod("toStubOut", Long::class.java).also { method ->
|
||||
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getMethod("toStubOut", Long::class.java).also { method ->
|
||||
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import net.corda.gradle.unwanted.*
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.hamcrest.core.IsNot.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.reflect.full.declaredMemberFunctions
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteAndStubTests {
|
||||
companion object {
|
||||
private const val VAR_PROPERTY_CLASS = "net.corda.gradle.HasVarPropertyForDeleteAndStub"
|
||||
private const val VAL_PROPERTY_CLASS = "net.corda.gradle.HasValPropertyForDeleteAndStub"
|
||||
private const val DELETED_FUN_CLASS = "net.corda.gradle.DeletedFunctionInsideStubbed"
|
||||
private const val DELETED_VAR_CLASS = "net.corda.gradle.DeletedVarInsideStubbed"
|
||||
private const val DELETED_VAL_CLASS = "net.corda.gradle.DeletedValInsideStubbed"
|
||||
private const val DELETED_PKG_CLASS = "net.corda.gradle.DeletePackageWithStubbed"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-and-stub")
|
||||
private val stringVal = isProperty("stringVal", String::class)
|
||||
private val longVar = isProperty("longVar", Long::class)
|
||||
private val getStringVal = isMethod("getStringVal", String::class.java)
|
||||
private val getLongVar = isMethod("getLongVar", Long::class.java)
|
||||
private val setLongVar = isMethod("setLongVar", Void.TYPE, Long::class.java)
|
||||
private val stringData = isFunction("stringData", String::class)
|
||||
private val unwantedFun = isFunction("unwantedFun", String::class, String::class)
|
||||
private val unwantedVar = isProperty("unwantedVar", String::class)
|
||||
private val unwantedVal = isProperty("unwantedVal", String::class)
|
||||
private val stringDataJava = isMethod("stringData", String::class.java)
|
||||
private val getUnwantedVal = isMethod("getUnwantedVal", String::class.java)
|
||||
private val getUnwantedVar = isMethod("getUnwantedVar", String::class.java)
|
||||
private val setUnwantedVar = isMethod("setUnwantedVar", Void.TYPE, String::class.java)
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteValProperty() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasStringVal>(VAL_PROPERTY_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertEquals(MESSAGE, obj.stringVal)
|
||||
}
|
||||
assertThat("stringVal not found", kotlin.declaredMemberProperties, hasItem(stringVal))
|
||||
assertThat("getStringVal() not found", kotlin.javaDeclaredMethods, hasItem(getStringVal))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasStringVal>(VAL_PROPERTY_CLASS).apply {
|
||||
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
|
||||
assertThat("stringVal still exists", kotlin.declaredMemberProperties, not(hasItem(stringVal)))
|
||||
assertThat("getStringVal() still exists", kotlin.javaDeclaredMethods, not(hasItem(getStringVal)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteVarProperty() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasLongVar>(VAR_PROPERTY_CLASS).apply {
|
||||
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { obj ->
|
||||
assertEquals(BIG_NUMBER, obj.longVar)
|
||||
}
|
||||
assertThat("longVar not found", kotlin.declaredMemberProperties, hasItem(longVar))
|
||||
assertThat("getLongVar() not found", kotlin.javaDeclaredMethods, hasItem(getLongVar))
|
||||
assertThat("setLongVar() not found", kotlin.javaDeclaredMethods, hasItem(setLongVar))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasLongVar>(VAR_PROPERTY_CLASS).apply {
|
||||
assertNotNull(getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER))
|
||||
assertThat("longVar still exists", kotlin.declaredMemberProperties, not(hasItem(longVar)))
|
||||
assertThat("getLongVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(getLongVar)))
|
||||
assertThat("setLongVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(setLongVar)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletedFunctionInsideStubbed() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(DELETED_FUN_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
|
||||
assertEquals(DEFAULT_MESSAGE, obj.stringData())
|
||||
assertEquals(MESSAGE, (obj as HasUnwantedFun).unwantedFun(MESSAGE))
|
||||
}
|
||||
assertThat("unwantedFun not found", kotlin.declaredMemberFunctions, hasItem(unwantedFun))
|
||||
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasString>(DELETED_FUN_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
|
||||
assertFailsWith<UnsupportedOperationException> { obj.stringData() }.also { ex ->
|
||||
assertThat(ex).hasMessage("Method has been deleted")
|
||||
}
|
||||
assertFailsWith<AbstractMethodError> { (obj as HasUnwantedFun).unwantedFun(MESSAGE) }
|
||||
}
|
||||
assertThat("unwantedFun still exists", kotlin.declaredMemberFunctions, not(hasItem(unwantedFun)))
|
||||
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletedVarInsideStubbed() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(DELETED_VAR_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
|
||||
assertEquals(DEFAULT_MESSAGE, obj.stringData())
|
||||
(obj as HasUnwantedVar).also {
|
||||
assertEquals(DEFAULT_MESSAGE, it.unwantedVar)
|
||||
it.unwantedVar = MESSAGE
|
||||
assertEquals(MESSAGE, it.unwantedVar)
|
||||
}
|
||||
}
|
||||
assertThat("unwantedVar not found", kotlin.declaredMemberProperties, hasItem(unwantedVar))
|
||||
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasString>(DELETED_VAR_CLASS).apply {
|
||||
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
|
||||
assertThat("unwantedVar still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
|
||||
assertThat("getUnwantedVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVar)))
|
||||
assertThat("setUnwantedVar() still exists", kotlin.javaDeclaredMethods, not(hasItem(setUnwantedVar)))
|
||||
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
|
||||
assertThat("stringData() not found", kotlin.javaDeclaredMethods, hasItem(stringDataJava))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletedValInsideStubbed() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(DELETED_VAL_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertEquals(MESSAGE, obj.stringData())
|
||||
assertEquals(MESSAGE, (obj as HasUnwantedVal).unwantedVal)
|
||||
}
|
||||
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
|
||||
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasString>(DELETED_VAL_CLASS).apply {
|
||||
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
|
||||
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
|
||||
assertThat("getUnwantedVal() still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
|
||||
assertThat("stringData() not found", kotlin.declaredMemberFunctions, hasItem(stringData))
|
||||
assertThat("stringData() not found", kotlin.javaDeclaredMethods, hasItem(stringDataJava))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletePackageWithStubbed() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(DELETED_PKG_CLASS).apply {
|
||||
getDeclaredMethod("stubbed", String::class.java).also { method ->
|
||||
assertEquals("[$MESSAGE]", method.invoke(null, MESSAGE))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
assertFailsWith<ClassNotFoundException> { cl.load<Any>(DELETED_PKG_CLASS) }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import net.corda.gradle.unwanted.HasAll
|
||||
import net.corda.gradle.unwanted.HasInt
|
||||
import net.corda.gradle.unwanted.HasLong
|
||||
import net.corda.gradle.unwanted.HasString
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.hamcrest.core.IsNot.not
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.jvm.kotlin
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteConstructorTest {
|
||||
companion object {
|
||||
private const val STRING_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryStringConstructorToDelete"
|
||||
private const val LONG_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryLongConstructorToDelete"
|
||||
private const val INT_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryIntConstructorToDelete"
|
||||
private const val SECONDARY_CONSTRUCTOR_CLASS = "net.corda.gradle.HasConstructorToDelete"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-constructor")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteConstructorWithLongParameter() {
|
||||
val longConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, Long::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
|
||||
assertEquals(BIG_NUMBER, it.longData())
|
||||
}
|
||||
assertThat("<init>(J) not found", kotlin.constructors, hasItem(longConstructor))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(Long::class.java) }
|
||||
assertThat("<init>(J) still exists", kotlin.constructors, not(hasItem(longConstructor)))
|
||||
assertNotNull("primary constructor missing", kotlin.primaryConstructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteConstructorWithStringParameter() {
|
||||
val stringConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, String::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
|
||||
assertEquals(MESSAGE, it.stringData())
|
||||
}
|
||||
assertThat("<init>(String) not found", kotlin.constructors, hasItem(stringConstructor))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(String::class.java) }
|
||||
assertThat("<init>(String) still exists", kotlin.constructors, not(hasItem(stringConstructor)))
|
||||
assertNotNull("primary constructor missing", kotlin.primaryConstructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showUnannotatedConstructorIsUnaffected() {
|
||||
val intConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, Int::class)
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasAll>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
|
||||
assertEquals(NUMBER, it.intData())
|
||||
assertEquals(NUMBER.toLong(), it.longData())
|
||||
assertEquals("<nothing>", it.stringData())
|
||||
}
|
||||
assertThat("<init>(Int) not found", kotlin.constructors, hasItem(intConstructor))
|
||||
assertNotNull("primary constructor missing", kotlin.primaryConstructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletePrimaryConstructorWithStringParameter() {
|
||||
val stringConstructor = isConstructor(STRING_PRIMARY_CONSTRUCTOR_CLASS, String::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
|
||||
assertEquals(MESSAGE, it.stringData())
|
||||
}
|
||||
assertThat("<init>(String) not found", kotlin.constructors, hasItem(stringConstructor))
|
||||
assertThat("primary constructor missing", kotlin.primaryConstructor!!, stringConstructor)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(String::class.java) }
|
||||
assertThat("<init>(String) still exists", kotlin.constructors, not(hasItem(stringConstructor)))
|
||||
assertNull("primary constructor still exists", kotlin.primaryConstructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletePrimaryConstructorWithLongParameter() {
|
||||
val longConstructor = isConstructor(LONG_PRIMARY_CONSTRUCTOR_CLASS, Long::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
|
||||
assertEquals(BIG_NUMBER, it.longData())
|
||||
}
|
||||
assertThat("<init>(J) not found", kotlin.constructors, hasItem(longConstructor))
|
||||
assertThat("primary constructor missing", kotlin.primaryConstructor!!, longConstructor)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(Long::class.java) }
|
||||
assertThat("<init>(J) still exists", kotlin.constructors, not(hasItem(longConstructor)))
|
||||
assertNull("primary constructor still exists", kotlin.primaryConstructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletePrimaryConstructorWithIntParameter() {
|
||||
val intConstructor = isConstructor(INT_PRIMARY_CONSTRUCTOR_CLASS, Int::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
|
||||
assertEquals(NUMBER, it.intData())
|
||||
}
|
||||
assertThat("<init>(I) not found", kotlin.constructors, hasItem(intConstructor))
|
||||
assertThat("primary constructor missing", kotlin.primaryConstructor!!, intConstructor)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(Int::class.java) }
|
||||
assertThat("<init>(I) still exists", kotlin.constructors, not(hasItem(intConstructor)))
|
||||
assertNull("primary constructor still exists", kotlin.primaryConstructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.isMethod
|
||||
import net.corda.gradle.jarfilter.matcher.isProperty
|
||||
import net.corda.gradle.jarfilter.matcher.javaDeclaredMethods
|
||||
import net.corda.gradle.unwanted.HasUnwantedVal
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.hamcrest.core.IsNot.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.reflect.full.declaredMemberExtensionProperties
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
|
||||
class DeleteExtensionValPropertyTest {
|
||||
companion object {
|
||||
private const val PROPERTY_CLASS = "net.corda.gradle.HasValExtension"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-extension-val")
|
||||
private val unwantedVal = isProperty("unwantedVal", String::class)
|
||||
private val getUnwantedVal = isMethod("getUnwantedVal", String::class.java, List::class.java)
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteExtensionProperty() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
|
||||
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
|
||||
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
|
||||
assertThat("List.unwantedVal not found", kotlin.declaredMemberExtensionProperties, hasItem(unwantedVal))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
|
||||
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
|
||||
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
|
||||
assertThat("List.unwantedVal still exists", kotlin.declaredMemberExtensionProperties, not(hasItem(unwantedVal)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.hamcrest.core.IsNot.not
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteFieldTest {
|
||||
companion object {
|
||||
private const val STRING_FIELD_CLASS = "net.corda.gradle.HasStringFieldToDelete"
|
||||
private const val INTEGER_FIELD_CLASS = "net.corda.gradle.HasIntFieldToDelete"
|
||||
private const val LONG_FIELD_CLASS = "net.corda.gradle.HasLongFieldToDelete"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-field")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStringField() {
|
||||
val stringField = isProperty("stringField", String::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(STRING_FIELD_CLASS).apply {
|
||||
val obj: Any = getDeclaredConstructor(String::class.java).newInstance(MESSAGE)
|
||||
getDeclaredField("stringField").also { field ->
|
||||
assertEquals(MESSAGE, field.get(obj))
|
||||
}
|
||||
assertThat("stringField not found", kotlin.declaredMemberProperties, hasItem(stringField))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(STRING_FIELD_CLASS).apply {
|
||||
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
|
||||
assertFailsWith<NoSuchFieldException> { getDeclaredField("stringField") }
|
||||
assertThat("stringField still exists", kotlin.declaredMemberProperties, not(hasItem(stringField)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteLongField() {
|
||||
val longField = isProperty("longField", Long::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(LONG_FIELD_CLASS).apply {
|
||||
val obj: Any = getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
|
||||
getDeclaredField("longField").also { field ->
|
||||
assertEquals(BIG_NUMBER, field.get(obj))
|
||||
}
|
||||
assertThat("longField not found", kotlin.declaredMemberProperties, hasItem(longField))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(LONG_FIELD_CLASS).apply {
|
||||
assertNotNull(getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER))
|
||||
assertFailsWith<NoSuchFieldException> { getDeclaredField("longField") }
|
||||
assertThat("longField still exists", kotlin.declaredMemberProperties, not(hasItem(longField)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteIntegerField() {
|
||||
val intField = isProperty("intField", Int::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(INTEGER_FIELD_CLASS).apply {
|
||||
val obj: Any = getDeclaredConstructor(Int::class.java).newInstance(NUMBER)
|
||||
getDeclaredField("intField").also { field ->
|
||||
assertEquals(NUMBER, field.get(obj))
|
||||
}
|
||||
assertThat("intField not found", kotlin.declaredMemberProperties, hasItem(intField))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(INTEGER_FIELD_CLASS).apply {
|
||||
assertNotNull(getDeclaredConstructor(Int::class.java).newInstance(NUMBER))
|
||||
assertFailsWith<NoSuchFieldException> { getDeclaredField("intField") }
|
||||
assertThat("intField still exists", kotlin.declaredMemberProperties, not(hasItem(intField)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import net.corda.gradle.unwanted.HasString
|
||||
import net.corda.gradle.unwanted.HasUnwantedFun
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.hamcrest.core.IsNot.not
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.jvm.kotlin
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteFunctionTest {
|
||||
companion object {
|
||||
private const val FUNCTION_CLASS = "net.corda.gradle.HasFunctionToDelete"
|
||||
private const val INDIRECT_CLASS = "net.corda.gradle.HasIndirectFunctionToDelete"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-function")
|
||||
private val unwantedFun = isFunction("unwantedFun", String::class, String::class)
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedFun>(FUNCTION_CLASS).apply {
|
||||
newInstance().also {
|
||||
assertEquals(MESSAGE, it.unwantedFun(MESSAGE))
|
||||
}
|
||||
assertThat("unwantedFun(String) not found", kotlin.declaredFunctions, hasItem(unwantedFun))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedFun>(FUNCTION_CLASS).apply {
|
||||
newInstance().also {
|
||||
assertFailsWith<AbstractMethodError> { it.unwantedFun(MESSAGE) }
|
||||
}
|
||||
assertThat("unwantedFun(String) still exists", kotlin.declaredFunctions, not(hasItem(unwantedFun)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteIndirectFunction() {
|
||||
val stringData = isFunction("stringData", String::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedFun>(INDIRECT_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
|
||||
assertEquals(MESSAGE, it.unwantedFun(MESSAGE))
|
||||
assertEquals(MESSAGE, (it as HasString).stringData())
|
||||
}
|
||||
assertThat("unwantedFun(String) not found", kotlin.declaredFunctions, hasItem(unwantedFun))
|
||||
assertThat("stringData() not found", kotlin.declaredFunctions, hasItem(stringData))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedFun>(INDIRECT_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
|
||||
assertFailsWith<AbstractMethodError> { it.unwantedFun(MESSAGE) }
|
||||
assertFailsWith<AbstractMethodError> { (it as HasString).stringData() }
|
||||
}
|
||||
assertThat("unwantedFun(String) still exists", kotlin.declaredFunctions, not(hasItem(unwantedFun)))
|
||||
assertThat("stringData still exists", kotlin.declaredFunctions, not(hasItem(stringData)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.isConstructor
|
||||
import net.corda.gradle.unwanted.HasInt
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.hamcrest.core.IsNot.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteInnerLambdaTest {
|
||||
companion object {
|
||||
private const val LAMBDA_CLASS = "net.corda.gradle.HasInnerLambda"
|
||||
private const val SIZE = 64
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-inner-lambda")
|
||||
private val constructInt = isConstructor(LAMBDA_CLASS, Int::class)
|
||||
private val constructBytes = isConstructor(LAMBDA_CLASS, ByteArray::class)
|
||||
|
||||
private lateinit var sourceClasses: List<String>
|
||||
private lateinit var filteredClasses: List<String>
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
sourceClasses = testProject.sourceJar.getClassNames(LAMBDA_CLASS)
|
||||
filteredClasses = testProject.filteredJar.getClassNames(LAMBDA_CLASS)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test lambda class is deleted`() {
|
||||
assertThat(sourceClasses)
|
||||
.contains(LAMBDA_CLASS)
|
||||
.hasSize(2)
|
||||
assertThat(filteredClasses).containsExactly(LAMBDA_CLASS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test host class`() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasInt>(LAMBDA_CLASS).apply {
|
||||
getConstructor(Int::class.java).newInstance(SIZE).also { obj ->
|
||||
assertEquals(SIZE, obj.intData())
|
||||
}
|
||||
kotlin.constructors.also { ctors ->
|
||||
assertThat("<init>(Int) not found", ctors, hasItem(constructInt))
|
||||
assertThat("<init>(byte[]) not found", ctors, hasItem(constructBytes))
|
||||
}
|
||||
|
||||
getConstructor(ByteArray::class.java).newInstance(ByteArray(SIZE)).also { obj ->
|
||||
assertEquals(SIZE, obj.intData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasInt>(LAMBDA_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getConstructor(Int::class.java) }
|
||||
kotlin.constructors.also { ctors ->
|
||||
assertThat("<init>(Int) still exists", ctors, not(hasItem(constructInt)))
|
||||
assertThat("<init>(byte[]) not found", ctors, hasItem(constructBytes))
|
||||
}
|
||||
|
||||
getConstructor(ByteArray::class.java).newInstance(ByteArray(SIZE)).also { obj ->
|
||||
assertEquals(SIZE, obj.intData())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import net.corda.gradle.unwanted.HasUnwantedVal
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.hamcrest.core.IsNot.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteLazyTest {
|
||||
companion object {
|
||||
private const val LAZY_VAL_CLASS = "net.corda.gradle.HasLazyVal"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-lazy")
|
||||
private val unwantedVal = isProperty("unwantedVal", String::class)
|
||||
private val getUnwantedVal = isMethod("getUnwantedVal", String::class.java)
|
||||
private lateinit var sourceClasses: List<String>
|
||||
private lateinit var filteredClasses: List<String>
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
sourceClasses = testProject.sourceJar.getClassNames(LAZY_VAL_CLASS)
|
||||
filteredClasses = testProject.filteredJar.getClassNames(LAZY_VAL_CLASS)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletedClasses() {
|
||||
assertThat(sourceClasses).contains(LAZY_VAL_CLASS)
|
||||
assertThat(filteredClasses).containsExactly(LAZY_VAL_CLASS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteLazyVal() {
|
||||
assertThat(sourceClasses).anyMatch { it.contains("\$unwantedVal\$") }
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedVal>(LAZY_VAL_CLASS).apply {
|
||||
getConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertEquals(MESSAGE, obj.unwantedVal)
|
||||
}
|
||||
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
|
||||
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedVal>(LAZY_VAL_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getConstructor(String::class.java) }
|
||||
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
|
||||
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteMultiFileTest {
|
||||
companion object {
|
||||
private const val MULTIFILE_CLASS = "net.corda.gradle.HasMultiData"
|
||||
private const val STRING_METHOD = "stringToDelete"
|
||||
private const val LONG_METHOD = "longToDelete"
|
||||
private const val INT_METHOD = "intToDelete"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-multifile")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStringFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(MULTIFILE_CLASS).apply {
|
||||
getMethod(STRING_METHOD, String::class.java).also { method ->
|
||||
method.invoke(null, MESSAGE).also { result ->
|
||||
assertThat(result)
|
||||
.isInstanceOf(String::class.java)
|
||||
.isEqualTo(MESSAGE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(MULTIFILE_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getMethod(STRING_METHOD, String::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteLongFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(MULTIFILE_CLASS).apply {
|
||||
getMethod(LONG_METHOD, Long::class.java).also { method ->
|
||||
method.invoke(null, BIG_NUMBER).also { result ->
|
||||
assertThat(result)
|
||||
.isInstanceOf(Long::class.javaObjectType)
|
||||
.isEqualTo(BIG_NUMBER)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(MULTIFILE_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getMethod(LONG_METHOD, Long::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteIntFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(MULTIFILE_CLASS).apply {
|
||||
getMethod(INT_METHOD, Int::class.java).also { method ->
|
||||
method.invoke(null, NUMBER).also { result ->
|
||||
assertThat(result)
|
||||
.isInstanceOf(Int::class.javaObjectType)
|
||||
.isEqualTo(NUMBER)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(MULTIFILE_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getMethod(INT_METHOD, Int::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.classMetadata
|
||||
import net.corda.gradle.jarfilter.matcher.isClass
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.hamcrest.core.IsNot.not
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteNestedClassTest {
|
||||
companion object {
|
||||
private const val HOST_CLASS = "net.corda.gradle.HasNestedClasses"
|
||||
private const val KEPT_CLASS = "$HOST_CLASS\$OneToKeep"
|
||||
private const val DELETED_CLASS = "$HOST_CLASS\$OneToThrowAway"
|
||||
|
||||
private const val SEALED_CLASS = "net.corda.gradle.SealedClass"
|
||||
private const val WANTED_SUBCLASS = "$SEALED_CLASS\$Wanted"
|
||||
private const val UNWANTED_SUBCLASS = "$SEALED_CLASS\$Unwanted"
|
||||
|
||||
private val keptClass = isClass(KEPT_CLASS)
|
||||
private val deletedClass = isClass(DELETED_CLASS)
|
||||
private val wantedSubclass = isClass(WANTED_SUBCLASS)
|
||||
private val unwantedSubclass = isClass(UNWANTED_SUBCLASS)
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-nested-class")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteNestedClass() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
val deleted = cl.load<Any>(DELETED_CLASS)
|
||||
val kept = cl.load<Any>(KEPT_CLASS)
|
||||
cl.load<Any>(HOST_CLASS).apply {
|
||||
assertThat(declaredClasses).containsExactlyInAnyOrder(deleted, kept)
|
||||
assertThat("OneToThrowAway class is missing", kotlin.nestedClasses, hasItem(deletedClass))
|
||||
assertThat("OneToKeep class is missing", kotlin.nestedClasses, hasItem(keptClass))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
assertFailsWith<ClassNotFoundException> { cl.load<Any>(DELETED_CLASS) }
|
||||
val kept = cl.load<Any>(KEPT_CLASS)
|
||||
cl.load<Any>(HOST_CLASS).apply {
|
||||
assertThat(declaredClasses).containsExactly(kept)
|
||||
assertThat("OneToThrowAway class still exists", kotlin.nestedClasses, not(hasItem(deletedClass)))
|
||||
assertThat("OneToKeep class is missing", kotlin.nestedClasses, hasItem(keptClass))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteFromSealedClass() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
val unwanted = cl.load<Any>(UNWANTED_SUBCLASS)
|
||||
val wanted = cl.load<Any>(WANTED_SUBCLASS)
|
||||
cl.load<Any>(SEALED_CLASS).apply {
|
||||
assertTrue(kotlin.isSealed)
|
||||
assertThat(declaredClasses).containsExactlyInAnyOrder(wanted, unwanted)
|
||||
assertThat("Wanted class is missing", kotlin.nestedClasses, hasItem(wantedSubclass))
|
||||
assertThat("Unwanted class is missing", kotlin.nestedClasses, hasItem(unwantedSubclass))
|
||||
assertThat(classMetadata.sealedSubclasses).containsExactlyInAnyOrder(WANTED_SUBCLASS, UNWANTED_SUBCLASS)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
assertFailsWith<ClassNotFoundException> { cl.load<Any>(UNWANTED_SUBCLASS) }
|
||||
val wanted = cl.load<Any>(WANTED_SUBCLASS)
|
||||
cl.load<Any>(SEALED_CLASS).apply {
|
||||
assertTrue(kotlin.isSealed)
|
||||
assertThat(declaredClasses).containsExactly(wanted)
|
||||
assertThat("Unwanted class still exists", kotlin.nestedClasses, not(hasItem(unwantedSubclass)))
|
||||
assertThat("Wanted class is missing", kotlin.nestedClasses, hasItem(wantedSubclass))
|
||||
assertThat(classMetadata.sealedSubclasses).containsExactly(WANTED_SUBCLASS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.unwanted.HasUnwantedFun
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteObjectTest {
|
||||
companion object {
|
||||
private const val OBJECT_CLASS = "net.corda.gradle.HasObjects"
|
||||
private const val UNWANTED_OBJ_METHOD = "getUnwantedObj"
|
||||
private const val UNWANTED_OBJ_FIELD = "unwantedObj"
|
||||
private const val UNWANTED_FUN_METHOD = "unwantedFun"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-object")
|
||||
private lateinit var sourceClasses: List<String>
|
||||
private lateinit var filteredClasses: List<String>
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
sourceClasses = testProject.sourceJar.getClassNames(OBJECT_CLASS)
|
||||
filteredClasses = testProject.filteredJar.getClassNames(OBJECT_CLASS)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletedClasses() {
|
||||
assertThat(sourceClasses).contains(OBJECT_CLASS)
|
||||
assertThat(filteredClasses).containsExactly(OBJECT_CLASS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteObject() {
|
||||
assertThat(sourceClasses).anyMatch { it.contains("\$unwantedObj\$") }
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(OBJECT_CLASS).apply {
|
||||
getDeclaredMethod(UNWANTED_OBJ_METHOD).also { method ->
|
||||
(method.invoke(null) as HasUnwantedFun).also { obj ->
|
||||
assertEquals(MESSAGE, obj.unwantedFun(MESSAGE))
|
||||
}
|
||||
}
|
||||
getDeclaredField(UNWANTED_OBJ_FIELD).also { field ->
|
||||
assertFalse(field.isAccessible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(OBJECT_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod(UNWANTED_OBJ_METHOD) }
|
||||
assertFailsWith<NoSuchFieldException> { getDeclaredField(UNWANTED_OBJ_FIELD) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteFunctionWithObject() {
|
||||
assertThat(sourceClasses).anyMatch { it.contains("\$unwantedFun\$") }
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(OBJECT_CLASS).apply {
|
||||
getDeclaredMethod(UNWANTED_FUN_METHOD).also { method ->
|
||||
assertEquals("<default-value>", method.invoke(null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(OBJECT_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod(UNWANTED_FUN_METHOD) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.classMetadata
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
/**
|
||||
* Sealed classes can have non-nested subclasses, so long as those subclasses
|
||||
* are declared in the same file as the sealed class. Check that the metadata
|
||||
* is still updated correctly in this case.
|
||||
*/
|
||||
class DeleteSealedSubclassTest {
|
||||
companion object {
|
||||
private const val SEALED_CLASS = "net.corda.gradle.SealedBaseClass"
|
||||
private const val WANTED_SUBCLASS = "net.corda.gradle.WantedSubclass"
|
||||
private const val UNWANTED_SUBCLASS = "net.corda.gradle.UnwantedSubclass"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-sealed-subclass")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteUnwantedSubclass() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(WANTED_SUBCLASS)
|
||||
cl.load<Any>(UNWANTED_SUBCLASS)
|
||||
cl.load<Any>(SEALED_CLASS).apply {
|
||||
assertTrue(kotlin.isSealed)
|
||||
assertThat(classMetadata.sealedSubclasses)
|
||||
.containsExactlyInAnyOrder(WANTED_SUBCLASS, UNWANTED_SUBCLASS)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(WANTED_SUBCLASS)
|
||||
assertFailsWith<ClassNotFoundException> { cl.load<Any>(UNWANTED_SUBCLASS) }
|
||||
cl.load<Any>(SEALED_CLASS).apply {
|
||||
assertTrue(kotlin.isSealed)
|
||||
assertThat(classMetadata.sealedSubclasses)
|
||||
.containsExactly(WANTED_SUBCLASS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteStaticFieldTest {
|
||||
companion object {
|
||||
private const val PROPERTY_CLASS = "net.corda.gradle.StaticFieldsToDelete"
|
||||
private const val DEFAULT_BIG_NUMBER: Long = 123456789L
|
||||
private const val DEFAULT_NUMBER: Int = 123456
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-static-field")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStringField() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
val getter = getDeclaredField("stringField")
|
||||
assertEquals(DEFAULT_MESSAGE, getter.get(null))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
assertFailsWith<NoSuchFieldException> { getDeclaredField("stringField") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteLongField() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
val getter = getDeclaredField("longField")
|
||||
assertEquals(DEFAULT_BIG_NUMBER, getter.get(null))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
assertFailsWith<NoSuchFieldException> { getDeclaredField("longField") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteIntField() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
val getter = getDeclaredField("intField")
|
||||
assertEquals(DEFAULT_NUMBER, getter.get(null))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
assertFailsWith<NoSuchFieldException> { getDeclaredField("intField") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteStaticFunctionTest {
|
||||
companion object {
|
||||
private const val FUNCTION_CLASS = "net.corda.gradle.StaticFunctionsToDelete"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-static-function")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStringFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getMethod("unwantedStringToDelete", String::class.java).also { method ->
|
||||
method.invoke(null, MESSAGE).also { result ->
|
||||
assertThat(result)
|
||||
.isInstanceOf(String::class.java)
|
||||
.isEqualTo(MESSAGE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getMethod("unwantedStringToDelete", String::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteLongFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getMethod("unwantedLongToDelete", Long::class.java).also { method ->
|
||||
method.invoke(null, BIG_NUMBER).also { result ->
|
||||
assertThat(result)
|
||||
.isInstanceOf(Long::class.javaObjectType)
|
||||
.isEqualTo(BIG_NUMBER)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getMethod("unwantedLongToDelete", Long::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteIntFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getMethod("unwantedIntToDelete", Int::class.java).also { method ->
|
||||
method.invoke(null, NUMBER).also { result ->
|
||||
assertThat(result)
|
||||
.isInstanceOf(Int::class.javaObjectType)
|
||||
.isEqualTo(NUMBER)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getMethod("unwantedIntToDelete", Int::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteStaticValPropertyTest {
|
||||
companion object {
|
||||
private const val PROPERTY_CLASS = "net.corda.gradle.StaticValToDelete"
|
||||
private const val DEFAULT_BIG_NUMBER: Long = 123456789L
|
||||
private const val DEFAULT_NUMBER: Int = 123456
|
||||
private object LocalBlob
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-static-val")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStringVal() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
val getter = getDeclaredMethod("getStringVal")
|
||||
assertEquals(DEFAULT_MESSAGE, getter.invoke(null))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getStringVal") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteLongVal() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
val getter = getDeclaredMethod("getLongVal")
|
||||
assertEquals(DEFAULT_BIG_NUMBER, getter.invoke(null))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getLongVal") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteIntVal() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
val getter = getDeclaredMethod("getIntVal")
|
||||
assertEquals(DEFAULT_NUMBER, getter.invoke(null))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getIntVal") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteMemberVal() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
val getter = getDeclaredMethod("getMemberVal", Any::class.java)
|
||||
assertEquals(LocalBlob, getter.invoke(null, LocalBlob))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getMemberVal", Any::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteStaticVarPropertyTest {
|
||||
companion object {
|
||||
private const val PROPERTY_CLASS = "net.corda.gradle.StaticVarToDelete"
|
||||
private const val DEFAULT_BIG_NUMBER: Long = 123456789L
|
||||
private const val DEFAULT_NUMBER: Int = 123456
|
||||
private object LocalBlob
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-static-var")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStringVar() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
val getter = getDeclaredMethod("getStringVar")
|
||||
val setter = getDeclaredMethod("setStringVar", String::class.java)
|
||||
assertEquals(DEFAULT_MESSAGE, getter.invoke(null))
|
||||
setter.invoke(null, MESSAGE)
|
||||
assertEquals(MESSAGE, getter.invoke(null))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getStringVar") }
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setStringVar", String::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteLongVar() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
val getter = getDeclaredMethod("getLongVar")
|
||||
val setter = getDeclaredMethod("setLongVar", Long::class.java)
|
||||
assertEquals(DEFAULT_BIG_NUMBER, getter.invoke(null))
|
||||
setter.invoke(null, BIG_NUMBER)
|
||||
assertEquals(BIG_NUMBER, getter.invoke(null))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getLongVar") }
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setLongVar", Long::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteIntVar() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
val getter = getDeclaredMethod("getIntVar")
|
||||
val setter = getDeclaredMethod("setIntVar", Int::class.java)
|
||||
assertEquals(DEFAULT_NUMBER, getter.invoke(null))
|
||||
setter.invoke(null, NUMBER)
|
||||
assertEquals(NUMBER, getter.invoke(null))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getIntVar") }
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setIntVar", Int::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteMemberVar() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
val getter = getDeclaredMethod("getMemberVar", Any::class.java)
|
||||
val setter = getDeclaredMethod("setMemberVar", Any::class.java, Any::class.java)
|
||||
assertEquals(LocalBlob, getter.invoke(null, LocalBlob))
|
||||
setter.invoke(null, LocalBlob, LocalBlob)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(PROPERTY_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("getMemberVar", Any::class.java) }
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredMethod("setMemberVar", Any::class.java, Any::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.fileMetadata
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
|
||||
class DeleteTypeAliasFromFileTest {
|
||||
companion object {
|
||||
private const val TYPEALIAS_CLASS = "net.corda.gradle.FileWithTypeAlias"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-file-typealias")
|
||||
private lateinit var sourceClasses: List<String>
|
||||
private lateinit var filteredClasses: List<String>
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
sourceClasses = testProject.sourceJar.getClassNames(TYPEALIAS_CLASS)
|
||||
filteredClasses = testProject.filteredJar.getClassNames(TYPEALIAS_CLASS)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteTypeAlias() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
val metadata = cl.load<Any>(TYPEALIAS_CLASS).fileMetadata
|
||||
assertThat(metadata.typeAliasNames)
|
||||
.containsExactlyInAnyOrder("FileWantedType", "FileUnwantedType")
|
||||
}
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
val metadata = cl.load<Any>(TYPEALIAS_CLASS).fileMetadata
|
||||
assertThat(metadata.typeAliasNames)
|
||||
.containsExactly("FileWantedType")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import net.corda.gradle.unwanted.HasUnwantedVal
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.hamcrest.core.IsNot.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteValPropertyTest {
|
||||
companion object {
|
||||
private const val PROPERTY_CLASS = "net.corda.gradle.HasValPropertyForDelete"
|
||||
private const val GETTER_CLASS = "net.corda.gradle.HasValGetterForDelete"
|
||||
private const val JVM_FIELD_CLASS = "net.corda.gradle.HasValJvmFieldForDelete"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-val-property")
|
||||
private val unwantedVal = isProperty("unwantedVal", String::class)
|
||||
private val getUnwantedVal = isMethod("getUnwantedVal", String::class.java)
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteProperty() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertEquals(MESSAGE, obj.unwantedVal)
|
||||
}
|
||||
assertFalse(getDeclaredField("unwantedVal").isAccessible)
|
||||
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
|
||||
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertFailsWith<AbstractMethodError> { obj.unwantedVal }
|
||||
}
|
||||
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVal") }
|
||||
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
|
||||
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteGetter() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedVal>(GETTER_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertEquals(MESSAGE, obj.unwantedVal)
|
||||
}
|
||||
assertFalse(getDeclaredField("unwantedVal").isAccessible)
|
||||
assertThat("getUnwantedVal not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVal))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedVal>(GETTER_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertFailsWith<AbstractMethodError> { obj.unwantedVal }
|
||||
}
|
||||
assertFalse(getDeclaredField("unwantedVal").isAccessible)
|
||||
assertThat("getUnwantedVal still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVal)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteJvmField() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(JVM_FIELD_CLASS).apply {
|
||||
val obj = getDeclaredConstructor(String::class.java).newInstance(MESSAGE)
|
||||
getDeclaredField("unwantedVal").also { field ->
|
||||
assertEquals(MESSAGE, field.get(obj))
|
||||
}
|
||||
assertThat("unwantedVal not found", kotlin.declaredMemberProperties, hasItem(unwantedVal))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(JVM_FIELD_CLASS).apply {
|
||||
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(MESSAGE))
|
||||
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVal") }
|
||||
assertThat("unwantedVal still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import net.corda.gradle.unwanted.HasUnwantedVar
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.hamcrest.core.IsNot.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class DeleteVarPropertyTest {
|
||||
companion object {
|
||||
private const val PROPERTY_CLASS = "net.corda.gradle.HasUnwantedVarPropertyForDelete"
|
||||
private const val GETTER_CLASS = "net.corda.gradle.HasUnwantedGetForDelete"
|
||||
private const val SETTER_CLASS = "net.corda.gradle.HasUnwantedSetForDelete"
|
||||
private const val JVM_FIELD_CLASS = "net.corda.gradle.HasVarJvmFieldForDelete"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "delete-var-property")
|
||||
private val unwantedVar = isProperty("unwantedVar", String::class)
|
||||
private val getUnwantedVar = isMethod("getUnwantedVar", String::class.java)
|
||||
private val setUnwantedVar = isMethod("setUnwantedVar", Void.TYPE, String::class.java)
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteProperty() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedVar>(PROPERTY_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
|
||||
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
|
||||
obj.unwantedVar = MESSAGE
|
||||
assertEquals(MESSAGE, obj.unwantedVar)
|
||||
}
|
||||
assertFalse(getDeclaredField("unwantedVar").isAccessible)
|
||||
assertThat("unwantedVar not found", kotlin.declaredMemberProperties, hasItem(unwantedVar))
|
||||
assertThat("getUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVar))
|
||||
assertThat("setUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(setUnwantedVar))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedVar>(PROPERTY_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
|
||||
assertFailsWith<AbstractMethodError> { obj.unwantedVar }
|
||||
assertFailsWith<AbstractMethodError> { obj.unwantedVar = MESSAGE }
|
||||
}
|
||||
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVar") }
|
||||
assertThat("unwantedVar still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
|
||||
assertThat("getUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVar)))
|
||||
assertThat("setUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(setUnwantedVar)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteGetter() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedVar>(GETTER_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertEquals(MESSAGE, obj.unwantedVar)
|
||||
}
|
||||
assertFalse(getDeclaredField("unwantedVar").isAccessible)
|
||||
assertThat("getUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(getUnwantedVar))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedVar>(GETTER_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertFailsWith<AbstractMethodError> { obj.unwantedVar }
|
||||
}
|
||||
assertFalse(getDeclaredField("unwantedVar").isAccessible)
|
||||
assertThat("getUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(getUnwantedVar)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteSetter() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
|
||||
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
|
||||
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
|
||||
obj.unwantedVar = MESSAGE
|
||||
assertEquals(MESSAGE, obj.unwantedVar)
|
||||
}
|
||||
getDeclaredField("unwantedVar").also { field ->
|
||||
assertFalse(field.isAccessible)
|
||||
}
|
||||
assertThat("setUnwantedVar not found", kotlin.javaDeclaredMethods, hasItem(setUnwantedVar))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
|
||||
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
|
||||
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
|
||||
assertFailsWith<AbstractMethodError> { obj.unwantedVar = MESSAGE }
|
||||
}
|
||||
getDeclaredField("unwantedVar").also { field ->
|
||||
assertFalse(field.isAccessible)
|
||||
}
|
||||
assertThat("setUnwantedVar still exists", kotlin.javaDeclaredMethods, not(hasItem(setUnwantedVar)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteJvmField() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(JVM_FIELD_CLASS).apply {
|
||||
val obj: Any = getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE)
|
||||
getDeclaredField("unwantedVar").also { field ->
|
||||
assertEquals(DEFAULT_MESSAGE, field.get(obj))
|
||||
field.set(obj, MESSAGE)
|
||||
assertEquals(MESSAGE, field.get(obj))
|
||||
}
|
||||
assertThat("unwantedVar not found", kotlin.declaredMemberProperties, hasItem(unwantedVar))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(JVM_FIELD_CLASS).apply {
|
||||
assertNotNull(getDeclaredConstructor(String::class.java).newInstance(DEFAULT_MESSAGE))
|
||||
assertFailsWith<NoSuchFieldException> { getDeclaredField("unwantedVar") }
|
||||
assertThat("unwantedVar still exists", kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.bytecode
|
||||
import net.corda.gradle.jarfilter.asm.resourceName
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.jar.Attributes.Name.MANIFEST_VERSION
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.jar.Manifest
|
||||
import java.util.zip.CRC32
|
||||
import java.util.zip.Deflater.NO_COMPRESSION
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipEntry.*
|
||||
|
||||
/**
|
||||
* Creates a dummy jar containing the following:
|
||||
* - META-INF/MANIFEST.MF
|
||||
* - A compressed class file
|
||||
* - A compressed binary non-class file
|
||||
* - An uncompressed text file
|
||||
* - A directory entry
|
||||
*
|
||||
* The compression level is set to NO_COMPRESSION
|
||||
* in order to force the Gradle task to compress
|
||||
* the entries properly.
|
||||
*/
|
||||
class DummyJar(
|
||||
private val projectDir: TemporaryFolder,
|
||||
private val testClass: Class<*>,
|
||||
private val name: String
|
||||
) : TestRule {
|
||||
private companion object {
|
||||
private const val DATA_SIZE = 512
|
||||
|
||||
private fun uncompressed(name: String, data: ByteArray) = ZipEntry(name).apply {
|
||||
method = STORED
|
||||
compressedSize = data.size.toLong()
|
||||
size = data.size.toLong()
|
||||
crc = CRC32().let { crc ->
|
||||
crc.update(data)
|
||||
crc.value
|
||||
}
|
||||
}
|
||||
|
||||
private fun compressed(name: String) = ZipEntry(name).apply { method = DEFLATED }
|
||||
|
||||
private fun directoryOf(type: Class<*>)
|
||||
= directory(type.`package`.name.toPathFormat + '/')
|
||||
|
||||
private fun directory(name: String) = ZipEntry(name).apply {
|
||||
method = STORED
|
||||
compressedSize = 0
|
||||
size = 0
|
||||
crc = 0
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var _path: Path
|
||||
val path: Path get() = _path
|
||||
|
||||
override fun apply(base: Statement, description: Description): Statement {
|
||||
return object : Statement() {
|
||||
override fun evaluate() {
|
||||
val manifest = Manifest().apply {
|
||||
mainAttributes.also { main ->
|
||||
main[MANIFEST_VERSION] = "1.0"
|
||||
}
|
||||
}
|
||||
_path = projectDir.pathOf("$name.jar")
|
||||
JarOutputStream(Files.newOutputStream(_path), manifest).use { jar ->
|
||||
jar.setComment(testClass.name)
|
||||
jar.setLevel(NO_COMPRESSION)
|
||||
|
||||
// One directory entry (stored)
|
||||
jar.putNextEntry(directoryOf(testClass))
|
||||
|
||||
// One compressed class file
|
||||
jar.putNextEntry(compressed(testClass.resourceName))
|
||||
jar.write(testClass.bytecode)
|
||||
|
||||
// One compressed non-class file
|
||||
jar.putNextEntry(compressed("binary.dat"))
|
||||
jar.write(arrayOfJunk(DATA_SIZE))
|
||||
|
||||
// One uncompressed text file
|
||||
val text = """Jar: ${_path.toAbsolutePath()}
|
||||
Class: ${testClass.name}
|
||||
""".toByteArray()
|
||||
jar.putNextEntry(uncompressed("comment.txt", text))
|
||||
jar.write(text)
|
||||
}
|
||||
assertThat(_path).isRegularFile()
|
||||
|
||||
base.evaluate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
@file:JvmName("EmptyPackage")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
/*
|
||||
* We need to put something in here so that Kotlin will create a class file.
|
||||
*/
|
||||
const val PLACEHOLDER = 0
|
@ -1,32 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class FieldElementTest {
|
||||
private companion object {
|
||||
private const val DESCRIPTOR = "Ljava.lang.String;"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFieldsMatchByNameOnly() {
|
||||
val elt = FieldElement(name = "fieldName", descriptor = DESCRIPTOR)
|
||||
assertEquals(FieldElement(name = "fieldName"), elt)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFieldWithDescriptorDoesNotExpire() {
|
||||
val elt = FieldElement(name = "fieldName", descriptor = DESCRIPTOR)
|
||||
assertFalse(elt.isExpired)
|
||||
assertFalse(elt.isExpired)
|
||||
assertFalse(elt.isExpired)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFieldWithoutDescriptorDoesExpire() {
|
||||
val elt = FieldElement(name = "fieldName")
|
||||
assertFalse(elt.isExpired)
|
||||
assertTrue(elt.isExpired)
|
||||
assertTrue(elt.isExpired)
|
||||
}
|
||||
}
|
@ -1,212 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.annotations.Deletable
|
||||
import net.corda.gradle.jarfilter.asm.bytecode
|
||||
import net.corda.gradle.jarfilter.asm.toClass
|
||||
import net.corda.gradle.jarfilter.matcher.isProperty
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.hamcrest.core.IsEqual.equalTo
|
||||
import org.hamcrest.core.IsNot.not
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertThat
|
||||
import org.junit.Test
|
||||
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Demonstrate that we can still instantiate objects, even after we've deleted
|
||||
* one of their properties. (Check we haven't blown the constructor away!)
|
||||
*/
|
||||
class FieldRemovalTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(FieldRemovalTest::class)
|
||||
private const val SHORT_NUMBER = 999.toShort()
|
||||
private const val BYTE_NUMBER = 99.toByte()
|
||||
private const val BIG_FLOATING_POINT = 9999999.9999
|
||||
private const val FLOATING_POINT = 9999.99f
|
||||
|
||||
private val objectField = isProperty(equalTo("objectField"), equalTo("T"))
|
||||
private val longField = isProperty("longField", Long::class)
|
||||
private val intField = isProperty("intField", Int::class)
|
||||
private val shortField = isProperty("shortField", Short::class)
|
||||
private val byteField = isProperty("byteField", Byte::class)
|
||||
private val charField = isProperty("charField", Char::class)
|
||||
private val booleanField = isProperty("booleanField", Boolean::class)
|
||||
private val doubleField = isProperty("doubleField", Double::class)
|
||||
private val floatField = isProperty("floatField", Float::class)
|
||||
private val arrayField = isProperty("arrayField", ByteArray::class)
|
||||
}
|
||||
|
||||
private inline fun <reified T: R, reified R: Any> transform(): Class<out R> = transform(T::class.java, R::class.java)
|
||||
|
||||
private fun <T: R, R: Any> transform(type: Class<in T>, asType: Class<out R>): Class<out R> {
|
||||
val bytecode = type.bytecode.execute({ writer ->
|
||||
FilterTransformer(
|
||||
visitor = writer,
|
||||
logger = logger,
|
||||
removeAnnotations = emptySet(),
|
||||
deleteAnnotations = setOf(Deletable::class.jvmName.descriptor),
|
||||
stubAnnotations = emptySet(),
|
||||
unwantedElements = UnwantedCache()
|
||||
)
|
||||
}, COMPUTE_MAXS)
|
||||
return bytecode.toClass(type, asType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeObject() {
|
||||
val sourceField = SampleGenericField(MESSAGE)
|
||||
assertEquals(MESSAGE, sourceField.objectField)
|
||||
assertThat("objectField not found", sourceField::class.declaredMemberProperties, hasItem(objectField))
|
||||
|
||||
val targetField = transform<SampleGenericField<String>, HasGenericField<String>>()
|
||||
.getDeclaredConstructor(Any::class.java).newInstance(MESSAGE)
|
||||
assertFailsWith<AbstractMethodError> { targetField.objectField }
|
||||
assertFailsWith<AbstractMethodError> { targetField.objectField = "New Value" }
|
||||
assertThat("objectField still exists", targetField::class.declaredMemberProperties, not(hasItem(objectField)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeLong() {
|
||||
val sourceField = SampleLongField(BIG_NUMBER)
|
||||
assertEquals(BIG_NUMBER, sourceField.longField)
|
||||
assertThat("longField not found", sourceField::class.declaredMemberProperties, hasItem(longField))
|
||||
|
||||
val targetField = transform<SampleLongField, HasLongField>()
|
||||
.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
|
||||
assertFailsWith<AbstractMethodError> { targetField.longField }
|
||||
assertFailsWith<AbstractMethodError> { targetField.longField = 10L }
|
||||
assertThat("longField still exists", targetField::class.declaredMemberProperties, not(hasItem(longField)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeInt() {
|
||||
val sourceField = SampleIntField(NUMBER)
|
||||
assertEquals(NUMBER, sourceField.intField)
|
||||
assertThat("intField not found", sourceField::class.declaredMemberProperties, hasItem(intField))
|
||||
|
||||
val targetField = transform<SampleIntField, HasIntField>()
|
||||
.getDeclaredConstructor(Int::class.java).newInstance(NUMBER)
|
||||
assertFailsWith<AbstractMethodError> { targetField.intField }
|
||||
assertFailsWith<AbstractMethodError> { targetField.intField = 100 }
|
||||
assertThat("intField still exists", targetField::class.declaredMemberProperties, not(hasItem(intField)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeShort() {
|
||||
val sourceField = SampleShortField(SHORT_NUMBER)
|
||||
assertEquals(SHORT_NUMBER, sourceField.shortField)
|
||||
assertThat("shortField not found", sourceField::class.declaredMemberProperties, hasItem(shortField))
|
||||
|
||||
val targetField = transform<SampleShortField, HasShortField>()
|
||||
.getDeclaredConstructor(Short::class.java).newInstance(SHORT_NUMBER)
|
||||
assertFailsWith<AbstractMethodError> { targetField.shortField }
|
||||
assertFailsWith<AbstractMethodError> { targetField.shortField = 15 }
|
||||
assertThat("shortField still exists", targetField::class.declaredMemberProperties, not(hasItem(shortField)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeByte() {
|
||||
val sourceField = SampleByteField(BYTE_NUMBER)
|
||||
assertEquals(BYTE_NUMBER, sourceField.byteField)
|
||||
assertThat("byteField not found", sourceField::class.declaredMemberProperties, hasItem(byteField))
|
||||
|
||||
val targetField = transform<SampleByteField, HasByteField>()
|
||||
.getDeclaredConstructor(Byte::class.java).newInstance(BYTE_NUMBER)
|
||||
assertFailsWith<AbstractMethodError> { targetField.byteField }
|
||||
assertFailsWith<AbstractMethodError> { targetField.byteField = 16 }
|
||||
assertThat("byteField still exists", targetField::class.declaredMemberProperties, not(hasItem(byteField)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeBoolean() {
|
||||
val sourceField = SampleBooleanField(true)
|
||||
assertTrue(sourceField.booleanField)
|
||||
assertThat("booleanField not found", sourceField::class.declaredMemberProperties, hasItem(booleanField))
|
||||
|
||||
val targetField = transform<SampleBooleanField, HasBooleanField>()
|
||||
.getDeclaredConstructor(Boolean::class.java).newInstance(true)
|
||||
assertFailsWith<AbstractMethodError> { targetField.booleanField }
|
||||
assertFailsWith<AbstractMethodError> { targetField.booleanField = false }
|
||||
assertThat("booleanField still exists", targetField::class.declaredMemberProperties, not(hasItem(booleanField)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeChar() {
|
||||
val sourceField = SampleCharField('?')
|
||||
assertEquals('?', sourceField.charField)
|
||||
assertThat("charField not found", sourceField::class.declaredMemberProperties, hasItem(charField))
|
||||
|
||||
val targetField = transform<SampleCharField, HasCharField>()
|
||||
.getDeclaredConstructor(Char::class.java).newInstance('?')
|
||||
assertFailsWith<AbstractMethodError> { targetField.charField }
|
||||
assertFailsWith<AbstractMethodError> { targetField.charField = 'A' }
|
||||
assertThat("charField still exists", targetField::class.declaredMemberProperties, not(hasItem(charField)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeDouble() {
|
||||
val sourceField = SampleDoubleField(BIG_FLOATING_POINT)
|
||||
assertEquals(BIG_FLOATING_POINT, sourceField.doubleField)
|
||||
assertThat("doubleField not found", sourceField::class.declaredMemberProperties, hasItem(doubleField))
|
||||
|
||||
val targetField = transform<SampleDoubleField, HasDoubleField>()
|
||||
.getDeclaredConstructor(Double::class.java).newInstance(BIG_FLOATING_POINT)
|
||||
assertFailsWith<AbstractMethodError> { targetField.doubleField }
|
||||
assertFailsWith<AbstractMethodError> { targetField.doubleField = 12345.678 }
|
||||
assertThat("doubleField still exists", targetField::class.declaredMemberProperties, not(hasItem(doubleField)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeFloat() {
|
||||
val sourceField = SampleFloatField(FLOATING_POINT)
|
||||
assertEquals(FLOATING_POINT, sourceField.floatField)
|
||||
assertThat("floatField not found", sourceField::class.declaredMemberProperties, hasItem(floatField))
|
||||
|
||||
val targetField = transform<SampleFloatField, HasFloatField>()
|
||||
.getDeclaredConstructor(Float::class.java).newInstance(FLOATING_POINT)
|
||||
assertFailsWith<AbstractMethodError> { targetField.floatField }
|
||||
assertFailsWith<AbstractMethodError> { targetField.floatField = 123.45f }
|
||||
assertThat("floatField still exists", targetField::class.declaredMemberProperties, not(hasItem(floatField)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeArray() {
|
||||
val sourceField = SampleArrayField(byteArrayOf())
|
||||
assertArrayEquals(byteArrayOf(), sourceField.arrayField)
|
||||
assertThat("arrayField not found", sourceField::class.declaredMemberProperties, hasItem(arrayField))
|
||||
|
||||
val targetField = transform<SampleArrayField, HasArrayField>()
|
||||
.getDeclaredConstructor(ByteArray::class.java).newInstance(byteArrayOf())
|
||||
assertFailsWith<AbstractMethodError> { targetField.arrayField }
|
||||
assertFailsWith<AbstractMethodError> { targetField.arrayField = byteArrayOf(0x35, 0x73) }
|
||||
assertThat("arrayField still exists", targetField::class.declaredMemberProperties, not(hasItem(arrayField)))
|
||||
}
|
||||
}
|
||||
|
||||
interface HasGenericField<T> { var objectField: T }
|
||||
interface HasLongField { var longField: Long }
|
||||
interface HasIntField { var intField: Int }
|
||||
interface HasShortField { var shortField: Short }
|
||||
interface HasByteField { var byteField: Byte }
|
||||
interface HasBooleanField { var booleanField: Boolean }
|
||||
interface HasCharField { var charField: Char }
|
||||
interface HasFloatField { var floatField: Float }
|
||||
interface HasDoubleField { var doubleField: Double }
|
||||
interface HasArrayField { var arrayField: ByteArray }
|
||||
|
||||
internal class SampleGenericField<T>(@Deletable override var objectField: T) : HasGenericField<T>
|
||||
internal class SampleLongField(@Deletable override var longField: Long) : HasLongField
|
||||
internal class SampleIntField(@Deletable override var intField: Int) : HasIntField
|
||||
internal class SampleShortField(@Deletable override var shortField: Short) : HasShortField
|
||||
internal class SampleByteField(@Deletable override var byteField: Byte) : HasByteField
|
||||
internal class SampleBooleanField(@Deletable override var booleanField: Boolean) : HasBooleanField
|
||||
internal class SampleCharField(@Deletable override var charField: Char) : HasCharField
|
||||
internal class SampleFloatField(@Deletable override var floatField: Float) : HasFloatField
|
||||
internal class SampleDoubleField(@Deletable override var doubleField: Double) : HasDoubleField
|
||||
internal class SampleArrayField(@Deletable override var arrayField: ByteArray) : HasArrayField
|
@ -1,61 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import java.lang.reflect.Modifier.*
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class InterfaceFunctionTest {
|
||||
companion object {
|
||||
private const val FUNCTION_CLASS = "net.corda.gradle.InterfaceFunctions"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "interface-function")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteInterfaceFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getMethod("toDelete", Long::class.java).also { method ->
|
||||
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
assertFailsWith<NoSuchMethodException> { getMethod("toDelete", Long::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cannotStubInterfaceFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getMethod("toStubOut", Long::class.java).also { method ->
|
||||
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getMethod("toStubOut", Long::class.java).also { method ->
|
||||
assertEquals(ABSTRACT, method.modifiers and ABSTRACT)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,272 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
import org.gradle.testkit.runner.BuildTask
|
||||
import org.gradle.testkit.runner.GradleRunner
|
||||
import org.gradle.testkit.runner.TaskOutcome.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
|
||||
class JarFilterConfigurationTest {
|
||||
private companion object {
|
||||
private const val AMBIGUOUS = "net.corda.gradle.jarfilter.Ambiguous"
|
||||
private const val DELETE = "net.corda.gradle.jarfilter.DeleteMe"
|
||||
private const val REMOVE = "net.corda.gradle.jarfilter.RemoveMe"
|
||||
private const val STUB = "net.corda.gradle.jarfilter.StubMeOut"
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testProjectDir = TemporaryFolder()
|
||||
|
||||
private lateinit var output: String
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
testProjectDir.installResource("gradle.properties")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkNoJarMeansNoSource() {
|
||||
val result = gradleProject("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
annotations {
|
||||
forDelete = ["$DELETE"]
|
||||
}
|
||||
}
|
||||
""").build()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
val jarFilter = result.forTask("jarFilter")
|
||||
assertEquals(NO_SOURCE, jarFilter.outcome)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkWithMissingJar() {
|
||||
val result = gradleProject("""
|
||||
plugins {
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars = file('does-not-exist.jar')
|
||||
}
|
||||
""").buildAndFail()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
assertThat(output).containsSubsequence(
|
||||
"Caused by: org.gradle.api.GradleException:",
|
||||
"Caused by: java.io.FileNotFoundException:"
|
||||
)
|
||||
|
||||
val jarFilter = result.forTask("jarFilter")
|
||||
assertEquals(FAILED, jarFilter.outcome)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkSameAnnotationForRemoveAndDelete() {
|
||||
val result = gradleProject("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars = jar
|
||||
annotations {
|
||||
forDelete = ["$AMBIGUOUS"]
|
||||
forRemove = ["$AMBIGUOUS"]
|
||||
}
|
||||
}
|
||||
""").buildAndFail()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
assertThat(output).containsSequence(
|
||||
"Caused by: org.gradle.api.InvalidUserDataException: Annotation 'net.corda.gradle.jarfilter.Ambiguous' also appears in JarFilter 'forDelete' section"
|
||||
)
|
||||
|
||||
val jarFilter = result.forTask("jarFilter")
|
||||
assertEquals(FAILED, jarFilter.outcome)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkSameAnnotationForRemoveAndStub() {
|
||||
val result = gradleProject("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars = jar
|
||||
annotations {
|
||||
forStub = ["$AMBIGUOUS"]
|
||||
forRemove = ["$AMBIGUOUS"]
|
||||
}
|
||||
}
|
||||
""").buildAndFail()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
assertThat(output).containsSequence(
|
||||
"Caused by: org.gradle.api.InvalidUserDataException: Annotation 'net.corda.gradle.jarfilter.Ambiguous' also appears in JarFilter 'forStub' section"
|
||||
)
|
||||
|
||||
val jarFilter = result.forTask("jarFilter")
|
||||
assertEquals(FAILED, jarFilter.outcome)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkSameAnnotationForStubAndDelete() {
|
||||
val result = gradleProject("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars = jar
|
||||
annotations {
|
||||
forStub = ["$AMBIGUOUS"]
|
||||
forDelete = ["$AMBIGUOUS"]
|
||||
}
|
||||
}
|
||||
""").buildAndFail()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
assertThat(output).containsSequence(
|
||||
"Caused by: org.gradle.api.InvalidUserDataException: Annotation 'net.corda.gradle.jarfilter.Ambiguous' also appears in JarFilter 'forStub' section"
|
||||
)
|
||||
|
||||
val jarFilter = result.forTask("jarFilter")
|
||||
assertEquals(FAILED, jarFilter.outcome)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkSameAnnotationForStubAndDeleteAndRemove() {
|
||||
val result = gradleProject("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars = jar
|
||||
annotations {
|
||||
forStub = ["$AMBIGUOUS"]
|
||||
forDelete = ["$AMBIGUOUS"]
|
||||
forRemove = ["$AMBIGUOUS"]
|
||||
}
|
||||
}
|
||||
""").buildAndFail()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
assertThat(output).containsSequence(
|
||||
"Caused by: org.gradle.api.InvalidUserDataException: Annotation 'net.corda.gradle.jarfilter.Ambiguous' also appears in JarFilter 'forDelete' section"
|
||||
)
|
||||
|
||||
val jarFilter = result.forTask("jarFilter")
|
||||
assertEquals(FAILED, jarFilter.outcome)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkRepeatedAnnotationForDelete() {
|
||||
val result = gradleProject("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars = jar
|
||||
annotations {
|
||||
forDelete = ["$DELETE", "$DELETE"]
|
||||
}
|
||||
}
|
||||
""").build()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
val jarFilter = result.forTask("jarFilter")
|
||||
assertEquals(SUCCESS, jarFilter.outcome)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkRepeatedAnnotationForStub() {
|
||||
val result = gradleProject("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars = jar
|
||||
annotations {
|
||||
forStub = ["$STUB", "$STUB"]
|
||||
}
|
||||
}
|
||||
""").build()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
val jarFilter = result.forTask("jarFilter")
|
||||
assertEquals(SUCCESS, jarFilter.outcome)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkRepeatedAnnotationForRemove() {
|
||||
val result = gradleProject("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars = jar
|
||||
annotations {
|
||||
forRemove = ["$REMOVE", "$REMOVE"]
|
||||
}
|
||||
}
|
||||
""").build()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
val jarFilter = result.forTask("jarFilter")
|
||||
assertEquals(SUCCESS, jarFilter.outcome)
|
||||
}
|
||||
|
||||
private fun gradleProject(script: String): GradleRunner {
|
||||
testProjectDir.newFile("build.gradle").writeText(script)
|
||||
return GradleRunner.create()
|
||||
.withProjectDir(testProjectDir.root)
|
||||
.withArguments(getBasicArgsForTasks("jarFilter"))
|
||||
.withPluginClasspath()
|
||||
}
|
||||
|
||||
private fun BuildResult.forTask(name: String): BuildTask {
|
||||
return task(":$name") ?: throw AssertionError("No outcome for $name task")
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.testkit.runner.GradleRunner
|
||||
import org.gradle.testkit.runner.TaskOutcome.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
import java.io.FileNotFoundException
|
||||
import java.nio.file.Path
|
||||
|
||||
class JarFilterProject(private val projectDir: TemporaryFolder, private val name: String) : TestRule {
|
||||
private var _sourceJar: Path? = null
|
||||
val sourceJar: Path get() = _sourceJar ?: throw FileNotFoundException("Input not found")
|
||||
|
||||
private var _filteredJar: Path? = null
|
||||
val filteredJar: Path get() = _filteredJar ?: throw FileNotFoundException("Output not found")
|
||||
|
||||
private var _output: String = ""
|
||||
val output: String get() = _output
|
||||
|
||||
override fun apply(statement: Statement, description: Description): Statement {
|
||||
return object : Statement() {
|
||||
override fun evaluate() {
|
||||
projectDir.installResources(
|
||||
"$name/build.gradle",
|
||||
"repositories.gradle",
|
||||
"gradle.properties",
|
||||
"settings.gradle"
|
||||
)
|
||||
|
||||
val result = GradleRunner.create()
|
||||
.withProjectDir(projectDir.root)
|
||||
.withArguments(getGradleArgsForTasks("jarFilter"))
|
||||
.withPluginClasspath()
|
||||
.build()
|
||||
_output = result.output
|
||||
println(output)
|
||||
|
||||
val jarFilter = result.task(":jarFilter")
|
||||
?: throw AssertionError("No outcome for jarFilter task")
|
||||
assertEquals(SUCCESS, jarFilter.outcome)
|
||||
|
||||
_sourceJar = projectDir.pathOf("build", "libs", "$name.jar")
|
||||
assertThat(sourceJar).isRegularFile()
|
||||
|
||||
_filteredJar = projectDir.pathOf("build", "filtered-libs", "$name-filtered.jar")
|
||||
assertThat(filteredJar).isRegularFile()
|
||||
|
||||
statement.evaluate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.testkit.runner.GradleRunner
|
||||
import org.gradle.testkit.runner.TaskOutcome.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runners.model.Statement
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.attribute.FileTime
|
||||
import java.util.*
|
||||
import java.util.Calendar.FEBRUARY
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class JarFilterTimestampTest {
|
||||
companion object {
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val sourceJar = DummyJar(testProjectDir, JarFilterTimestampTest::class.java, "timestamps")
|
||||
|
||||
private val CONSTANT_TIME: FileTime = FileTime.fromMillis(
|
||||
GregorianCalendar(1980, FEBRUARY, 1).apply {
|
||||
timeZone = TimeZone.getTimeZone("UTC")
|
||||
}.timeInMillis
|
||||
)
|
||||
|
||||
private lateinit var filteredJar: Path
|
||||
private lateinit var output: String
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(sourceJar)
|
||||
.around(createTestProject())
|
||||
|
||||
private fun createTestProject() = TestRule { base, _ ->
|
||||
object : Statement() {
|
||||
override fun evaluate() {
|
||||
testProjectDir.installResource("gradle.properties")
|
||||
testProjectDir.newFile("build.gradle").writeText("""
|
||||
plugins {
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars file("${sourceJar.path.toUri()}")
|
||||
preserveTimestamps = false
|
||||
}
|
||||
""")
|
||||
val result = GradleRunner.create()
|
||||
.withProjectDir(testProjectDir.root)
|
||||
.withArguments(getGradleArgsForTasks("jarFilter"))
|
||||
.withPluginClasspath()
|
||||
.build()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
val metafix = result.task(":jarFilter")
|
||||
?: throw AssertionError("No outcome for jarFilter task")
|
||||
assertEquals(SUCCESS, metafix.outcome)
|
||||
|
||||
filteredJar = testProjectDir.pathOf("build", "filtered-libs", "timestamps-filtered.jar")
|
||||
assertThat(filteredJar).isRegularFile()
|
||||
|
||||
base.evaluate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val ZipEntry.methodName: String get() = if (method == ZipEntry.STORED) "Stored" else "Deflated"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fileTimestampsAreRemoved() {
|
||||
var directoryCount = 0
|
||||
var classCount = 0
|
||||
var otherCount = 0
|
||||
|
||||
ZipFile(filteredJar.toFile()).use { jar ->
|
||||
for (entry in jar.entries()) {
|
||||
println("Entry: ${entry.name}")
|
||||
println("- ${entry.methodName} (${entry.size} size / ${entry.compressedSize} compressed) bytes")
|
||||
assertThat(entry.lastModifiedTime).isEqualTo(CONSTANT_TIME)
|
||||
assertThat(entry.lastAccessTime).isNull()
|
||||
assertThat(entry.creationTime).isNull()
|
||||
|
||||
if (entry.isDirectory) {
|
||||
++directoryCount
|
||||
} else if (entry.name.endsWith(".class")) {
|
||||
++classCount
|
||||
} else {
|
||||
++otherCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(directoryCount).isGreaterThan(0)
|
||||
assertThat(classCount).isGreaterThan(0)
|
||||
assertThat(otherCount).isGreaterThan(0)
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.bytecode
|
||||
import net.corda.gradle.jarfilter.asm.toClass
|
||||
import net.corda.gradle.jarfilter.matcher.isConstructor
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class MetaFixAnnotationTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixAnnotationTest::class)
|
||||
private val defaultCon = isConstructor(
|
||||
returnType = SimpleAnnotation::class
|
||||
)
|
||||
private val valueCon = isConstructor(
|
||||
returnType = AnnotationWithValue::class,
|
||||
parameters = *arrayOf(String::class)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleAnnotation() {
|
||||
val sourceClass = SimpleAnnotation::class.java
|
||||
assertThat("<init>() not found", sourceClass.kotlin.constructors, hasItem(defaultCon))
|
||||
|
||||
// Rewrite the metadata according to the contents of the bytecode.
|
||||
val fixedClass = sourceClass.bytecode.fixMetadata(logger, pathsOf(SimpleAnnotation::class))
|
||||
.toClass<SimpleAnnotation, Any>()
|
||||
assertThat("<init>() not found", fixedClass.kotlin.constructors, hasItem(defaultCon))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAnnotationWithValue() {
|
||||
val sourceClass = AnnotationWithValue::class.java
|
||||
assertThat("<init>(String) not found", sourceClass.kotlin.constructors, hasItem(valueCon))
|
||||
|
||||
// Rewrite the metadata according to the contents of the bytecode.
|
||||
val fixedClass = sourceClass.bytecode.fixMetadata(logger, pathsOf(AnnotationWithValue::class))
|
||||
.toClass<AnnotationWithValue, Any>()
|
||||
assertThat("<init>(String) not found", fixedClass.kotlin.constructors, hasItem(valueCon))
|
||||
}
|
||||
}
|
||||
|
||||
annotation class AnnotationWithValue(val str: String)
|
||||
annotation class SimpleAnnotation
|
@ -1,79 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
import org.gradle.testkit.runner.BuildTask
|
||||
import org.gradle.testkit.runner.GradleRunner
|
||||
import org.gradle.testkit.runner.TaskOutcome.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
|
||||
class MetaFixConfigurationTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testProjectDir = TemporaryFolder()
|
||||
|
||||
private lateinit var output: String
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
testProjectDir.installResource("gradle.properties")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkNoJarMeansNoSource() {
|
||||
val result = gradleProject("""
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.MetaFixerTask
|
||||
task metafix(type: MetaFixerTask)
|
||||
""").build()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
val metafix = result.forTask("metafix")
|
||||
assertEquals(NO_SOURCE, metafix.outcome)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkWithMissingJar() {
|
||||
val result = gradleProject("""
|
||||
plugins {
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.MetaFixerTask
|
||||
task metafix(type: MetaFixerTask) {
|
||||
jars = file('does-not-exist.jar')
|
||||
}
|
||||
""").buildAndFail()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
assertThat(output).containsSubsequence(
|
||||
"Caused by: org.gradle.api.GradleException:",
|
||||
"Caused by: java.io.FileNotFoundException:"
|
||||
)
|
||||
|
||||
val metafix = result.forTask("metafix")
|
||||
assertEquals(FAILED, metafix.outcome)
|
||||
}
|
||||
|
||||
private fun gradleProject(script: String): GradleRunner {
|
||||
testProjectDir.newFile("build.gradle").writeText(script)
|
||||
return GradleRunner.create()
|
||||
.withProjectDir(testProjectDir.root)
|
||||
.withArguments(getBasicArgsForTasks("metafix"))
|
||||
.withPluginClasspath()
|
||||
}
|
||||
|
||||
private fun BuildResult.forTask(name: String): BuildTask {
|
||||
return task(":$name") ?: throw AssertionError("No outcome for $name task")
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.recodeMetadataFor
|
||||
import net.corda.gradle.jarfilter.asm.toClass
|
||||
import net.corda.gradle.jarfilter.matcher.isConstructor
|
||||
import net.corda.gradle.unwanted.HasAll
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
|
||||
class MetaFixConstructorDefaultParameterTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixConstructorDefaultParameterTest::class)
|
||||
private val primaryCon
|
||||
= isConstructor(WithConstructorParameters::class, Long::class, Int::class, String::class)
|
||||
private val secondaryCon
|
||||
= isConstructor(WithConstructorParameters::class, Char::class, String::class)
|
||||
|
||||
lateinit var sourceClass: Class<out HasAll>
|
||||
lateinit var fixedClass: Class<out HasAll>
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
val bytecode = recodeMetadataFor<WithConstructorParameters, MetadataTemplate>()
|
||||
sourceClass = bytecode.toClass<WithConstructorParameters, HasAll>()
|
||||
fixedClass = bytecode.fixMetadata(logger, pathsOf(WithConstructorParameters::class))
|
||||
.toClass<WithConstructorParameters, HasAll>()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test source constructor has optional parameters`() {
|
||||
with(sourceClass.kotlin.constructors) {
|
||||
assertThat(size).isEqualTo(2)
|
||||
assertThat("source primary constructor missing", this, hasItem(primaryCon))
|
||||
assertThat("source secondary constructor missing", this, hasItem(secondaryCon))
|
||||
}
|
||||
|
||||
val sourcePrimary = sourceClass.kotlin.primaryConstructor
|
||||
?: throw AssertionError("source primary constructor missing")
|
||||
sourcePrimary.call(BIG_NUMBER, NUMBER, MESSAGE).apply {
|
||||
assertThat(longData()).isEqualTo(BIG_NUMBER)
|
||||
assertThat(intData()).isEqualTo(NUMBER)
|
||||
assertThat(stringData()).isEqualTo(MESSAGE)
|
||||
}
|
||||
|
||||
val sourceSecondary = sourceClass.kotlin.constructors.firstOrNull { it != sourcePrimary }
|
||||
?: throw AssertionError("source secondary constructor missing")
|
||||
sourceSecondary.call('X', MESSAGE).apply {
|
||||
assertThat(stringData()).isEqualTo("X$MESSAGE")
|
||||
}
|
||||
|
||||
assertTrue("All source parameters should have defaults", sourcePrimary.hasAllOptionalParameters)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test fixed constructors exist`() {
|
||||
with(fixedClass.kotlin.constructors) {
|
||||
assertThat(size).isEqualTo(2)
|
||||
assertThat("fixed primary constructor missing", this, hasItem(primaryCon))
|
||||
assertThat("fixed secondary constructor missing", this, hasItem(secondaryCon))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test fixed primary constructor has mandatory parameters`() {
|
||||
val fixedPrimary = fixedClass.kotlin.primaryConstructor
|
||||
?: throw AssertionError("fixed primary constructor missing")
|
||||
assertTrue("All fixed parameters should be mandatory", fixedPrimary.hasAllMandatoryParameters)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test fixed secondary constructor still has optional parameters`() {
|
||||
val fixedSecondary = (fixedClass.kotlin.constructors - fixedClass.kotlin.primaryConstructor).firstOrNull()
|
||||
?: throw AssertionError("fixed secondary constructor missing")
|
||||
assertTrue("Some fixed parameters should be optional", fixedSecondary.hasAnyOptionalParameters)
|
||||
}
|
||||
|
||||
class MetadataTemplate(
|
||||
private val longData: Long = 0,
|
||||
private val intData: Int = 0,
|
||||
private val message: String = DEFAULT_MESSAGE
|
||||
) : HasAll {
|
||||
@Suppress("UNUSED")
|
||||
constructor(prefix: Char, message: String = DEFAULT_MESSAGE) : this(message = prefix + message)
|
||||
|
||||
override fun longData(): Long = longData
|
||||
override fun intData(): Int = intData
|
||||
override fun stringData(): String = message
|
||||
}
|
||||
}
|
||||
|
||||
class WithConstructorParameters(
|
||||
private val longData: Long,
|
||||
private val intData: Int,
|
||||
private val message: String
|
||||
) : HasAll {
|
||||
@Suppress("UNUSED")
|
||||
constructor(prefix: Char, message: String = DEFAULT_MESSAGE) : this(0, 0, prefix + message)
|
||||
|
||||
override fun longData(): Long = longData
|
||||
override fun intData(): Int = intData
|
||||
override fun stringData(): String = message
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.unwanted.*
|
||||
import net.corda.gradle.jarfilter.asm.*
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.hamcrest.core.IsNot.not
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import kotlin.jvm.kotlin
|
||||
|
||||
class MetaFixConstructorTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixConstructorTest::class)
|
||||
private val unwantedCon = isConstructor(WithConstructor::class, Int::class, Long::class)
|
||||
private val wantedCon = isConstructor(WithConstructor::class, Long::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testConstructorRemovedFromMetadata() {
|
||||
val bytecode = recodeMetadataFor<WithConstructor, MetadataTemplate>()
|
||||
val sourceClass = bytecode.toClass<WithConstructor, HasLong>()
|
||||
|
||||
// Check that the unwanted constructor has been successfully
|
||||
// added to the metadata, and that the class is valid.
|
||||
val sourceObj = sourceClass.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
|
||||
assertEquals(BIG_NUMBER, sourceObj.longData())
|
||||
with(sourceClass.kotlin.constructors) {
|
||||
assertThat("<init>(Int,Long) not found", this, hasItem(unwantedCon))
|
||||
assertThat("<init>(Long) not found", this, hasItem(wantedCon))
|
||||
}
|
||||
|
||||
// Rewrite the metadata according to the contents of the bytecode.
|
||||
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithConstructor::class)).toClass<WithConstructor, HasLong>()
|
||||
val fixedObj = fixedClass.getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER)
|
||||
assertEquals(BIG_NUMBER, fixedObj.longData())
|
||||
with(fixedClass.kotlin.constructors) {
|
||||
assertThat("<init>(Int,Long) still exists", this, not(hasItem(unwantedCon)))
|
||||
assertThat("<init>(Long) not found", this, hasItem(wantedCon))
|
||||
}
|
||||
}
|
||||
|
||||
class MetadataTemplate(private val longData: Long) : HasLong {
|
||||
@Suppress("UNUSED_PARAMETER", "UNUSED")
|
||||
constructor(intData: Int, longData: Long) : this(longData)
|
||||
override fun longData(): Long = longData
|
||||
}
|
||||
}
|
||||
|
||||
class WithConstructor(private val longData: Long) : HasLong {
|
||||
override fun longData(): Long = longData
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.recodeMetadataFor
|
||||
import net.corda.gradle.jarfilter.asm.toClass
|
||||
import net.corda.gradle.jarfilter.matcher.isFunction
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.hamcrest.core.IsCollectionContaining.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
|
||||
class MetaFixFunctionDefaultParameterTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixFunctionDefaultParameterTest::class)
|
||||
private val hasMandatoryParams
|
||||
= isFunction("hasMandatoryParams", String::class, Long::class, Int::class, String::class)
|
||||
private val hasOptionalParams
|
||||
= isFunction("hasOptionalParams", String::class, String::class)
|
||||
|
||||
lateinit var sourceClass: Class<out Any>
|
||||
lateinit var fixedClass: Class<out Any>
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
val bytecode = recodeMetadataFor<WithFunctionParameters, MetadataTemplate>()
|
||||
sourceClass = bytecode.toClass<WithFunctionParameters, Any>()
|
||||
fixedClass = bytecode.fixMetadata(logger, pathsOf(WithFunctionParameters::class))
|
||||
.toClass<WithFunctionParameters, Any>()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test source functions have default parameters`() {
|
||||
with(sourceClass.kotlin.declaredFunctions) {
|
||||
assertThat(size).isEqualTo(2)
|
||||
assertThat("source mandatory parameters missing", this, hasItem(hasMandatoryParams))
|
||||
assertThat("source optional parameters missing", this, hasItem(hasOptionalParams))
|
||||
}
|
||||
|
||||
val sourceUnwanted = sourceClass.kotlin.declaredFunctions.findOrFail("hasMandatoryParams")
|
||||
assertThat(sourceUnwanted.call(sourceClass.newInstance(), BIG_NUMBER, NUMBER, MESSAGE))
|
||||
.isEqualTo("Long: $BIG_NUMBER, Int: $NUMBER, String: $MESSAGE")
|
||||
|
||||
assertTrue("All source parameters should be optional", sourceUnwanted.hasAllOptionalParameters)
|
||||
|
||||
val sourceWanted = sourceClass.kotlin.declaredFunctions.findOrFail("hasOptionalParams")
|
||||
assertThat(sourceWanted.call(sourceClass.newInstance(), MESSAGE))
|
||||
.isEqualTo(MESSAGE)
|
||||
|
||||
assertTrue("All source parameters should be optional", sourceWanted.hasAllOptionalParameters)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test fixed functions exist`() {
|
||||
with(fixedClass.kotlin.declaredFunctions) {
|
||||
assertThat(size).isEqualTo(2)
|
||||
assertThat("fixed mandatory parameters missing", this, hasItem(hasMandatoryParams))
|
||||
assertThat("fixed optional parameters missing", this, hasItem(hasOptionalParams))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test unwanted default parameters are removed`() {
|
||||
val fixedMandatory = fixedClass.kotlin.declaredFunctions.findOrFail("hasMandatoryParams")
|
||||
assertTrue("All fixed parameters should be mandatory", fixedMandatory.hasAllMandatoryParameters)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test wanted default parameters are kept`() {
|
||||
val fixedOptional = fixedClass.kotlin.declaredFunctions.findOrFail("hasOptionalParams")
|
||||
assertTrue("All fixed parameters should be optional", fixedOptional.hasAllOptionalParameters)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
abstract class MetadataTemplate {
|
||||
abstract fun hasMandatoryParams(longData: Long = 0, intData: Int = 0, message: String = DEFAULT_MESSAGE): String
|
||||
abstract fun hasOptionalParams(message: String = DEFAULT_MESSAGE): String
|
||||
}
|
||||
|
||||
private fun <T> Iterable<KFunction<T>>.findOrFail(name: String): KFunction<T> {
|
||||
return find { it.name == name } ?: throw AssertionError("$name missing")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
class WithFunctionParameters {
|
||||
fun hasMandatoryParams(longData: Long, intData: Int, message: String): String {
|
||||
return "Long: $longData, Int: $intData, String: $message"
|
||||
}
|
||||
|
||||
fun hasOptionalParams(message: String = DEFAULT_MESSAGE): String = message
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.unwanted.*
|
||||
import net.corda.gradle.jarfilter.asm.*
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.hamcrest.core.IsNot.not
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import kotlin.jvm.kotlin
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
|
||||
class MetaFixFunctionTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixFunctionTest::class)
|
||||
private val longData = isFunction("longData", Long::class)
|
||||
private val unwantedFun = isFunction("unwantedFun", String::class, String::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFunctionRemovedFromMetadata() {
|
||||
val bytecode = recodeMetadataFor<WithFunction, MetadataTemplate>()
|
||||
val sourceClass = bytecode.toClass<WithFunction, HasLong>()
|
||||
|
||||
// Check that the unwanted function has been successfully
|
||||
// added to the metadata, and that the class is valid.
|
||||
val sourceObj = sourceClass.newInstance()
|
||||
assertEquals(BIG_NUMBER, sourceObj.longData())
|
||||
with(sourceClass.kotlin.declaredFunctions) {
|
||||
assertThat("unwantedFun(String) not found", this, hasItem(unwantedFun))
|
||||
assertThat("longData not found", this, hasItem(longData))
|
||||
}
|
||||
|
||||
// Rewrite the metadata according to the contents of the bytecode.
|
||||
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithFunction::class)).toClass<WithFunction, HasLong>()
|
||||
val fixedObj = fixedClass.newInstance()
|
||||
assertEquals(BIG_NUMBER, fixedObj.longData())
|
||||
with(fixedClass.kotlin.declaredFunctions) {
|
||||
assertThat("unwantedFun(String) still exists", this, not(hasItem(unwantedFun)))
|
||||
assertThat("longData not found", this, hasItem(longData))
|
||||
}
|
||||
}
|
||||
|
||||
class MetadataTemplate : HasLong {
|
||||
override fun longData(): Long = 0
|
||||
@Suppress("UNUSED") fun unwantedFun(str: String): String = "UNWANTED[$str]"
|
||||
}
|
||||
}
|
||||
|
||||
class WithFunction : HasLong {
|
||||
override fun longData(): Long = BIG_NUMBER
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.*
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.junit.Test
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
|
||||
/**
|
||||
* Kotlin reflection will attempt to validate the nested classes stored in the [kotlin.Metadata]
|
||||
* annotation rather than just reporting what is there, which means that it can tell us nothing
|
||||
* about what the MetaFixer task has done.
|
||||
*/
|
||||
class MetaFixNestedClassTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixNestedClassTest::class)
|
||||
private val WANTED_CLASS: String = WithNestedClass.Wanted::class.jvmName
|
||||
private val UNWANTED_CLASS: String = "${WithNestedClass::class.jvmName}\$Unwanted"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNestedClassRemovedFromMetadata() {
|
||||
val bytecode = recodeMetadataFor<WithNestedClass, MetadataTemplate>()
|
||||
val sourceClass = bytecode.toClass<WithNestedClass, Any>()
|
||||
assertThat(sourceClass.classMetadata.nestedClasses).containsExactlyInAnyOrder(WANTED_CLASS, UNWANTED_CLASS)
|
||||
|
||||
// Rewrite the metadata according to the contents of the bytecode.
|
||||
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithNestedClass::class, WithNestedClass.Wanted::class))
|
||||
.toClass<WithNestedClass, Any>()
|
||||
assertThat(fixedClass.classMetadata.nestedClasses).containsExactly(WANTED_CLASS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAllNestedClassesRemovedFromMetadata() {
|
||||
val bytecode = recodeMetadataFor<WithoutNestedClass, MetadataTemplate>()
|
||||
val sourceClass = bytecode.toClass<WithoutNestedClass, Any>()
|
||||
assertThat(sourceClass.classMetadata.nestedClasses)
|
||||
.containsExactlyInAnyOrder("${WithoutNestedClass::class.jvmName}\$Wanted", "${WithoutNestedClass::class.jvmName}\$Unwanted")
|
||||
|
||||
// Rewrite the metadata according to the contents of the bytecode.
|
||||
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithoutNestedClass::class))
|
||||
.toClass<WithoutNestedClass, Any>()
|
||||
assertThat(fixedClass.classMetadata.nestedClasses).isEmpty()
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
class MetadataTemplate {
|
||||
class Wanted
|
||||
class Unwanted
|
||||
}
|
||||
}
|
||||
|
||||
class WithNestedClass {
|
||||
class Wanted
|
||||
}
|
||||
|
||||
class WithoutNestedClass
|
@ -1,39 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.metadataAs
|
||||
import net.corda.gradle.jarfilter.asm.toClass
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
/**
|
||||
* These tests cannot actually "test" anything until Kotlin reflection
|
||||
* supports package metadata. Until then, we can only execute the code
|
||||
* paths to ensure they don't throw any exceptions.
|
||||
*/
|
||||
class MetaFixPackageDefaultParameterTest {
|
||||
companion object {
|
||||
private const val TEMPLATE_CLASS = "net.corda.gradle.jarfilter.template.PackageWithDefaultParameters"
|
||||
private const val DEFAULT_PARAMETERS_CLASS = "net.corda.gradle.jarfilter.PackageWithDefaultParameters"
|
||||
private val logger: Logger = StdOutLogging(MetaFixPackageDefaultParameterTest::class)
|
||||
|
||||
lateinit var sourceClass: Class<out Any>
|
||||
lateinit var fixedClass: Class<out Any>
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
val defaultParametersClass = Class.forName(DEFAULT_PARAMETERS_CLASS)
|
||||
val bytecode = defaultParametersClass.metadataAs(Class.forName(TEMPLATE_CLASS))
|
||||
sourceClass = bytecode.toClass(defaultParametersClass, Any::class.java)
|
||||
fixedClass = bytecode.fixMetadata(logger, setOf(DEFAULT_PARAMETERS_CLASS)).toClass(sourceClass, Any::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test package functions`() {
|
||||
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredFunctions }
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
@file:JvmName("PackageTemplate")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.*
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import kotlin.jvm.kotlin
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
import kotlin.reflect.full.declaredMembers
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
/**
|
||||
* These tests cannot actually "test" anything until Kotlin reflection
|
||||
* supports package metadata. Until then, we can only execute the code
|
||||
* paths to ensure they don't throw any exceptions.
|
||||
*/
|
||||
class MetaFixPackageTest {
|
||||
companion object {
|
||||
private const val TEMPLATE_CLASS = "net.corda.gradle.jarfilter.PackageTemplate"
|
||||
private const val EMPTY_CLASS = "net.corda.gradle.jarfilter.EmptyPackage"
|
||||
private val logger: Logger = StdOutLogging(MetaFixPackageTest::class)
|
||||
private val staticVal = isProperty("templateVal", Long::class)
|
||||
private val staticVar = isProperty("templateVar", Int::class)
|
||||
private val staticFun = isFunction("templateFun", String::class)
|
||||
|
||||
private lateinit var sourceClass: Class<out Any>
|
||||
private lateinit var fixedClass: Class<out Any>
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
val emptyClass = Class.forName(EMPTY_CLASS)
|
||||
val bytecode = emptyClass.metadataAs(Class.forName(TEMPLATE_CLASS))
|
||||
sourceClass = bytecode.toClass(emptyClass, Any::class.java)
|
||||
fixedClass = bytecode.fixMetadata(logger, setOf(EMPTY_CLASS)).toClass(sourceClass, Any::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPackageFunction() {
|
||||
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredFunctions }
|
||||
//assertThat("templateFun() not found", sourceClass.kotlin.declaredFunctions, hasItem(staticFun))
|
||||
//assertThat("templateFun() still exists", fixedClass.kotlin.declaredFunctions, not(hasItem(staticFun)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPackageVal() {
|
||||
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredMembers }
|
||||
//assertThat("templateVal not found", sourceClass.kotlin.declaredMembers, hasItem(staticVal))
|
||||
//assertThat("templateVal still exists", fixedClass.kotlin.declaredMembers, not(hasItem(staticVal)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPackageVar() {
|
||||
assertFailsWith<UnsupportedOperationException> { fixedClass.kotlin.declaredMembers }
|
||||
//assertThat("templateVar not found", sourceClass.kotlin.declaredMembers, hasItem(staticVar))
|
||||
//assertThat("templateVar still exists", fixedClass.kotlin.declaredMembers, not(hasItem(staticVar)))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun templateFun(): String = MESSAGE
|
||||
internal const val templateVal: Long = BIG_NUMBER
|
||||
internal var templateVar: Int = NUMBER
|
@ -1,57 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.testkit.runner.GradleRunner
|
||||
import org.gradle.testkit.runner.TaskOutcome.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
import java.io.FileNotFoundException
|
||||
import java.nio.file.Path
|
||||
|
||||
@Suppress("UNUSED")
|
||||
class MetaFixProject(private val projectDir: TemporaryFolder, private val name: String) : TestRule {
|
||||
private var _sourceJar: Path? = null
|
||||
val sourceJar: Path get() = _sourceJar ?: throw FileNotFoundException("Input not found")
|
||||
|
||||
private var _metafixedJar: Path? = null
|
||||
val metafixedJar: Path get() = _metafixedJar ?: throw FileNotFoundException("Output not found")
|
||||
|
||||
private var _output: String = ""
|
||||
val output: String get() = _output
|
||||
|
||||
override fun apply(base: Statement, description: Description): Statement {
|
||||
return object : Statement() {
|
||||
override fun evaluate() {
|
||||
projectDir.installResources(
|
||||
"$name/build.gradle",
|
||||
"repositories.gradle",
|
||||
"gradle.properties",
|
||||
"settings.gradle"
|
||||
)
|
||||
|
||||
val result = GradleRunner.create()
|
||||
.withProjectDir(projectDir.root)
|
||||
.withArguments(getGradleArgsForTasks("metafix"))
|
||||
.withPluginClasspath()
|
||||
.build()
|
||||
_output = result.output
|
||||
println(output)
|
||||
|
||||
val metafix = result.task(":metafix")
|
||||
?: throw AssertionError("No outcome for metafix task")
|
||||
assertEquals(SUCCESS, metafix.outcome)
|
||||
|
||||
_sourceJar = projectDir.pathOf("build", "libs", "$name.jar")
|
||||
assertThat(sourceJar).isRegularFile()
|
||||
|
||||
_metafixedJar = projectDir.pathOf("build", "metafixer-libs", "$name-metafixed.jar")
|
||||
assertThat(metafixedJar).isRegularFile()
|
||||
|
||||
base.evaluate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.asm.*
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.junit.Test
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
|
||||
class MetaFixSealedClassTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixSealedClassTest::class)
|
||||
private val UNWANTED_CLASS: String = "${MetaSealedClass::class.jvmName}\$Unwanted"
|
||||
private val WANTED_CLASS: String = MetaSealedClass.Wanted::class.jvmName
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSealedSubclassRemovedFromMetadata() {
|
||||
val bytecode = recodeMetadataFor<MetaSealedClass, MetadataTemplate>()
|
||||
val sourceClass = bytecode.toClass<MetaSealedClass, Any>()
|
||||
assertThat(sourceClass.classMetadata.sealedSubclasses).containsExactlyInAnyOrder(UNWANTED_CLASS, WANTED_CLASS)
|
||||
|
||||
// Rewrite the metadata according to the contents of the bytecode.
|
||||
val fixedClass = bytecode.fixMetadata(logger, pathsOf(MetaSealedClass::class, MetaSealedClass.Wanted::class))
|
||||
.toClass<MetaSealedClass, Any>()
|
||||
assertThat(fixedClass.classMetadata.sealedSubclasses).containsExactly(WANTED_CLASS)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
sealed class MetadataTemplate {
|
||||
class Wanted : MetadataTemplate()
|
||||
class Unwanted : MetadataTemplate()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class MetaSealedClass {
|
||||
class Wanted : MetaSealedClass()
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.gradle.testkit.runner.GradleRunner
|
||||
import org.gradle.testkit.runner.TaskOutcome.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runners.model.Statement
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.attribute.FileTime
|
||||
import java.util.*
|
||||
import java.util.Calendar.FEBRUARY
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipEntry.*
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class MetaFixTimestampTest {
|
||||
companion object {
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val sourceJar = DummyJar(testProjectDir, MetaFixTimestampTest::class.java, "timestamps")
|
||||
|
||||
private val CONSTANT_TIME: FileTime = FileTime.fromMillis(
|
||||
GregorianCalendar(1980, FEBRUARY, 1).apply {
|
||||
timeZone = TimeZone.getTimeZone("UTC")
|
||||
}.timeInMillis
|
||||
)
|
||||
|
||||
private lateinit var metafixedJar: Path
|
||||
private lateinit var output: String
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(sourceJar)
|
||||
.around(createTestProject())
|
||||
|
||||
private fun createTestProject() = TestRule { base, _ ->
|
||||
object : Statement() {
|
||||
override fun evaluate() {
|
||||
testProjectDir.installResource("gradle.properties")
|
||||
testProjectDir.newFile("build.gradle").writeText("""
|
||||
plugins {
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.MetaFixerTask
|
||||
task metafix(type: MetaFixerTask) {
|
||||
jars file("${sourceJar.path.toUri()}")
|
||||
preserveTimestamps = false
|
||||
}
|
||||
""")
|
||||
val result = GradleRunner.create()
|
||||
.withProjectDir(testProjectDir.root)
|
||||
.withArguments(getGradleArgsForTasks("metafix"))
|
||||
.withPluginClasspath()
|
||||
.build()
|
||||
output = result.output
|
||||
println(output)
|
||||
|
||||
val metafix = result.task(":metafix")
|
||||
?: throw AssertionError("No outcome for metafix task")
|
||||
assertEquals(SUCCESS, metafix.outcome)
|
||||
|
||||
metafixedJar = testProjectDir.pathOf("build", "metafixer-libs", "timestamps-metafixed.jar")
|
||||
assertThat(metafixedJar).isRegularFile()
|
||||
|
||||
base.evaluate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val ZipEntry.methodName: String get() = if (method == STORED) "Stored" else "Deflated"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fileTimestampsAreRemoved() {
|
||||
var directoryCount = 0
|
||||
var classCount = 0
|
||||
var otherCount = 0
|
||||
|
||||
ZipFile(metafixedJar.toFile()).use { jar ->
|
||||
for (entry in jar.entries()) {
|
||||
println("Entry: ${entry.name}")
|
||||
println("- ${entry.methodName} (${entry.size} size / ${entry.compressedSize} compressed) bytes")
|
||||
assertThat(entry.lastModifiedTime).isEqualTo(CONSTANT_TIME)
|
||||
assertThat(entry.lastAccessTime).isNull()
|
||||
assertThat(entry.creationTime).isNull()
|
||||
|
||||
if (entry.isDirectory) {
|
||||
++directoryCount
|
||||
} else if (entry.name.endsWith(".class")) {
|
||||
++classCount
|
||||
} else {
|
||||
++otherCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(directoryCount).isGreaterThan(0)
|
||||
assertThat(classCount).isGreaterThan(0)
|
||||
assertThat(otherCount).isGreaterThan(0)
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.unwanted.*
|
||||
import net.corda.gradle.jarfilter.asm.*
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.hamcrest.core.IsNot.not
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import kotlin.jvm.kotlin
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
|
||||
class MetaFixValPropertyTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixValPropertyTest::class)
|
||||
private val unwantedVal = isProperty("unwantedVal", String::class)
|
||||
private val intVal = isProperty("intVal", Int::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPropertyRemovedFromMetadata() {
|
||||
val bytecode = recodeMetadataFor<WithValProperty, MetadataTemplate>()
|
||||
val sourceClass = bytecode.toClass<WithValProperty, HasIntVal>()
|
||||
|
||||
// Check that the unwanted property has been successfully
|
||||
// added to the metadata, and that the class is valid.
|
||||
val sourceObj = sourceClass.newInstance()
|
||||
assertEquals(NUMBER, sourceObj.intVal)
|
||||
assertThat("unwantedVal not found", sourceClass.kotlin.declaredMemberProperties, hasItem(unwantedVal))
|
||||
assertThat("intVal not found", sourceClass.kotlin.declaredMemberProperties, hasItem(intVal))
|
||||
|
||||
// Rewrite the metadata according to the contents of the bytecode.
|
||||
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithValProperty::class)).toClass<WithValProperty, HasIntVal>()
|
||||
val fixedObj = fixedClass.newInstance()
|
||||
assertEquals(NUMBER, fixedObj.intVal)
|
||||
assertThat("unwantedVal still exists", fixedClass.kotlin.declaredMemberProperties, not(hasItem(unwantedVal)))
|
||||
assertThat("intVal not found", fixedClass.kotlin.declaredMemberProperties, hasItem(intVal))
|
||||
}
|
||||
|
||||
class MetadataTemplate : HasIntVal {
|
||||
override val intVal: Int = 0
|
||||
@Suppress("UNUSED") val unwantedVal: String = "UNWANTED"
|
||||
}
|
||||
}
|
||||
|
||||
class WithValProperty : HasIntVal {
|
||||
override val intVal: Int = NUMBER
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.unwanted.*
|
||||
import net.corda.gradle.jarfilter.asm.*
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.hamcrest.core.IsNot.not
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import kotlin.jvm.kotlin
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
|
||||
class MetaFixVarPropertyTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(MetaFixVarPropertyTest::class)
|
||||
private val unwantedVar = isProperty("unwantedVar", String::class)
|
||||
private val intVar = isProperty("intVar", Int::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPropertyRemovedFromMetadata() {
|
||||
val bytecode = recodeMetadataFor<WithVarProperty, MetadataTemplate>()
|
||||
val sourceClass = bytecode.toClass<WithVarProperty, HasIntVar>()
|
||||
|
||||
// Check that the unwanted property has been successfully
|
||||
// added to the metadata, and that the class is valid.
|
||||
val sourceObj = sourceClass.newInstance()
|
||||
assertEquals(NUMBER, sourceObj.intVar)
|
||||
assertThat("unwantedVar not found", sourceClass.kotlin.declaredMemberProperties, hasItem(unwantedVar))
|
||||
assertThat("intVar not found", sourceClass.kotlin.declaredMemberProperties, hasItem(intVar))
|
||||
|
||||
// Rewrite the metadata according to the contents of the bytecode.
|
||||
val fixedClass = bytecode.fixMetadata(logger, pathsOf(WithVarProperty::class)).toClass<WithVarProperty, HasIntVar>()
|
||||
val fixedObj = fixedClass.newInstance()
|
||||
assertEquals(NUMBER, fixedObj.intVar)
|
||||
assertThat("unwantedVar still exists", fixedClass.kotlin.declaredMemberProperties, not(hasItem(unwantedVar)))
|
||||
assertThat("intVar not found", fixedClass.kotlin.declaredMemberProperties, hasItem(intVar))
|
||||
}
|
||||
|
||||
class MetadataTemplate : HasIntVar {
|
||||
override var intVar: Int = 0
|
||||
@Suppress("UNUSED") var unwantedVar: String = "UNWANTED"
|
||||
}
|
||||
}
|
||||
|
||||
class WithVarProperty : HasIntVar {
|
||||
override var intVar: Int = NUMBER
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
|
||||
class MethodElementTest {
|
||||
private companion object {
|
||||
private const val DESCRIPTOR = "()Ljava.lang.String;"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMethodsMatchByNameAndDescriptor() {
|
||||
val elt = MethodElement(
|
||||
name = "getThing",
|
||||
descriptor = DESCRIPTOR,
|
||||
access = ACC_PUBLIC or ACC_ABSTRACT or ACC_FINAL
|
||||
)
|
||||
assertEquals(MethodElement(name="getThing", descriptor=DESCRIPTOR), elt)
|
||||
assertNotEquals(MethodElement(name="getOther", descriptor=DESCRIPTOR), elt)
|
||||
assertNotEquals(MethodElement(name="getThing", descriptor="()J"), elt)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBasicMethodVisibleName() {
|
||||
val elt = MethodElement(
|
||||
name = "getThing",
|
||||
descriptor = DESCRIPTOR,
|
||||
access = ACC_PUBLIC
|
||||
)
|
||||
assertEquals("getThing", elt.visibleName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMethodVisibleNameWithSuffix() {
|
||||
val elt = MethodElement(
|
||||
name = "getThing\$extra",
|
||||
descriptor = DESCRIPTOR,
|
||||
access = ACC_PUBLIC
|
||||
)
|
||||
assertEquals("getThing", elt.visibleName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSyntheticMethodSuffix() {
|
||||
val elt = MethodElement(
|
||||
name = "getThing\$extra",
|
||||
descriptor = DESCRIPTOR,
|
||||
access = ACC_PUBLIC or ACC_SYNTHETIC
|
||||
)
|
||||
assertTrue(elt.isKotlinSynthetic("extra"))
|
||||
assertFalse(elt.isKotlinSynthetic("something"))
|
||||
assertTrue(elt.isKotlinSynthetic("extra", "something"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPublicMethodSuffix() {
|
||||
val elt = MethodElement(
|
||||
name = "getThing\$extra",
|
||||
descriptor = DESCRIPTOR,
|
||||
access = ACC_PUBLIC
|
||||
)
|
||||
assertFalse(elt.isKotlinSynthetic("extra"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMethodDoesNotExpire() {
|
||||
val elt = MethodElement(
|
||||
name = "getThing\$extra",
|
||||
descriptor = DESCRIPTOR,
|
||||
access = ACC_PUBLIC
|
||||
)
|
||||
assertFalse(elt.isExpired)
|
||||
assertFalse(elt.isExpired)
|
||||
assertFalse(elt.isExpired)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testArtificialMethodDoesExpire() {
|
||||
val elt = MethodElement(
|
||||
name = "getThing\$extra",
|
||||
descriptor = DESCRIPTOR
|
||||
)
|
||||
assertFalse(elt.isExpired)
|
||||
assertTrue(elt.isExpired)
|
||||
assertTrue(elt.isExpired)
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
@file:JvmName("PackageWithDefaultParameters")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
/**
|
||||
* Example package functions, one with default parameter values and one without.
|
||||
* We will rewrite this class's metadata so that it expects both functions to
|
||||
* have default parameter values, and then ask the [MetaFixerTask] to fix it.
|
||||
*/
|
||||
fun hasDefaultParameters(intData: Int=0, message: String=DEFAULT_MESSAGE): String = "$message: intData=$intData"
|
||||
|
||||
fun hasMandatoryParameters(longData: Long, message: String): String = "$message: longData=$longData"
|
@ -1,176 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.unwanted.HasUnwantedFun
|
||||
import net.corda.gradle.unwanted.HasUnwantedVal
|
||||
import net.corda.gradle.unwanted.HasUnwantedVar
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
|
||||
class RemoveAnnotationsTest {
|
||||
companion object {
|
||||
private const val ANNOTATED_CLASS = "net.corda.gradle.HasUnwantedAnnotations"
|
||||
private const val REMOVE_ME_CLASS = "net.corda.gradle.jarfilter.RemoveMe"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "remove-annotations")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteFromClass() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<Any>(ANNOTATED_CLASS).apply {
|
||||
assertNotNull(getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<Any>(ANNOTATED_CLASS).apply {
|
||||
assertNull(getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteFromDefaultConstructor() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<Any>(ANNOTATED_CLASS).apply {
|
||||
getDeclaredConstructor().also { con ->
|
||||
assertNotNull(con.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<Any>(ANNOTATED_CLASS).apply {
|
||||
getDeclaredConstructor().also { con ->
|
||||
assertNull(con.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteFromPrimaryConstructor() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<Any>(ANNOTATED_CLASS).apply {
|
||||
getDeclaredConstructor(Long::class.java, String::class.java).also { con ->
|
||||
assertNotNull(con.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<Any>(ANNOTATED_CLASS).apply {
|
||||
getDeclaredConstructor(Long::class.java, String::class.java).also { con ->
|
||||
assertNull(con.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteFromField() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<Any>(ANNOTATED_CLASS).apply {
|
||||
getField("longField").also { field ->
|
||||
assertNotNull(field.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<Any>(ANNOTATED_CLASS).apply {
|
||||
getField("longField").also { field ->
|
||||
assertNull(field.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteFromMethod() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<HasUnwantedFun>(ANNOTATED_CLASS).apply {
|
||||
getMethod("unwantedFun", String::class.java).also { method ->
|
||||
assertNotNull(method.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<HasUnwantedFun>(ANNOTATED_CLASS).apply {
|
||||
getMethod("unwantedFun", String::class.java).also { method ->
|
||||
assertNull(method.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteFromValProperty() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<HasUnwantedVal>(ANNOTATED_CLASS).apply {
|
||||
getMethod("getUnwantedVal").also { method ->
|
||||
assertNotNull(method.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<HasUnwantedVal>(ANNOTATED_CLASS).apply {
|
||||
getMethod("getUnwantedVal").also { method ->
|
||||
assertNull(method.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteFromVarProperty() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<HasUnwantedVar>(ANNOTATED_CLASS).apply {
|
||||
getMethod("getUnwantedVar").also { method ->
|
||||
assertNotNull(method.getAnnotation(removeMe))
|
||||
}
|
||||
getMethod("setUnwantedVar", String::class.java).also { method ->
|
||||
assertNotNull(method.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
val removeMe = cl.load<Annotation>(REMOVE_ME_CLASS)
|
||||
cl.load<HasUnwantedVar>(ANNOTATED_CLASS).apply {
|
||||
getMethod("getUnwantedVar").also { method ->
|
||||
assertNull(method.getAnnotation(removeMe))
|
||||
}
|
||||
getMethod("setUnwantedVar", String::class.java).also { method ->
|
||||
assertNull(method.getAnnotation(removeMe))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import net.corda.gradle.unwanted.HasInt
|
||||
import net.corda.gradle.unwanted.HasLong
|
||||
import net.corda.gradle.unwanted.HasString
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.jvm.kotlin
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class SanitiseDeleteConstructorTest {
|
||||
companion object {
|
||||
private const val COMPLEX_CONSTRUCTOR_CLASS = "net.corda.gradle.HasOverloadedComplexConstructorToDelete"
|
||||
private const val COUNT_INITIAL_OVERLOADED = 1
|
||||
private const val COUNT_INITIAL_MULTIPLE = 2
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "sanitise-delete-constructor")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteOverloadedLongConstructor() = checkClassWithLongParameter(
|
||||
"net.corda.gradle.HasOverloadedLongConstructorToDelete",
|
||||
COUNT_INITIAL_OVERLOADED
|
||||
)
|
||||
|
||||
@Test
|
||||
fun deleteMultipleLongConstructor() = checkClassWithLongParameter(
|
||||
"net.corda.gradle.HasMultipleLongConstructorsToDelete",
|
||||
COUNT_INITIAL_MULTIPLE
|
||||
)
|
||||
|
||||
private fun checkClassWithLongParameter(longClass: String, initialCount: Int) {
|
||||
val longConstructor = isConstructor(longClass, Long::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasLong>(longClass).apply {
|
||||
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
|
||||
assertEquals(BIG_NUMBER, it.longData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(J) not found", this, hasItem(longConstructor))
|
||||
assertEquals(initialCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER)
|
||||
|
||||
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
|
||||
assertThat(noArg.callBy(emptyMap()).longData()).isEqualTo(0)
|
||||
assertThat(newInstance().longData()).isEqualTo(0)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasLong>(longClass).apply {
|
||||
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
|
||||
assertEquals(BIG_NUMBER, it.longData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(J) not found", this, hasItem(longConstructor))
|
||||
assertEquals(1, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER)
|
||||
|
||||
assertNull("no-arg constructor exists", kotlin.noArgConstructor)
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteOverloadedIntConstructor() = checkClassWithIntParameter(
|
||||
"net.corda.gradle.HasOverloadedIntConstructorToDelete",
|
||||
COUNT_INITIAL_OVERLOADED
|
||||
)
|
||||
|
||||
@Test
|
||||
fun deleteMultipleIntConstructor() = checkClassWithIntParameter(
|
||||
"net.corda.gradle.HasMultipleIntConstructorsToDelete",
|
||||
COUNT_INITIAL_MULTIPLE
|
||||
)
|
||||
|
||||
private fun checkClassWithIntParameter(intClass: String, initialCount: Int) {
|
||||
val intConstructor = isConstructor(intClass, Int::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasInt>(intClass).apply {
|
||||
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
|
||||
assertEquals(NUMBER, it.intData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(I) not found", this, hasItem(intConstructor))
|
||||
assertEquals(initialCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER)
|
||||
|
||||
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
|
||||
assertThat(noArg.callBy(emptyMap()).intData()).isEqualTo(0)
|
||||
assertThat(newInstance().intData()).isEqualTo(0)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasInt>(intClass).apply {
|
||||
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
|
||||
assertEquals(NUMBER, it.intData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(I) not found", this, hasItem(intConstructor))
|
||||
assertEquals(1, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER)
|
||||
|
||||
assertNull("no-arg constructor exists", kotlin.noArgConstructor)
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteOverloadedStringConstructor() = checkClassWithStringParameter(
|
||||
"net.corda.gradle.HasOverloadedStringConstructorToDelete",
|
||||
COUNT_INITIAL_OVERLOADED
|
||||
)
|
||||
|
||||
@Test
|
||||
fun deleteMultipleStringConstructor() = checkClassWithStringParameter(
|
||||
"net.corda.gradle.HasMultipleStringConstructorsToDelete",
|
||||
COUNT_INITIAL_MULTIPLE
|
||||
)
|
||||
|
||||
private fun checkClassWithStringParameter(stringClass: String, initialCount: Int) {
|
||||
val stringConstructor = isConstructor(stringClass, String::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(stringClass).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
|
||||
assertEquals(MESSAGE, it.stringData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(String) not found", this, hasItem(stringConstructor))
|
||||
assertEquals(initialCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE)
|
||||
|
||||
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
|
||||
assertThat(noArg.callBy(emptyMap()).stringData()).isEqualTo(DEFAULT_MESSAGE)
|
||||
assertThat(newInstance().stringData()).isEqualTo(DEFAULT_MESSAGE)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasString>(stringClass).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
|
||||
assertEquals(MESSAGE, it.stringData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(String) not found", this, hasItem(stringConstructor))
|
||||
assertEquals(1, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE)
|
||||
|
||||
assertNull("no-arg constructor exists", kotlin.noArgConstructor)
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteOverloadedComplexConstructor() {
|
||||
val complexConstructor = isConstructor(COMPLEX_CONSTRUCTOR_CLASS, Int::class, String::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(COMPLEX_CONSTRUCTOR_CLASS).apply {
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(Int,String) not found", this, hasItem(complexConstructor))
|
||||
assertEquals(1, this.size)
|
||||
}
|
||||
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
primary.call(NUMBER, MESSAGE).also { complex ->
|
||||
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
|
||||
assertThat((complex as HasInt).intData()).isEqualTo(NUMBER)
|
||||
}
|
||||
|
||||
primary.callBy(mapOf(primary.parameters[1] to MESSAGE)).also { complex ->
|
||||
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
|
||||
assertThat((complex as HasInt).intData()).isEqualTo(0)
|
||||
}
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { complex ->
|
||||
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
|
||||
assertThat((complex as HasInt).intData()).isEqualTo(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(COMPLEX_CONSTRUCTOR_CLASS).apply {
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(Int,String) not found", this, hasItem(complexConstructor))
|
||||
assertEquals(1, this.size)
|
||||
}
|
||||
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
primary.call(NUMBER, MESSAGE).also { complex ->
|
||||
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
|
||||
assertThat((complex as HasInt).intData()).isEqualTo(NUMBER)
|
||||
}
|
||||
|
||||
assertThat(assertFailsWith<IllegalArgumentException> { primary.callBy(mapOf(primary.parameters[1] to MESSAGE)) })
|
||||
.hasMessageContaining("No argument provided for a required parameter")
|
||||
assertFailsWith<NoSuchMethodException> { getDeclaredConstructor(String::class.java) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,250 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.matcher.*
|
||||
import net.corda.gradle.unwanted.HasInt
|
||||
import net.corda.gradle.unwanted.HasLong
|
||||
import net.corda.gradle.unwanted.HasString
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.hamcrest.core.IsCollectionContaining.hasItem
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import kotlin.jvm.kotlin
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class SanitiseStubConstructorTest {
|
||||
companion object {
|
||||
private const val COMPLEX_CONSTRUCTOR_CLASS = "net.corda.gradle.HasOverloadedComplexConstructorToStub"
|
||||
private const val COUNT_OVERLOADED = 1
|
||||
private const val COUNT_MULTIPLE = 2
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "sanitise-stub-constructor")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubOverloadedLongConstructor() = checkClassWithLongParameter(
|
||||
"net.corda.gradle.HasOverloadedLongConstructorToStub",
|
||||
COUNT_OVERLOADED
|
||||
)
|
||||
|
||||
@Test
|
||||
fun stubMultipleLongConstructor() = checkClassWithLongParameter(
|
||||
"net.corda.gradle.HasMultipleLongConstructorsToStub",
|
||||
COUNT_MULTIPLE
|
||||
)
|
||||
|
||||
private fun checkClassWithLongParameter(longClass: String, constructorCount: Int) {
|
||||
val longConstructor = isConstructor(longClass, Long::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasLong>(longClass).apply {
|
||||
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
|
||||
assertEquals(BIG_NUMBER, it.longData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(J) not found", this, hasItem(longConstructor))
|
||||
assertEquals(constructorCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER)
|
||||
|
||||
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
|
||||
assertThat(noArg.callBy(emptyMap()).longData()).isEqualTo(0)
|
||||
assertThat(newInstance().longData()).isEqualTo(0)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasLong>(longClass).apply {
|
||||
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also {
|
||||
assertEquals(BIG_NUMBER, it.longData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(J) not found", this, hasItem(longConstructor))
|
||||
assertEquals(constructorCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER)
|
||||
|
||||
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
|
||||
assertThat(assertFailsWith<InvocationTargetException> { noArg.callBy(emptyMap()) }.targetException)
|
||||
.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
assertThat(assertFailsWith<UnsupportedOperationException> { newInstance() })
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubOverloadedIntConstructor() = checkClassWithIntParameter(
|
||||
"net.corda.gradle.HasOverloadedIntConstructorToStub",
|
||||
COUNT_OVERLOADED
|
||||
)
|
||||
|
||||
@Test
|
||||
fun stubMultipleIntConstructor() = checkClassWithIntParameter(
|
||||
"net.corda.gradle.HasMultipleIntConstructorsToStub",
|
||||
COUNT_MULTIPLE
|
||||
)
|
||||
|
||||
private fun checkClassWithIntParameter(intClass: String, constructorCount: Int) {
|
||||
val intConstructor = isConstructor(intClass, Int::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasInt>(intClass).apply {
|
||||
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
|
||||
assertEquals(NUMBER, it.intData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(I) not found", this, hasItem(intConstructor))
|
||||
assertEquals(constructorCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER)
|
||||
|
||||
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
|
||||
assertThat(noArg.callBy(emptyMap()).intData()).isEqualTo(0)
|
||||
assertThat(newInstance().intData()).isEqualTo(0)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasInt>(intClass).apply {
|
||||
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also {
|
||||
assertEquals(NUMBER, it.intData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(I) not found", this, hasItem(intConstructor))
|
||||
assertEquals(constructorCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER)
|
||||
|
||||
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
|
||||
assertThat(assertFailsWith<InvocationTargetException> { noArg.callBy(emptyMap()) }.targetException)
|
||||
.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
assertThat(assertFailsWith<UnsupportedOperationException> { newInstance() })
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubOverloadedStringConstructor() = checkClassWithStringParameter(
|
||||
"net.corda.gradle.HasOverloadedStringConstructorToStub",
|
||||
COUNT_OVERLOADED
|
||||
)
|
||||
|
||||
@Test
|
||||
fun stubMultipleStringConstructor() = checkClassWithStringParameter(
|
||||
"net.corda.gradle.HasMultipleStringConstructorsToStub",
|
||||
COUNT_MULTIPLE
|
||||
)
|
||||
|
||||
private fun checkClassWithStringParameter(stringClass: String, constructorCount: Int) {
|
||||
val stringConstructor = isConstructor(stringClass, String::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(stringClass).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
|
||||
assertEquals(MESSAGE, it.stringData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(String) not found", this, hasItem(stringConstructor))
|
||||
assertEquals(constructorCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE)
|
||||
|
||||
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
|
||||
assertThat(noArg.callBy(emptyMap()).stringData()).isEqualTo(DEFAULT_MESSAGE)
|
||||
assertThat(newInstance().stringData()).isEqualTo(DEFAULT_MESSAGE)
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasString>(stringClass).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also {
|
||||
assertEquals(MESSAGE, it.stringData())
|
||||
}
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(String) not found", this, hasItem(stringConstructor))
|
||||
assertEquals(constructorCount, this.size)
|
||||
}
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE)
|
||||
|
||||
val noArg = kotlin.noArgConstructor ?: throw AssertionError("no-arg constructor missing")
|
||||
assertThat(assertFailsWith<InvocationTargetException> { noArg.callBy(emptyMap()) }.targetException)
|
||||
.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
assertThat(assertFailsWith<UnsupportedOperationException> { newInstance() })
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubOverloadedComplexConstructor() {
|
||||
val complexConstructor = isConstructor(COMPLEX_CONSTRUCTOR_CLASS, Int::class, String::class)
|
||||
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(COMPLEX_CONSTRUCTOR_CLASS).apply {
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(Int,String) not found", this, hasItem(complexConstructor))
|
||||
assertEquals(1, this.size)
|
||||
}
|
||||
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
primary.call(NUMBER, MESSAGE).also { complex ->
|
||||
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
|
||||
assertThat((complex as HasInt).intData()).isEqualTo(NUMBER)
|
||||
}
|
||||
|
||||
primary.callBy(mapOf(primary.parameters[1] to MESSAGE)).also { complex ->
|
||||
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
|
||||
assertThat((complex as HasInt).intData()).isEqualTo(0)
|
||||
}
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { complex ->
|
||||
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
|
||||
assertThat((complex as HasInt).intData()).isEqualTo(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(COMPLEX_CONSTRUCTOR_CLASS).apply {
|
||||
kotlin.constructors.apply {
|
||||
assertThat("<init>(Int,String) not found", this, hasItem(complexConstructor))
|
||||
assertEquals(1, this.size)
|
||||
}
|
||||
|
||||
val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing")
|
||||
primary.call(NUMBER, MESSAGE).also { complex ->
|
||||
assertThat((complex as HasString).stringData()).isEqualTo(MESSAGE)
|
||||
assertThat((complex as HasInt).intData()).isEqualTo(NUMBER)
|
||||
}
|
||||
|
||||
assertThat(assertFailsWith<InvocationTargetException> { primary.callBy(mapOf(primary.parameters[1] to MESSAGE)) }.targetException)
|
||||
.isInstanceOf(kotlin.UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
assertThat(assertFailsWith<InvocationTargetException> { getDeclaredConstructor(String::class.java).newInstance(MESSAGE) }.targetException)
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
@file:JvmName("StaticFields")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.jarfilter.annotations.Deletable
|
||||
import net.corda.gradle.jarfilter.asm.bytecode
|
||||
import net.corda.gradle.jarfilter.asm.toClass
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.junit.Assert.*
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
/**
|
||||
* Static properties are all initialised in the same <clinit> block.
|
||||
* Show that deleting some field references doesn't break the other
|
||||
* properties' initialisation code.
|
||||
*/
|
||||
class StaticFieldRemovalTest {
|
||||
companion object {
|
||||
private val logger: Logger = StdOutLogging(StaticFieldRemovalTest::class)
|
||||
private const val FIELD_CLASS = "net.corda.gradle.jarfilter.StaticFields"
|
||||
|
||||
private lateinit var sourceClass: Class<out Any>
|
||||
private lateinit var targetClass: Class<out Any>
|
||||
|
||||
private fun <T : R, R : Any> transform(type: Class<in T>, asType: Class<out R>): Class<out R> {
|
||||
val bytecode = type.bytecode.execute({ writer ->
|
||||
FilterTransformer(
|
||||
visitor = writer,
|
||||
logger = logger,
|
||||
removeAnnotations = emptySet(),
|
||||
deleteAnnotations = setOf(Deletable::class.jvmName.descriptor),
|
||||
stubAnnotations = emptySet(),
|
||||
unwantedElements = UnwantedCache()
|
||||
)
|
||||
}, COMPUTE_MAXS)
|
||||
return bytecode.toClass(type, asType)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@BeforeClass
|
||||
fun setup() {
|
||||
sourceClass = Class.forName(FIELD_CLASS)
|
||||
targetClass = transform(sourceClass, Any::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStaticString() {
|
||||
assertEquals("1", sourceClass.getDeclaredMethod("getStaticString").invoke(null))
|
||||
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticString") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStaticLong() {
|
||||
assertEquals(2L, sourceClass.getDeclaredMethod("getStaticLong").invoke(null))
|
||||
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticLong") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStaticInt() {
|
||||
assertEquals(3, sourceClass.getDeclaredMethod("getStaticInt").invoke(null))
|
||||
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticInt") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStaticShort() {
|
||||
assertEquals(4.toShort(), sourceClass.getDeclaredMethod("getStaticShort").invoke(null))
|
||||
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticShort") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStaticByte() {
|
||||
assertEquals(5.toByte(), sourceClass.getDeclaredMethod("getStaticByte").invoke(null))
|
||||
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticByte") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteStaticChar() {
|
||||
assertEquals(6.toChar(), sourceClass.getDeclaredMethod("getStaticChar").invoke(null))
|
||||
assertFailsWith<NoSuchMethodException> { targetClass.getDeclaredMethod("getStaticChar") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkSeedHasBeenIncremented() {
|
||||
assertEquals(6, sourceClass.getDeclaredMethod("getStaticSeed").invoke(null))
|
||||
assertEquals(6, targetClass.getDeclaredMethod("getStaticSeed").invoke(null))
|
||||
}
|
||||
}
|
||||
|
||||
private var seed: Int = 0
|
||||
val staticSeed get() = seed
|
||||
|
||||
@Deletable val staticString: String = (++seed).toString()
|
||||
@Deletable val staticLong: Long = (++seed).toLong()
|
||||
@Deletable val staticInt: Int = ++seed
|
||||
@Deletable val staticShort: Short = (++seed).toShort()
|
||||
@Deletable val staticByte: Byte = (++seed).toByte()
|
||||
@Deletable val staticChar: Char = (++seed).toChar()
|
@ -1,262 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.gradle.api.logging.LogLevel.*
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.slf4j.Marker
|
||||
import org.slf4j.helpers.MessageFormatter
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class StdOutLogging(private val name: String, private val threshold: LogLevel = INFO) : Logger {
|
||||
constructor(clazz: KClass<*>) : this(clazz.java.simpleName)
|
||||
|
||||
override fun getName(): String = name
|
||||
|
||||
override fun isErrorEnabled(): Boolean = isEnabled(ERROR)
|
||||
override fun isErrorEnabled(marker: Marker): Boolean = isEnabled(ERROR)
|
||||
|
||||
override fun isWarnEnabled(): Boolean = isEnabled(WARN)
|
||||
override fun isWarnEnabled(marker: Marker): Boolean = isEnabled(WARN)
|
||||
|
||||
override fun isInfoEnabled(): Boolean = isEnabled(INFO)
|
||||
override fun isInfoEnabled(marker: Marker): Boolean = isEnabled(INFO)
|
||||
|
||||
override fun isDebugEnabled(): Boolean = isEnabled(DEBUG)
|
||||
override fun isDebugEnabled(marker: Marker): Boolean = isEnabled(DEBUG)
|
||||
|
||||
override fun isTraceEnabled(): Boolean = isEnabled(DEBUG)
|
||||
override fun isTraceEnabled(marker: Marker): Boolean = isEnabled(DEBUG)
|
||||
|
||||
override fun isQuietEnabled(): Boolean = isEnabled(QUIET)
|
||||
|
||||
override fun isLifecycleEnabled(): Boolean = isEnabled(LIFECYCLE)
|
||||
|
||||
override fun isEnabled(level: LogLevel): Boolean = threshold <= level
|
||||
|
||||
override fun warn(msg: String) = log(WARN, msg)
|
||||
override fun warn(msg: String, obj: Any?) = log(WARN, msg, obj)
|
||||
override fun warn(msg: String, vararg objects: Any?) = log(WARN, msg, *objects)
|
||||
override fun warn(msg: String, obj1: Any?, obj2: Any?) = log(WARN, msg, obj1, obj2)
|
||||
override fun warn(msg: String, ex: Throwable) = log(WARN, msg, ex)
|
||||
|
||||
override fun warn(marker: Marker, msg: String) {
|
||||
if (isWarnEnabled(marker)) {
|
||||
print(WARN, msg)
|
||||
}
|
||||
}
|
||||
|
||||
override fun warn(marker: Marker, msg: String, obj: Any?) {
|
||||
if (isWarnEnabled(marker)) {
|
||||
print(WARN, msg, obj)
|
||||
}
|
||||
}
|
||||
|
||||
override fun warn(marker: Marker, msg: String, obj1: Any?, obj2: Any?) {
|
||||
if (isWarnEnabled(marker)) {
|
||||
print(WARN, msg, obj1, obj2)
|
||||
}
|
||||
}
|
||||
|
||||
override fun warn(marker: Marker, msg: String, vararg objects: Any?) {
|
||||
if (isWarnEnabled(marker)) {
|
||||
printAny(WARN, msg, *objects)
|
||||
}
|
||||
}
|
||||
|
||||
override fun warn(marker: Marker, msg: String, ex: Throwable) {
|
||||
if (isWarnEnabled(marker)) {
|
||||
print(WARN, msg, ex)
|
||||
}
|
||||
}
|
||||
|
||||
override fun info(message: String, vararg objects: Any?) = log(INFO, message, *objects)
|
||||
override fun info(message: String) = log(INFO, message)
|
||||
override fun info(message: String, obj: Any?) = log(INFO, message, obj)
|
||||
override fun info(message: String, obj1: Any?, obj2: Any?) = log(INFO, message, obj1, obj2)
|
||||
override fun info(message: String, ex: Throwable) = log(INFO, message, ex)
|
||||
|
||||
override fun info(marker: Marker, msg: String) {
|
||||
if (isInfoEnabled(marker)) {
|
||||
print(INFO, msg)
|
||||
}
|
||||
}
|
||||
|
||||
override fun info(marker: Marker, msg: String, obj: Any?) {
|
||||
if (isInfoEnabled(marker)) {
|
||||
print(INFO, msg, obj)
|
||||
}
|
||||
}
|
||||
|
||||
override fun info(marker: Marker, msg: String, obj1: Any?, obj2: Any?) {
|
||||
if (isInfoEnabled(marker)) {
|
||||
print(INFO, msg, obj1, obj2)
|
||||
}
|
||||
}
|
||||
|
||||
override fun info(marker: Marker, msg: String, vararg objects: Any?) {
|
||||
if (isInfoEnabled(marker)) {
|
||||
printAny(INFO, msg, *objects)
|
||||
}
|
||||
}
|
||||
|
||||
override fun info(marker: Marker, msg: String, ex: Throwable) {
|
||||
if (isInfoEnabled(marker)) {
|
||||
print(INFO, msg, ex)
|
||||
}
|
||||
}
|
||||
|
||||
override fun error(message: String) = log(ERROR, message)
|
||||
override fun error(message: String, obj: Any?) = log(ERROR, message, obj)
|
||||
override fun error(message: String, obj1: Any?, obj2: Any?) = log(ERROR, message, obj1, obj2)
|
||||
override fun error(message: String, vararg objects: Any?) = log(ERROR, message, *objects)
|
||||
override fun error(message: String, ex: Throwable) = log(ERROR, message, ex)
|
||||
|
||||
override fun error(marker: Marker, msg: String) {
|
||||
if (isErrorEnabled(marker)) {
|
||||
print(ERROR, msg)
|
||||
}
|
||||
}
|
||||
|
||||
override fun error(marker: Marker, msg: String, obj: Any?) {
|
||||
if (isErrorEnabled(marker)) {
|
||||
print(ERROR, msg, obj)
|
||||
}
|
||||
}
|
||||
|
||||
override fun error(marker: Marker, msg: String, obj1: Any?, obj2: Any?) {
|
||||
if (isErrorEnabled(marker)) {
|
||||
print(ERROR, msg, obj1, obj2)
|
||||
}
|
||||
}
|
||||
|
||||
override fun error(marker: Marker, msg: String, vararg objects: Any?) {
|
||||
if (isErrorEnabled(marker)) {
|
||||
printAny(ERROR, msg, *objects)
|
||||
}
|
||||
}
|
||||
|
||||
override fun error(marker: Marker, msg: String, ex: Throwable) {
|
||||
if (isErrorEnabled(marker)) {
|
||||
print(ERROR, msg, ex)
|
||||
}
|
||||
}
|
||||
|
||||
override fun log(level: LogLevel, message: String) {
|
||||
if (isEnabled(level)) {
|
||||
print(level, message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun log(level: LogLevel, message: String, vararg objects: Any?) {
|
||||
if (isEnabled(level)) {
|
||||
printAny(level, message, *objects)
|
||||
}
|
||||
}
|
||||
|
||||
override fun log(level: LogLevel, message: String, ex: Throwable) {
|
||||
if (isEnabled(level)) {
|
||||
print(level, message, ex)
|
||||
}
|
||||
}
|
||||
|
||||
override fun debug(message: String, vararg objects: Any?) = log(DEBUG, message, *objects)
|
||||
override fun debug(message: String) = log(DEBUG, message)
|
||||
override fun debug(message: String, obj: Any?) = log(DEBUG, message, obj)
|
||||
override fun debug(message: String, obj1: Any?, obj2: Any?) = log(DEBUG, message, obj1, obj2)
|
||||
override fun debug(message: String, ex: Throwable) = log(DEBUG, message, ex)
|
||||
|
||||
override fun debug(marker: Marker, msg: String) {
|
||||
if (isDebugEnabled(marker)) {
|
||||
print(DEBUG, msg)
|
||||
}
|
||||
}
|
||||
|
||||
override fun debug(marker: Marker, msg: String, obj: Any?) {
|
||||
if (isDebugEnabled(marker)) {
|
||||
print(DEBUG, msg, obj)
|
||||
}
|
||||
}
|
||||
|
||||
override fun debug(marker: Marker, msg: String, obj1: Any?, obj2: Any?) {
|
||||
if (isDebugEnabled(marker)) {
|
||||
print(DEBUG, msg, obj1, obj2)
|
||||
}
|
||||
}
|
||||
|
||||
override fun debug(marker: Marker, msg: String, vararg objects: Any?) {
|
||||
if (isDebugEnabled(marker)) {
|
||||
printAny(DEBUG, msg, *objects)
|
||||
}
|
||||
}
|
||||
|
||||
override fun debug(marker: Marker, msg: String, ex: Throwable) {
|
||||
if (isDebugEnabled(marker)) {
|
||||
print(DEBUG, msg, ex)
|
||||
}
|
||||
}
|
||||
|
||||
override fun lifecycle(message: String) = log(LIFECYCLE, message)
|
||||
override fun lifecycle(message: String, vararg objects: Any?) = log(LIFECYCLE, message, *objects)
|
||||
override fun lifecycle(message: String, ex: Throwable) = log(LIFECYCLE, message, ex)
|
||||
|
||||
override fun quiet(message: String) = log(QUIET, message)
|
||||
override fun quiet(message: String, vararg objects: Any?) = log(QUIET, message, *objects)
|
||||
override fun quiet(message: String, ex: Throwable) = log(QUIET, message, ex)
|
||||
|
||||
override fun trace(message: String) = debug(message)
|
||||
override fun trace(message: String, obj: Any?) = debug(message, obj)
|
||||
override fun trace(message: String, obj1: Any?, obj2: Any?) = debug(message, obj1, obj2)
|
||||
override fun trace(message: String, vararg objects: Any?) = debug(message, *objects)
|
||||
override fun trace(message: String, ex: Throwable) = debug(message, ex)
|
||||
|
||||
override fun trace(marker: Marker, msg: String) {
|
||||
if (isTraceEnabled(marker)) {
|
||||
print(DEBUG, msg)
|
||||
}
|
||||
}
|
||||
|
||||
override fun trace(marker: Marker, msg: String, obj: Any?) {
|
||||
if (isTraceEnabled(marker)) {
|
||||
print(DEBUG, msg, obj)
|
||||
}
|
||||
}
|
||||
|
||||
override fun trace(marker: Marker, msg: String, obj1: Any?, obj2: Any?) {
|
||||
if (isTraceEnabled(marker)) {
|
||||
print(DEBUG, msg, obj1, obj2)
|
||||
}
|
||||
}
|
||||
|
||||
override fun trace(marker: Marker, msg: String, vararg objects: Any?) {
|
||||
if (isTraceEnabled(marker)) {
|
||||
printAny(DEBUG, msg, *objects)
|
||||
}
|
||||
}
|
||||
|
||||
override fun trace(marker: Marker, msg: String, ex: Throwable) {
|
||||
if (isTraceEnabled) {
|
||||
print(DEBUG, msg, ex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun print(level: LogLevel, message: String) {
|
||||
println("$name - $level: $message")
|
||||
}
|
||||
|
||||
private fun print(level: LogLevel, message: String, ex: Throwable) {
|
||||
print(level, message)
|
||||
ex.printStackTrace(System.out)
|
||||
}
|
||||
|
||||
private fun print(level: LogLevel, message: String, obj: Any?) {
|
||||
print(level, MessageFormatter.format(message, obj).message)
|
||||
}
|
||||
|
||||
private fun print(level: LogLevel, message: String, obj1: Any?, obj2: Any?) {
|
||||
print(level, MessageFormatter.format(message, obj1, obj2).message)
|
||||
}
|
||||
|
||||
private fun printAny(level: LogLevel, message: String, vararg objects: Any?) {
|
||||
print(level, MessageFormatter.arrayFormat(message, objects).message)
|
||||
}
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.unwanted.HasAll
|
||||
import net.corda.gradle.unwanted.HasInt
|
||||
import net.corda.gradle.unwanted.HasLong
|
||||
import net.corda.gradle.unwanted.HasString
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class StubConstructorTest {
|
||||
companion object {
|
||||
private const val STRING_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryStringConstructorToStub"
|
||||
private const val LONG_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryLongConstructorToStub"
|
||||
private const val INT_PRIMARY_CONSTRUCTOR_CLASS = "net.corda.gradle.PrimaryIntConstructorToStub"
|
||||
private const val SECONDARY_CONSTRUCTOR_CLASS = "net.corda.gradle.HasConstructorToStub"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "stub-constructor")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubConstructorWithLongParameter() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { obj ->
|
||||
assertEquals(BIG_NUMBER, obj.longData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasLong>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Long::class.java).also {
|
||||
assertFailsWith<InvocationTargetException> { it.newInstance(BIG_NUMBER) }.targetException.also { ex ->
|
||||
assertThat(ex)
|
||||
.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubConstructorWithStringParameter() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertEquals(MESSAGE, obj.stringData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasString>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).also {
|
||||
assertFailsWith<InvocationTargetException> { it.newInstance(MESSAGE) }.targetException.also { ex ->
|
||||
assertThat(ex)
|
||||
.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showUnannotatedConstructorIsUnaffected() {
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasAll>(SECONDARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { obj ->
|
||||
assertEquals(NUMBER, obj.intData())
|
||||
assertEquals(NUMBER.toLong(), obj.longData())
|
||||
assertEquals("<nothing>", obj.stringData())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubPrimaryConstructorWithStringParameter() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertEquals(MESSAGE, obj.stringData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasString>(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).also {
|
||||
assertFailsWith<InvocationTargetException> { it.newInstance(MESSAGE) }.targetException.also { ex ->
|
||||
assertThat(ex)
|
||||
.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubPrimaryConstructorWithLongParameter() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { obj ->
|
||||
assertEquals(BIG_NUMBER, obj.longData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasLong>(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Long::class.java).also {
|
||||
assertFailsWith<InvocationTargetException> { it.newInstance(BIG_NUMBER) }.targetException.also { ex ->
|
||||
assertThat(ex)
|
||||
.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubPrimaryConstructorWithIntParameter() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { obj ->
|
||||
assertEquals(NUMBER, obj.intData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasInt>(INT_PRIMARY_CONSTRUCTOR_CLASS).apply {
|
||||
getDeclaredConstructor(Int::class.java).apply {
|
||||
val error = assertFailsWith<InvocationTargetException> { newInstance(NUMBER) }.targetException
|
||||
assertThat(error)
|
||||
.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.unwanted.HasUnwantedFun
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import javax.annotation.Resource
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class StubFunctionOutTest {
|
||||
companion object {
|
||||
private const val FUNCTION_CLASS = "net.corda.gradle.HasFunctionToStub"
|
||||
private const val STUB_ME_OUT_ANNOTATION = "net.corda.gradle.jarfilter.StubMeOut"
|
||||
private const val PARAMETER_ANNOTATION = "net.corda.gradle.Parameter"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "stub-function")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
val stubMeOut = cl.load<Annotation>(STUB_ME_OUT_ANNOTATION)
|
||||
val parameter = cl.load<Annotation>(PARAMETER_ANNOTATION)
|
||||
|
||||
cl.load<HasUnwantedFun>(FUNCTION_CLASS).apply {
|
||||
newInstance().also { obj ->
|
||||
assertEquals(MESSAGE, obj.unwantedFun(MESSAGE))
|
||||
}
|
||||
getMethod("unwantedFun", String::class.java).also { method ->
|
||||
assertTrue("StubMeOut annotation missing", method.isAnnotationPresent (stubMeOut))
|
||||
assertTrue("Resource annotation missing", method.isAnnotationPresent(Resource::class.java))
|
||||
method.parameterAnnotations.also { paramAnns ->
|
||||
assertEquals(1, paramAnns.size)
|
||||
assertThat(paramAnns[0])
|
||||
.hasOnlyOneElementSatisfying { a -> a.javaClass.isInstance(parameter) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
val stubMeOut = cl.load<Annotation>(STUB_ME_OUT_ANNOTATION)
|
||||
val parameter = cl.load<Annotation>(PARAMETER_ANNOTATION)
|
||||
|
||||
cl.load<HasUnwantedFun>(FUNCTION_CLASS).apply {
|
||||
newInstance().also { obj ->
|
||||
assertFailsWith<UnsupportedOperationException> { obj.unwantedFun(MESSAGE) }.also { ex ->
|
||||
assertEquals("Method has been deleted", ex.message)
|
||||
}
|
||||
}
|
||||
getMethod("unwantedFun", String::class.java).also { method ->
|
||||
assertFalse("StubMeOut annotation present", method.isAnnotationPresent(stubMeOut))
|
||||
assertTrue("Resource annotation missing", method.isAnnotationPresent(Resource::class.java))
|
||||
method.parameterAnnotations.also { paramAnns ->
|
||||
assertEquals(1, paramAnns.size)
|
||||
assertThat(paramAnns[0])
|
||||
.hasOnlyOneElementSatisfying { a -> a.javaClass.isInstance(parameter) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import kotlin.test.*
|
||||
|
||||
class StubStaticFunctionTest {
|
||||
companion object {
|
||||
private const val FUNCTION_CLASS = "net.corda.gradle.StaticFunctionsToStub"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "stub-static-function")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubStringFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getDeclaredMethod("unwantedStringToStub", String::class.java).also { method ->
|
||||
method.invoke(null, MESSAGE).also { result ->
|
||||
assertThat(result)
|
||||
.isInstanceOf(String::class.java)
|
||||
.isEqualTo(MESSAGE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getDeclaredMethod("unwantedStringToStub", String::class.java).also { method ->
|
||||
assertFailsWith<InvocationTargetException> { method.invoke(null, MESSAGE) }.targetException.also { ex ->
|
||||
assertThat(ex)
|
||||
.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubLongFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getDeclaredMethod("unwantedLongToStub", Long::class.java).also { method ->
|
||||
method.invoke(null, BIG_NUMBER).also { result ->
|
||||
assertThat(result)
|
||||
.isInstanceOf(Long::class.javaObjectType)
|
||||
.isEqualTo(BIG_NUMBER)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getDeclaredMethod("unwantedLongToStub", Long::class.java).also { method ->
|
||||
assertFailsWith<InvocationTargetException> { method.invoke(null, BIG_NUMBER) }.targetException.also { ex ->
|
||||
assertThat(ex)
|
||||
.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubIntFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getDeclaredMethod("unwantedIntToStub", Int::class.java).also { method ->
|
||||
method.invoke(null, NUMBER).also { result ->
|
||||
assertThat(result)
|
||||
.isInstanceOf(Int::class.javaObjectType)
|
||||
.isEqualTo(NUMBER)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
getDeclaredMethod("unwantedIntToStub", Int::class.java).also { method ->
|
||||
assertFailsWith<InvocationTargetException> { method.invoke(null, NUMBER) }.targetException.also { ex ->
|
||||
assertThat(ex)
|
||||
.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
.hasMessage("Method has been deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stubVoidFunction() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
val staticSeed = getDeclaredMethod("getStaticSeed")
|
||||
assertEquals(0, staticSeed.invoke(null))
|
||||
getDeclaredMethod("unwantedVoidToStub").invoke(null)
|
||||
assertEquals(1, staticSeed.invoke(null))
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<Any>(FUNCTION_CLASS).apply {
|
||||
val staticSeed = getDeclaredMethod("getStaticSeed")
|
||||
assertEquals(0, staticSeed.invoke(null))
|
||||
getDeclaredMethod("unwantedVoidToStub").invoke(null)
|
||||
assertEquals(0, staticSeed.invoke(null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.unwanted.HasUnwantedVal
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class StubValPropertyTest {
|
||||
companion object {
|
||||
private const val PROPERTY_CLASS = "net.corda.gradle.HasValPropertyForStub"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "stub-val-property")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteGetter() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertEquals(MESSAGE, obj.unwantedVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedVal>(PROPERTY_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertFailsWith<UnsupportedOperationException> { obj.unwantedVal }.also { ex ->
|
||||
assertEquals("Method has been deleted", ex.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import net.corda.gradle.unwanted.HasUnwantedVar
|
||||
import org.junit.Assert.*
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class StubVarPropertyTest {
|
||||
companion object {
|
||||
private const val GETTER_CLASS = "net.corda.gradle.HasUnwantedGetForStub"
|
||||
private const val SETTER_CLASS = "net.corda.gradle.HasUnwantedSetForStub"
|
||||
|
||||
private val testProjectDir = TemporaryFolder()
|
||||
private val testProject = JarFilterProject(testProjectDir, "stub-var-property")
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val rules: TestRule = RuleChain
|
||||
.outerRule(testProjectDir)
|
||||
.around(testProject)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteGetter() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedVar>(GETTER_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertEquals(MESSAGE, obj.unwantedVar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedVar>(GETTER_CLASS).apply {
|
||||
getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { obj ->
|
||||
assertFailsWith<UnsupportedOperationException> { obj.unwantedVar }.also { ex ->
|
||||
assertEquals("Method has been deleted", ex.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteSetter() {
|
||||
classLoaderFor(testProject.sourceJar).use { cl ->
|
||||
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
|
||||
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
|
||||
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
|
||||
obj.unwantedVar = MESSAGE
|
||||
assertEquals(MESSAGE, obj.unwantedVar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classLoaderFor(testProject.filteredJar).use { cl ->
|
||||
cl.load<HasUnwantedVar>(SETTER_CLASS).apply {
|
||||
getConstructor(String::class.java).newInstance(DEFAULT_MESSAGE).also { obj ->
|
||||
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
|
||||
obj.unwantedVar = MESSAGE
|
||||
assertEquals(DEFAULT_MESSAGE, obj.unwantedVar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class UnwantedCacheTest {
|
||||
private companion object {
|
||||
private const val CLASS_NAME = "org.testing.MyClass"
|
||||
private const val LONG_ARG = "(J)V"
|
||||
private const val NO_ARG = "()V"
|
||||
}
|
||||
|
||||
private lateinit var cache: UnwantedCache
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
cache = UnwantedCache()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEmptyCache() {
|
||||
assertFalse(cache.containsClass(CLASS_NAME))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, null, null))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "<init>", NO_ARG))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddingClass() {
|
||||
cache.addClass(CLASS_NAME)
|
||||
assertTrue(cache.containsClass(CLASS_NAME))
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, null, null))
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, "<init>", NO_ARG))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddingMethod() {
|
||||
cache.addMethod(CLASS_NAME, MethodElement("<init>", LONG_ARG))
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, "<init>", LONG_ARG))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "<init>", NO_ARG))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "destroy", LONG_ARG))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, null, null))
|
||||
assertFalse(cache.containsMethod(CLASS_NAME, "nonsense", null))
|
||||
assertFalse(cache.containsClass(CLASS_NAME))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddingMethodFollowedByClass() {
|
||||
cache.addMethod(CLASS_NAME, MethodElement("<init>", LONG_ARG))
|
||||
cache.addClass(CLASS_NAME)
|
||||
assertTrue(cache.containsMethod(CLASS_NAME, "<init>", LONG_ARG))
|
||||
assertEquals(0, cache.classMethods.size)
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
@file:JvmName("Utilities")
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.junit.AssumptionViolatedException
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.StandardCopyOption.*
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.stream.Collectors.*
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.full.valueParameters
|
||||
|
||||
const val DEFAULT_MESSAGE = "<default-value>"
|
||||
const val MESSAGE = "Goodbye, Cruel World!"
|
||||
const val NUMBER = 111
|
||||
const val BIG_NUMBER = 9999L
|
||||
|
||||
private val classLoader: ClassLoader = object {}.javaClass.classLoader
|
||||
|
||||
// The AssumptionViolatedException must be caught by the JUnit test runner,
|
||||
// which means that it must not be thrown when this class loads.
|
||||
private val testGradleUserHomeValue: String? = System.getProperty("test.gradle.user.home")
|
||||
private val testGradleUserHome: String get() = testGradleUserHomeValue
|
||||
?: throw AssumptionViolatedException("System property 'test.gradle.user.home' not set.")
|
||||
|
||||
fun getGradleArgsForTasks(vararg taskNames: String): MutableList<String> = getBasicArgsForTasks(*taskNames).apply { add("--info") }
|
||||
fun getBasicArgsForTasks(vararg taskNames: String): MutableList<String> = mutableListOf(*taskNames, "--stacktrace", "-g", testGradleUserHome)
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun copyResourceTo(resourceName: String, target: Path) {
|
||||
classLoader.getResourceAsStream(resourceName).use { source ->
|
||||
Files.copy(source, target, REPLACE_EXISTING)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun copyResourceTo(resourceName: String, target: File) = copyResourceTo(resourceName, target.toPath())
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun TemporaryFolder.installResources(vararg resourceNames: String) {
|
||||
resourceNames.forEach { installResource(it) }
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun TemporaryFolder.installResource(resourceName: String): File = newFile(resourceName.fileName).let { file ->
|
||||
copyResourceTo(resourceName, file)
|
||||
file
|
||||
}
|
||||
|
||||
private val String.fileName: String get() = substring(1 + lastIndexOf('/'))
|
||||
|
||||
val String.toPackageFormat: String get() = replace('/', '.')
|
||||
fun pathsOf(vararg types: KClass<*>): Set<String> = types.map { it.java.name.toPathFormat }.toSet()
|
||||
|
||||
fun TemporaryFolder.pathOf(vararg elements: String): Path = Paths.get(root.absolutePath, *elements)
|
||||
|
||||
fun arrayOfJunk(size: Int) = ByteArray(size).apply {
|
||||
for (i in 0 until size) {
|
||||
this[i] = (i and 0xFF).toByte()
|
||||
}
|
||||
}
|
||||
|
||||
val KFunction<*>.hasAnyOptionalParameters: Boolean
|
||||
get() = valueParameters.any(KParameter::isOptional)
|
||||
|
||||
val KFunction<*>.hasAllOptionalParameters: Boolean
|
||||
get() = valueParameters.all(KParameter::isOptional)
|
||||
|
||||
val KFunction<*>.hasAllMandatoryParameters: Boolean
|
||||
get() = valueParameters.none(KParameter::isOptional)
|
||||
|
||||
val <T : Any> KClass<T>.noArgConstructor: KFunction<T>?
|
||||
get() = constructors.firstOrNull(KFunction<*>::hasAllOptionalParameters)
|
||||
|
||||
@Throws(MalformedURLException::class)
|
||||
fun classLoaderFor(jar: Path) = URLClassLoader(arrayOf(jar.toUri().toURL()), classLoader)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@Throws(ClassNotFoundException::class)
|
||||
fun <T> ClassLoader.load(className: String)
|
||||
= Class.forName(className, true, this) as Class<T>
|
||||
|
||||
fun Path.getClassNames(prefix: String): List<String> {
|
||||
val resourcePrefix = prefix.toPathFormat
|
||||
return ZipFile(toFile()).stream()
|
||||
.filter { it.name.startsWith(resourcePrefix) && it.name.endsWith(".class") }
|
||||
.map { it.name.removeSuffix(".class").toPackageFormat }
|
||||
.collect(toList<String>())
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.InvalidUserDataException
|
||||
import org.junit.Test
|
||||
import java.io.IOException
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class UtilsTest {
|
||||
@Test
|
||||
fun testRethrowingCheckedException() {
|
||||
val ex = assertFailsWith<GradleException> { rethrowAsUncheckedException(IOException(MESSAGE)) }
|
||||
assertThat(ex)
|
||||
.hasMessage(MESSAGE)
|
||||
.hasCauseExactlyInstanceOf(IOException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRethrowingCheckExceptionWithoutMessage() {
|
||||
val ex = assertFailsWith<GradleException> { rethrowAsUncheckedException(IOException()) }
|
||||
assertThat(ex)
|
||||
.hasMessage("")
|
||||
.hasCauseExactlyInstanceOf(IOException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRethrowingUncheckedException() {
|
||||
val ex = assertFailsWith<IllegalArgumentException> { rethrowAsUncheckedException(IllegalArgumentException(MESSAGE)) }
|
||||
assertThat(ex)
|
||||
.hasMessage(MESSAGE)
|
||||
.hasNoCause()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRethrowingGradleException() {
|
||||
val ex = assertFailsWith<InvalidUserDataException> { rethrowAsUncheckedException(InvalidUserDataException(MESSAGE)) }
|
||||
assertThat(ex)
|
||||
.hasMessage(MESSAGE)
|
||||
.hasNoCause()
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package net.corda.gradle.jarfilter.annotations
|
||||
|
||||
import kotlin.annotation.AnnotationRetention.BINARY
|
||||
import kotlin.annotation.AnnotationTarget.PROPERTY
|
||||
|
||||
@Retention(BINARY)
|
||||
@Target(PROPERTY)
|
||||
annotation class Deletable
|
@ -1,47 +0,0 @@
|
||||
@file:JvmName("AsmTools")
|
||||
package net.corda.gradle.jarfilter.asm
|
||||
|
||||
import net.corda.gradle.jarfilter.descriptor
|
||||
import net.corda.gradle.jarfilter.toPathFormat
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
fun ByteArray.accept(visitor: (ClassVisitor) -> ClassVisitor): ByteArray {
|
||||
return ClassWriter(COMPUTE_MAXS).let { writer ->
|
||||
ClassReader(this).accept(visitor(writer), 0)
|
||||
writer.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
private val String.resourceName: String get() = "$toPathFormat.class"
|
||||
val Class<*>.resourceName get() = name.resourceName
|
||||
val Class<*>.bytecode: ByteArray get() = classLoader.getResourceAsStream(resourceName).use { it.readBytes() }
|
||||
val Class<*>.descriptor: String get() = name.descriptor
|
||||
|
||||
/**
|
||||
* Functions for converting bytecode into a "live" Java class.
|
||||
*/
|
||||
inline fun <reified T: R, reified R: Any> ByteArray.toClass(): Class<out R> = toClass(T::class.java, R::class.java)
|
||||
|
||||
fun <T: R, R: Any> ByteArray.toClass(type: Class<in T>, asType: Class<out R>): Class<out R>
|
||||
= BytecodeClassLoader(this, type.name, type.classLoader).createClass().asSubclass(asType)
|
||||
|
||||
private class BytecodeClassLoader(
|
||||
private val bytecode: ByteArray,
|
||||
private val className: String,
|
||||
parent: ClassLoader
|
||||
) : ClassLoader(parent) {
|
||||
internal fun createClass(): Class<*> {
|
||||
return defineClass(className, bytecode, 0, bytecode.size).apply { resolveClass(this) }
|
||||
}
|
||||
|
||||
// Ensure that the class we create also honours Class<*>.bytecode (above).
|
||||
override fun getResourceAsStream(name: String): InputStream? {
|
||||
return if (name == className.resourceName) ByteArrayInputStream(bytecode) else super.getResourceAsStream(name)
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package net.corda.gradle.jarfilter.asm
|
||||
|
||||
import net.corda.gradle.jarfilter.MetadataTransformer
|
||||
import net.corda.gradle.jarfilter.getClassInternalName
|
||||
import net.corda.gradle.jarfilter.toPackageFormat
|
||||
import net.corda.gradle.jarfilter.mutableList
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||
|
||||
internal class ClassMetadata(
|
||||
logger: Logger,
|
||||
d1: List<String>,
|
||||
d2: List<String>
|
||||
) : MetadataTransformer<ProtoBuf.Class>(
|
||||
logger,
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
{},
|
||||
d1,
|
||||
d2,
|
||||
ProtoBuf.Class::parseFrom
|
||||
) {
|
||||
override val typeTable = TypeTable(message.typeTable)
|
||||
override val className = nameResolver.getClassInternalName(message.fqName)
|
||||
override val nestedClassNames = mutableList(message.nestedClassNameList)
|
||||
override val properties = mutableList(message.propertyList)
|
||||
override val functions = mutableList(message.functionList)
|
||||
override val constructors = mutableList(message.constructorList)
|
||||
override val typeAliases = mutableList(message.typeAliasList)
|
||||
override val sealedSubclassNames = mutableList(message.sealedSubclassFqNameList)
|
||||
|
||||
override fun rebuild(): ProtoBuf.Class = message
|
||||
|
||||
val sealedSubclasses: List<String> = sealedSubclassNames.map {
|
||||
// Transform "a/b/c/BaseName$SubclassName" -> "a.b.c.BaseName$SubclassName"
|
||||
nameResolver.getClassInternalName(it).toPackageFormat }.toList()
|
||||
|
||||
val nestedClasses: List<String>
|
||||
|
||||
init {
|
||||
val internalClassName = className.toPackageFormat
|
||||
nestedClasses = nestedClassNames.map { "$internalClassName\$${nameResolver.getClassInternalName(it)}" }.toList()
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package net.corda.gradle.jarfilter.asm
|
||||
|
||||
import net.corda.gradle.jarfilter.MetadataTransformer
|
||||
import net.corda.gradle.jarfilter.mutableList
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.deserialization.TypeTable
|
||||
|
||||
internal class FileMetadata(
|
||||
logger: Logger,
|
||||
d1: List<String>,
|
||||
d2: List<String>
|
||||
) : MetadataTransformer<ProtoBuf.Package>(
|
||||
logger,
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
{},
|
||||
d1,
|
||||
d2,
|
||||
ProtoBuf.Package::parseFrom
|
||||
) {
|
||||
override val typeTable = TypeTable(message.typeTable)
|
||||
override val properties = mutableList(message.propertyList)
|
||||
override val functions = mutableList(message.functionList)
|
||||
override val typeAliases = mutableList(message.typeAliasList)
|
||||
|
||||
override fun rebuild(): ProtoBuf.Package = message
|
||||
|
||||
val typeAliasNames: List<String> = typeAliases.map { nameResolver.getString(it.name) }.toList()
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
@file:JvmName("MetadataTools")
|
||||
package net.corda.gradle.jarfilter.asm
|
||||
|
||||
import net.corda.gradle.jarfilter.StdOutLogging
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.*
|
||||
import org.objectweb.asm.*
|
||||
import org.objectweb.asm.Opcodes.ASM6
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private val metadataClass: Class<out Annotation>
|
||||
= object {}.javaClass.classLoader.loadClass("kotlin.Metadata") as Class<out Annotation>
|
||||
|
||||
/**
|
||||
* Rewrite the bytecode for this class with the Kotlin @Metadata of another class.
|
||||
*/
|
||||
inline fun <reified T: Any, reified X: Any> recodeMetadataFor(): ByteArray = T::class.java.metadataAs(X::class.java)
|
||||
|
||||
fun <T: Any, X: Any> Class<in T>.metadataAs(template: Class<in X>): ByteArray {
|
||||
val metadata = template.readMetadata().let { m ->
|
||||
val templateDescriptor = template.descriptor
|
||||
val templatePrefix = templateDescriptor.dropLast(1) + '$'
|
||||
val targetDescriptor = descriptor
|
||||
val targetPrefix = targetDescriptor.dropLast(1) + '$'
|
||||
Pair(m.first, m.second.map { s ->
|
||||
when {
|
||||
// Replace any references to the template class with the target class.
|
||||
s == templateDescriptor -> targetDescriptor
|
||||
s.startsWith(templatePrefix) -> targetPrefix + s.substring(templatePrefix.length)
|
||||
else -> s
|
||||
}
|
||||
}.toList())
|
||||
}
|
||||
return bytecode.accept { w -> MetadataWriter(metadata, w) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Kotlin reflection only supports classes atm, so use this to examine file metadata.
|
||||
*/
|
||||
internal val Class<*>.fileMetadata: FileMetadata get() {
|
||||
val (d1, d2) = readMetadata()
|
||||
return FileMetadata(StdOutLogging(kotlin), d1, d2)
|
||||
}
|
||||
|
||||
/**
|
||||
* For accessing the parts of class metadata that Kotlin reflection cannot reach.
|
||||
*/
|
||||
internal val Class<*>.classMetadata: ClassMetadata get() {
|
||||
val (d1, d2) = readMetadata()
|
||||
return ClassMetadata(StdOutLogging(kotlin), d1, d2)
|
||||
}
|
||||
|
||||
private fun Class<*>.readMetadata(): Pair<List<String>, List<String>> {
|
||||
val metadata = getAnnotation(metadataClass)
|
||||
val d1 = metadataClass.getMethod(METADATA_DATA_FIELD_NAME)
|
||||
val d2 = metadataClass.getMethod(METADATA_STRINGS_FIELD_NAME)
|
||||
return Pair(d1.invoke(metadata).asList(), d2.invoke(metadata).asList())
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> Any.asList(): List<T> {
|
||||
return (this as? Array<T>)?.toList() ?: emptyList()
|
||||
}
|
||||
|
||||
private class MetadataWriter(metadata: Pair<List<String>, List<String>>, visitor: ClassVisitor) : ClassVisitor(ASM6, visitor) {
|
||||
private val kotlinMetadata: MutableMap<String, List<String>> = mutableMapOf(
|
||||
METADATA_DATA_FIELD_NAME to metadata.first,
|
||||
METADATA_STRINGS_FIELD_NAME to metadata.second
|
||||
)
|
||||
|
||||
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
|
||||
val av = super.visitAnnotation(descriptor, visible) ?: return null
|
||||
return if (descriptor == METADATA_DESC) KotlinMetadataWriter(av) else av
|
||||
}
|
||||
|
||||
private inner class KotlinMetadataWriter(av: AnnotationVisitor) : AnnotationVisitor(api, av) {
|
||||
override fun visitArray(name: String): AnnotationVisitor? {
|
||||
val av = super.visitArray(name)
|
||||
if (av != null) {
|
||||
val data = kotlinMetadata.remove(name) ?: return av
|
||||
data.forEach { av.visit(null, it) }
|
||||
av.visitEnd()
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
@file:JvmName("JavaMatchers")
|
||||
package net.corda.gradle.jarfilter.matcher
|
||||
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.DiagnosingMatcher
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.core.IsEqual.*
|
||||
import java.lang.reflect.Method
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
fun isMethod(name: Matcher<in String>, returnType: Matcher<in Class<*>>, vararg parameters: Matcher<in Class<*>>): Matcher<Method> {
|
||||
return MethodMatcher(name, returnType, *parameters)
|
||||
}
|
||||
|
||||
fun isMethod(name: String, returnType: Class<*>, vararg parameters: Class<*>): Matcher<Method> {
|
||||
return isMethod(equalTo(name), equalTo(returnType), *parameters.toMatchers())
|
||||
}
|
||||
|
||||
private fun Array<out Class<*>>.toMatchers() = map(::equalTo).toTypedArray()
|
||||
|
||||
val KClass<*>.javaDeclaredMethods: List<Method> get() = java.declaredMethods.toList()
|
||||
|
||||
/**
|
||||
* Matcher logic for a Java [Method] object. Also applicable to constructors.
|
||||
*/
|
||||
private class MethodMatcher(
|
||||
private val name: Matcher<in String>,
|
||||
private val returnType: Matcher<in Class<*>>,
|
||||
vararg parameters: Matcher<in Class<*>>
|
||||
) : DiagnosingMatcher<Method>() {
|
||||
private val parameters = listOf(*parameters)
|
||||
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("Method[name as ").appendDescriptionOf(name)
|
||||
.appendText(", returnType as ").appendDescriptionOf(returnType)
|
||||
.appendText(", parameters as '")
|
||||
if (parameters.isNotEmpty()) {
|
||||
val param = parameters.iterator()
|
||||
description.appendValue(param.next())
|
||||
while (param.hasNext()) {
|
||||
description.appendText(",").appendValue(param.next())
|
||||
}
|
||||
}
|
||||
description.appendText("']")
|
||||
}
|
||||
|
||||
override fun matches(obj: Any?, mismatch: Description): Boolean {
|
||||
if (obj == null) {
|
||||
mismatch.appendText("is null")
|
||||
return false
|
||||
}
|
||||
|
||||
val method: Method = obj as? Method ?: return false
|
||||
if (!name.matches(method.name)) {
|
||||
mismatch.appendText("name is ").appendValue(method.name)
|
||||
return false
|
||||
}
|
||||
method.returnType.apply {
|
||||
if (!returnType.matches(this)) {
|
||||
mismatch.appendText("returnType is ").appendValue(this.name)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (method.parameterTypes.size != parameters.size) {
|
||||
mismatch.appendText("number of parameters is ").appendValue(method.parameterTypes.size)
|
||||
.appendText(", parameters=").appendValueList("[", ",", "]", method.parameterTypes)
|
||||
return false
|
||||
}
|
||||
|
||||
var i = 0
|
||||
method.parameterTypes.forEach { param ->
|
||||
if (!parameters[i].matches(param)) {
|
||||
mismatch.appendText("parameter[").appendValue(i).appendText("] is ").appendValue(param)
|
||||
return false
|
||||
}
|
||||
++i
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
@ -1,195 +0,0 @@
|
||||
@file:JvmName("KotlinMatchers")
|
||||
package net.corda.gradle.jarfilter.matcher
|
||||
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.DiagnosingMatcher
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.core.IsEqual.equalTo
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.full.valueParameters
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
|
||||
fun isFunction(name: Matcher<in String>, returnType: Matcher<in String>, vararg parameters: Matcher<in KParameter>): Matcher<KFunction<*>> {
|
||||
return KFunctionMatcher(name, returnType, *parameters)
|
||||
}
|
||||
|
||||
fun isFunction(name: String, returnType: KClass<*>, vararg parameters: KClass<*>): Matcher<KFunction<*>> {
|
||||
return isFunction(equalTo(name), matches(returnType), *parameters.toMatchers())
|
||||
}
|
||||
|
||||
fun isConstructor(returnType: Matcher<in String>, vararg parameters: Matcher<in KParameter>): Matcher<KFunction<*>> {
|
||||
return KFunctionMatcher(equalTo("<init>"), returnType, *parameters)
|
||||
}
|
||||
|
||||
fun isConstructor(returnType: KClass<*>, vararg parameters: KClass<*>): Matcher<KFunction<*>> {
|
||||
return isConstructor(matches(returnType), *parameters.toMatchers())
|
||||
}
|
||||
|
||||
fun isConstructor(returnType: String, vararg parameters: KClass<*>): Matcher<KFunction<*>> {
|
||||
return isConstructor(equalTo(returnType), *parameters.toMatchers())
|
||||
}
|
||||
|
||||
fun hasParam(type: Matcher<in String>): Matcher<KParameter> = KParameterMatcher(type)
|
||||
|
||||
fun hasParam(type: KClass<*>): Matcher<KParameter> = hasParam(matches(type))
|
||||
|
||||
fun isProperty(name: String, type: KClass<*>): Matcher<KProperty<*>> = isProperty(equalTo(name), matches(type))
|
||||
|
||||
fun isProperty(name: Matcher<in String>, type: Matcher<in String>): Matcher<KProperty<*>> = KPropertyMatcher(name, type)
|
||||
|
||||
fun isClass(name: String): Matcher<KClass<*>> = KClassMatcher(equalTo(name))
|
||||
|
||||
fun matches(type: KClass<*>): Matcher<in String> = equalTo(type.qualifiedName)
|
||||
|
||||
private fun Array<out KClass<*>>.toMatchers() = map(::hasParam).toTypedArray()
|
||||
|
||||
/**
|
||||
* Matcher logic for a Kotlin [KFunction] object. Also applicable to constructors.
|
||||
*/
|
||||
private class KFunctionMatcher(
|
||||
private val name: Matcher<in String>,
|
||||
private val returnType: Matcher<in String>,
|
||||
vararg parameters: Matcher<in KParameter>
|
||||
) : DiagnosingMatcher<KFunction<*>>() {
|
||||
private val parameters = listOf(*parameters)
|
||||
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("KFunction[name as ").appendDescriptionOf(name)
|
||||
.appendText(", returnType as ").appendDescriptionOf(returnType)
|
||||
.appendText(", parameters as '")
|
||||
if (parameters.isNotEmpty()) {
|
||||
val param = parameters.iterator()
|
||||
description.appendValue(param.next())
|
||||
while (param.hasNext()) {
|
||||
description.appendText(",").appendValue(param.next())
|
||||
}
|
||||
}
|
||||
description.appendText("']")
|
||||
}
|
||||
|
||||
override fun matches(obj: Any?, mismatch: Description): Boolean {
|
||||
if (obj == null) {
|
||||
mismatch.appendText("is null")
|
||||
return false
|
||||
}
|
||||
|
||||
val function: KFunction<*> = obj as? KFunction<*> ?: return false
|
||||
if (!name.matches(function.name)) {
|
||||
mismatch.appendText("name is ").appendValue(function.name)
|
||||
return false
|
||||
}
|
||||
function.returnType.toString().apply {
|
||||
if (!returnType.matches(this)) {
|
||||
mismatch.appendText("returnType is ").appendValue(this)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (function.valueParameters.size != parameters.size) {
|
||||
mismatch.appendText("number of parameters is ").appendValue(function.valueParameters.size)
|
||||
.appendText(", parameters=").appendValueList("[", ",", "]", function.valueParameters)
|
||||
return false
|
||||
}
|
||||
|
||||
var i = 0
|
||||
function.valueParameters.forEach { param ->
|
||||
if (!parameters[i].matches(param)) {
|
||||
mismatch.appendText("parameter[").appendValue(i).appendText("] is ").appendValue(param)
|
||||
return false
|
||||
}
|
||||
++i
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matcher logic for a Kotlin [KParameter] object.
|
||||
*/
|
||||
private class KParameterMatcher(
|
||||
private val type: Matcher<in String>
|
||||
) : DiagnosingMatcher<KParameter>() {
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("KParameter[type as ").appendDescriptionOf(type)
|
||||
.appendText("]")
|
||||
}
|
||||
|
||||
override fun matches(obj: Any?, mismatch: Description): Boolean {
|
||||
if (obj == null) {
|
||||
mismatch.appendText("is null")
|
||||
return false
|
||||
}
|
||||
|
||||
val parameter: KParameter = obj as? KParameter ?: return false
|
||||
parameter.type.toString().apply {
|
||||
if (!type.matches(this)) {
|
||||
mismatch.appendText("type is ").appendValue(this)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matcher logic for a Kotlin [KProperty] object.
|
||||
*/
|
||||
private class KPropertyMatcher(
|
||||
private val name: Matcher<in String>,
|
||||
private val type: Matcher<in String>
|
||||
) : DiagnosingMatcher<KProperty<*>>() {
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("KProperty[name as ").appendDescriptionOf(name)
|
||||
.appendText(", type as ").appendDescriptionOf(type)
|
||||
.appendText("]")
|
||||
}
|
||||
|
||||
override fun matches(obj: Any?, mismatch: Description): Boolean {
|
||||
if (obj == null) {
|
||||
mismatch.appendText("is null")
|
||||
return false
|
||||
}
|
||||
|
||||
val property: KProperty<*> = obj as? KProperty<*> ?: return false
|
||||
if (!name.matches(property.name)) {
|
||||
mismatch.appendText("name is ").appendValue(property.name)
|
||||
return false
|
||||
}
|
||||
property.returnType.toString().apply {
|
||||
if (!type.matches(this)) {
|
||||
mismatch.appendText("type is ").appendValue(this)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matcher logic for a Kotlin [KClass] object.
|
||||
*/
|
||||
private class KClassMatcher(private val className: Matcher<in String>) : DiagnosingMatcher<KClass<*>>() {
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("KClass[name as ").appendDescriptionOf(className)
|
||||
.appendText("]")
|
||||
}
|
||||
|
||||
override fun matches(obj: Any?, mismatch: Description): Boolean {
|
||||
if (obj == null) {
|
||||
mismatch.appendText("is null")
|
||||
return false
|
||||
}
|
||||
|
||||
val type: KClass<*> = obj as? KClass<*> ?: return false
|
||||
type.jvmName.apply {
|
||||
if (!className.matches(this)) {
|
||||
mismatch.appendText("name is ").appendValue(this)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
@file:JvmName("PackageWithDefaultParameters")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle.jarfilter.template
|
||||
|
||||
import net.corda.gradle.jarfilter.DEFAULT_MESSAGE
|
||||
|
||||
fun hasDefaultParameters(intData: Int=0, message: String=DEFAULT_MESSAGE): String = "$message: intData=$intData"
|
||||
|
||||
fun hasMandatoryParameters(longData: Long=0, message: String=DEFAULT_MESSAGE): String = "$message: longData=$longData"
|
@ -1,34 +0,0 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '$kotlin_version'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
apply from: 'repositories.gradle'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
kotlin {
|
||||
srcDir files(
|
||||
'../resources/test/abstract-function/kotlin',
|
||||
'../resources/test/annotations/kotlin'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
compileOnly files('../../unwanteds/build/libs/unwanteds.jar')
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName = 'abstract-function'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars jar
|
||||
annotations {
|
||||
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
|
||||
forStub = ["net.corda.gradle.jarfilter.StubMeOut"]
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
import net.corda.gradle.jarfilter.StubMeOut
|
||||
|
||||
abstract class AbstractFunctions {
|
||||
@DeleteMe
|
||||
abstract fun toDelete(value: Long): Long
|
||||
|
||||
@StubMeOut
|
||||
abstract fun toStubOut(value: Long): Long
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import kotlin.annotation.AnnotationRetention.*
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
import kotlin.annotation.Retention
|
||||
import kotlin.annotation.Target
|
||||
|
||||
@Target(
|
||||
FILE,
|
||||
CLASS,
|
||||
CONSTRUCTOR,
|
||||
FUNCTION,
|
||||
PROPERTY,
|
||||
PROPERTY_GETTER,
|
||||
PROPERTY_SETTER,
|
||||
FIELD,
|
||||
TYPEALIAS
|
||||
)
|
||||
@Retention(BINARY)
|
||||
annotation class DeleteMe
|
@ -1,19 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import kotlin.annotation.AnnotationRetention.*
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
import kotlin.annotation.Retention
|
||||
import kotlin.annotation.Target
|
||||
|
||||
@Target(
|
||||
FILE,
|
||||
CLASS,
|
||||
CONSTRUCTOR,
|
||||
FUNCTION,
|
||||
PROPERTY,
|
||||
PROPERTY_GETTER,
|
||||
PROPERTY_SETTER,
|
||||
FIELD
|
||||
)
|
||||
@Retention(RUNTIME)
|
||||
annotation class RemoveMe
|
@ -1,15 +0,0 @@
|
||||
package net.corda.gradle.jarfilter
|
||||
|
||||
import kotlin.annotation.AnnotationRetention.*
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
import kotlin.annotation.Retention
|
||||
import kotlin.annotation.Target
|
||||
|
||||
@Target(
|
||||
CONSTRUCTOR,
|
||||
FUNCTION,
|
||||
PROPERTY_GETTER,
|
||||
PROPERTY_SETTER
|
||||
)
|
||||
@Retention(RUNTIME)
|
||||
annotation class StubMeOut
|
@ -1,34 +0,0 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '$kotlin_version'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
apply from: 'repositories.gradle'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
kotlin {
|
||||
srcDir files(
|
||||
'../resources/test/delete-and-stub/kotlin',
|
||||
'../resources/test/annotations/kotlin'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
compileOnly files('../../unwanteds/build/libs/unwanteds.jar')
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName = 'delete-and-stub'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars jar
|
||||
annotations {
|
||||
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
|
||||
forStub = ["net.corda.gradle.jarfilter.StubMeOut"]
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
@file:JvmName("DeletePackageWithStubbed")
|
||||
@file:Suppress("UNUSED")
|
||||
@file:DeleteMe
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
import net.corda.gradle.jarfilter.StubMeOut
|
||||
|
||||
fun bracket(str: String): String = "[$str]"
|
||||
|
||||
@StubMeOut
|
||||
fun stubbed(str: String): String = bracket(str)
|
@ -1,28 +0,0 @@
|
||||
@file:JvmName("HasDeletedInsideStubbed")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
import net.corda.gradle.jarfilter.StubMeOut
|
||||
import net.corda.gradle.unwanted.HasString
|
||||
import net.corda.gradle.unwanted.HasUnwantedFun
|
||||
import net.corda.gradle.unwanted.HasUnwantedVal
|
||||
import net.corda.gradle.unwanted.HasUnwantedVar
|
||||
|
||||
class DeletedFunctionInsideStubbed(private val data: String): HasString, HasUnwantedFun {
|
||||
@DeleteMe
|
||||
override fun unwantedFun(str: String): String = str
|
||||
|
||||
@StubMeOut
|
||||
override fun stringData(): String = unwantedFun(data)
|
||||
}
|
||||
|
||||
class DeletedValInsideStubbed(@DeleteMe override val unwantedVal: String): HasString, HasUnwantedVal {
|
||||
@StubMeOut
|
||||
override fun stringData(): String = unwantedVal
|
||||
}
|
||||
|
||||
class DeletedVarInsideStubbed(@DeleteMe override var unwantedVar: String) : HasString, HasUnwantedVar {
|
||||
@StubMeOut
|
||||
override fun stringData(): String = unwantedVar
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
@file:JvmName("HasPropertyForDeleteAndStub")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
import net.corda.gradle.jarfilter.StubMeOut
|
||||
import net.corda.gradle.unwanted.*
|
||||
|
||||
class HasVarPropertyForDeleteAndStub(value: Long) : HasLongVar {
|
||||
@DeleteMe
|
||||
@get:StubMeOut
|
||||
@set:StubMeOut
|
||||
override var longVar: Long = value
|
||||
}
|
||||
|
||||
class HasValPropertyForDeleteAndStub(str: String) : HasStringVal {
|
||||
@DeleteMe
|
||||
@get:StubMeOut
|
||||
override val stringVal: String = str
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '$kotlin_version'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
apply from: 'repositories.gradle'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
kotlin {
|
||||
srcDir files(
|
||||
'../resources/test/delete-constructor/kotlin',
|
||||
'../resources/test/annotations/kotlin'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
compileOnly files('../../unwanteds/build/libs/unwanteds.jar')
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName = 'delete-constructor'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars jar
|
||||
annotations {
|
||||
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
import net.corda.gradle.unwanted.HasAll
|
||||
|
||||
class HasConstructorToDelete(private val message: String, private val data: Long) : HasAll {
|
||||
@DeleteMe constructor(message: String) : this(message, 0)
|
||||
@DeleteMe constructor(data: Long) : this("<nothing>", data)
|
||||
constructor(data: Int) : this("<nothing>", data.toLong())
|
||||
|
||||
override fun stringData(): String = message
|
||||
override fun longData(): Long = data
|
||||
override fun intData(): Int = data.toInt()
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
@file:JvmName("PrimaryConstructorsToDelete")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
import net.corda.gradle.unwanted.HasInt
|
||||
import net.corda.gradle.unwanted.HasLong
|
||||
import net.corda.gradle.unwanted.HasString
|
||||
|
||||
class PrimaryIntConstructorToDelete @DeleteMe constructor(private val value: Int) : HasInt {
|
||||
override fun intData() = value
|
||||
}
|
||||
|
||||
class PrimaryLongConstructorToDelete @DeleteMe constructor(private val value: Long) : HasLong {
|
||||
override fun longData() = value
|
||||
}
|
||||
|
||||
class PrimaryStringConstructorToDelete @DeleteMe constructor(private val value: String) : HasString {
|
||||
override fun stringData() = value
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '$kotlin_version'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
apply from: 'repositories.gradle'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
kotlin {
|
||||
srcDir files(
|
||||
'../resources/test/delete-extension-val/kotlin',
|
||||
'../resources/test/annotations/kotlin'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
compileOnly files('../../unwanteds/build/libs/unwanteds.jar')
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName = 'delete-extension-val'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars jar
|
||||
annotations {
|
||||
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
import net.corda.gradle.unwanted.HasUnwantedVal
|
||||
|
||||
class HasValExtension(override val unwantedVal: String) : HasUnwantedVal {
|
||||
@DeleteMe
|
||||
val List<String>.unwantedVal: String get() = this[0]
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '$kotlin_version'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
apply from: 'repositories.gradle'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
kotlin {
|
||||
srcDir files(
|
||||
'../resources/test/delete-field/kotlin',
|
||||
'../resources/test/annotations/kotlin'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName = 'delete-field'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars jar
|
||||
annotations {
|
||||
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
@file:JvmName("HasFieldToDelete")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
|
||||
class HasStringFieldToDelete(value: String) {
|
||||
@JvmField
|
||||
@field:DeleteMe
|
||||
val stringField: String = value
|
||||
}
|
||||
|
||||
class HasLongFieldToDelete(value: Long) {
|
||||
@JvmField
|
||||
@field:DeleteMe
|
||||
val longField: Long = value
|
||||
}
|
||||
|
||||
class HasIntFieldToDelete(value: Int) {
|
||||
@JvmField
|
||||
@field:DeleteMe
|
||||
val intField: Int = value
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '$kotlin_version'
|
||||
id 'net.corda.plugins.jar-filter'
|
||||
}
|
||||
apply from: 'repositories.gradle'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
kotlin {
|
||||
srcDir files(
|
||||
'../resources/test/delete-file-typealias/kotlin',
|
||||
'../resources/test/annotations/kotlin'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName = 'delete-file-typealias'
|
||||
}
|
||||
|
||||
import net.corda.gradle.jarfilter.JarFilterTask
|
||||
task jarFilter(type: JarFilterTask) {
|
||||
jars jar
|
||||
annotations {
|
||||
forDelete = ["net.corda.gradle.jarfilter.DeleteMe"]
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
@file:JvmName("FileWithTypeAlias")
|
||||
@file:Suppress("UNUSED")
|
||||
package net.corda.gradle
|
||||
|
||||
import net.corda.gradle.jarfilter.DeleteMe
|
||||
|
||||
typealias FileWantedType = Long
|
||||
|
||||
@DeleteMe
|
||||
typealias FileUnwantedType = (String) -> Boolean
|
||||
|
||||
val Any.FileUnwantedType: String get() = "<value>"
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user