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