Copilot commented on code in PR #21:
URL: https://github.com/apache/maven-war-plugin/pull/21#discussion_r3665313427


##########
src/main/java/org/apache/maven/plugins/war/packaging/WarPackagingContext.java:
##########
@@ -96,6 +97,17 @@ public interface WarPackagingContext
      */
     boolean archiveClasses();
 
+    /**
+     * Skip explded war creation, build web archive on source paths.

Review Comment:
   Typo in Javadoc: change 'explded' to 'exploded' (this is a user-facing API 
doc).



##########
src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java:
##########
@@ -446,14 +466,15 @@ protected String[] getDependentWarIncludes()
      * @throws MojoExecutionException In case of failure.
      * @throws MojoFailureException In case of failure.
      */
-    public void buildExplodedWebapp( File webapplicationDirectory )
+    public WarPackagingContext buildExplodedWebapp( File 
webapplicationDirectory )

Review Comment:
   Changing these public methods from `void` to `WarPackagingContext` is 
source/binary incompatible for any external code or subclasses that 
override/call them. Prefer adding new overloads (or new methods) that return 
`WarPackagingContext` while keeping the existing signatures (possibly 
deprecated) to preserve compatibility.



##########
src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java:
##########
@@ -373,6 +374,15 @@ public abstract class AbstractWarMojo
     @Parameter( defaultValue = "WEB-INF/lib/" )
     private String outdatedCheckPath;
 
+    /**
+     * You can skip the exploded war creation when building the war archive.
+     * War archive are created based on the source paths.
+     *
+     * @since 3.3.3
+     */
+    @Parameter( defaultValue = "false", name = "maven.war.exploded.skip" )
+    private boolean skipExplodedWarCreation;

Review Comment:
   If this is intended to be set via CLI system properties (e.g. 
`-Dmaven.war.exploded.skip=true`), `@Parameter` should use the `property = ...` 
attribute rather than `name = ...`. `name` controls the XML element name in 
plugin configuration, while `property` enables `-D` wiring.



##########
src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java:
##########
@@ -471,7 +492,7 @@ public void buildExplodedWebapp( File 
webapplicationDirectory )
      * @throws MojoFailureException if an unexpected error occurred while 
packaging the webapp
      * @throws IOException if an error occurred while copying the files
      */
-    public void buildWebapp( MavenProject mavenProject, File 
webapplicationDirectory )
+    public WarPackagingContext buildWebapp( MavenProject mavenProject, File 
webapplicationDirectory )

Review Comment:
   Changing these public methods from `void` to `WarPackagingContext` is 
source/binary incompatible for any external code or subclasses that 
override/call them. Prefer adding new overloads (or new methods) that return 
`WarPackagingContext` while keeping the existing signatures (possibly 
deprecated) to preserve compatibility.



##########
src/main/java/org/apache/maven/plugins/war/WarMojo.java:
##########
@@ -226,16 +242,48 @@ private void performPackaging( File warFile )
             + " from the generated webapp archive." );
         getLog().debug( "Including " + Arrays.asList( getPackagingIncludes() ) 
+ " in the generated webapp archive." );
 
-        warArchiver.addDirectory( getWebappDirectory(), 
getPackagingIncludes(), getPackagingExcludes() );
-
         final File webXmlFile = new File( getWebappDirectory(), 
"WEB-INF/web.xml" );
         if ( webXmlFile.exists() )
         {
             warArchiver.setWebxml( webXmlFile );
         }
 
-        warArchiver.setRecompressAddedZips( isRecompressZippedFiles() );
+        if ( context.skipExplodedWarCreation() )
+        {
+            Map<String, File> map = 
context.getWarResourceCopy().getSourceTargetMappings();
+            if ( map.containsKey( "WEB-INF/web.xml" ) )
+            {
+                warArchiver.setWebxml( map.get( "WEB-INF/web.xml" ) );
+                map.remove( "WEB-INF/web.xml" );
+            }

Review Comment:
   Mutating the context’s shared `sourceTargetMappings` here can have 
unintended side effects later in the method (and for any other consumers of the 
context). Also, removing `WEB-INF/web.xml` from the mapping means it may no 
longer be part of the computed exclude set later, potentially allowing it to be 
added again via `addDirectory(...)` if it exists in the exploded dir. Prefer 
working on a defensive copy of the map and ensure `WEB-INF/web.xml` is always 
excluded from the later directory add when `setWebxml(...)` is used.



##########
src/main/java/org/apache/maven/plugins/war/WarMojo.java:
##########
@@ -226,16 +242,48 @@ private void performPackaging( File warFile )
             + " from the generated webapp archive." );
         getLog().debug( "Including " + Arrays.asList( getPackagingIncludes() ) 
+ " in the generated webapp archive." );
 
-        warArchiver.addDirectory( getWebappDirectory(), 
getPackagingIncludes(), getPackagingExcludes() );
-
         final File webXmlFile = new File( getWebappDirectory(), 
"WEB-INF/web.xml" );
         if ( webXmlFile.exists() )
         {
             warArchiver.setWebxml( webXmlFile );
         }
 
-        warArchiver.setRecompressAddedZips( isRecompressZippedFiles() );
+        if ( context.skipExplodedWarCreation() )
+        {
+            Map<String, File> map = 
context.getWarResourceCopy().getSourceTargetMappings();
+            if ( map.containsKey( "WEB-INF/web.xml" ) )
+            {
+                warArchiver.setWebxml( map.get( "WEB-INF/web.xml" ) );
+                map.remove( "WEB-INF/web.xml" );
+            }
+
+            SourceTargetMappingResourceFilter filter = new 
SourceTargetMappingResourceFilter( warArchiver );
+            Map<String, PlexusIoResource> plexusIoResourceMap =
+                    filter.filteredResources( getPackagingIncludes(), 
getPackagingExcludes(), "", map );
+            Iterator<Map.Entry<String, PlexusIoResource>> it = 
plexusIoResourceMap.entrySet().iterator();
+            while ( it.hasNext() )
+            {
+                Map.Entry<String, PlexusIoResource> rez = it.next();
+                warArchiver.addResource( rez.getValue(), rez.getKey(), -1 );
+            }
 
+            //copy from exploded war
+            //case 1: user uses some other plugins to copy files to exploded 
war
+            //case 2: copy all filtered files, filtered resources are not 
tracked and still synced to exploded war
+            //case 3: web.xml using variables that would be filtered by 
filteringDeploymentDescriptors
+            Set<String> targetPaths = 
context.getWarResourceCopy().getSourceTargetMappings().keySet();
+            Set<String> newExcludes = new HashSet<>( targetPaths );
+            newExcludes.addAll( Arrays.asList( getPackagingExcludes() ) );
+            warArchiver
+                    .addDirectory( getWebappDirectory(), 
getPackagingIncludes(), newExcludes.toArray( new String[0] ) );

Review Comment:
   Building an excludes array containing one entry per mapped file can become 
extremely large for big projects, which risks negating the performance gain 
(DirectoryScanner exclude matching cost + memory pressure). Consider avoiding 
the full `addDirectory(...)` scan in skip mode and instead add only the known 
remaining resources (e.g., explicitly tracked filtered outputs and descriptors) 
or exclude via coarser patterns/prefixes rather than enumerating every file.



##########
src/main/java/org/apache/maven/plugins/war/util/ClassesPackager.java:
##########
@@ -41,6 +42,32 @@
 public class ClassesPackager
 {
 
+
+    public void packageClasses( Map<String, File> allClassFiles, File 
targetFile, JarArchiver jarArchiver,
+                                MavenSession session,
+                                MavenProject project, 
MavenArchiveConfiguration archiveConfiguration,
+                                String outputTimestamp )
+            throws MojoExecutionException
+    {
+        try
+        {
+            final MavenArchiver archiver = new MavenArchiver();
+            archiver.setArchiver( jarArchiver );
+            archiver.setOutputFile( targetFile );
+            archiver.setCreatedBy( "Maven WAR Plugin", 
"org.apache.maven.plugins", "maven-war-plugin" );
+            archiver.configureReproducible( outputTimestamp );
+            for ( Map.Entry<String, File> entry : allClassFiles.entrySet() )
+            {
+                archiver.getArchiver().addFile( entry.getValue(), 
entry.getKey() );
+            }

Review Comment:
   Iteration order of `Map.entrySet()` is not deterministic for `HashMap`, 
which can make the produced classes JAR non-reproducible (entry ordering can 
vary between runs). To preserve reproducible builds, add entries in a stable 
order (e.g., sort by the archive path key, or require/preserve an ordered map).



##########
src/main/java/org/apache/maven/plugins/war/util/SourceTargetMappingResourceFilter.java:
##########
@@ -0,0 +1,502 @@
+package org.apache.maven.plugins.war.util;
+
+/*
+ * 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.
+ */
+
+import org.codehaus.plexus.archiver.war.WarArchiver;
+import org.codehaus.plexus.components.io.attributes.FileAttributes;
+import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes;
+import 
org.codehaus.plexus.components.io.resources.PlexusIoFileResourceCollection;
+import org.codehaus.plexus.components.io.resources.PlexusIoResource;
+import org.codehaus.plexus.components.io.resources.ResourceFactory;
+import org.codehaus.plexus.util.AbstractScanner;
+import org.codehaus.plexus.util.NioFiles;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * Filter resources for resources collected as SourceTargetMappings
+ */
+public class SourceTargetMappingResourceFilter
+{
+
+    private WarArchiver warArchiver;
+
+    public SourceTargetMappingResourceFilter( WarArchiver archiver )
+    {
+        this.warArchiver = archiver;
+    }
+
+    public Map<String, PlexusIoResource> filteredResources( String[] includes, 
String[] excludes, String prefix,
+                                                            Map<String, File> 
mappings ) throws IOException
+    {
+
+        // The PlexusIoFileResourceCollection contains platform-specific 
File.separatorChar which
+        // is an interesting cause of grief, see PLXCOMP-192
+        final ResourceCollection collection =
+                new ResourceCollection( mappings, 
warArchiver.getFilenameComparator() );
+        collection.setFollowingSymLinks( false );
+
+        collection.setIncludes( includes );
+        collection.setExcludes( excludes );
+        collection.setIncludingEmptyDirectories( 
warArchiver.getIncludeEmptyDirs() );
+        collection.setPrefix( prefix );
+        collection.setCaseSensitive( true );
+        collection.setUsingDefaultExcludes( true );
+
+        if ( warArchiver.getOverrideDirectoryMode() > -1 || 
warArchiver.getOverrideFileMode() > -1
+                || warArchiver.getOverrideUid() > -1
+                || warArchiver.getOverrideGid() > -1 || 
warArchiver.getOverrideUserName() != null
+                || warArchiver.getOverrideGroupName() != null )
+        {
+            collection.setOverrideAttributes( warArchiver.getOverrideUid(), 
warArchiver.getOverrideUserName(),
+                    warArchiver.getOverrideGid(),
+                    warArchiver.getOverrideGroupName(), 
warArchiver.getOverrideFileMode(),
+                    warArchiver.getOverrideDirectoryMode() );
+        }
+
+        if ( warArchiver.getDefaultDirectoryMode() > -1 || 
warArchiver.getDefaultFileMode() > -1 )
+        {
+            collection.setDefaultAttributes( -1, null, -1, null, 
warArchiver.getDefaultFileMode(),
+                    warArchiver.getDefaultDirectoryMode() );
+        }
+
+        return collection.getResourceMap();
+    }
+
+
+    private static class ResourceCollection extends 
PlexusIoFileResourceCollection
+    {
+
+        private Map<String, File> sourceTargetMappings;
+        private Comparator<String> fileNameComparator;
+
+        ResourceCollection( Map<String, File> mappings, Comparator<String> 
fileNameComparator )
+        {
+            this.sourceTargetMappings = mappings;
+            this.fileNameComparator = fileNameComparator;
+        }
+
+        public Map<String, PlexusIoResource> getResourceMap() throws 
IOException
+        {
+            final SourceTargetMappingResourcesScanner ds = new 
SourceTargetMappingResourcesScanner();
+            ds.setMappings( this.sourceTargetMappings );
+            final String[] inc = getIncludes();
+            if ( inc != null && inc.length > 0 )
+            {
+                ds.setIncludes( inc );
+            }
+            final String[] exc = getExcludes();
+            if ( exc != null && exc.length > 0 )
+            {
+                ds.setExcludes( exc );
+            }
+            if ( isUsingDefaultExcludes() )
+            {
+                ds.addDefaultExcludes();
+            }
+            ds.setCaseSensitive( isCaseSensitive() );
+            ds.setFollowSymlinks( isFollowingSymLinks() );
+            ds.setFilenameComparator( fileNameComparator );
+            ds.scan();
+
+            final Map<String, PlexusIoResource> result = new HashMap<>();
+            if ( isIncludingEmptyDirectories() )
+            {
+                String[] dirs = ds.getIncludedDirectories();
+                addResources( result, dirs );
+            }
+
+            String[] files = ds.getIncludedFiles();
+            addResources( result, files );
+            return result;
+        }
+
+        private void addResources( Map<String, PlexusIoResource> result, 
String[] resources )
+                throws IOException
+        {
+
+            final HashMap<Integer, String> cache1 = new HashMap<>();
+            final HashMap<Integer, String> cache2 = new HashMap<>();
+            for ( String name : resources )
+            {
+                File f = sourceTargetMappings.get( name );
+                if ( f != null )
+                {
+                    PlexusIoResourceAttributes attrs = new FileAttributes( f, 
cache1, cache2 );
+                    attrs = mergeAttributes( attrs, f.isDirectory() );
+
+                    String remappedName = getName( name );
+
+                    PlexusIoResource resource =
+                            ResourceFactory.createResource( f, remappedName, 
null, getStreamTransformer(), attrs );
+
+                    if ( isSelected( resource ) )
+                    {
+                        result.put( name, resource );
+                    }
+                }
+
+            }
+        }
+    }
+
+
+    /**
+     * A revised version
+     * <p>
+     * filter with target path, but mapping file from source path
+     */
+    private static class SourceTargetMappingResourcesScanner
+            extends AbstractScanner
+    {
+
+
+        /**
+         * The files which matched at least one include and no excludes and 
were selected.
+         */
+        protected Vector<String> filesIncluded;
+
+        /**
+         * The files which did not match any includes or selectors.
+         */
+        protected Vector<String> filesNotIncluded;
+
+        /**
+         * The files which matched at least one include and at least one 
exclude.
+         */
+        protected Vector<String> filesExcluded;
+
+        /**
+         * The directories which matched at least one include and no excludes 
and were selected.
+         */
+        protected Vector<String> dirsIncluded;
+
+        /**
+         * The directories which were found and did not match any includes.
+         */
+        protected Vector<String> dirsNotIncluded;
+
+        /**
+         * The directories which matched at least one include and at least one 
exclude.
+         */
+        protected Vector<String> dirsExcluded;
+
+        /**
+         * The files which matched at least one include and no excludes and 
which a selector discarded.
+         */
+        protected Vector<String> filesDeselected;
+
+        /**
+         * The directories which matched at least one include and no excludes 
but which a selector discarded.
+         */
+        protected Vector<String> dirsDeselected;
+
+        /**
+         * Whether or not symbolic links should be followed.
+         *
+         * @since Ant 1.5
+         */
+        private boolean followSymlinks = true;
+
+        /**
+         * Whether or not everything tested so far has been included.
+         */
+        protected boolean everythingIncluded = true;
+
+        private final String[] tokenizedEmpty = tokenizePathToString( "", 
File.separator );
+
+        private Map<String, File> sourceTargetMappings = new HashMap<>();
+
+        public void setMappings( Map<String, File> mappings )
+        {
+            this.sourceTargetMappings = mappings;
+        }
+
+        /**
+         * Sole constructor.
+         */
+        SourceTargetMappingResourcesScanner()
+        {
+        }
+
+
+        /**
+         * Sets whether or not symbolic links should be followed.
+         *
+         * @param followSymlinks whether or not symbolic links should be 
followed
+         */
+        public void setFollowSymlinks( boolean followSymlinks )
+        {
+            this.followSymlinks = followSymlinks;
+        }
+
+        /**
+         * Scans the base directory for files which match at least one include 
pattern and don't match any exclude
+         * patterns. If there are selectors then the files must pass muster 
there, as well.
+         *
+         * @throws IllegalStateException if the base directory was set 
incorrectly (i.e. if it is <code>null</code>,
+         *                               doesn't exist, or isn't a directory).
+         */
+        public void scan()
+                throws IllegalStateException
+        {
+
+            setupDefaultFilters();
+            setupMatchPatterns();
+
+            filesIncluded = new Vector<String>();
+            filesNotIncluded = new Vector<String>();
+            filesExcluded = new Vector<String>();
+            filesDeselected = new Vector<String>();
+            dirsIncluded = new Vector<String>();
+            dirsNotIncluded = new Vector<String>();
+            dirsExcluded = new Vector<String>();
+            dirsDeselected = new Vector<String>();
+
+            if ( isIncluded( "", tokenizedEmpty ) )
+            {
+
+                if ( !isExcluded( "", tokenizedEmpty ) )
+                {
+                    dirsIncluded.addElement( "" );
+                }
+                else
+                {
+                    dirsExcluded.addElement( "" );
+                }
+            }
+            else
+            {
+                dirsNotIncluded.addElement( "" );
+            }
+            scanAllFiles( "", true );
+        }
+
+
+        /**
+         * Scans the given directory for files and directories. Found files 
and directories are placed in their
+         * respective collections, based on the matching of includes, 
excludes, and the selectors. When a directory is
+         * found, it is scanned recursively.
+         *
+         * @param vpath The path relative to the base directory (needed to 
prevent problems with an absolute path when
+         *              using dir). Must not be <code>null</code>.
+         * @param fast  Whether or not this call is part of a fast scan.
+         * @see #filesIncluded
+         * @see #filesNotIncluded
+         * @see #filesExcluded
+         * @see #dirsIncluded
+         * @see #dirsNotIncluded
+         * @see #dirsExcluded
+         */
+        protected void scanAllFiles( String vpath, boolean fast )
+        {
+            String[] newfiles = sourceTargetMappings.keySet().toArray( new 
String[0] );
+            if ( newfiles == null )
+            {
+                newfiles = new String[0];
+            }
+
+            if ( !followSymlinks )
+            {
+                ArrayList<String> noLinks = new ArrayList<String>();
+                for ( String newfile : newfiles )
+                {
+                    try
+                    {
+                        File sourceFile = sourceTargetMappings.get( newfile );
+                        File dir = sourceFile.getParentFile();
+                        if ( isParentSymbolicLink( dir, newfile ) )
+                        {
+                            String name = vpath + newfile;
+                            if ( sourceFile.isDirectory() )
+                            {
+                                dirsExcluded.addElement( name );
+                            }
+                            else
+                            {
+                                filesExcluded.addElement( name );
+                            }
+                        }
+                        else
+                        {
+                            noLinks.add( newfile );
+                        }
+                    }
+                    catch ( IOException ioe )
+                    {
+                        String msg = "IOException caught while checking " + 
"for links, couldn't get canonical path!";
+                        // will be caught and redirected to Ant's logging 
system
+                        System.err.println( msg );
+                        noLinks.add( newfile );
+                    }
+                }
+                newfiles = noLinks.toArray( new String[noLinks.size()] );
+            }
+
+            if ( filenameComparator != null )
+            {
+                Arrays.sort( newfiles, filenameComparator );
+            }
+
+            for ( String newfile : newfiles )
+            {
+                File file = sourceTargetMappings.get( newfile );
+                String name = vpath + newfile;
+                String[] tokenizedName = tokenizePathToString( name, "/" );
+                if ( file.isFile() )
+                {
+                    if ( isIncluded( name, tokenizedName ) )
+                    {
+                        if ( !isExcluded( name, tokenizedName ) )
+                        {
+                            filesIncluded.addElement( name );
+                        }
+                        else
+                        {
+                            everythingIncluded = false;
+                            filesExcluded.addElement( name );
+                        }
+                    }
+                    else
+                    {
+                        everythingIncluded = false;
+                        filesNotIncluded.addElement( name );
+                    }
+                }
+                else
+                {
+                    throw new IllegalStateException( "Should not be here" );
+                }

Review Comment:
   This scanner throws for any mapping that points to a directory. In skip 
mode, that makes it fragile if directory entries ever get recorded (e.g., if 
you later extend it to support empty dirs, or if a directory ends up in the 
mappings). Prefer handling directories explicitly (track them in `dirsIncluded` 
when `includeEmptyDirs` is enabled) or at least skip them rather than throwing.



##########
src/main/java/org/apache/maven/plugins/war/util/SourceTargetMappingResourceFilter.java:
##########
@@ -0,0 +1,502 @@
+package org.apache.maven.plugins.war.util;
+
+/*
+ * 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.
+ */
+
+import org.codehaus.plexus.archiver.war.WarArchiver;
+import org.codehaus.plexus.components.io.attributes.FileAttributes;
+import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes;
+import 
org.codehaus.plexus.components.io.resources.PlexusIoFileResourceCollection;
+import org.codehaus.plexus.components.io.resources.PlexusIoResource;
+import org.codehaus.plexus.components.io.resources.ResourceFactory;
+import org.codehaus.plexus.util.AbstractScanner;
+import org.codehaus.plexus.util.NioFiles;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * Filter resources for resources collected as SourceTargetMappings
+ */
+public class SourceTargetMappingResourceFilter
+{
+
+    private WarArchiver warArchiver;
+
+    public SourceTargetMappingResourceFilter( WarArchiver archiver )
+    {
+        this.warArchiver = archiver;
+    }
+
+    public Map<String, PlexusIoResource> filteredResources( String[] includes, 
String[] excludes, String prefix,
+                                                            Map<String, File> 
mappings ) throws IOException
+    {
+
+        // The PlexusIoFileResourceCollection contains platform-specific 
File.separatorChar which
+        // is an interesting cause of grief, see PLXCOMP-192
+        final ResourceCollection collection =
+                new ResourceCollection( mappings, 
warArchiver.getFilenameComparator() );
+        collection.setFollowingSymLinks( false );
+
+        collection.setIncludes( includes );
+        collection.setExcludes( excludes );
+        collection.setIncludingEmptyDirectories( 
warArchiver.getIncludeEmptyDirs() );
+        collection.setPrefix( prefix );
+        collection.setCaseSensitive( true );
+        collection.setUsingDefaultExcludes( true );
+
+        if ( warArchiver.getOverrideDirectoryMode() > -1 || 
warArchiver.getOverrideFileMode() > -1
+                || warArchiver.getOverrideUid() > -1
+                || warArchiver.getOverrideGid() > -1 || 
warArchiver.getOverrideUserName() != null
+                || warArchiver.getOverrideGroupName() != null )
+        {
+            collection.setOverrideAttributes( warArchiver.getOverrideUid(), 
warArchiver.getOverrideUserName(),
+                    warArchiver.getOverrideGid(),
+                    warArchiver.getOverrideGroupName(), 
warArchiver.getOverrideFileMode(),
+                    warArchiver.getOverrideDirectoryMode() );
+        }
+
+        if ( warArchiver.getDefaultDirectoryMode() > -1 || 
warArchiver.getDefaultFileMode() > -1 )
+        {
+            collection.setDefaultAttributes( -1, null, -1, null, 
warArchiver.getDefaultFileMode(),
+                    warArchiver.getDefaultDirectoryMode() );
+        }
+
+        return collection.getResourceMap();
+    }
+
+
+    private static class ResourceCollection extends 
PlexusIoFileResourceCollection
+    {
+
+        private Map<String, File> sourceTargetMappings;
+        private Comparator<String> fileNameComparator;
+
+        ResourceCollection( Map<String, File> mappings, Comparator<String> 
fileNameComparator )
+        {
+            this.sourceTargetMappings = mappings;
+            this.fileNameComparator = fileNameComparator;
+        }
+
+        public Map<String, PlexusIoResource> getResourceMap() throws 
IOException
+        {
+            final SourceTargetMappingResourcesScanner ds = new 
SourceTargetMappingResourcesScanner();
+            ds.setMappings( this.sourceTargetMappings );
+            final String[] inc = getIncludes();
+            if ( inc != null && inc.length > 0 )
+            {
+                ds.setIncludes( inc );
+            }
+            final String[] exc = getExcludes();
+            if ( exc != null && exc.length > 0 )
+            {
+                ds.setExcludes( exc );
+            }
+            if ( isUsingDefaultExcludes() )
+            {
+                ds.addDefaultExcludes();
+            }
+            ds.setCaseSensitive( isCaseSensitive() );
+            ds.setFollowSymlinks( isFollowingSymLinks() );
+            ds.setFilenameComparator( fileNameComparator );
+            ds.scan();
+
+            final Map<String, PlexusIoResource> result = new HashMap<>();
+            if ( isIncludingEmptyDirectories() )
+            {
+                String[] dirs = ds.getIncludedDirectories();
+                addResources( result, dirs );
+            }
+
+            String[] files = ds.getIncludedFiles();
+            addResources( result, files );
+            return result;
+        }
+
+        private void addResources( Map<String, PlexusIoResource> result, 
String[] resources )
+                throws IOException
+        {
+
+            final HashMap<Integer, String> cache1 = new HashMap<>();
+            final HashMap<Integer, String> cache2 = new HashMap<>();
+            for ( String name : resources )
+            {
+                File f = sourceTargetMappings.get( name );
+                if ( f != null )
+                {
+                    PlexusIoResourceAttributes attrs = new FileAttributes( f, 
cache1, cache2 );
+                    attrs = mergeAttributes( attrs, f.isDirectory() );
+
+                    String remappedName = getName( name );
+
+                    PlexusIoResource resource =
+                            ResourceFactory.createResource( f, remappedName, 
null, getStreamTransformer(), attrs );
+
+                    if ( isSelected( resource ) )
+                    {
+                        result.put( name, resource );
+                    }
+                }
+
+            }
+        }

Review Comment:
   The `PlexusIoResource` is created with `remappedName`, but the map key uses 
the original `name`. When a non-empty prefix is configured (or when 
`getName(...)` normalizes paths), `warArchiver.addResource(resource, key, ...)` 
can receive an inconsistent target path. Store the resource under 
`remappedName` (or alternatively always use `resource.getName()` when adding to 
the archiver).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to