1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  package org.apache.maven.shared.release.phase;
20  
21  import java.io.File;
22  import java.net.URI;
23  import java.text.DateFormat;
24  import java.text.SimpleDateFormat;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.Date;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Properties;
32  import java.util.TimeZone;
33  import java.util.regex.Matcher;
34  import java.util.regex.Pattern;
35  
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.artifact.ArtifactUtils;
38  import org.apache.maven.model.Build;
39  import org.apache.maven.model.BuildBase;
40  import org.apache.maven.model.Model;
41  import org.apache.maven.model.ModelBase;
42  import org.apache.maven.model.Plugin;
43  import org.apache.maven.model.Profile;
44  import org.apache.maven.project.MavenProject;
45  import org.apache.maven.scm.ScmException;
46  import org.apache.maven.scm.ScmFileSet;
47  import org.apache.maven.scm.command.edit.EditScmResult;
48  import org.apache.maven.scm.manager.NoSuchScmProviderException;
49  import org.apache.maven.scm.provider.ScmProvider;
50  import org.apache.maven.scm.repository.ScmRepository;
51  import org.apache.maven.scm.repository.ScmRepositoryException;
52  import org.apache.maven.shared.release.ReleaseExecutionException;
53  import org.apache.maven.shared.release.ReleaseFailureException;
54  import org.apache.maven.shared.release.ReleaseResult;
55  import org.apache.maven.shared.release.config.ReleaseDescriptor;
56  import org.apache.maven.shared.release.env.ReleaseEnvironment;
57  import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
58  import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
59  import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
60  import org.apache.maven.shared.release.scm.ScmTranslator;
61  import org.apache.maven.shared.release.transform.MavenCoordinate;
62  import org.apache.maven.shared.release.transform.ModelETL;
63  import org.apache.maven.shared.release.transform.ModelETLFactory;
64  import org.apache.maven.shared.release.transform.ModelETLRequest;
65  import org.apache.maven.shared.release.transform.jdom2.JDomModelETLFactory;
66  import org.apache.maven.shared.release.util.ReleaseUtil;
67  import org.codehaus.plexus.util.StringUtils;
68  
69  import static java.util.Objects.requireNonNull;
70  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
71  
72  
73  
74  
75  
76  
77  public abstract class AbstractRewritePomsPhase extends AbstractReleasePhase implements ResourceGenerator {
78      
79  
80  
81      private final ScmRepositoryConfigurator scmRepositoryConfigurator;
82  
83      private final Map<String, ModelETLFactory> modelETLFactories;
84  
85      
86  
87  
88      private Map<String, ScmTranslator> scmTranslators;
89  
90      
91  
92  
93      private String modelETL = JDomModelETLFactory.NAME;
94  
95      
96  
97  
98  
99      private static final Pattern EXPRESSION_PATTERN = Pattern.compile("\\$\\{(.+)\\}");
100 
101     
102 
103 
104 
105     private static final List<String> CI_FRIENDLY_PROPERTIES = Arrays.asList("revision", "sha1", "changelist");
106 
107     private long startTime = -1 * 1000;
108 
109     protected AbstractRewritePomsPhase(
110             ScmRepositoryConfigurator scmRepositoryConfigurator,
111             Map<String, ModelETLFactory> modelETLFactories,
112             Map<String, ScmTranslator> scmTranslators) {
113         this.scmRepositoryConfigurator = requireNonNull(scmRepositoryConfigurator);
114         this.modelETLFactories = requireNonNull(modelETLFactories);
115         this.scmTranslators = requireNonNull(scmTranslators);
116     }
117 
118     
119 
120 
121 
122 
123     protected final Map<String, ScmTranslator> getScmTranslators() {
124         return scmTranslators;
125     }
126 
127     
128 
129 
130 
131 
132     public void setModelETL(String modelETL) {
133         this.modelETL = modelETL;
134     }
135 
136     
137 
138 
139 
140 
141     public void setStartTime(long startTime) {
142         this.startTime = startTime;
143     }
144 
145     
146 
147 
148 
149 
150     protected abstract String getPomSuffix();
151 
152     @Override
153     public ReleaseResult execute(
154             ReleaseDescriptor releaseDescriptor,
155             ReleaseEnvironment releaseEnvironment,
156             List<MavenProject> reactorProjects)
157             throws ReleaseExecutionException, ReleaseFailureException {
158         ReleaseResult result = new ReleaseResult();
159 
160         transform(releaseDescriptor, releaseEnvironment, reactorProjects, false, result);
161 
162         result.setResultCode(ReleaseResult.SUCCESS);
163 
164         return result;
165     }
166 
167     @Override
168     public ReleaseResult simulate(
169             ReleaseDescriptor releaseDescriptor,
170             ReleaseEnvironment releaseEnvironment,
171             List<MavenProject> reactorProjects)
172             throws ReleaseExecutionException, ReleaseFailureException {
173         ReleaseResult result = new ReleaseResult();
174 
175         transform(releaseDescriptor, releaseEnvironment, reactorProjects, true, result);
176 
177         result.setResultCode(ReleaseResult.SUCCESS);
178 
179         return result;
180     }
181 
182     @Override
183     public ReleaseResult clean(List<MavenProject> reactorProjects) {
184         ReleaseResult result = new ReleaseResult();
185 
186         if (reactorProjects != null) {
187             for (MavenProject project : reactorProjects) {
188                 File pomFile = ReleaseUtil.getStandardPom(project);
189                 
190                 if (pomFile != null) {
191                     File file = new File(pomFile.getParentFile(), pomFile.getName() + "." + getPomSuffix());
192                     if (file.exists()) {
193                         file.delete();
194                     }
195                 }
196             }
197         }
198 
199         result.setResultCode(ReleaseResult.SUCCESS);
200 
201         return result;
202     }
203 
204     private void transform(
205             ReleaseDescriptor releaseDescriptor,
206             ReleaseEnvironment releaseEnvironment,
207             List<MavenProject> reactorProjects,
208             boolean simulate,
209             ReleaseResult result)
210             throws ReleaseExecutionException, ReleaseFailureException {
211         result.setStartTime((startTime >= 0) ? startTime : System.currentTimeMillis());
212 
213         URI root = ReleaseUtil.getRootProject(reactorProjects).getBasedir().toURI();
214 
215         for (MavenProject project : reactorProjects) {
216             URI pom = project.getFile().toURI();
217             logInfo(
218                     result,
219                     "Transforming " + root.relativize(pom).getPath() + ' '
220                             + buffer().project(project.getArtifactId()) + " '" + project.getName() + "'"
221                             + (simulate ? " with ." + getPomSuffix() + " suffix" : "") + "...");
222 
223             transformProject(project, releaseDescriptor, releaseEnvironment, simulate, result);
224         }
225     }
226 
227     private void transformProject(
228             MavenProject project,
229             ReleaseDescriptor releaseDescriptor,
230             ReleaseEnvironment releaseEnvironment,
231             boolean simulate,
232             ReleaseResult result)
233             throws ReleaseExecutionException, ReleaseFailureException {
234         File pomFile = ReleaseUtil.getStandardPom(project);
235 
236         ModelETLRequest request = new ModelETLRequest();
237         request.setProject(project);
238         request.setReleaseDescriptor(releaseDescriptor);
239 
240         ModelETL etl = modelETLFactories.get(modelETL).newInstance(request);
241 
242         etl.extract(pomFile);
243 
244         ScmRepository scmRepository = null;
245         ScmProvider provider = null;
246 
247         if (isUpdateScm()) {
248             try {
249                 scmRepository = scmRepositoryConfigurator.getConfiguredRepository(
250                         releaseDescriptor, releaseEnvironment.getSettings());
251 
252                 provider = scmRepositoryConfigurator.getRepositoryProvider(scmRepository);
253             } catch (ScmRepositoryException e) {
254                 throw new ReleaseScmRepositoryException(e.getMessage(), e.getValidationMessages());
255             } catch (NoSuchScmProviderException e) {
256                 throw new ReleaseExecutionException("Unable to configure SCM repository: " + e.getMessage(), e);
257             }
258         }
259 
260         transformDocument(project, etl.getModel(), releaseDescriptor, scmRepository, result, simulate);
261 
262         File outputFile;
263         if (simulate) {
264             outputFile = new File(pomFile.getParentFile(), pomFile.getName() + "." + getPomSuffix());
265         } else {
266             outputFile = pomFile;
267             prepareScm(pomFile, releaseDescriptor, scmRepository, provider);
268         }
269         etl.load(outputFile);
270     }
271 
272     private void transformDocument(
273             MavenProject project,
274             Model modelTarget,
275             ReleaseDescriptor releaseDescriptor,
276             ScmRepository scmRepository,
277             ReleaseResult result,
278             boolean simulate)
279             throws ReleaseExecutionException, ReleaseFailureException {
280         Model model = project.getModel();
281 
282         Properties properties = modelTarget.getProperties();
283 
284         String parentVersion = rewriteParent(project, modelTarget, result, releaseDescriptor, simulate);
285 
286         String projectId = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
287 
288         rewriteVersion(modelTarget, releaseDescriptor, projectId, project);
289 
290         Build buildTarget = modelTarget.getBuild();
291         if (buildTarget != null) {
292             
293             rewriteArtifactVersions(
294                     toMavenCoordinates(buildTarget.getExtensions()),
295                     model,
296                     properties,
297                     result,
298                     releaseDescriptor,
299                     simulate);
300 
301             rewriteArtifactVersions(
302                     toMavenCoordinates(buildTarget.getPlugins()),
303                     model,
304                     properties,
305                     result,
306                     releaseDescriptor,
307                     simulate);
308 
309             for (Plugin plugin : buildTarget.getPlugins()) {
310                 rewriteArtifactVersions(
311                         toMavenCoordinates(plugin.getDependencies()),
312                         model,
313                         properties,
314                         result,
315                         releaseDescriptor,
316                         simulate);
317             }
318 
319             if (buildTarget.getPluginManagement() != null) {
320                 rewriteArtifactVersions(
321                         toMavenCoordinates(buildTarget.getPluginManagement().getPlugins()),
322                         model,
323                         properties,
324                         result,
325                         releaseDescriptor,
326                         simulate);
327 
328                 for (Plugin plugin : buildTarget.getPluginManagement().getPlugins()) {
329                     rewriteArtifactVersions(
330                             toMavenCoordinates(plugin.getDependencies()),
331                             model,
332                             properties,
333                             result,
334                             releaseDescriptor,
335                             simulate);
336                 }
337             }
338         }
339 
340         for (Profile profile : modelTarget.getProfiles()) {
341             BuildBase profileBuild = profile.getBuild();
342             if (profileBuild != null) {
343                 rewriteArtifactVersions(
344                         toMavenCoordinates(profileBuild.getPlugins()),
345                         model,
346                         properties,
347                         result,
348                         releaseDescriptor,
349                         simulate);
350 
351                 for (Plugin plugin : profileBuild.getPlugins()) {
352                     rewriteArtifactVersions(
353                             toMavenCoordinates(plugin.getDependencies()),
354                             model,
355                             properties,
356                             result,
357                             releaseDescriptor,
358                             simulate);
359                 }
360 
361                 if (profileBuild.getPluginManagement() != null) {
362                     rewriteArtifactVersions(
363                             toMavenCoordinates(
364                                     profileBuild.getPluginManagement().getPlugins()),
365                             model,
366                             properties,
367                             result,
368                             releaseDescriptor,
369                             simulate);
370 
371                     for (Plugin plugin : profileBuild.getPluginManagement().getPlugins()) {
372                         rewriteArtifactVersions(
373                                 toMavenCoordinates(plugin.getDependencies()),
374                                 model,
375                                 properties,
376                                 result,
377                                 releaseDescriptor,
378                                 simulate);
379                     }
380                 }
381             }
382         }
383 
384         List<ModelBase> modelBases = new ArrayList<>();
385         modelBases.add(modelTarget);
386         modelBases.addAll(modelTarget.getProfiles());
387 
388         for (ModelBase modelBase : modelBases) {
389             rewriteArtifactVersions(
390                     toMavenCoordinates(modelBase.getDependencies()),
391                     model,
392                     properties,
393                     result,
394                     releaseDescriptor,
395                     simulate);
396 
397             if (modelBase.getDependencyManagement() != null) {
398                 rewriteArtifactVersions(
399                         toMavenCoordinates(modelBase.getDependencyManagement().getDependencies()),
400                         model,
401                         properties,
402                         result,
403                         releaseDescriptor,
404                         simulate);
405             }
406 
407             if (modelBase.getReporting() != null) {
408                 rewriteArtifactVersions(
409                         toMavenCoordinates(modelBase.getReporting().getPlugins()),
410                         model,
411                         properties,
412                         result,
413                         releaseDescriptor,
414                         simulate);
415             }
416         }
417 
418         transformScm(project, modelTarget, releaseDescriptor, projectId, scmRepository, result);
419 
420         if (properties != null) {
421             rewriteBuildOutputTimestampProperty(properties, result);
422         }
423     }
424 
425     private void rewriteBuildOutputTimestampProperty(Properties properties, ReleaseResult result) {
426         String buildOutputTimestamp = properties.getProperty("project.build.outputTimestamp");
427         if (buildOutputTimestamp == null || (buildOutputTimestamp == null || buildOutputTimestamp.isEmpty())) {
428             
429             return;
430         }
431 
432         if (StringUtils.isNumeric(buildOutputTimestamp)) {
433             
434             buildOutputTimestamp = String.valueOf(result.getStartTime() / 1000);
435         } else if (buildOutputTimestamp.length() <= 1) {
436             
437             return;
438         } else {
439             
440             DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
441             df.setTimeZone(TimeZone.getTimeZone("UTC"));
442             buildOutputTimestamp = df.format(new Date(result.getStartTime()));
443         }
444         properties.setProperty("project.build.outputTimestamp", buildOutputTimestamp);
445     }
446 
447     private void rewriteVersion(
448             Model modelTarget, ReleaseDescriptor releaseDescriptor, String projectId, MavenProject project)
449             throws ReleaseFailureException {
450         String version = getNextVersion(releaseDescriptor, projectId);
451         if (version == null) {
452             throw new ReleaseFailureException("Version for '" + project.getName() + "' was not mapped");
453         }
454 
455         modelTarget.setVersion(version);
456     }
457 
458     
459 
460 
461 
462 
463     public static String extractPropertyFromExpression(String expression) {
464         Matcher matcher = EXPRESSION_PATTERN.matcher(expression);
465         if (!matcher.matches()) {
466             return null;
467         }
468         return matcher.group(1);
469     }
470 
471     public static boolean isCiFriendlyVersion(String version) {
472         return CI_FRIENDLY_PROPERTIES.contains(extractPropertyFromExpression(version));
473     }
474 
475     private String rewriteParent(
476             MavenProject project,
477             Model targetModel,
478             ReleaseResult result,
479             ReleaseDescriptor releaseDescriptor,
480             boolean simulate)
481             throws ReleaseFailureException {
482         String parentVersion = null;
483         if (project.hasParent()) {
484             MavenProject parent = project.getParent();
485             String key = ArtifactUtils.versionlessKey(parent.getGroupId(), parent.getArtifactId());
486             parentVersion = getNextVersion(releaseDescriptor, key);
487             if (parentVersion == null) {
488                 
489                 parentVersion = getResolvedSnapshotVersion(key, releaseDescriptor);
490             }
491             if (parentVersion == null) {
492                 String original = getOriginalVersion(releaseDescriptor, key, simulate);
493                 if (parent.getVersion().equals(original)) {
494                     throw new ReleaseFailureException("Version for parent '" + parent.getName() + "' was not mapped");
495                 }
496             } else {
497                 if (!isCiFriendlyVersion(targetModel.getParent().getVersion())) {
498                     targetModel.getParent().setVersion(parentVersion);
499                 } else {
500                     logInfo(
501                             result,
502                             "  Ignoring parent version update for CI friendly expression " + parent.getVersion());
503                 }
504             }
505         }
506         return parentVersion;
507     }
508 
509     private void rewriteArtifactVersions(
510             Collection<MavenCoordinate> elements,
511             Model projectModel,
512             Properties properties,
513             ReleaseResult result,
514             ReleaseDescriptor releaseDescriptor,
515             boolean simulate)
516             throws ReleaseExecutionException, ReleaseFailureException {
517         if (elements == null) {
518             return;
519         }
520         String projectId = ArtifactUtils.versionlessKey(projectModel.getGroupId(), projectModel.getArtifactId());
521         for (MavenCoordinate coordinate : elements) {
522             String rawVersion = coordinate.getVersion();
523             if (rawVersion == null) {
524                 
525                 continue;
526             }
527 
528             String rawGroupId = coordinate.getGroupId();
529             if (rawGroupId == null) {
530                 if ("plugin".equals(coordinate.getName())) {
531                     rawGroupId = "org.apache.maven.plugins";
532                 } else {
533                     
534                     continue;
535                 }
536             }
537             String groupId = ReleaseUtil.interpolate(rawGroupId, projectModel);
538 
539             String rawArtifactId = coordinate.getArtifactId();
540             if (rawArtifactId == null) {
541                 
542                 continue;
543             }
544             String artifactId = ReleaseUtil.interpolate(rawArtifactId, projectModel);
545 
546             String key = ArtifactUtils.versionlessKey(groupId, artifactId);
547             String resolvedSnapshotVersion = getResolvedSnapshotVersion(key, releaseDescriptor);
548             String mappedVersion = getNextVersion(releaseDescriptor, key);
549             String originalVersion = getOriginalVersion(releaseDescriptor, key, simulate);
550             if (originalVersion == null) {
551                 originalVersion = getOriginalResolvedSnapshotVersion(key, releaseDescriptor);
552             }
553 
554             
555             if (mappedVersion != null
556                     && mappedVersion.endsWith(Artifact.SNAPSHOT_VERSION)
557                     && !rawVersion.endsWith(Artifact.SNAPSHOT_VERSION)
558                     && !releaseDescriptor.isUpdateDependencies()) {
559                 continue;
560             }
561 
562             if (mappedVersion != null) {
563                 if (rawVersion.equals(originalVersion)) {
564                     logInfo(result, "  Updating " + artifactId + " to " + mappedVersion);
565                     coordinate.setVersion(mappedVersion);
566                 } else {
567                     String property = extractPropertyFromExpression(rawVersion);
568                     if (property != null) {
569                         if (property.startsWith("project.")
570                                 || property.startsWith("pom.")
571                                 || "version".equals(property)) {
572                             if (!mappedVersion.equals(getNextVersion(releaseDescriptor, projectId))) {
573                                 logInfo(result, "  Updating " + artifactId + " to " + mappedVersion);
574                                 coordinate.setVersion(mappedVersion);
575                             } else {
576                                 logInfo(result, "  Ignoring artifact version update for expression " + rawVersion);
577                             }
578                         } else if (properties != null) {
579                             
580                             String propertyValue = properties.getProperty(property);
581                             if (propertyValue != null) {
582                                 if (propertyValue.equals(originalVersion)) {
583                                     logInfo(result, "  Updating " + rawVersion + " to " + mappedVersion);
584                                     
585                                     properties.setProperty(property, mappedVersion);
586                                 } else if (mappedVersion.equals(propertyValue)) {
587                                     
588                                     logInfo(
589                                             result,
590                                             "  Ignoring artifact version update for expression " + rawVersion
591                                                     + " because it is already updated");
592                                 } else if (!mappedVersion.equals(rawVersion)) {
593                                     
594                                     
595                                     
596                                     if (mappedVersion.matches("\\$\\{project.+\\}")
597                                             || mappedVersion.matches("\\$\\{pom.+\\}")
598                                             || "${version}".equals(mappedVersion)) {
599                                         logInfo(
600                                                 result,
601                                                 "  Ignoring artifact version update for expression " + mappedVersion);
602                                         
603                                     } else {
604                                         
605                                         throw new ReleaseFailureException("The artifact (" + key + ") requires a "
606                                                 + "different version (" + mappedVersion + ") than what is found ("
607                                                 + propertyValue + ") for the expression (" + rawVersion + ") in the "
608                                                 + "project (" + projectId + ").");
609                                     }
610                                 }
611                             } else {
612                                 if (CI_FRIENDLY_PROPERTIES.contains(property)) {
613                                     logInfo(
614                                             result,
615                                             "  Ignoring artifact version update for CI friendly expression "
616                                                     + rawVersion);
617                                 } else {
618                                     
619                                     
620                                     throw new ReleaseFailureException(
621                                             "Could not find property resolving version expression: " + rawVersion);
622                                 }
623                             }
624                         } else {
625                             
626                             
627                             throw new ReleaseFailureException(
628                                     "Could not find properties resolving version expression : " + rawVersion);
629                         }
630                     } else {
631                         
632                     }
633                 }
634             } else if (resolvedSnapshotVersion != null) {
635                 logInfo(result, "  Updating " + artifactId + " to " + resolvedSnapshotVersion);
636 
637                 coordinate.setVersion(resolvedSnapshotVersion);
638             } else {
639                 
640             }
641         }
642     }
643 
644     private void prepareScm(
645             File pomFile, ReleaseDescriptor releaseDescriptor, ScmRepository repository, ScmProvider provider)
646             throws ReleaseExecutionException, ReleaseScmCommandException {
647         try {
648             if (isUpdateScm() && (releaseDescriptor.isScmUseEditMode() || provider.requiresEditMode())) {
649                 EditScmResult result = provider.edit(
650                         repository, new ScmFileSet(new File(releaseDescriptor.getWorkingDirectory()), pomFile));
651 
652                 if (!result.isSuccess()) {
653                     throw new ReleaseScmCommandException("Unable to enable editing on the POM", result);
654                 }
655             }
656         } catch (ScmException e) {
657             throw new ReleaseExecutionException("An error occurred enabling edit mode: " + e.getMessage(), e);
658         }
659     }
660 
661     
662 
663 
664 
665 
666 
667 
668     protected abstract String getResolvedSnapshotVersion(
669             String artifactVersionlessKey, ReleaseDescriptor releaseDscriptor);
670 
671     
672 
673 
674 
675 
676 
677 
678 
679     protected abstract String getOriginalVersion(
680             ReleaseDescriptor releaseDescriptor, String projectKey, boolean simulate);
681 
682     
683 
684 
685 
686 
687 
688 
689     protected abstract String getNextVersion(ReleaseDescriptor releaseDescriptor, String key);
690 
691     
692 
693 
694 
695 
696 
697 
698 
699 
700 
701 
702     protected abstract void transformScm(
703             MavenProject project,
704             Model modelTarget,
705             ReleaseDescriptor releaseDescriptor,
706             String projectId,
707             ScmRepository scmRepository,
708             ReleaseResult result)
709             throws ReleaseExecutionException;
710 
711     
712 
713 
714 
715 
716 
717     protected boolean isUpdateScm() {
718         return true;
719     }
720 
721     
722 
723 
724 
725 
726 
727 
728     protected String getOriginalResolvedSnapshotVersion(
729             String artifactVersionlessKey, ReleaseDescriptor releaseDescriptor) {
730         return releaseDescriptor.getDependencyOriginalVersion(artifactVersionlessKey);
731     }
732 
733     
734 
735 
736 
737 
738 
739 
740 
741 
742     protected static String translateUrlPath(String trunkPath, String tagPath, String urlPath) {
743         trunkPath = trunkPath.trim();
744         tagPath = tagPath.trim();
745         
746         if (trunkPath.endsWith("/")) {
747             trunkPath = trunkPath.substring(0, trunkPath.length() - 1);
748         }
749         if (tagPath.endsWith("/")) {
750             tagPath = tagPath.substring(0, tagPath.length() - 1);
751         }
752         char[] tagPathChars = trunkPath.toCharArray();
753         char[] trunkPathChars = tagPath.toCharArray();
754         
755         int i = 0;
756         while ((i < tagPathChars.length) && (i < trunkPathChars.length) && tagPathChars[i] == trunkPathChars[i]) {
757             ++i;
758         }
759         
760         
761         if (i == 0 || urlPath.indexOf(trunkPath.substring(i)) < 0) {
762             return tagPath;
763         } else {
764             return StringUtils.replace(urlPath, trunkPath.substring(i), tagPath.substring(i));
765         }
766     }
767 
768     private Collection<MavenCoordinate> toMavenCoordinates(List<?> objects) {
769         Collection<MavenCoordinate> coordinates = new ArrayList<>(objects.size());
770         for (Object object : objects) {
771             if (object instanceof MavenCoordinate) {
772                 coordinates.add((MavenCoordinate) object);
773             } else {
774                 throw new UnsupportedOperationException();
775             }
776         }
777         return coordinates;
778     }
779 }