View Javadoc
1   package org.apache.maven.archiver;
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.artifact.Artifact;
23  import org.apache.maven.artifact.DependencyResolutionRequiredException;
24  import org.apache.maven.artifact.versioning.ArtifactVersion;
25  import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
26  import org.apache.maven.execution.MavenSession;
27  import org.apache.maven.project.MavenProject;
28  import org.codehaus.plexus.archiver.jar.JarArchiver;
29  import org.codehaus.plexus.archiver.jar.Manifest;
30  import org.codehaus.plexus.archiver.jar.ManifestException;
31  import org.codehaus.plexus.interpolation.InterpolationException;
32  import org.codehaus.plexus.interpolation.Interpolator;
33  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
34  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
35  import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource;
36  import org.codehaus.plexus.interpolation.RecursionInterceptor;
37  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
38  import org.codehaus.plexus.interpolation.ValueSource;
39  import org.apache.maven.shared.utils.StringUtils;
40  
41  import java.io.File;
42  import java.io.IOException;
43  import java.util.ArrayList;
44  import java.util.Collections;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.Properties;
48  import java.util.Set;
49  
50  /**
51   * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
52   * @author kama
53   * @version $Revision: 1741255 $ $Date: 2016-04-27 15:14:30 +0200 (Wed, 27 Apr 2016) $
54   */
55  public class MavenArchiver
56  {
57  
58      /**
59       * The simply layout.
60       */
61      public static final String SIMPLE_LAYOUT =
62          "${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}";
63  
64      /**
65       * Repository layout.
66       */
67      public static final String REPOSITORY_LAYOUT =
68          "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
69              + "${artifact.version}${dashClassifier?}.${artifact.extension}";
70  
71      /**
72       * simple layout non unique.
73       */
74      public static final String SIMPLE_LAYOUT_NONUNIQUE =
75          "${artifact.artifactId}-${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
76  
77      /**
78       * Repository layout non unique.
79       */
80      public static final String REPOSITORY_LAYOUT_NONUNIQUE =
81          "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
82              + "${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
83  
84      private static final List<String> ARTIFACT_EXPRESSION_PREFIXES;
85  
86      static
87      {
88          List<String> artifactExpressionPrefixes = new ArrayList<String>();
89          artifactExpressionPrefixes.add( "artifact." );
90  
91          ARTIFACT_EXPRESSION_PREFIXES = artifactExpressionPrefixes;
92      }
93  
94      private JarArchiver archiver;
95  
96      private File archiveFile;
97  
98      /**
99       * @param session The Maven Session.
100      * @param project The Maven Project.
101      * @param config The MavenArchiveConfiguration
102      * @return The {@link Manifest}
103      * @throws ManifestException In case of a failure.
104      * @throws DependencyResolutionRequiredException Resolution failure.
105      */
106     public Manifest getManifest( MavenSession session, MavenProject project, MavenArchiveConfiguration config )
107         throws ManifestException, DependencyResolutionRequiredException
108     {
109         boolean hasManifestEntries = !config.isManifestEntriesEmpty();
110         Map<String, String> entries =
111             hasManifestEntries ? config.getManifestEntries() : Collections.<String, String>emptyMap();
112 
113         Manifest manifest = getManifest( session, project, config.getManifest(), entries );
114 
115         // any custom manifest entries in the archive configuration manifest?
116         if ( hasManifestEntries )
117         {
118 
119             for ( Map.Entry<String, String> entry : entries.entrySet() )
120             {
121                 String key = entry.getKey();
122                 String value = entry.getValue();
123                 Manifest.Attribute attr = manifest.getMainSection().getAttribute( key );
124                 if ( key.equals( "Class-Path" ) && attr != null )
125                 {
126                     // Merge the user-supplied Class-Path value with the programmatically
127                     // created Class-Path. Note that the user-supplied value goes first
128                     // so that resources there will override any in the standard Class-Path.
129                     attr.setValue( value + " " + attr.getValue() );
130                 }
131                 else
132                 {
133                     addManifestAttribute( manifest, key, value );
134                 }
135             }
136         }
137 
138         // any custom manifest sections in the archive configuration manifest?
139         if ( !config.isManifestSectionsEmpty() )
140         {
141             for ( ManifestSection section : config.getManifestSections() )
142             {
143                 Manifest.Section theSection = new Manifest.Section();
144                 theSection.setName( section.getName() );
145 
146                 if ( !section.isManifestEntriesEmpty() )
147                 {
148                     Map<String, String> sectionEntries = section.getManifestEntries();
149 
150                     for ( Map.Entry<String, String> entry : sectionEntries.entrySet() )
151                     {
152                         String key = entry.getKey();
153                         String value = entry.getValue();
154                         Manifest.Attribute attr = new Manifest.Attribute( key, value );
155                         theSection.addConfiguredAttribute( attr );
156                     }
157                 }
158 
159                 manifest.addConfiguredSection( theSection );
160             }
161         }
162 
163         return manifest;
164     }
165 
166     /**
167      * Return a pre-configured manifest
168      *
169      * @param project {@link MavenProject}
170      * @param config {@link ManifestConfiguration}
171      * @return {@link Manifest}
172      * @throws ManifestException Manifest exception.
173      * @throws DependencyResolutionRequiredException Dependency resolution exception.
174      */
175     // TODO Add user attributes list and user groups list
176     public Manifest getManifest( MavenProject project, ManifestConfiguration config )
177         throws ManifestException, DependencyResolutionRequiredException
178     {
179         return getManifest( null, project, config, Collections.<String, String>emptyMap() );
180     }
181 
182     /**
183      * @param mavenSession {@link MavenSession}
184      * @param project {@link MavenProject}
185      * @param config {@link ManifestConfiguration}
186      * @return {@link Manifest}
187      * @throws ManifestException The manifest exception.
188      * @throws DependencyResolutionRequiredException The dependency resolution required exception.
189      */
190     public Manifest getManifest( MavenSession mavenSession, MavenProject project, ManifestConfiguration config )
191         throws ManifestException, DependencyResolutionRequiredException
192     {
193         return getManifest( mavenSession, project, config, Collections.<String, String>emptyMap() );
194     }
195 
196     private void addManifestAttribute( Manifest manifest, Map<String, String> map, String key, String value )
197         throws ManifestException
198     {
199         if ( map.containsKey( key ) )
200         {
201             return; // The map value will be added later
202         }
203         addManifestAttribute( manifest, key, value );
204     }
205 
206     private void addManifestAttribute( Manifest manifest, String key, String value )
207         throws ManifestException
208     {
209         if ( !StringUtils.isEmpty( value ) )
210         {
211             Manifest.Attribute attr = new Manifest.Attribute( key, value );
212             manifest.addConfiguredAttribute( attr );
213         }
214         else
215         {
216             // if the value is empty we have create an entry with an empty string
217             // to prevent null print in the manifest file
218             Manifest.Attribute attr = new Manifest.Attribute( key, "" );
219             manifest.addConfiguredAttribute( attr );
220         }
221     }
222 
223     /**
224      * @param session {@link MavenSession}
225      * @param project {@link MavenProject}
226      * @param config {@link ManifestConfiguration}
227      * @param entries The entries.
228      * @return {@link Manifest}
229      * @throws ManifestException The manifest exception.
230      * @throws DependencyResolutionRequiredException The dependency resolution required exception.
231      */
232     protected Manifest getManifest( MavenSession session, MavenProject project, ManifestConfiguration config,
233                                     Map<String, String> entries )
234                                         throws ManifestException, DependencyResolutionRequiredException
235     {
236         // TODO: Should we replace "map" with a copy? Note, that we modify it!
237 
238         // Added basic entries
239         Manifest m = new Manifest();
240         addCreatedByEntry( session, m, entries );
241 
242         addCustomEntries( m, entries, config );
243 
244         if ( config.isAddClasspath() )
245         {
246             StringBuilder classpath = new StringBuilder();
247 
248             List<String> artifacts = project.getRuntimeClasspathElements();
249             String classpathPrefix = config.getClasspathPrefix();
250             String layoutType = config.getClasspathLayoutType();
251             String layout = config.getCustomClasspathLayout();
252 
253             Interpolator interpolator = new StringSearchInterpolator();
254 
255             for ( String artifactFile : artifacts )
256             {
257                 File f = new File( artifactFile );
258                 if ( f.getAbsoluteFile().isFile() )
259                 {
260                     Artifact artifact = findArtifactWithFile( project.getArtifacts(), f );
261 
262                     if ( classpath.length() > 0 )
263                     {
264                         classpath.append( " " );
265                     }
266                     classpath.append( classpathPrefix );
267 
268                     // NOTE: If the artifact or layout type (from config) is null, give up and use the file name by
269                     // itself.
270                     if ( artifact == null || layoutType == null )
271                     {
272                         classpath.append( f.getName() );
273                     }
274                     else
275                     {
276                         List<ValueSource> valueSources = new ArrayList<ValueSource>();
277 
278                         handleExtraExpression( artifact, valueSources );
279 
280                         for ( ValueSource vs : valueSources )
281                         {
282                             interpolator.addValueSource( vs );
283                         }
284 
285                         RecursionInterceptor recursionInterceptor =
286                             new PrefixAwareRecursionInterceptor( ARTIFACT_EXPRESSION_PREFIXES );
287 
288                         try
289                         {
290                             if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_SIMPLE.equals( layoutType ) )
291                             {
292                                 if ( config.isUseUniqueVersions() )
293                                 {
294                                     classpath.append( interpolator.interpolate( SIMPLE_LAYOUT, recursionInterceptor ) );
295                                 }
296                                 else
297                                 {
298                                     classpath.append( interpolator.interpolate( SIMPLE_LAYOUT_NONUNIQUE,
299                                                                                 recursionInterceptor ) );
300                                 }
301                             }
302                             else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_REPOSITORY.equals( layoutType ) )
303                             {
304                                 // we use layout /$groupId[0]/../${groupId[n]/$artifactId/$version/{fileName}
305                                 // here we must find the Artifact in the project Artifacts to create the maven layout
306                                 if ( config.isUseUniqueVersions() )
307                                 {
308                                     classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT,
309                                                                                 recursionInterceptor ) );
310                                 }
311                                 else
312                                 {
313                                     classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT_NONUNIQUE,
314                                                                                 recursionInterceptor ) );
315                                 }
316                             }
317                             else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM.equals( layoutType ) )
318                             {
319                                 if ( layout == null )
320                                 {
321                                     throw new ManifestException( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM
322                                         + " layout type was declared, but custom layout expression was not"
323                                         + " specified. Check your <archive><manifest><customLayout/> element." );
324                                 }
325 
326                                 classpath.append( interpolator.interpolate( layout, recursionInterceptor ) );
327                             }
328                             else
329                             {
330                                 throw new ManifestException( "Unknown classpath layout type: '" + layoutType
331                                     + "'. Check your <archive><manifest><layoutType/> element." );
332                             }
333                         }
334                         catch ( InterpolationException e )
335                         {
336                             ManifestException error =
337                                 new ManifestException( "Error interpolating artifact path for classpath entry: "
338                                     + e.getMessage() );
339 
340                             error.initCause( e );
341                             throw error;
342                         }
343                         finally
344                         {
345                             for ( ValueSource vs : valueSources )
346                             {
347                                 interpolator.removeValuesSource( vs );
348                             }
349                         }
350                     }
351                 }
352             }
353 
354             if ( classpath.length() > 0 )
355             {
356                 // Class-Path is special and should be added to manifest even if
357                 // it is specified in the manifestEntries section
358                 addManifestAttribute( m, "Class-Path", classpath.toString() );
359             }
360         }
361 
362         if ( config.isAddDefaultSpecificationEntries() )
363         {
364             handleSpecificationEntries( project, entries, m );
365         }
366 
367         if ( config.isAddDefaultImplementationEntries() )
368         {
369             handleImplementationEntries( project, entries, m );
370         }
371 
372         String mainClass = config.getMainClass();
373         if ( mainClass != null && !"".equals( mainClass ) )
374         {
375             addManifestAttribute( m, entries, "Main-Class", mainClass );
376         }
377 
378         if ( config.isAddExtensions() )
379         {
380             handleExtensions( project, entries, m );
381         }
382 
383         return m;
384     }
385 
386     private void handleExtraExpression( Artifact artifact, List<ValueSource> valueSources )
387     {
388         valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES, artifact,
389                                                          true ) );
390         valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES,
391                                                          artifact.getArtifactHandler(), true ) );
392 
393         Properties extraExpressions = new Properties();
394         // FIXME: This query method SHOULD NOT affect the internal
395         // state of the artifact version, but it does.
396         if ( !artifact.isSnapshot() )
397         {
398             extraExpressions.setProperty( "baseVersion", artifact.getVersion() );
399         }
400 
401         extraExpressions.setProperty( "groupIdPath", artifact.getGroupId().replace( '.', '/' ) );
402         if ( StringUtils.isNotEmpty( artifact.getClassifier() ) )
403         {
404             extraExpressions.setProperty( "dashClassifier", "-" + artifact.getClassifier() );
405             extraExpressions.setProperty( "dashClassifier?", "-" + artifact.getClassifier() );
406         }
407         else
408         {
409             extraExpressions.setProperty( "dashClassifier", "" );
410             extraExpressions.setProperty( "dashClassifier?", "" );
411         }
412         valueSources.add( new PrefixedPropertiesValueSource( ARTIFACT_EXPRESSION_PREFIXES,
413                                                              extraExpressions, true ) );
414     }
415 
416     private void handleExtensions( MavenProject project, Map<String, String> entries, Manifest m )
417         throws ManifestException
418     {
419         // TODO: this is only for applets - should we distinguish them as a packaging?
420         StringBuilder extensionsList = new StringBuilder();
421         Set<Artifact> artifacts = (Set<Artifact>) project.getArtifacts();
422 
423         for ( Artifact artifact : artifacts )
424         {
425             if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
426             {
427                 if ( "jar".equals( artifact.getType() ) )
428                 {
429                     if ( extensionsList.length() > 0 )
430                     {
431                         extensionsList.append( " " );
432                     }
433                     extensionsList.append( artifact.getArtifactId() );
434                 }
435             }
436         }
437 
438         if ( extensionsList.length() > 0 )
439         {
440             addManifestAttribute( m, entries, "Extension-List", extensionsList.toString() );
441         }
442 
443         for ( Object artifact1 : artifacts )
444         {
445             // TODO: the correct solution here would be to have an extension type, and to read
446             // the real extension values either from the artifact's manifest or some part of the POM
447             Artifact artifact = (Artifact) artifact1;
448             if ( "jar".equals( artifact.getType() ) )
449             {
450                 String artifactId = artifact.getArtifactId().replace( '.', '_' );
451                 String ename = artifactId + "-Extension-Name";
452                 addManifestAttribute( m, entries, ename, artifact.getArtifactId() );
453                 String iname = artifactId + "-Implementation-Version";
454                 addManifestAttribute( m, entries, iname, artifact.getVersion() );
455 
456                 if ( artifact.getRepository() != null )
457                 {
458                     iname = artifactId + "-Implementation-URL";
459                     String url = artifact.getRepository().getUrl() + "/" + artifact.toString();
460                     addManifestAttribute( m, entries, iname, url );
461                 }
462             }
463         }
464     }
465 
466     private void handleImplementationEntries( MavenProject project, Map<String, String> entries, Manifest m )
467         throws ManifestException
468     {
469         addManifestAttribute( m, entries, "Implementation-Title", project.getName() );
470         addManifestAttribute( m, entries, "Implementation-Version", project.getVersion() );
471         // MJAR-5
472         addManifestAttribute( m, entries, "Implementation-Vendor-Id", project.getGroupId() );
473 
474         if ( project.getOrganization() != null )
475         {
476             addManifestAttribute( m, entries, "Implementation-Vendor", project.getOrganization().getName() );
477         }
478 
479         if ( project.getUrl() != null )
480         {
481             addManifestAttribute( m, entries, "Implementation-URL", project.getUrl() );
482         }
483     }
484 
485     private void handleSpecificationEntries( MavenProject project, Map<String, String> entries, Manifest m )
486         throws ManifestException
487     {
488         addManifestAttribute( m, entries, "Specification-Title", project.getName() );
489 
490         try
491         {
492             ArtifactVersion version = project.getArtifact().getSelectedVersion();
493             String specVersion = String.format( "%s.%s", version.getMajorVersion(), version.getMinorVersion() );
494             addManifestAttribute( m, entries, "Specification-Version", specVersion );
495         }
496         catch ( OverConstrainedVersionException e )
497         {
498             throw new ManifestException( "Failed to get selected artifact version to calculate"
499                 + " the specification version: " + e.getMessage() );
500         }
501 
502         if ( project.getOrganization() != null )
503         {
504             addManifestAttribute( m, entries, "Specification-Vendor", project.getOrganization().getName() );
505         }
506     }
507 
508     private void addCustomEntries( Manifest m, Map<String, String> entries, ManifestConfiguration config )
509         throws ManifestException
510     {
511         addManifestAttribute( m, entries, "Built-By", System.getProperty( "user.name" ) );
512         addManifestAttribute( m, entries, "Build-Jdk", System.getProperty( "java.version" ) );
513 
514         /*
515          * TODO: rethink this, it wasn't working Artifact projectArtifact = project.getArtifact(); if (
516          * projectArtifact.isSnapshot() ) { Manifest.Attribute buildNumberAttr = new Manifest.Attribute( "Build-Number",
517          * "" + project.getSnapshotDeploymentBuildNumber() ); m.addConfiguredAttribute( buildNumberAttr ); }
518          */
519         if ( config.getPackageName() != null )
520         {
521             addManifestAttribute( m, entries, "Package", config.getPackageName() );
522         }
523     }
524 
525     /**
526      * @return {@link JarArchiver}
527      */
528     public JarArchiver getArchiver()
529     {
530         return archiver;
531     }
532 
533     /**
534      * @param archiver {@link JarArchiver}
535      */
536     public void setArchiver( JarArchiver archiver )
537     {
538         this.archiver = archiver;
539     }
540 
541     /**
542      * @param outputFile Set output file.
543      */
544     public void setOutputFile( File outputFile )
545     {
546         archiveFile = outputFile;
547     }
548 
549     /**
550      * @param session {@link MavenSession}
551      * @param project {@link MavenProject}
552      * @param archiveConfiguration {@link MavenArchiveConfiguration}
553      * @throws org.codehaus.plexus.archiver.ArchiverException Archiver Exception.
554      * @throws ManifestException Manifest Exception.
555      * @throws IOException IO Exception.
556      * @throws DependencyResolutionRequiredException Dependency resolution exception.
557      */
558     public void createArchive( MavenSession session, MavenProject project,
559                                MavenArchiveConfiguration archiveConfiguration )
560                                    throws ManifestException, IOException,
561                                    DependencyResolutionRequiredException
562     {
563         // we have to clone the project instance so we can write out the pom with the deployment version,
564         // without impacting the main project instance...
565         MavenProject workingProject = null;
566         workingProject = (MavenProject) project.clone();
567 
568         boolean forced = archiveConfiguration.isForced();
569         if ( archiveConfiguration.isAddMavenDescriptor() )
570         {
571             // ----------------------------------------------------------------------
572             // We want to add the metadata for the project to the JAR in two forms:
573             //
574             // The first form is that of the POM itself. Applications that wish to
575             // access the POM for an artifact using maven tools they can.
576             //
577             // The second form is that of a properties file containing the basic
578             // top-level POM elements so that applications that wish to access
579             // POM information without the use of maven tools can do so.
580             // ----------------------------------------------------------------------
581 
582             if ( workingProject.getArtifact().isSnapshot() )
583             {
584                 workingProject.setVersion( workingProject.getArtifact().getVersion() );
585             }
586 
587             String groupId = workingProject.getGroupId();
588 
589             String artifactId = workingProject.getArtifactId();
590 
591             archiver.addFile( project.getFile(), "META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml" );
592 
593             // ----------------------------------------------------------------------
594             // Create pom.properties file
595             // ----------------------------------------------------------------------
596 
597             File customPomPropertiesFile = archiveConfiguration.getPomPropertiesFile();
598             File dir = new File( workingProject.getBuild().getDirectory(), "maven-archiver" );
599             File pomPropertiesFile = new File( dir, "pom.properties" );
600 
601             new PomPropertiesUtil().createPomProperties( session, workingProject, archiver,
602                 customPomPropertiesFile, pomPropertiesFile, forced );
603         }
604 
605         // ----------------------------------------------------------------------
606         // Create the manifest
607         // ----------------------------------------------------------------------
608 
609         File manifestFile = archiveConfiguration.getManifestFile();
610 
611         if ( manifestFile != null )
612         {
613             archiver.setManifest( manifestFile );
614         }
615 
616         Manifest manifest = getManifest( session, workingProject, archiveConfiguration );
617 
618         // Configure the jar
619         archiver.addConfiguredManifest( manifest );
620 
621         archiver.setCompress( archiveConfiguration.isCompress() );
622 
623         archiver.setRecompressAddedZips( archiveConfiguration.isRecompressAddedZips() );
624 
625         archiver.setIndex( archiveConfiguration.isIndex() );
626 
627         archiver.setDestFile( archiveFile );
628 
629         // make the archiver index the jars on the classpath, if we are adding that to the manifest
630         if ( archiveConfiguration.getManifest().isAddClasspath() )
631         {
632             List<String> artifacts = project.getRuntimeClasspathElements();
633             for ( String artifact : artifacts )
634             {
635                 File f = new File( artifact );
636                 archiver.addConfiguredIndexJars( f );
637             }
638         }
639 
640         archiver.setForced( forced );
641         if ( !archiveConfiguration.isForced() && archiver.isSupportingForced() )
642         {
643             // TODO Should issue a warning here, but how do we get a logger?
644             // TODO getLog().warn(
645             // "Forced build is disabled, but disabling the forced mode isn't supported by the archiver." );
646         }
647 
648         // create archive
649         archiver.createArchive();
650     }
651 
652     private void addCreatedByEntry( MavenSession session, Manifest m, Map<String, String> entries )
653         throws ManifestException
654     {
655         String createdBy = "Apache Maven";
656         if ( session != null ) // can be null due to API backwards compatibility
657         {
658             String mavenVersion = session.getSystemProperties().getProperty( "maven.version" );
659             if ( mavenVersion != null )
660             {
661                 createdBy += " " + mavenVersion;
662             }
663         }
664         addManifestAttribute( m, entries, "Created-By", createdBy );
665     }
666 
667     private Artifact findArtifactWithFile( Set<Artifact> artifacts, File file )
668     {
669         for ( Artifact artifact : artifacts )
670         {
671             // normally not null but we can check
672             if ( artifact.getFile() != null )
673             {
674                 if ( artifact.getFile().equals( file ) )
675                 {
676                     return artifact;
677                 }
678             }
679         }
680         return null;
681     }
682 }