import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public final class SchemaContextUtil {
private static final Logger LOG = LoggerFactory.getLogger(SchemaContextUtil.class);
private static final Splitter COLON_SPLITTER = Splitter.on(':');
- private static final Splitter SLASH_SPLITTER = Splitter.on('/');
+ private static final Splitter SLASH_SPLITTER = Splitter.on('/').omitEmptyStrings();
private SchemaContextUtil() {
}
Preconditions.checkArgument(parentModule != null, "Parent Module reference cannot be NULL");
Preconditions.checkArgument(xpath != null, "XPath string reference cannot be NULL");
- final List<QName> path = new LinkedList<>();
+ final List<QName> path = new ArrayList<>();
for (final String pathComponent : SLASH_SPLITTER.split(xpath)) {
- if (!pathComponent.isEmpty()) {
- path.add(stringPathPartToQName(context, parentModule, pathComponent));
- }
+ path.add(stringPathPartToQName(context, parentModule, pathComponent));
}
return path;
}
Preconditions.checkState(actualSchemaNode.getPath() != null,
"Schema Path reference for Leafref cannot be NULL");
- final Iterable<String> xpaths = SLASH_SPLITTER.split(relativeXPath.toString());
+ List<String> xpaths = new ArrayList<>();
+ splitXPath(relativeXPath.toString(), xpaths);
- // Find out how many "parent" components there are
- // FIXME: is .contains() the right check here?
- // FIXME: case ../../node1/node2/../node3/../node4
- int colCount = 0;
- for (final Iterator<String> it = xpaths.iterator(); it.hasNext() && it.next().contains(".."); ) {
- ++colCount;
+ // Find out how many "parent" components there are and trim them
+ final int colCount = normalizeXPath(xpaths);
+ if (colCount != 0) {
+ xpaths = xpaths.subList(colCount, xpaths.size());
}
final Iterable<QName> schemaNodePath = actualSchemaNode.getPath().getPathFromRoot();
if (Iterables.size(schemaNodePath) - colCount >= 0) {
return Iterables.concat(Iterables.limit(schemaNodePath, Iterables.size(schemaNodePath) - colCount),
- Iterables.transform(Iterables.skip(xpaths, colCount),
- input -> stringPathPartToQName(context, module, input)));
+ Iterables.transform(xpaths, input -> stringPathPartToQName(context, module, input)));
}
return Iterables.concat(schemaNodePath,
- Iterables.transform(Iterables.skip(xpaths, colCount),
- input -> stringPathPartToQName(context, module, input)));
+ Iterables.transform(xpaths, input -> stringPathPartToQName(context, module, input)));
+ }
+
+ @VisibleForTesting
+ static int normalizeXPath(final List<String> xpath) {
+ LOG.trace("Normalize {}", xpath);
+
+ // We need to make multiple passes here, as the leading XPaths as we can have "../abc/../../def", which really
+ // is "../../def"
+ while (true) {
+ // Next up: count leading ".." components
+ int leadingParents = 0;
+ while (true) {
+ if (leadingParents == xpath.size()) {
+ return leadingParents;
+ }
+ if (!"..".equals(xpath.get(leadingParents))) {
+ break;
+ }
+
+ ++leadingParents;
+ }
+
+ // Now let's see if there there is a '..' in the rest
+ final int dots = findDots(xpath, leadingParents + 1);
+ if (dots == -1) {
+ return leadingParents;
+ }
+
+ xpath.remove(dots - 1);
+ xpath.remove(dots - 1);
+ LOG.trace("Next iteration {}", xpath);
+ }
+ }
+
+ private static int findDots(final List<String> xpath, final int startIndex) {
+ for (int i = startIndex; i < xpath.size(); ++i) {
+ if ("..".equals(xpath.get(i))) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private static void splitXPath(final String xpath, final List<String> output) {
+ // This is a major hack, but should do the trick for now.
+ final int deref = xpath.indexOf("deref(");
+ if (deref == -1) {
+ doSplitXPath(xpath, output);
+ return;
+ }
+
+ // Interpret leading part
+ doSplitXPath(xpath.substring(0, deref), output);
+
+ // Find matching parentheses
+ final int start = deref + 6;
+ final int paren = xpath.indexOf(')', start);
+ Preconditions.checkArgument(paren != -1, "Cannot find matching parentheses in %s", xpath);
+
+ // Interpret the argument
+ doSplitXPath(xpath.substring(start, paren), output);
+
+ // And now the last bit
+ splitXPath(xpath.substring(paren + 1), output);
+ }
+
+ private static void doSplitXPath(final String xpath, final List<String> output) {
+ SLASH_SPLITTER.split(xpath).forEach(output::add);
}
/**
package org.opendaylight.yangtools.yang.model.util;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doReturn;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
import java.net.URI;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Optional;
+import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.runners.MockitoJUnitRunner;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaPath;
import org.opendaylight.yangtools.yang.model.util.type.BaseTypes;
+@RunWith(MockitoJUnitRunner.class)
public class SchemaContextUtilTest {
+ private static final Splitter SPACE_SPLITTER = Splitter.on(' ');
private static final URI NAMESPACE = URI.create("abc");
+
+ // The idea is:
+ // container baz {
+ // leaf xyzzy {
+ // type leafref;
+ // }
+ // leaf foo {
+ // type string;
+ // }
+ // leaf bar {
+ // type string;
+ // }
+ // }
+ private static final QName FOO = QName.create(NAMESPACE, "foo");
+ private static final QName BAR = QName.create(NAMESPACE, "bar");
+ private static final QName BAZ = QName.create(NAMESPACE, "baz");
+ private static final QName XYZZY = QName.create(NAMESPACE, "xyzzy");
+
@Mock
private SchemaContext mockSchemaContext;
@Mock
private Module mockModule;
- @Test
- public void testFindDummyData() {
- MockitoAnnotations.initMocks(this);
+ @Mock
+ private SchemaNode schemaNode;
+
+ @Before
+ public void before() {
doReturn(Optional.empty()).when(mockSchemaContext).findModule(any(QNameModule.class));
doReturn(Optional.empty()).when(mockSchemaContext).findDataTreeChild(any(Iterable.class));
doReturn(QNameModule.create(NAMESPACE)).when(mockModule).getQNameModule();
doReturn(Optional.empty()).when(mockModule).getRevision();
+ doReturn(SchemaPath.create(true, BAZ, XYZZY)).when(schemaNode).getPath();
+ }
+
+ @Test
+ public void testFindDummyData() {
+
QName qname = QName.create("namespace", "localname");
SchemaPath schemaPath = SchemaPath.create(Collections.singletonList(qname), true);
- assertEquals("Should be null. Module TestQName not found", null,
+ assertNull("Should be null. Module TestQName not found",
SchemaContextUtil.findDataSchemaNode(mockSchemaContext, schemaPath));
RevisionAwareXPath xpath = new RevisionAwareXPathImpl("/test:bookstore/test:book/test:title", true);
- assertEquals("Should be null. Module bookstore not found", null,
+ assertNull("Should be null. Module bookstore not found",
SchemaContextUtil.findDataSchemaNode(mockSchemaContext, mockModule, xpath));
- SchemaNode schemaNode = BaseTypes.int32Type();
+ SchemaNode int32node = BaseTypes.int32Type();
RevisionAwareXPath xpathRelative = new RevisionAwareXPathImpl("../prefix", false);
- assertEquals("Should be null, Module prefix not found", null,
+ assertNull("Should be null, Module prefix not found",
SchemaContextUtil.findDataSchemaNodeForRelativeXPath(
- mockSchemaContext, mockModule, schemaNode, xpathRelative));
+ mockSchemaContext, mockModule, int32node, xpathRelative));
- assertEquals("Should be null. Module TestQName not found", null,
+ assertNull("Should be null. Module TestQName not found",
SchemaContextUtil.findNodeInSchemaContext(mockSchemaContext, Collections.singleton(qname)));
- assertEquals("Should be null.", null, SchemaContextUtil.findParentModule(mockSchemaContext, schemaNode));
+ assertNull("Should be null.", SchemaContextUtil.findParentModule(mockSchemaContext, int32node));
+ }
+
+ @Test
+ public void testDeref() {
+ RevisionAwareXPath xpath = new RevisionAwareXPathImpl("deref(../foo)/../bar", false);
+ assertNull(SchemaContextUtil.findDataSchemaNodeForRelativeXPath(mockSchemaContext, mockModule, schemaNode,
+ xpath));
+ }
+
+ @Test
+ public void testNormalizeXPath() {
+ assertNormalizedPath(0, ImmutableList.of(""), "");
+ assertNormalizedPath(0, ImmutableList.of("a"), "a");
+ assertNormalizedPath(0, ImmutableList.of("a", "b"), "a b");
+ assertNormalizedPath(1, ImmutableList.of("..", "b"), ".. b");
+ assertNormalizedPath(0, ImmutableList.of(), "a ..");
+ assertNormalizedPath(0, ImmutableList.of("b"), "a .. b");
+ assertNormalizedPath(2, ImmutableList.of("..", "..", "a", "c"), ".. .. a b .. c");
+ assertNormalizedPath(3, ImmutableList.of("..", "..", "..", "b"), ".. .. a .. .. b");
+ }
+
+ private static void assertNormalizedPath(final int expectedLead, final List<String> expectedList,
+ final String input) {
+ final List<String> list = new ArrayList<>(SPACE_SPLITTER.splitToList(input));
+ assertEquals(expectedLead, SchemaContextUtil.normalizeXPath(list));
+ assertEquals(expectedList, list);
}
}