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