View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.model.validation;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.File;
26  import java.util.Arrays;
27  import java.util.Deque;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.LinkedList;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Objects;
35  import java.util.Optional;
36  import java.util.Set;
37  import java.util.function.Consumer;
38  import java.util.function.Supplier;
39  import java.util.regex.Matcher;
40  import java.util.regex.Pattern;
41  import java.util.stream.Collectors;
42  import java.util.stream.StreamSupport;
43  
44  import org.apache.maven.model.Activation;
45  import org.apache.maven.model.Build;
46  import org.apache.maven.model.BuildBase;
47  import org.apache.maven.model.Dependency;
48  import org.apache.maven.model.DependencyManagement;
49  import org.apache.maven.model.DistributionManagement;
50  import org.apache.maven.model.Exclusion;
51  import org.apache.maven.model.InputLocation;
52  import org.apache.maven.model.InputLocationTracker;
53  import org.apache.maven.model.Model;
54  import org.apache.maven.model.Parent;
55  import org.apache.maven.model.Plugin;
56  import org.apache.maven.model.PluginExecution;
57  import org.apache.maven.model.PluginManagement;
58  import org.apache.maven.model.Profile;
59  import org.apache.maven.model.ReportPlugin;
60  import org.apache.maven.model.Reporting;
61  import org.apache.maven.model.Repository;
62  import org.apache.maven.model.Resource;
63  import org.apache.maven.model.building.ModelBuildingRequest;
64  import org.apache.maven.model.building.ModelProblem.Severity;
65  import org.apache.maven.model.building.ModelProblem.Version;
66  import org.apache.maven.model.building.ModelProblemCollector;
67  import org.apache.maven.model.building.ModelProblemCollectorRequest;
68  import org.apache.maven.model.interpolation.ModelVersionProcessor;
69  import org.codehaus.plexus.util.StringUtils;
70  
71  /**
72   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
73   */
74  @Named
75  @Singleton
76  public class DefaultModelValidator implements ModelValidator {
77  
78      private static final Pattern CI_FRIENDLY_EXPRESSION = Pattern.compile("\\$\\{(.+?)}");
79      private static final Pattern EXPRESSION_PROJECT_NAME_PATTERN = Pattern.compile("\\$\\{(project.+?)}");
80  
81      private static final String ILLEGAL_FS_CHARS = "\\/:\"<>|?*";
82  
83      private static final String ILLEGAL_VERSION_CHARS = ILLEGAL_FS_CHARS;
84  
85      private static final String ILLEGAL_REPO_ID_CHARS = ILLEGAL_FS_CHARS;
86  
87      private static final String EMPTY = "";
88  
89      private final Set<String> validIds = new HashSet<>();
90  
91      private ModelVersionProcessor versionProcessor;
92  
93      @Inject
94      public DefaultModelValidator(ModelVersionProcessor versionProcessor) {
95          this.versionProcessor = versionProcessor;
96      }
97  
98      @Override
99      public void validateRawModel(Model m, ModelBuildingRequest request, ModelProblemCollector problems) {
100         Parent parent = m.getParent();
101         if (parent != null) {
102             validateStringNotEmpty(
103                     "parent.groupId", problems, Severity.FATAL, Version.BASE, parent.getGroupId(), parent);
104 
105             validateStringNotEmpty(
106                     "parent.artifactId", problems, Severity.FATAL, Version.BASE, parent.getArtifactId(), parent);
107 
108             validateStringNotEmpty(
109                     "parent.version", problems, Severity.FATAL, Version.BASE, parent.getVersion(), parent);
110 
111             if (equals(parent.getGroupId(), m.getGroupId()) && equals(parent.getArtifactId(), m.getArtifactId())) {
112                 addViolation(
113                         problems,
114                         Severity.FATAL,
115                         Version.BASE,
116                         "parent.artifactId",
117                         null,
118                         "must be changed"
119                                 + ", the parent element cannot have the same groupId:artifactId as the project.",
120                         parent);
121             }
122 
123             if (equals("LATEST", parent.getVersion()) || equals("RELEASE", parent.getVersion())) {
124                 addViolation(
125                         problems,
126                         Severity.WARNING,
127                         Version.BASE,
128                         "parent.version",
129                         null,
130                         "is either LATEST or RELEASE (both of them are being deprecated)",
131                         parent);
132             }
133         }
134 
135         if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
136             Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
137 
138             // [MNG-6074] Maven should produce an error if no model version has been set in a POM file used to build an
139             // effective model.
140             //
141             // As of 3.4, the model version is mandatory even in raw models. The XML element still is optional in the
142             // XML schema and this will not change anytime soon. We do not want to build effective models based on
143             // models without a version starting with 3.4.
144             validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.V20, m.getModelVersion(), m);
145 
146             validateModelVersion(problems, m.getModelVersion(), m, "4.0.0");
147 
148             validateStringNoExpression("groupId", problems, Severity.WARNING, Version.V20, m.getGroupId(), m);
149             if (parent == null) {
150                 validateStringNotEmpty("groupId", problems, Severity.FATAL, Version.V20, m.getGroupId(), m);
151             }
152 
153             validateStringNoExpression("artifactId", problems, Severity.WARNING, Version.V20, m.getArtifactId(), m);
154             validateStringNotEmpty("artifactId", problems, Severity.FATAL, Version.V20, m.getArtifactId(), m);
155 
156             validateVersionNoExpression("version", problems, Severity.WARNING, Version.V20, m.getVersion(), m);
157             if (parent == null) {
158                 validateStringNotEmpty("version", problems, Severity.FATAL, Version.V20, m.getVersion(), m);
159             }
160 
161             validate20RawDependencies(problems, m.getDependencies(), "dependencies.dependency.", EMPTY, request);
162 
163             validate20RawDependenciesSelfReferencing(
164                     problems, m, m.getDependencies(), "dependencies.dependency", request);
165 
166             if (m.getDependencyManagement() != null) {
167                 validate20RawDependencies(
168                         problems,
169                         m.getDependencyManagement().getDependencies(),
170                         "dependencyManagement.dependencies.dependency.",
171                         EMPTY,
172                         request);
173             }
174 
175             validateRawRepositories(problems, m.getRepositories(), "repositories.repository.", EMPTY, request);
176 
177             validateRawRepositories(
178                     problems, m.getPluginRepositories(), "pluginRepositories.pluginRepository.", EMPTY, request);
179 
180             Build build = m.getBuild();
181             if (build != null) {
182                 validate20RawPlugins(problems, build.getPlugins(), "build.plugins.plugin.", EMPTY, request);
183 
184                 PluginManagement mgmt = build.getPluginManagement();
185                 if (mgmt != null) {
186                     validate20RawPlugins(
187                             problems, mgmt.getPlugins(), "build.pluginManagement.plugins.plugin.", EMPTY, request);
188                 }
189             }
190 
191             Set<String> profileIds = new HashSet<>();
192 
193             for (Profile profile : m.getProfiles()) {
194                 String prefix = "profiles.profile[" + profile.getId() + "].";
195 
196                 if (!profileIds.add(profile.getId())) {
197                     addViolation(
198                             problems,
199                             errOn30,
200                             Version.V20,
201                             "profiles.profile.id",
202                             null,
203                             "must be unique but found duplicate profile with id " + profile.getId(),
204                             profile);
205                 }
206 
207                 validate30RawProfileActivation(problems, profile.getActivation(), prefix);
208 
209                 validate20RawDependencies(
210                         problems, profile.getDependencies(), prefix, "dependencies.dependency.", request);
211 
212                 if (profile.getDependencyManagement() != null) {
213                     validate20RawDependencies(
214                             problems,
215                             profile.getDependencyManagement().getDependencies(),
216                             prefix,
217                             "dependencyManagement.dependencies.dependency.",
218                             request);
219                 }
220 
221                 validateRawRepositories(
222                         problems, profile.getRepositories(), prefix, "repositories.repository.", request);
223 
224                 validateRawRepositories(
225                         problems,
226                         profile.getPluginRepositories(),
227                         prefix,
228                         "pluginRepositories.pluginRepository.",
229                         request);
230 
231                 BuildBase buildBase = profile.getBuild();
232                 if (buildBase != null) {
233                     validate20RawPlugins(problems, buildBase.getPlugins(), prefix, "plugins.plugin.", request);
234 
235                     PluginManagement mgmt = buildBase.getPluginManagement();
236                     if (mgmt != null) {
237                         validate20RawPlugins(
238                                 problems, mgmt.getPlugins(), prefix, "pluginManagement.plugins.plugin.", request);
239                     }
240                 }
241             }
242         }
243     }
244 
245     private void validate30RawProfileActivation(ModelProblemCollector problems, Activation activation, String prefix) {
246         if (activation == null) {
247             return;
248         }
249         class ActivationFrame {
250             String location;
251             Optional<? extends InputLocationTracker> parent;
252 
253             ActivationFrame(String location, Optional<? extends InputLocationTracker> parent) {
254                 this.location = location;
255                 this.parent = parent;
256             }
257         }
258         final Deque<ActivationFrame> stk = new LinkedList<>();
259 
260         final Supplier<String> pathSupplier = () -> {
261             final boolean parallel = false;
262             return StreamSupport.stream(((Iterable<ActivationFrame>) stk::descendingIterator).spliterator(), parallel)
263                     .map(f -> f.location)
264                     .collect(Collectors.joining("."));
265         };
266         final Supplier<InputLocation> locationSupplier = () -> {
267             if (stk.size() < 2) {
268                 return null;
269             }
270             Iterator<ActivationFrame> f = stk.iterator();
271 
272             String location = f.next().location;
273             ActivationFrame parent = f.next();
274 
275             return parent.parent.map(p -> p.getLocation(location)).orElse(null);
276         };
277         final Consumer<String> validator = s -> {
278             if (hasProjectExpression(s)) {
279                 String path = pathSupplier.get();
280                 Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(s);
281                 while (matcher.find()) {
282                     String propertyName = matcher.group(0);
283 
284                     if (path.startsWith("activation.file.") && "${project.basedir}".equals(propertyName)) {
285                         continue;
286                     }
287                     addViolation(
288                             problems,
289                             Severity.WARNING,
290                             Version.V30,
291                             prefix + path,
292                             null,
293                             "Failed to interpolate profile activation property " + s + ": " + propertyName
294                                     + " expressions are not supported during profile activation.",
295                             locationSupplier.get());
296                 }
297             }
298         };
299         Optional<Activation> root = Optional.of(activation);
300         stk.push(new ActivationFrame("activation", root));
301         root.map(Activation::getFile).ifPresent(fa -> {
302             stk.push(new ActivationFrame("file", Optional.of(fa)));
303             stk.push(new ActivationFrame("exists", Optional.empty()));
304             validator.accept(fa.getExists());
305             stk.peek().location = "missing";
306             validator.accept(fa.getMissing());
307             stk.pop();
308             stk.pop();
309         });
310         root.map(Activation::getOs).ifPresent(oa -> {
311             stk.push(new ActivationFrame("os", Optional.of(oa)));
312             stk.push(new ActivationFrame("arch", Optional.empty()));
313             validator.accept(oa.getArch());
314             stk.peek().location = "family";
315             validator.accept(oa.getFamily());
316             stk.peek().location = "name";
317             validator.accept(oa.getName());
318             stk.peek().location = "version";
319             validator.accept(oa.getVersion());
320             stk.pop();
321             stk.pop();
322         });
323         root.map(Activation::getProperty).ifPresent(pa -> {
324             stk.push(new ActivationFrame("property", Optional.of(pa)));
325             stk.push(new ActivationFrame("name", Optional.empty()));
326             validator.accept(pa.getName());
327             stk.peek().location = "value";
328             validator.accept(pa.getValue());
329             stk.pop();
330             stk.pop();
331         });
332         root.map(Activation::getJdk).ifPresent(jdk -> {
333             stk.push(new ActivationFrame("jdk", Optional.empty()));
334             validator.accept(jdk);
335             stk.pop();
336         });
337     }
338 
339     private void validate20RawPlugins(
340             ModelProblemCollector problems,
341             List<Plugin> plugins,
342             String prefix,
343             String prefix2,
344             ModelBuildingRequest request) {
345         Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);
346 
347         Map<String, Plugin> index = new HashMap<>();
348 
349         for (Plugin plugin : plugins) {
350             if (plugin.getGroupId() == null
351                     || (plugin.getGroupId() != null
352                             && plugin.getGroupId().trim().isEmpty())) {
353                 addViolation(
354                         problems,
355                         Severity.FATAL,
356                         Version.V20,
357                         prefix + prefix2 + "(groupId:artifactId)",
358                         null,
359                         "groupId of a plugin must be defined. ",
360                         plugin);
361             }
362 
363             if (plugin.getArtifactId() == null
364                     || (plugin.getArtifactId() != null
365                             && plugin.getArtifactId().trim().isEmpty())) {
366                 addViolation(
367                         problems,
368                         Severity.FATAL,
369                         Version.V20,
370                         prefix + prefix2 + "(groupId:artifactId)",
371                         null,
372                         "artifactId of a plugin must be defined. ",
373                         plugin);
374             }
375 
376             // This will catch cases like <version></version> or <version/>
377             if (plugin.getVersion() != null && plugin.getVersion().trim().isEmpty()) {
378                 addViolation(
379                         problems,
380                         Severity.FATAL,
381                         Version.V20,
382                         prefix + prefix2 + "(groupId:artifactId)",
383                         null,
384                         "version of a plugin must be defined. ",
385                         plugin);
386             }
387 
388             String key = plugin.getKey();
389 
390             Plugin existing = index.get(key);
391 
392             if (existing != null) {
393                 addViolation(
394                         problems,
395                         errOn31,
396                         Version.V20,
397                         prefix + prefix2 + "(groupId:artifactId)",
398                         null,
399                         "must be unique but found duplicate declaration of plugin " + key,
400                         plugin);
401             } else {
402                 index.put(key, plugin);
403             }
404 
405             Set<String> executionIds = new HashSet<>();
406 
407             for (PluginExecution exec : plugin.getExecutions()) {
408                 if (!executionIds.add(exec.getId())) {
409                     addViolation(
410                             problems,
411                             Severity.ERROR,
412                             Version.V20,
413                             prefix + prefix2 + "[" + plugin.getKey() + "].executions.execution.id",
414                             null,
415                             "must be unique but found duplicate execution with id " + exec.getId(),
416                             exec);
417                 }
418             }
419         }
420     }
421 
422     @Override
423     @SuppressWarnings("checkstyle:MethodLength")
424     public void validateEffectiveModel(Model m, ModelBuildingRequest request, ModelProblemCollector problems) {
425         validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.BASE, m.getModelVersion(), m);
426 
427         validateId("groupId", problems, m.getGroupId(), m);
428 
429         validateId("artifactId", problems, m.getArtifactId(), m);
430 
431         validateStringNotEmpty("packaging", problems, Severity.ERROR, Version.BASE, m.getPackaging(), m);
432 
433         if (!m.getModules().isEmpty()) {
434             if (!"pom".equals(m.getPackaging())) {
435                 addViolation(
436                         problems,
437                         Severity.ERROR,
438                         Version.BASE,
439                         "packaging",
440                         null,
441                         "with value '" + m.getPackaging() + "' is invalid. Aggregator projects "
442                                 + "require 'pom' as packaging.",
443                         m);
444             }
445 
446             for (int i = 0, n = m.getModules().size(); i < n; i++) {
447                 String module = m.getModules().get(i);
448                 if (StringUtils.isBlank(module)) {
449                     addViolation(
450                             problems,
451                             Severity.ERROR,
452                             Version.BASE,
453                             "modules.module[" + i + "]",
454                             null,
455                             "has been specified without a path to the project directory.",
456                             m.getLocation("modules"));
457                 }
458             }
459         }
460 
461         validateStringNotEmpty("version", problems, Severity.ERROR, Version.BASE, m.getVersion(), m);
462 
463         Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
464 
465         validateEffectiveDependencies(problems, m, m.getDependencies(), false, request);
466 
467         DependencyManagement mgmt = m.getDependencyManagement();
468         if (mgmt != null) {
469             validateEffectiveDependencies(problems, m, mgmt.getDependencies(), true, request);
470         }
471 
472         if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
473             Set<String> modules = new HashSet<>();
474             for (int i = 0, n = m.getModules().size(); i < n; i++) {
475                 String module = m.getModules().get(i);
476                 if (!modules.add(module)) {
477                     addViolation(
478                             problems,
479                             Severity.ERROR,
480                             Version.V20,
481                             "modules.module[" + i + "]",
482                             null,
483                             "specifies duplicate child module " + module,
484                             m.getLocation("modules"));
485                 }
486             }
487 
488             Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);
489 
490             validateBannedCharacters(
491                     EMPTY, "version", problems, errOn31, Version.V20, m.getVersion(), null, m, ILLEGAL_VERSION_CHARS);
492             validate20ProperSnapshotVersion("version", problems, errOn31, Version.V20, m.getVersion(), null, m);
493 
494             Build build = m.getBuild();
495             if (build != null) {
496                 for (Plugin p : build.getPlugins()) {
497                     validateStringNotEmpty(
498                             "build.plugins.plugin.artifactId",
499                             problems,
500                             Severity.ERROR,
501                             Version.V20,
502                             p.getArtifactId(),
503                             p);
504 
505                     validateStringNotEmpty(
506                             "build.plugins.plugin.groupId", problems, Severity.ERROR, Version.V20, p.getGroupId(), p);
507 
508                     validate20PluginVersion(
509                             "build.plugins.plugin.version", problems, p.getVersion(), p.getKey(), p, request);
510 
511                     validateBoolean(
512                             "build.plugins.plugin.inherited",
513                             EMPTY,
514                             problems,
515                             errOn30,
516                             Version.V20,
517                             p.getInherited(),
518                             p.getKey(),
519                             p);
520 
521                     validateBoolean(
522                             "build.plugins.plugin.extensions",
523                             EMPTY,
524                             problems,
525                             errOn30,
526                             Version.V20,
527                             p.getExtensions(),
528                             p.getKey(),
529                             p);
530 
531                     validate20EffectivePluginDependencies(problems, p, request);
532                 }
533 
534                 validate20RawResources(problems, build.getResources(), "build.resources.resource.", request);
535 
536                 validate20RawResources(
537                         problems, build.getTestResources(), "build.testResources.testResource.", request);
538             }
539 
540             Reporting reporting = m.getReporting();
541             if (reporting != null) {
542                 for (ReportPlugin p : reporting.getPlugins()) {
543                     validateStringNotEmpty(
544                             "reporting.plugins.plugin.artifactId",
545                             problems,
546                             Severity.ERROR,
547                             Version.V20,
548                             p.getArtifactId(),
549                             p);
550 
551                     validateStringNotEmpty(
552                             "reporting.plugins.plugin.groupId",
553                             problems,
554                             Severity.ERROR,
555                             Version.V20,
556                             p.getGroupId(),
557                             p);
558                 }
559             }
560 
561             for (Repository repository : m.getRepositories()) {
562                 validate20EffectiveRepository(problems, repository, "repositories.repository.", request);
563             }
564 
565             for (Repository repository : m.getPluginRepositories()) {
566                 validate20EffectiveRepository(problems, repository, "pluginRepositories.pluginRepository.", request);
567             }
568 
569             DistributionManagement distMgmt = m.getDistributionManagement();
570             if (distMgmt != null) {
571                 if (distMgmt.getStatus() != null) {
572                     addViolation(
573                             problems,
574                             Severity.ERROR,
575                             Version.V20,
576                             "distributionManagement.status",
577                             null,
578                             "must not be specified.",
579                             distMgmt);
580                 }
581 
582                 validate20EffectiveRepository(
583                         problems, distMgmt.getRepository(), "distributionManagement.repository.", request);
584                 validate20EffectiveRepository(
585                         problems,
586                         distMgmt.getSnapshotRepository(),
587                         "distributionManagement.snapshotRepository.",
588                         request);
589             }
590         }
591     }
592 
593     private void validate20RawDependencies(
594             ModelProblemCollector problems,
595             List<Dependency> dependencies,
596             String prefix,
597             String prefix2,
598             ModelBuildingRequest request) {
599         Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
600         Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);
601 
602         Map<String, Dependency> index = new HashMap<>();
603 
604         for (Dependency dependency : dependencies) {
605             String key = dependency.getManagementKey();
606 
607             if ("import".equals(dependency.getScope())) {
608                 if (!"pom".equals(dependency.getType())) {
609                     addViolation(
610                             problems,
611                             Severity.WARNING,
612                             Version.V20,
613                             prefix + prefix2 + "type",
614                             key,
615                             "must be 'pom' to import the managed dependencies.",
616                             dependency);
617                 } else if (StringUtils.isNotEmpty(dependency.getClassifier())) {
618                     addViolation(
619                             problems,
620                             errOn30,
621                             Version.V20,
622                             prefix + prefix2 + "classifier",
623                             key,
624                             "must be empty, imported POM cannot have a classifier.",
625                             dependency);
626                 }
627             } else if ("system".equals(dependency.getScope())) {
628 
629                 if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1) {
630                     addViolation(
631                             problems,
632                             Severity.WARNING,
633                             Version.V31,
634                             prefix + prefix2 + "scope",
635                             key,
636                             "declares usage of deprecated 'system' scope ",
637                             dependency);
638                 }
639 
640                 String sysPath = dependency.getSystemPath();
641                 if (StringUtils.isNotEmpty(sysPath)) {
642                     if (!hasExpression(sysPath)) {
643                         addViolation(
644                                 problems,
645                                 Severity.WARNING,
646                                 Version.V20,
647                                 prefix + prefix2 + "systemPath",
648                                 key,
649                                 "should use a variable instead of a hard-coded path " + sysPath,
650                                 dependency);
651                     } else if (sysPath.contains("${basedir}") || sysPath.contains("${project.basedir}")) {
652                         addViolation(
653                                 problems,
654                                 Severity.WARNING,
655                                 Version.V20,
656                                 prefix + prefix2 + "systemPath",
657                                 key,
658                                 "should not point at files within the project directory, " + sysPath
659                                         + " will be unresolvable by dependent projects",
660                                 dependency);
661                     }
662                 }
663             }
664 
665             if (equals("LATEST", dependency.getVersion()) || equals("RELEASE", dependency.getVersion())) {
666                 addViolation(
667                         problems,
668                         Severity.WARNING,
669                         Version.BASE,
670                         prefix + prefix2 + "version",
671                         key,
672                         "is either LATEST or RELEASE (both of them are being deprecated)",
673                         dependency);
674             }
675 
676             Dependency existing = index.get(key);
677 
678             if (existing != null) {
679                 String msg;
680                 if (equals(existing.getVersion(), dependency.getVersion())) {
681                     msg = "duplicate declaration of version " + Objects.toString(dependency.getVersion(), "(?)");
682                 } else {
683                     msg = "version " + Objects.toString(existing.getVersion(), "(?)") + " vs "
684                             + Objects.toString(dependency.getVersion(), "(?)");
685                 }
686 
687                 addViolation(
688                         problems,
689                         errOn31,
690                         Version.V20,
691                         prefix + prefix2 + "(groupId:artifactId:type:classifier)",
692                         null,
693                         "must be unique: " + key + " -> " + msg,
694                         dependency);
695             } else {
696                 index.put(key, dependency);
697             }
698         }
699     }
700 
701     private void validate20RawDependenciesSelfReferencing(
702             ModelProblemCollector problems,
703             Model m,
704             List<Dependency> dependencies,
705             String prefix,
706             ModelBuildingRequest request) {
707         // We only check for groupId/artifactId/version/classifier cause if there is another
708         // module with the same groupId/artifactId/version/classifier this will fail the build
709         // earlier like "Project '...' is duplicated in the reactor.
710         // So it is sufficient to check only groupId/artifactId/version/classifier and not the
711         // packaging type.
712         for (Dependency dependency : dependencies) {
713             String key = dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getVersion()
714                     + (dependency.getClassifier() != null ? ":" + dependency.getClassifier() : EMPTY);
715             String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
716             if (key.equals(mKey)) {
717                 // This means a module which is build has a dependency which has the same
718                 // groupId, artifactId, version and classifier coordinates. This is in consequence
719                 // a self reference or in other words a circular reference which can not being resolved.
720                 addViolation(
721                         problems,
722                         Severity.FATAL,
723                         Version.V31,
724                         prefix + "[" + key + "]",
725                         key,
726                         "is referencing itself.",
727                         dependency);
728             }
729         }
730     }
731 
732     private void validateEffectiveDependencies(
733             ModelProblemCollector problems,
734             Model m,
735             List<Dependency> dependencies,
736             boolean management,
737             ModelBuildingRequest request) {
738         Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
739 
740         String prefix = management ? "dependencyManagement.dependencies.dependency." : "dependencies.dependency.";
741 
742         for (Dependency d : dependencies) {
743             validateEffectiveDependency(problems, d, management, prefix, request);
744 
745             if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
746                 validateBoolean(
747                         prefix, "optional", problems, errOn30, Version.V20, d.getOptional(), d.getManagementKey(), d);
748 
749                 if (!management) {
750                     validateVersion(
751                             prefix, "version", problems, errOn30, Version.V20, d.getVersion(), d.getManagementKey(), d);
752 
753                     /*
754                      * TODO Extensions like Flex Mojos use custom scopes like "merged", "internal", "external", etc. In
755                      * order to don't break backward-compat with those, only warn but don't error out.
756                      */
757                     validateEnum(
758                             prefix,
759                             "scope",
760                             problems,
761                             Severity.WARNING,
762                             Version.V20,
763                             d.getScope(),
764                             d.getManagementKey(),
765                             d,
766                             "provided",
767                             "compile",
768                             "runtime",
769                             "test",
770                             "system");
771 
772                     validateEffectiveModelAgainstDependency(prefix, problems, m, d, request);
773                 } else {
774                     validateEnum(
775                             prefix,
776                             "scope",
777                             problems,
778                             Severity.WARNING,
779                             Version.V20,
780                             d.getScope(),
781                             d.getManagementKey(),
782                             d,
783                             "provided",
784                             "compile",
785                             "runtime",
786                             "test",
787                             "system",
788                             "import");
789                 }
790             }
791         }
792     }
793 
794     private void validateEffectiveModelAgainstDependency(
795             String prefix, ModelProblemCollector problems, Model m, Dependency d, ModelBuildingRequest request) {
796         String key = d.getGroupId() + ":" + d.getArtifactId() + ":" + d.getVersion()
797                 + (d.getClassifier() != null ? ":" + d.getClassifier() : EMPTY);
798         String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
799         if (key.equals(mKey)) {
800             // This means a module which is build has a dependency which has the same
801             // groupId, artifactId, version and classifier coordinates. This is in consequence
802             // a self reference or in other words a circular reference which can not being resolved.
803             addViolation(
804                     problems, Severity.FATAL, Version.V31, prefix + "[" + key + "]", key, "is referencing itself.", d);
805         }
806     }
807 
808     private void validate20EffectivePluginDependencies(
809             ModelProblemCollector problems, Plugin plugin, ModelBuildingRequest request) {
810         List<Dependency> dependencies = plugin.getDependencies();
811 
812         if (!dependencies.isEmpty()) {
813             String prefix = "build.plugins.plugin[" + plugin.getKey() + "].dependencies.dependency.";
814 
815             Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
816 
817             for (Dependency d : dependencies) {
818                 validateEffectiveDependency(problems, d, false, prefix, request);
819 
820                 validateVersion(
821                         prefix, "version", problems, errOn30, Version.BASE, d.getVersion(), d.getManagementKey(), d);
822 
823                 validateEnum(
824                         prefix,
825                         "scope",
826                         problems,
827                         errOn30,
828                         Version.BASE,
829                         d.getScope(),
830                         d.getManagementKey(),
831                         d,
832                         "compile",
833                         "runtime",
834                         "system");
835             }
836         }
837     }
838 
839     private void validateEffectiveDependency(
840             ModelProblemCollector problems,
841             Dependency d,
842             boolean management,
843             String prefix,
844             ModelBuildingRequest request) {
845         validateId(
846                 prefix,
847                 "artifactId",
848                 problems,
849                 Severity.ERROR,
850                 Version.BASE,
851                 d.getArtifactId(),
852                 d.getManagementKey(),
853                 d);
854 
855         validateId(prefix, "groupId", problems, Severity.ERROR, Version.BASE, d.getGroupId(), d.getManagementKey(), d);
856 
857         if (!management) {
858             validateStringNotEmpty(
859                     prefix, "type", problems, Severity.ERROR, Version.BASE, d.getType(), d.getManagementKey(), d);
860 
861             validateDependencyVersion(problems, d, prefix);
862         }
863 
864         if ("system".equals(d.getScope())) {
865             String systemPath = d.getSystemPath();
866 
867             if (StringUtils.isEmpty(systemPath)) {
868                 addViolation(
869                         problems,
870                         Severity.ERROR,
871                         Version.BASE,
872                         prefix + "systemPath",
873                         d.getManagementKey(),
874                         "is missing.",
875                         d);
876             } else {
877                 File sysFile = new File(systemPath);
878                 if (!sysFile.isAbsolute()) {
879                     addViolation(
880                             problems,
881                             Severity.ERROR,
882                             Version.BASE,
883                             prefix + "systemPath",
884                             d.getManagementKey(),
885                             "must specify an absolute path but is " + systemPath,
886                             d);
887                 } else if (!sysFile.isFile()) {
888                     String msg = "refers to a non-existing file " + sysFile.getAbsolutePath();
889                     systemPath = systemPath.replace('/', File.separatorChar).replace('\\', File.separatorChar);
890                     String jdkHome =
891                             request.getSystemProperties().getProperty("java.home", EMPTY) + File.separator + "..";
892                     if (systemPath.startsWith(jdkHome)) {
893                         msg += ". Please verify that you run Maven using a JDK and not just a JRE.";
894                     }
895                     addViolation(
896                             problems,
897                             Severity.WARNING,
898                             Version.BASE,
899                             prefix + "systemPath",
900                             d.getManagementKey(),
901                             msg,
902                             d);
903                 }
904             }
905         } else if (StringUtils.isNotEmpty(d.getSystemPath())) {
906             addViolation(
907                     problems,
908                     Severity.ERROR,
909                     Version.BASE,
910                     prefix + "systemPath",
911                     d.getManagementKey(),
912                     "must be omitted." + " This field may only be specified for a dependency with system scope.",
913                     d);
914         }
915 
916         if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
917             for (Exclusion exclusion : d.getExclusions()) {
918                 if (request.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0) {
919                     validateId(
920                             prefix,
921                             "exclusions.exclusion.groupId",
922                             problems,
923                             Severity.WARNING,
924                             Version.V20,
925                             exclusion.getGroupId(),
926                             d.getManagementKey(),
927                             exclusion);
928 
929                     validateId(
930                             prefix,
931                             "exclusions.exclusion.artifactId",
932                             problems,
933                             Severity.WARNING,
934                             Version.V20,
935                             exclusion.getArtifactId(),
936                             d.getManagementKey(),
937                             exclusion);
938                 } else {
939                     validateIdWithWildcards(
940                             prefix,
941                             "exclusions.exclusion.groupId",
942                             problems,
943                             Severity.WARNING,
944                             Version.V30,
945                             exclusion.getGroupId(),
946                             d.getManagementKey(),
947                             exclusion);
948 
949                     validateIdWithWildcards(
950                             prefix,
951                             "exclusions.exclusion.artifactId",
952                             problems,
953                             Severity.WARNING,
954                             Version.V30,
955                             exclusion.getArtifactId(),
956                             d.getManagementKey(),
957                             exclusion);
958                 }
959             }
960         }
961     }
962 
963     /**
964      * @since 3.2.4
965      */
966     protected void validateDependencyVersion(ModelProblemCollector problems, Dependency d, String prefix) {
967         validateStringNotEmpty(
968                 prefix, "version", problems, Severity.ERROR, Version.BASE, d.getVersion(), d.getManagementKey(), d);
969     }
970 
971     private void validateRawRepositories(
972             ModelProblemCollector problems,
973             List<Repository> repositories,
974             String prefix,
975             String prefix2,
976             ModelBuildingRequest request) {
977         Map<String, Repository> index = new HashMap<>();
978 
979         for (Repository repository : repositories) {
980             validateStringNotEmpty(
981                     prefix, prefix2, "id", problems, Severity.ERROR, Version.V20, repository.getId(), null, repository);
982 
983             validateStringNotEmpty(
984                     prefix,
985                     prefix2,
986                     "[" + repository.getId() + "].url",
987                     problems,
988                     Severity.ERROR,
989                     Version.V20,
990                     repository.getUrl(),
991                     null,
992                     repository);
993 
994             String key = repository.getId();
995 
996             Repository existing = index.get(key);
997 
998             if (existing != null) {
999                 Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
1000 
1001                 addViolation(
1002                         problems,
1003                         errOn30,
1004                         Version.V20,
1005                         prefix + prefix2 + "id",
1006                         null,
1007                         "must be unique: " + repository.getId() + " -> " + existing.getUrl() + " vs "
1008                                 + repository.getUrl(),
1009                         repository);
1010             } else {
1011                 index.put(key, repository);
1012             }
1013         }
1014     }
1015 
1016     private void validate20EffectiveRepository(
1017             ModelProblemCollector problems, Repository repository, String prefix, ModelBuildingRequest request) {
1018         if (repository != null) {
1019             Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);
1020 
1021             validateBannedCharacters(
1022                     prefix,
1023                     "id",
1024                     problems,
1025                     errOn31,
1026                     Version.V20,
1027                     repository.getId(),
1028                     null,
1029                     repository,
1030                     ILLEGAL_REPO_ID_CHARS);
1031 
1032             if ("local".equals(repository.getId())) {
1033                 addViolation(
1034                         problems,
1035                         errOn31,
1036                         Version.V20,
1037                         prefix + "id",
1038                         null,
1039                         "must not be 'local'" + ", this identifier is reserved for the local repository"
1040                                 + ", using it for other repositories will corrupt your repository metadata.",
1041                         repository);
1042             }
1043 
1044             if ("legacy".equals(repository.getLayout())) {
1045                 addViolation(
1046                         problems,
1047                         Severity.WARNING,
1048                         Version.V20,
1049                         prefix + "layout",
1050                         repository.getId(),
1051                         "uses the unsupported value 'legacy', artifact resolution might fail.",
1052                         repository);
1053             }
1054         }
1055     }
1056 
1057     private void validate20RawResources(
1058             ModelProblemCollector problems, List<Resource> resources, String prefix, ModelBuildingRequest request) {
1059         Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
1060 
1061         for (Resource resource : resources) {
1062             validateStringNotEmpty(
1063                     prefix,
1064                     "directory",
1065                     problems,
1066                     Severity.ERROR,
1067                     Version.V20,
1068                     resource.getDirectory(),
1069                     null,
1070                     resource);
1071 
1072             validateBoolean(
1073                     prefix,
1074                     "filtering",
1075                     problems,
1076                     errOn30,
1077                     Version.V20,
1078                     resource.getFiltering(),
1079                     resource.getDirectory(),
1080                     resource);
1081         }
1082     }
1083 
1084     // ----------------------------------------------------------------------
1085     // Field validation
1086     // ----------------------------------------------------------------------
1087 
1088     private boolean validateId(
1089             String fieldName, ModelProblemCollector problems, String id, InputLocationTracker tracker) {
1090         return validateId(EMPTY, fieldName, problems, Severity.ERROR, Version.BASE, id, null, tracker);
1091     }
1092 
1093     @SuppressWarnings("checkstyle:parameternumber")
1094     private boolean validateId(
1095             String prefix,
1096             String fieldName,
1097             ModelProblemCollector problems,
1098             Severity severity,
1099             Version version,
1100             String id,
1101             String sourceHint,
1102             InputLocationTracker tracker) {
1103         if (validIds.contains(id)) {
1104             return true;
1105         }
1106         if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
1107             return false;
1108         } else {
1109             if (!isValidId(id)) {
1110                 addViolation(
1111                         problems,
1112                         severity,
1113                         version,
1114                         prefix + fieldName,
1115                         sourceHint,
1116                         "with value '" + id + "' does not match a valid id pattern.",
1117                         tracker);
1118                 return false;
1119             }
1120             validIds.add(id);
1121             return true;
1122         }
1123     }
1124 
1125     private boolean isValidId(String id) {
1126         for (int i = 0; i < id.length(); i++) {
1127             char c = id.charAt(i);
1128             if (!isValidIdCharacter(c)) {
1129                 return false;
1130             }
1131         }
1132         return true;
1133     }
1134 
1135     private boolean isValidIdCharacter(char c) {
1136         return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.';
1137     }
1138 
1139     @SuppressWarnings("checkstyle:parameternumber")
1140     private boolean validateIdWithWildcards(
1141             String prefix,
1142             String fieldName,
1143             ModelProblemCollector problems,
1144             Severity severity,
1145             Version version,
1146             String id,
1147             String sourceHint,
1148             InputLocationTracker tracker) {
1149         if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
1150             return false;
1151         } else {
1152             if (!isValidIdWithWildCards(id)) {
1153                 addViolation(
1154                         problems,
1155                         severity,
1156                         version,
1157                         prefix + fieldName,
1158                         sourceHint,
1159                         "with value '" + id + "' does not match a valid id pattern.",
1160                         tracker);
1161                 return false;
1162             }
1163             return true;
1164         }
1165     }
1166 
1167     private boolean isValidIdWithWildCards(String id) {
1168         for (int i = 0; i < id.length(); i++) {
1169             char c = id.charAt(i);
1170             if (!isValidIdWithWildCardCharacter(c)) {
1171                 return false;
1172             }
1173         }
1174         return true;
1175     }
1176 
1177     private boolean isValidIdWithWildCardCharacter(char c) {
1178         return isValidIdCharacter(c) || c == '?' || c == '*';
1179     }
1180 
1181     private boolean validateStringNoExpression(
1182             String fieldName,
1183             ModelProblemCollector problems,
1184             Severity severity,
1185             Version version,
1186             String string,
1187             InputLocationTracker tracker) {
1188         if (!hasExpression(string)) {
1189             return true;
1190         }
1191 
1192         addViolation(
1193                 problems,
1194                 severity,
1195                 version,
1196                 fieldName,
1197                 null,
1198                 "contains an expression but should be a constant.",
1199                 tracker);
1200 
1201         return false;
1202     }
1203 
1204     private boolean validateVersionNoExpression(
1205             String fieldName,
1206             ModelProblemCollector problems,
1207             Severity severity,
1208             Version version,
1209             String string,
1210             InputLocationTracker tracker) {
1211         if (!hasExpression(string)) {
1212             return true;
1213         }
1214 
1215         Matcher m = CI_FRIENDLY_EXPRESSION.matcher(string.trim());
1216         while (m.find()) {
1217             String property = m.group(1);
1218             if (!versionProcessor.isValidProperty(property)) {
1219                 addViolation(
1220                         problems,
1221                         severity,
1222                         version,
1223                         fieldName,
1224                         null,
1225                         "contains an expression but should be a constant.",
1226                         tracker);
1227                 return false;
1228             }
1229         }
1230 
1231         return true;
1232     }
1233 
1234     private boolean hasExpression(String value) {
1235         return value != null && value.contains("${");
1236     }
1237 
1238     private boolean hasProjectExpression(String value) {
1239         return value != null && value.contains("${project.");
1240     }
1241 
1242     private boolean validateStringNotEmpty(
1243             String fieldName,
1244             ModelProblemCollector problems,
1245             Severity severity,
1246             Version version,
1247             String string,
1248             InputLocationTracker tracker) {
1249         return validateStringNotEmpty(EMPTY, fieldName, problems, severity, version, string, null, tracker);
1250     }
1251 
1252     /**
1253      * Asserts:
1254      * <p/>
1255      * <ul>
1256      * <li><code>string != null</code>
1257      * <li><code>string.length > 0</code>
1258      * </ul>
1259      */
1260     @SuppressWarnings("checkstyle:parameternumber")
1261     private boolean validateStringNotEmpty(
1262             String prefix,
1263             String prefix2,
1264             String fieldName,
1265             ModelProblemCollector problems,
1266             Severity severity,
1267             Version version,
1268             String string,
1269             String sourceHint,
1270             InputLocationTracker tracker) {
1271         if (!validateNotNull(prefix, prefix2, fieldName, problems, severity, version, string, sourceHint, tracker)) {
1272             return false;
1273         }
1274 
1275         if (!string.isEmpty()) {
1276             return true;
1277         }
1278 
1279         addViolation(problems, severity, version, prefix + prefix2 + fieldName, sourceHint, "is missing.", tracker);
1280 
1281         return false;
1282     }
1283 
1284     /**
1285      * Asserts:
1286      * <p/>
1287      * <ul>
1288      * <li><code>string != null</code>
1289      * <li><code>string.length > 0</code>
1290      * </ul>
1291      */
1292     @SuppressWarnings("checkstyle:parameternumber")
1293     private boolean validateStringNotEmpty(
1294             String prefix,
1295             String fieldName,
1296             ModelProblemCollector problems,
1297             Severity severity,
1298             Version version,
1299             String string,
1300             String sourceHint,
1301             InputLocationTracker tracker) {
1302         if (!validateNotNull(prefix, fieldName, problems, severity, version, string, sourceHint, tracker)) {
1303             return false;
1304         }
1305 
1306         if (string.length() > 0) {
1307             return true;
1308         }
1309 
1310         addViolation(problems, severity, version, prefix + fieldName, sourceHint, "is missing.", tracker);
1311 
1312         return false;
1313     }
1314 
1315     /**
1316      * Asserts:
1317      * <p/>
1318      * <ul>
1319      * <li><code>string != null</code>
1320      * </ul>
1321      */
1322     @SuppressWarnings("checkstyle:parameternumber")
1323     private boolean validateNotNull(
1324             String prefix,
1325             String fieldName,
1326             ModelProblemCollector problems,
1327             Severity severity,
1328             Version version,
1329             Object object,
1330             String sourceHint,
1331             InputLocationTracker tracker) {
1332         if (object != null) {
1333             return true;
1334         }
1335 
1336         addViolation(problems, severity, version, prefix + fieldName, sourceHint, "is missing.", tracker);
1337 
1338         return false;
1339     }
1340 
1341     /**
1342      * Asserts:
1343      * <p/>
1344      * <ul>
1345      * <li><code>string != null</code>
1346      * </ul>
1347      */
1348     @SuppressWarnings("checkstyle:parameternumber")
1349     private boolean validateNotNull(
1350             String prefix,
1351             String prefix2,
1352             String fieldName,
1353             ModelProblemCollector problems,
1354             Severity severity,
1355             Version version,
1356             Object object,
1357             String sourceHint,
1358             InputLocationTracker tracker) {
1359         if (object != null) {
1360             return true;
1361         }
1362 
1363         addViolation(problems, severity, version, prefix + prefix2 + fieldName, sourceHint, "is missing.", tracker);
1364 
1365         return false;
1366     }
1367 
1368     @SuppressWarnings("checkstyle:parameternumber")
1369     private boolean validateBoolean(
1370             String prefix,
1371             String fieldName,
1372             ModelProblemCollector problems,
1373             Severity severity,
1374             Version version,
1375             String string,
1376             String sourceHint,
1377             InputLocationTracker tracker) {
1378         if (string == null || string.length() <= 0) {
1379             return true;
1380         }
1381 
1382         if ("true".equalsIgnoreCase(string) || "false".equalsIgnoreCase(string)) {
1383             return true;
1384         }
1385 
1386         addViolation(
1387                 problems,
1388                 severity,
1389                 version,
1390                 prefix + fieldName,
1391                 sourceHint,
1392                 "must be 'true' or 'false' but is '" + string + "'.",
1393                 tracker);
1394 
1395         return false;
1396     }
1397 
1398     @SuppressWarnings("checkstyle:parameternumber")
1399     private boolean validateEnum(
1400             String prefix,
1401             String fieldName,
1402             ModelProblemCollector problems,
1403             Severity severity,
1404             Version version,
1405             String string,
1406             String sourceHint,
1407             InputLocationTracker tracker,
1408             String... validValues) {
1409         if (string == null || string.length() <= 0) {
1410             return true;
1411         }
1412 
1413         List<String> values = Arrays.asList(validValues);
1414 
1415         if (values.contains(string)) {
1416             return true;
1417         }
1418 
1419         addViolation(
1420                 problems,
1421                 severity,
1422                 version,
1423                 prefix + fieldName,
1424                 sourceHint,
1425                 "must be one of " + values + " but is '" + string + "'.",
1426                 tracker);
1427 
1428         return false;
1429     }
1430 
1431     @SuppressWarnings("checkstyle:parameternumber")
1432     private boolean validateModelVersion(
1433             ModelProblemCollector problems, String string, InputLocationTracker tracker, String... validVersions) {
1434         if (string == null || string.length() <= 0) {
1435             return true;
1436         }
1437 
1438         List<String> values = Arrays.asList(validVersions);
1439 
1440         if (values.contains(string)) {
1441             return true;
1442         }
1443 
1444         boolean newerThanAll = true;
1445         boolean olderThanAll = true;
1446         for (String validValue : validVersions) {
1447             final int comparison = compareModelVersions(validValue, string);
1448             newerThanAll = newerThanAll && comparison < 0;
1449             olderThanAll = olderThanAll && comparison > 0;
1450         }
1451 
1452         if (newerThanAll) {
1453             addViolation(
1454                     problems,
1455                     Severity.FATAL,
1456                     Version.V20,
1457                     "modelVersion",
1458                     null,
1459                     "of '" + string + "' is newer than the versions supported by this version of Maven: " + values
1460                             + ". Building this project requires a newer version of Maven.",
1461                     tracker);
1462 
1463         } else if (olderThanAll) {
1464             // note this will not be hit for Maven 1.x project.xml as it is an incompatible schema
1465             addViolation(
1466                     problems,
1467                     Severity.FATAL,
1468                     Version.V20,
1469                     "modelVersion",
1470                     null,
1471                     "of '" + string + "' is older than the versions supported by this version of Maven: " + values
1472                             + ". Building this project requires an older version of Maven.",
1473                     tracker);
1474 
1475         } else {
1476             addViolation(
1477                     problems,
1478                     Severity.ERROR,
1479                     Version.V20,
1480                     "modelVersion",
1481                     null,
1482                     "must be one of " + values + " but is '" + string + "'.",
1483                     tracker);
1484         }
1485 
1486         return false;
1487     }
1488 
1489     /**
1490      * Compares two model versions.
1491      *
1492      * @param first the first version.
1493      * @param second the second version.
1494      * @return negative if the first version is newer than the second version, zero if they are the same or positive if
1495      * the second version is the newer.
1496      */
1497     private static int compareModelVersions(String first, String second) {
1498         // we use a dedicated comparator because we control our model version scheme.
1499         String[] firstSegments = StringUtils.split(first, ".");
1500         String[] secondSegments = StringUtils.split(second, ".");
1501         for (int i = 0; i < Math.max(firstSegments.length, secondSegments.length); i++) {
1502             int result = Long.valueOf(i < firstSegments.length ? firstSegments[i] : "0")
1503                     .compareTo(Long.valueOf(i < secondSegments.length ? secondSegments[i] : "0"));
1504             if (result != 0) {
1505                 return result;
1506             }
1507         }
1508         return 0;
1509     }
1510 
1511     @SuppressWarnings("checkstyle:parameternumber")
1512     private boolean validateBannedCharacters(
1513             String prefix,
1514             String fieldName,
1515             ModelProblemCollector problems,
1516             Severity severity,
1517             Version version,
1518             String string,
1519             String sourceHint,
1520             InputLocationTracker tracker,
1521             String banned) {
1522         if (string != null) {
1523             for (int i = string.length() - 1; i >= 0; i--) {
1524                 if (banned.indexOf(string.charAt(i)) >= 0) {
1525                     addViolation(
1526                             problems,
1527                             severity,
1528                             version,
1529                             prefix + fieldName,
1530                             sourceHint,
1531                             "must not contain any of these characters " + banned + " but found " + string.charAt(i),
1532                             tracker);
1533                     return false;
1534                 }
1535             }
1536         }
1537 
1538         return true;
1539     }
1540 
1541     @SuppressWarnings("checkstyle:parameternumber")
1542     private boolean validateVersion(
1543             String prefix,
1544             String fieldName,
1545             ModelProblemCollector problems,
1546             Severity severity,
1547             Version version,
1548             String string,
1549             String sourceHint,
1550             InputLocationTracker tracker) {
1551         if (string == null || string.length() <= 0) {
1552             return true;
1553         }
1554 
1555         if (hasExpression(string)) {
1556             addViolation(
1557                     problems,
1558                     severity,
1559                     version,
1560                     prefix + fieldName,
1561                     sourceHint,
1562                     "must be a valid version but is '" + string + "'.",
1563                     tracker);
1564             return false;
1565         }
1566 
1567         return validateBannedCharacters(
1568                 prefix, fieldName, problems, severity, version, string, sourceHint, tracker, ILLEGAL_VERSION_CHARS);
1569     }
1570 
1571     private boolean validate20ProperSnapshotVersion(
1572             String fieldName,
1573             ModelProblemCollector problems,
1574             Severity severity,
1575             Version version,
1576             String string,
1577             String sourceHint,
1578             InputLocationTracker tracker) {
1579         if (string == null || string.length() <= 0) {
1580             return true;
1581         }
1582 
1583         if (string.endsWith("SNAPSHOT") && !string.endsWith("-SNAPSHOT")) {
1584             addViolation(
1585                     problems,
1586                     severity,
1587                     version,
1588                     fieldName,
1589                     sourceHint,
1590                     "uses an unsupported snapshot version format, should be '*-SNAPSHOT' instead.",
1591                     tracker);
1592             return false;
1593         }
1594 
1595         return true;
1596     }
1597 
1598     private boolean validate20PluginVersion(
1599             String fieldName,
1600             ModelProblemCollector problems,
1601             String string,
1602             String sourceHint,
1603             InputLocationTracker tracker,
1604             ModelBuildingRequest request) {
1605         if (string == null) {
1606             // NOTE: The check for missing plugin versions is handled directly by the model builder
1607             return true;
1608         }
1609 
1610         Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
1611 
1612         if (!validateVersion(EMPTY, fieldName, problems, errOn30, Version.V20, string, sourceHint, tracker)) {
1613             return false;
1614         }
1615 
1616         if (string.length() <= 0 || "RELEASE".equals(string) || "LATEST".equals(string)) {
1617             addViolation(
1618                     problems,
1619                     errOn30,
1620                     Version.V20,
1621                     fieldName,
1622                     sourceHint,
1623                     "must be a valid version but is '" + string + "'.",
1624                     tracker);
1625             return false;
1626         }
1627 
1628         return true;
1629     }
1630 
1631     private static void addViolation(
1632             ModelProblemCollector problems,
1633             Severity severity,
1634             Version version,
1635             String fieldName,
1636             String sourceHint,
1637             String message,
1638             InputLocationTracker tracker) {
1639         StringBuilder buffer = new StringBuilder(256);
1640         buffer.append('\'').append(fieldName).append('\'');
1641 
1642         if (sourceHint != null) {
1643             buffer.append(" for ").append(sourceHint);
1644         }
1645 
1646         buffer.append(' ').append(message);
1647 
1648         // CHECKSTYLE_OFF: LineLength
1649         problems.add(new ModelProblemCollectorRequest(severity, version)
1650                 .setMessage(buffer.toString())
1651                 .setLocation(getLocation(fieldName, tracker)));
1652         // CHECKSTYLE_ON: LineLength
1653     }
1654 
1655     private static InputLocation getLocation(String fieldName, InputLocationTracker tracker) {
1656         InputLocation location = null;
1657 
1658         if (tracker != null) {
1659             if (fieldName != null) {
1660                 Object key = fieldName;
1661 
1662                 int idx = fieldName.lastIndexOf('.');
1663                 if (idx >= 0) {
1664                     fieldName = fieldName.substring(idx + 1);
1665                     key = fieldName;
1666                 }
1667 
1668                 if (fieldName.endsWith("]")) {
1669                     key = fieldName.substring(fieldName.lastIndexOf('[') + 1, fieldName.length() - 1);
1670                     try {
1671                         key = Integer.valueOf(key.toString());
1672                     } catch (NumberFormatException e) {
1673                         // use key as is
1674                     }
1675                 }
1676 
1677                 location = tracker.getLocation(key);
1678             }
1679 
1680             if (location == null) {
1681                 location = tracker.getLocation(EMPTY);
1682             }
1683         }
1684 
1685         return location;
1686     }
1687 
1688     private static boolean equals(String s1, String s2) {
1689         return StringUtils.clean(s1).equals(StringUtils.clean(s2));
1690     }
1691 
1692     private static Severity getSeverity(ModelBuildingRequest request, int errorThreshold) {
1693         return getSeverity(request.getValidationLevel(), errorThreshold);
1694     }
1695 
1696     private static Severity getSeverity(int validationLevel, int errorThreshold) {
1697         if (validationLevel < errorThreshold) {
1698             return Severity.WARNING;
1699         } else {
1700             return Severity.ERROR;
1701         }
1702     }
1703 }