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