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