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