*/
package org.opendaylight.yangtools.yang.model.api;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.concepts.Immutable;
import org.opendaylight.yangtools.yang.xpath.api.YangBinaryOperator;
import org.opendaylight.yangtools.yang.xpath.api.YangFunction;
import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep;
+import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Relative;
+import org.opendaylight.yangtools.yang.xpath.api.YangPathExpr;
import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis;
import org.opendaylight.yangtools.yang.xpath.api.YangXPathExpression;
@Beta
@NonNullByDefault
public interface PathExpression extends Immutable {
+ /**
+ * Abstract base class for expressing steps of a PathExpression.
+ */
+ abstract class Steps {
+ Steps() {
+ // Prevent external subclassing
+ }
+
+ @Override
+ public abstract int hashCode();
+
+ @Override
+ public abstract boolean equals(@Nullable Object obj);
+
+ @Override
+ public final String toString() {
+ return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
+ }
+
+ abstract ToStringHelper addToStringAttributes(ToStringHelper helper);
+ }
+
+ /**
+ * Steps of a PathExpression which is a LocationPath, corresponding to RFC7950 base specification.
+ */
+ final class LocationPathSteps extends Steps {
+ private final YangLocationPath locationPath;
+
+ public LocationPathSteps(final YangLocationPath locationPath) {
+ this.locationPath = requireNonNull(locationPath);
+ }
+
+ public YangLocationPath getLocationPath() {
+ return locationPath;
+ }
+
+ @Override
+ public int hashCode() {
+ return locationPath.hashCode();
+ }
+
+ @Override
+ public boolean equals(final @Nullable Object obj) {
+ return this == obj
+ || obj instanceof LocationPathSteps && locationPath.equals(((LocationPathSteps) obj).locationPath);
+ }
+
+ @Override
+ ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+ return helper.add("locationPath", locationPath);
+ }
+ }
+
+ /**
+ * Steps of a PathExpression which is a combination of {@code deref()} function call and a relative path,
+ * corresponding to Errata 5617.
+ */
+ final class DerefSteps extends Steps {
+ private final Relative derefArgument;
+ private final Relative relativePath;
+
+ public DerefSteps(final Relative derefArgument, final Relative relativePath) {
+ this.derefArgument = requireNonNull(derefArgument);
+ this.relativePath = requireNonNull(relativePath);
+ }
+
+ public Relative getDerefArgument() {
+ return derefArgument;
+ }
+
+ public Relative getRelativePath() {
+ return relativePath;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * derefArgument.hashCode() + relativePath.hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof DerefSteps)) {
+ return false;
+ }
+ final DerefSteps other = (DerefSteps) obj;
+ return derefArgument.equals(other.derefArgument) && relativePath.equals(other.relativePath);
+ }
+
+ @Override
+ ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+ return helper.add("derefArgument", derefArgument).add("relativePath", relativePath);
+ }
+ }
+
/**
* Returns the path expression formatted string as is defined in model. For example:
* {@code /prefix:container/prefix:container::cond[when()=foo]/prefix:leaf}
String getOriginalString();
/**
- * Return the {@link YangLocationPath} of this expression.
+ * Return the path of this expression, which can either be a {@link YangLocationPath} (compliant to RFC7950) or
+ * a {@link YangPathExpr} with filter being an invocation of {@link YangFunction#DEREF}.
*
- * @return The location path
+ * @return The path's steps
* @throws UnsupportedOperationException if the implementation has not parsed the string. Implementations are
* strongly encouraged to perform proper parsing.
*/
- YangLocationPath getLocation();
+ Steps getSteps();
/**
* Returns <code>true</code> if the XPapth starts in root of YANG model, otherwise returns <code>false</code>.
* @return <code>true</code> if the XPapth starts in root of YANG model, otherwise returns <code>false</code>
*/
default boolean isAbsolute() {
- return getLocation().isAbsolute();
+ final Steps steps = getSteps();
+ return steps instanceof LocationPathSteps && ((LocationPathSteps) steps).getLocationPath().isAbsolute();
}
}
*/
package org.opendaylight.yangtools.yang.model.util;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.base.MoreObjects.ToStringHelper;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
/**
* A simple XPathExpression implementation.
@Deprecated
@NonNullByDefault
public final class PathExpressionImpl extends AbstractPathExpression {
- private final @Nullable YangLocationPath location;
+ private final @Nullable Steps steps;
private final boolean absolute;
@SuppressFBWarnings(value = "NP_STORE_INTO_NONNULL_FIELD", justification = "Non-grok on SpotBugs part")
public PathExpressionImpl(final String xpath, final boolean absolute) {
super(xpath);
this.absolute = absolute;
- this.location = null;
+ this.steps = null;
}
- public PathExpressionImpl(final String xpath, final YangLocationPath location) {
+ public PathExpressionImpl(final String xpath, final Steps steps) {
super(xpath);
- this.absolute = location.isAbsolute();
- this.location = location;
+ this.steps = requireNonNull(steps);
+ this.absolute = steps instanceof LocationPathSteps
+ && ((LocationPathSteps) steps).getLocationPath().isAbsolute();
}
@Override
}
@Override
- public YangLocationPath getLocation() {
- final YangLocationPath loc = location;
+ public Steps getSteps() {
+ final Steps loc = steps;
if (loc == null) {
- throw new UnsupportedOperationException("Location has not been provided");
+ throw new UnsupportedOperationException("Steps have not been provided");
}
return loc;
}
@Override
protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
- return super.addToStringAttributes(helper.add("absolute", absolute).add("location", location));
+ return super.addToStringAttributes(helper.add("absolute", absolute).add("steps", steps));
}
}
RIGHT_PARENTHESIS : ')' ;
CURRENT_KEYWORD : 'current';
+DEREF_KEYWORD : 'deref';
SEP: [ \n\r\t]+ ;
IDENTIFIER : [a-zA-Z_][a-zA-Z0-9_\-.]*;
tokenVocab = LeafRefPathLexer;
}
-path_arg : (absolute_path | relative_path) EOF;
+path_arg : (deref_expr | path_str) EOF;
+
+deref_expr : deref_function_invocation SEP? SLASH SEP? relative_path;
+
+path_str : absolute_path | relative_path;
absolute_path : (SLASH node_identifier (path_predicate)*)+;
current_function_invocation : CURRENT_KEYWORD SEP? LEFT_PARENTHESIS SEP? RIGHT_PARENTHESIS;
+deref_function_invocation : DEREF_KEYWORD SEP? LEFT_PARENTHESIS SEP? relative_path SEP? RIGHT_PARENTHESIS;
+
prefix : identifier;
-identifier: IDENTIFIER | CURRENT_KEYWORD;
+identifier: IDENTIFIER | CURRENT_KEYWORD | DEREF_KEYWORD;
import static java.util.Objects.requireNonNull;
import com.google.common.base.MoreObjects.ToStringHelper;
-import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.opendaylight.yangtools.yang.model.util.AbstractPathExpression;
-import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
+@NonNullByDefault
final class ParsedPathExpression extends AbstractPathExpression {
- private final @NonNull YangLocationPath location;
+ private final Steps steps;
- ParsedPathExpression(final YangLocationPath location, final String originalString) {
+ ParsedPathExpression(final Steps steps, final String originalString) {
super(originalString);
- this.location = requireNonNull(location);
+ this.steps = requireNonNull(steps);
}
@Override
- public YangLocationPath getLocation() {
- return location;
+ public Steps getSteps() {
+ return steps;
}
@Override
protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
- return super.addToStringAttributes(helper.add("location", location));
+ return super.addToStringAttributes(helper.add("steps", steps));
}
}
import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathLexer;
import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser;
import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Absolute_pathContext;
+import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Deref_exprContext;
+import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Deref_function_invocationContext;
import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Descendant_pathContext;
import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Node_identifierContext;
import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Path_argContext;
import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Path_equality_exprContext;
import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Path_key_exprContext;
import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Path_predicateContext;
+import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Path_strContext;
import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Rel_path_keyexprContext;
import org.opendaylight.yangtools.antlrv4.code.gen.LeafRefPathParser.Relative_pathContext;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.UnqualifiedQName;
import org.opendaylight.yangtools.yang.model.api.PathExpression;
+import org.opendaylight.yangtools.yang.model.api.PathExpression.DerefSteps;
+import org.opendaylight.yangtools.yang.model.api.PathExpression.LocationPathSteps;
+import org.opendaylight.yangtools.yang.model.api.PathExpression.Steps;
import org.opendaylight.yangtools.yang.parser.rfc7950.antlr.SourceExceptionParser;
import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
ctx.getStatementSourceReference());
final ParseTree childPath = path.getChild(0);
- final YangLocationPath location;
+ final Steps steps;
+ if (childPath instanceof Path_strContext) {
+ steps = new LocationPathSteps(parsePathStr(ctx, pathArg, (Path_strContext) childPath));
+ } else if (childPath instanceof Deref_exprContext) {
+ final Deref_exprContext deref = (Deref_exprContext) childPath;
+ steps = new DerefSteps(parseRelative(ctx, pathArg,
+ getChild(deref, 0, Deref_function_invocationContext.class).getChild(Relative_pathContext.class, 0)),
+ parseRelative(ctx, pathArg, getChild(deref, deref.getChildCount() - 1, Relative_pathContext.class)));
+ } else {
+ throw new IllegalStateException("Unsupported child " + childPath);
+ }
+ return new ParsedPathExpression(steps, pathArg);
+ }
+
+ private static YangLocationPath parsePathStr(final StmtContext<?, ?, ?> ctx, final String pathArg,
+ final Path_strContext path) {
+ final ParseTree childPath = path.getChild(0);
if (childPath instanceof Absolute_pathContext) {
- location = parseAbsolute(ctx, pathArg, (Absolute_pathContext) childPath);
+ return parseAbsolute(ctx, pathArg, (Absolute_pathContext) childPath);
} else if (childPath instanceof Relative_pathContext) {
- location = parseRelative(ctx, pathArg, (Relative_pathContext) childPath);
+ return parseRelative(ctx, pathArg, (Relative_pathContext) childPath);
} else {
throw new IllegalStateException("Unsupported child " + childPath);
}
-
- return new ParsedPathExpression(location, pathArg);
}
private static Absolute parseAbsolute(final StmtContext<?, ?, ?> ctx, final String pathArg,
private static Relative parseRelative(final StmtContext<?, ?, ?> ctx, final String pathArg,
final Relative_pathContext relative) {
- final List<Step> steps = new ArrayList<>();
-
final int relativeChildren = relative.getChildCount();
verify(relativeChildren % 2 != 0, "Unexpected child count %s", relativeChildren);
- for (int i = 0; i < relativeChildren / 2; ++i) {
+
+ final int stepCount = relativeChildren / 2;
+ final List<Step> steps = new ArrayList<>(stepCount);
+ for (int i = 0; i < stepCount; ++i) {
steps.add(YangXPathAxis.PARENT.asStep());
}
+ return parseRelative(ctx, pathArg, relative, steps);
+ }
+ private static Relative parseRelative(final StmtContext<?, ?, ?> ctx, final String pathArg,
+ final Relative_pathContext relative, final List<Step> steps) {
+ final int relativeChildren = relative.getChildCount();
final Descendant_pathContext descendant = getChild(relative, relativeChildren - 1,
Descendant_pathContext.class);
final Node_identifierContext qname = getChild(descendant, 0, Node_identifierContext.class);
import com.google.common.base.MoreObjects.ToStringHelper;
import org.opendaylight.yangtools.yang.model.util.AbstractPathExpression;
import org.opendaylight.yangtools.yang.parser.rfc7950.stmt.ArgumentUtils;
-import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
final class UnparsedPathExpression extends AbstractPathExpression {
private final RuntimeException cause;
}
@Override
- public YangLocationPath getLocation() {
+ public Steps getSteps() {
throw new UnsupportedOperationException("Expression '" + getOriginalString() + "' was not parsed", cause);
}
package org.opendaylight.yangtools.yang.parser.rfc7950.stmt.path;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.isA;
import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import org.opendaylight.yangtools.yang.common.UnqualifiedQName;
+import org.opendaylight.yangtools.yang.model.api.PathExpression;
+import org.opendaylight.yangtools.yang.model.api.PathExpression.DerefSteps;
+import org.opendaylight.yangtools.yang.model.api.PathExpression.Steps;
import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
+import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
+import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class PathExpressionParserTest {
private StmtContext<?, ?, ?> ctx;
@Mock
private StatementSourceReference ref;
+ private final PathExpressionParser parser = new PathExpressionParser();
@Before
public void before() {
@Test
public void testDerefPath() {
- try {
- // deref() is not valid as per RFC7950, but YANGTOOLS-968 would allow it
- new PathExpressionParser().parseExpression(ctx, "deref(../id)/../type");
- fail("SourceException should have been thrown");
- } catch (SourceException e) {
- assertSame(ref, e.getSourceReference());
- assertThat(e.getMessage(), startsWith("mismatched input '(' expecting "));
- assertThat(e.getMessage(), containsString(" at 1:5 [at "));
- }
+ // deref() is not valid as per RFC7950, but we tolarate it.
+ final PathExpression deref = parser.parseExpression(ctx, "deref(../id)/../type");
+
+ final Steps steps = deref.getSteps();
+ assertThat(steps, isA(DerefSteps.class));
+
+ final DerefSteps derefSteps = (DerefSteps) steps;
+ assertEquals(YangLocationPath.relative(YangXPathAxis.PARENT.asStep(),
+ YangXPathAxis.CHILD.asStep(UnqualifiedQName.of("type"))), derefSteps.getRelativePath());
+ assertEquals(YangLocationPath.relative(YangXPathAxis.PARENT.asStep(),
+ YangXPathAxis.CHILD.asStep(UnqualifiedQName.of("id"))), derefSteps.getDerefArgument());
}
@Test
public void testInvalidLeftParent() {
try {
- new PathExpressionParser().parseExpression(ctx, "foo(");
+ parser.parseExpression(ctx, "foo(");
fail("SourceException should have been thrown");
} catch (SourceException e) {
assertSame(ref, e.getSourceReference());
@Test
public void testInvalidRightParent() {
try {
- new PathExpressionParser().parseExpression(ctx, "foo)");
+ parser.parseExpression(ctx, "foo)");
fail("SourceException should have been thrown");
} catch (SourceException e) {
assertSame(ref, e.getSourceReference());
@Test
public void testInvalidIdentifier() {
try {
- new PathExpressionParser().parseExpression(ctx, "foo%");
+ parser.parseExpression(ctx, "foo%");
fail("SourceException should have been thrown");
} catch (SourceException e) {
assertSame(ref, e.getSourceReference());
assertTrue(leafrefEff.equals(leafrefEff));
}
+ @Test
+ public void testLeafrefWithDeref() {
+ currentLeaf = (LeafSchemaNode) types.getDataChildByName(QName
+ .create(types.getQNameModule(), "leaf-leafref-deref"));
+ assertNotNull(currentLeaf.getType());
+
+ final LeafrefTypeDefinition leafrefEff = ((LeafrefSpecificationEffectiveStatement)
+ ((LeafEffectiveStatement) currentLeaf).effectiveSubstatements().iterator().next())
+ .getTypeDefinition();
+
+ assertEquals("deref(../container-test)/leaf-test",
+ leafrefEff.getPathStatement().getOriginalString());
+ assertNull(leafrefEff.getBaseType());
+ assertEquals(Optional.empty(), leafrefEff.getUnits());
+ assertEquals(Optional.empty(), leafrefEff.getDefaultValue());
+ assertNotNull(leafrefEff.toString());
+ assertEquals("leafref", leafrefEff.getQName().getLocalName());
+ assertEquals(Status.CURRENT, leafrefEff.getStatus());
+ assertNotNull(leafrefEff.getUnknownSchemaNodes());
+ assertEquals("leafref", leafrefEff.getPath().getLastComponent().getLocalName());
+ assertFalse(leafrefEff.getDescription().isPresent());
+ assertFalse(leafrefEff.getReference().isPresent());
+ assertNotNull(leafrefEff.hashCode());
+ assertFalse(leafrefEff.equals(null));
+ assertFalse(leafrefEff.equals("test"));
+ assertTrue(leafrefEff.equals(leafrefEff));
+ }
+
@Test
public void testIntAll() {
currentLeaf = (LeafSchemaNode) types.getDataChildByName(QName.create(types.getQNameModule(), "leaf-int8"));
}
}
+ leaf leaf-leafref-deref {
+ type leafref {
+ path "deref(../container-test)/leaf-test";
+ }
+ }
+
container container-test {
leaf leaf-test {
type int8;
return new YangFunctionCallExpr(name);
}
+ public static YangFunctionCallExpr of(final QName name, final YangExpr argument) {
+ return new WithArgs(name, ImmutableList.of(argument));
+ }
+
public static YangFunctionCallExpr of(final QName name, final List<YangExpr> arguments) {
return arguments.isEmpty() ? of(name) : new WithArgs(name, arguments);
}