This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-shade-plugin.git


The following commit(s) were added to refs/heads/master by this push:
     new f2dfe51  Quote text within regular expression patterns correctly
f2dfe51 is described below

commit f2dfe51ac49294fec442eab825dd720ed07702f6
Author: Konrad Windszus <[email protected]>
AuthorDate: Thu Mar 5 11:37:07 2026 +0100

    Quote text within regular expression patterns correctly
    
    Use compiled RegEx patterns to speed up replacement
    Clarify which pattern fields allow which wildcard (follow which syntax)
    Prevent using empty patterns.
    Clarify javadoc for mojo parameter "relocations"
    
    This closes #790
    This closes #793
---
 .../empty-relocation-pattern/invoker.properties    |  18 +++
 src/it/projects/empty-relocation-pattern/pom.xml   |   5 +-
 .../apache/maven/plugins/shade/mojo/ShadeMojo.java |  17 ++-
 .../plugins/shade/relocation/SimpleRelocator.java  | 156 +++++++++++++++------
 src/site/apt/examples/class-relocation.apt.vm      |  24 ++--
 .../relocation/SimpleRelocatorParameterTest.java   |   6 +-
 .../shade/relocation/SimpleRelocatorTest.java      |   4 +
 7 files changed, 169 insertions(+), 61 deletions(-)

diff --git a/src/it/projects/empty-relocation-pattern/invoker.properties 
b/src/it/projects/empty-relocation-pattern/invoker.properties
new file mode 100644
index 0000000..e64d99e
--- /dev/null
+++ b/src/it/projects/empty-relocation-pattern/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+invoker.buildResult = failure
diff --git a/src/it/projects/empty-relocation-pattern/pom.xml 
b/src/it/projects/empty-relocation-pattern/pom.xml
index f7aea0f..fc7320c 100644
--- a/src/it/projects/empty-relocation-pattern/pom.xml
+++ b/src/it/projects/empty-relocation-pattern/pom.xml
@@ -30,9 +30,9 @@ under the License.
   <version>0.1-SNAPSHOT</version>
   <packaging>jar</packaging>
   
-  <name>MSHADE-94</name>
+  <name>Prevent usage of empty pattern</name>
   <description>
-    Test that NullPointerException is not thrown when pattern parameter is 
unset.
+    Test that relocations with empty patterns lead to build failure 
(https://github.com/apache/maven-shade-plugin/issues/793).
   </description>
   
 
@@ -51,6 +51,7 @@ under the License.
             <configuration>
               <relocations>
                 <relocation>
+                  <!-- not valid -->
                   <pattern />
                   <shadedPattern>org.example</shadedPattern>
                 </relocation>
diff --git a/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java 
b/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java
index 0170cdf..2eb2f64 100644
--- a/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java
+++ b/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java
@@ -145,18 +145,29 @@ public class ShadeMojo extends AbstractMojo {
      *       &lt;exclude&gt;org.apache.maven.Public*&lt;/exclude&gt;
      *     &lt;/excludes&gt;
      *   &lt;/relocation&gt;
+     *   &lt;relocation&gt;
+     *     &lt;pattern&gt;Lorg/aspectj/&lt;/pattern&gt;
+     *     
&lt;shadedPattern&gt;Lorg/example/shaded/aspectj/&lt;/shadedPattern&gt;
+     *     &lt;rawString&gt;true&lt;/rawString&gt;
+     *   &lt;/relocation&gt;
      * &lt;/relocations&gt;
      * </pre>
      *
-     * <em>Note:</em> Support for includes exists only since version 1.4.
+     * Each {@code relocation} item must contain a non-empty {@code pattern} 
element. Its value may use either {@code /} or {@code .} as separator. It will 
be normalized accordingly depending on the relocation context (filesystem or 
fully qualified class/package name).
+     * The {@code shadedPattern} element is optional, if not given or empty 
the shaded pattern will be the same as the pattern prefixed by {@code hidden.}. 
Its value may use either {@code /} or {@code .} as separator. It will be 
normalized accordingly depending on the relocation context (filesystem or fully 
qualified class/package name).
+     * When {@code rawString} is set to {@code true} the given pattern is 
treated as regular expression {@link Pattern} otherwise is treated as plain 
String (not supporting any wildcards).
+     * The {@code includes} and {@code excludes} elements are optional and can 
be used to further fine-tune the set of classes to be relocated.
+     * Both are <a 
href="https://ant.apache.org/manual/dirtasks.html#patterns";>Ant-based 
patterns</a>, i.e. support wildcards {@code *}, {@code **} and {@code ?}.
+     * If both {@code includes} and {@code excludes} are given both conditions 
need to be fulfilled for a relocation to happen (i.e. both included and not 
excluded).
+     * @see <a 
href="https://maven.apache.org/plugins/maven-shade-plugin/examples/class-relocation.html";>Class
 Relocation</a>
      */
     @SuppressWarnings("MismatchedReadAndWriteOfArray")
     @Parameter
     private PackageRelocation[] relocations;
 
     /**
-     * Resource transformers to be used. Please see the "Examples" section for 
more information on available
-     * transformers and their configuration.
+     * Resource transformers to be used.
+     * @see <a 
href="https://maven.apache.org/plugins/maven-shade-plugin/examples/resource-transformers.html";>Resource
 Transformers</a>
      */
     @Parameter
     private ResourceTransformer[] transformers;
diff --git 
a/src/main/java/org/apache/maven/plugins/shade/relocation/SimpleRelocator.java 
b/src/main/java/org/apache/maven/plugins/shade/relocation/SimpleRelocator.java
index b801fd0..67c900a 100644
--- 
a/src/main/java/org/apache/maven/plugins/shade/relocation/SimpleRelocator.java
+++ 
b/src/main/java/org/apache/maven/plugins/shade/relocation/SimpleRelocator.java
@@ -22,15 +22,19 @@ import java.util.Collection;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.codehaus.plexus.util.SelectorUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * @author Jason van Zyl
  * @author Mauro Talevi
  */
 public class SimpleRelocator implements Relocator {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(SimpleRelocator.class);
     /**
      * Match dot, slash or space at end of string
      */
@@ -52,85 +56,147 @@ public class SimpleRelocator implements Relocator {
                     + "|"
                     + "([{}(=;,]|\\*/) $");
 
-    private final String pattern;
+    /** plain text, no wildcards, always refers to fully qualified package 
names, may be {@code null} when rawString is {@code true}. */
+    private final String originalPattern;
 
-    private final String pathPattern;
+    /** plain text, no wildcards, always refers to paths with "/" separator, 
never {@code null}. */
+    private final String originalPathPattern;
 
+    /**
+     * May be {@code null} when {@link #rawString} is true
+     */
+    private final Pattern regExPattern;
+
+    private final Pattern regExPathPattern;
+
+    /**
+     * Replacement (no wildcards) for {@link #originalPattern}, may be {@code 
null} when {@link #rawString} is true.
+     */
     private final String shadedPattern;
 
+    /**
+     * Replacement (no wildcards) for {@link #originalPathPattern}.
+     */
     private final String shadedPathPattern;
 
+    /**
+     * Patterns to include in relocation.
+     * Only Ant-based patterns (used with SelectorUtils.matchPath), must not 
start with "%ant[" prefix. Include both forms with dots and slashes, e.g. 
"my/package/**" and "my/package/*" for class patterns, "my/path/**" and 
"my/path/*" for path patterns.
+     */
     private final Set<String> includes;
 
+    /**
+     * Patterns to exclude from relocation.
+     * Only Ant-based patterns (used with SelectorUtils.matchPath), must not 
start with "%ant[" prefix. Include both forms with dots and slashes, e.g. 
"my/package/**" and "my/package/*" for class patterns, "my/path/**" and 
"my/path/*" for path patterns.
+     */
     private final Set<String> excludes;
 
+    // prefix (no wildcards), derived from excludes
     private final Set<String> sourcePackageExcludes = new LinkedHashSet<>();
 
+    // prefix (no wildcards), derived from excludes
     private final Set<String> sourcePathExcludes = new LinkedHashSet<>();
 
     private final boolean rawString;
 
+    /**
+     * Same as {@link #SimpleRelocator(String, String, List, List, boolean)} 
with {@code rawString} set to {@code false}.
+     * @param patt
+     * @param shadedPattern
+     * @param includes
+     * @param excludes
+     */
     public SimpleRelocator(String patt, String shadedPattern, List<String> 
includes, List<String> excludes) {
         this(patt, shadedPattern, includes, excludes, false);
     }
 
+    /**
+     * Creates a relocator with the given patterns and includes/excludes. If 
{@code rawString} is {@code true}, then the given pattern is
+     * treated as regular expression pattern, otherwise it is treated as plain 
text with no wildcards.
+     * In the latter case, the pattern is expected to refer to classes, not 
paths, and the constructor will derive the path pattern by replacing dots with 
slashes.
+     * @param pattern
+     * @param shadedPattern
+     * @param includes
+     * @param excludes
+     * @param rawString
+     */
     public SimpleRelocator(
-            String patt, String shadedPattern, List<String> includes, 
List<String> excludes, boolean rawString) {
+            String pattern, String shadedPattern, List<String> includes, 
List<String> excludes, boolean rawString) {
         this.rawString = rawString;
-
         if (rawString) {
-            this.pathPattern = patt;
+            originalPathPattern = pattern;
             this.shadedPathPattern = shadedPattern;
 
-            this.pattern = null; // not used for raw string relocator
+            originalPattern = null; // not used for raw string relocator
             this.shadedPattern = null; // not used for raw string relocator
         } else {
-            if (patt == null) {
-                this.pattern = "";
-                this.pathPattern = "";
+            if (pattern == null) {
+                // means default package
+                throw new IllegalArgumentException(
+                        "Pattern must not be null, otherwise it is unclear 
what to relocate!");
             } else {
-                this.pattern = patt.replace('/', '.');
-                this.pathPattern = patt.replace('.', '/');
+                originalPattern = pattern.replace('/', '.');
+                originalPathPattern = pattern.replace('.', '/');
             }
 
             if (shadedPattern != null) {
                 this.shadedPattern = shadedPattern.replace('/', '.');
                 this.shadedPathPattern = shadedPattern.replace('.', '/');
             } else {
-                this.shadedPattern = "hidden." + this.pattern;
-                this.shadedPathPattern = "hidden/" + this.pathPattern;
+                this.shadedPattern = "hidden." + originalPattern;
+                this.shadedPathPattern = "hidden/" + originalPathPattern;
             }
         }
 
-        this.includes = normalizePatterns(includes);
-        this.excludes = normalizePatterns(excludes);
+        if (rawString) {
+            if ((includes != null && !includes.isEmpty()) || (excludes != null 
&& !excludes.isEmpty())) {
+                LOGGER.warn("Includes and excludes are ignored when rawString 
is true");
+            }
+            // In raw string mode, the pattern is treated as regular 
expression, so we compile it as is, without quoting
+            // it.
+            this.regExPattern = originalPattern != null ? 
Pattern.compile(originalPattern) : null;
+            this.regExPathPattern = Pattern.compile(originalPathPattern);
+            this.includes = null;
+            this.excludes = null;
+        } else {
+            this.includes = normalizePatterns(includes);
+            this.excludes = normalizePatterns(excludes);
 
-        // Don't replace all dots to slashes, otherwise 
/META-INF/maven/${groupId} can't be matched.
-        if (includes != null && !includes.isEmpty()) {
-            this.includes.addAll(includes);
-        }
+            // Don't replace all dots to slashes, otherwise 
/META-INF/maven/${groupId} can't be matched.
+            if (includes != null && !includes.isEmpty()) {
+                this.includes.addAll(includes);
+            }
 
-        if (excludes != null && !excludes.isEmpty()) {
-            this.excludes.addAll(excludes);
-        }
+            if (excludes != null && !excludes.isEmpty()) {
+                this.excludes.addAll(excludes);
+            }
 
-        if (!rawString && this.excludes != null) {
-            // Create exclude pattern sets for sources
-            for (String exclude : this.excludes) {
-                // Excludes should be subpackages of the global pattern
-                if (exclude.startsWith(pattern)) {
-                    sourcePackageExcludes.add(
-                            
exclude.substring(pattern.length()).replaceFirst("[.][*]$", ""));
-                }
-                // Excludes should be subpackages of the global pattern
-                if (exclude.startsWith(pathPattern)) {
-                    sourcePathExcludes.add(
-                            
exclude.substring(pathPattern.length()).replaceFirst("[/][*]$", ""));
+            if (this.excludes != null) {
+                // Create exclude pattern sets for sources
+                for (String exclude : this.excludes) {
+                    // Excludes should be subpackages of the global pattern
+                    if (exclude.startsWith(originalPattern)) {
+                        sourcePackageExcludes.add(
+                                
exclude.substring(originalPattern.length()).replaceFirst("[.][*]$", ""));
+                    }
+                    // Excludes should be subpackages of the global pattern
+                    if (exclude.startsWith(originalPathPattern)) {
+                        sourcePathExcludes.add(
+                                
exclude.substring(originalPathPattern.length()).replaceFirst("[/][*]$", ""));
+                    }
                 }
             }
+            this.regExPattern = originalPattern != null ? 
Pattern.compile(Pattern.quote(originalPattern)) : null;
+            this.regExPathPattern = 
Pattern.compile(Pattern.quote(originalPathPattern));
         }
     }
 
+    /**
+     * Normalizes the given patterns by replacing dots with slashes and 
slashes with dots so that both forms are returned.
+     *
+     * @param patterns
+     * @return the normalized patterns, or {@code null} if the given patterns 
were {@code null} or empty
+     */
     private static Set<String> normalizePatterns(Collection<String> patterns) {
         Set<String> normalized = null;
 
@@ -177,7 +243,7 @@ public class SimpleRelocator implements Relocator {
     @Override
     public boolean canRelocatePath(String path) {
         if (rawString) {
-            return Pattern.compile(pathPattern).matcher(path).find();
+            return regExPathPattern.matcher(path).find();
         }
 
         if (path.endsWith(".class")) {
@@ -190,7 +256,13 @@ public class SimpleRelocator implements Relocator {
             path = path.substring(1);
         }
 
-        return isIncluded(path) && !isExcluded(path) && 
path.startsWith(pathPattern);
+        if (isIncluded(path) && !isExcluded(path)) {
+            Matcher matcher = regExPathPattern.matcher(path);
+            if (matcher.find()) {
+                return matcher.start() == 0;
+            }
+        }
+        return false;
     }
 
     @Override
@@ -201,20 +273,20 @@ public class SimpleRelocator implements Relocator {
     @Override
     public String relocatePath(String path) {
         if (rawString) {
-            return path.replaceAll(pathPattern, shadedPathPattern);
+            return 
regExPathPattern.matcher(path).replaceAll(shadedPathPattern);
         } else {
-            return path.replaceFirst(pathPattern, shadedPathPattern);
+            return 
regExPathPattern.matcher(path).replaceFirst(shadedPathPattern);
         }
     }
 
     @Override
     public String relocateClass(String input) {
-        return rawString ? input : input.replaceFirst(pattern, shadedPattern);
+        return regExPattern == null ? input : 
regExPattern.matcher(input).replaceFirst(shadedPattern);
     }
 
     @Override
     public String relocateAllClasses(String input) {
-        return rawString ? input : input.replaceAll(pattern, shadedPattern);
+        return regExPattern == null ? input : 
regExPattern.matcher(input).replaceAll(shadedPattern);
     }
 
     @Override
@@ -222,8 +294,8 @@ public class SimpleRelocator implements Relocator {
         if (rawString) {
             return sourceContent;
         }
-        sourceContent = shadeSourceWithExcludes(sourceContent, pattern, 
shadedPattern, sourcePackageExcludes);
-        return shadeSourceWithExcludes(sourceContent, pathPattern, 
shadedPathPattern, sourcePathExcludes);
+        sourceContent = shadeSourceWithExcludes(sourceContent, 
originalPattern, shadedPattern, sourcePackageExcludes);
+        return shadeSourceWithExcludes(sourceContent, originalPathPattern, 
shadedPathPattern, sourcePathExcludes);
     }
 
     private String shadeSourceWithExcludes(
diff --git a/src/site/apt/examples/class-relocation.apt.vm 
b/src/site/apt/examples/class-relocation.apt.vm
index d895fee..0dd6213 100644
--- a/src/site/apt/examples/class-relocation.apt.vm
+++ b/src/site/apt/examples/class-relocation.apt.vm
@@ -67,23 +67,29 @@ Relocating Classes
 +-----
 
   This instructs the plugin to move classes from the package 
<<<org.codehaus.plexus.util>>> and its subpackages
-  into the package <<<org.shaded.plexus.util>>> by moving the corresponding 
JAR file entries and rewritting the
+  into the package <<<org.shaded.plexus.util>>> by moving the corresponding 
JAR file entries and rewriting the
   affected bytecode. The class <<<Xpp3Dom>>> and some others will remain in 
their original package.
 
 
-  It's also possible to narrow the pattern with the <<<include>>> tag:
+  It's also possible to narrow the pattern with the <<<include>>> and/or 
<<<exclude>>> tag:
 
 
 +-----
 <project>
   ...
-                <relocation>
-                  <pattern>org.codehaus.plexus.util</pattern>
-                  <shadedPattern>org.shaded.plexus.util</shadedPattern>
-                  <includes>
-                    <include>org.codehaud.plexus.util.io.*</include>
-                  </includes>
-                </relocation>
+    <relocation>
+      <pattern>org.codehaus.plexus.util</pattern>
+      <shadedPattern>org.shaded.plexus.util</shadedPattern>
+      <includes>
+        <include>org.codehaud.plexus.util.io.*</include>
+      </includes>
+      <excludes>
+        <exclude>org.codehaud.plexus.util.io.*</exclude>
+      </excludes>
+    </relocation>
   ...
 </project>
 +-----
+
+When using relocations with the <<<rawString>>> element set to <<<true>>>, 
both <<<includes>>> and <<<excludes>>> are disregarded but the pattern is 
treated as 
{{{https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html}regular
 expression pattern}}. 
+Also the <<<pattern>>> and <<<shadedPattern>>> are always referring to a 
filesystem path (and must use forward slash (<<</>>>) as separator). Both are 
not normalized in this case.
diff --git 
a/src/test/java/org/apache/maven/plugins/shade/relocation/SimpleRelocatorParameterTest.java
 
b/src/test/java/org/apache/maven/plugins/shade/relocation/SimpleRelocatorParameterTest.java
index ec208eb..461d6d5 100644
--- 
a/src/test/java/org/apache/maven/plugins/shade/relocation/SimpleRelocatorParameterTest.java
+++ 
b/src/test/java/org/apache/maven/plugins/shade/relocation/SimpleRelocatorParameterTest.java
@@ -26,11 +26,6 @@ import static org.junit.Assert.fail;
 
 public class SimpleRelocatorParameterTest {
 
-    @Test
-    public void 
testThatNullPatternInConstructorShouldNotThrowNullPointerException() {
-        constructThenFailOnNullPointerException(null, "");
-    }
-
     @Test
     public void 
testThatNullShadedPatternInConstructorShouldNotThrowNullPointerException() {
         constructThenFailOnNullPointerException("", null);
@@ -43,5 +38,6 @@ public class SimpleRelocatorParameterTest {
         } catch (NullPointerException e) {
             fail("Constructor should not throw null pointer exceptions");
         }
+        // any other exception leads to test failure as well
     }
 }
diff --git 
a/src/test/java/org/apache/maven/plugins/shade/relocation/SimpleRelocatorTest.java
 
b/src/test/java/org/apache/maven/plugins/shade/relocation/SimpleRelocatorTest.java
index 44afee3..45f6601 100644
--- 
a/src/test/java/org/apache/maven/plugins/shade/relocation/SimpleRelocatorTest.java
+++ 
b/src/test/java/org/apache/maven/plugins/shade/relocation/SimpleRelocatorTest.java
@@ -155,6 +155,8 @@ public class SimpleRelocatorTest {
 
         relocator = new SimpleRelocator("org.foo", "private.stuff", null, 
null);
         assertEquals("private.stuff.bar.Class", 
relocator.relocateClass("org.foo.bar.Class"));
+        // make sure that "." does not match "x" in "orgxfoo.bar.Class"
+        assertEquals("orgxfoo.bar.Class", 
relocator.relocateClass("orgxfoo.bar.Class"));
     }
 
     @Test
@@ -170,6 +172,8 @@ public class SimpleRelocatorTest {
         assertEquals(
                 "private.stuff.bar.Class, private.stuff.bar.Class and 
private.stuff.bar.Class",
                 relocator.relocateAllClasses("org.foo.bar.Class, 
private.stuff.bar.Class and org.foo.bar.Class"));
+        // make sure that "." does not match "x" in "orgxfoo.bar.Class"
+        assertEquals("orgxfoo.bar.Class", 
relocator.relocateClass("orgxfoo.bar.Class"));
     }
 
     @Test

Reply via email to