1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  package org.apache.maven.impl.model;
20  
21  import java.io.File;
22  import java.util.Arrays;
23  import java.util.Deque;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Objects;
31  import java.util.Optional;
32  import java.util.Set;
33  import java.util.concurrent.ConcurrentHashMap;
34  import java.util.function.Function;
35  import java.util.function.Supplier;
36  import java.util.function.UnaryOperator;
37  import java.util.regex.Matcher;
38  import java.util.regex.Pattern;
39  import java.util.stream.Collectors;
40  import java.util.stream.StreamSupport;
41  
42  import org.apache.maven.api.Session;
43  import org.apache.maven.api.annotations.Nullable;
44  import org.apache.maven.api.di.Inject;
45  import org.apache.maven.api.di.Named;
46  import org.apache.maven.api.di.Singleton;
47  import org.apache.maven.api.model.Activation;
48  import org.apache.maven.api.model.ActivationFile;
49  import org.apache.maven.api.model.ActivationOS;
50  import org.apache.maven.api.model.ActivationProperty;
51  import org.apache.maven.api.model.Build;
52  import org.apache.maven.api.model.BuildBase;
53  import org.apache.maven.api.model.Dependency;
54  import org.apache.maven.api.model.DependencyManagement;
55  import org.apache.maven.api.model.DistributionManagement;
56  import org.apache.maven.api.model.Exclusion;
57  import org.apache.maven.api.model.InputLocation;
58  import org.apache.maven.api.model.InputLocationTracker;
59  import org.apache.maven.api.model.Model;
60  import org.apache.maven.api.model.Parent;
61  import org.apache.maven.api.model.Plugin;
62  import org.apache.maven.api.model.PluginExecution;
63  import org.apache.maven.api.model.PluginManagement;
64  import org.apache.maven.api.model.Profile;
65  import org.apache.maven.api.model.ReportPlugin;
66  import org.apache.maven.api.model.Reporting;
67  import org.apache.maven.api.model.Repository;
68  import org.apache.maven.api.model.Resource;
69  import org.apache.maven.api.services.BuilderProblem.Severity;
70  import org.apache.maven.api.services.ModelBuilder;
71  import org.apache.maven.api.services.ModelProblem;
72  import org.apache.maven.api.services.ModelProblem.Version;
73  import org.apache.maven.api.services.ModelProblemCollector;
74  import org.apache.maven.api.services.model.ModelValidator;
75  import org.apache.maven.api.xml.XmlNode;
76  import org.apache.maven.api.xml.XmlService;
77  import org.apache.maven.impl.InternalSession;
78  import org.apache.maven.model.v4.MavenModelVersion;
79  import org.apache.maven.model.v4.MavenTransformer;
80  import org.eclipse.aether.scope.DependencyScope;
81  import org.eclipse.aether.scope.ScopeManager;
82  
83  import static java.util.Objects.requireNonNull;
84  
85  
86  
87  @Named
88  @Singleton
89  public class DefaultModelValidator implements ModelValidator {
90      public static final String BUILD_ALLOW_EXPRESSION_IN_EFFECTIVE_PROJECT_VERSION =
91              "maven.build.allowExpressionInEffectiveProjectVersion";
92  
93      private static final Pattern EXPRESSION_NAME_PATTERN = Pattern.compile("\\$\\{(.+?)}");
94      private static final Pattern EXPRESSION_PROJECT_NAME_PATTERN = Pattern.compile("\\$\\{(project.+?)}");
95  
96      private static final String ILLEGAL_FS_CHARS = "\\/:\"<>|?*";
97  
98      private static final String ILLEGAL_VERSION_CHARS = ILLEGAL_FS_CHARS;
99  
100     private static final String ILLEGAL_REPO_ID_CHARS = ILLEGAL_FS_CHARS;
101 
102     private static final String EMPTY = "";
103 
104     private record ActivationFrame(String location, Optional<? extends InputLocationTracker> parent) {}
105 
106     private static class ActivationWalker extends MavenTransformer {
107 
108         private final Deque<ActivationFrame> stk;
109 
110         ActivationWalker(Deque<ActivationFrame> stk, UnaryOperator<String> transformer) {
111             super(transformer);
112             this.stk = stk;
113         }
114 
115         private ActivationFrame nextFrame(String property) {
116             return new ActivationFrame(property, Optional.empty());
117         }
118 
119         private <P> ActivationFrame nextFrame(String property, Function<P, InputLocationTracker> child) {
120             @SuppressWarnings("unchecked")
121             final Optional<P> parent = (Optional<P>) stk.peek().parent;
122             return new ActivationFrame(property, parent.map(child));
123         }
124 
125         @Override
126         public Activation transformActivation(Activation target) {
127             stk.push(new ActivationFrame("activation", Optional.of(target)));
128             try {
129                 return super.transformActivation(target);
130             } finally {
131                 stk.pop();
132             }
133         }
134 
135         @Override
136         protected Activation.Builder transformActivation_File(
137                 Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
138             stk.push(nextFrame("file", Activation::getFile));
139             try {
140                 return super.transformActivation_File(creator, builder, target);
141             } finally {
142                 stk.pop();
143             }
144         }
145 
146         @Override
147         protected ActivationFile.Builder transformActivationFile_Exists(
148                 Supplier<? extends ActivationFile.Builder> creator,
149                 ActivationFile.Builder builder,
150                 ActivationFile target) {
151             stk.push(nextFrame("exists"));
152             try {
153                 return super.transformActivationFile_Exists(creator, builder, target);
154             } finally {
155                 stk.pop();
156             }
157         }
158 
159         @Override
160         protected ActivationFile.Builder transformActivationFile_Missing(
161                 Supplier<? extends ActivationFile.Builder> creator,
162                 ActivationFile.Builder builder,
163                 ActivationFile target) {
164             stk.push(nextFrame("missing"));
165             try {
166                 return super.transformActivationFile_Missing(creator, builder, target);
167             } finally {
168                 stk.pop();
169             }
170         }
171 
172         @Override
173         protected Activation.Builder transformActivation_Jdk(
174                 Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
175             stk.push(nextFrame("jdk"));
176             try {
177                 return super.transformActivation_Jdk(creator, builder, target);
178             } finally {
179                 stk.pop();
180             }
181         }
182 
183         @Override
184         protected Activation.Builder transformActivation_Os(
185                 Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
186             stk.push(nextFrame("os", Activation::getOs));
187             try {
188                 return super.transformActivation_Os(creator, builder, target);
189             } finally {
190                 stk.pop();
191             }
192         }
193 
194         @Override
195         protected ActivationOS.Builder transformActivationOS_Arch(
196                 Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
197             stk.push(nextFrame("arch"));
198             try {
199                 return super.transformActivationOS_Arch(creator, builder, target);
200             } finally {
201                 stk.pop();
202             }
203         }
204 
205         @Override
206         protected ActivationOS.Builder transformActivationOS_Family(
207                 Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
208             stk.push(nextFrame("family"));
209             try {
210                 return super.transformActivationOS_Family(creator, builder, target);
211             } finally {
212                 stk.pop();
213             }
214         }
215 
216         @Override
217         protected ActivationOS.Builder transformActivationOS_Name(
218                 Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
219             stk.push(nextFrame("name"));
220             try {
221                 return super.transformActivationOS_Name(creator, builder, target);
222             } finally {
223                 stk.pop();
224             }
225         }
226 
227         @Override
228         protected ActivationOS.Builder transformActivationOS_Version(
229                 Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
230             stk.push(nextFrame("version"));
231             try {
232                 return super.transformActivationOS_Version(creator, builder, target);
233             } finally {
234                 stk.pop();
235             }
236         }
237 
238         @Override
239         protected Activation.Builder transformActivation_Packaging(
240                 Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
241             stk.push(nextFrame("packaging"));
242             try {
243                 return super.transformActivation_Packaging(creator, builder, target);
244             } finally {
245                 stk.pop();
246             }
247         }
248 
249         @Override
250         protected Activation.Builder transformActivation_Property(
251                 Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
252             stk.push(nextFrame("property", Activation::getProperty));
253             try {
254                 return super.transformActivation_Property(creator, builder, target);
255             } finally {
256                 stk.pop();
257             }
258         }
259 
260         @Override
261         protected ActivationProperty.Builder transformActivationProperty_Name(
262                 Supplier<? extends ActivationProperty.Builder> creator,
263                 ActivationProperty.Builder builder,
264                 ActivationProperty target) {
265             stk.push(nextFrame("name"));
266             try {
267                 return super.transformActivationProperty_Name(creator, builder, target);
268             } finally {
269                 stk.pop();
270             }
271         }
272 
273         @Override
274         protected ActivationProperty.Builder transformActivationProperty_Value(
275                 Supplier<? extends ActivationProperty.Builder> creator,
276                 ActivationProperty.Builder builder,
277                 ActivationProperty target) {
278             stk.push(nextFrame("value"));
279             try {
280                 return super.transformActivationProperty_Value(creator, builder, target);
281             } finally {
282                 stk.pop();
283             }
284         }
285 
286         @Override
287         protected Activation.Builder transformActivation_Condition(
288                 Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
289             stk.push(nextFrame("condition"));
290             try {
291                 return super.transformActivation_Condition(creator, builder, target);
292             } finally {
293                 stk.pop();
294             }
295         }
296     }
297 
298     private final Set<String> validCoordinatesIds = ConcurrentHashMap.newKeySet();
299 
300     private final Set<String> validProfileIds = ConcurrentHashMap.newKeySet();
301 
302     @Inject
303     public DefaultModelValidator() {}
304 
305     @Override
306     @SuppressWarnings("checkstyle:MethodLength")
307     public void validateFileModel(Session s, Model m, int validationLevel, ModelProblemCollector problems) {
308 
309         Parent parent = m.getParent();
310         if (parent != null) {
311             validateStringNotEmpty(
312                     "parent.groupId", problems, Severity.FATAL, Version.BASE, parent.getGroupId(), parent);
313 
314             validateStringNotEmpty(
315                     "parent.artifactId", problems, Severity.FATAL, Version.BASE, parent.getArtifactId(), parent);
316 
317             if (equals(parent.getGroupId(), m.getGroupId()) && equals(parent.getArtifactId(), m.getArtifactId())) {
318                 addViolation(
319                         problems,
320                         Severity.FATAL,
321                         Version.BASE,
322                         "parent.artifactId",
323                         null,
324                         "must be changed"
325                                 + ", the parent element cannot have the same groupId:artifactId as the project.",
326                         parent);
327             }
328 
329             if (equals("LATEST", parent.getVersion()) || equals("RELEASE", parent.getVersion())) {
330                 addViolation(
331                         problems,
332                         Severity.WARNING,
333                         Version.BASE,
334                         "parent.version",
335                         null,
336                         "is either LATEST or RELEASE (both of them are being deprecated)",
337                         parent);
338             }
339 
340             if (parent.getRelativePath() != null
341                     && !parent.getRelativePath().isEmpty()
342                     && (parent.getGroupId() != null && !parent.getGroupId().isEmpty()
343                             || parent.getArtifactId() != null
344                                     && !parent.getArtifactId().isEmpty())
345                     && validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_4_0
346                     && ModelBuilder.KNOWN_MODEL_VERSIONS.contains(m.getModelVersion())
347                     && !Objects.equals(m.getModelVersion(), ModelBuilder.MODEL_VERSION_4_0_0)) {
348                 addViolation(
349                         problems,
350                         Severity.WARNING,
351                         Version.BASE,
352                         "parent.relativePath",
353                         null,
354                         "only specify relativePath or groupId/artifactId in modelVersion 4.1.0",
355                         parent);
356             }
357         }
358 
359         if (validationLevel == ModelValidator.VALIDATION_LEVEL_MINIMAL) {
360             
361             HashSet<String> minProfileIds = new HashSet<>();
362             for (Profile profile : m.getProfiles()) {
363                 if (!minProfileIds.add(profile.getId())) {
364                     addViolation(
365                             problems,
366                             Severity.WARNING,
367                             Version.BASE,
368                             "profiles.profile.id",
369                             null,
370                             "Duplicate activation for profile " + profile.getId(),
371                             profile);
372                 }
373             }
374         } else if (validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_2_0) {
375             validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.V20, m.getModelVersion(), m);
376 
377             validateModelVersion(problems, m.getModelVersion(), m, ModelBuilder.KNOWN_MODEL_VERSIONS);
378 
379             Set<String> modules = new HashSet<>();
380             for (int i = 0, n = m.getModules().size(); i < n; i++) {
381                 String module = m.getModules().get(i);
382                 if (!modules.add(module)) {
383                     addViolation(
384                             problems,
385                             Severity.ERROR,
386                             Version.V20,
387                             "modules.module[" + i + "]",
388                             null,
389                             "specifies duplicate child module " + module,
390                             m.getLocation("modules"));
391                 }
392             }
393             String modelVersion = m.getModelVersion();
394             if (Objects.equals(modelVersion, ModelBuilder.MODEL_VERSION_4_0_0)) {
395                 if (!m.getSubprojects().isEmpty()) {
396                     addViolation(
397                             problems,
398                             Severity.ERROR,
399                             Version.V40,
400                             "subprojects",
401                             null,
402                             "unexpected subprojects element",
403                             m.getLocation("subprojects"));
404                 }
405             } else {
406                 Set<String> subprojects = new HashSet<>();
407                 for (int i = 0, n = m.getSubprojects().size(); i < n; i++) {
408                     String subproject = m.getSubprojects().get(i);
409                     if (!subprojects.add(subproject)) {
410                         addViolation(
411                                 problems,
412                                 Severity.ERROR,
413                                 Version.V41,
414                                 "subprojects.subproject[" + i + "]",
415                                 null,
416                                 "specifies duplicate subproject " + subproject,
417                                 m.getLocation("subprojects"));
418                     }
419                 }
420                 if (!modules.isEmpty()) {
421                     if (subprojects.isEmpty()) {
422                         addViolation(
423                                 problems,
424                                 Severity.WARNING,
425                                 Version.V41,
426                                 "modules",
427                                 null,
428                                 "deprecated modules element, use subprojects instead",
429                                 m.getLocation("modules"));
430                     } else {
431                         addViolation(
432                                 problems,
433                                 Severity.ERROR,
434                                 Version.V41,
435                                 "modules",
436                                 null,
437                                 "cannot use both modules and subprojects element",
438                                 m.getLocation("modules"));
439                     }
440                 }
441             }
442 
443             Severity errOn30 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_0);
444 
445             boolean isModelVersion41OrMore = !Objects.equals(ModelBuilder.MODEL_VERSION_4_0_0, m.getModelVersion());
446             if (isModelVersion41OrMore) {
447                 validateStringNoExpression("groupId", problems, Severity.FATAL, Version.V41, m.getGroupId(), m);
448 
449                 validateStringNotEmpty("artifactId", problems, Severity.FATAL, Version.V20, m.getArtifactId(), m);
450                 validateStringNoExpression("artifactId", problems, Severity.FATAL, Version.V20, m.getArtifactId(), m);
451 
452                 validateVersionNoExpression("version", problems, Severity.FATAL, Version.V41, m.getVersion(), m);
453 
454                 if (parent != null) {
455                     validateStringNoExpression(
456                             "groupId", problems, Severity.FATAL, Version.V41, parent.getGroupId(), m);
457                     validateStringNoExpression(
458                             "artifactId", problems, Severity.FATAL, Version.V41, parent.getArtifactId(), m);
459                     validateVersionNoExpression(
460                             "version", problems, Severity.FATAL, Version.V41, parent.getVersion(), m);
461                 }
462             } else {
463                 validateStringNoExpression("groupId", problems, Severity.WARNING, Version.V20, m.getGroupId(), m);
464                 if (parent == null) {
465                     validateStringNotEmpty("groupId", problems, Severity.FATAL, Version.V20, m.getGroupId(), m);
466                 }
467 
468                 validateStringNoExpression("artifactId", problems, Severity.WARNING, Version.V20, m.getArtifactId(), m);
469                 validateStringNotEmpty("artifactId", problems, Severity.FATAL, Version.V20, m.getArtifactId(), m);
470 
471                 validateVersionNoExpression("version", problems, Severity.WARNING, Version.V20, m.getVersion(), m);
472                 if (parent == null) {
473                     validateStringNotEmpty("version", problems, Severity.FATAL, Version.V20, m.getVersion(), m);
474                 }
475             }
476 
477             validateStringNoExpression("packaging", problems, Severity.WARNING, Version.V20, m.getPackaging(), m);
478 
479             validate20RawDependencies(
480                     problems,
481                     m.getDependencies(),
482                     "dependencies.dependency.",
483                     EMPTY,
484                     isModelVersion41OrMore,
485                     validationLevel);
486 
487             validate20RawDependenciesSelfReferencing(problems, m, m.getDependencies(), "dependencies.dependency");
488 
489             if (m.getDependencyManagement() != null) {
490                 validate20RawDependencies(
491                         problems,
492                         m.getDependencyManagement().getDependencies(),
493                         "dependencyManagement.dependencies.dependency.",
494                         EMPTY,
495                         isModelVersion41OrMore,
496                         validationLevel);
497             }
498 
499             validateRawRepositories(problems, m.getRepositories(), "repositories.repository.", EMPTY, validationLevel);
500 
501             validateRawRepositories(
502                     problems,
503                     m.getPluginRepositories(),
504                     "pluginRepositories.pluginRepository.",
505                     EMPTY,
506                     validationLevel);
507 
508             Build build = m.getBuild();
509             if (build != null) {
510                 validate20RawPlugins(problems, build.getPlugins(), "build.plugins.plugin.", EMPTY, validationLevel);
511 
512                 PluginManagement mgmt = build.getPluginManagement();
513                 if (mgmt != null) {
514                     validate20RawPlugins(
515                             problems,
516                             mgmt.getPlugins(),
517                             "build.pluginManagement.plugins.plugin.",
518                             EMPTY,
519                             validationLevel);
520                 }
521             }
522 
523             Set<String> profileIds = new HashSet<>();
524 
525             for (Profile profile : m.getProfiles()) {
526                 String prefix = "profiles.profile[" + profile.getId() + "].";
527 
528                 validateProfileId(prefix, "id", problems, Severity.ERROR, Version.V40, profile.getId(), null, m);
529 
530                 if (!profileIds.add(profile.getId())) {
531                     addViolation(
532                             problems,
533                             errOn30,
534                             Version.V20,
535                             "profiles.profile.id",
536                             null,
537                             "must be unique but found duplicate profile with id " + profile.getId(),
538                             profile);
539                 }
540 
541                 validate30RawProfileActivation(problems, profile.getActivation(), prefix);
542 
543                 validate20RawDependencies(
544                         problems,
545                         profile.getDependencies(),
546                         prefix,
547                         "dependencies.dependency.",
548                         isModelVersion41OrMore,
549                         validationLevel);
550 
551                 if (profile.getDependencyManagement() != null) {
552                     validate20RawDependencies(
553                             problems,
554                             profile.getDependencyManagement().getDependencies(),
555                             prefix,
556                             "dependencyManagement.dependencies.dependency.",
557                             isModelVersion41OrMore,
558                             validationLevel);
559                 }
560 
561                 validateRawRepositories(
562                         problems, profile.getRepositories(), prefix, "repositories.repository.", validationLevel);
563 
564                 validateRawRepositories(
565                         problems,
566                         profile.getPluginRepositories(),
567                         prefix,
568                         "pluginRepositories.pluginRepository.",
569                         validationLevel);
570 
571                 BuildBase buildBase = profile.getBuild();
572                 if (buildBase != null) {
573                     validate20RawPlugins(problems, buildBase.getPlugins(), prefix, "plugins.plugin.", validationLevel);
574 
575                     PluginManagement mgmt = buildBase.getPluginManagement();
576                     if (mgmt != null) {
577                         validate20RawPlugins(
578                                 problems,
579                                 mgmt.getPlugins(),
580                                 prefix,
581                                 "pluginManagement.plugins.plugin.",
582                                 validationLevel);
583                     }
584                 }
585             }
586         }
587     }
588 
589     @Override
590     public void validateRawModel(Session s, Model m, int validationLevel, ModelProblemCollector problems) {
591         
592         
593         String minVersion = new MavenModelVersion().getModelVersion(m);
594         if (m.getModelVersion() != null && compareModelVersions(minVersion, m.getModelVersion()) > 0) {
595             addViolation(
596                     problems,
597                     Severity.FATAL,
598                     Version.V40,
599                     "model",
600                     null,
601                     "the model contains elements that require a model version of " + minVersion,
602                     m);
603         }
604 
605         Parent parent = m.getParent();
606 
607         if (parent != null) {
608             validateStringNotEmpty(
609                     "parent.groupId", problems, Severity.FATAL, Version.BASE, parent.getGroupId(), parent);
610 
611             validateStringNotEmpty(
612                     "parent.artifactId", problems, Severity.FATAL, Version.BASE, parent.getArtifactId(), parent);
613 
614             validateStringNotEmpty(
615                     "parent.version", problems, Severity.FATAL, Version.BASE, parent.getVersion(), parent);
616 
617             if (equals(parent.getGroupId(), m.getGroupId()) && equals(parent.getArtifactId(), m.getArtifactId())) {
618                 addViolation(
619                         problems,
620                         Severity.FATAL,
621                         Version.BASE,
622                         "parent.artifactId",
623                         null,
624                         "must be changed"
625                                 + ", the parent element cannot have the same groupId:artifactId as the project.",
626                         parent);
627             }
628 
629             if (equals("LATEST", parent.getVersion()) || equals("RELEASE", parent.getVersion())) {
630                 addViolation(
631                         problems,
632                         Severity.WARNING,
633                         Version.BASE,
634                         "parent.version",
635                         null,
636                         "is either LATEST or RELEASE (both of them are being deprecated)",
637                         parent);
638             }
639         }
640     }
641 
642     private void validate30RawProfileActivation(ModelProblemCollector problems, Activation activation, String prefix) {
643         if (activation == null) {
644             return;
645         }
646 
647         final Deque<ActivationFrame> stk = new LinkedList<>();
648 
649         final Supplier<String> pathSupplier = () -> {
650             final boolean parallel = false;
651             return StreamSupport.stream(((Iterable<ActivationFrame>) stk::descendingIterator).spliterator(), parallel)
652                     .map(ActivationFrame::location)
653                     .collect(Collectors.joining("."));
654         };
655         final Supplier<InputLocation> locationSupplier = () -> {
656             if (stk.size() < 2) {
657                 return null;
658             }
659             Iterator<ActivationFrame> f = stk.iterator();
660 
661             String location = f.next().location;
662             ActivationFrame parent = f.next();
663 
664             return parent.parent.map(p -> p.getLocation(location)).orElse(null);
665         };
666         final UnaryOperator<String> transformer = s -> {
667             if (hasProjectExpression(s)) {
668                 String path = pathSupplier.get();
669                 Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(s);
670                 while (matcher.find()) {
671                     String propertyName = matcher.group(0);
672 
673                     if (path.startsWith("activation.file.") && "${project.basedir}".equals(propertyName)) {
674                         continue;
675                     }
676                     addViolation(
677                             problems,
678                             Severity.WARNING,
679                             Version.V30,
680                             prefix + path,
681                             null,
682                             "Failed to interpolate profile activation property " + s + ": " + propertyName
683                                     + " expressions are not supported during profile activation.",
684                             locationSupplier.get());
685                 }
686             }
687             return s;
688         };
689         new ActivationWalker(stk, transformer).transformActivation(activation);
690     }
691 
692     private void validate20RawPlugins(
693             ModelProblemCollector problems, List<Plugin> plugins, String prefix, String prefix2, int validationLevel) {
694         Severity errOn31 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_1);
695 
696         Map<String, Plugin> index = new HashMap<>();
697 
698         for (Plugin plugin : plugins) {
699             if (plugin.getGroupId() == null
700                     || (plugin.getGroupId() != null
701                             && plugin.getGroupId().trim().isEmpty())) {
702                 addViolation(
703                         problems,
704                         Severity.FATAL,
705                         Version.V20,
706                         prefix + prefix2 + "(groupId:artifactId)",
707                         null,
708                         "groupId of a plugin must be defined. ",
709                         plugin);
710             }
711 
712             if (plugin.getArtifactId() == null
713                     || (plugin.getArtifactId() != null
714                             && plugin.getArtifactId().trim().isEmpty())) {
715                 addViolation(
716                         problems,
717                         Severity.FATAL,
718                         Version.V20,
719                         prefix + prefix2 + "(groupId:artifactId)",
720                         null,
721                         "artifactId of a plugin must be defined. ",
722                         plugin);
723             }
724 
725             
726             if (plugin.getVersion() != null && plugin.getVersion().trim().isEmpty()) {
727                 addViolation(
728                         problems,
729                         Severity.FATAL,
730                         Version.V20,
731                         prefix + prefix2 + "(groupId:artifactId)",
732                         null,
733                         "version of a plugin must be defined. ",
734                         plugin);
735             }
736 
737             String key = plugin.getKey();
738 
739             Plugin existing = index.get(key);
740 
741             if (existing != null) {
742                 addViolation(
743                         problems,
744                         errOn31,
745                         Version.V20,
746                         prefix + prefix2 + "(groupId:artifactId)",
747                         null,
748                         "must be unique but found duplicate declaration of plugin " + key,
749                         plugin);
750             } else {
751                 index.put(key, plugin);
752             }
753 
754             Set<String> executionIds = new HashSet<>();
755 
756             if (validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_4_0 && plugin.getConfiguration() != null) {
757                 validateXmlNodeRecursively(
758                         problems,
759                         prefix + prefix2 + "[" + plugin.getKey() + "].configuration",
760                         plugin,
761                         plugin.getConfiguration());
762             }
763 
764             for (PluginExecution exec : plugin.getExecutions()) {
765                 if (!executionIds.add(exec.getId())) {
766                     addViolation(
767                             problems,
768                             Severity.ERROR,
769                             Version.V20,
770                             prefix + prefix2 + "[" + plugin.getKey() + "].executions.execution.id",
771                             null,
772                             "must be unique but found duplicate execution with id " + exec.getId(),
773                             exec);
774                 }
775                 if (validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_4_0 && exec.getConfiguration() != null) {
776                     validateXmlNodeRecursively(
777                             problems,
778                             prefix + prefix2 + "[" + plugin.getKey() + "].executions.execution." + exec.getId(),
779                             exec,
780                             exec.getConfiguration());
781                 }
782             }
783         }
784     }
785 
786     private void validateXmlNodeRecursively(
787             ModelProblemCollector problems, String fieldPathPrefix, InputLocationTracker tracker, XmlNode xmlNode) {
788         validateXmlNode(problems, fieldPathPrefix, tracker, xmlNode);
789         for (XmlNode child : xmlNode.children()) {
790             validateXmlNodeRecursively(problems, fieldPathPrefix + "." + xmlNode.name(), tracker, child);
791         }
792     }
793 
794     private void validateXmlNode(
795             ModelProblemCollector problems, String fieldPathPrefix, InputLocationTracker tracker, XmlNode xmlNode) {
796         String childrenCombinationModeAttribute = xmlNode.attributes()
797                 .getOrDefault(
798                         XmlService.CHILDREN_COMBINATION_MODE_ATTRIBUTE, XmlService.DEFAULT_CHILDREN_COMBINATION_MODE);
799         if (!(XmlService.CHILDREN_COMBINATION_APPEND.equals(childrenCombinationModeAttribute)
800                 || XmlService.CHILDREN_COMBINATION_MERGE.equals(childrenCombinationModeAttribute))) {
801             addViolation(
802                     problems,
803                     Severity.ERROR,
804                     Version.V40,
805                     fieldPathPrefix + "." + xmlNode.name(),
806                     SourceHint.xmlNodeInputLocation(xmlNode),
807                     "Unsupported value '" + childrenCombinationModeAttribute + "' for "
808                             + XmlService.CHILDREN_COMBINATION_MODE_ATTRIBUTE + " attribute. " + "Valid values are: "
809                             + XmlService.CHILDREN_COMBINATION_APPEND + ", and " + XmlService.CHILDREN_COMBINATION_MERGE
810                             + " (default is: " + XmlService.DEFAULT_SELF_COMBINATION_MODE + ")",
811                     tracker);
812         }
813         String selfCombinationModeAttribute = xmlNode.attributes()
814                 .getOrDefault(XmlService.SELF_COMBINATION_MODE_ATTRIBUTE, XmlService.DEFAULT_SELF_COMBINATION_MODE);
815         if (!(XmlService.SELF_COMBINATION_OVERRIDE.equals(selfCombinationModeAttribute)
816                 || XmlService.SELF_COMBINATION_MERGE.equals(selfCombinationModeAttribute)
817                 || XmlService.SELF_COMBINATION_REMOVE.equals(selfCombinationModeAttribute))) {
818             addViolation(
819                     problems,
820                     Severity.ERROR,
821                     Version.V40,
822                     fieldPathPrefix + "." + xmlNode.name(),
823                     SourceHint.xmlNodeInputLocation(xmlNode),
824                     "Unsupported value '" + selfCombinationModeAttribute + "' for "
825                             + XmlService.SELF_COMBINATION_MODE_ATTRIBUTE + " attribute. " + "Valid values are: "
826                             + XmlService.SELF_COMBINATION_OVERRIDE + ", " + XmlService.SELF_COMBINATION_MERGE + ", and "
827                             + XmlService.SELF_COMBINATION_REMOVE
828                             + " (default is: " + XmlService.DEFAULT_SELF_COMBINATION_MODE + ")",
829                     tracker);
830         }
831     }
832 
833     @Override
834     @SuppressWarnings("checkstyle:MethodLength")
835     public void validateEffectiveModel(Session s, Model m, int validationLevel, ModelProblemCollector problems) {
836         validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.BASE, m.getModelVersion(), m);
837 
838         validateCoordinatesId("groupId", problems, m.getGroupId(), m);
839 
840         validateCoordinatesId("artifactId", problems, m.getArtifactId(), m);
841 
842         validateStringNotEmpty("packaging", problems, Severity.ERROR, Version.BASE, m.getPackaging(), m);
843 
844         if (!m.getModules().isEmpty()) {
845             if (!"pom".equals(m.getPackaging())) {
846                 addViolation(
847                         problems,
848                         Severity.ERROR,
849                         Version.BASE,
850                         "packaging",
851                         null,
852                         "with value '" + m.getPackaging() + "' is invalid. Aggregator projects "
853                                 + "require 'pom' as packaging.",
854                         m);
855             }
856 
857             for (int i = 0, n = m.getModules().size(); i < n; i++) {
858                 String module = m.getModules().get(i);
859 
860                 boolean isBlankModule = true;
861                 if (module != null) {
862                     for (int j = 0; j < module.length(); j++) {
863                         if (!Character.isWhitespace(module.charAt(j))) {
864                             isBlankModule = false;
865                         }
866                     }
867                 }
868 
869                 if (isBlankModule) {
870                     addViolation(
871                             problems,
872                             Severity.ERROR,
873                             Version.BASE,
874                             "modules.module[" + i + "]",
875                             null,
876                             "has been specified without a path to the project directory.",
877                             m.getLocation("modules"));
878                 }
879             }
880         }
881 
882         validateStringNotEmpty("version", problems, Severity.ERROR, Version.BASE, m.getVersion(), m);
883 
884         Severity errOn30 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_0);
885 
886         validateEffectiveDependencies(s, problems, m, m.getDependencies(), false, validationLevel);
887 
888         DependencyManagement mgmt = m.getDependencyManagement();
889         if (mgmt != null) {
890             validateEffectiveDependencies(s, problems, m, mgmt.getDependencies(), true, validationLevel);
891         }
892 
893         if (validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_2_0) {
894             Severity errOn31 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_1);
895 
896             validateBannedCharacters(
897                     EMPTY, "version", problems, errOn31, Version.V20, m.getVersion(), null, m, ILLEGAL_VERSION_CHARS);
898             validate20ProperSnapshotVersion("version", problems, errOn31, Version.V20, m.getVersion(), null, m);
899             if (hasExpression(m.getVersion())) {
900                 Severity versionExpressionSeverity = Severity.ERROR;
901                 if (m.getProperties() != null
902                         && Boolean.parseBoolean(
903                                 m.getProperties().get(BUILD_ALLOW_EXPRESSION_IN_EFFECTIVE_PROJECT_VERSION))) {
904                     versionExpressionSeverity = Severity.WARNING;
905                 }
906                 addViolation(
907                         problems,
908                         versionExpressionSeverity,
909                         Version.V20,
910                         "version",
911                         null,
912                         "must be a constant version but is '" + m.getVersion() + "'.",
913                         m);
914             }
915 
916             Build build = m.getBuild();
917             if (build != null) {
918                 for (Plugin p : build.getPlugins()) {
919                     validateStringNotEmpty(
920                             "build.plugins.plugin.artifactId",
921                             problems,
922                             Severity.ERROR,
923                             Version.V20,
924                             p.getArtifactId(),
925                             p);
926 
927                     validateStringNotEmpty(
928                             "build.plugins.plugin.groupId", problems, Severity.ERROR, Version.V20, p.getGroupId(), p);
929 
930                     validate20PluginVersion(
931                             "build.plugins.plugin.version",
932                             problems,
933                             p.getVersion(),
934                             SourceHint.pluginKey(p),
935                             p,
936                             validationLevel);
937 
938                     validateBoolean(
939                             "build.plugins.plugin.inherited",
940                             EMPTY,
941                             problems,
942                             errOn30,
943                             Version.V20,
944                             p.getInherited(),
945                             SourceHint.pluginKey(p),
946                             p);
947 
948                     validateBoolean(
949                             "build.plugins.plugin.extensions",
950                             EMPTY,
951                             problems,
952                             errOn30,
953                             Version.V20,
954                             p.getExtensions(),
955                             SourceHint.pluginKey(p),
956                             p);
957 
958                     validate20EffectivePluginDependencies(problems, p, validationLevel);
959                 }
960 
961                 validate20RawResources(problems, build.getResources(), "build.resources.resource.", validationLevel);
962 
963                 validate20RawResources(
964                         problems, build.getTestResources(), "build.testResources.testResource.", validationLevel);
965             }
966 
967             Reporting reporting = m.getReporting();
968             if (reporting != null) {
969                 for (ReportPlugin p : reporting.getPlugins()) {
970                     validateStringNotEmpty(
971                             "reporting.plugins.plugin.artifactId",
972                             problems,
973                             Severity.ERROR,
974                             Version.V20,
975                             p.getArtifactId(),
976                             p);
977 
978                     validateStringNotEmpty(
979                             "reporting.plugins.plugin.groupId",
980                             problems,
981                             Severity.ERROR,
982                             Version.V20,
983                             p.getGroupId(),
984                             p);
985                 }
986             }
987 
988             for (Repository repository : m.getRepositories()) {
989                 validate20EffectiveRepository(problems, repository, "repositories.repository.", validationLevel);
990             }
991 
992             for (Repository repository : m.getPluginRepositories()) {
993                 validate20EffectiveRepository(
994                         problems, repository, "pluginRepositories.pluginRepository.", validationLevel);
995             }
996 
997             DistributionManagement distMgmt = m.getDistributionManagement();
998             if (distMgmt != null) {
999                 if (distMgmt.getStatus() != null) {
1000                     addViolation(
1001                             problems,
1002                             Severity.ERROR,
1003                             Version.V20,
1004                             "distributionManagement.status",
1005                             null,
1006                             "must not be specified.",
1007                             distMgmt);
1008                 }
1009 
1010                 validate20EffectiveRepository(
1011                         problems, distMgmt.getRepository(), "distributionManagement.repository.", validationLevel);
1012                 validate20EffectiveRepository(
1013                         problems,
1014                         distMgmt.getSnapshotRepository(),
1015                         "distributionManagement.snapshotRepository.",
1016                         validationLevel);
1017             }
1018         }
1019     }
1020 
1021     private void validate20RawDependencies(
1022             ModelProblemCollector problems,
1023             List<Dependency> dependencies,
1024             String prefix,
1025             String prefix2,
1026             boolean is41OrBeyond,
1027             int validationLevel) {
1028         Severity errOn30 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_0);
1029         Severity errOn31 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_1);
1030 
1031         Map<String, Dependency> index = new HashMap<>();
1032 
1033         for (Dependency dependency : dependencies) {
1034             String key = dependency.getManagementKey();
1035 
1036             if ("import".equals(dependency.getScope())) {
1037                 if (!"pom".equals(dependency.getType())) {
1038                     addViolation(
1039                             problems,
1040                             Severity.WARNING,
1041                             Version.V20,
1042                             prefix + prefix2 + "type",
1043                             SourceHint.dependencyManagementKey(dependency),
1044                             "must be 'pom' to import the managed dependencies.",
1045                             dependency);
1046                 } else if (!is41OrBeyond
1047                         && dependency.getClassifier() != null
1048                         && !dependency.getClassifier().isEmpty()) {
1049                     addViolation(
1050                             problems,
1051                             errOn30,
1052                             Version.V20,
1053                             prefix + prefix2 + "classifier",
1054                             SourceHint.dependencyManagementKey(dependency),
1055                             "must be empty, imported POM cannot have a classifier.",
1056                             dependency);
1057                 }
1058             } else if ("system".equals(dependency.getScope())) {
1059 
1060                 if (validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_3_1) {
1061                     addViolation(
1062                             problems,
1063                             Severity.WARNING,
1064                             Version.V31,
1065                             prefix + prefix2 + "scope",
1066                             SourceHint.dependencyManagementKey(dependency),
1067                             "declares usage of deprecated 'system' scope ",
1068                             dependency);
1069                 }
1070 
1071                 String sysPath = dependency.getSystemPath();
1072                 if (sysPath != null && !sysPath.isEmpty()) {
1073                     if (!hasExpression(sysPath)) {
1074                         addViolation(
1075                                 problems,
1076                                 Severity.WARNING,
1077                                 Version.V20,
1078                                 prefix + prefix2 + "systemPath",
1079                                 SourceHint.dependencyManagementKey(dependency),
1080                                 "should use a variable instead of a hard-coded path " + sysPath,
1081                                 dependency);
1082                     } else if (sysPath.contains("${basedir}") || sysPath.contains("${project.basedir}")) {
1083                         addViolation(
1084                                 problems,
1085                                 Severity.WARNING,
1086                                 Version.V20,
1087                                 prefix + prefix2 + "systemPath",
1088                                 SourceHint.dependencyManagementKey(dependency),
1089                                 "should not point at files within the project directory, " + sysPath
1090                                         + " will be unresolvable by dependent projects",
1091                                 dependency);
1092                     }
1093                 }
1094             }
1095 
1096             if (equals("LATEST", dependency.getVersion()) || equals("RELEASE", dependency.getVersion())) {
1097                 addViolation(
1098                         problems,
1099                         Severity.WARNING,
1100                         Version.BASE,
1101                         prefix + prefix2 + "version",
1102                         SourceHint.dependencyManagementKey(dependency),
1103                         "is either LATEST or RELEASE (both of them are being deprecated)",
1104                         dependency);
1105             }
1106 
1107             Dependency existing = index.get(key);
1108 
1109             if (existing != null) {
1110                 String msg;
1111                 if (equals(existing.getVersion(), dependency.getVersion())) {
1112                     msg = "duplicate declaration of version " + Objects.toString(dependency.getVersion(), "(?)");
1113                 } else {
1114                     msg = "version " + Objects.toString(existing.getVersion(), "(?)") + " vs "
1115                             + Objects.toString(dependency.getVersion(), "(?)");
1116                 }
1117 
1118                 addViolation(
1119                         problems,
1120                         errOn31,
1121                         Version.V20,
1122                         prefix + prefix2 + "(groupId:artifactId:type:classifier)",
1123                         null,
1124                         "must be unique: " + key + " -> " + msg,
1125                         dependency);
1126             } else {
1127                 index.put(key, dependency);
1128             }
1129         }
1130     }
1131 
1132     private void validate20RawDependenciesSelfReferencing(
1133             ModelProblemCollector problems, Model m, List<Dependency> dependencies, String prefix) {
1134         
1135         
1136         
1137         
1138         
1139         for (Dependency dependency : dependencies) {
1140             String key = dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getVersion()
1141                     + (dependency.getClassifier() != null ? ":" + dependency.getClassifier() : EMPTY);
1142             String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
1143             if (key.equals(mKey)) {
1144                 
1145                 
1146                 
1147                 addViolation(
1148                         problems,
1149                         Severity.FATAL,
1150                         Version.V31,
1151                         prefix + "[" + key + "]",
1152                         SourceHint.gav(key),
1153                         "is referencing itself.",
1154                         dependency);
1155             }
1156         }
1157     }
1158 
1159     private void validateEffectiveDependencies(
1160             Session s,
1161             ModelProblemCollector problems,
1162             Model m,
1163             List<Dependency> dependencies,
1164             boolean management,
1165             int validationLevel) {
1166         Severity errOn30 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_0);
1167 
1168         String prefix = management ? "dependencyManagement.dependencies.dependency." : "dependencies.dependency.";
1169 
1170         for (Dependency d : dependencies) {
1171             validateEffectiveDependency(problems, d, management, prefix, validationLevel);
1172 
1173             if (validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_2_0) {
1174                 validateBoolean(
1175                         prefix,
1176                         "optional",
1177                         problems,
1178                         errOn30,
1179                         Version.V20,
1180                         d.getOptional(),
1181                         SourceHint.dependencyManagementKey(d),
1182                         d);
1183 
1184                 if (!management) {
1185                     validateVersion(
1186                             prefix,
1187                             "version",
1188                             problems,
1189                             errOn30,
1190                             Version.V20,
1191                             d.getVersion(),
1192                             SourceHint.dependencyManagementKey(d),
1193                             d);
1194 
1195                     
1196 
1197 
1198 
1199                     ScopeManager scopeManager =
1200                             InternalSession.from(s).getSession().getScopeManager();
1201                     validateEnum(
1202                             prefix,
1203                             "scope",
1204                             problems,
1205                             Severity.WARNING,
1206                             Version.V20,
1207                             d.getScope(),
1208                             SourceHint.dependencyManagementKey(d),
1209                             d,
1210                             scopeManager.getDependencyScopeUniverse().stream()
1211                                     .map(DependencyScope::getId)
1212                                     .distinct()
1213                                     .toArray(String[]::new));
1214 
1215                     validateEffectiveModelAgainstDependency(prefix, problems, m, d);
1216                 } else {
1217                     ScopeManager scopeManager =
1218                             InternalSession.from(s).getSession().getScopeManager();
1219                     Set<String> scopes = scopeManager.getDependencyScopeUniverse().stream()
1220                             .map(DependencyScope::getId)
1221                             .collect(Collectors.toCollection(HashSet::new));
1222                     scopes.add("import");
1223                     validateEnum(
1224                             prefix,
1225                             "scope",
1226                             problems,
1227                             Severity.WARNING,
1228                             Version.V20,
1229                             d.getScope(),
1230                             SourceHint.dependencyManagementKey(d),
1231                             d,
1232                             scopes.toArray(new String[0]));
1233                 }
1234             }
1235         }
1236     }
1237 
1238     private void validateEffectiveModelAgainstDependency(
1239             String prefix, ModelProblemCollector problems, Model m, Dependency d) {
1240         String key = d.getGroupId() + ":" + d.getArtifactId() + ":" + d.getVersion()
1241                 + (d.getClassifier() != null ? ":" + d.getClassifier() : EMPTY);
1242         String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
1243         if (key.equals(mKey)) {
1244             
1245             
1246             
1247             addViolation(
1248                     problems,
1249                     Severity.FATAL,
1250                     Version.V31,
1251                     prefix + "[" + key + "]",
1252                     SourceHint.gav(key),
1253                     "is referencing itself.",
1254                     d);
1255         }
1256     }
1257 
1258     private void validate20EffectivePluginDependencies(
1259             ModelProblemCollector problems, Plugin plugin, int validationLevel) {
1260         List<Dependency> dependencies = plugin.getDependencies();
1261 
1262         if (!dependencies.isEmpty()) {
1263             String prefix = "build.plugins.plugin[" + plugin.getKey() + "].dependencies.dependency.";
1264 
1265             Severity errOn30 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_0);
1266 
1267             for (Dependency d : dependencies) {
1268                 validateEffectiveDependency(problems, d, false, prefix, validationLevel);
1269 
1270                 validateVersion(
1271                         prefix,
1272                         "version",
1273                         problems,
1274                         errOn30,
1275                         Version.BASE,
1276                         d.getVersion(),
1277                         SourceHint.dependencyManagementKey(d),
1278                         d);
1279 
1280                 validateEnum(
1281                         prefix,
1282                         "scope",
1283                         problems,
1284                         errOn30,
1285                         Version.BASE,
1286                         d.getScope(),
1287                         SourceHint.dependencyManagementKey(d),
1288                         d,
1289                         "compile",
1290                         "runtime",
1291                         "system");
1292             }
1293         }
1294     }
1295 
1296     private void validateEffectiveDependency(
1297             ModelProblemCollector problems, Dependency d, boolean management, String prefix, int validationLevel) {
1298         validateCoordinatesId(
1299                 prefix,
1300                 "artifactId",
1301                 problems,
1302                 Severity.ERROR,
1303                 Version.BASE,
1304                 d.getArtifactId(),
1305                 SourceHint.dependencyManagementKey(d),
1306                 d);
1307 
1308         validateCoordinatesId(
1309                 prefix,
1310                 "groupId",
1311                 problems,
1312                 Severity.ERROR,
1313                 Version.BASE,
1314                 d.getGroupId(),
1315                 SourceHint.dependencyManagementKey(d),
1316                 d);
1317 
1318         if (!management) {
1319             validateStringNotEmpty(
1320                     prefix,
1321                     "type",
1322                     problems,
1323                     Severity.ERROR,
1324                     Version.BASE,
1325                     d.getType(),
1326                     SourceHint.dependencyManagementKey(d),
1327                     d);
1328 
1329             validateDependencyVersion(problems, d, prefix);
1330         }
1331 
1332         if ("system".equals(d.getScope())) {
1333             String systemPath = d.getSystemPath();
1334 
1335             if (systemPath == null || systemPath.isEmpty()) {
1336                 addViolation(
1337                         problems,
1338                         Severity.ERROR,
1339                         Version.BASE,
1340                         prefix + "systemPath",
1341                         SourceHint.dependencyManagementKey(d),
1342                         "is missing.",
1343                         d);
1344             } else {
1345                 File sysFile = new File(systemPath);
1346                 if (!sysFile.isAbsolute()) {
1347                     addViolation(
1348                             problems,
1349                             Severity.ERROR,
1350                             Version.BASE,
1351                             prefix + "systemPath",
1352                             SourceHint.dependencyManagementKey(d),
1353                             "must specify an absolute path but is " + systemPath,
1354                             d);
1355                 } else if (!sysFile.isFile()) {
1356                     String msg = "refers to a non-existing file " + sysFile.getAbsolutePath() + ".";
1357                     addViolation(
1358                             problems,
1359                             Severity.WARNING,
1360                             Version.BASE,
1361                             prefix + "systemPath",
1362                             SourceHint.dependencyManagementKey(d),
1363                             msg,
1364                             d);
1365                 }
1366             }
1367         } else if (d.getSystemPath() != null && !d.getSystemPath().isEmpty()) {
1368             addViolation(
1369                     problems,
1370                     Severity.ERROR,
1371                     Version.BASE,
1372                     prefix + "systemPath",
1373                     SourceHint.dependencyManagementKey(d),
1374                     "must be omitted. This field may only be specified for a dependency with system scope.",
1375                     d);
1376         }
1377 
1378         if (validationLevel >= ModelValidator.VALIDATION_LEVEL_MAVEN_2_0) {
1379             for (Exclusion exclusion : d.getExclusions()) {
1380                 if (validationLevel < ModelValidator.VALIDATION_LEVEL_MAVEN_3_0) {
1381                     validateCoordinatesId(
1382                             prefix,
1383                             "exclusions.exclusion.groupId",
1384                             problems,
1385                             Severity.WARNING,
1386                             Version.V20,
1387                             exclusion.getGroupId(),
1388                             SourceHint.dependencyManagementKey(d),
1389                             exclusion);
1390 
1391                     validateCoordinatesId(
1392                             prefix,
1393                             "exclusions.exclusion.artifactId",
1394                             problems,
1395                             Severity.WARNING,
1396                             Version.V20,
1397                             exclusion.getArtifactId(),
1398                             SourceHint.dependencyManagementKey(d),
1399                             exclusion);
1400                 } else {
1401                     validateCoordinatesIdWithWildcards(
1402                             prefix,
1403                             "exclusions.exclusion.groupId",
1404                             problems,
1405                             Severity.WARNING,
1406                             Version.V30,
1407                             exclusion.getGroupId(),
1408                             SourceHint.dependencyManagementKey(d),
1409                             exclusion);
1410 
1411                     validateCoordinatesIdWithWildcards(
1412                             prefix,
1413                             "exclusions.exclusion.artifactId",
1414                             problems,
1415                             Severity.WARNING,
1416                             Version.V30,
1417                             exclusion.getArtifactId(),
1418                             SourceHint.dependencyManagementKey(d),
1419                             exclusion);
1420                 }
1421             }
1422         }
1423     }
1424 
1425     
1426 
1427 
1428     protected void validateDependencyVersion(ModelProblemCollector problems, Dependency d, String prefix) {
1429         validateStringNotEmpty(
1430                 prefix,
1431                 "version",
1432                 problems,
1433                 Severity.ERROR,
1434                 Version.BASE,
1435                 d.getVersion(),
1436                 SourceHint.dependencyManagementKey(d),
1437                 d);
1438     }
1439 
1440     private void validateRawRepositories(
1441             ModelProblemCollector problems,
1442             List<Repository> repositories,
1443             String prefix,
1444             String prefix2,
1445             int validationLevel) {
1446         Map<String, Repository> index = new HashMap<>();
1447 
1448         for (Repository repository : repositories) {
1449             validateStringNotEmpty(
1450                     prefix, prefix2, "id", problems, Severity.ERROR, Version.V20, repository.getId(), null, repository);
1451 
1452             if (validateStringNotEmpty(
1453                     prefix,
1454                     prefix2,
1455                     "[" + repository.getId() + "].url",
1456                     problems,
1457                     Severity.ERROR,
1458                     Version.V20,
1459                     repository.getUrl(),
1460                     null,
1461                     repository)) {
1462                 
1463                 Matcher m = EXPRESSION_NAME_PATTERN.matcher(repository.getUrl());
1464                 while (m.find()) {
1465                     String expr = m.group(1);
1466                     if (!("basedir".equals(expr)
1467                             || "project.basedir".equals(expr)
1468                             || expr.startsWith("project.basedir.")
1469                             || "project.rootDirectory".equals(expr)
1470                             || expr.startsWith("project.rootDirectory."))) {
1471                         addViolation(
1472                                 problems,
1473                                 Severity.ERROR,
1474                                 Version.V40,
1475                                 prefix + prefix2 + "[" + repository.getId() + "].url",
1476                                 null,
1477                                 "contains an unsupported expression (only expressions starting with 'project.basedir' or 'project.rootDirectory' are supported).",
1478                                 repository);
1479                         break;
1480                     }
1481                 }
1482             }
1483 
1484             String key = repository.getId();
1485 
1486             Repository existing = index.get(key);
1487 
1488             if (existing != null) {
1489                 Severity errOn30 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_0);
1490 
1491                 addViolation(
1492                         problems,
1493                         errOn30,
1494                         Version.V20,
1495                         prefix + prefix2 + "id",
1496                         null,
1497                         "must be unique: " + repository.getId() + " -> " + existing.getUrl() + " vs "
1498                                 + repository.getUrl(),
1499                         repository);
1500             } else {
1501                 index.put(key, repository);
1502             }
1503         }
1504     }
1505 
1506     private void validate20EffectiveRepository(
1507             ModelProblemCollector problems, Repository repository, String prefix, int validationLevel) {
1508         if (repository != null) {
1509             Severity errOn31 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_1);
1510 
1511             validateBannedCharacters(
1512                     prefix,
1513                     "id",
1514                     problems,
1515                     errOn31,
1516                     Version.V20,
1517                     repository.getId(),
1518                     null,
1519                     repository,
1520                     ILLEGAL_REPO_ID_CHARS);
1521 
1522             if ("local".equals(repository.getId())) {
1523                 addViolation(
1524                         problems,
1525                         errOn31,
1526                         Version.V20,
1527                         prefix + "id",
1528                         null,
1529                         "must not be 'local'" + ", this identifier is reserved for the local repository"
1530                                 + ", using it for other repositories will corrupt your repository metadata.",
1531                         repository);
1532             }
1533 
1534             if ("legacy".equals(repository.getLayout())) {
1535                 addViolation(
1536                         problems,
1537                         Severity.WARNING,
1538                         Version.V20,
1539                         prefix + "layout",
1540                         SourceHint.repoId(repository),
1541                         "uses the unsupported value 'legacy', artifact resolution might fail.",
1542                         repository);
1543             }
1544         }
1545     }
1546 
1547     private void validate20RawResources(
1548             ModelProblemCollector problems, List<Resource> resources, String prefix, int validationLevel) {
1549         Severity errOn30 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_0);
1550 
1551         for (Resource resource : resources) {
1552             validateStringNotEmpty(
1553                     prefix,
1554                     "directory",
1555                     problems,
1556                     Severity.ERROR,
1557                     Version.V20,
1558                     resource.getDirectory(),
1559                     null,
1560                     resource);
1561 
1562             validateBoolean(
1563                     prefix,
1564                     "filtering",
1565                     problems,
1566                     errOn30,
1567                     Version.V20,
1568                     resource.getFiltering(),
1569                     SourceHint.resourceDirectory(resource),
1570                     resource);
1571         }
1572     }
1573 
1574     
1575     
1576     
1577 
1578     private boolean validateCoordinatesId(
1579             String fieldName, ModelProblemCollector problems, String id, InputLocationTracker tracker) {
1580         return validateCoordinatesId(EMPTY, fieldName, problems, Severity.ERROR, Version.BASE, id, null, tracker);
1581     }
1582 
1583     @SuppressWarnings("checkstyle:parameternumber")
1584     private boolean validateCoordinatesId(
1585             String prefix,
1586             String fieldName,
1587             ModelProblemCollector problems,
1588             Severity severity,
1589             Version version,
1590             String id,
1591             @Nullable SourceHint sourceHint,
1592             InputLocationTracker tracker) {
1593         if (id != null && validCoordinatesIds.contains(id)) {
1594             return true;
1595         }
1596         if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
1597             return false;
1598         } else {
1599             if (!isValidCoordinatesId(id)) {
1600                 addViolation(
1601                         problems,
1602                         severity,
1603                         version,
1604                         prefix + fieldName,
1605                         sourceHint,
1606                         "with value '" + id + "' does not match a valid coordinate id pattern.",
1607                         tracker);
1608                 return false;
1609             }
1610             validCoordinatesIds.add(id);
1611             return true;
1612         }
1613     }
1614 
1615     private boolean isValidCoordinatesId(String id) {
1616         for (int i = 0; i < id.length(); i++) {
1617             char c = id.charAt(i);
1618             if (!isValidCoordinatesIdCharacter(c)) {
1619                 return false;
1620             }
1621         }
1622         return true;
1623     }
1624 
1625     private boolean isValidCoordinatesIdCharacter(char c) {
1626         return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.';
1627     }
1628 
1629     @SuppressWarnings("checkstyle:parameternumber")
1630     private boolean validateProfileId(
1631             String prefix,
1632             String fieldName,
1633             ModelProblemCollector problems,
1634             Severity severity,
1635             Version version,
1636             String id,
1637             @Nullable SourceHint sourceHint,
1638             InputLocationTracker tracker) {
1639         if (validProfileIds.contains(id)) {
1640             return true;
1641         }
1642         if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
1643             return false;
1644         } else {
1645             if (!isValidProfileId(id)) {
1646                 addViolation(
1647                         problems,
1648                         severity,
1649                         version,
1650                         prefix + fieldName,
1651                         sourceHint,
1652                         "with value '" + id + "' does not match a valid profile id pattern.",
1653                         tracker);
1654                 return false;
1655             }
1656             validProfileIds.add(id);
1657             return true;
1658         }
1659     }
1660 
1661     private boolean isValidProfileId(String id) {
1662         return switch (id.charAt(0)) { 
1663                 
1664                 
1665                 
1666             case '+', '-', '!', '?' -> false;
1667             default -> true;
1668         };
1669     }
1670 
1671     @SuppressWarnings("checkstyle:parameternumber")
1672     private boolean validateCoordinatesIdWithWildcards(
1673             String prefix,
1674             String fieldName,
1675             ModelProblemCollector problems,
1676             Severity severity,
1677             Version version,
1678             String id,
1679             @Nullable SourceHint sourceHint,
1680             InputLocationTracker tracker) {
1681         if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
1682             return false;
1683         } else {
1684             if (!isValidCoordinatesIdWithWildCards(id)) {
1685                 addViolation(
1686                         problems,
1687                         severity,
1688                         version,
1689                         prefix + fieldName,
1690                         sourceHint,
1691                         "with value '" + id + "' does not match a valid coordinate id pattern.",
1692                         tracker);
1693                 return false;
1694             }
1695             return true;
1696         }
1697     }
1698 
1699     private boolean isValidCoordinatesIdWithWildCards(String id) {
1700         for (int i = 0; i < id.length(); i++) {
1701             char c = id.charAt(i);
1702             if (!isValidCoordinatesIdWithWildCardCharacter(c)) {
1703                 return false;
1704             }
1705         }
1706         return true;
1707     }
1708 
1709     private boolean isValidCoordinatesIdWithWildCardCharacter(char c) {
1710         return isValidCoordinatesIdCharacter(c) || c == '?' || c == '*';
1711     }
1712 
1713     private boolean validateStringNoExpression(
1714             String fieldName,
1715             ModelProblemCollector problems,
1716             Severity severity,
1717             Version version,
1718             String string,
1719             InputLocationTracker tracker) {
1720         if (!hasExpression(string)) {
1721             return true;
1722         }
1723 
1724         addViolation(
1725                 problems,
1726                 severity,
1727                 version,
1728                 fieldName,
1729                 null,
1730                 "contains an expression but should be a constant.",
1731                 tracker);
1732 
1733         return false;
1734     }
1735 
1736     private boolean validateVersionNoExpression(
1737             String fieldName,
1738             ModelProblemCollector problems,
1739             Severity severity,
1740             Version version,
1741             String string,
1742             InputLocationTracker tracker) {
1743         if (!hasExpression(string)) {
1744             return true;
1745         }
1746 
1747         Matcher m = EXPRESSION_NAME_PATTERN.matcher(string.trim());
1748         if (m.find()) {
1749             addViolation(
1750                     problems,
1751                     severity,
1752                     version,
1753                     fieldName,
1754                     null,
1755                     "contains an expression but should be a constant.",
1756                     tracker);
1757         }
1758 
1759         return true;
1760     }
1761 
1762     private boolean hasExpression(String value) {
1763         return value != null && value.contains("${");
1764     }
1765 
1766     private boolean hasProjectExpression(String value) {
1767         return value != null && value.contains("${project.");
1768     }
1769 
1770     private boolean validateStringNotEmpty(
1771             String fieldName,
1772             ModelProblemCollector problems,
1773             Severity severity,
1774             Version version,
1775             String string,
1776             InputLocationTracker tracker) {
1777         return validateStringNotEmpty(EMPTY, fieldName, problems, severity, version, string, null, tracker);
1778     }
1779 
1780     
1781 
1782 
1783 
1784 
1785 
1786 
1787 
1788     @SuppressWarnings("checkstyle:parameternumber")
1789     private boolean validateStringNotEmpty(
1790             String prefix,
1791             String prefix2,
1792             String fieldName,
1793             ModelProblemCollector problems,
1794             Severity severity,
1795             Version version,
1796             String string,
1797             @Nullable SourceHint sourceHint,
1798             InputLocationTracker tracker) {
1799         if (!validateNotNull(prefix, prefix2, fieldName, problems, severity, version, string, sourceHint, tracker)) {
1800             return false;
1801         }
1802 
1803         if (!string.isEmpty()) {
1804             return true;
1805         }
1806 
1807         addViolation(problems, severity, version, prefix + prefix2 + fieldName, sourceHint, "is missing.", tracker);
1808 
1809         return false;
1810     }
1811 
1812     
1813 
1814 
1815 
1816 
1817 
1818 
1819 
1820     @SuppressWarnings("checkstyle:parameternumber")
1821     private boolean validateStringNotEmpty(
1822             String prefix,
1823             String fieldName,
1824             ModelProblemCollector problems,
1825             Severity severity,
1826             Version version,
1827             String string,
1828             @Nullable SourceHint sourceHint,
1829             InputLocationTracker tracker) {
1830         if (!validateNotNull(prefix, fieldName, problems, severity, version, string, sourceHint, tracker)) {
1831             return false;
1832         }
1833 
1834         if (!string.isEmpty()) {
1835             return true;
1836         }
1837 
1838         addViolation(problems, severity, version, prefix + fieldName, sourceHint, "is missing.", tracker);
1839 
1840         return false;
1841     }
1842 
1843     
1844 
1845 
1846 
1847 
1848 
1849 
1850     @SuppressWarnings("checkstyle:parameternumber")
1851     private boolean validateNotNull(
1852             String prefix,
1853             String fieldName,
1854             ModelProblemCollector problems,
1855             Severity severity,
1856             Version version,
1857             Object object,
1858             @Nullable SourceHint sourceHint,
1859             InputLocationTracker tracker) {
1860         if (object != null) {
1861             return true;
1862         }
1863 
1864         addViolation(problems, severity, version, prefix + fieldName, sourceHint, "is missing.", tracker);
1865 
1866         return false;
1867     }
1868 
1869     
1870 
1871 
1872 
1873 
1874 
1875 
1876     @SuppressWarnings("checkstyle:parameternumber")
1877     private boolean validateNotNull(
1878             String prefix,
1879             String prefix2,
1880             String fieldName,
1881             ModelProblemCollector problems,
1882             Severity severity,
1883             Version version,
1884             Object object,
1885             @Nullable SourceHint sourceHint,
1886             InputLocationTracker tracker) {
1887         if (object != null) {
1888             return true;
1889         }
1890 
1891         addViolation(problems, severity, version, prefix + prefix2 + fieldName, sourceHint, "is missing.", tracker);
1892 
1893         return false;
1894     }
1895 
1896     @SuppressWarnings("checkstyle:parameternumber")
1897     private boolean validateBoolean(
1898             String prefix,
1899             String fieldName,
1900             ModelProblemCollector problems,
1901             Severity severity,
1902             Version version,
1903             String string,
1904             @Nullable SourceHint sourceHint,
1905             InputLocationTracker tracker) {
1906         if (string == null || string.isEmpty()) {
1907             return true;
1908         }
1909 
1910         if ("true".equalsIgnoreCase(string) || "false".equalsIgnoreCase(string)) {
1911             return true;
1912         }
1913 
1914         addViolation(
1915                 problems,
1916                 severity,
1917                 version,
1918                 prefix + fieldName,
1919                 sourceHint,
1920                 "must be 'true' or 'false' but is '" + string + "'.",
1921                 tracker);
1922 
1923         return false;
1924     }
1925 
1926     @SuppressWarnings("checkstyle:parameternumber")
1927     private boolean validateEnum(
1928             String prefix,
1929             String fieldName,
1930             ModelProblemCollector problems,
1931             Severity severity,
1932             Version version,
1933             String string,
1934             @Nullable SourceHint sourceHint,
1935             InputLocationTracker tracker,
1936             String... validValues) {
1937         if (string == null || string.isEmpty()) {
1938             return true;
1939         }
1940 
1941         List<String> values = Arrays.asList(validValues);
1942 
1943         if (values.contains(string)) {
1944             return true;
1945         }
1946 
1947         addViolation(
1948                 problems,
1949                 severity,
1950                 version,
1951                 prefix + fieldName,
1952                 sourceHint,
1953                 "must be one of " + values + " but is '" + string + "'.",
1954                 tracker);
1955 
1956         return false;
1957     }
1958 
1959     @SuppressWarnings("checkstyle:parameternumber")
1960     private boolean validateModelVersion(
1961             ModelProblemCollector problems, String string, InputLocationTracker tracker, List<String> validVersions) {
1962         if (string == null || string.isEmpty()) {
1963             return true;
1964         }
1965 
1966         if (validVersions.contains(string)) {
1967             return true;
1968         }
1969 
1970         boolean newerThanAll = true;
1971         boolean olderThanAll = true;
1972         for (String validValue : validVersions) {
1973             final int comparison = compareModelVersions(validValue, string);
1974             newerThanAll = newerThanAll && comparison < 0;
1975             olderThanAll = olderThanAll && comparison > 0;
1976         }
1977 
1978         if (newerThanAll) {
1979             addViolation(
1980                     problems,
1981                     Severity.FATAL,
1982                     Version.V20,
1983                     "modelVersion",
1984                     null,
1985                     "of '" + string + "' is newer than the versions supported by this version of Maven: "
1986                             + validVersions + ". Building this project requires a newer version of Maven.",
1987                     tracker);
1988 
1989         } else if (olderThanAll) {
1990             
1991             addViolation(
1992                     problems,
1993                     Severity.FATAL,
1994                     Version.V20,
1995                     "modelVersion",
1996                     null,
1997                     "of '" + string + "' is older than the versions supported by this version of Maven: "
1998                             + validVersions + ". Building this project requires an older version of Maven.",
1999                     tracker);
2000 
2001         } else {
2002             addViolation(
2003                     problems,
2004                     Severity.ERROR,
2005                     Version.V20,
2006                     "modelVersion",
2007                     null,
2008                     "must be one of " + validVersions + " but is '" + string + "'.",
2009                     tracker);
2010         }
2011 
2012         return false;
2013     }
2014 
2015     
2016 
2017 
2018 
2019 
2020 
2021 
2022 
2023     private static int compareModelVersions(String first, String second) {
2024         
2025         String[] firstSegments = first.split("\\.");
2026         String[] secondSegments = second.split("\\.");
2027         for (int i = 0; i < Math.max(firstSegments.length, secondSegments.length); i++) {
2028             int result = asLong(i, firstSegments).compareTo(asLong(i, secondSegments));
2029             if (result != 0) {
2030                 return result;
2031             }
2032         }
2033         return 0;
2034     }
2035 
2036     private static Long asLong(int index, String[] segments) {
2037         try {
2038             return Long.valueOf(index < segments.length ? segments[index] : "0");
2039         } catch (NumberFormatException e) {
2040             return 0L;
2041         }
2042     }
2043 
2044     @SuppressWarnings("checkstyle:parameternumber")
2045     private boolean validateBannedCharacters(
2046             String prefix,
2047             String fieldName,
2048             ModelProblemCollector problems,
2049             Severity severity,
2050             Version version,
2051             String string,
2052             @Nullable SourceHint sourceHint,
2053             InputLocationTracker tracker,
2054             String banned) {
2055         if (string != null) {
2056             for (int i = string.length() - 1; i >= 0; i--) {
2057                 if (banned.indexOf(string.charAt(i)) >= 0) {
2058                     addViolation(
2059                             problems,
2060                             severity,
2061                             version,
2062                             prefix + fieldName,
2063                             sourceHint,
2064                             "must not contain any of these characters " + banned + " but found " + string.charAt(i),
2065                             tracker);
2066                     return false;
2067                 }
2068             }
2069         }
2070 
2071         return true;
2072     }
2073 
2074     @SuppressWarnings("checkstyle:parameternumber")
2075     private boolean validateVersion(
2076             String prefix,
2077             String fieldName,
2078             ModelProblemCollector problems,
2079             Severity severity,
2080             Version version,
2081             String string,
2082             @Nullable SourceHint sourceHint,
2083             InputLocationTracker tracker) {
2084         if (string == null || string.isEmpty()) {
2085             return true;
2086         }
2087 
2088         if (hasExpression(string)) {
2089             addViolation(
2090                     problems,
2091                     severity,
2092                     version,
2093                     prefix + fieldName,
2094                     sourceHint,
2095                     "must be a valid version but is '" + string + "'.",
2096                     tracker);
2097             return false;
2098         }
2099 
2100         return validateBannedCharacters(
2101                 prefix, fieldName, problems, severity, version, string, sourceHint, tracker, ILLEGAL_VERSION_CHARS);
2102     }
2103 
2104     private boolean validate20ProperSnapshotVersion(
2105             String fieldName,
2106             ModelProblemCollector problems,
2107             Severity severity,
2108             Version version,
2109             String string,
2110             @Nullable SourceHint sourceHint,
2111             InputLocationTracker tracker) {
2112         if (string == null || string.isEmpty()) {
2113             return true;
2114         }
2115 
2116         if (string.endsWith("SNAPSHOT") && !string.endsWith("-SNAPSHOT")) {
2117             addViolation(
2118                     problems,
2119                     severity,
2120                     version,
2121                     fieldName,
2122                     sourceHint,
2123                     "uses an unsupported snapshot version format, should be '*-SNAPSHOT' instead.",
2124                     tracker);
2125             return false;
2126         }
2127 
2128         return true;
2129     }
2130 
2131     private boolean validate20PluginVersion(
2132             String fieldName,
2133             ModelProblemCollector problems,
2134             String string,
2135             @Nullable SourceHint sourceHint,
2136             InputLocationTracker tracker,
2137             int validationLevel) {
2138         if (string == null) {
2139             addViolation(
2140                     problems,
2141                     Severity.WARNING,
2142                     ModelProblem.Version.V20,
2143                     fieldName,
2144                     sourceHint,
2145                     " is missing.",
2146                     tracker);
2147             return false;
2148         }
2149 
2150         Severity errOn30 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_0);
2151 
2152         if (!validateVersion(EMPTY, fieldName, problems, errOn30, Version.V20, string, sourceHint, tracker)) {
2153             return false;
2154         }
2155 
2156         if (string.isEmpty() || "RELEASE".equals(string) || "LATEST".equals(string)) {
2157             addViolation(
2158                     problems,
2159                     errOn30,
2160                     Version.V20,
2161                     fieldName,
2162                     sourceHint,
2163                     "must be a valid version but is '" + string + "'.",
2164                     tracker);
2165             return false;
2166         }
2167 
2168         return true;
2169     }
2170 
2171     private static void addViolation(
2172             ModelProblemCollector problems,
2173             Severity severity,
2174             Version version,
2175             String fieldName,
2176             @Nullable SourceHint sourceHint,
2177             String message,
2178             InputLocationTracker tracker) {
2179         StringBuilder buffer = new StringBuilder(256);
2180         buffer.append('\'').append(fieldName).append('\'');
2181 
2182         if (sourceHint != null) {
2183             buffer.append(" for ").append(sourceHint);
2184         }
2185 
2186         buffer.append(' ').append(message);
2187 
2188         problems.add(severity, version, buffer.toString(), getLocation(fieldName, tracker));
2189     }
2190 
2191     private static InputLocation getLocation(String fieldName, InputLocationTracker tracker) {
2192         InputLocation location = null;
2193 
2194         if (tracker != null) {
2195             if (fieldName != null) {
2196                 Object key = fieldName;
2197 
2198                 int idx = fieldName.lastIndexOf('.');
2199                 if (idx >= 0) {
2200                     fieldName = fieldName.substring(idx + 1);
2201                     key = fieldName;
2202                 }
2203 
2204                 if (fieldName.endsWith("]")) {
2205                     key = fieldName.substring(fieldName.lastIndexOf('[') + 1, fieldName.length() - 1);
2206                     try {
2207                         key = Integer.valueOf(key.toString());
2208                     } catch (NumberFormatException e) {
2209                         
2210                     }
2211                 }
2212 
2213                 location = tracker.getLocation(key);
2214             }
2215 
2216             if (location == null) {
2217                 location = tracker.getLocation(EMPTY);
2218             }
2219         }
2220 
2221         return location;
2222     }
2223 
2224     private static boolean equals(String s1, String s2) {
2225         String c1 = s1 == null ? "" : s1.trim();
2226         String c2 = s2 == null ? "" : s2.trim();
2227         return c1.equals(c2);
2228     }
2229 
2230     private static Severity getSeverity(int validationLevel, int errorThreshold) {
2231         if (validationLevel < errorThreshold) {
2232             return Severity.WARNING;
2233         } else {
2234             return Severity.ERROR;
2235         }
2236     }
2237 
2238     private static class SourceHint {
2239         @Nullable
2240         public static SourceHint xmlNodeInputLocation(XmlNode xmlNode) {
2241             if (xmlNode.inputLocation() != null) {
2242                 return new SourceHint(xmlNode.inputLocation().toString(), null);
2243             } else {
2244                 return null;
2245             }
2246         }
2247 
2248         public static SourceHint gav(String gav) {
2249             return new SourceHint(gav, null); 
2250         }
2251 
2252         public static SourceHint dependencyManagementKey(Dependency dependency) {
2253             String hint;
2254             if (dependency.getClassifier() == null
2255                     || dependency.getClassifier().trim().isEmpty()) {
2256                 hint = String.format(
2257                         "groupId=%s, artifactId=%s, type=%s",
2258                         nvl(dependency.getGroupId()), nvl(dependency.getArtifactId()), nvl(dependency.getType()));
2259             } else {
2260                 hint = String.format(
2261                         "groupId=%s, artifactId=%s, classifier=%s, type=%s",
2262                         nvl(dependency.getGroupId()),
2263                         nvl(dependency.getArtifactId()),
2264                         nvl(dependency.getClassifier()),
2265                         nvl(dependency.getType()));
2266             }
2267             return new SourceHint(hint, null); 
2268         }
2269 
2270         private static String nvl(String value) {
2271             return value == null ? "" : "'" + value + "'";
2272         }
2273 
2274         public static SourceHint pluginKey(Plugin plugin) {
2275             return new SourceHint(plugin.getKey(), null); 
2276         }
2277 
2278         public static SourceHint repoId(Repository repository) {
2279             return new SourceHint(repository.getId(), null); 
2280         }
2281 
2282         @Nullable
2283         public static SourceHint resourceDirectory(Resource resource) {
2284             if (resource.getDirectory() == null) {
2285                 return null;
2286             }
2287             return new SourceHint(resource.getDirectory(), null); 
2288         }
2289 
2290         private final String hint;
2291         private final String format;
2292 
2293         private SourceHint(String hint, String format) {
2294             this.hint = requireNonNull(hint, "hint");
2295             this.format = format;
2296         }
2297 
2298         @Override
2299         public String toString() {
2300             String result = hint;
2301             if (format != null) {
2302                 result = result + " (" + format + ")";
2303             }
2304             return result;
2305         }
2306     }
2307 }