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
038/**
039 * A dependency manager managing transitive dependencies supporting transitive dependency management.
040 *
041 * @author Christian Schulte
042 * @since 1.4.0
043 */
044public final class TransitiveDependencyManager
045    implements DependencyManager
046{
047
048    private final Map<Object, String> managedVersions;
049
050    private final Map<Object, String> managedScopes;
051
052    private final Map<Object, Boolean> managedOptionals;
053
054    private final Map<Object, String> managedLocalPaths;
055
056    private final Map<Object, Collection<Exclusion>> managedExclusions;
057
058    private final int depth;
059
060    private int hashCode;
061
062    /**
063     * Creates a new dependency manager without any management information.
064     */
065    public TransitiveDependencyManager()
066    {
067        this( 0, Collections.<Object, String>emptyMap(), Collections.<Object, String>emptyMap(),
068              Collections.<Object, Boolean>emptyMap(), Collections.<Object, String>emptyMap(),
069              Collections.<Object, Collection<Exclusion>>emptyMap() );
070    }
071
072    private TransitiveDependencyManager( final int depth,
073                                         final Map<Object, String> managedVersions,
074                                         final Map<Object, String> managedScopes,
075                                         final Map<Object, Boolean> managedOptionals,
076                                         final Map<Object, String> managedLocalPaths,
077                                         final Map<Object, Collection<Exclusion>> managedExclusions )
078    {
079        super();
080        this.depth = depth;
081        this.managedVersions = managedVersions;
082        this.managedScopes = managedScopes;
083        this.managedOptionals = managedOptionals;
084        this.managedLocalPaths = managedLocalPaths;
085        this.managedExclusions = managedExclusions;
086    }
087
088    public DependencyManager deriveChildManager( final DependencyCollectionContext context )
089    {
090        Map<Object, String> versions = managedVersions;
091        Map<Object, String> scopes = managedScopes;
092        Map<Object, Boolean> optionals = managedOptionals;
093        Map<Object, String> localPaths = managedLocalPaths;
094        Map<Object, Collection<Exclusion>> exclusions = managedExclusions;
095
096        for ( Dependency managedDependency : context.getManagedDependencies() )
097        {
098            Artifact artifact = managedDependency.getArtifact();
099            Object key = getKey( artifact );
100
101            String version = artifact.getVersion();
102            if ( version.length() > 0 && !versions.containsKey( key ) )
103            {
104                if ( versions == managedVersions )
105                {
106                    versions = new HashMap<>( managedVersions );
107                }
108                versions.put( key, version );
109            }
110
111            String scope = managedDependency.getScope();
112            if ( scope.length() > 0 && !scopes.containsKey( key ) )
113            {
114                if ( scopes == this.managedScopes )
115                {
116                    scopes = new HashMap<>( this.managedScopes );
117                }
118                scopes.put( key, scope );
119            }
120
121            Boolean optional = managedDependency.getOptional();
122            if ( optional != null && !optionals.containsKey( key ) )
123            {
124                if ( optionals == managedOptionals )
125                {
126                    optionals = new HashMap<>( managedOptionals );
127                }
128                optionals.put( key, optional );
129            }
130
131            String localPath = managedDependency.getArtifact().getProperty( ArtifactProperties.LOCAL_PATH, null );
132            if ( localPath != null && !localPaths.containsKey( key ) )
133            {
134                if ( localPaths == this.managedLocalPaths )
135                {
136                    localPaths = new HashMap<>( managedLocalPaths );
137                }
138                localPaths.put( key, localPath );
139            }
140
141            if ( !managedDependency.getExclusions().isEmpty() )
142            {
143                if ( exclusions == managedExclusions )
144                {
145                    exclusions = new HashMap<>( managedExclusions );
146                }
147                Collection<Exclusion> managed = exclusions.get( key );
148                if ( managed == null )
149                {
150                    managed = new LinkedHashSet<>();
151                    exclusions.put( key, managed );
152                }
153                managed.addAll( managedDependency.getExclusions() );
154            }
155        }
156
157        return new TransitiveDependencyManager( depth + 1, versions, scopes, optionals, localPaths,
158                                                exclusions );
159
160    }
161
162    public DependencyManagement manageDependency( Dependency dependency )
163    {
164        DependencyManagement management = null;
165
166        Object key = getKey( dependency.getArtifact() );
167
168        if ( depth >= 2 )
169        {
170            String version = managedVersions.get( key );
171            if ( version != null )
172            {
173                management = new DependencyManagement();
174                management.setVersion( version );
175            }
176
177            String scope = managedScopes.get( key );
178            if ( scope != null )
179            {
180                if ( management == null )
181                {
182                    management = new DependencyManagement();
183                }
184                management.setScope( scope );
185
186                if ( !JavaScopes.SYSTEM.equals( scope ) && dependency.getArtifact().getProperty(
187                        ArtifactProperties.LOCAL_PATH, null ) != null )
188                {
189                    Map<String, String> properties = new HashMap<>( dependency.getArtifact().getProperties() );
190                    properties.remove( ArtifactProperties.LOCAL_PATH );
191                    management.setProperties( properties );
192                }
193            }
194
195            if ( ( JavaScopes.SYSTEM.equals( scope ) )
196                     || ( scope == null && JavaScopes.SYSTEM.equals( dependency.getScope() ) ) )
197            {
198                String localPath = managedLocalPaths.get( key );
199                if ( localPath != null )
200                {
201                    if ( management == null )
202                    {
203                        management = new DependencyManagement();
204                    }
205                    Map<String, String> properties = new HashMap<>( dependency.getArtifact().getProperties() );
206                    properties.put( ArtifactProperties.LOCAL_PATH, localPath );
207                    management.setProperties( properties );
208                }
209            }
210
211            Boolean optional = managedOptionals.get( key );
212            if ( optional != null )
213            {
214                if ( management == null )
215                {
216                    management = new DependencyManagement();
217                }
218                management.setOptional( optional );
219            }
220        }
221
222        Collection<Exclusion> exclusions = managedExclusions.get( key );
223        if ( exclusions != null )
224        {
225            if ( management == null )
226            {
227                management = new DependencyManagement();
228            }
229            Collection<Exclusion> result = new LinkedHashSet<>( dependency.getExclusions() );
230            result.addAll( exclusions );
231            management.setExclusions( result );
232        }
233
234        return management;
235    }
236
237    private Object getKey( Artifact a )
238    {
239        return new Key( a );
240    }
241
242    @Override
243    public boolean equals( final Object obj )
244    {
245        boolean equal = obj instanceof TransitiveDependencyManager;
246
247        if ( equal )
248        {
249            final TransitiveDependencyManager that = (TransitiveDependencyManager) obj;
250            return depth == that.depth
251                       && Objects.equals( managedVersions, that.managedVersions )
252                       && Objects.equals( managedScopes, that.managedScopes )
253                       && Objects.equals( managedOptionals, that.managedOptionals )
254                       && Objects.equals( managedExclusions, that.managedExclusions );
255        }
256
257        return false;
258    }
259
260    @Override
261    public int hashCode()
262    {
263        if ( hashCode == 0 )
264        {
265            hashCode = Objects.hash( depth, managedVersions, managedScopes, managedOptionals, managedExclusions );
266        }
267        return hashCode;
268    }
269
270    static class Key
271    {
272        private final Artifact artifact;
273
274        private final int hashCode;
275
276        Key( final Artifact artifact )
277        {
278            this.artifact = artifact;
279            this.hashCode = Objects.hash( artifact.getGroupId(), artifact.getArtifactId() );
280        }
281
282        @Override
283        public boolean equals( final Object obj )
284        {
285            boolean equal = obj instanceof Key;
286
287            if ( equal )
288            {
289                final Key that = (Key) obj;
290                return Objects.equals( artifact.getArtifactId(), that.artifact.getArtifactId() )
291                           && Objects.equals( artifact.getGroupId(), that.artifact.getGroupId() )
292                           && Objects.equals( artifact.getExtension(), that.artifact.getExtension() )
293                           && Objects.equals( artifact.getClassifier(), that.artifact.getClassifier() );
294            }
295
296            return equal;
297        }
298
299        @Override
300        public int hashCode()
301        {
302            return this.hashCode;
303        }
304
305    }
306
307}