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