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