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