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