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 org.eclipse.jdt.annotation.Nullable;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
22 * Thread-local hack to make recursive extensions work without too much hassle. The idea is that prior to instantiating
23 * an extension, the definition object checks whether it is already present on the stack, recorded object is returned.
26 * If it is not, it will push itself to the stack as unresolved and invoke the constructor. The constructor's lowermost
27 * class calls to this class and if the topmost entry is not resolved, it will leak itself.
30 * Upon return from the constructor, the topmost entry is removed and if the queue is empty, the thread-local variable
34 * WARNING: BE CAREFUL WHEN USING THIS CLASS. IT LEAKS OBJECTS WHICH ARE NOT COMPLETELY INITIALIZED.
37 * WARNING: THIS CLASS LEAVES THREAD-LOCAL RESIDUE. MAKE SURE IT IS OKAY OR CALL {@link #cleanup()} IN APPROPRIATE
41 * THIS CLASS IS EXTREMELY DANGEROUS (okay, not as much as sun.misc.unsafe). YOU HAVE BEEN WARNED. IF SOMETHING BREAKS
42 * IT IS PROBABLY YOUR FAULT AND YOU ARE ON YOUR OWN.
44 * @author Robert Varga
47 public final class RecursiveObjectLeaker {
48 // Logging note. Only keys passed can be logged, as objects beng resolved may not be properly constructed.
49 private static final Logger LOG = LoggerFactory.getLogger(RecursiveObjectLeaker.class);
51 // Initial value is set to null on purpose, so we do not allocate anything (aside the map)
52 private static final ThreadLocal<Deque<Entry<?, Object>>> STACK = new ThreadLocal<>();
54 private RecursiveObjectLeaker() {
55 throw new UnsupportedOperationException();
58 // Key is checked for identity
59 public static void beforeConstructor(final Object key) {
60 Deque<Entry<?, Object>> stack = STACK.get();
62 // Biased: this class is expected to be rarely and shallowly used
63 stack = new ArrayDeque<>(1);
67 LOG.debug("Resolving key {}", key);
68 stack.push(new SimpleEntry<>(key, null));
71 // Can potentially store a 'null' mapping. Make sure cleanup() is called
72 public static void inConstructor(final Object obj) {
73 final Deque<Entry<?, Object>> stack = STACK.get();
75 final Entry<?, Object> top = stack.peek();
77 if (top.getValue() == null) {
78 LOG.debug("Resolved key {}", top.getKey());
82 LOG.info("Cleaned stale empty stack", new Exception());
86 LOG.trace("No thread stack");
90 // Make sure to call this from a finally block
91 public static void afterConstructor(final Object key) {
92 final Deque<Entry<?, Object>> stack = STACK.get();
93 checkState(stack != null, "No stack allocated when completing %s", key);
95 final Entry<?, Object> top = stack.pop();
96 if (stack.isEmpty()) {
97 LOG.trace("Removed empty thread stack");
101 checkState(key == top.getKey(), "Expected key %s, have %s", top.getKey(), key);
102 checkState(top.getValue() != null, "");
105 // BEWARE: this method returns incpmpletely-initialized objects (that is the purpose of this class).
107 // BE VERY CAREFUL WHAT OBJECT STATE YOU TOUCH
108 public static <T> @Nullable T lookup(final Object key, final Class<T> requiredClass) {
109 final Deque<Entry<?, Object>> stack = STACK.get();
111 for (Entry<?, Object> e : stack) {
112 // Keys are treated as identities
113 if (key == e.getKey()) {
114 checkState(e.getValue() != null, "Object for %s is not resolved", key);
115 LOG.debug("Looked up key {}", e.getKey());
116 return requiredClass.cast(e.getValue());
124 // Be sure to call this in from a finally block when bulk processing is done, so that this class can be unloaded
125 public static void cleanup() {
127 LOG.debug("Removed thread state");