= Java Binding Specification 2 Tony Tkacik ; Robert Varga ; Martin Sunal ; Martin Ciglan :rfc6020: https://tools.ietf.org/html/rfc6020 :toc: :toclevels: 4 == Introduction === Terminology Namespace:: naming space, in which all identifiers needs to be unique identifiers. Data Node:: Data Tree:: Instance Identifier:: Instantiated Data Node:: Instantiated Data Tree:: Transfer Object:: Builder:: builder === YANG Identifiers Mapping Every non-Java char in identifier is converted to Java char by its unicode name http://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8 JAVA SE SPECIFICATIONS - Identifiers. This mapping solves various issues from Binding Specification v1, which led to compilation issues. There are special types of mapping non-java chars to original identifiers according to specific Java type: * class, enum, interface ** without special separator the first character of identifier, any other first character of identifier part mapped by ** non-Java char name from unicode and char in identifier behind non-java char name are converting to upper case examples: example* - ExampleAsterisk example*example - ExampleAserisksExample \example - ReverseSolidusExample 1example - DigitOneExample example1 - Example1 int - IntReservedKeyword con - ConReservedKeyword * enum value, constant ** used underscore as special separator ** converted identifier to upper case examples: example* - EXAMPLE_ASTERISK example*example - EXAMPLE_ASTERISK_EXAMPLE \example - REVERSE_SOLIDUS_EXAMPLE 1example - DIGIT_ONE_EXAMPLE example1 - EXAMPLE1 int - INT_RESERVED_KEYWORD con - CON_RESERVED_KEYWORD * method, variable ** without special separator ** the first character of identifier is converting to lower case ** any other first character of identifier part mapped by non-Java char name from unicode and char in identifier behind non-java char name are converting to upper case examples: example* - exampleAsterisk example*example - exampleAserisksExample \example - reverseSolidusExample 1example - digitOneExample example1 - example1 int - intReservedKeyword con - conReservedKeyword * package - full package name - https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html ** parts of package name are separated by dots ** parts of package name are converting to lower case ** if parts of package name are reserved Java or Windows keywords, such as 'int' the suggested convention is to add an underscore to keyword ** dash is parsed as underscore according to https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html examples: org.example* - org.exampleasterisk org.example*example - org.exampleasteriskexample org.example - org.reversesolidusexample org.1example - org.digitoneexample org.example1 - org.example1 org.int - org.int_ org.con - org.con_ org.foo-cont - org.foo_cont ==== Special case - '-' in identifiers There is special case in CLASS, INTERFACE, ENUM, ENUM VALUE, CONSTANT, METHOD and VARIABLE if identifier contains single dash - then the converter ignores the single dash in the way of the non-java chars. In other way, if dash is the first or the last char in the identifier or there is more dashes in a row in the identifier, then these dashes are converted as non-java chars. Example: * class, enum, interface foo-cont - FooCont foo--cont - FooHyphenMinusHyphenMinusCont -foo - HyphenMinusFoo foo- - FooHyphenMinus * enum value, constant foo-cont - FOO_CONT foo--cont - FOO_HYPHEN_MINUS_HYPHEN_MINUS_CONT -foo - HYPHEN_MINUS_FOO foo- - FOO_HYPHEN_MINUS * method, variable foo-cont - fooCont foo--cont - fooHyphenMinusHyphenMinusCont -foo - hyphenMinusFoo foo- - fooHyphenMinus ==== Special case - same class (or enum or interface) names with different camel cases Next special case talks about normalizing class name which already exists in package - but with different camel cases (foo, Foo, fOo, ...). To every next classes with same names will by added their actual rank (serial number), except the first one. This working for CLASS, ENUM and INTEFACE java identifiers. If there exist the same ENUM VALUES in ENUM (with different camel cases), then it's parsed with same logic like CLASSES, ENUMS and INTERFACES but according to list of pairs of their ENUM parent. Example: * class, enum, interface package name org.example, class (or interface or enum) Foo - normalized to Foo package name org.example, class (or interface or enum) fOo - normalized to Foo1 * enum value type enumeration { enum foo; enum Foo; } YANG enum values will be mapped to 'FOO' and 'FOO_1' Java enum values. === Binding Specification v2 Concepts <>:: Represent node, which is instantiable by users as a part of notification, rpc, action or data tree. <>:: Represents node, which is part of instantiated data tree, this interface is not used directly, but rather via <>. See <> for more information. <>:: Represents virtual root of instantiated data tree. <>:: Represents node, which is part of instantiated data tree and is not root of data tree. <>:: Represents instantiated node, which is subjectible to be extended / augmented by `augment` statement from external module. <>:: Represents extension to instantiated node, which is introduced from different model than instantiated node. <>:: Unique identifier of node / subtree in data tree, which provides unambiguous information, how to reference node / subtree in Instantiated Data Tree. [cols="6"] |=== .2+|Statement .2+| In groupings 3+| Instantiable .2+| Augmentable | In Data | In RPC | In Notification | `grouping` | Yes | No | No | No | No | `container` | Yes | Yes | Yes | Yes | Yes | `leaf` | Yes | Yes | Yes | Yes | No | `leaf-list` | Yes | Yes | Yes | Yes | No | `list` | Yes | Yes | Yes | Yes | Yes | `anydata` | Yes | Yes | Yes | Yes | No | `anyxml` | Yes | Yes | Yes | Yes | No | `choice` | Yes | Yes | Yes | Yes | Yes | `case` | Yes | Yes | Yes | Yes | Yes | `input` | Yes | No | Yes | No | Yes | `output` | Yes | No | Yes | No | Yes | `notification` | Yes | No | No | Yes | Yes |=== == Namespaces YANG defines several namespaces and naming space of YANG is wider then applicable namespace of JAVA language. In order to decrease conflicts between various YANG-defined namespaces and classes introduced by Binding Specification, it is needed to: * separate namespaces by Java package hierarchy ** each namespace must define rules how to construct package name, which will not conflict with other namespace * if multiple classes are generated for YANG statement they need to be in separate packages to decrease possible conflicts with siblings. * if Binding Specification introduces new concepts, which does not have explicit namespace rules in YANG, these concepts needs to be in their own, separate namespaces, in order to not conflict on valid YANG namespace items. This rules allows to identify two types of namespaces: .Namespace types by source of namespace YANG namespace:: Naming space explicitly defined in YANG specification, which needs to be explicitly supported in order to prevent naming conflicts. Binding namespace:: Naming space introduced by Binding Specification for additional properties and functionality of Binding Specification. This namespaces needs to be separate from YANG namespaces in order to not have naming conflict with YANG-derived. Binding Specification v2 uses following namespaces: .Concrete namespaces used in Binding Specification <>:: YANG namespace containing representation for all modules. <>:: YANG namespace containing representation for all `identity` statements. Identities needs to be separated to prevent naming conflict between Grouping, Data, Type namespaces. <>:: YANG namespace containing representation for all `typedef` statements and annonymous definitions of `union`, `enumeration` and `bits` types. Types needs to be seperated to prevent naming conflict between Identity, Grouping and Data namespaces. <>:: YANG namespace containing representation for all `grouping` statements and their child data node statements. Groupings needs to be separated to prevent naming conflict between Identity, Type, Data namespaces. <>:: Binding namespace containing representation for all `key` statements. Representations of key statements needs to be in separate namespace, since it is not defined in YANG specification. <>:: YANG namespace containing representation of instantiated data tree. Data needs to be separated to prevent naming conflict between Identity, Type, Grouping namespaces. <>:: Binding namespace containing Transfer Objects and Builders representing instantiated data tree items. NOTE: Most of Binding Namespaces were introduced to decrease possibility of name conflict between concepts defined in YANG and additional concepts introduced by Binding Specification. === Package hierarchy .Package hierarchy for model [cols="1,1,4"] |=== |Namespace | Package | Description | <> | `ident` | flat package containing representation for all `identity` .3+| <> | `type` | flat package containing representations for all top-level `typedef` statements | `type.grp` | path-based package hierarchy containing representation for `typedef` statements nested in grouping statements, or anonymous types requiring code generation defined inside groupings | `type.data` | path-based package hierarchy containing representation for `typedef` statements nested in grouping statements, or anonymous types requiring code generation defined inside instantiated data nodes | <> | `key` | path-based package hierarchy containing representation of key statements for grouping code generation defined inside groupings | <> | `grp` | path-based package hierarchy containing representation for `grouping` statements and data node statements nested in these groupings | <> | `data` | path-based package hierarchy containing representation of instantiated data nodes | <> | `dto` | path-based package hierarchy containing Tranfer Objects and their builders for instantiated data nodes |=== [[module-namespace]] === Module Namespace [[identity-namespace]] === Identity Namespace [[type-namespace]] === Type Namespace [[grouping-namespace]] === Grouping Namespace [[key-namespace]] === Key Namespace [[data-namespace]] === Data Namespace [[dto-namespace]] === Builder Namespace == Generic rules [[class-naming]] === Class Naming [[subpackage-structure]] === Subpackage Naming [[accessor-rules]] === Accessor rules == Type rules === `typedef` statement ==== Globally scoped `typedef` ==== Subtree scoped `typedef` Subtree scoped `typedef` statement is type definition, which is not substatement of `module` or `submodule`, and is only visible to child elements of parent statement. * Representation is generated in Type namespace according to following rules: === Type mapping YANG types does not provide single, simple model of behaviour - some times exhibits special properties to extensibility or limiting scope of valid values when type is derived //// .Base types and their behaviours |=== | YANG Type | Description | Java Mapping | `binary` | Any binary data | `Binary`? | `bits` | A set of bits or flags | Custom class | `boolean` | `true` or `false` | `Boolean` | `decimal64` | 64-bit signed decimal number | No | `empty` | A leaf that does not have any value | No | `enumeration` | Enumerated strings | No | `identityref` | A reference to an abstract identity | Yes | `instance-identifier` | References a data tree node | Yes | `int8` | 8-bit signed integer | No | `int16` | 16-bit signed integer | No | `int32` | 32-bit signed integer | No | `int64` | 64-bit signed integer | No | `leafref` | A reference to a leaf instance | Maybe | `string` | Human-readable string | No | `uint8` | 8-bit unsigned integer | No | `uint16` | 16-bit unsigned integer | No | `uint32` | 32-bit unsigned integer | No | `uint64` | 64-bit unsigned integer | No | `union` | Choice of member types | Maybe |=== FIXME: Finalize table //// ==== `boolean` type ==== `empty` type ==== `int8` type ==== `int16` type ==== `int32` type ==== `int64` type ==== `uint8` type ==== `uint16` type ==== `uint32` type ==== `uint64` type ==== `string` type ==== `binary` type ==== `enumeration` type ==== `bits` type ==== `identityref` type ==== `instance-identifier` type ==== `leafref` type ==== `union` type [[data-node-rules]] == Data Node rules Data nodes could be separated into two distinct groups, based on presence of child nodes: Leaf node:: Node, which according to YANG schema does not have child nodes, is leaf node and carries only simple value. Interior node:: Node, which according to YANG schema may have child nodes, node itself does not carry values, values are stored in descendant leaf nodes. === `leaf` Statement === `leaf-list` Statement === `container` Statement Builders will be located in package "dto" [source,yang] ---- container foo { } container foo-builder { } ---- [uml, file="container-builder.png"] -- set namespaceSeparator none interface data.Foo { } interface data.FooBuilder { } -- In situations where we have a containing element which has as its child a single container, we should make it easy to autobox it. This should not be implemented in the interfaces themselves, but rather should be a method in the associated builder. [source,yang] ---- container example-outter { container example-inner { leaf example { type string; } } } ---- === `list` Statement ==== Keyed List [source,yang] ---- list foo { key identifier key fookey; leaf identifier { type union { type string; } } leaf key { type string; } leaf fookey { type string; } } ---- [uml, file="list-Keyed.png"] -- set namespaceSeparator none interface data.Foo { } interface key.foo.FooIdentifier { } interface key.foo.FooKey { } interface key.foo.FooFooKey { } interface type.foo.identifier.IdentifierUnion { } data.Foo o- key.foo.FooIdentifier data.Foo o- key.foo.FooKey data.Foo o- key.foo.FooFooKey key.foo.FooIdentifier o- type.foo.identifier.IdentifierUnion -- ==== List without Key === `choice` Statement === `case` Statement [source,yang] ---- container top { choice base { case foo { container foo; } case bar { leaf bar { type string; } } } } ---- [uml, file="case.png"] -- set namespaceSeparator none package spec { interface Choice interface Case } interface data.Top { + getBase() : data.top.Base; } interface data.top.Base interface data.top.base.Foo { + getFoo() : data.top.base.foo.Foo } interface data.top.base.foo.Foo interface data.top.base.Bar { + getBar() : String } data.top.Base -u-|> Choice data.top.base.Foo -u-|> Case data.top.base.Bar -u-|> Case data.top.base.Foo -u-|> data.top.Base data.top.base.Bar -u-|> data.top.Base data.Top o- data.top.Base data.top.base.Foo o- data.top.base.foo.Foo -- == Specific rules [[instantiated-data-node-rules]] === Instantiated Data Node Rules //// FIXME: Do we need section per type, or should just general rules be described. //// ==== `container` Statement //// FIXME: Here should be Augmentable & Instantiated //// ==== `leaf` Statement ==== `leaf-list` Statement ==== `choice` Statement ==== `case` Statement //// FIXME: Here should be Augmentable & Instantiated //// ==== `list` Statement //// FIXME: Here should be Augmentable & Instantiated, List signature uses concrete interfaces //// ==== `input` Statement //// FIXME: Here should be Augmentable & Instantiated //// ==== `output` Statement //// FIXME: Here should be Augmentable & Instantiated //// ==== `notification` Statement //// FIXME: Here should be Augmentable & Instantiated //// [[instantiated-data-tree-rules]] === Instantiated Data Tree Rules ==== `container` Statement //// FIXME: Here should be Augmentable & Instantied & ChildDataNode //// ==== `leaf` Statement ==== `leaf-list` Statement ==== `case` Statement //// FIXME: Here should be Augmentable & Instantied & ChildDataNode //// ==== `list` Statement //// FIXME: Here should be Augmentable & Instantied & ChildDataNode //// === `grouping` Statement * `grouping` statement is represented by `interface` ** interface name is generated according to <> with suffix `Grouping` * Representations of `grouping` statements are generated into <> * schema nodes under grouping are represented by `interface` and are generated into <> + name of grouping ** getters (accessors) from parent nodes are generated according to <> ** class name is generated according to <> with suffix `Data` ** schema nodes does not follow <>, these interfaces are used only in instantiated data tree. .Simple Grouping ==== .YANG Snippet [source, yang] ---- grouping simple { <1> container foo; <2> leaf bar { type string;} <3> } ---- <1> Is represented by interface `grp.SimpleGrouping` <2> Is represented by interface `grp.simple.FooData` and getter in `grp.SimpleGrouping` with signature `public grp.simple.FooData getFoo();` <3> Is represented by getter in `grp.SimpleGrouping` with signature `public String getBar()` [uml, file="grouping1.png"] -- interface grp.SimpleGrouping { + getBar() : String + getFoo() : grp.simple.FooData } interface grp.simple.FooData grp.SimpleGrouping o- grp.simple.FooData -- ==== ==== Data Node substatements Representations of data node substatements are generated according to rules described in <> with following changes: //// MS: proposed interface names: case - Case choice - <Choice container, list - //// //// MC: I would keep Data suffix, but idea about distinguishing cases and choices is to think about //// * Interface names for `case`, `choice`, `container` and `list`, is suffixed by `Data` suffix, in order to not conflict with same named groupings inside same package ** Getters in parent node, are still generated without `Data` suffix, so the getter signature is in form `FooData getFoo()` ** If return value of getter is constructed using generics (eg. `list`) instead of signature `List` or `Map`, wildcarded `? extends ListItem` generic argument are used to allow for overriding during <>. ==== `list` substatement //// FIXME: Add reasoning / examples for need to use ? extends, instead of directly using generics. //// ==== `leaf-list` susbstatement //// FIXME: Add reasoning / examples for need to use ? extends, instead of directly using generics for types, which may need instantiation //// [[uses-statement]] === `uses` Statement * `uses` statement triggers interface of parent statement to extend (implement) interface of `grouping` referenced by `uses` argument. * As in YANG `uses` statement triggers instatiation of data children of `grouping` which will result in generation of these children as-if they were direct children of parent statement ** data node children are generated according to rules defined for parent statement. Different rules apply based on parent type (instantiated data tree, `input`, `output` or `grouping`) ** interfaces generated for data children extends (implements) interfaces for same children generated for referenced `grouping` .Simple Grouping and Uses ==== .YANG Snippet [source, yang] ---- grouping simple { container foo; leaf bar { type string;} } container top { uses simple; } ---- [uml, file="grouping2.png"] -- set namespaceSeparator none interface grp.SimpleGrouping { + getBar() : String + getFoo() : grp.simple.FooData } interface grp.simple.FooData interface data.Top { + getFoo() : data.top.Foo } interface data.top.Foo grp.SimpleGrouping o-- grp.simple.FooData data.Top o-- data.top.Foo data.Top -|> grp.SimpleGrouping data.top.Foo -|> grp.simple.FooData -- NOTE: Diagram does not show all details for `data.Top` and `data.top.Foo`, which are based on <> ==== .Grouping with Nested Grouping ==== .YANG Snippet [source, yang] ---- grouping with-inner { grouping inner { container cont; } uses inner; } container top { uses with-inner; } ---- [uml, file="grouping3.png"] -- set namespaceSeparator none interface grp.withinner.inner.ContData interface grp.withinner.InnerGrouping { + getCont() : grp.withinner.inner.ContData } interface grp.withinner.ContData interface grp.WithInnerGrouping { + getCont() : grp.withinner.ContData } interface data.Top { + getCont() : data.top.Cont } interface data.top.Cont { } data.Top o-- data.top.Cont : contains data.Top -|> grp.WithInnerGrouping data.top.Cont -|> grp.withinner.ContData grp.WithInnerGrouping -|> grp.withinner.InnerGrouping : uses (implements) grp.WithInnerGrouping o-- grp.withinner.ContData : contains grp.withinner.InnerGrouping o-- grp.withinner.inner.ContData : contains grp.withinner.ContData -|> grp.withinner.inner.ContData : is concretization of (implements) -- NOTE: Diagram does not show all details for `data.Top` and `data.top.Cont`, which are based on <> ==== [[uses-augment]] ==== `augment` substatement .Uses & Augment in instantiated Data Tree ==== [source,yang] ---- grouping example { container nested { leaf foo { type string; } } } container top { uses example { augment nested { container bar { } } } } ---- [uml, file="grouping4.png"] -- set namespaceSeparator none interface data.Top interface data.top.Nested interface data.top.nested.Bar data.Top o-- data.top.Nested data.top.Nested o-- data.top.nested.Bar interface grp.ExampleGrouping interface grp.example.NestedData grp.ExampleGrouping o-- grp.example.NestedData data.Top -|> grp.ExampleGrouping data.top.Nested -|> grp.example.NestedData -- NOTE: Diagram does not show all details for `data.Top`, `data.top.Nested` and `data.top.nested.Bar`, which are based on <> ==== .Uses & Augment in grouping ==== [source,yang] ---- grouping example { container nested { leaf foo { type string; } } } grouping top { uses example { augment nested { container bar { } } } } ---- [uml, file="grouping5.png"] -- set namespaceSeparator none interface grp.TopGrouping interface grp.top.NestedData interface grp.top.nested.BarData grp.TopGrouping o-- grp.top.NestedData grp.top.NestedData o-- grp.top.nested.BarData interface grp.ExampleGrouping interface grp.example.NestedData grp.ExampleGrouping o-- grp.example.NestedData grp.TopGrouping -|> grp.ExampleGrouping grp.top.NestedData -|> grp.example.NestedData -- ==== === `augment` statement Representation of `augment` statement depends on module in which target node of augment statement is defined * <> - data nodes are represented as-if their statements were inlined in target node. See <> section for details. * <> - data nodes are represented as-if their statements were inlined in target. See <> for details & examples. * <> - interface representing augmentation is generated, child data nodes are generated by rules for <>. See <> for details & examples. `augment` statement targets only instantiated data nodes, so child data nodes representation is always generated. [[augment-same-module]] ==== Augmentation target in same module All data node children are generated as-if they were directly defined inside target node. There are no externally observable artefacts in generated representation of these nodes, which would point out that they were defined using `augment` statement instead of directly inlining them in target node. .Why augment of same module is same as inlining [IMPORTANT] ==== This rule may seems counterintuitive at first sight, but YANG defines backwards compatibility in terms of effective model instead of way how model is represented. `augment` statement, when targeting node in same module is not externally observable and could factored out by inlining these statements. Definition of `augment` statement in YANG also defines different behaviour when target is same module and allows all features as-if this statements were directly inlined. ==== .Augment with target in same module ==== .YANG module written using augmentations [source,yang] ---- container top { } augment "/top" { container foo { } } ---- .Same module written without need to augment ---- container top { container foo { } } ---- .Same module written with grouping ---- grouping a { container foo { } } container top { uses a; } ---- Java representation for all variants [uml, file="augment1.png"] -- set namespaceSeparator none interface data.Top interface data.top.Foo data.Top o- data.top.Foo -- ==== [[augment-other-module]] ==== Augmentation target in other module .Augment with target in other module ==== [source,yang] ---- module top { ... container top { } } module foo { ... import top { prefix top; } ... augment "/top:top" { container bar { } } } ---- [uml,file="augment2.png"] -- set namespaceSeparator none interface Augmentable interface Augmentation interface top.data.Top interface foo.data.FooTop { + getBar() : Bar } interface foo.data.top.Bar top.data.Top -u-|> Augmentable : T = top.data.Top foo.data.FooTop -u-|> Augmentation : T = top.data.Top top.data.Top o-- foo.data.FooTop foo.data.FooTop o-- foo.data.top.Bar -- ==== .Multiple augments with same target ==== [source,yang] ---- module top { ... container top { } } module foo { ... import top { prefix top; } ... augment "/top:top" { container bar { } } augment "/top:top" { container baz { } } } ---- [uml,file="augment3.png"] -- set namespaceSeparator none interface Augmentable interface Augmentation interface top.data.Top interface foo.data.FooTop { + getBar() : Bar + getBaz() : Baz } interface foo.data.top.Bar interface foo.data.top.Baz top.data.Top -u-|> Augmentable : T = top.data.Top foo.data.FooTop -u-|> Augmentation : T = top.data.Top top.data.Top o-- foo.data.FooTop foo.data.FooTop o-- foo.data.top.Bar foo.data.FooTop o-- foo.data.top.Baz -- ==== .Multiple augments with different targets ==== [source,yang] ---- module target { ... container first { } container second { } } module foo { ... import target { prefix t; } ... augment "/t:first" { container bar { } } augment "/t:second" { container baz { } } } ---- [uml, file="augment4.png"] -- set namespaceSeparator none interface Augmentable interface Augmentation interface target.data.First interface target.data.Second interface foo.data.FooFirst { + getBar() : Bar } interface foo.data.FooSecond { + getBaz() : Baz } interface foo.data.first.Bar interface foo.data.second.Baz target.data.First -u-|> Augmentable : T = target.data.First target.data.Second -u-|> Augmentable : T = target.data.Second foo.data.FooFirst -u-|> Augmentation : T = target.data.First foo.data.FooSecond -u-|> Augmentation : T = target.data.Second target.data.First o-- foo.data.FooFirst target.data.Second o-- foo.data.FooSecond foo.data.FooFirst o-- foo.data.first.Bar foo.data.FooSecond o-- foo.data.second.Baz -- ==== .Key in grouping ==== [source,yang] ---- grouping nodes { list node { key id; leaf id { type string; } } } ---- grouping key.grp.nodes.node. instantiated key.data.nodes.node.