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 Map<String, ResolutionScopeImpl> resolutionScopes;
068
069    public ScopeManagerImpl(ScopeManagerConfiguration configuration) {
070        this.id = configuration.getId();
071        this.strictDependencyScopes = configuration.isStrictDependencyScopes();
072        this.strictResolutionScopes = configuration.isStrictResolutionScopes();
073        this.buildScopeSource = configuration.getBuildScopeSource();
074        this.systemDependencyScope = new AtomicReference<>(null);
075        this.dependencyScopes = Collections.unmodifiableMap(buildDependencyScopes(configuration));
076        this.resolutionScopes = Collections.unmodifiableMap(buildResolutionScopes(configuration));
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 new HashSet<>(dependencyScopes.values());
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 new HashSet<>(resolutionScopes.values());
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(ResolutionScope resolutionScope) {
143        ResolutionScopeImpl rs = translate(resolutionScope);
144        Set<String> directlyExcludedLabels = getDirectlyExcludedLabels(rs);
145        Set<String> transitivelyExcludedLabels = getTransitivelyExcludedLabels(rs);
146
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    }
155
156    @Override
157    public DependencyGraphTransformer getDependencyGraphTransformer(ResolutionScope resolutionScope) {
158        return new ChainedDependencyGraphTransformer(
159                new ConflictResolver(
160                        new ConfigurableVersionSelector(), new ManagedScopeSelector(this),
161                        new SimpleOptionalitySelector(), new ManagedScopeDeriver(this)),
162                new ManagedDependencyContextRefiner(this));
163    }
164
165    @Override
166    public CollectResult postProcess(ResolutionScope resolutionScope, CollectResult collectResult) {
167        ResolutionScopeImpl rs = translate(resolutionScope);
168        if (rs.getMode() == Mode.ELIMINATE) {
169            CloningDependencyVisitor cloning = new CloningDependencyVisitor();
170            FilteringDependencyVisitor filter = new FilteringDependencyVisitor(
171                    cloning, new ScopeDependencyFilter(null, getDirectlyExcludedLabels(rs)));
172            collectResult.getRoot().accept(filter);
173            collectResult.setRoot(cloning.getRootNode());
174        }
175        return collectResult;
176    }
177
178    @Override
179    public DependencyFilter getDependencyFilter(ResolutionScope resolutionScope) {
180        return new ScopeDependencyFilter(null, getDirectlyExcludedLabels(translate(resolutionScope)));
181    }
182
183    @Override
184    public DependencyScope createDependencyScope(String id, boolean transitive, Collection<BuildScopeQuery> presence) {
185        return new DependencyScopeImpl(id, transitive, presence);
186    }
187
188    @Override
189    public SystemDependencyScope createSystemDependencyScope(
190            String id, boolean transitive, Collection<BuildScopeQuery> presence, String systemPathProperty) {
191        SystemDependencyScopeImpl system = new SystemDependencyScopeImpl(id, transitive, presence, systemPathProperty);
192        if (systemDependencyScope.compareAndSet(null, system)) {
193            return system;
194        } else {
195            throw new IllegalStateException("system dependency scope already created");
196        }
197    }
198
199    @Override
200    public ResolutionScope createResolutionScope(
201            String id,
202            Mode mode,
203            Collection<BuildScopeQuery> wantedPresence,
204            Collection<DependencyScope> explicitlyIncluded,
205            Collection<DependencyScope> transitivelyExcluded) {
206        return new ResolutionScopeImpl(id, mode, wantedPresence, explicitlyIncluded, transitivelyExcluded);
207    }
208
209    private Set<DependencyScope> collectScopes(Collection<BuildScopeQuery> wantedPresence) {
210        HashSet<DependencyScope> result = new HashSet<>();
211        for (BuildScope buildScope : buildScopeSource.query(wantedPresence)) {
212            dependencyScopes.values().stream()
213                    .filter(s -> buildScopeSource.query(s.getPresence()).contains(buildScope))
214                    .filter(s -> systemDependencyScope.get() == null
215                            || !systemDependencyScope.get().is(s.id)) // system scope must be always explicitly added
216                    .forEach(result::add);
217        }
218        return result;
219    }
220
221    private int calculateDependencyScopeWidth(DependencyScopeImpl dependencyScope) {
222        int result = 0;
223        if (dependencyScope.isTransitive()) {
224            result += 1000;
225        }
226        for (BuildScope buildScope : buildScopeSource.query(dependencyScope.getPresence())) {
227            result += 1000
228                    / buildScope.getProjectPaths().stream()
229                            .map(ProjectPath::order)
230                            .reduce(0, Integer::sum);
231        }
232        return result;
233    }
234
235    private BuildScope calculateMainProjectBuildScope(DependencyScopeImpl dependencyScope) {
236        for (ProjectPath projectPath : buildScopeSource.allProjectPaths().stream()
237                .sorted(Comparator.comparing(ProjectPath::order))
238                .collect(Collectors.toList())) {
239            for (BuildPath buildPath : buildScopeSource.allBuildPaths().stream()
240                    .sorted(Comparator.comparing(BuildPath::order))
241                    .collect(Collectors.toList())) {
242                for (BuildScope buildScope : buildScopeSource.query(dependencyScope.getPresence())) {
243                    if (buildScope.getProjectPaths().contains(projectPath)
244                            && buildScope.getBuildPaths().contains(buildPath)) {
245                        return buildScope;
246                    }
247                }
248            }
249        }
250        return null;
251    }
252
253    /**
254     * Visible for testing.
255     */
256    Set<String> getDirectlyIncludedLabels(ResolutionScope resolutionScope) {
257        return translate(resolutionScope).getDirectlyIncluded().stream()
258                .map(DependencyScope::getId)
259                .collect(Collectors.toSet());
260    }
261
262    /**
263     * Visible for testing.
264     */
265    Set<String> getDirectlyExcludedLabels(ResolutionScope resolutionScope) {
266        ResolutionScopeImpl rs = translate(resolutionScope);
267        return dependencyScopes.values().stream()
268                .filter(s -> !rs.getDirectlyIncluded().contains(s))
269                .map(DependencyScope::getId)
270                .collect(Collectors.toSet());
271    }
272
273    /**
274     * Visible for testing.
275     */
276    Set<String> getTransitivelyExcludedLabels(ResolutionScope resolutionScope) {
277        return translate(resolutionScope).getTransitivelyExcluded().stream()
278                .map(DependencyScope::getId)
279                .collect(Collectors.toSet());
280    }
281
282    /**
283     * Visible for testing.
284     */
285    Set<BuildScopeQuery> getPresence(DependencyScope dependencyScope) {
286        return translate(dependencyScope).getPresence();
287    }
288
289    /**
290     * Visible for testing.
291     */
292    BuildScopeSource getBuildScopeSource() {
293        return buildScopeSource;
294    }
295
296    private DependencyScopeImpl translate(DependencyScope dependencyScope) {
297        return requireNonNull(dependencyScopes.get(dependencyScope.getId()), "unknown dependency scope");
298    }
299
300    private ResolutionScopeImpl translate(ResolutionScope resolutionScope) {
301        return requireNonNull(resolutionScopes.get(resolutionScope.getId()), "unknown resolution scope");
302    }
303
304    @Override
305    public boolean equals(Object o) {
306        if (this == o) {
307            return true;
308        }
309        if (o == null || getClass() != o.getClass()) {
310            return false;
311        }
312        ScopeManagerImpl that = (ScopeManagerImpl) o;
313        return Objects.equals(id, that.id);
314    }
315
316    @Override
317    public int hashCode() {
318        return Objects.hash(id);
319    }
320
321    @Override
322    public String toString() {
323        return id;
324    }
325
326    private class DependencyScopeImpl implements DependencyScope {
327        private final String id;
328        private final boolean transitive;
329        private final Set<BuildScopeQuery> presence;
330        private final BuildScope mainBuildScope;
331        private final int width;
332
333        private DependencyScopeImpl(String id, boolean transitive, Collection<BuildScopeQuery> presence) {
334            this.id = requireNonNull(id, "id");
335            this.transitive = transitive;
336            this.presence = Collections.unmodifiableSet(new HashSet<>(presence));
337            this.mainBuildScope = calculateMainProjectBuildScope(this);
338            this.width = calculateDependencyScopeWidth(this);
339        }
340
341        @Override
342        public String getId() {
343            return id;
344        }
345
346        @Override
347        public boolean isTransitive() {
348            return transitive;
349        }
350
351        public Set<BuildScopeQuery> getPresence() {
352            return presence;
353        }
354
355        public BuildScope getMainBuildScope() {
356            return mainBuildScope;
357        }
358
359        public int getWidth() {
360            return width;
361        }
362
363        @Override
364        public boolean equals(Object o) {
365            if (this == o) {
366                return true;
367            }
368            if (o == null || getClass() != o.getClass()) {
369                return false;
370            }
371            DependencyScopeImpl that = (DependencyScopeImpl) o;
372            return Objects.equals(id, that.id);
373        }
374
375        @Override
376        public int hashCode() {
377            return Objects.hash(id);
378        }
379
380        @Override
381        public String toString() {
382            return id;
383        }
384    }
385
386    private class SystemDependencyScopeImpl extends DependencyScopeImpl implements SystemDependencyScope {
387        private final String systemPathProperty;
388
389        private SystemDependencyScopeImpl(
390                String id, boolean transitive, Collection<BuildScopeQuery> presence, String systemPathProperty) {
391            super(id, transitive, presence);
392            this.systemPathProperty = requireNonNull(systemPathProperty);
393        }
394
395        @Override
396        public String getSystemPath(Artifact artifact) {
397            return artifact.getProperty(systemPathProperty, null);
398        }
399
400        @Override
401        public void setSystemPath(Map<String, String> properties, String systemPath) {
402            if (systemPath == null) {
403                properties.remove(systemPathProperty);
404            } else {
405                properties.put(systemPathProperty, systemPath);
406            }
407        }
408    }
409
410    private class ResolutionScopeImpl implements ResolutionScope {
411
412        private final String id;
413        private final Mode mode;
414        private final Set<BuildScopeQuery> wantedPresence;
415        private final Set<DependencyScope> directlyIncluded;
416        private final Set<DependencyScope> transitivelyExcluded;
417
418        private ResolutionScopeImpl(
419                String id,
420                Mode mode,
421                Collection<BuildScopeQuery> wantedPresence,
422                Collection<DependencyScope> explicitlyIncluded,
423                Collection<DependencyScope> transitivelyExcluded) {
424            this.id = requireNonNull(id, "id");
425            this.mode = requireNonNull(mode, "mode");
426            this.wantedPresence = Collections.unmodifiableSet(new HashSet<>(wantedPresence));
427            Set<DependencyScope> included = collectScopes(wantedPresence);
428            // here we may have null elements, based on existence of system scope
429            if (explicitlyIncluded != null && !explicitlyIncluded.isEmpty()) {
430                explicitlyIncluded.stream().filter(Objects::nonNull).forEach(included::add);
431            }
432            this.directlyIncluded = Collections.unmodifiableSet(included);
433            this.transitivelyExcluded = Collections.unmodifiableSet(
434                    transitivelyExcluded.stream().filter(Objects::nonNull).collect(Collectors.toSet()));
435        }
436
437        @Override
438        public String getId() {
439            return id;
440        }
441
442        public Mode getMode() {
443            return mode;
444        }
445
446        public Set<BuildScopeQuery> getWantedPresence() {
447            return wantedPresence;
448        }
449
450        public Set<DependencyScope> getDirectlyIncluded() {
451            return directlyIncluded;
452        }
453
454        public Set<DependencyScope> getTransitivelyExcluded() {
455            return transitivelyExcluded;
456        }
457
458        @Override
459        public boolean equals(Object o) {
460            if (this == o) {
461                return true;
462            }
463            if (o == null || getClass() != o.getClass()) {
464                return false;
465            }
466            ResolutionScopeImpl that = (ResolutionScopeImpl) o;
467            return Objects.equals(id, that.id);
468        }
469
470        @Override
471        public int hashCode() {
472            return Objects.hash(id);
473        }
474
475        @Override
476        public String toString() {
477            return id;
478        }
479    }
480}