2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.util;
10 import static com.google.common.base.Preconditions.checkState;
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.concurrent.ThreadSafe;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
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.
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.
31 * Upon return from the constructor, the topmost entry is removed and if the queue is empty, the thread-local variable
35 * WARNING: BE CAREFUL WHEN USING THIS CLASS. IT LEAKS OBJECTS WHICH ARE NOT COMPLETELY INITIALIZED.
38 * WARNING: THIS CLASS EAVES THREAD-LOCAL RESIDUE. MAKE SURE IT IS OKAY OR CALL {@link #cleanup()} IN APPROPRIATE
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.
45 * @author Robert Varga
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);
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<>();
56 private RecursiveObjectLeaker() {
57 throw new UnsupportedOperationException();
60 // Key is checked for identity
61 public static void beforeConstructor(final Object key) {
62 Deque<Entry<?, Object>> stack = STACK.get();
64 // Biased: this class is expected to be rarely and shallowly used
65 stack = new ArrayDeque<>(1);
69 LOG.debug("Resolving key {}", key);
70 stack.push(new SimpleEntry<>(key, null));
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();
77 final Entry<?, Object> top = stack.peek();
79 if (top.getValue() == null) {
80 LOG.debug("Resolved key {}", top.getKey());
84 LOG.info("Cleaned stale empty stack", new Exception());
88 LOG.trace("No thread stack");
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);
97 final Entry<?, Object> top = stack.pop();
98 if (stack.isEmpty()) {
99 LOG.trace("Removed empty thread stack");
103 checkState(key == top.getKey(), "Expected key %s, have %s", top.getKey(), key);
104 checkState(top.getValue() != null, "");
107 // BEWARE: this method returns incpmpletely-initialized objects (that is the purpose of this class).
109 // BE VERY CAREFUL WHAT OBJECT STATE YOU TOUCH
110 public static <T> @Nullable T lookup(final Object key, final Class<T> requiredClass) {
111 final Deque<Entry<?, Object>> stack = STACK.get();
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());
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() {
129 LOG.debug("Removed thread state");