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