/*
* Copyright (c) 2017 Cisco Systems, Inc. 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.binding.javav2.generator.impl;
import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.annotateDeprecatedIfNecessary;
import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.augGenTypeName;
import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.constructGetter;
import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.createDescription;
import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.getAugmentIdentifier;
import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.qNameConstant;
import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil.packageNameForGeneratedType;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes;
import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifier;
import org.opendaylight.mdsal.binding.javav2.generator.util.NonJavaCharsConverter;
import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
import org.opendaylight.mdsal.binding.javav2.generator.util.generated.type.builder.GeneratedTypeBuilderImpl;
import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
import org.opendaylight.mdsal.binding.javav2.model.api.Type;
import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilder;
import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentable;
import org.opendaylight.mdsal.binding.javav2.util.BindingMapping;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
import org.opendaylight.yangtools.yang.model.api.SchemaPath;
import org.opendaylight.yangtools.yang.model.api.UsesNode;
import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
/**
* Helper util class used for generation of types in Binding spec v2.
*/
@Beta
final class GenHelperUtil {
private GenHelperUtil() {
throw new UnsupportedOperationException("Util class");
}
/**
* Create GeneratedTypeBuilder object from module argument.
*
* @param module
* Module object from which builder will be created
* @param genCtx
* @param verboseClassComments
*
* @return GeneratedTypeBuilder
which is internal
* representation of the module
* @throws IllegalArgumentException
* if module is null
*/
static GeneratedTypeBuilder moduleToDataType(final Module module, Map genCtx, final boolean verboseClassComments) {
Preconditions.checkArgument(module != null, "Module reference cannot be NULL.");
final GeneratedTypeBuilder moduleDataTypeBuilder = moduleTypeBuilder(module, "Data", verboseClassComments);
addImplementedInterfaceFromUses(module, moduleDataTypeBuilder, genCtx);
moduleDataTypeBuilder.addImplementsType(BindingTypes.TREE_ROOT);
moduleDataTypeBuilder.addComment(module.getDescription());
moduleDataTypeBuilder.setDescription(createDescription(module, verboseClassComments));
moduleDataTypeBuilder.setReference(module.getReference());
return moduleDataTypeBuilder;
}
/**
* Generates type builder for module
.
*
* @param module
* Module which is source of package name for generated type
* builder
* @param postfix
* string which is added to the module class name representation
* as suffix
* @param verboseClassComments
* @return instance of GeneratedTypeBuilder which represents
* module
.
* @throws IllegalArgumentException
* if module
is null
*/
private static GeneratedTypeBuilder moduleTypeBuilder(final Module module, final String postfix, final boolean
verboseClassComments) {
Preconditions.checkArgument(module != null, "Module reference cannot be NULL.");
final String packageName = BindingMapping.getRootPackageName(module);
final String moduleName = BindingMapping.getClassName(NonJavaCharsConverter.convertIdentifier(module.getName
(), JavaIdentifier.CLASS)) + postfix;
final GeneratedTypeBuilderImpl moduleBuilder = new GeneratedTypeBuilderImpl(packageName, moduleName);
moduleBuilder.setDescription(createDescription(module, verboseClassComments));
moduleBuilder.setReference(module.getReference());
moduleBuilder.setModuleName(moduleName);
return moduleBuilder;
}
/**
* Adds the implemented types to type builder.
*
* The method passes through the list of uses in
* {@code dataNodeContainer}. For every use is obtained corresponding
* generated type from all groupings
* allGroupings} which is added as implements type to
* builder
*
* @param dataNodeContainer
* element which contains the list of used YANG groupings
* @param builder
* builder to which are added implemented types according to
* dataNodeContainer
* @param genCtx generated context
* @return generated type builder with all implemented types
*/
private static GeneratedTypeBuilder addImplementedInterfaceFromUses(final DataNodeContainer dataNodeContainer,
final GeneratedTypeBuilder builder, Map genCtx) {
for (final UsesNode usesNode : dataNodeContainer.getUses()) {
final GeneratedType genType = findGroupingByPath(usesNode.getGroupingPath(), genCtx).toInstance();
if (genType == null) {
throw new IllegalStateException("Grouping " + usesNode.getGroupingPath() + "is not resolved for "
+ builder.getName());
}
builder.addImplementsType(genType);
}
return builder;
}
static GeneratedTypeBuilder findGroupingByPath(final SchemaPath path, Map genCtx) {
for (final ModuleContext ctx : genCtx.values()) {
final GeneratedTypeBuilder result = ctx.getGrouping(path);
if (result != null) {
return result;
}
}
return null;
}
/**
* Adds the methods to typeBuilder
which represent subnodes of
* node for which typeBuilder
was created.
*
* The subnodes aren't mapped to the methods if they are part of grouping or
* augment (in this case are already part of them).
*
* @param module
* current module
* @param basePackageName
* string contains the module package name
* @param parent
* generated type builder which represents any node. The subnodes
* of this node are added to the typeBuilder
as
* methods. The subnode can be of type leaf, leaf-list, list,
* container, choice.
* @param childOf
* parent type
* @param schemaNodes
* set of data schema nodes which are the children of the node
* for which typeBuilder
was created
* @return generated type builder which is the same builder as input
* parameter. The getter methods (representing child nodes) could be
* added to it.
*/
static GeneratedTypeBuilder resolveDataSchemaNodes(final Module module, final String basePackageName,
final GeneratedTypeBuilder parent, final GeneratedTypeBuilder childOf,
final Iterable schemaNodes, Map genCtx,
final SchemaContext schemaContext, final boolean verboseClassComments,
Map> genTypeBuilders) {
if (schemaNodes != null && parent != null) {
for (final DataSchemaNode schemaNode : schemaNodes) {
if (!schemaNode.isAugmenting() && !schemaNode.isAddedByUses()) {
addSchemaNodeToBuilderAsMethod(basePackageName, schemaNode, parent, childOf, module, genCtx,
schemaContext, verboseClassComments, genTypeBuilders);
}
}
}
return parent;
}
static GeneratedTypeBuilder addDefaultInterfaceDefinition(final String packageName, final SchemaNode
schemaNode, final Module module, Map genCtx, final SchemaContext schemaContext,
final boolean verboseClassComments, Map> genTypeBuilders) {
return addDefaultInterfaceDefinition(packageName, schemaNode, null, module, genCtx, schemaContext,
verboseClassComments, genTypeBuilders);
}
static Map processUsesAugments(final SchemaContext schemaContext, final
DataNodeContainer node, final Module module, Map genCtx, Map> genTypeBuilders, final boolean verboseClassComments) {
final String basePackageName = BindingMapping.getRootPackageName(module);
for (final UsesNode usesNode : node.getUses()) {
for (final AugmentationSchema augment : usesNode.getAugmentations()) {
genCtx = AugmentToGenType.usesAugmentationToGenTypes(schemaContext, basePackageName, augment, module,
usesNode,
node, genCtx, genTypeBuilders, verboseClassComments);
genCtx = processUsesAugments(schemaContext, augment, module, genCtx, genTypeBuilders, verboseClassComments);
}
}
return genCtx;
}
static GeneratedTypeBuilder findChildNodeByPath(final SchemaPath path, Map genCtx) {
for (final ModuleContext ctx : genCtx.values()) {
final GeneratedTypeBuilder result = ctx.getChildNode(path);
if (result != null) {
return result;
}
}
return null;
}
static GeneratedTypeBuilder findCaseByPath(final SchemaPath path, Map genCtx) {
for (final ModuleContext ctx : genCtx.values()) {
final GeneratedTypeBuilder result = ctx.getCase(path);
if (result != null) {
return result;
}
}
return null;
}
/**
* Returns a generated type builder for an augmentation.
*
* The name of the type builder is equal to the name of augmented node with
* serial number as suffix.
*
* @param module
* current module
* @param augmentPackageName
* string with contains the package name to which the augment
* belongs
* @param basePackageName
* string with the package name to which the augmented node
* belongs
* @param targetTypeRef
* target type
* @param augSchema
* augmentation schema which contains data about the child nodes
* and uses of augment
* @return generated type builder for augment in genCtx
*/
static Map addRawAugmentGenTypeDefinition(final Module module, final String augmentPackageName,
final String basePackageName, final Type targetTypeRef, final AugmentationSchema augSchema,
Map> genTypeBuilders, Map genCtx) {
Map augmentBuilders = genTypeBuilders.get(augmentPackageName);
if (augmentBuilders == null) {
augmentBuilders = new HashMap<>();
genTypeBuilders.put(augmentPackageName, augmentBuilders);
}
final String augIdentifier = getAugmentIdentifier(augSchema.getUnknownSchemaNodes());
String augTypeName;
if (augIdentifier != null) {
augTypeName = BindingMapping.getClassName(augIdentifier);
} else {
augTypeName = augGenTypeName(augmentBuilders, targetTypeRef.getName());
}
GeneratedTypeBuilder augTypeBuilder = new GeneratedTypeBuilderImpl(augmentPackageName, augTypeName);
augTypeBuilder.addImplementsType(BindingTypes.TREE_NODE);
augTypeBuilder.addImplementsType(Types.augmentationTypeFor(targetTypeRef));
annotateDeprecatedIfNecessary(augSchema.getStatus(), augTypeBuilder);
augTypeBuilder = addImplementedInterfaceFromUses(augSchema, augTypeBuilder, genCtx);
augTypeBuilder = augSchemaNodeToMethods(module, basePackageName, augTypeBuilder, augTypeBuilder, augSchema
.getChildNodes());
augmentBuilders.put(augTypeName, augTypeBuilder);
if(!augSchema.getChildNodes().isEmpty()) {
genCtx.get(module).addTypeToAugmentation(augTypeBuilder, augSchema);
}
genCtx.get(module).addAugmentType(augTypeBuilder);
return genCtx;
}
/**
* Adds the methods to typeBuilder
what represents subnodes of
* node for which typeBuilder
was created.
*
* @param module
* current module
* @param basePackageName
* string contains the module package name
* @param typeBuilder
* generated type builder which represents any node. The subnodes
* of this node are added to the typeBuilder
as
* methods. The subnode can be of type leaf, leaf-list, list,
* container, choice.
* @param childOf
* parent type
* @param schemaNodes
* set of data schema nodes which are the children of the node
* for which typeBuilder
was created
* @return generated type builder which is the same object as the input
* parameter typeBuilder
. The getter method could be
* added to it.
*/
private static GeneratedTypeBuilder augSchemaNodeToMethods(final Module module, final String basePackageName,
final GeneratedTypeBuilder typeBuilder, final GeneratedTypeBuilder childOf,
final Iterable schemaNodes) {
if ((schemaNodes != null) && (typeBuilder != null)) {
for (final DataSchemaNode schemaNode : schemaNodes) {
if (!schemaNode.isAugmenting()) {
//TODO: design decomposition and implement it
//addSchemaNodeToBuilderAsMethod(basePackageName, schemaNode, typeBuilder, childOf, module);
}
}
}
return typeBuilder;
}
/**
* Instantiates generated type builder with packageName
and
* schemaNode
.
*
* The new builder always implements
* {@link TreeNode TreeNode}.
* If schemaNode
is instance of GroupingDefinition it also
* implements {@link Augmentable
* Augmentable}.
* If schemaNode
is instance of
* {@link org.opendaylight.yangtools.yang.model.api.DataNodeContainer
* DataNodeContainer} it can also implement nodes which are specified in
* uses.
*
* @param packageName
* string with the name of the package to which
* schemaNode
belongs.
* @param schemaNode
* schema node for which is created generated type builder
* @param parent
* parent type (can be null)
* @param schemaContext schema context
* @return generated type builder schemaNode
*/
private static GeneratedTypeBuilder addDefaultInterfaceDefinition(final String packageName, final SchemaNode
schemaNode, final Type parent, final Module module, Map genCtx,
final SchemaContext schemaContext, final boolean verboseClassComments, Map> genTypeBuilders) {
GeneratedTypeBuilder it = addRawInterfaceDefinition(packageName, schemaNode, schemaContext, "",
verboseClassComments, genTypeBuilders);
if (parent == null) {
it.addImplementsType(BindingTypes.TREE_NODE);
} else {
it.addImplementsType(BindingTypes.treeChildNode(parent));
}
if (!(schemaNode instanceof GroupingDefinition)) {
it.addImplementsType(BindingTypes.augmentable(it));
}
if (schemaNode instanceof DataNodeContainer) {
//TODO: design decomposition and implement it
//groupingsToGenTypes(module, ((DataNodeContainer) schemaNode).getGroupings());
it = addImplementedInterfaceFromUses((DataNodeContainer) schemaNode, it, genCtx);
}
return it;
}
/**
* Returns reference to generated type builder for specified
* schemaNode
with packageName
.
*
* Firstly the generated type builder is searched in
* {@link BindingGeneratorImpl#genTypeBuilders genTypeBuilders}. If it isn't
* found it is created and added to genTypeBuilders
.
*
* @param packageName
* string with the package name to which returning generated type
* builder belongs
* @param schemaNode
* schema node which provide data about the schema node name
* @param schemaContext
* @param prefix
* return type name prefix
* @return generated type builder for schemaNode
* @throws IllegalArgumentException
*
* - if
schemaNode
is null
* - if
packageName
is null
* - if QName of schema node is null
* - if schemaNode name is null
*
*
*/
private static GeneratedTypeBuilder addRawInterfaceDefinition(final String packageName, final SchemaNode schemaNode,
final SchemaContext schemaContext, final String prefix, final boolean verboseClassComments,
Map> genTypeBuilders) {
Preconditions.checkArgument(schemaNode != null, "Data Schema Node cannot be NULL.");
Preconditions.checkArgument(packageName != null, "Package Name for Generated Type cannot be NULL.");
final String schemaNodeName = schemaNode.getQName().getLocalName();
Preconditions.checkArgument(schemaNodeName != null, "Local Name of QName for Data Schema Node cannot be NULL.");
String genTypeName;
if (prefix == null) {
genTypeName = BindingMapping.getClassName(NonJavaCharsConverter.convertIdentifier(schemaNodeName,
JavaIdentifier.CLASS));
} else {
genTypeName = prefix + BindingMapping.getClassName(NonJavaCharsConverter.convertIdentifier
(schemaNodeName, JavaIdentifier.CLASS));
}
final GeneratedTypeBuilderImpl newType = new GeneratedTypeBuilderImpl(packageName, genTypeName);
final Module module = SchemaContextUtil.findParentModule(schemaContext, schemaNode);
qNameConstant(newType, BindingMapping.QNAME_STATIC_FIELD_NAME, schemaNode.getQName());
newType.addComment(schemaNode.getDescription());
newType.setDescription(createDescription(schemaNode, newType.getFullyQualifiedName(), schemaContext, verboseClassComments));
newType.setReference(schemaNode.getReference());
newType.setSchemaPath((List) schemaNode.getPath().getPathFromRoot());
newType.setModuleName(module.getName());
if (!genTypeBuilders.containsKey(packageName)) {
final Map builders = new HashMap<>();
builders.put(genTypeName, newType);
genTypeBuilders.put(packageName, builders);
} else {
final Map builders = genTypeBuilders.get(packageName);
if (!builders.containsKey(genTypeName)) {
builders.put(genTypeName, newType);
}
}
return newType;
}
private static void addSchemaNodeToBuilderAsMethod(final String basePackageName, final DataSchemaNode node,
final GeneratedTypeBuilder typeBuilder, final GeneratedTypeBuilder childOf, final Module module,
Map genCtx, final SchemaContext schemaContext, final boolean verboseClassComments,
Map> genTypeBuilders) {
//TODO: implement rest of schema nodes GTO building
if (node != null && typeBuilder != null) {
if (node instanceof ContainerSchemaNode) {
containerToGenType(module, basePackageName, typeBuilder, childOf, (ContainerSchemaNode) node,
schemaContext, verboseClassComments, genCtx, genTypeBuilders);
}
}
}
private static void containerToGenType(final Module module, final String basePackageName,
final GeneratedTypeBuilder parent, final GeneratedTypeBuilder childOf, final ContainerSchemaNode node,
final SchemaContext schemaContext, final boolean verboseClassComments, Map genCtx,
Map> genTypeBuilders) {
final GeneratedTypeBuilder genType = processDataSchemaNode(module, basePackageName, childOf, node,
schemaContext, verboseClassComments, genCtx, genTypeBuilders);
if (genType != null) {
constructGetter(parent, node.getQName().getLocalName(), node.getDescription(), genType, node.getStatus());
resolveDataSchemaNodes(module, basePackageName, genType, genType, node.getChildNodes(), genCtx,
schemaContext, verboseClassComments, genTypeBuilders);
}
}
private static GeneratedTypeBuilder processDataSchemaNode(final Module module, final String basePackageName,
final GeneratedTypeBuilder childOf, final DataSchemaNode node, final SchemaContext schemaContext,
final boolean verboseClassComments, Map genCtx, Map> genTypeBuilders) {
if (node.isAugmenting() || node.isAddedByUses()) {
return null;
}
final String packageName = packageNameForGeneratedType(basePackageName, node.getPath(), BindingNamespaceType.Data);
final GeneratedTypeBuilder genType = addDefaultInterfaceDefinition(packageName, node, childOf, module,
genCtx, schemaContext, verboseClassComments, genTypeBuilders);
genType.addComment(node.getDescription());
annotateDeprecatedIfNecessary(node.getStatus(), genType);
genType.setDescription(createDescription(node, genType.getFullyQualifiedName(), schemaContext, verboseClassComments));
genType.setModuleName(module.getName());
genType.setReference(node.getReference());
genType.setSchemaPath((List) node.getPath().getPathFromRoot());
if (node instanceof DataNodeContainer) {
genCtx.get(module).addChildNodeType(node, genType);
//TODO: implement groupings to GTO building first
// groupingsToGenTypes(module, ((DataNodeContainer) node).getGroupings());
processUsesAugments(schemaContext, (DataNodeContainer) node, module, genCtx, genTypeBuilders,
verboseClassComments);
}
return genType;
}
}