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