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