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