@@ -669,6 +669,8 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
669669 boolean hasScript = false ;
670670 boolean hasMain = false ;
671671 boolean hasTest = false ;
672+ boolean hasMainResources = false ;
673+ boolean hasTestResources = false ;
672674 for (var source : sources ) {
673675 var src = DefaultSourceRoot .fromModel (session , baseDir , outputDirectory , source );
674676 project .addSourceRoot (src );
@@ -680,6 +682,13 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
680682 } else {
681683 hasTest |= ProjectScope .TEST .equals (scope );
682684 }
685+ } else if (Language .RESOURCES .equals (language )) {
686+ ProjectScope scope = src .scope ();
687+ if (ProjectScope .MAIN .equals (scope )) {
688+ hasMainResources = true ;
689+ } else {
690+ hasTestResources |= ProjectScope .TEST .equals (scope );
691+ }
683692 } else {
684693 hasScript |= Language .SCRIPT .equals (language );
685694 }
@@ -700,11 +709,149 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
700709 if (!hasTest ) {
701710 project .addTestCompileSourceRoot (build .getTestSourceDirectory ());
702711 }
703- for (Resource resource : project .getBuild ().getDelegate ().getResources ()) {
704- project .addSourceRoot (new DefaultSourceRoot (baseDir , ProjectScope .MAIN , resource ));
712+ // Extract modules from sources to detect modular projects
713+ Set <String > modules = extractModules (sources );
714+ boolean isModularProject = !modules .isEmpty ();
715+
716+ logger .debug (
717+ "Module detection for project {}: found {} module(s) {} - modular project: {}" ,
718+ project .getId (),
719+ modules .size (),
720+ modules ,
721+ isModularProject );
722+
723+ /*
724+ * Handle main resources - modular project has highest priority:
725+ * 1. Modular project: use resources from <sources> if present, otherwise inject defaults
726+ * 2. Classic project: use resources from <sources> if present, otherwise use legacy <resources>
727+ */
728+ List <Resource > resources = project .getBuild ().getDelegate ().getResources ();
729+ if (isModularProject ) {
730+ if (hasMainResources ) {
731+ // Modular project with resources configured via <sources> - already added above
732+ if (!resources .isEmpty ()
733+ && !hasOnlySuperPomDefaults (resources , baseDir , ProjectScope .MAIN .id ())) {
734+ logger .warn ("Legacy <resources> element is ignored because main resources are "
735+ + "configured via <source><lang>resources</lang></source> in <sources>" );
736+ }
737+ logger .debug (
738+ "Main resources configured via <sources> element, ignoring legacy <resources> element" );
739+ } else {
740+ // Modular project without resources in <sources> - inject module-aware defaults
741+ if (!resources .isEmpty ()
742+ && !hasOnlySuperPomDefaults (resources , baseDir , ProjectScope .MAIN .id ())) {
743+ String message =
744+ "Legacy <resources> element is ignored because modular sources are configured. "
745+ + "Use <source><lang>resources</lang></source> in <sources> for custom resource paths." ;
746+ logger .warn (message );
747+ result .getProblemCollector ()
748+ .reportProblem (new org .apache .maven .impl .model .DefaultModelProblem (
749+ message ,
750+ Severity .WARNING ,
751+ Version .V41 ,
752+ project .getModel ().getDelegate (),
753+ -1 ,
754+ -1 ,
755+ null ));
756+ }
757+ logger .debug ("Injecting module-aware main resource roots for {} modules" , modules .size ());
758+ for (String module : modules ) {
759+ Path resourcePath = baseDir .resolve ("src" )
760+ .resolve (module )
761+ .resolve (ProjectScope .MAIN .id ())
762+ .resolve ("resources" );
763+ logger .debug (" - Adding main resource root: {} (module: {})" , resourcePath , module );
764+ project .addSourceRoot (
765+ createModularResourceRoot (baseDir , module , ProjectScope .MAIN , outputDirectory ));
766+ }
767+ }
768+ } else {
769+ // Classic (non-modular) project
770+ if (hasMainResources ) {
771+ // Resources configured via <sources> - already added above
772+ if (!resources .isEmpty ()
773+ && !hasOnlySuperPomDefaults (resources , baseDir , ProjectScope .MAIN .id ())) {
774+ logger .warn ("Legacy <resources> element is ignored because main resources are "
775+ + "configured via <source><lang>resources</lang></source> in <sources>" );
776+ }
777+ logger .debug (
778+ "Main resources configured via <sources> element, ignoring legacy <resources> element" );
779+ } else {
780+ // Use legacy <resources> element
781+ logger .debug ("Using explicit or default resources ({} resources configured)" , resources .size ());
782+ for (Resource resource : resources ) {
783+ project .addSourceRoot (new DefaultSourceRoot (baseDir , ProjectScope .MAIN , resource ));
784+ }
785+ }
705786 }
706- for (Resource resource : project .getBuild ().getDelegate ().getTestResources ()) {
707- project .addSourceRoot (new DefaultSourceRoot (baseDir , ProjectScope .TEST , resource ));
787+
788+ /*
789+ * Handle test resources - same priority as main resources:
790+ * 1. Modular project: use test resources from <sources> if present, otherwise inject defaults
791+ * 2. Classic project: use test resources from <sources> if present, otherwise use legacy <testResources>
792+ */
793+ List <Resource > testResources = project .getBuild ().getDelegate ().getTestResources ();
794+ if (isModularProject ) {
795+ if (hasTestResources ) {
796+ // Modular project with test resources configured via <sources> - already added above
797+ if (!testResources .isEmpty ()
798+ && !hasOnlySuperPomDefaults (testResources , baseDir , ProjectScope .TEST .id ())) {
799+ logger .warn (
800+ "Legacy <testResources> element is ignored because test resources are "
801+ + "configured via <source><lang>resources</lang><scope>test</scope></source> in <sources>" );
802+ }
803+ logger .debug (
804+ "Test resources configured via <sources> element, ignoring legacy <testResources> element" );
805+ } else {
806+ // Modular project without test resources in <sources> - inject module-aware defaults
807+ if (!testResources .isEmpty ()
808+ && !hasOnlySuperPomDefaults (testResources , baseDir , ProjectScope .TEST .id ())) {
809+ String message =
810+ "Legacy <testResources> element is ignored because modular sources are configured. "
811+ + "Use <source><lang>resources</lang><scope>test</scope></source> in <sources> for custom resource paths." ;
812+ logger .warn (message );
813+ result .getProblemCollector ()
814+ .reportProblem (new org .apache .maven .impl .model .DefaultModelProblem (
815+ message ,
816+ Severity .WARNING ,
817+ Version .V41 ,
818+ project .getModel ().getDelegate (),
819+ -1 ,
820+ -1 ,
821+ null ));
822+ }
823+ logger .debug ("Injecting module-aware test resource roots for {} modules" , modules .size ());
824+ for (String module : modules ) {
825+ Path resourcePath = baseDir .resolve ("src" )
826+ .resolve (module )
827+ .resolve (ProjectScope .TEST .id ())
828+ .resolve ("resources" );
829+ logger .debug (" - Adding test resource root: {} (module: {})" , resourcePath , module );
830+ project .addSourceRoot (
831+ createModularResourceRoot (baseDir , module , ProjectScope .TEST , outputDirectory ));
832+ }
833+ }
834+ } else {
835+ // Classic (non-modular) project
836+ if (hasTestResources ) {
837+ // Test resources configured via <sources> - already added above
838+ if (!testResources .isEmpty ()
839+ && !hasOnlySuperPomDefaults (testResources , baseDir , ProjectScope .TEST .id ())) {
840+ logger .warn (
841+ "Legacy <testResources> element is ignored because test resources are "
842+ + "configured via <source><lang>resources</lang><scope>test</scope></source> in <sources>" );
843+ }
844+ logger .debug (
845+ "Test resources configured via <sources> element, ignoring legacy <testResources> element" );
846+ } else {
847+ // Use legacy <testResources> element
848+ logger .debug (
849+ "Using explicit or default test resources ({} resources configured)" ,
850+ testResources .size ());
851+ for (Resource resource : testResources ) {
852+ project .addSourceRoot (new DefaultSourceRoot (baseDir , ProjectScope .TEST , resource ));
853+ }
854+ }
708855 }
709856 }
710857
@@ -1099,6 +1246,86 @@ public Set<Entry<K, V>> entrySet() {
10991246 }
11001247 }
11011248
1249+ /**
1250+ * Extracts unique module names from the given list of source elements.
1251+ * A project is considered modular if it has at least one module name.
1252+ *
1253+ * @param sources list of source elements from the build
1254+ * @return set of non-blank module names
1255+ */
1256+ private Set <String > extractModules (List <org .apache .maven .api .model .Source > sources ) {
1257+ return sources .stream ()
1258+ .map (org .apache .maven .api .model .Source ::getModule )
1259+ .filter (Objects ::nonNull )
1260+ .map (String ::trim )
1261+ .filter (s -> !s .isBlank ())
1262+ .collect (Collectors .toSet ());
1263+ }
1264+
1265+ /**
1266+ * Creates a DefaultSourceRoot for module-aware resource directories.
1267+ * Generates paths following the pattern: src/<module>/<scope>/resources
1268+ *
1269+ * @param baseDir base directory of the project
1270+ * @param module module name
1271+ * @param scope project scope (main or test)
1272+ * @param outputDirectory function providing output directory for the scope
1273+ * @return configured DefaultSourceRoot for the module's resources
1274+ */
1275+ private DefaultSourceRoot createModularResourceRoot (
1276+ Path baseDir , String module , ProjectScope scope , Function <ProjectScope , String > outputDirectory ) {
1277+ Path resourceDir =
1278+ baseDir .resolve ("src" ).resolve (module ).resolve (scope .id ()).resolve ("resources" );
1279+
1280+ return new DefaultSourceRoot (
1281+ scope ,
1282+ Language .RESOURCES ,
1283+ module ,
1284+ null , // targetVersion
1285+ resourceDir ,
1286+ null , // includes
1287+ null , // excludes
1288+ false , // stringFiltering
1289+ Path .of (module ), // targetPath - resources go to target/classes/<module>
1290+ true // enabled
1291+ );
1292+ }
1293+
1294+ /**
1295+ * Checks if the given resource list contains only Super POM default resources.
1296+ * Super POM defaults are: src/{scope}/resources and src/{scope}/resources-filtered
1297+ *
1298+ * @param resources list of resources to check
1299+ * @param baseDir project base directory
1300+ * @param scope scope (main or test)
1301+ * @return true if only Super POM defaults are present
1302+ */
1303+ private boolean hasOnlySuperPomDefaults (List <Resource > resources , Path baseDir , String scope ) {
1304+ if (resources .isEmpty ()) {
1305+ return false ;
1306+ }
1307+
1308+ // Super POM default paths
1309+ String defaultPath =
1310+ baseDir .resolve ("src" ).resolve (scope ).resolve ("resources" ).toString ();
1311+ String defaultFilteredPath = baseDir .resolve ("src" )
1312+ .resolve (scope )
1313+ .resolve ("resources-filtered" )
1314+ .toString ();
1315+
1316+ // Check if all resources are Super POM defaults
1317+ for (Resource resource : resources ) {
1318+ String resourceDir = resource .getDirectory ();
1319+ if (resourceDir != null && !resourceDir .equals (defaultPath ) && !resourceDir .equals (defaultFilteredPath )) {
1320+ // Found a non-default resource
1321+ return false ;
1322+ }
1323+ }
1324+
1325+ logger .debug ("Detected only Super POM default resources for scope: {}" , scope );
1326+ return true ;
1327+ }
1328+
11021329 private Model injectLifecycleBindings (
11031330 Model model ,
11041331 ModelBuilderRequest request ,
0 commit comments