/*
* 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.spi.meta;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.Beta;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.base.Verify;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.concepts.AbstractSimpleIdentifiable;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
import org.opendaylight.yangtools.yang.parser.spi.SchemaTreeNamespace;
/**
* Definition / implementation of specific Identifier Namespace behaviour. A namespace behaviour is built on top
* of a tree of {@link NamespaceStorageNode} which represents local context of one of types defined
* n {@link StorageNodeType}.
*
*
* For common behaviour models please use static factories {@link #global(Class)}, {@link #sourceLocal(Class)} and
* {@link #treeScoped(Class)}.
*
* @param Key type
* @param Value type
* @param Namespace Type
*/
public abstract class NamespaceBehaviour>
extends AbstractSimpleIdentifiable> {
public enum StorageNodeType {
/**
* Global storage, visible from all sources.
*/
GLOBAL,
/**
* Storage of the root statement of a particular source and any sources it is importing.
*/
// FIXME: 7.0.0: this is a misnomer and should be renamed
SOURCE_LOCAL_SPECIAL,
/**
* Storage of a single statement.
*/
STATEMENT_LOCAL,
/**
* Storage of the root statement of a particular source.
*/
ROOT_STATEMENT_LOCAL
}
public interface Registry {
/**
* Get a namespace behavior.
*
* @param type Namespace type class
* @param key type
* @param value type
* @param namespace type
* @return Namespace behaviour
* @throws NamespaceNotAvailableException when the namespace is not available
*/
> NamespaceBehaviour getNamespaceBehaviour(Class type);
}
public interface NamespaceStorageNode {
/**
* Return local namespace behaviour type.
*
* @return local namespace behaviour type {@link NamespaceBehaviour}
*/
StorageNodeType getStorageNodeType();
@Nullable NamespaceStorageNode getParentNamespaceStorage();
> @Nullable V getFromLocalStorage(Class type, K key);
> @Nullable Map getAllFromLocalStorage(Class type);
/**
* Populate specified namespace with a key/value pair, overwriting previous contents. Similar to
* {@link Map#put(Object, Object)}.
*
* @param type Namespace identifier
* @param key Key
* @param value Value
* @return Previously-stored value, or null if the key was not present
*/
> @Nullable V putToLocalStorage(Class type, K key, V value);
/**
* Populate specified namespace with a key/value pair unless the key is already associated with a value. Similar
* to {@link Map#putIfAbsent(Object, Object)}.
*
* @param type Namespace identifier
* @param key Key
* @param value Value
* @return Preexisting value or null if there was no previous mapping
*/
> @Nullable V putToLocalStorageIfAbsent(Class type, K key, V value);
}
/**
* Interface implemented by {@link NamespaceStorageNode}s which support dynamic addition of child elements as they
* are requested. This means that such a node can, defer creation of child namespace storage nodes, in effect lazily
* expanding this namespace on an if-needed basis.
*/
@Beta
public interface OnDemandSchemaTreeStorageNode extends NamespaceStorageNode {
/**
* Request that a new member of this node's schema tree statement be added. Implementations are required to
* perform lookup in their internal structure and create a child if tractable. Resulting node is expected to
* have been registered with local storage, so that it is accessible through
* {@link #getFromLocalStorage(Class, Object)}.
*
*
* This method must not change its mind about a child's presence -- once it returns non-present, it has to be
* always returning non-present.
*
*
* The results produced by this method are expected to be consistent with
* {@link SchemaTreeAwareEffectiveStatement#findSchemaTreeNode(QName)} and
* {@link SchemaTreeNamespace#getFrom(NamespaceStorageNode, QName)}.
*
* @param qname node identifier of the child being requested
* @return Requested child, if it is present.
* @throws NullPointerException in {@code qname} is null
*/
, E extends SchemaTreeEffectiveStatement>
@Nullable StmtContext requestSchemaTreeChild(QName qname);
}
protected NamespaceBehaviour(final Class identifier) {
super(identifier);
}
/**
* Creates a global namespace behaviour for supplied namespace type. Global behaviour stores and loads all values
* from root {@link NamespaceStorageNode} with type of {@link StorageNodeType#GLOBAL}.
*
* @param identifier Namespace identifier.
* @param type parameter
* @param type parameter
* @param type parameter
* @return global namespace behaviour for supplied namespace type.
*/
public static > @NonNull NamespaceBehaviour global(
final Class identifier) {
return new StorageSpecific<>(identifier, StorageNodeType.GLOBAL);
}
/**
* Creates source-local namespace behaviour for supplied namespace type. Source-local namespace behaviour stores
* and loads all values from closest {@link NamespaceStorageNode} ancestor with type
* of {@link StorageNodeType#SOURCE_LOCAL_SPECIAL}.
*
* @param identifier Namespace identifier.
* @param type parameter
* @param type parameter
* @param type parameter
* @return source-local namespace behaviour for supplied namespace type.
*/
public static > @NonNull NamespaceBehaviour sourceLocal(
final Class identifier) {
return new StorageSpecific<>(identifier, StorageNodeType.SOURCE_LOCAL_SPECIAL);
}
public static > @NonNull NamespaceBehaviour statementLocal(
final Class identifier) {
return new StatementLocal<>(identifier);
}
/**
* Creates a root-statement-local namespace behaviour for supplied namespace type. Root-statement-local namespace
* behaviour stores and loads all values from closest {@link NamespaceStorageNode} ancestor with type
* of {@link StorageNodeType#ROOT_STATEMENT_LOCAL}.
*
* @param identifier Namespace identifier.
* @param type parameter
* @param type parameter
* @param type parameter
* @return root-statement-local namespace behaviour for supplied namespace type.
*/
public static > @NonNull NamespaceBehaviour rootStatementLocal(
final Class identifier) {
return new StorageSpecific<>(identifier, StorageNodeType.ROOT_STATEMENT_LOCAL);
}
/**
* Creates tree-scoped namespace behaviour for supplied namespace type. Tree-scoped namespace behaviour searches
* for value in all storage nodes up to the root and stores values in supplied node.
*
* @param identifier
* Namespace identifier.
* @param type parameter
* @param type parameter
* @param type parameter
* @return tree-scoped namespace behaviour for supplied namespace type.
*/
public static > @NonNull NamespaceBehaviour treeScoped(
final Class identifier) {
return new TreeScoped<>(identifier);
}
/**
* Returns a value from model namespace storage according to key param class.
*
* @param storage namespace storage
* @param key type parameter
* @return value from model namespace storage according to key param class
*/
public abstract V getFrom(NamespaceStorageNode storage, K key);
/**
* Returns the key/value mapping best matching specified criterion.
*
* @param storage namespace storage
* @param criterion selection criterion
* @return Selected mapping, if available.
*/
public final Optional> getFrom(final NamespaceStorageNode storage,
final NamespaceKeyCriterion criterion) {
final Map mappings = getAllFrom(storage);
if (mappings == null) {
return Optional.empty();
}
Entry match = null;
for (Entry entry : mappings.entrySet()) {
final K key = entry.getKey();
if (criterion.match(key)) {
if (match != null) {
final K selected = criterion.select(match.getKey(), key);
if (selected.equals(match.getKey())) {
continue;
}
Verify.verify(selected == key, "Criterion %s selected invalid key %s from candidates [%s %s]",
selected, match.getKey(), key);
}
match = entry;
}
}
return Optional.ofNullable(match);
}
/**
* Returns all values of a keys of param class from model namespace storage.
*
* @param storage namespace storage
* @return all values of keys of param class from model namespace storage
*/
public abstract Map getAllFrom(NamespaceStorageNode storage);
/**
* Adds a key/value to corresponding namespace storage according to param class.
*
* @param storage namespace storage
* @param key type parameter
* @param value type parameter
*/
public abstract void addTo(NamespaceStorageNode storage, K key, V value);
protected final V getFromLocalStorage(final NamespaceStorageNode storage, final K key) {
return storage.getFromLocalStorage(getIdentifier(), key);
}
protected final Map getAllFromLocalStorage(final NamespaceStorageNode storage) {
return storage.getAllFromLocalStorage(getIdentifier());
}
protected final void addToStorage(final NamespaceStorageNode storage, final K key, final V value) {
storage.putToLocalStorage(getIdentifier(), key, value);
}
abstract static class AbstractSpecific>
extends NamespaceBehaviour {
AbstractSpecific(final Class identifier) {
super(identifier);
}
@Override
public final V getFrom(final NamespaceStorageNode storage, final K key) {
return getFromLocalStorage(findStorageNode(storage), key);
}
@Override
public final Map getAllFrom(final NamespaceStorageNode storage) {
return getAllFromLocalStorage(findStorageNode(storage));
}
@Override
public final void addTo(final NamespaceStorageNode storage, final K key, final V value) {
addToStorage(findStorageNode(storage), key, value);
}
abstract NamespaceStorageNode findStorageNode(NamespaceStorageNode storage);
}
static final class StatementLocal> extends AbstractSpecific {
StatementLocal(final Class identifier) {
super(identifier);
}
@Override
NamespaceStorageNode findStorageNode(final NamespaceStorageNode storage) {
return storage;
}
}
static final class StorageSpecific> extends AbstractSpecific {
private final StorageNodeType storageType;
StorageSpecific(final Class identifier, final StorageNodeType type) {
super(identifier);
storageType = requireNonNull(type);
}
@Override
NamespaceStorageNode findStorageNode(final NamespaceStorageNode storage) {
return findClosestTowardsRoot(storage, storageType);
}
@Override
protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
return super.addToStringAttributes(helper.add("type", storageType));
}
}
static final class TreeScoped> extends NamespaceBehaviour {
TreeScoped(final Class identifier) {
super(identifier);
}
@Override
public V getFrom(final NamespaceStorageNode storage, final K key) {
NamespaceStorageNode current = storage;
while (current != null) {
final V val = getFromLocalStorage(current, key);
if (val != null) {
return val;
}
current = current.getParentNamespaceStorage();
}
return null;
}
@Override
public Map getAllFrom(final NamespaceStorageNode storage) {
NamespaceStorageNode current = storage;
while (current != null) {
final Map val = getAllFromLocalStorage(current);
if (val != null) {
return val;
}
current = current.getParentNamespaceStorage();
}
return null;
}
@Override
public void addTo(final NamespaceStorageNode storage, final K key, final V value) {
addToStorage(storage, key, value);
}
}
protected static NamespaceStorageNode findClosestTowardsRoot(final NamespaceStorageNode storage,
final StorageNodeType type) {
NamespaceStorageNode current = storage;
while (current != null && current.getStorageNodeType() != type) {
current = current.getParentNamespaceStorage();
}
return current;
}
@Override
protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
return helper.add("identifier", getIdentifier().getName());
}
}