import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace;
import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType;
import org.opendaylight.yangtools.yang.parser.stmt.reactor.SourceSpecificContext.PhaseCompletionProgress;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.RecursiveObjectLeaker;
import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.EffectiveSchemaContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
List<DeclaredStatement<?>> rootStatements = new ArrayList<>(sources.size());
List<EffectiveStatement<?,?>> rootEffectiveStatements = new ArrayList<>(sources.size());
- for (SourceSpecificContext source : sources) {
- final RootStatementContext<?, ?, ?> root = source.getRoot();
- rootStatements.add(root.buildDeclared());
- rootEffectiveStatements.add(root.buildEffective());
+ try {
+ for (SourceSpecificContext source : sources) {
+ final RootStatementContext<?, ?, ?> root = source.getRoot();
+ rootStatements.add(root.buildDeclared());
+ rootEffectiveStatements.add(root.buildEffective());
+ }
+ } finally {
+ RecursiveObjectLeaker.cleanup();
}
return new EffectiveSchemaContext(rootStatements, rootEffectiveStatements);
*/
package org.opendaylight.yangtools.yang.parser.stmt.rfc6020;
-import java.util.HashSet;
-import java.util.Set;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.model.api.Rfc6020Mapping;
import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
}
public static class Definition extends AbstractStatementSupport<QName,ExtensionStatement,EffectiveStatement<QName,ExtensionStatement>> {
- private static final ThreadLocal<Set<StmtContext<?, ?, ?>>> BUILDING = new ThreadLocal<>();
-
public Definition() {
super(Rfc6020Mapping.EXTENSION);
}
@Override
public EffectiveStatement<QName,ExtensionStatement> createEffective(
- final StmtContext<QName,ExtensionStatement, EffectiveStatement<QName,ExtensionStatement>> ctx) {
- Set<StmtContext<?, ?, ?>> building = BUILDING.get();
- if (building == null) {
- building = new HashSet<>();
- BUILDING.set(building);
+ final StmtContext<QName,ExtensionStatement ,EffectiveStatement<QName,ExtensionStatement>> ctx) {
+
+ // Look at the thread-local leak in case we are invoked recursively
+ final ExtensionEffectiveStatementImpl existing = RecursiveObjectLeaker.lookup(ctx,
+ ExtensionEffectiveStatementImpl.class);
+ if (existing != null) {
+ // Careful! this not fully initialized!
+ return existing;
}
- SourceException.throwIf(building.contains(ctx), ctx.getStatementSourceReference(),
- "Extension %s references itself", ctx.getStatementArgument());
-
- building.add(ctx);
+ RecursiveObjectLeaker.beforeConstructor(ctx);
try {
+ // This result is fine, we know it has been completely initialized
return new ExtensionEffectiveStatementImpl(ctx);
} finally {
- building.remove(ctx);
- if (building.isEmpty()) {
- BUILDING.remove();
- }
+ RecursiveObjectLeaker.afterConstructor(ctx);
}
}
--- /dev/null
+/*
+ * 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");
+ }
+}
import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.RecursiveObjectLeaker;
public abstract class EffectiveStatementBase<A, D extends DeclaredStatement<A>> implements EffectiveStatement<A, D> {
}
substatementsInit.addAll(effectiveSubstatements);
+ // WARNING: this leaks an incompletely-initialized pbject
+ RecursiveObjectLeaker.inConstructor(this);
+
this.substatements = ImmutableList.copyOf(Collections2.transform(Collections2.filter(substatementsInit,
IS_SUPPORTED_TO_BUILD_EFFECTIVE), StmtContextUtils.buildEffective()));
}
package org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective;
import com.google.common.collect.ImmutableList;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Deque;
import java.util.List;
import java.util.Objects;
import org.opendaylight.yangtools.yang.common.QName;
public class ExtensionEffectiveStatementImpl extends AbstractEffectiveDocumentedNode<QName, ExtensionStatement>
implements ExtensionDefinition {
+ private static final class RecursionDetector extends ThreadLocal<Deque<ExtensionEffectiveStatementImpl>> {
+ boolean check(final ExtensionEffectiveStatementImpl current) {
+ final Deque<ExtensionEffectiveStatementImpl> stack = get();
+ if (stack != null) {
+ for (ExtensionEffectiveStatementImpl s : stack) {
+ if (s == current) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ void push(final ExtensionEffectiveStatementImpl current) {
+ Deque<ExtensionEffectiveStatementImpl> stack = get();
+ if (stack == null) {
+ stack = new ArrayDeque<>(1);
+ set(stack);
+ }
+
+ stack.push(current);
+ }
+
+ void pop() {
+ Deque<ExtensionEffectiveStatementImpl> stack = get();
+ stack.pop();
+ if (stack.isEmpty()) {
+ remove();
+ }
+ }
+ }
+
+ private static final RecursionDetector TOSTRING_DETECTOR = new RecursionDetector();
+
private final QName qname;
private final String argument;
private final SchemaPath schemaPath;
@Override
public String toString() {
- StringBuilder sb = new StringBuilder(ExtensionEffectiveStatementImpl.class.getSimpleName());
- sb.append("[");
- sb.append("argument=").append(argument);
- sb.append(", qname=").append(qname);
- sb.append(", schemaPath=").append(schemaPath);
- sb.append(", extensionSchemaNodes=").append(unknownNodes);
- sb.append(", yin=").append(yin);
- sb.append("]");
- return sb.toString();
+ if (TOSTRING_DETECTOR.check(this)) {
+ return recursedToString();
+ }
+
+ TOSTRING_DETECTOR.push(this);
+ try {
+ StringBuilder sb = new StringBuilder(ExtensionEffectiveStatementImpl.class.getSimpleName());
+ sb.append("[");
+ sb.append("argument=").append(argument);
+ sb.append(", qname=").append(qname);
+ sb.append(", schemaPath=").append(schemaPath);
+ sb.append(", extensionSchemaNodes=").append(unknownNodes);
+ sb.append(", yin=").append(yin);
+ sb.append("]");
+ return sb.toString();
+ } finally {
+ TOSTRING_DETECTOR.pop();
+ }
+ }
+
+ private String recursedToString() {
+ return ExtensionEffectiveStatementImpl.class.getSimpleName() + "[" +
+ "argument=" + argument +
+ ", qname=" + qname +
+ ", schemaPath=" + schemaPath +
+ ", yin=" + yin +
+ " <RECURSIVE> ]";
}
}
nodeType = ctx.getPublicDefinition().getArgumentName();
} else {
extension = (ExtensionEffectiveStatementImpl) extensionInit.buildEffective();
- nodeType = extension.getQName();
+ nodeType = null;
}
// initCopyType
@Override
public QName getNodeType() {
- return nodeType;
+ return extension == null ? nodeType : extension.getQName();
}
@Override
@Override
public String toString() {
+ final QName type = getNodeType();
+
StringBuilder sb = new StringBuilder();
- sb.append(nodeType.getNamespace());
+ sb.append(type.getNamespace());
sb.append(":");
- sb.append(nodeType.getLocalName());
+ sb.append(type.getLocalName());
sb.append(" ");
sb.append(nodeParameter);
return sb.toString();
}
-
}
import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
public class Bug4456Test {
- @Test(expected=SourceException.class)
+ @Test
public void test() throws IOException, URISyntaxException, SourceException, ReactorException {
SchemaContext schema = StmtTestUtils.parseYangSources("/bugs/bug4456");
assertNotNull(schema);