Repository: GradleUp/shadow (kotlin)

Issue:
The plugin changes constants with values similar to package names
### Shadow Version 1.2.3
### Gradle Version 2.14
### Expected Behavior: There will be an option to control this behavior, preferably using filters
### Actual Behavior: All constants are changed

For example
Before Shadow:
`public static final String AttributePrefix = "org.mypackage";`
After Shadow
 `public static final String AttributePrefix = "shadow.mypackage";`

Pull request:
- Closes #232.
- Closes #606.
- Ports https://github.com/apache/maven-shade-plugin/pull/249.

---

- [x] [CHANGELOG](https://github.com/GradleUp/shadow/blob/main/docs/changes/README.md)'s "Unreleased" section has been updated, if applicable.

Patch:
diff --git a/api/shadow.api b/api/shadow.api
index bea4872d9..a82d5997b 100644
--- a/api/shadow.api
+++ b/api/shadow.api
@@ -112,6 +112,7 @@ public abstract interface class com/github/jengelman/gradle/plugins/shadow/reloc
 	public abstract fun applyToSourceContent (Ljava/lang/String;)Ljava/lang/String;
 	public abstract fun canRelocateClass (Ljava/lang/String;)Z
 	public abstract fun canRelocatePath (Ljava/lang/String;)Z
+	public fun getSkipStringConstants ()Z
 	public abstract fun relocateClass (Lcom/github/jengelman/gradle/plugins/shadow/relocation/RelocateClassContext;)Ljava/lang/String;
 	public abstract fun relocatePath (Lcom/github/jengelman/gradle/plugins/shadow/relocation/RelocatePathContext;)Ljava/lang/String;
 }
@@ -127,7 +128,8 @@ public class com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocat
 	public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
 	public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)V
 	public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Z)V
-	public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ZZ)V
+	public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
 	public fun applyToSourceContent (Ljava/lang/String;)Ljava/lang/String;
 	public fun canRelocateClass (Ljava/lang/String;)Z
 	public fun canRelocatePath (Ljava/lang/String;)Z
@@ -135,10 +137,12 @@ public class com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocat
 	public fun exclude (Ljava/lang/String;)V
 	public final fun getExcludes ()Ljava/util/Set;
 	public final fun getIncludes ()Ljava/util/Set;
+	public fun getSkipStringConstants ()Z
 	public fun hashCode ()I
 	public fun include (Ljava/lang/String;)V
 	public fun relocateClass (Lcom/github/jengelman/gradle/plugins/shadow/relocation/RelocateClassContext;)Ljava/lang/String;
 	public fun relocatePath (Lcom/github/jengelman/gradle/plugins/shadow/relocation/RelocatePathContext;)Ljava/lang/String;
+	public fun setSkipStringConstants (Z)V
 }
 
 public abstract interface class com/github/jengelman/gradle/plugins/shadow/tasks/DependencyFilter : java/io/Serializable {
diff --git a/docs/changes/README.md b/docs/changes/README.md
index b9ea958ca..7bd6cf4cd 100644
--- a/docs/changes/README.md
+++ b/docs/changes/README.md
@@ -4,6 +4,7 @@
 
 **Added**
 
+- Support skipping string constant remapping. ([#1401](https://github.com/GradleUp/shadow/pull/1401))
 - Let `assemble` depend on `shadowJar`. ([#1524](https://github.com/GradleUp/shadow/pull/1524))
 - Fail build when inputting AAR files or using Shadow with AGP. ([#1530](https://github.com/GradleUp/shadow/pull/1530))
 
diff --git a/docs/configuration/relocation/README.md b/docs/configuration/relocation/README.md
index 3cfe0e8d0..c8fc680dd 100644
--- a/docs/configuration/relocation/README.md
+++ b/docs/configuration/relocation/README.md
@@ -93,6 +93,63 @@ expression in `%regex[]` before passing it to `include`/`exclude`.
     }
     ```
 
+## Skipping Relocation for String Constants
+
+If there is a class like:
+
+```java
+package foo;
+
+public class Bar {
+  public static void main(String[] args) {
+    System.out.println("foo.Bar");
+  }
+}
+```
+
+in your project, and you configure the relocation like:
+
+=== "Kotlin"
+
+    ```kotlin
+    tasks.shadowJar {
+      relocate("foo", "my.foo")
+    }
+    ```
+
+=== "Groovy"
+
+    ```groovy
+    tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
+      relocate 'foo', 'my.foo'
+    }
+    ```
+
+the string constant `"foo.Bar"` will be relocated to `"my.foo.Bar"` by default. This may not be what you want, you can
+skip relocating string constants in the classes like:
+
+=== "Kotlin"
+
+    ```kotlin
+    tasks.shadowJar {
+      relocate("foo", "my.foo") {
+        // Optionally, defaults to `false`.
+        skipStringConstants = true
+      }
+    }
+    ```
+
+=== "Groovy"
+
+    ```groovy
+    tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
+      relocate('foo', 'my.foo') {
+        // Optionally, defaults to `false`.
+        skipStringConstants = true
+      }
+    }
+    ```
+
 ## Automatically Relocating Dependencies
 
 Shadow is shipped with a task that can be used to automatically configure all packages from all dependencies to be
diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt
index d362996b1..232f5eccd 100644
--- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt
+++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt
@@ -19,13 +19,20 @@ internal class RelocatorRemapper(
 
   override fun mapValue(value: Any): Any {
     return if (value is String) {
-      map(value)
+      mapName(value, mapLiterals = true)
     } else {
       super.mapValue(value)
     }
   }
 
-  override fun map(name: String): String {
+  override fun map(internalName: String): String = mapName(internalName)
+
+  fun mapPath(path: String): String {
+    val dotIndex = path.indexOf('.')
+    return if (dotIndex == -1) path else map(path.take(dotIndex))
+  }
+
+  private fun mapName(name: String, mapLiterals: Boolean = false): String {
     var newName = name
     var prefix = ""
     var suffix = ""
@@ -38,7 +45,9 @@ internal class RelocatorRemapper(
     }
 
     for (relocator in relocators) {
-      if (relocator.canRelocateClass(newName)) {
+      if (mapLiterals && relocator.skipStringConstants) {
+        return name
+      } else if (relocator.canRelocateClass(newName)) {
         return prefix + relocator.relocateClass(newName) + suffix
       } else if (relocator.canRelocatePath(newName)) {
         return prefix + relocator.relocatePath(newName) + suffix
@@ -47,8 +56,4 @@ internal class RelocatorRemapper(
 
     return name
   }
-
-  fun mapPath(path: String): String {
-    return map(path.substringBefore('.'))
-  }
 }
diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/Relocator.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/Relocator.kt
index 073c29726..50616f83e 100644
--- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/Relocator.kt
+++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/Relocator.kt
@@ -1,6 +1,7 @@
 package com.github.jengelman.gradle.plugins.shadow.relocation
 
 import com.github.jengelman.gradle.plugins.shadow.transformers.CacheableTransformer
+import org.gradle.api.tasks.Input
 
 /**
  * Modified from [org.apache.maven.plugins.shade.relocation.Relocator.java](https://github.com/apache/maven-shade-plugin/blob/master/src/main/java/org/apache/maven/plugins/shade/relocation/Relocator.java).
@@ -19,6 +20,14 @@ public interface Relocator {
 
   public fun applyToSourceContent(sourceContent: String): String
 
+  /**
+   * Indicates whether this relocator should skip relocating string constants.
+   *
+   * Defaults to `false`.
+   */
+  @get:Input
+  public val skipStringConstants: Boolean get() = false
+
   public companion object {
     public val ROLE: String = Relocator::class.java.name
   }
diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt
index ad2d7d0e5..1ae34df27 100644
--- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt
+++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/relocation/SimpleRelocator.kt
@@ -19,6 +19,7 @@ public open class SimpleRelocator @JvmOverloads constructor(
   includes: List<String>? = null,
   excludes: List<String>? = null,
   private val rawString: Boolean = false,
+  @get:Input override var skipStringConstants: Boolean = false,
 ) : Relocator {
   private val pattern: String
   private val pathPattern: String
@@ -137,6 +138,7 @@ public open class SimpleRelocator @JvmOverloads constructor(
     if (this === other) return true
     if (other !is SimpleRelocator) return false
     return rawString == other.rawString &&
+      skipStringConstants == other.skipStringConstants &&
       pattern == other.pattern &&
       pathPattern == other.pathPattern &&
       shadedPattern == other.shadedPattern &&
@@ -149,6 +151,7 @@ public open class SimpleRelocator @JvmOverloads constructor(
 
   override fun hashCode(): Int {
     var result = rawString.hashCode()
+    result = 31 * result + skipStringConstants.hashCode()
     result = 31 * result + pattern.hashCode()
     result = 31 * result + pathPattern.hashCode()
     result = 31 * result + shadedPattern.hashCode()


Test patch:
diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt
index 083d955ce..d8c42802c 100644
--- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt
+++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt
@@ -585,16 +585,7 @@ class RelocationTest : BasePluginTest() {
 
   @Test
   fun relocateStringConstantsByDefault() {
-    writeClass {
-      """
-        package my;
-        public class Main {
-          public static void main(String[] args) {
-            System.out.println("junit.framework.Test");
-          }
-        }
-      """.trimIndent()
-    }
+    writeClassWithStringRef()
     projectScriptPath.appendText(
       """
         $shadowJar {
@@ -614,6 +605,46 @@ class RelocationTest : BasePluginTest() {
     )
   }
 
+  @Issue(
+    "https://github.com/GradleUp/shadow/issues/232",
+  )
+  @ParameterizedTest
+  @ValueSource(booleans = [false, true])
+  fun canDisableRelocateStringConstants(skipStringConstants: Boolean) {
+    writeClassWithStringRef()
+    projectScriptPath.appendText(
+      """
+        $shadowJar {
+          manifest {
+            attributes '$mainClassAttributeKey': 'my.Main'
+          }
+          relocate('junit', 'foo.junit') {
+            skipStringConstants = $skipStringConstants
+          }
+        }
+      """.trimIndent(),
+    )
+
+    run(shadowJarTask)
+    val result = runProcess("java", "-jar", outputShadowJar.use { it.toString() })
+
+    val expected = if (skipStringConstants) "junit.framework.Test" else "foo.junit.framework.Test"
+    assertThat(result).contains(expected)
+  }
+
+  private fun writeClassWithStringRef() {
+    writeClass {
+      """
+        package my;
+        public class Main {
+          public static void main(String[] args) {
+            System.out.println("junit.framework.Test");
+          }
+        }
+      """.trimIndent()
+    }
+  }
+
   private companion object {
     @JvmStatic
     fun prefixProvider() = listOf(