Promote SchemaSourceRepresentation
[yangtools.git] / yang / yang-repo-spi / src / main / java / org / opendaylight / yangtools / yang / model / repo / spi / SoftSchemaSourceCache.java
1 /*
2  * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.model.repo.spi;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.util.concurrent.Futures;
12 import com.google.common.util.concurrent.ListenableFuture;
13 import java.lang.ref.Cleaner;
14 import java.lang.ref.Cleaner.Cleanable;
15 import java.lang.ref.Reference;
16 import java.lang.ref.SoftReference;
17 import java.util.concurrent.ConcurrentHashMap;
18 import java.util.concurrent.ConcurrentMap;
19 import org.opendaylight.yangtools.concepts.Registration;
20 import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
21 import org.opendaylight.yangtools.yang.model.api.source.SourceRepresentation;
22 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
23 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource.Costs;
24
25 /**
26  * A simple {@link AbstractSchemaSourceCache} maintaining soft references.
27  *
28  * @param <T> {@link SourceRepresentation} type stored in this cache
29  */
30 @Beta
31 public final class SoftSchemaSourceCache<T extends SourceRepresentation> extends AbstractSchemaSourceCache<T>
32         implements AutoCloseable {
33     private static final Cleaner CLEANER = Cleaner.create();
34
35     private final ConcurrentMap<SourceIdentifier, SoftReference<T>> references = new ConcurrentHashMap<>();
36     private final ConcurrentMap<Registration, Cleanable> cleanables = new ConcurrentHashMap<>();
37
38     private boolean closed;
39
40     public SoftSchemaSourceCache(final SchemaSourceRegistry consumer, final Class<T> representation) {
41         super(consumer, representation, Costs.IMMEDIATE);
42     }
43
44     @Override
45     public ListenableFuture<? extends T> getSource(final SourceIdentifier sourceIdentifier) {
46         final var ref = references.get(sourceIdentifier);
47         if (ref != null) {
48             final var src = ref.get();
49             if (src != null) {
50                 // We have a hit
51                 return Futures.immediateFuture(src);
52             }
53
54             // Expired entry: remove it
55             references.remove(sourceIdentifier, ref);
56         }
57
58         return Futures.immediateFailedFuture(new MissingSchemaSourceException("Source not found", sourceIdentifier));
59     }
60
61     @Override
62     public synchronized void close() {
63         if (!closed) {
64             closed = true;
65             while (!cleanables.isEmpty()) {
66                 cleanables.values().forEach(Cleanable::clean);
67             }
68         }
69     }
70
71     @Override
72     protected synchronized void offer(final T source) {
73         if (closed) {
74             return;
75         }
76
77         final var sourceId = source.sourceId();
78         final var ref = new SoftReference<>(source);
79
80         while (true) {
81             final var prev = references.putIfAbsent(sourceId, ref);
82             if (prev == null) {
83                 // We have performed a fresh insert and need to add a cleanup
84                 break;
85             }
86
87             if (prev.get() != null) {
88                 // We still have a source for this identifier, no further action is needed
89                 return;
90             }
91
92             // Existing reference is dead, remove it and retry
93             references.remove(sourceId, prev);
94         }
95
96         // We have populated a cache entry, register the source and a cleanup action
97         final var reg = register(sourceId);
98         cleanables.put(reg, CLEANER.register(source, () -> {
99             cleanables.remove(reg);
100             reg.close();
101             references.remove(sourceId, ref);
102         }));
103
104         // Ensure 'source' is still reachable here. This is needed to ensure the cleanable action does not fire before
105         // we have had a chance to insert it into the map.
106         Reference.reachabilityFence(source);
107     }
108 }