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