/*
* Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.yangtools.yang.parser.stmt.rfc6020;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map.Entry;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Thread-local hack to make recursive extensions work without too much hassle. The idea is that prior to instantiating
* an extension, the definition object checks whether it is already present on the stack, recorded object is returned.
*
* If it is not, it will push itself to the stack as unresolved and invoke the constructor. The constructor's lowermost
* class calls to this class and if the topmost entry is not resolved, it will leak itself.
*
* Upon return from the constructor, the topmost entry is removed and if the queue is empty, the thread-local variable
* will be cleaned up.
*
* @author Robert Varga
*/
@Beta
public final class RecursiveObjectLeaker {
// Logging note. Only keys passed can be logged, as objects beng resolved may not be properly constructed.
private static final Logger LOG = LoggerFactory.getLogger(RecursiveObjectLeaker.class);
// Initial value is set to null on purpose, so we do not allocate anything (aside the map)
private static final ThreadLocal<Deque<Entry<?, Object>>> STACK = new ThreadLocal<>();
private RecursiveObjectLeaker() {
throw new UnsupportedOperationException();
}
// Key is checked for identity
public static void beforeConstructor(final Object key) {
Deque<Entry<?, Object>> stack = STACK.get();
if (stack == null) {
// Biased: this class is expected to be rarely and shallowly used
stack = new ArrayDeque<>(1);
STACK.set(stack);
}
LOG.debug("Resolving key {}", key);
stack.push(new SimpleEntry<>(key, null));
}
// Can potentially store a 'null' mapping. Make sure cleanup() is called
public static void inConstructor(final Object obj) {
final Deque<Entry<?, Object>> stack = STACK.get();
if (stack != null) {
final Entry<?, Object> top = stack.peek();
if (top != null) {
if (top.getValue() == null) {
LOG.debug("Resolved key {}", top.getKey());
top.setValue(obj);
}
} else {
LOG.info("Cleaned stale empty stack", new Exception());
STACK.set(null);
}
} else {
LOG.trace("No thread stack");
}
}
// Make sure to call this from a finally block
public static void afterConstructor(final Object key) {
final Deque<Entry<?, Object>> stack = STACK.get();
Preconditions.checkState(stack != null, "No stack allocated when completing %s", key);
final Entry<?, Object> top = stack.pop();
if (stack.isEmpty()) {
LOG.trace("Removed empty thread stack");
STACK.set(null);
}
Preconditions.checkState(key == top.getKey(), "Expected key %s, have %s", top.getKey(), key);
Preconditions.checkState(top.getValue() != null, "");
}
// BEWARE: this method returns incpmpletely-initialized objects (that is the purpose of this class).
//
// BE VERY CAREFUL WHAT OBJECT STATE YOU TOUCH
public static @Nullable <T> T lookup(final Object key, final Class<T> requiredClass) {
final Deque<Entry<?, Object>> stack = STACK.get();
if (stack != null) {
for (Entry<?, Object> e : stack) {
// Keys are treated as identities
if (key == e.getKey()) {
Preconditions.checkState(e.getValue() != null, "Object for %s is not resolved", key);
LOG.debug("Looked up key {}", e.getKey());
return requiredClass.cast(e.getValue());
}
}
}
return null;
}
// Be sure to call this in from a finally block when bulk processing is done, so that this class can be unloaded
public static void cleanup() {
STACK.remove();
LOG.debug("Removed thread state");
}
}