【一课专栏】解构2 - 明察YangInstanceIdentifier

大家好,我是耿兴元,欢迎收听新一期的《解构ODL:从代码到架构设计》。

屈原《离骚》中云:“路漫漫其修远兮,吾将上下而求索”,其实,学习软件编程,学习SDN技术也是一个路漫漫,上下求索的过程。

闲言少叙,本期的内容就是和路径有关。在计算机中,我们最熟悉的路径就是文件系统的目录路径,其表示方式是由"/"分割若干文件夹。另外还有一个可能是大家不怎么常见的-XPath,XPath(XML Path language)也是一种用类似目录树的方法来描述在XML文档中的路径,也使用"/"来表示上下层级间的间隔。但在XPath中,我们还能使用运算符(带谓语的表达式),类似于/bookstore/book[price>35.0]这样。

说到路径,那路径有什么特点呢?首先是路径具有相对性,我们描述一条路径一定是说从哪个节点(树的根节点也是节点)到哪个节点的路径;其次,把若干条路径拼接起来,其形式还是路径,把一条路径从分割符"/"处拆成几部分,每一部分还是路径的形式,也就是说路径在形式上是自包含的。在ODL中,定义了一个Path接口,以描述上面的特性,看一下Path接口的定义:

public interface Path<P extends Path<P>> {

boolean contains(@Nonnull P other);

}

有没有感觉到这个接口定义的很简洁,精练。

那实现这个接口的类有哪些,在yangtools的项目源码中,可以搜索到YangInstanceIdentifier抽象类及其子类FixedYangInstanceIdentifier和StackedYangInstanceIdentifier,在mdsal项目中可以搜索到InstanceIdentifier类,为什么ODL中对数据访问的路径实现类这样命名呢?其实,这个名称来源于YANG语言,YANG语言里有一个内建类型(Built-In Type)instance-identifier,用来表示用YANG定义的数据树中的路径,其语法格式是XPath的简化格式的子集。

我们先看一下YANG语言规范RFC 6020里关于intsance-identifier的例子,了解它的形式和组成。如下的YANG模型

module example {

namespace "ii:example";

prefix "ex";

container system {

description "Contains various system parameters";

container services {

description "Configure externally available services";

container "ssh" {

leaf port {

type uint16;

}

}

}

list user {

key "name";

config true;

description "This is a list of users in the system.";

leaf name {

type string;

}

leaf full-name {

type string;

}

}

container stats {

list ports {

leaf port-number {

type uint16;

}

leaf port-status {

type uint16;

}

}

}

}

}

对于按照上述模型定义写入的数据,我们可以通过下面的instance-identifier索引其中的数据节点。

/* instance-identifier for a container */

/ex:system/ex:services/ex:ssh

/* instance-identifier for a leaf */

/ex:system/ex:services/ex:ssh/ex:port

/* instance-identifier for a list entry */

/ex:system/ex:user[ex:name='fred']

/* instance-identifier for a list entry without keys */

/ex:system/ex:stats/ex:ports[3]

我们可以看到,其表示形式类似于一个文件路径,都是以"/"进行分割,而两个"/"之间,是我们上一篇讲到的QName+附加条件,即带谓语的表达式。

YangInstanceIdentifier的类定义

好,接下来,我们一起看一下yangtools项目里的YangInstanceIdentifier类定义,其源码路径在yang/yang-data-api目录下。

public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentifier>, Immutable, Serializable {

private final int hash;

......

public abstract List<PathArgument> getPathArguments();

boolean contains(@Nonnull final YangInstanceIdentifier other){...}

......

}

这是一个抽象类,为了简洁,上面省略了一些方法声明。从前面我们知道文件系统的目录路径由文件夹名称组成,XPath由XML的元素名称+谓语表达式组成,在ODL中,YangInstanceIdentifier由PathArgument组成,即PathArgument就是组成YangInstanceIdentifier的要素,其定义如下:

public interface PathArgument extends Comparable<PathArgument>, Immutable, Serializable {

QName getNodeType();

String toRelativeString(PathArgument previous);

}

从PathArgument定义能看到,它定义了两个方法 QName getNodeType()和String toRelativeString(PathArgument previous),第一个方法表示它由QName组成,第二个方法表示它包含关系字符串。实际的实现代码中,toRelativeString()方法默认会按照QName的toString()方法返回,但如果previous与当前PathArgument对象的QName属于同一个namespace,则该方法直接返回当前PathArgument对象的QName的localName值;如果其中的QName标识的是一个带key的list,该方法会在原来的返回值基础上附加上"[key-name='key-value']"进行返回;如果其中的QName标识的是一个leaf节点,该方法会在原来的返回值基础上附加上['value']进行返回。以上即具体实现PathArgument这个接口的三个子类NodeIdentifier,NodeIdentifierWithPredicates,NodeWithValue在实现toRelativeString方法时的实现逻辑。实现方法分别见下面

  • NodeIdentifier:

public String toRelativeString(final PathArgument previous) {

if (previous instanceof AbstractPathArgument) {

final QNameModule mod = previous.getNodeType().getModule();

if (getNodeType().getModule().equals(mod)) {

return getNodeType().getLocalName();

}

}

return getNodeType().toString();

}

  • NodeIdentifierWithPredicates:

public String toRelativeString(final PathArgument previous) {

return super.toRelativeString(previous) + '[' + keyValues + ']';

}

NodeWithValue:

public String toRelativeString(final PathArgument previous) {

return super.toRelativeString(previous) + '[' + value + ']';

}

画一下PathArgument接口及其实现类的类图,见下面:

另外,我们通过路径主要想快速的对数据树进行查询,根据节点的路径快速定位到节点,因此在YangInstanceIdentifier类里也定义里了若干抽象方法访问路径,我们在处理YangInstanceIdentifier时也可以直接调用,主要方法方法包括:

YangInstanceIdentifier createRelativeIdentifier(int skipFromRoot);

Collection<PathArgument> tryPathArguments();

Collection<PathArgument> tryReversePathArguments();

boolean isEmpty();

YangInstanceIdentifier getParent();

YangInstanceIdentifier getAncestor(int depth);

List<PathArgument> getPathArguments();

List<PathArgument> getReversePathArguments();

PathArgument getLastPathArgument();

由于YangInstanceIdentifier只是一个抽象类,那肯定就必须要有具体实现类。在yangtools项目源码中,其实现类有两个:FixedYangInstanceIdentifier和StackedYangInstanceIdentifier。这两个实现类主要区别是其内部实现一个按照普通的列表的处理方式实现的,一个是把按照栈的逻辑实现的。这两个实现类不是public的,因此在其定义的package外面是无法访问的。

YangInstanceIdentifier类提供了多个方法方便我们创建YangInstanceIdentifier对象,如下:

YangInstanceIdentifier create(final Iterable<? extends PathArgument> path);

YangInstanceIdentifier create(final PathArgument... path);

YangInstanceIdentifier node(final QName name);

YangInstanceIdentifier node(final PathArgument arg);

YangInstanceIdentifier of(final QName name);

大家可以使用上面的方法创建YangInstanceIdentifier对象。另外,YangInstanceIdentifier类还提供了一个builder()方法,该方法会创建一个`YangInstanceIdentifierBuilder对象,通过这个builder,也可以方便的构建YangInstanceIdentifier对象。

构建YangInstanceIdentifier对象的方法的入参如果是PathArgument类型,我们需要创建该接口的实现类NodeIdentifier或NodeIdentifierWithPredicates或NodeWithValue的实例,作为方法入参。

下面就是本人绘制的YangInstanceIdentifier类及YangInstanceIdentifierBuilder类的类图,供大家参考。

YangInstanceIdentifier的比较

由于YangInstanceIdentifier本质是路经,那在查询和检索数据树时,就避免不了进行YangInstanceIdentifier对象的比较。YangInstanceIdentifier有两个方法进行比较,一个是equals,其实现代码如下:

public boolean equals(final Object obj) {

if (this == obj) {

return true;

}

if (!(obj instanceof YangInstanceIdentifier)) {

return false;

}

YangInstanceIdentifier other = (YangInstanceIdentifier) obj;

if (this.hashCode() != obj.hashCode()) {

return false;

}

return pathArgumentsEqual(other);

}

这个方法覆写了Object的equals()方法,实现代码里第一个if判断即如果引用一致,则两个对象一定相等;第二个if判断,如果两者类型不一致,则肯定不相等,也对后面的强制类型转换作了保护,避免出现异常。再看上面红色部分代码,比较两个对象的hash值,如果两者hash值不同,则两者肯定不相等,最后才调用一个方法去比较YangInstanceIdentifier的PathArgument是否都相同。这段实现代码充分考虑到了效率和异常保护,值得我们参考借鉴。

另外一个比较方法就是Path接口定义的contains()方法,实现代码如下

public final boolean contains(@Nonnull final YangInstanceIdentifier other) {

if (this == other) {

return true;

}

checkArgument(other != null, "other should not be null");

final Iterator<PathArgument> lit = getPathArguments().iterator();

final Iterator<PathArgument> oit = other.getPathArguments().iterator();

while (lit.hasNext()) {

if (!oit.hasNext()) {

return false;

}

if (!lit.next().equals(oit.next())) {

return false;

}

}

return true;

}

从这段代码实现上,可以看出其比较的过程,从第一个PathArgument开始比较,依次迭代进行比较所有源路经里PathArgument是否与目标路径(方法入参other)里的PathArgument相等,如果两者比较过程中源路径已到末尾,源路径最后一个PathArgument仍然相等,则返回true。简单理解上述处理逻辑就是PathArgument依次比较都相等的情况下,短的路经包含长的路经。

InstanceIdentifier解读

其实,我们在基于ODL进行开发时,经常使用的基本上是binding接口,而binding接口的定义,并没有直接使用YangInstanceIdentifier这个类,而是用的类InstanceIdentifier,这个类的定义不在yangtools项目中,而是在mdsal项目的binding/yang-binding目录下,看它的定义:

public class InstanceIdentifier<T extends DataObject> implements Path<InstanceIdentifier<? extends DataObject>>, Immutable, Serializable {

......

final Iterable<PathArgument> pathArguments;

private final Class<T> targetType;

......

}

这个类表示的也是路径,其内部包含了一个迭代器,可以看作就是PathArgument列表。但这个类定义里包含了一个Class类型的变量targetType,把路经与根据yang文件生成的Java类关联了起来,以方便大家可以直接使用根据yang生成的类。

InstanceIdentifier也提供了一个builder类以实现InstanceIdentifier对象的创建,使用方法如下面的形式:

InstanceIdentifierBuilder.builder(Nodes.class).child(Node.class, new NodeKey(new NodeId("openflow:1")).build();

InstanceIdentifier类的代码实现细节比较复杂,待后续整理《从dom到binding》这篇时,可能会再讲一下相关内容。

本篇主要介绍了对应YANG中instance-identifier这个类型,也即数据树中的路径的类的实现源代码,还介绍了它的构造方法及比较的实现机制,这样大家再碰到这个类,在使用这个类的对象时,就会心中有数了。

本期课程就到这里,谢谢大家,我们下期再见!

原文发布于微信公众号 - SDNLAB(SDNLAB)

原文发表时间:2018-09-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏游戏杂谈

Objective-C 内存管理

1) 手动引用计数 MRC (Mannul Reference Counting);

1251
来自专栏技术总结

iOS不可错过的关键字

建议查看原文:https://www.jianshu.com/p/dce05b24d288(不定时更新)

893
来自专栏轮子工厂

5. 很“迷”的字符与字符串

最近一直在为自己的浏览量而担忧啦,都快被厂长大人约谈了……我真的有尽力在写稿子哦,所以也请各位老铁,如果觉得我的文章还不错就转发到朋友圈或者微信群之类的,让更多...

1222
来自专栏编程

linux基础(三)

一、文本处理工具 1、文本查看工具less和cat cat -E filename 能看到行的结束符 -A filename 能看到tab键 回车 (hexdu...

2857
来自专栏IT探索

g++&&gcc

3.C++:在构造函数中,当使用初始化列表来初始化成员变量时,如果初始化顺序与定义成员变量的顺序不一致,当使用-Wreorder选项时,会重新调整顺序初始化顺序...

741
来自专栏机器学习和数学

[编程经验]python2和python3的区别总结

最近涨粉涨的厉害啊,那天看刚破800,今天打开又有32个新粉丝,有点开心,哈哈哈。说实话,看的人多的话,是我坚持下去最大的动力。。。 = = = print("...

3124
来自专栏noteless

[三]java8 函数式编程Stream 概念深入理解 Stream 运行原理 Stream设计思路

        流不是存储元素的数据结构;相反,它通过一个计算操作的管道,从一个数据源,如数据结构、数组、生成器函数或i/o通道中传递元素

4085
来自专栏流柯技术学院

Jmeter函数组件开发

在eclipse新建项目,导入jmeter目录下\lib\ext目录中的的ApacheJMeter_core.jar,继承AbstractFunction类。

1081
来自专栏Brian

Python进阶教程(二)

概述 在上一篇博客中,我们介绍了Python进阶教程(一),还有一些新的技巧没有翻译完,我们下面来继续我们的翻译。 Intermediate Python 中译...

4828
来自专栏求索之路

android阿里面试java基础锦集

接着上一篇 android阿里面试锦集 今天给大家带来一篇 android阿里面试java基础锦集。很多知识都是Thinking in Java上面的,所以如...

37711

扫码关注云+社区

领取腾讯云代金券