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.collect;
020
021import java.util.Collection;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Objects;
025import java.util.concurrent.ConcurrentHashMap;
026
027import org.eclipse.aether.Keys;
028import org.eclipse.aether.RepositoryCache;
029import org.eclipse.aether.RepositorySystemSession;
030import org.eclipse.aether.artifact.Artifact;
031import org.eclipse.aether.collection.DependencyManager;
032import org.eclipse.aether.collection.DependencySelector;
033import org.eclipse.aether.collection.DependencyTraverser;
034import org.eclipse.aether.collection.VersionFilter;
035import org.eclipse.aether.graph.Dependency;
036import org.eclipse.aether.graph.DependencyNode;
037import org.eclipse.aether.repository.ArtifactRepository;
038import org.eclipse.aether.repository.RemoteRepository;
039import org.eclipse.aether.resolution.ArtifactDescriptorException;
040import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
041import org.eclipse.aether.resolution.ArtifactDescriptorResult;
042import org.eclipse.aether.resolution.VersionRangeRequest;
043import org.eclipse.aether.resolution.VersionRangeResult;
044import org.eclipse.aether.util.ConfigUtils;
045import org.eclipse.aether.util.concurrency.ConcurrentWeakCache;
046import org.eclipse.aether.version.Version;
047import org.eclipse.aether.version.VersionConstraint;
048
049/**
050 * Internal helper class for collector implementations.
051 */
052public final class DataPool {
053    public static final String CONFIG_PROPS_PREFIX = DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "pool.";
054
055    /**
056     * Flag controlling interning data pool type used by dependency collector for Artifact instances, matters for
057     * heap consumption. By default, uses “weak” references (consume less heap). Using “hard” will make it much
058     * more memory aggressive and possibly faster (system and Java dependent). Supported values: "hard", "weak".
059     *
060     * @since 1.9.5
061     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
062     * @configurationType {@link java.lang.String}
063     * @configurationDefaultValue {@link #WEAK}
064     */
065    public static final String CONFIG_PROP_COLLECTOR_POOL_ARTIFACT = CONFIG_PROPS_PREFIX + "artifact";
066
067    /**
068     * Flag controlling interning data pool type used by dependency collector for Dependency instances, matters for
069     * heap consumption. By default, uses “weak” references (consume less heap). Using “hard” will make it much
070     * more memory aggressive and possibly faster (system and Java dependent). Supported values: "hard", "weak".
071     *
072     * @since 1.9.5
073     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
074     * @configurationType {@link java.lang.String}
075     * @configurationDefaultValue {@link #WEAK}
076     */
077    public static final String CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY = CONFIG_PROPS_PREFIX + "dependency";
078
079    /**
080     * Flag controlling interning data pool type used by dependency collector for ArtifactDescriptor (POM) instances,
081     * matters for heap consumption. By default, uses “weak” references (consume less heap). Using “hard” will make it
082     * much more memory aggressive and possibly faster (system and Java dependent). Supported values: "hard", "weak".
083     *
084     * @since 1.9.5
085     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
086     * @configurationType {@link java.lang.String}
087     * @configurationDefaultValue {@link #HARD}
088     */
089    public static final String CONFIG_PROP_COLLECTOR_POOL_DESCRIPTOR = CONFIG_PROPS_PREFIX + "descriptor";
090
091    /**
092     * Flag controlling interning data pool type used by dependency lists collector for ArtifactDescriptor (POM) instances,
093     * matters for heap consumption. By default, uses “weak” references (consume less heap). Using “hard” will make it
094     * much more memory aggressive and possibly faster (system and Java dependent). Supported values: "hard", "weak".
095     *
096     * @since 1.9.22
097     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
098     * @configurationType {@link java.lang.String}
099     * @configurationDefaultValue {@link #HARD}
100     */
101    public static final String CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY_LISTS =
102            "aether.dependencyCollector.pool.dependencyLists";
103
104    /**
105     * Flag controlling interning artifact descriptor dependencies.
106     *
107     * @since 1.9.22
108     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
109     * @configurationType {@link java.lang.Boolean}
110     * @configurationDefaultValue false
111     */
112    public static final String CONFIG_PROP_COLLECTOR_POOL_INTERN_ARTIFACT_DESCRIPTOR_DEPENDENCIES =
113            "aether.dependencyCollector.pool.internArtifactDescriptorDependencies";
114
115    /**
116     * Flag controlling interning artifact descriptor managed dependencies.
117     *
118     * @since 1.9.22
119     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
120     * @configurationType {@link java.lang.Boolean}
121     * @configurationDefaultValue true
122     */
123    public static final String CONFIG_PROP_COLLECTOR_POOL_INTERN_ARTIFACT_DESCRIPTOR_MANAGED_DEPENDENCIES =
124            "aether.dependencyCollector.pool.internArtifactDescriptorManagedDependencies";
125
126    private static final Object ARTIFACT_POOL = Keys.of(DataPool.class, "artifact");
127
128    private static final Object DEPENDENCY_POOL = Keys.of(DataPool.class, "dependency");
129
130    private static final Object DESCRIPTORS = Keys.of(DataPool.class, "descriptors");
131
132    private static final Object DEPENDENCY_LISTS_POOL = Keys.of(DataPool.class, "dependencyLists");
133
134    public static final ArtifactDescriptorResult NO_DESCRIPTOR =
135            new ArtifactDescriptorResult(new ArtifactDescriptorRequest());
136
137    /**
138     * Artifact interning pool, lives across session (if session carries non-null {@link RepositoryCache}).
139     */
140    private final InternPool<Artifact, Artifact> artifacts;
141
142    /**
143     * Dependency interning pool, lives across session (if session carries non-null {@link RepositoryCache}).
144     */
145    private final InternPool<Dependency, Dependency> dependencies;
146
147    /**
148     * Descriptor interning pool, lives across session (if session carries non-null {@link RepositoryCache}).
149     */
150    private final InternPool<DescriptorKey, Descriptor> descriptors;
151
152    /**
153     * {@link Dependency} list interning pool, lives across session (if session carries non-null {@link RepositoryCache}).
154     */
155    private final InternPool<List<Dependency>, List<Dependency>> dependencyLists;
156
157    /**
158     * Constraint cache, lives during single collection invocation (same as this DataPool instance).
159     */
160    private final ConcurrentHashMap<Object, Constraint> constraints;
161
162    /**
163     * DependencyNode cache, lives during single collection invocation (same as this DataPool instance).
164     */
165    private final ConcurrentHashMap<Object, List<DependencyNode>> nodes;
166
167    private final boolean internArtifactDescriptorDependencies;
168
169    private final boolean internArtifactDescriptorManagedDependencies;
170
171    @SuppressWarnings("unchecked")
172    public DataPool(RepositorySystemSession session) {
173        final RepositoryCache cache = session.getCache();
174
175        internArtifactDescriptorDependencies = ConfigUtils.getBoolean(
176                session, false, CONFIG_PROP_COLLECTOR_POOL_INTERN_ARTIFACT_DESCRIPTOR_DEPENDENCIES);
177        internArtifactDescriptorManagedDependencies = ConfigUtils.getBoolean(
178                session, true, CONFIG_PROP_COLLECTOR_POOL_INTERN_ARTIFACT_DESCRIPTOR_MANAGED_DEPENDENCIES);
179
180        InternPool<Artifact, Artifact> artifactsPool;
181        InternPool<Dependency, Dependency> dependenciesPool;
182        InternPool<DescriptorKey, Descriptor> descriptorsPool;
183        InternPool<List<Dependency>, List<Dependency>> dependencyListsPool;
184        if (cache != null) {
185            artifactsPool = (InternPool<Artifact, Artifact>) cache.computeIfAbsent(
186                    session,
187                    ARTIFACT_POOL,
188                    () -> createPool(ConfigUtils.getString(session, WEAK, CONFIG_PROP_COLLECTOR_POOL_ARTIFACT)));
189            dependenciesPool = (InternPool<Dependency, Dependency>) cache.computeIfAbsent(
190                    session,
191                    DEPENDENCY_POOL,
192                    () -> createPool(ConfigUtils.getString(session, WEAK, CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY)));
193            descriptorsPool = (InternPool<DescriptorKey, Descriptor>) cache.computeIfAbsent(
194                    session,
195                    DESCRIPTORS,
196                    () -> createPool(ConfigUtils.getString(session, HARD, CONFIG_PROP_COLLECTOR_POOL_DESCRIPTOR)));
197            dependencyListsPool = (InternPool<List<Dependency>, List<Dependency>>) cache.computeIfAbsent(
198                    session,
199                    DEPENDENCY_LISTS_POOL,
200                    () -> createPool(
201                            ConfigUtils.getString(session, HARD, CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY_LISTS)));
202        } else {
203            artifactsPool = createPool(ConfigUtils.getString(session, WEAK, CONFIG_PROP_COLLECTOR_POOL_ARTIFACT));
204            dependenciesPool = createPool(ConfigUtils.getString(session, WEAK, CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY));
205            descriptorsPool = createPool(ConfigUtils.getString(session, HARD, CONFIG_PROP_COLLECTOR_POOL_DESCRIPTOR));
206            dependencyListsPool =
207                    createPool(ConfigUtils.getString(session, HARD, CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY_LISTS));
208        }
209
210        this.artifacts = artifactsPool;
211        this.dependencies = dependenciesPool;
212        this.descriptors = descriptorsPool;
213        this.dependencyLists = dependencyListsPool;
214
215        this.constraints = new ConcurrentHashMap<>(256);
216        this.nodes = new ConcurrentHashMap<>(256);
217    }
218
219    public Artifact intern(Artifact artifact) {
220        return artifacts.intern(artifact, artifact);
221    }
222
223    public Dependency intern(Dependency dependency) {
224        return dependencies.intern(dependency, dependency);
225    }
226
227    public DescriptorKey toKey(ArtifactDescriptorRequest request) {
228        return new DescriptorKey(request.getArtifact(), request.getRepositories());
229    }
230
231    public ArtifactDescriptorResult getDescriptor(DescriptorKey key, ArtifactDescriptorRequest request) {
232        Descriptor descriptor = descriptors.get(key);
233        if (descriptor != null) {
234            return descriptor.toResult(request);
235        }
236        return null;
237    }
238
239    public void putDescriptor(DescriptorKey key, ArtifactDescriptorResult result) {
240        if (internArtifactDescriptorDependencies) {
241            result.setDependencies(intern(result.getDependencies()));
242        }
243        if (internArtifactDescriptorManagedDependencies) {
244            result.setManagedDependencies(intern(result.getManagedDependencies()));
245        }
246        descriptors.intern(key, new GoodDescriptor(result));
247    }
248
249    public void putDescriptor(DescriptorKey key, ArtifactDescriptorException e) {
250        descriptors.intern(key, new BadDescriptor(e));
251    }
252
253    /**
254     * Returns the failure reason of a previously cached bad descriptor, or {@code null} if the given key does not
255     * map to a cached failure. Only the exception message is retained (not the exception itself), to avoid pinning
256     * the originating request in the session-wide pool.
257     */
258    public String getDescriptorFailure(DescriptorKey key) {
259        Descriptor descriptor = descriptors.get(key);
260        if (descriptor instanceof BadDescriptor) {
261            return ((BadDescriptor) descriptor).reason;
262        }
263        return null;
264    }
265
266    private List<Dependency> intern(List<Dependency> dependencies) {
267        return dependencyLists.intern(dependencies, dependencies);
268    }
269
270    public Object toKey(VersionRangeRequest request) {
271        return new ConstraintKey(request);
272    }
273
274    public VersionRangeResult getConstraint(Object key, VersionRangeRequest request) {
275        Constraint constraint = constraints.get(key);
276        if (constraint != null) {
277            return constraint.toResult(request);
278        }
279        return null;
280    }
281
282    public void putConstraint(Object key, VersionRangeResult result) {
283        constraints.put(key, new Constraint(result));
284    }
285
286    public Object toKey(
287            Artifact artifact,
288            List<RemoteRepository> repositories,
289            DependencySelector selector,
290            DependencyManager manager,
291            DependencyTraverser traverser,
292            VersionFilter filter) {
293        return new GraphKey(artifact, repositories, selector, manager, traverser, filter);
294    }
295
296    public List<DependencyNode> getChildren(Object key) {
297        return nodes.get(key);
298    }
299
300    public void putChildren(Object key, List<DependencyNode> children) {
301        nodes.put(key, children);
302    }
303
304    public static final class DescriptorKey {
305        private final Artifact artifact;
306        private final List<RemoteRepository> repositories;
307        private final int hashCode;
308
309        private DescriptorKey(Artifact artifact, List<RemoteRepository> repositories) {
310            this.artifact = artifact;
311            this.repositories = repositories;
312            this.hashCode = Objects.hashCode(artifact);
313        }
314
315        @Override
316        public boolean equals(Object o) {
317            if (this == o) {
318                return true;
319            }
320            if (o == null || getClass() != o.getClass()) {
321                return false;
322            }
323            DescriptorKey that = (DescriptorKey) o;
324            return Objects.equals(artifact, that.artifact) && repositoriesEquals(repositories, that.repositories);
325        }
326
327        @Override
328        public int hashCode() {
329            return hashCode;
330        }
331
332        @Override
333        public String toString() {
334            return getClass().getSimpleName() + "{" + "artifact='" + artifact + '\'' + ", repositories='" + repositories
335                    + '\'' + '}';
336        }
337    }
338
339    abstract static class Descriptor {
340        public abstract ArtifactDescriptorResult toResult(ArtifactDescriptorRequest request);
341    }
342
343    static final class GoodDescriptor extends Descriptor {
344
345        final Artifact artifact;
346
347        final List<Artifact> relocations;
348
349        final Collection<Artifact> aliases;
350
351        final List<RemoteRepository> repositories;
352
353        final List<Dependency> dependencies;
354
355        final List<Dependency> managedDependencies;
356
357        GoodDescriptor(ArtifactDescriptorResult result) {
358            artifact = result.getArtifact();
359            relocations = result.getRelocations();
360            aliases = result.getAliases();
361            dependencies = result.getDependencies();
362            managedDependencies = result.getManagedDependencies();
363            repositories = result.getRepositories();
364        }
365
366        public ArtifactDescriptorResult toResult(ArtifactDescriptorRequest request) {
367            ArtifactDescriptorResult result = new ArtifactDescriptorResult(request);
368            result.setArtifact(artifact);
369            result.setRelocations(relocations);
370            result.setAliases(aliases);
371            result.setDependencies(dependencies);
372            result.setManagedDependencies(managedDependencies);
373            result.setRepositories(repositories);
374            return result;
375        }
376    }
377
378    static final class BadDescriptor extends Descriptor {
379
380        final String reason;
381
382        BadDescriptor(ArtifactDescriptorException exception) {
383            this.reason = exception != null ? exception.getMessage() : null;
384        }
385
386        public ArtifactDescriptorResult toResult(ArtifactDescriptorRequest request) {
387            return NO_DESCRIPTOR;
388        }
389    }
390
391    private static final class Constraint {
392        final VersionRepo[] repositories;
393
394        final VersionConstraint versionConstraint;
395
396        Constraint(VersionRangeResult result) {
397            versionConstraint = result.getVersionConstraint();
398            List<Version> versions = result.getVersions();
399            repositories = new VersionRepo[versions.size()];
400            int i = 0;
401            for (Version version : versions) {
402                repositories[i++] = new VersionRepo(version, result.getRepository(version));
403            }
404        }
405
406        VersionRangeResult toResult(VersionRangeRequest request) {
407            VersionRangeResult result = new VersionRangeResult(request);
408            for (VersionRepo vr : repositories) {
409                result.addVersion(vr.version);
410                result.setRepository(vr.version, vr.repo);
411            }
412            result.setVersionConstraint(versionConstraint);
413            return result;
414        }
415
416        static final class VersionRepo {
417            final Version version;
418
419            final ArtifactRepository repo;
420
421            VersionRepo(Version version, ArtifactRepository repo) {
422                this.version = version;
423                this.repo = repo;
424            }
425        }
426    }
427
428    static final class ConstraintKey {
429        private final Artifact artifact;
430
431        private final List<RemoteRepository> repositories;
432
433        private final int hashCode;
434
435        ConstraintKey(VersionRangeRequest request) {
436            artifact = request.getArtifact();
437            repositories = request.getRepositories();
438            hashCode = artifact.hashCode();
439        }
440
441        @Override
442        public boolean equals(Object obj) {
443            if (obj == this) {
444                return true;
445            } else if (!(obj instanceof ConstraintKey)) {
446                return false;
447            }
448            ConstraintKey that = (ConstraintKey) obj;
449            return artifact.equals(that.artifact) && repositoriesEquals(repositories, that.repositories);
450        }
451
452        @Override
453        public int hashCode() {
454            return hashCode;
455        }
456    }
457
458    private static boolean repositoriesEquals(List<RemoteRepository> repos1, List<RemoteRepository> repos2) {
459        if (repos1.size() != repos2.size()) {
460            return false;
461        }
462        for (Iterator<RemoteRepository> it1 = repos1.iterator(), it2 = repos2.iterator();
463                it1.hasNext() && it2.hasNext(); ) {
464            RemoteRepository repo1 = it1.next();
465            RemoteRepository repo2 = it2.next();
466            if (repo1.isRepositoryManager() != repo2.isRepositoryManager()) {
467                return false;
468            }
469            if (repo1.isRepositoryManager()) {
470                if (!repositoriesEquals(repo1.getMirroredRepositories(), repo2.getMirroredRepositories())) {
471                    return false;
472                }
473            } else if (!repo1.getUrl().equals(repo2.getUrl())) {
474                return false;
475            } else if (repo1.getPolicy(true).isEnabled()
476                    != repo2.getPolicy(true).isEnabled()) {
477                return false;
478            } else if (repo1.getPolicy(false).isEnabled()
479                    != repo2.getPolicy(false).isEnabled()) {
480                return false;
481            }
482        }
483        return true;
484    }
485
486    static final class GraphKey {
487        private final Artifact artifact;
488
489        private final List<RemoteRepository> repositories;
490
491        private final DependencySelector selector;
492
493        private final DependencyManager manager;
494
495        private final DependencyTraverser traverser;
496
497        private final VersionFilter filter;
498
499        private final int hashCode;
500
501        GraphKey(
502                Artifact artifact,
503                List<RemoteRepository> repositories,
504                DependencySelector selector,
505                DependencyManager manager,
506                DependencyTraverser traverser,
507                VersionFilter filter) {
508            this.artifact = artifact;
509            this.repositories = repositories;
510            this.selector = selector;
511            this.manager = manager;
512            this.traverser = traverser;
513            this.filter = filter;
514
515            hashCode = Objects.hash(artifact, repositories, selector, manager, traverser, filter);
516        }
517
518        @Override
519        public boolean equals(Object obj) {
520            if (obj == this) {
521                return true;
522            } else if (!(obj instanceof GraphKey)) {
523                return false;
524            }
525            GraphKey that = (GraphKey) obj;
526            return Objects.equals(artifact, that.artifact)
527                    && Objects.equals(repositories, that.repositories)
528                    && Objects.equals(selector, that.selector)
529                    && Objects.equals(manager, that.manager)
530                    && Objects.equals(traverser, that.traverser)
531                    && Objects.equals(filter, that.filter);
532        }
533
534        @Override
535        public int hashCode() {
536            return hashCode;
537        }
538    }
539
540    private static <K, V> InternPool<K, V> createPool(String type) {
541        if (HARD.equals(type)) {
542            return new HardInternPool<>();
543        } else if (WEAK.equals(type)) {
544            return new WeakInternPool<>();
545        } else {
546            throw new IllegalArgumentException("Unknown object pool type: '" + type + "'");
547        }
548    }
549
550    public static final String HARD = "hard";
551
552    public static final String WEAK = "weak";
553
554    private interface InternPool<K, V> {
555        V get(K key);
556
557        V intern(K key, V value);
558    }
559
560    private static class HardInternPool<K, V> implements InternPool<K, V> {
561        private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>(256);
562
563        @Override
564        public V get(K key) {
565            return map.get(key);
566        }
567
568        @Override
569        public V intern(K key, V value) {
570            return map.computeIfAbsent(key, k -> value);
571        }
572    }
573
574    /**
575     * Intern pool backed by ConcurrentWeakCache with weak keys and weak values.
576     * Lock-free reads (ConcurrentHashMap.get is a volatile read, zero allocation via
577     * ThreadLocal lookup key), lock-striped writes, weak keys and values allow GC of
578     * interned objects when no longer strongly referenced.
579     * Uses putIfAbsent to guarantee concurrent callers for the same key get the same instance.
580     */
581    private static class WeakInternPool<K, V> implements InternPool<K, V> {
582        private final ConcurrentWeakCache<K, V> cache = new ConcurrentWeakCache<>(256);
583
584        @Override
585        public V get(K key) {
586            return cache.get(key);
587        }
588
589        @Override
590        public V intern(K key, V value) {
591            return cache.putIfAbsent(key, value);
592        }
593    }
594}