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