Cleanup use of Guava library
[yangtools.git] / common / util / src / main / java / org / opendaylight / yangtools / util / RecursiveObjectLeaker.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. 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.util;
9
10 import static com.google.common.base.Preconditions.checkState;
11
12 import com.google.common.annotations.Beta;
13 import java.util.AbstractMap.SimpleEntry;
14 import java.util.ArrayDeque;
15 import java.util.Deque;
16 import java.util.Map.Entry;
17 import javax.annotation.Nullable;
18 import javax.annotation.concurrent.ThreadSafe;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 /**
23  * Thread-local hack to make recursive extensions work without too much hassle. The idea is that prior to instantiating
24  * an extension, the definition object checks whether it is already present on the stack, recorded object is returned.
25  *
26  * <p>
27  * If it is not, it will push itself to the stack as unresolved and invoke the constructor. The constructor's lowermost
28  * class calls to this class and if the topmost entry is not resolved, it will leak itself.
29  *
30  * <p>
31  * Upon return from the constructor, the topmost entry is removed and if the queue is empty, the thread-local variable
32  * will be cleaned up.
33  *
34  * <p>
35  * WARNING: BE CAREFUL WHEN USING THIS CLASS. IT LEAKS OBJECTS WHICH ARE NOT COMPLETELY INITIALIZED.
36  *
37  * <p>
38  * WARNING: THIS CLASS EAVES THREAD-LOCAL RESIDUE. MAKE SURE IT IS OKAY OR CALL {@link #cleanup()} IN APPROPRIATE
39  *          PLACES.
40  *
41  * <p>
42  * THIS CLASS IS EXTREMELY DANGEROUS (okay, not as much as sun.misc.unsafe). YOU HAVE BEEN WARNED. IF SOMETHING BREAKS
43  * IT IS PROBABLY YOUR FAULT AND YOU ARE ON YOUR OWN.
44  *
45  * @author Robert Varga
46  */
47 @Beta
48 @ThreadSafe
49 public final class RecursiveObjectLeaker {
50     // Logging note. Only keys passed can be logged, as objects beng resolved may not be properly constructed.
51     private static final Logger LOG = LoggerFactory.getLogger(RecursiveObjectLeaker.class);
52
53     // Initial value is set to null on purpose, so we do not allocate anything (aside the map)
54     private static final ThreadLocal<Deque<Entry<?, Object>>> STACK = new ThreadLocal<>();
55
56     private RecursiveObjectLeaker() {
57         throw new UnsupportedOperationException();
58     }
59
60     // Key is checked for identity
61     public static void beforeConstructor(final Object key) {
62         Deque<Entry<?, Object>> stack = STACK.get();
63         if (stack == null) {
64             // Biased: this class is expected to be rarely and shallowly used
65             stack = new ArrayDeque<>(1);
66             STACK.set(stack);
67         }
68
69         LOG.debug("Resolving key {}", key);
70         stack.push(new SimpleEntry<>(key, null));
71     }
72
73     // Can potentially store a 'null' mapping. Make sure cleanup() is called
74     public static void inConstructor(final Object obj) {
75         final Deque<Entry<?, Object>> stack = STACK.get();
76         if (stack != null) {
77             final Entry<?, Object> top = stack.peek();
78             if (top != null) {
79                 if (top.getValue() == null) {
80                     LOG.debug("Resolved key {}", top.getKey());
81                     top.setValue(obj);
82                 }
83             } else {
84                 LOG.info("Cleaned stale empty stack", new Exception());
85                 STACK.set(null);
86             }
87         } else {
88             LOG.trace("No thread stack");
89         }
90     }
91
92     // Make sure to call this from a finally block
93     public static void afterConstructor(final Object key) {
94         final Deque<Entry<?, Object>> stack = STACK.get();
95         checkState(stack != null, "No stack allocated when completing %s", key);
96
97         final Entry<?, Object> top = stack.pop();
98         if (stack.isEmpty()) {
99             LOG.trace("Removed empty thread stack");
100             STACK.set(null);
101         }
102
103         checkState(key == top.getKey(), "Expected key %s, have %s", top.getKey(), key);
104         checkState(top.getValue() != null, "");
105     }
106
107     // BEWARE: this method returns incpmpletely-initialized objects (that is the purpose of this class).
108     //
109     //         BE VERY CAREFUL WHAT OBJECT STATE YOU TOUCH
110     public static @Nullable <T> T lookup(final Object key, final Class<T> requiredClass) {
111         final Deque<Entry<?, Object>> stack = STACK.get();
112         if (stack != null) {
113             for (Entry<?, Object> e : stack) {
114                 // Keys are treated as identities
115                 if (key == e.getKey()) {
116                     checkState(e.getValue() != null, "Object for %s is not resolved", key);
117                     LOG.debug("Looked up key {}", e.getKey());
118                     return requiredClass.cast(e.getValue());
119                 }
120             }
121         }
122
123         return null;
124     }
125
126     // Be sure to call this in from a finally block when bulk processing is done, so that this class can be unloaded
127     public static void cleanup() {
128         STACK.remove();
129         LOG.debug("Removed thread state");
130     }
131 }