View Javadoc
1   package org.apache.maven.shared.release.phase;
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 java.io.File;
23  import java.text.DateFormat;
24  import java.text.SimpleDateFormat;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.Date;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Properties;
31  import java.util.TimeZone;
32  
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.artifact.ArtifactUtils;
35  import org.apache.maven.model.Build;
36  import org.apache.maven.model.BuildBase;
37  import org.apache.maven.model.Model;
38  import org.apache.maven.model.ModelBase;
39  import org.apache.maven.model.Plugin;
40  import org.apache.maven.model.Profile;
41  import org.apache.maven.project.MavenProject;
42  import org.apache.maven.scm.ScmException;
43  import org.apache.maven.scm.ScmFileSet;
44  import org.apache.maven.scm.command.edit.EditScmResult;
45  import org.apache.maven.scm.manager.NoSuchScmProviderException;
46  import org.apache.maven.scm.provider.ScmProvider;
47  import org.apache.maven.scm.repository.ScmRepository;
48  import org.apache.maven.scm.repository.ScmRepositoryException;
49  import org.apache.maven.shared.release.ReleaseExecutionException;
50  import org.apache.maven.shared.release.ReleaseFailureException;
51  import org.apache.maven.shared.release.ReleaseResult;
52  import org.apache.maven.shared.release.config.ReleaseDescriptor;
53  import org.apache.maven.shared.release.env.ReleaseEnvironment;
54  import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
55  import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
56  import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
57  import org.apache.maven.shared.release.scm.ScmTranslator;
58  import org.apache.maven.shared.release.transform.ModelETLRequest;
59  import org.apache.maven.shared.release.transform.MavenCoordinate;
60  import org.apache.maven.shared.release.transform.ModelETL;
61  import org.apache.maven.shared.release.transform.ModelETLFactory;
62  import org.apache.maven.shared.release.transform.jdom.JDomModelETLFactory;
63  import org.apache.maven.shared.release.util.ReleaseUtil;
64  import org.codehaus.plexus.component.annotations.Requirement;
65  import org.codehaus.plexus.util.StringUtils;
66  
67  /**
68   * Base class for rewriting phases.
69   *
70   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
71   */
72  public abstract class AbstractRewritePomsPhase
73      extends AbstractReleasePhase implements ResourceGenerator
74  {
75      /**
76       * Tool that gets a configured SCM repository from release configuration.
77       */
78      @Requirement
79      private ScmRepositoryConfigurator scmRepositoryConfigurator;
80  
81      @Requirement( role = ModelETLFactory.class )
82      private Map<String, ModelETLFactory> modelETLFactories;
83  
84      /**
85       * Use jdom-sax as default
86       */
87      private String modelETL = JDomModelETLFactory.ROLE_HINT;
88  
89      /**
90       * SCM URL translators mapped by provider name.
91       */
92      @Requirement( role = ScmTranslator.class )
93      private Map<String, ScmTranslator> scmTranslators;
94  
95      protected final Map<String, ScmTranslator> getScmTranslators()
96      {
97          return scmTranslators;
98      }
99  
100     private String ls = ReleaseUtil.LS;
101 
102     public void setLs( String ls )
103     {
104         this.ls = ls;
105     }
106 
107     public void setModelETL( String modelETL )
108     {
109         this.modelETL = modelETL;
110     }
111 
112     private long startTime = -1 * 1000;
113 
114     public void setStartTime( long startTime )
115     {
116         this.startTime = startTime;
117     }
118 
119     protected abstract String getPomSuffix();
120 
121     @Override
122     public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
123                                   List<MavenProject> reactorProjects )
124         throws ReleaseExecutionException, ReleaseFailureException
125     {
126         ReleaseResult result = new ReleaseResult();
127 
128         transform( releaseDescriptor, releaseEnvironment, reactorProjects, false, result );
129 
130         result.setResultCode( ReleaseResult.SUCCESS );
131 
132         return result;
133     }
134 
135     @Override
136     public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
137                                    List<MavenProject> reactorProjects )
138         throws ReleaseExecutionException, ReleaseFailureException
139     {
140         ReleaseResult result = new ReleaseResult();
141 
142         transform( releaseDescriptor, releaseEnvironment, reactorProjects, true, result );
143 
144         result.setResultCode( ReleaseResult.SUCCESS );
145 
146         return result;
147     }
148 
149     @Override
150     public ReleaseResult clean( List<MavenProject> reactorProjects )
151     {
152         ReleaseResult result = new ReleaseResult();
153 
154         if ( reactorProjects != null )
155         {
156             for ( MavenProject project : reactorProjects )
157             {
158                 File pomFile = ReleaseUtil.getStandardPom( project );
159                 // MRELEASE-273 : if no pom
160                 if ( pomFile != null )
161                 {
162                     File file = new File( pomFile.getParentFile(), pomFile.getName() + "." + getPomSuffix() );
163                     if ( file.exists() )
164                     {
165                         file.delete();
166                     }
167                 }
168             }
169         }
170 
171         result.setResultCode( ReleaseResult.SUCCESS );
172 
173         return result;
174     }
175 
176     private void transform( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
177                             List<MavenProject> reactorProjects, boolean simulate, ReleaseResult result )
178         throws ReleaseExecutionException, ReleaseFailureException
179     {
180         result.setStartTime( ( startTime >= 0 ) ? startTime : System.currentTimeMillis() );
181 
182         for ( MavenProject project : reactorProjects )
183         {
184             logInfo( result, "Transforming '" + project.getName() + "'..." );
185 
186             transformProject( project, releaseDescriptor, releaseEnvironment, simulate, result );
187         }
188     }
189 
190     private void transformProject( MavenProject project, ReleaseDescriptor releaseDescriptor,
191                                    ReleaseEnvironment releaseEnvironment, boolean simulate,
192                                    ReleaseResult result )
193         throws ReleaseExecutionException, ReleaseFailureException
194     {
195         File pomFile = ReleaseUtil.getStandardPom( project );
196 
197         ModelETLRequest request = new ModelETLRequest();
198         request.setLineSeparator( ls );
199         request.setProject( project );
200         request.setReleaseDescriptor( releaseDescriptor );
201 
202         ModelETL etl = modelETLFactories.get( modelETL ).newInstance( request );
203 
204         etl.extract( pomFile );
205 
206         ScmRepository scmRepository = null;
207         ScmProvider provider = null;
208 
209         if ( isUpdateScm() )
210         {
211             try
212             {
213                 scmRepository = scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
214                                                                                    releaseEnvironment.getSettings() );
215 
216                 provider = scmRepositoryConfigurator.getRepositoryProvider( scmRepository );
217             }
218             catch ( ScmRepositoryException e )
219             {
220                 throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
221             }
222             catch ( NoSuchScmProviderException e )
223             {
224                 throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
225             }
226         }
227 
228         transformDocument( project, etl.getModel(), releaseDescriptor, scmRepository, result,
229                            simulate );
230 
231         File outputFile;
232         if ( simulate )
233         {
234             outputFile = new File( pomFile.getParentFile(), pomFile.getName() + "." + getPomSuffix() );
235         }
236         else
237         {
238             outputFile = pomFile;
239             prepareScm( pomFile, releaseDescriptor, scmRepository, provider );
240         }
241         etl.load( outputFile );
242 
243     }
244 
245     private void transformDocument( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
246                                     ScmRepository scmRepository, ReleaseResult result,
247                                     boolean simulate )
248         throws ReleaseExecutionException, ReleaseFailureException
249     {
250         Model model = project.getModel();
251 
252         Properties properties = modelTarget.getProperties();
253 
254         String parentVersion = rewriteParent( project, modelTarget, releaseDescriptor, simulate );
255 
256         String projectId = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
257 
258         rewriteVersion( modelTarget, releaseDescriptor, projectId, project, parentVersion );
259 
260         Build buildTarget = modelTarget.getBuild();
261         if ( buildTarget != null )
262         {
263             // profile.build.extensions doesn't exist, so only rewrite project.build.extensions
264             rewriteArtifactVersions( toMavenCoordinates( buildTarget.getExtensions() ), 
265                                      model, properties, result, releaseDescriptor, simulate );
266 
267             rewriteArtifactVersions( toMavenCoordinates( buildTarget.getPlugins() ), 
268                                      model, properties, result, releaseDescriptor, simulate );
269 
270             for ( Plugin plugin : buildTarget.getPlugins() )
271             {
272                 rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ),
273                                          model, properties,
274                                          result, releaseDescriptor, simulate );
275             }
276 
277             if ( buildTarget.getPluginManagement() != null )
278             {
279                 rewriteArtifactVersions( toMavenCoordinates( buildTarget.getPluginManagement().getPlugins() ), model,
280                                          properties, result, releaseDescriptor, simulate );
281 
282                 for ( Plugin plugin : buildTarget.getPluginManagement().getPlugins() )
283                 {
284                     rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties, result,
285                                              releaseDescriptor, simulate );
286                 }
287             }
288         }
289 
290         for ( Profile profile : modelTarget.getProfiles() )
291         {
292             BuildBase profileBuild = profile.getBuild();
293             if ( profileBuild != null )
294             {
295                 rewriteArtifactVersions( toMavenCoordinates( profileBuild.getPlugins() ), model, properties, result,
296                                          releaseDescriptor, simulate );
297 
298                 for ( Plugin plugin : profileBuild.getPlugins() )
299                 {
300                     rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties, result,
301                                              releaseDescriptor, simulate );
302                 }
303 
304                 if ( profileBuild.getPluginManagement() != null )
305                 {
306                     rewriteArtifactVersions( toMavenCoordinates( profileBuild.getPluginManagement().getPlugins() ),
307                                              model, properties, result, releaseDescriptor, simulate );
308 
309                     for ( Plugin plugin : profileBuild.getPluginManagement().getPlugins() )
310                     {
311                         rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties,
312                                                  result, releaseDescriptor, simulate );
313                     }
314                 }
315             }
316         }
317 
318         List<ModelBase> modelBases = new ArrayList<>();
319         modelBases.add( modelTarget );
320         modelBases.addAll( modelTarget.getProfiles() );
321 
322         for ( ModelBase modelBase : modelBases )
323         {
324             rewriteArtifactVersions( toMavenCoordinates( modelBase.getDependencies() ), model, properties, result,
325                                      releaseDescriptor, simulate );
326 
327             if ( modelBase.getDependencyManagement() != null )
328             {
329                 rewriteArtifactVersions( toMavenCoordinates( modelBase.getDependencyManagement().getDependencies() ),
330                                          model, properties, result, releaseDescriptor, simulate );
331             }
332 
333             if ( modelBase.getReporting() != null )
334             {
335                 rewriteArtifactVersions( toMavenCoordinates( modelBase.getReporting().getPlugins() ), model, properties,
336                                          result, releaseDescriptor, simulate );
337             }
338         }
339 
340         transformScm( project, modelTarget, releaseDescriptor, projectId, scmRepository, result );
341 
342         if ( properties != null )
343         {
344             rewriteBuildOutputTimestampProperty( properties, result );
345         }
346     }
347 
348     private void rewriteBuildOutputTimestampProperty( Properties properties, ReleaseResult result )
349     {
350         String buildOutputTimestamp = properties.getProperty( "project.build.outputTimestamp" );
351         if ( buildOutputTimestamp == null )
352         {
353             // no Reproducible Builds output timestamp defined
354             return;
355         }
356         if ( buildOutputTimestamp.length() <= 1 )
357         {
358             // value length == 1 means disable Reproducible Builds
359             return;
360         }
361 
362         if ( StringUtils.isNumeric( buildOutputTimestamp ) )
363         {
364             // int representing seconds since the epoch, like SOURCE_DATE_EPOCH
365             buildOutputTimestamp = String.valueOf( result.getStartTime() / 1000 );
366         }
367         else
368         {
369             // ISO-8601
370             DateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss'Z'" );
371             df.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
372             buildOutputTimestamp = df.format( new Date( result.getStartTime() ) );
373         }
374         properties.setProperty( "project.build.outputTimestamp", buildOutputTimestamp );
375     }
376 
377     private void rewriteVersion( Model modelTarget, ReleaseDescriptor releaseDescriptor, String projectId,
378                                  MavenProject project, String parentVersion )
379         throws ReleaseFailureException
380     {
381         String version = getNextVersion( releaseDescriptor, projectId );
382         if ( version == null )
383         {
384             throw new ReleaseFailureException( "Version for '" + project.getName() + "' was not mapped" );
385         }
386 
387         modelTarget.setVersion( version );
388     }
389 
390     private String rewriteParent( MavenProject project, Model targetModel, 
391                                   ReleaseDescriptor releaseDescriptor, boolean simulate )
392         throws ReleaseFailureException
393     {
394         String parentVersion = null;
395         if ( project.hasParent() )
396         {
397             MavenProject parent = project.getParent();
398             String key = ArtifactUtils.versionlessKey( parent.getGroupId(), parent.getArtifactId() );
399             parentVersion = getNextVersion( releaseDescriptor, key );
400             if ( parentVersion == null )
401             {
402                 //MRELEASE-317
403                 parentVersion = getResolvedSnapshotVersion( key, releaseDescriptor );
404             }
405             if ( parentVersion == null )
406             {
407                 String original = getOriginalVersion( releaseDescriptor, key, simulate );
408                 if ( parent.getVersion().equals( original ) )
409                 {
410                     throw new ReleaseFailureException( "Version for parent '" + parent.getName() + "' was not mapped" );
411                 }
412             }
413             else
414             {
415                 targetModel.getParent().setVersion( parentVersion );
416             }
417         }
418         return parentVersion;
419     }
420 
421     private void rewriteArtifactVersions( Collection<MavenCoordinate> elements, Model projectModel,
422                                           Properties properties, ReleaseResult result,
423                                           ReleaseDescriptor releaseDescriptor, boolean simulate )
424         throws ReleaseExecutionException, ReleaseFailureException
425     {
426         if ( elements == null )
427         {
428             return;
429         }
430         String projectId = ArtifactUtils.versionlessKey( projectModel.getGroupId(), projectModel.getArtifactId() );
431         for ( MavenCoordinate coordinate : elements )
432         {
433             String rawVersion = coordinate.getVersion();
434             if ( rawVersion == null )
435             {
436                 // managed dependency or unversioned plugin
437                 continue;
438             }
439 
440             String rawGroupId = coordinate.getGroupId();
441             if ( rawGroupId == null )
442             {
443                 if ( "plugin".equals( coordinate.getName() ) )
444                 {
445                     rawGroupId = "org.apache.maven.plugins";
446                 }
447                 else
448                 {
449                     // incomplete dependency
450                     continue;
451                 }
452             }
453             String groupId = ReleaseUtil.interpolate( rawGroupId, projectModel );
454 
455             String rawArtifactId = coordinate.getArtifactId();
456             if ( rawArtifactId == null )
457             {
458                 // incomplete element
459                 continue;
460             }
461             String artifactId = ReleaseUtil.interpolate( rawArtifactId, projectModel );
462 
463             String key = ArtifactUtils.versionlessKey( groupId, artifactId );
464             String resolvedSnapshotVersion = getResolvedSnapshotVersion( key, releaseDescriptor );
465             String mappedVersion = getNextVersion( releaseDescriptor, key );
466             String originalVersion = getOriginalVersion( releaseDescriptor, key, simulate );
467             if ( originalVersion == null )
468             {
469                 originalVersion = getOriginalResolvedSnapshotVersion( key, releaseDescriptor );
470             }
471 
472             // MRELEASE-220
473             if ( mappedVersion != null && mappedVersion.endsWith( Artifact.SNAPSHOT_VERSION )
474                 && !rawVersion.endsWith( Artifact.SNAPSHOT_VERSION ) && !releaseDescriptor.isUpdateDependencies() )
475             {
476                 continue;
477             }
478 
479             if ( mappedVersion != null )
480             {
481                 if ( rawVersion.equals( originalVersion ) )
482                 {
483                     logInfo( result, "  Updating " + artifactId + " to " + mappedVersion );
484                     coordinate.setVersion( mappedVersion );
485                 }
486                 else if ( rawVersion.matches( "\\$\\{.+\\}" ) )
487                 {
488                     String expression = rawVersion.substring( 2, rawVersion.length() - 1 );
489 
490                     if ( expression.startsWith( "project." ) || expression.startsWith( "pom." )
491                         || "version".equals( expression ) )
492                     {
493                         if ( !mappedVersion.equals( getNextVersion( releaseDescriptor, projectId ) ) )
494                         {
495                             logInfo( result, "  Updating " + artifactId + " to " + mappedVersion );
496                             coordinate.setVersion( mappedVersion );
497                         }
498                         else
499                         {
500                             logInfo( result, "  Ignoring artifact version update for expression " + rawVersion );
501                         }
502                     }
503                     else if ( properties != null )
504                     {
505                         // version is an expression, check for properties to update instead
506 
507                         String propertyValue = properties.getProperty( expression );
508 
509                         if ( propertyValue != null )
510                         {
511                             if ( propertyValue.equals( originalVersion ) )
512                             {
513                                 logInfo( result, "  Updating " + rawVersion + " to " + mappedVersion );
514                                 // change the property only if the property is the same as what's in the reactor
515                                 properties.setProperty( expression, mappedVersion );
516                             }
517                             else if ( mappedVersion.equals( propertyValue ) )
518                             {
519                                 // this property may have been updated during processing a sibling.
520                                 logInfo( result, "  Ignoring artifact version update for expression " + rawVersion
521                                     + " because it is already updated" );
522                             }
523                             else if ( !mappedVersion.equals( rawVersion ) )
524                             {
525                                 if ( mappedVersion.matches( "\\$\\{project.+\\}" )
526                                     || mappedVersion.matches( "\\$\\{pom.+\\}" )
527                                     || "${version}".equals( mappedVersion ) )
528                                 {
529                                     logInfo( result, "  Ignoring artifact version update for expression "
530                                         + mappedVersion );
531                                     // ignore... we cannot update this expression
532                                 }
533                                 else
534                                 {
535                                     // the value of the expression conflicts with what the user wanted to release
536                                     throw new ReleaseFailureException( "The artifact (" + key + ") requires a "
537                                         + "different version (" + mappedVersion + ") than what is found ("
538                                         + propertyValue + ") for the expression (" + expression + ") in the "
539                                         + "project (" + projectId + ")." );
540                                 }
541                             }
542                         }
543                         else
544                         {
545                             // the expression used to define the version of this artifact may be inherited
546                             // TODO needs a better error message, what pom? what dependency?
547                             throw new ReleaseFailureException( "The version could not be updated: " + rawVersion );
548                         }
549                     }
550                 }
551                 else
552                 {
553                     // different/previous version not related to current release
554                 }
555             }
556             else if ( resolvedSnapshotVersion != null )
557             {
558                 logInfo( result, "  Updating " + artifactId + " to " + resolvedSnapshotVersion );
559 
560                 coordinate.setVersion( resolvedSnapshotVersion );
561             }
562             else
563             {
564                 // artifact not related to current release
565             }
566         }
567     }
568 
569     private void prepareScm( File pomFile, ReleaseDescriptor releaseDescriptor, ScmRepository repository,
570                            ScmProvider provider )
571         throws ReleaseExecutionException, ReleaseScmCommandException
572     {
573         try
574         {
575             if ( isUpdateScm() && ( releaseDescriptor.isScmUseEditMode() || provider.requiresEditMode() ) )
576             {
577                 EditScmResult result = provider.edit( repository, new ScmFileSet(
578                     new File( releaseDescriptor.getWorkingDirectory() ), pomFile ) );
579 
580                 if ( !result.isSuccess() )
581                 {
582                     throw new ReleaseScmCommandException( "Unable to enable editing on the POM", result );
583                 }
584             }
585         }
586         catch ( ScmException e )
587         {
588             throw new ReleaseExecutionException( "An error occurred enabling edit mode: " + e.getMessage(), e );
589         }
590     }
591 
592 
593     protected abstract String getResolvedSnapshotVersion( String artifactVersionlessKey,
594                                                           ReleaseDescriptor releaseDscriptor );
595 
596     protected abstract String getOriginalVersion( ReleaseDescriptor releaseDescriptor, String projectKey,
597                                                   boolean simulate );
598 
599     protected abstract String getNextVersion( ReleaseDescriptor releaseDescriptor, String key );
600 
601     protected abstract void transformScm( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
602                                           String projectId, ScmRepository scmRepository,
603                                           ReleaseResult result )
604         throws ReleaseExecutionException;
605 
606     /**
607      *
608      * @return {@code true} if the SCM-section should be updated, otherwise {@code false}
609      * @since 2.4
610      */
611     protected boolean isUpdateScm()
612     {
613         return true;
614     }
615 
616     protected String getOriginalResolvedSnapshotVersion( String artifactVersionlessKey,
617                                                          ReleaseDescriptor releaseDescriptor )
618     {
619         return releaseDescriptor.getDependencyOriginalVersion( artifactVersionlessKey );
620     }
621 
622     /**
623      * Determines the relative path from trunk to tag, and adds this relative path
624      * to the url.
625      *
626      * @param trunkPath - The trunk url
627      * @param tagPath   - The tag base
628      * @param urlPath   - scm.url or scm.connection
629      * @return The url path for the tag.
630      */
631     protected static String translateUrlPath( String trunkPath, String tagPath, String urlPath )
632     {
633         trunkPath = trunkPath.trim();
634         tagPath = tagPath.trim();
635         //Strip the slash at the end if one is present
636         if ( trunkPath.endsWith( "/" ) )
637         {
638             trunkPath = trunkPath.substring( 0, trunkPath.length() - 1 );
639         }
640         if ( tagPath.endsWith( "/" ) )
641         {
642             tagPath = tagPath.substring( 0, tagPath.length() - 1 );
643         }
644         char[] tagPathChars = trunkPath.toCharArray();
645         char[] trunkPathChars = tagPath.toCharArray();
646         // Find the common path between trunk and tags
647         int i = 0;
648         while ( ( i < tagPathChars.length ) && ( i < trunkPathChars.length ) && tagPathChars[i] == trunkPathChars[i] )
649         {
650             ++i;
651         }
652         // If there is nothing common between trunk and tags, or the relative
653         // path does not exist in the url, then just return the tag.
654         if ( i == 0 || urlPath.indexOf( trunkPath.substring( i ) ) < 0 )
655         {
656             return tagPath;
657         }
658         else
659         {
660             return StringUtils.replace( urlPath, trunkPath.substring( i ), tagPath.substring( i ) );
661         }
662     }
663 
664     private Collection<MavenCoordinate> toMavenCoordinates( List<?> objects )
665     {
666         Collection<MavenCoordinate> coordinates = new ArrayList<>( objects.size() );
667         for ( Object object : objects )
668         {
669             if ( object instanceof MavenCoordinate )
670             {
671                 coordinates.add( (MavenCoordinate) object );
672             }
673             else
674             {
675                 throw new UnsupportedOperationException();
676             }
677         }
678         return coordinates;
679     }
680 
681 
682 }