Coverage Report - com.jcabi.aether.Classpath
 
Classes in this File Line Coverage Branch Coverage Complexity
Classpath
79%
65/82
48%
35/72
3.769
Classpath$AjcClosure1
100%
1/1
N/A
3.769
Classpath$AjcClosure3
0%
0/1
N/A
3.769
 
 1  2
 /**
 2  
  * Copyright (c) 2012-2014, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package com.jcabi.aether;
 31  
 
 32  
 import com.jcabi.aspects.Loggable;
 33  
 import java.io.File;
 34  
 import java.util.AbstractSet;
 35  
 import java.util.Arrays;
 36  
 import java.util.Collection;
 37  
 import java.util.HashSet;
 38  
 import java.util.Iterator;
 39  
 import java.util.LinkedHashSet;
 40  
 import java.util.LinkedList;
 41  
 import java.util.Set;
 42  
 import java.util.concurrent.TimeUnit;
 43  
 import javax.validation.constraints.NotNull;
 44  
 import lombok.EqualsAndHashCode;
 45  
 import org.apache.commons.lang3.StringUtils;
 46  
 import org.apache.maven.artifact.DependencyResolutionRequiredException;
 47  
 import org.apache.maven.model.Dependency;
 48  
 import org.apache.maven.project.MavenProject;
 49  
 import org.sonatype.aether.artifact.Artifact;
 50  
 import org.sonatype.aether.resolution.DependencyResolutionException;
 51  
 import org.sonatype.aether.util.artifact.DefaultArtifact;
 52  
 import org.sonatype.aether.util.artifact.JavaScopes;
 53  
 import org.sonatype.aether.util.version.GenericVersionScheme;
 54  
 import org.sonatype.aether.version.InvalidVersionSpecificationException;
 55  
 import org.sonatype.aether.version.VersionScheme;
 56  
 
 57  
 /**
 58  
  * A classpath of a Maven Project.
 59  
  *
 60  
  * <p>It is a convenient wrapper around {@link Aether} class, that allows you
 61  
  * to fetch all dependencies of a Maven Project by their scope. The class
 62  
  * implements a {@link Set} of {@link File}s and can be used like this:
 63  
  *
 64  
  * <pre> String classpath = StringUtils.join(
 65  
  *   new Classpath(project, localRepo, "runtime")
 66  
  *   System.getProperty("path.separator")
 67  
  * );</pre>
 68  
  *
 69  
  * <p>Important to notice that this class resolves artifacts from repositories
 70  
  * only once per instance. It means that once resolved the list of files
 71  
  * is cached and never flushes. In order to resolve again (if you think that
 72  
  * content of repositories is changed), make a new instance of the class.
 73  
  *
 74  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 75  
  * @version $Id$
 76  
  * @since 0.7.16
 77  
  * @see Aether
 78  
  * @checkstyle ClassDataAbstractionCoupling (500 lines)
 79  
  */
 80  2
 @EqualsAndHashCode(callSuper = false, of = { "project", "aether", "scopes" })
 81  
 @Loggable(
 82  
     value = Loggable.DEBUG,
 83  
     limit = 1, unit = TimeUnit.MINUTES,
 84  
     trim = false
 85  
 )
 86  
 @SuppressWarnings("PMD.TooManyMethods")
 87  
 public final class Classpath extends AbstractSet<File> {
 88  
 
 89  
     /**
 90  
      * Maven Project.
 91  
      */
 92  
     private final transient MavenProject project;
 93  
 
 94  
     /**
 95  
      * Aether to work with.
 96  
      */
 97  
     private final transient Aether aether;
 98  
 
 99  
     /**
 100  
      * Artifact scopes to include.
 101  
      */
 102  
     private final transient Set<String> scopes;
 103  
 
 104  
     /**
 105  
      * Public ctor.
 106  
      * @param prj The Maven project
 107  
      * @param repo Local repository location (directory path)
 108  
      * @param scp The scope to use, e.g. "runtime" or "compile"
 109  
      */
 110  
     public Classpath(@NotNull final MavenProject prj,
 111  
         @NotNull final File repo, @NotNull final String scp) {
 112  5
         this(prj, repo, Arrays.asList(scp));
 113  5
     }
 114  
 
 115  
     /**
 116  
      * Public ctor.
 117  
      * @param prj The Maven project
 118  
      * @param repo Local repository location (directory path)
 119  
      * @param scps All scopes to include
 120  
      */
 121  
     public Classpath(@NotNull final MavenProject prj,
 122  
         @NotNull final File repo, @NotNull final Collection<String> scps) {
 123  5
         super();
 124  5
         this.project = prj;
 125  5
         this.aether = new Aether(prj, repo);
 126  5
         this.scopes = new HashSet<String>(scps);
 127  5
     }
 128  
 
 129  
     /**
 130  
      * {@inheritDoc}
 131  
      */
 132  
     @Override
 133  
     public String toString() {
 134  1
         return StringUtils.join(this.roots(), "\n");
 135  
     }
 136  
 
 137  
     /**
 138  
      * {@inheritDoc}
 139  
      */
 140  
     @Override
 141  
     public Iterator<File> iterator() {
 142  
         try {
 143  14
             return this.fetch().iterator();
 144  0
         } catch (final DependencyResolutionException ex) {
 145  0
             throw new IllegalStateException(ex);
 146  
         }
 147  
     }
 148  
 
 149  
     /**
 150  
      * {@inheritDoc}
 151  
      */
 152  
     @Override
 153  
     public int size() {
 154  
         try {
 155  0
             return this.fetch().size();
 156  0
         } catch (final DependencyResolutionException ex) {
 157  0
             throw new IllegalStateException(ex);
 158  
         }
 159  
     }
 160  
 
 161  
     /**
 162  
      * Fetch all files found (JAR, ZIP, directories, etc).
 163  
      * @return Set of files
 164  
      * @throws DependencyResolutionException If can't resolve
 165  
      */
 166  
     @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
 167  
     private Set<File> fetch() throws DependencyResolutionException {
 168  7
         final Set<File> files = new LinkedHashSet<File>(0);
 169  7
         for (final String path : this.elements()) {
 170  5
             files.add(new File(path));
 171  5
         }
 172  7
         for (final Artifact artifact : this.artifacts()) {
 173  20
             files.add(artifact.getFile());
 174  20
         }
 175  7
         return files;
 176  
     }
 177  
 
 178  
     /**
 179  
      * Get Maven Project elements.
 180  
      * @return Collection of them
 181  
      */
 182  
     private Collection<String> elements() {
 183  7
         final Collection<String> elements = new LinkedList<String>();
 184  
         try {
 185  7
             if (this.scopes.contains(JavaScopes.TEST)) {
 186  5
                 elements.addAll(this.project.getTestClasspathElements());
 187  
             }
 188  7
             if (this.scopes.contains(JavaScopes.RUNTIME)) {
 189  0
                 elements.addAll(this.project.getRuntimeClasspathElements());
 190  
             }
 191  7
             if (this.scopes.contains(JavaScopes.SYSTEM)) {
 192  0
                 elements.addAll(this.project.getSystemClasspathElements());
 193  
             }
 194  7
             if (this.scopes.contains(JavaScopes.COMPILE)
 195  
                 || this.scopes.contains(JavaScopes.PROVIDED)) {
 196  2
                 elements.addAll(this.project.getCompileClasspathElements());
 197  
             }
 198  0
         } catch (final DependencyResolutionRequiredException ex) {
 199  0
             throw new IllegalStateException("Failed to read classpath", ex);
 200  7
         }
 201  7
         return elements;
 202  
     }
 203  
 
 204  
     /**
 205  
      * Set of unique artifacts, which should be available in classpath.
 206  
      *
 207  
      * <p>This method gets a full list of artifacts of the project,
 208  
      * including their transitive dependencies.
 209  
      *
 210  
      * @return The set of artifacts
 211  
      * @throws DependencyResolutionException If can't resolve some of them
 212  
      */
 213  
     private Set<Artifact> artifacts() throws DependencyResolutionException {
 214  7
         final Set<Artifact> artifacts = new LinkedHashSet<Artifact>(0);
 215  7
         for (final RootArtifact root : this.roots()) {
 216  9
             for (final Artifact child : root.children()) {
 217  22
                 if (Classpath.contains(child, artifacts)) {
 218  2
                     final Artifact found = Classpath.find(child, artifacts);
 219  2
                     if (found.getVersion().equals(child.getVersion())) {
 220  0
                         continue;
 221  
                     }
 222  2
                     final Artifact newer = Classpath.newer(child, found);
 223  2
                     if (newer.equals(child)) {
 224  2
                         artifacts.remove(found);
 225  2
                         artifacts.add(newer);
 226  
                     }
 227  
                 }
 228  22
                 if (root.excluded(child)) {
 229  0
                     continue;
 230  
                 }
 231  22
                 artifacts.add(child);
 232  22
             }
 233  9
         }
 234  7
         return artifacts;
 235  
     }
 236  
 
 237  
     /**
 238  
      * Find which artifact has newer version.
 239  
      * @param child One of the artifacts to compare.
 240  
      * @param found Second artifact to compare.
 241  
      * @return Newer artifact of the two provided.
 242  
      */
 243  
     private static Artifact newer(final Artifact child, final Artifact found) {
 244  2
         final VersionScheme scheme = new GenericVersionScheme();
 245  
         final Artifact newer;
 246  
         try {
 247  2
             if (scheme.parseVersion(child.getVersion())
 248  
                 .compareTo(scheme.parseVersion(found.getVersion())) < 0) {
 249  0
                 newer = found;
 250  
             } else {
 251  2
                 newer = child;
 252  
             }
 253  0
         } catch (final InvalidVersionSpecificationException ex) {
 254  0
             throw new IllegalStateException(ex);
 255  2
         }
 256  2
         return newer;
 257  
     }
 258  
 
 259  
     /**
 260  
      * Convert dependencies to root artifacts.
 261  
      *
 262  
      * <p>The method is getting a list of artifacts from Maven Project, without
 263  
      * their transitive dependencies (that's why they are called "root"
 264  
      * artifacts).
 265  
      *
 266  
      * @return The set of root artifacts
 267  
      */
 268  
     private Set<RootArtifact> roots() {
 269  8
         final Set<RootArtifact> roots = new LinkedHashSet<RootArtifact>(0);
 270  8
         for (final Dependency dep : this.project.getDependencies()) {
 271  10
             if (!this.scopes.contains(dep.getScope())) {
 272  0
                 continue;
 273  
             }
 274  10
             roots.add(this.root(dep));
 275  10
         }
 276  8
         return roots;
 277  
     }
 278  
 
 279  
     /**
 280  
      * Convert dependency to root artifact.
 281  
      * @param dep Dependency
 282  
      * @return Root artifact
 283  
      */
 284  
     private RootArtifact root(final Dependency dep) {
 285  10
         return new RootArtifact(
 286  
             this.aether,
 287  
             new DefaultArtifact(
 288  
                 dep.getGroupId(),
 289  
                 dep.getArtifactId(),
 290  
                 dep.getClassifier(),
 291  
                 dep.getType(),
 292  
                 dep.getVersion()
 293  
             ),
 294  
             dep.getExclusions()
 295  
         );
 296  
     }
 297  
 
 298  
     /**
 299  
      * Artifact exists in collection?
 300  
      * @param artifact The artifact
 301  
      * @param artifacts Collection of them
 302  
      * @return TRUE if it is already there
 303  
      */
 304  
     private static boolean contains(final Artifact artifact,
 305  
         final Collection<Artifact> artifacts) {
 306  22
         boolean contains = false;
 307  22
         for (final Artifact exists : artifacts) {
 308  35
             if (artifact.getArtifactId().equals(exists.getArtifactId())
 309  
                 && artifact.getGroupId().equals(exists.getGroupId())
 310  
                 && artifact.getClassifier().equals(exists.getClassifier())) {
 311  2
                 contains = true;
 312  2
                 break;
 313  
             }
 314  33
         }
 315  22
         return contains;
 316  
     }
 317  
 
 318  
     /**
 319  
      * Find artifact in collection.
 320  
      * Artifact has to exist in the collection otherwise
 321  
      * IllegalArgumentException will be thrown.
 322  
      *
 323  
      * @param artifact The artifact
 324  
      * @param artifacts Collection of them
 325  
      * @return Found artifact,
 326  
      */
 327  
     private static Artifact find(final Artifact artifact,
 328  
         final Collection<Artifact> artifacts) {
 329  2
         for (final Artifact exists : artifacts) {
 330  2
             if (artifact.getArtifactId().equals(exists.getArtifactId())
 331  
                 && artifact.getGroupId().equals(exists.getGroupId())
 332  
                 && artifact.getClassifier().equals(exists.getClassifier())) {
 333  2
                 return exists;
 334  
             }
 335  0
         }
 336  0
         throw new IllegalArgumentException("Artifact not found");
 337  
     }
 338  
 }
 339