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.internal.impl.scope;
020
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Comparator;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.Map;
027import java.util.Objects;
028import java.util.Optional;
029import java.util.Set;
030import java.util.concurrent.atomic.AtomicReference;
031import java.util.stream.Collectors;
032
033import org.eclipse.aether.RepositorySystemSession;
034import org.eclipse.aether.artifact.Artifact;
035import org.eclipse.aether.collection.CollectResult;
036import org.eclipse.aether.collection.DependencySelector;
037import org.eclipse.aether.impl.scope.BuildPath;
038import org.eclipse.aether.impl.scope.BuildScope;
039import org.eclipse.aether.impl.scope.BuildScopeQuery;
040import org.eclipse.aether.impl.scope.BuildScopeSource;
041import org.eclipse.aether.impl.scope.InternalScopeManager;
042import org.eclipse.aether.impl.scope.ProjectPath;
043import org.eclipse.aether.impl.scope.ScopeManagerConfiguration;
044import org.eclipse.aether.scope.DependencyScope;
045import org.eclipse.aether.scope.ResolutionScope;
046import org.eclipse.aether.scope.SystemDependencyScope;
047import org.eclipse.aether.util.filter.ScopeDependencyFilter;
048import org.eclipse.aether.util.graph.selector.AndDependencySelector;
049import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
050import org.eclipse.aether.util.graph.visitor.CloningDependencyVisitor;
051import org.eclipse.aether.util.graph.visitor.FilteringDependencyVisitor;
052
053import static java.util.Objects.requireNonNull;
054
055public final class ScopeManagerImpl implements InternalScopeManager {
056    private final String id;
057    private final boolean strictDependencyScopes;
058    private final boolean strictResolutionScopes;
059    private final BuildScopeSource buildScopeSource;
060    private final AtomicReference<SystemDependencyScopeImpl> systemDependencyScope;
061    private final Map<String, DependencyScopeImpl> dependencyScopes;
062    private final Collection<DependencyScope> dependencyScopesUniverse;
063    private final Map<String, ResolutionScopeImpl> resolutionScopes;
064    private final Collection<ResolutionScope> resolutionScopesUniverse;
065
066    public ScopeManagerImpl(ScopeManagerConfiguration configuration) {
067        this.id = configuration.getId();
068        this.strictDependencyScopes = configuration.isStrictDependencyScopes();
069        this.strictResolutionScopes = configuration.isStrictResolutionScopes();
070        this.buildScopeSource = configuration.getBuildScopeSource();
071        this.systemDependencyScope = new AtomicReference<>(null);
072        this.dependencyScopes = Collections.unmodifiableMap(buildDependencyScopes(configuration));
073        this.dependencyScopesUniverse = Collections.unmodifiableCollection(new HashSet<>(dependencyScopes.values()));
074        this.resolutionScopes = Collections.unmodifiableMap(buildResolutionScopes(configuration));
075        this.resolutionScopesUniverse = Collections.unmodifiableCollection(new HashSet<>(resolutionScopes.values()));
076    }
077
078    private Map<String, DependencyScopeImpl> buildDependencyScopes(ScopeManagerConfiguration configuration) {
079        Collection<DependencyScope> dependencyScopes = configuration.buildDependencyScopes(this);
080        HashMap<String, DependencyScopeImpl> result = new HashMap<>(dependencyScopes.size());
081        dependencyScopes.forEach(d -> result.put(d.getId(), (DependencyScopeImpl) d));
082        return result;
083    }
084
085    private Map<String, ResolutionScopeImpl> buildResolutionScopes(ScopeManagerConfiguration configuration) {
086        Collection<ResolutionScope> resolutionScopes = configuration.buildResolutionScopes(this);
087        HashMap<String, ResolutionScopeImpl> result = new HashMap<>(resolutionScopes.size());
088        resolutionScopes.forEach(r -> result.put(r.getId(), (ResolutionScopeImpl) r));
089        return result;
090    }
091
092    @Override
093    public String getId() {
094        return id;
095    }
096
097    @Override
098    public Optional<SystemDependencyScope> getSystemDependencyScope() {
099        return Optional.ofNullable(systemDependencyScope.get());
100    }
101
102    @Override
103    public Optional<DependencyScope> getDependencyScope(String id) {
104        DependencyScope dependencyScope = dependencyScopes.get(id);
105        if (strictDependencyScopes && dependencyScope == null) {
106            throw new IllegalArgumentException("unknown dependency scope");
107        }
108        return Optional.ofNullable(dependencyScope);
109    }
110
111    @Override
112    public Collection<DependencyScope> getDependencyScopeUniverse() {
113        return dependencyScopesUniverse;
114    }
115
116    @Override
117    public Optional<ResolutionScope> getResolutionScope(String id) {
118        ResolutionScope resolutionScope = resolutionScopes.get(id);
119        if (strictResolutionScopes && resolutionScope == null) {
120            throw new IllegalArgumentException("unknown resolution scope");
121        }
122        return Optional.ofNullable(resolutionScope);
123    }
124
125    @Override
126    public Collection<ResolutionScope> getResolutionScopeUniverse() {
127        return resolutionScopesUniverse;
128    }
129
130    @Override
131    public int getDependencyScopeWidth(DependencyScope dependencyScope) {
132        return translate(dependencyScope).getWidth();
133    }
134
135    @Override
136    public Optional<BuildScope> getDependencyScopeMainProjectBuildScope(DependencyScope dependencyScope) {
137        return Optional.ofNullable(translate(dependencyScope).getMainBuildScope());
138    }
139
140    @Override
141    public DependencySelector getDependencySelector(RepositorySystemSession session, ResolutionScope resolutionScope) {
142        ResolutionScopeImpl rs = translate(resolutionScope);
143        Set<String> directlyExcludedLabels = getDirectlyExcludedLabels(rs);
144        Set<String> transitivelyExcludedLabels = getTransitivelyExcludedLabels(rs);
145        if (session.getDependencySelector() != null) {
146            return new AndDependencySelector(
147                    rs.getMode() == Mode.ELIMINATE
148                            ? ScopeDependencySelector.fromTo(2, 2, null, directlyExcludedLabels)
149                            : ScopeDependencySelector.fromTo(1, 2, null, directlyExcludedLabels),
150                    ScopeDependencySelector.from(2, null, transitivelyExcludedLabels),
151                    OptionalDependencySelector.fromDirect(),
152                    new ExclusionDependencySelector(),
153                    session.getDependencySelector());
154        } else {
155            return new AndDependencySelector(
156                    rs.getMode() == Mode.ELIMINATE
157                            ? ScopeDependencySelector.fromTo(2, 2, null, directlyExcludedLabels)
158                            : ScopeDependencySelector.fromTo(1, 2, null, directlyExcludedLabels),
159                    ScopeDependencySelector.from(2, null, transitivelyExcludedLabels),
160                    OptionalDependencySelector.fromDirect(),
161                    new ExclusionDependencySelector());
162        }
163    }
164
165    @Override
166    public CollectResult postProcess(
167            RepositorySystemSession session, ResolutionScope resolutionScope, CollectResult collectResult) {
168        ResolutionScopeImpl rs = translate(resolutionScope);
169        if (rs.getMode() == Mode.ELIMINATE) {
170            CloningDependencyVisitor cloning = new CloningDependencyVisitor();
171            FilteringDependencyVisitor filter = new FilteringDependencyVisitor(
172                    cloning, new ScopeDependencyFilter(null, getDirectlyExcludedLabels(rs)));
173            collectResult.getRoot().accept(filter);
174            collectResult.setRoot(cloning.getRootNode());
175        }
176        return collectResult;
177    }
178
179    @Override
180    public DependencyScope createDependencyScope(String id, boolean transitive, Collection<BuildScopeQuery> presence) {
181        return new DependencyScopeImpl(id, transitive, presence);
182    }
183
184    @Override
185    public SystemDependencyScope createSystemDependencyScope(
186            String id, boolean transitive, Collection<BuildScopeQuery> presence, String systemPathProperty) {
187        SystemDependencyScopeImpl system = new SystemDependencyScopeImpl(id, transitive, presence, systemPathProperty);
188        if (systemDependencyScope.compareAndSet(null, system)) {
189            return system;
190        } else {
191            throw new IllegalStateException("system dependency scope already created");
192        }
193    }
194
195    @Override
196    public ResolutionScope createResolutionScope(
197            String id,
198            Mode mode,
199            Collection<BuildScopeQuery> wantedPresence,
200            Collection<DependencyScope> explicitlyIncluded,
201            Collection<DependencyScope> transitivelyExcluded) {
202        return new ResolutionScopeImpl(id, mode, wantedPresence, explicitlyIncluded, transitivelyExcluded);
203    }
204
205    private Set<DependencyScope> collectScopes(Collection<BuildScopeQuery> wantedPresence) {
206        HashSet<DependencyScope> result = new HashSet<>();
207        for (BuildScope buildScope : buildScopeSource.query(wantedPresence)) {
208            dependencyScopes.values().stream()
209                    .filter(s -> buildScopeSource.query(s.getPresence()).contains(buildScope))
210                    .filter(s -> systemDependencyScope.get() == null
211                            || !systemDependencyScope.get().is(s.id)) // system scope must be always explicitly added
212                    .forEach(result::add);
213        }
214        return result;
215    }
216
217    private int calculateDependencyScopeWidth(DependencyScopeImpl dependencyScope) {
218        int result = 0;
219        if (dependencyScope.isTransitive()) {
220            result += 1000;
221        }
222        for (BuildScope buildScope : buildScopeSource.query(dependencyScope.getPresence())) {
223            result += 1000
224                    / buildScope.getProjectPaths().stream()
225                            .map(ProjectPath::order)
226                            .reduce(0, Integer::sum);
227        }
228        return result;
229    }
230
231    private BuildScope calculateMainProjectBuildScope(DependencyScopeImpl dependencyScope) {
232        for (ProjectPath projectPath : buildScopeSource.allProjectPaths().stream()
233                .sorted(Comparator.comparing(ProjectPath::order))
234                .collect(Collectors.toList())) {
235            for (BuildPath buildPath : buildScopeSource.allBuildPaths().stream()
236                    .sorted(Comparator.comparing(BuildPath::order))
237                    .collect(Collectors.toList())) {
238                for (BuildScope buildScope : buildScopeSource.query(dependencyScope.getPresence())) {
239                    if (buildScope.getProjectPaths().contains(projectPath)
240                            && buildScope.getBuildPaths().contains(buildPath)) {
241                        return buildScope;
242                    }
243                }
244            }
245        }
246        return null;
247    }
248
249    /**
250     * Visible for testing.
251     */
252    Set<String> getDirectlyIncludedLabels(ResolutionScope resolutionScope) {
253        return translate(resolutionScope).getDirectlyIncluded().stream()
254                .map(DependencyScope::getId)
255                .collect(Collectors.toSet());
256    }
257
258    /**
259     * Visible for testing.
260     */
261    Set<String> getDirectlyExcludedLabels(ResolutionScope resolutionScope) {
262        ResolutionScopeImpl rs = translate(resolutionScope);
263        return dependencyScopes.values().stream()
264                .filter(s -> !rs.getDirectlyIncluded().contains(s))
265                .map(DependencyScope::getId)
266                .collect(Collectors.toSet());
267    }
268
269    /**
270     * Visible for testing.
271     */
272    Set<String> getTransitivelyExcludedLabels(ResolutionScope resolutionScope) {
273        return translate(resolutionScope).getTransitivelyExcluded().stream()
274                .map(DependencyScope::getId)
275                .collect(Collectors.toSet());
276    }
277
278    /**
279     * Visible for testing.
280     */
281    Set<BuildScopeQuery> getPresence(DependencyScope dependencyScope) {
282        return translate(dependencyScope).getPresence();
283    }
284
285    /**
286     * Visible for testing.
287     */
288    BuildScopeSource getBuildScopeSource() {
289        return buildScopeSource;
290    }
291
292    private DependencyScopeImpl translate(DependencyScope dependencyScope) {
293        return requireNonNull(dependencyScopes.get(dependencyScope.getId()), "unknown dependency scope");
294    }
295
296    private ResolutionScopeImpl translate(ResolutionScope resolutionScope) {
297        return requireNonNull(resolutionScopes.get(resolutionScope.getId()), "unknown resolution scope");
298    }
299
300    @Override
301    public boolean equals(Object o) {
302        if (this == o) {
303            return true;
304        }
305        if (o == null || getClass() != o.getClass()) {
306            return false;
307        }
308        ScopeManagerImpl that = (ScopeManagerImpl) o;
309        return Objects.equals(id, that.id);
310    }
311
312    @Override
313    public int hashCode() {
314        return Objects.hash(id);
315    }
316
317    @Override
318    public String toString() {
319        return id;
320    }
321
322    private class DependencyScopeImpl implements DependencyScope {
323        private final String id;
324        private final boolean transitive;
325        private final Set<BuildScopeQuery> presence;
326        private final BuildScope mainBuildScope;
327        private final int width;
328
329        private DependencyScopeImpl(String id, boolean transitive, Collection<BuildScopeQuery> presence) {
330            this.id = requireNonNull(id, "id");
331            this.transitive = transitive;
332            this.presence = Collections.unmodifiableSet(new HashSet<>(presence));
333            this.mainBuildScope = calculateMainProjectBuildScope(this);
334            this.width = calculateDependencyScopeWidth(this);
335        }
336
337        @Override
338        public String getId() {
339            return id;
340        }
341
342        @Override
343        public boolean isTransitive() {
344            return transitive;
345        }
346
347        public Set<BuildScopeQuery> getPresence() {
348            return presence;
349        }
350
351        public BuildScope getMainBuildScope() {
352            return mainBuildScope;
353        }
354
355        public int getWidth() {
356            return width;
357        }
358
359        @Override
360        public boolean equals(Object o) {
361            if (this == o) {
362                return true;
363            }
364            if (o == null || getClass() != o.getClass()) {
365                return false;
366            }
367            DependencyScopeImpl that = (DependencyScopeImpl) o;
368            return Objects.equals(id, that.id);
369        }
370
371        @Override
372        public int hashCode() {
373            return Objects.hash(id);
374        }
375
376        @Override
377        public String toString() {
378            return id;
379        }
380    }
381
382    private class SystemDependencyScopeImpl extends DependencyScopeImpl implements SystemDependencyScope {
383        private final String systemPathProperty;
384
385        private SystemDependencyScopeImpl(
386                String id, boolean transitive, Collection<BuildScopeQuery> presence, String systemPathProperty) {
387            super(id, transitive, presence);
388            this.systemPathProperty = requireNonNull(systemPathProperty);
389        }
390
391        @Override
392        public String getSystemPath(Artifact artifact) {
393            return artifact.getProperty(systemPathProperty, null);
394        }
395
396        @Override
397        public void setSystemPath(Map<String, String> properties, String systemPath) {
398            if (systemPath == null) {
399                properties.remove(systemPathProperty);
400            } else {
401                properties.put(systemPathProperty, systemPath);
402            }
403        }
404    }
405
406    private class ResolutionScopeImpl implements ResolutionScope {
407        private final String id;
408        private final Mode mode;
409        private final Set<BuildScopeQuery> wantedPresence;
410        private final Set<DependencyScope> directlyIncluded;
411        private final Set<DependencyScope> transitivelyExcluded;
412
413        private ResolutionScopeImpl(
414                String id,
415                Mode mode,
416                Collection<BuildScopeQuery> wantedPresence,
417                Collection<DependencyScope> explicitlyIncluded,
418                Collection<DependencyScope> transitivelyExcluded) {
419            this.id = requireNonNull(id, "id");
420            this.mode = requireNonNull(mode, "mode");
421            this.wantedPresence = Collections.unmodifiableSet(new HashSet<>(wantedPresence));
422            Set<DependencyScope> included = collectScopes(wantedPresence);
423            // here we may have null elements, based on existence of system scope
424            if (explicitlyIncluded != null && !explicitlyIncluded.isEmpty()) {
425                explicitlyIncluded.stream().filter(Objects::nonNull).forEach(included::add);
426            }
427            this.directlyIncluded = Collections.unmodifiableSet(included);
428            this.transitivelyExcluded = Collections.unmodifiableSet(
429                    transitivelyExcluded.stream().filter(Objects::nonNull).collect(Collectors.toSet()));
430        }
431
432        @Override
433        public String getId() {
434            return id;
435        }
436
437        public Mode getMode() {
438            return mode;
439        }
440
441        public Set<BuildScopeQuery> getWantedPresence() {
442            return wantedPresence;
443        }
444
445        public Set<DependencyScope> getDirectlyIncluded() {
446            return directlyIncluded;
447        }
448
449        public Set<DependencyScope> getTransitivelyExcluded() {
450            return transitivelyExcluded;
451        }
452
453        @Override
454        public boolean equals(Object o) {
455            if (this == o) {
456                return true;
457            }
458            if (o == null || getClass() != o.getClass()) {
459                return false;
460            }
461            ResolutionScopeImpl that = (ResolutionScopeImpl) o;
462            return Objects.equals(id, that.id);
463        }
464
465        @Override
466        public int hashCode() {
467            return Objects.hash(id);
468        }
469
470        @Override
471        public String toString() {
472            return id;
473        }
474    }
475}