001package org.eclipse.aether.util.graph.manager;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.LinkedHashSet;
026import java.util.Map;
027import java.util.Objects;
028
029import org.eclipse.aether.artifact.Artifact;
030import org.eclipse.aether.artifact.ArtifactProperties;
031import org.eclipse.aether.collection.DependencyCollectionContext;
032import org.eclipse.aether.collection.DependencyManagement;
033import org.eclipse.aether.collection.DependencyManager;
034import org.eclipse.aether.graph.Dependency;
035import org.eclipse.aether.graph.Exclusion;
036import org.eclipse.aether.util.artifact.JavaScopes;
037
038import static java.util.Objects.requireNonNull;
039
040/**
041 * A dependency manager managing transitive dependencies supporting transitive dependency management.
042 *
043 * @author Christian Schulte
044 * @since 1.4.0
045 */
046public final class TransitiveDependencyManager
047    implements DependencyManager
048{
049
050    private final Map<Object, String> managedVersions;
051
052    private final Map<Object, String> managedScopes;
053
054    private final Map<Object, Boolean> managedOptionals;
055
056    private final Map<Object, String> managedLocalPaths;
057
058    private final Map<Object, Collection<Exclusion>> managedExclusions;
059
060    private final int depth;
061
062    private int hashCode;
063
064    /**
065     * Creates a new dependency manager without any management information.
066     */
067    public TransitiveDependencyManager()
068    {
069        this( 0, Collections.<Object, String>emptyMap(), Collections.<Object, String>emptyMap(),
070              Collections.<Object, Boolean>emptyMap(), Collections.<Object, String>emptyMap(),
071              Collections.<Object, Collection<Exclusion>>emptyMap() );
072    }
073
074    private TransitiveDependencyManager( final int depth,
075                                         final Map<Object, String> managedVersions,
076                                         final Map<Object, String> managedScopes,
077                                         final Map<Object, Boolean> managedOptionals,
078                                         final Map<Object, String> managedLocalPaths,
079                                         final Map<Object, Collection<Exclusion>> managedExclusions )
080    {
081        super();
082        this.depth = depth;
083        this.managedVersions = managedVersions;
084        this.managedScopes = managedScopes;
085        this.managedOptionals = managedOptionals;
086        this.managedLocalPaths = managedLocalPaths;
087        this.managedExclusions = managedExclusions;
088    }
089
090    public DependencyManager deriveChildManager( final DependencyCollectionContext context )
091    {
092        requireNonNull( context, "context cannot be null" );
093        Map<Object, String> versions = managedVersions;
094        Map<Object, String> scopes = managedScopes;
095        Map<Object, Boolean> optionals = managedOptionals;
096        Map<Object, String> localPaths = managedLocalPaths;
097        Map<Object, Collection<Exclusion>> exclusions = managedExclusions;
098
099        for ( Dependency managedDependency : context.getManagedDependencies() )
100        {
101            Artifact artifact = managedDependency.getArtifact();
102            Object key = getKey( artifact );
103
104            String version = artifact.getVersion();
105            if ( version.length() > 0 && !versions.containsKey( key ) )
106            {
107                if ( versions == managedVersions )
108                {
109                    versions = new HashMap<>( managedVersions );
110                }
111                versions.put( key, version );
112            }
113
114            String scope = managedDependency.getScope();
115            if ( scope.length() > 0 && !scopes.containsKey( key ) )
116            {
117                if ( scopes == this.managedScopes )
118                {
119                    scopes = new HashMap<>( this.managedScopes );
120                }
121                scopes.put( key, scope );
122            }
123
124            Boolean optional = managedDependency.getOptional();
125            if ( optional != null && !optionals.containsKey( key ) )
126            {
127                if ( optionals == managedOptionals )
128                {
129                    optionals = new HashMap<>( managedOptionals );
130                }
131                optionals.put( key, optional );
132            }
133
134            String localPath = managedDependency.getArtifact().getProperty( ArtifactProperties.LOCAL_PATH, null );
135            if ( localPath != null && !localPaths.containsKey( key ) )
136            {
137                if ( localPaths == this.managedLocalPaths )
138                {
139                    localPaths = new HashMap<>( managedLocalPaths );
140                }
141                localPaths.put( key, localPath );
142            }
143
144            if ( !managedDependency.getExclusions().isEmpty() )
145            {
146                if ( exclusions == managedExclusions )
147                {
148                    exclusions = new HashMap<>( managedExclusions );
149                }
150                Collection<Exclusion> managed = exclusions.computeIfAbsent( key, k -> new LinkedHashSet<>() );
151                managed.addAll( managedDependency.getExclusions() );
152            }
153        }
154
155        return new TransitiveDependencyManager( depth + 1, versions, scopes, optionals, localPaths,
156                                                exclusions );
157
158    }
159
160    public DependencyManagement manageDependency( Dependency dependency )
161    {
162        requireNonNull( dependency, "dependency cannot be null" );
163        DependencyManagement management = null;
164
165        Object key = getKey( dependency.getArtifact() );
166
167        if ( depth >= 2 )
168        {
169            String version = managedVersions.get( key );
170            if ( version != null )
171            {
172                management = new DependencyManagement();
173                management.setVersion( version );
174            }
175
176            String scope = managedScopes.get( key );
177            if ( scope != null )
178            {
179                if ( management == null )
180                {
181                    management = new DependencyManagement();
182                }
183                management.setScope( scope );
184
185                if ( !JavaScopes.SYSTEM.equals( scope ) && dependency.getArtifact().getProperty(
186                        ArtifactProperties.LOCAL_PATH, null ) != null )
187                {
188                    Map<String, String> properties = new HashMap<>( dependency.getArtifact().getProperties() );
189                    properties.remove( ArtifactProperties.LOCAL_PATH );
190                    management.setProperties( properties );
191                }
192            }
193
194            if ( ( JavaScopes.SYSTEM.equals( scope ) )
195                     || ( scope == null && JavaScopes.SYSTEM.equals( dependency.getScope() ) ) )
196            {
197                String localPath = managedLocalPaths.get( key );
198                if ( localPath != null )
199                {
200                    if ( management == null )
201                    {
202                        management = new DependencyManagement();
203                    }
204                    Map<String, String> properties = new HashMap<>( dependency.getArtifact().getProperties() );
205                    properties.put( ArtifactProperties.LOCAL_PATH, localPath );
206                    management.setProperties( properties );
207                }
208            }
209
210            Boolean optional = managedOptionals.get( key );
211            if ( optional != null )
212            {
213                if ( management == null )
214                {
215                    management = new DependencyManagement();
216                }
217                management.setOptional( optional );
218            }
219        }
220
221        Collection<Exclusion> exclusions = managedExclusions.get( key );
222        if ( exclusions != null )
223        {
224            if ( management == null )
225            {
226                management = new DependencyManagement();
227            }
228            Collection<Exclusion> result = new LinkedHashSet<>( dependency.getExclusions() );
229            result.addAll( exclusions );
230            management.setExclusions( result );
231        }
232
233        return management;
234    }
235
236    private Object getKey( Artifact a )
237    {
238        return new Key( a );
239    }
240
241    @Override
242    public boolean equals( final Object obj )
243    {
244        boolean equal = obj instanceof TransitiveDependencyManager;
245
246        if ( equal )
247        {
248            final TransitiveDependencyManager that = (TransitiveDependencyManager) obj;
249            return depth == that.depth
250                       && Objects.equals( managedVersions, that.managedVersions )
251                       && Objects.equals( managedScopes, that.managedScopes )
252                       && Objects.equals( managedOptionals, that.managedOptionals )
253                       && Objects.equals( managedExclusions, that.managedExclusions );
254        }
255
256        return false;
257    }
258
259    @Override
260    public int hashCode()
261    {
262        if ( hashCode == 0 )
263        {
264            hashCode = Objects.hash( depth, managedVersions, managedScopes, managedOptionals, managedExclusions );
265        }
266        return hashCode;
267    }
268
269    static class Key
270    {
271        private final Artifact artifact;
272
273        private final int hashCode;
274
275        Key( final Artifact artifact )
276        {
277            this.artifact = artifact;
278            this.hashCode = Objects.hash( artifact.getGroupId(), artifact.getArtifactId() );
279        }
280
281        @Override
282        public boolean equals( final Object obj )
283        {
284            boolean equal = obj instanceof Key;
285
286            if ( equal )
287            {
288                final Key that = (Key) obj;
289                return Objects.equals( artifact.getArtifactId(), that.artifact.getArtifactId() )
290                           && Objects.equals( artifact.getGroupId(), that.artifact.getGroupId() )
291                           && Objects.equals( artifact.getExtension(), that.artifact.getExtension() )
292                           && Objects.equals( artifact.getClassifier(), that.artifact.getClassifier() );
293            }
294
295            return false;
296        }
297
298        @Override
299        public int hashCode()
300        {
301            return this.hashCode;
302        }
303
304    }
305
306}