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