/* * 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.addTOToTypeBuilder; 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.createReturnTypeForUnion; import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.getAugmentIdentifier; import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.isInnerType; import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.qNameConstant; import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.resolveInnerEnumFromTypeDefinition; import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil.packageNameForGeneratedType; import static org.opendaylight.yangtools.yang.model.util.SchemaContextUtil.findParentModule; import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import java.util.HashMap; import java.util.List; import java.util.Map; import org.opendaylight.mdsal.binding.javav2.generator.spi.TypeProvider; import org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil; 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.generator.yang.types.TypeProviderImpl; import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType; import org.opendaylight.mdsal.binding.javav2.model.api.Restrictions; import org.opendaylight.mdsal.binding.javav2.model.api.Type; import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.EnumBuilder; import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTOBuilder; import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilder; import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.MethodSignatureBuilder; 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.LeafSchemaNode; 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.TypeDefinition; import org.opendaylight.yangtools.yang.model.api.UsesNode; import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition; 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, final 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, final 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, final 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, final Map genCtx, final SchemaContext schemaContext, final boolean verboseClassComments, final Map> genTypeBuilders, final TypeProvider typeProvider) { 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, typeProvider); } } } return parent; } static GeneratedTypeBuilder addDefaultInterfaceDefinition(final String packageName, final SchemaNode schemaNode, final Module module, final Map genCtx, final SchemaContext schemaContext, final boolean verboseClassComments, final 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, final Map> genTypeBuilders, final boolean verboseClassComments, final TypeProvider typeProvider) { 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, typeProvider); genCtx = processUsesAugments(schemaContext, augment, module, genCtx, genTypeBuilders, verboseClassComments, typeProvider); } } return genCtx; } static GeneratedTypeBuilder findChildNodeByPath(final SchemaPath path, final 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, final 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, final Map> genTypeBuilders, final 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, final Map genCtx, final SchemaContext schemaContext, final boolean verboseClassComments, final 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, final 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.normalizeClassIdentifier(packageName, schemaNodeName)); } else { genTypeName = prefix + BindingMapping .getClassName(NonJavaCharsConverter.normalizeClassIdentifier(packageName, schemaNodeName)); } 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, final Map genCtx, final SchemaContext schemaContext, final boolean verboseClassComments, final Map> genTypeBuilders, final TypeProvider typeProvider) { //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, typeProvider); } else if (node instanceof LeafSchemaNode) { resolveLeafSchemaNodeAsMethod(schemaContext, typeBuilder, genCtx, (LeafSchemaNode) node, module, typeProvider); } } } 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, final Map genCtx, final Map> genTypeBuilders, final TypeProvider typeProvider) { final GeneratedTypeBuilder genType = processDataSchemaNode(module, basePackageName, childOf, node, schemaContext, verboseClassComments, genCtx, genTypeBuilders, typeProvider); if (genType != null) { constructGetter(parent, node.getQName().getLocalName(), node.getDescription(), genType, node.getStatus()); resolveDataSchemaNodes(module, basePackageName, genType, genType, node.getChildNodes(), genCtx, schemaContext, verboseClassComments, genTypeBuilders, typeProvider); } } /** * Converts leaf to the getter method which is added to * typeBuilder. * * @param typeBuilder * generated type builder to which is added getter method as * leaf mapping * @param leaf * leaf schema node which is mapped as getter method which is * added to typeBuilder * @param module * Module in which type was defined * @return boolean value *
    *
  • false - if leaf or typeBuilder are * null
  • *
  • true - in other cases
  • *
*/ private static Type resolveLeafSchemaNodeAsMethod(final SchemaContext schemaContext, final GeneratedTypeBuilder typeBuilder, final Map genCtx, final LeafSchemaNode leaf, final Module module, final TypeProvider typeProvider) { if (leaf == null || typeBuilder == null || leaf.isAddedByUses()) { return null; } final String leafName = leaf.getQName().getLocalName(); if (leafName == null) { return null; } final Module parentModule = findParentModule(schemaContext, leaf); Type returnType = null; final TypeDefinition typeDef = leaf.getType(); if (isInnerType(leaf, typeDef)) { if (typeDef instanceof EnumTypeDefinition) { returnType = typeProvider.javaTypeForSchemaDefinitionType(typeDef, leaf); final EnumTypeDefinition enumTypeDef = (EnumTypeDefinition) typeDef; final EnumBuilder enumBuilder = resolveInnerEnumFromTypeDefinition(enumTypeDef, leaf.getQName(), genCtx, typeBuilder, module); if (enumBuilder != null) { returnType = enumBuilder.toInstance(typeBuilder); } ((TypeProviderImpl) typeProvider).putReferencedType(leaf.getPath(), returnType); } else if (typeDef instanceof UnionTypeDefinition) { GeneratedTOBuilder genTOBuilder = addTOToTypeBuilder(typeDef, typeBuilder, leaf, parentModule, typeProvider, schemaContext); if (genTOBuilder != null) { //TODO: https://bugs.opendaylight.org/show_bug.cgi?id=2289 returnType = createReturnTypeForUnion(genTOBuilder, typeDef, typeBuilder, parentModule, typeProvider); } } else if (typeDef instanceof BitsTypeDefinition) { GeneratedTOBuilder genTOBuilder = addTOToTypeBuilder(typeDef, typeBuilder, leaf, parentModule, typeProvider, schemaContext); if (genTOBuilder != null) { returnType = genTOBuilder.toInstance(); } } else { // It is constrained version of already declared type (inner declared type exists, // onlyfor special cases (Enum, Union, Bits), which were already checked. // In order to get proper class we need to look up closest derived type // and apply restrictions from leaf type final Restrictions restrictions = BindingGeneratorUtil.getRestrictions(typeDef); returnType = typeProvider.javaTypeForSchemaDefinitionType(getBaseOrDeclaredType(typeDef), leaf, restrictions); } } else { final Restrictions restrictions = BindingGeneratorUtil.getRestrictions(typeDef); returnType = typeProvider.javaTypeForSchemaDefinitionType(typeDef, leaf, restrictions); } if (returnType == null) { return null; } if (typeDef instanceof EnumTypeDefinition) { ((TypeProviderImpl) typeProvider).putReferencedType(leaf.getPath(), returnType); } String leafDesc = leaf.getDescription(); if (leafDesc == null) { leafDesc = ""; } final MethodSignatureBuilder getter = constructGetter(typeBuilder, leafName, leafDesc, returnType, leaf.getStatus()); //FIXME: deal with context-ref return returnType; } private static TypeDefinition getBaseOrDeclaredType(final TypeDefinition typeDef) { final TypeDefinition baseType = typeDef.getBaseType(); return (baseType != null && baseType.getBaseType() != null) ? baseType : typeDef; } private static GeneratedTypeBuilder processDataSchemaNode(final Module module, final String basePackageName, final GeneratedTypeBuilder childOf, final DataSchemaNode node, final SchemaContext schemaContext, final boolean verboseClassComments, final Map genCtx, final Map> genTypeBuilders, final TypeProvider typeProvider) { 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, typeProvider); } return genType; } }