/* * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.mdsal.dom.spi.query; import static com.google.common.base.Preconditions.checkArgument; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.Map.Entry; import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; import org.opendaylight.mdsal.dom.api.query.DOMQuery; import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate; import org.opendaylight.mdsal.dom.api.query.DOMQueryResult; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; @NonNullByDefault public final class DOMQueryEvaluator { private DOMQueryEvaluator() { } /** * Evaluate {@link DOMQuery} on its data element. The element is expected to correspond to * {@link DOMQuery#getRoot()}. * * @param query Query to execute * @param queryRoot Query root object * @return Result of evaluation * @throws NullPointerException if any argument is null */ public static DOMQueryResult evaluateOn(final DOMQuery query, final NormalizedNode queryRoot) { final YangInstanceIdentifier path = query.getSelect(); return path.isEmpty() ? evalSingle(queryRoot, query) : evalPath(queryRoot, query); } /** * Evaluate {@link DOMQuery} on a conceptual root. The element is expected to correspond to the conceptual data tree * root. This method will first find the {@link DOMQuery#getRoot()} and then defer to * {@link #evaluateOn(DOMQuery, NormalizedNode)}. * * @param query Query to execute * @param root Conceptual root object * @return Result of evaluation * @throws NullPointerException if any argument is null */ public static DOMQueryResult evaluateOnRoot(final DOMQuery query, final NormalizedNode root) { NormalizedNode evalRoot = root; for (PathArgument arg : query.getRoot().getPathArguments()) { final Optional> next = NormalizedNodes.findNode(root, arg); if (next.isEmpty()) { return DOMQueryResult.of(); } evalRoot = next.orElseThrow(); } return evaluateOn(query, evalRoot); } private static DOMQueryResult evalSingle(final NormalizedNode data, final DOMQuery query) { return matches(data, query) ? DOMQueryResult.of() : DOMQueryResult.of(new SimpleImmutableEntry<>(query.getRoot(), data)); } private static DOMQueryResult evalPath(final NormalizedNode queryRoot, final DOMQuery query) { // FIXME: this is eager evaluation, we should be doing lazy traversal final List>> result = new ArrayList<>(); evalPath(result, new ArrayDeque<>(query.getRoot().getPathArguments()), new ArrayDeque<>(query.getSelect().getPathArguments()), queryRoot, query); return DOMQueryResult.of(result); } private static void evalPath(final List>> result, final Deque path, final ArrayDeque remaining, final NormalizedNode data, final DOMQuery query) { final PathArgument next = remaining.poll(); if (next == null) { if (matches(data, query)) { result.add(new SimpleImmutableEntry<>(YangInstanceIdentifier.create(path), data)); } return; } if (data instanceof MapNode && next instanceof NodeIdentifier) { checkArgument(data.getIdentifier().equals(next), "Unexpected step %s", next); for (MapEntryNode child : ((MapNode) data).getValue()) { evalChild(result, path, remaining, query, child); } } else { NormalizedNodes.getDirectChild(data, next).ifPresent( child -> evalChild(result, path, remaining, query, child)); } remaining.push(next); } private static void evalChild(final List>> result, final Deque path, final ArrayDeque remaining, final DOMQuery query, final NormalizedNode child) { path.addLast(child.getIdentifier()); evalPath(result, path, remaining, child, query); path.removeLast(); } private static boolean matches(final NormalizedNode data, final DOMQuery query) { for (DOMQueryPredicate pred : query.getPredicates()) { // Okay, now we need to deal with predicates, but do it in a smart fashion, so we do not end up iterating // all over the place. Typically we will be matching just a leaf. final YangInstanceIdentifier path = pred.getPath(); final Optional> node; if (path.coerceParent().isEmpty()) { node = NormalizedNodes.getDirectChild(data, path.getLastPathArgument()); } else { node = NormalizedNodes.findNode(data, path); } if (!pred.test(node.orElse(null))) { return false; } } return true; } }