All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.onosproject.yang.runtime.impl.ModelConverterUtil Maven / Gradle / Ivy

There is a newer version: 2.6.1
Show newest version
/*
 * Copyright 2017-present Open Networking Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.onosproject.yang.runtime.impl;


import org.onosproject.yang.compiler.datamodel.TraversalType;
import org.onosproject.yang.compiler.datamodel.YangAugment;
import org.onosproject.yang.compiler.datamodel.YangCase;
import org.onosproject.yang.compiler.datamodel.YangDerivedInfo;
import org.onosproject.yang.compiler.datamodel.YangIdentity;
import org.onosproject.yang.compiler.datamodel.YangIdentityRef;
import org.onosproject.yang.compiler.datamodel.YangLeaf;
import org.onosproject.yang.compiler.datamodel.YangLeafList;
import org.onosproject.yang.compiler.datamodel.YangLeafRef;
import org.onosproject.yang.compiler.datamodel.YangNode;
import org.onosproject.yang.compiler.datamodel.YangNotification;
import org.onosproject.yang.compiler.datamodel.YangOutput;
import org.onosproject.yang.compiler.datamodel.YangRpc;
import org.onosproject.yang.compiler.datamodel.YangSchemaNode;
import org.onosproject.yang.compiler.datamodel.YangType;
import org.onosproject.yang.compiler.datamodel.utils.builtindatatype.YangDataTypes;
import org.onosproject.yang.model.AtomicPath;
import org.onosproject.yang.model.LeafSchemaContext;
import org.onosproject.yang.model.MultiInstanceLeaf;
import org.onosproject.yang.model.MultiInstanceNode;
import org.onosproject.yang.model.SingleInstanceLeaf;
import org.onosproject.yang.model.SingleInstanceNode;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import static org.onosproject.yang.compiler.datamodel.TraversalType.PARENT;
import static org.onosproject.yang.compiler.datamodel.YangSchemaNodeType.YANG_AUGMENT_NODE;
import static org.onosproject.yang.compiler.datamodel.YangSchemaNodeType.YANG_MULTI_INSTANCE_NODE;
import static org.onosproject.yang.compiler.datamodel.utils.builtindatatype.YangDataTypes.BOOLEAN;
import static org.onosproject.yang.compiler.datamodel.utils.builtindatatype.YangDataTypes.EMPTY;
import static org.onosproject.yang.compiler.datamodel.utils.builtindatatype.YangDataTypes.INT16;
import static org.onosproject.yang.compiler.datamodel.utils.builtindatatype.YangDataTypes.INT32;
import static org.onosproject.yang.compiler.datamodel.utils.builtindatatype.YangDataTypes.INT64;
import static org.onosproject.yang.compiler.datamodel.utils.builtindatatype.YangDataTypes.INT8;
import static org.onosproject.yang.compiler.datamodel.utils.builtindatatype.YangDataTypes.LEAFREF;
import static org.onosproject.yang.compiler.datamodel.utils.builtindatatype.YangDataTypes.UINT16;
import static org.onosproject.yang.compiler.datamodel.utils.builtindatatype.YangDataTypes.UINT32;
import static org.onosproject.yang.compiler.datamodel.utils.builtindatatype.YangDataTypes.UINT8;
import static org.onosproject.yang.runtime.RuntimeHelper.PERIOD;
import static org.onosproject.yang.runtime.RuntimeHelper.UNDER_SCORE;
import static org.onosproject.yang.runtime.RuntimeHelper.getCapitalCase;


/**
 * Representation of utility for YANG tree builder.
 */
final class ModelConverterUtil {

    /**
     * Static attribute for string value having null.
     */
    static final String STR_NULL = "null";
    static final String FALSE = "false";
    static final String TRUE = "true";
    private static final int ONE = 1;
    private static final String ENUM_LEAF_IDENTIFIER = "$LeafIdentifier";
    private static final Set PRIMITIVE_TYPES =
            new HashSet<>(Arrays.asList(INT8, INT16, INT32, INT64, UINT8,
                                        UINT16, UINT32, BOOLEAN, EMPTY));
    private static final String TO_STRING = "toString";
    private static final String IS_VAL_SET = "isLeafValueSet";
    private static final Base64.Encoder BASE64_BASIC_ENCODER = Base64.getEncoder();

    // No instantiation.
    private ModelConverterUtil() {
    }

    /**
     * Returns the object of the node from the node info. Getting object for
     * augment and case differs from other node.
     *
     * @param nodeInfo node info of the holder
     * @param yangNode YANG node of the holder
     * @return object of the parent
     */
    static Object getParentObjectOfNode(DataTreeNodeInfo nodeInfo,
                                        YangNode yangNode) {
        Object object;
        if (yangNode instanceof YangCase) {
            object = nodeInfo.getCaseObject();
        } else if (yangNode instanceof YangAugment) {
            object = nodeInfo.getAugmentObject();
        } else {
            object = nodeInfo.getYangObject();
        }
        return object;
    }

    /**
     * Returns the value of an attribute, in a class object. The attribute
     * name is taken from the YANG node java name.
     *
     * @param nodeObj   object of the node
     * @param fieldName name of the attribute
     * @return object of the attribute
     * @throws NoSuchMethodException method not found exception
     */
    static Object getAttributeOfObject(Object nodeObj, String fieldName)
            throws NoSuchMethodException {
        Class nodeClass = nodeObj.getClass();
        Method getterMethod;
        try {
            getterMethod = nodeClass.getDeclaredMethod(fieldName);
            return getterMethod.invoke(nodeObj);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new ModelConverterException(e);
        }
    }

    /**
     * Returns the value of an attribute, in a class object. The attribute
     * name is taken from the YANG node java name.
     *
     * @param nodeObj   object of the node
     * @param fieldName name of the attribute
     * @return object of the attribute
     * @throws NoSuchMethodException method not found exception
     */
    static Object getAugmentObject(Object nodeObj, String fieldName)
            throws NoSuchMethodException {
        Class nodeClass = nodeObj.getClass().getSuperclass();
        Method getterMethod;
        try {
            getterMethod = nodeClass.getDeclaredMethod(fieldName);
            return getterMethod.invoke(nodeObj);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new ModelConverterException(e);
        }
    }

    /**
     * Returns the object of the declared method in parent class by invoking
     * through the child class object.
     *
     * @param childClass child class which inherits the parent class
     * @param methodName name of the declared method
     * @return value of the method
     */
    static Object getAttributeFromInheritance(Object childClass,
                                              String methodName) {
        Class parentClass = childClass.getClass().getSuperclass();
        Method getterMethod;
        try {
            getterMethod = parentClass.getDeclaredMethod(methodName);
            return getterMethod.invoke(childClass);
        } catch (InvocationTargetException | NoSuchMethodException |
                IllegalAccessException e) {
            throw new ModelConverterException(e);
        }
    }

    /**
     * Returns interface class from an implementation class object.
     *
     * @param obj implementation class object
     * @return interface class
     */
    static Class getInterfaceClassFromImplClass(Object obj) {
        Class[] interfaces = obj.getClass().getInterfaces();
        if (interfaces.length > ONE) {
            // TODO: Need to handle when impl class has more than one interface.
            throw new ModelConverterException("Implementation class having more than one" +
                                                      " interface is not handled");
        }
        return interfaces[0];
    }

    /**
     * Returns true, if data type of leaf is primitive data type; false
     * otherwise.
     *
     * @param yangType leaf type
     * @return true if data type is primitive; false otherwise
     */
    static boolean isTypePrimitive(YangType yangType) {
        if (yangType.getDataType() == LEAFREF) {
            YangLeafRef leafRef =
                    (YangLeafRef) yangType.getDataTypeExtendedInfo();
            return isPrimitiveDataType(leafRef.getEffectiveDataType()
                                               .getDataType());
        }
        return isPrimitiveDataType(yangType.getDataType());
    }

    /**
     * Returns the registered class from the YSR of the module node where
     * augment is present.
     *
     * @param curNode  current augment node
     * @param registry schema registry
     * @return class loader of module
     */
    static Class getClassLoaderForAugment(
            YangNode curNode, DefaultYangModelRegistry registry) {
        return registry.getRegisteredClass(curNode);
    }

    /**
     * Returns true, if the leaf data is actually set; false otherwise.
     *
     * @param holder   leaf holder
     * @param nodeObj  object if the node
     * @param javaName java name of the leaf
     * @return status of the value set flag
     */
    static boolean isLeafValueSet(YangSchemaNode holder, Object nodeObj,
                                  String javaName) {

        Class nodeClass = nodeObj.getClass();

        // Appends the enum inner package to the interface class package.
        String enumPackage = holder.getJavaPackage() + PERIOD +
                getCapitalCase(holder.getJavaClassNameOrBuiltInType()) +
                ENUM_LEAF_IDENTIFIER;

        ClassLoader classLoader = nodeClass.getClassLoader();
        Class leafEnum;
        try {
            leafEnum = classLoader.loadClass(enumPackage);
            Method getterMethod = nodeClass.getMethod(IS_VAL_SET, leafEnum);
            // Gets the value of the enum.
            Enum value = Enum.valueOf(leafEnum, javaName.toUpperCase());
            // Invokes the method with the value of enum as param.
            return (boolean) getterMethod.invoke(nodeObj, value);
        } catch (IllegalAccessException | InvocationTargetException |
                ClassNotFoundException | NoSuchMethodException e) {
            throw new ModelConverterException(e);
        }
    }

    /**
     * Returns the string value from the respective data types of the
     * leaf/leaf-list.
     *
     * @param holder    leaf/leaf-list holder
     * @param holderObj leaf/leaf-list holder object
     * @param name      leaf/leaf-list name
     * @param fieldObj  object of the leaf/leaf-list field
     * @param dataType  type of the leaf/leaf-list
     * @return finalized object
     */
    static Object getObjFromType(YangSchemaNode holder, Object holderObj,
                                 Object leaf, String name, Object fieldObj,
                                 YangType dataType) {

        if (fieldObj == null) {
            throw new ModelConverterException("Value of " + holder.getName()
                                                      + " is null");
        }

        YangDataTypes type = dataType.getDataType();
        switch (type) {
            case INT8:
            case INT16:
            case INT32:
            case INT64:
            case UINT8:
            case UINT16:
            case UINT32:
            case UINT64:
            case STRING:
            case BOOLEAN:
            case DECIMAL64:
            case EMPTY:
                return fieldObj;

            case INSTANCE_IDENTIFIER:
            case ENUMERATION:
                return String.valueOf(fieldObj).trim();

            case BINARY:
                return BASE64_BASIC_ENCODER.encodeToString((byte[]) fieldObj);

            case BITS:
                return getBitsValue(holder, holderObj, name, fieldObj).trim();

            case IDENTITYREF:
                YangIdentityRef ir = (YangIdentityRef) dataType
                        .getDataTypeExtendedInfo();
                if (ir.isInGrouping()) {
                    return String.valueOf(fieldObj).trim();
                }
                return getIdentityRefValue(fieldObj, ir, holderObj);

            case LEAFREF:
                YangLeafRef leafRef = (YangLeafRef) dataType
                        .getDataTypeExtendedInfo();
                return getObjFromType(holder, holderObj, leaf, name, fieldObj,
                                      leafRef.getEffectiveDataType());

            case DERIVED:
            case UNION:
                String val = String.valueOf(fieldObj).trim();
                return ((LeafSchemaContext) leaf).fromString(val);

            default:
                throw new ModelConverterException(
                        "Unsupported data type. Cannot be processed.");
        }
    }

    /**
     * Returns the string values for the data type bits.
     *
     * @param holder    leaf/leaf-list holder
     * @param holderObj leaf/leaf-list holder object
     * @param name      leaf/leaf-list name
     * @param fieldObj  object of the leaf/leaf-list field
     * @return string value for bits type
     */
    private static String getBitsValue(YangSchemaNode holder, Object holderObj,
                                       String name, Object fieldObj) {

        Class holderClass = holderObj.getClass();
        String interfaceName = holder.getJavaClassNameOrBuiltInType();
        String className = interfaceName.toLowerCase() + PERIOD +
                getCapitalCase(name);
        String pkgName = holder.getJavaPackage() + PERIOD + className;
        ClassLoader classLoader = holderClass.getClassLoader();

        Class bitClass;
        try {
            bitClass = classLoader.loadClass(pkgName);
            Method getterMethod = bitClass.getDeclaredMethod(
                    TO_STRING, fieldObj.getClass());
            return String.valueOf(getterMethod.invoke(null, fieldObj));
        } catch (ClassNotFoundException | NoSuchMethodException |
                InvocationTargetException | IllegalAccessException e) {
            throw new ModelConverterException(e);
        }
    }

    /**
     * Returns the string value of the type identity-ref.
     *
     * @param fieldObj  object of the leaf/leaf-list field
     * @param ir        YANG identity ref
     * @param holderObj leaf/leaf-list holder object
     * @return string value for identity ref type
     */
    private static String getIdentityRefValue(Object fieldObj, YangIdentityRef ir,
                                              Object holderObj) {

        YangIdentity id = getDerivedIdentity(fieldObj, ir);
        if (id == null) {
            throw new ModelConverterException("Value for identity is invalid");
        }
        String idName = id.getJavaClassNameOrBuiltInType();
        String idPkg = id.getJavaPackage() + PERIOD + getCapitalCase(idName);
        String methodName = idName + getCapitalCase(TO_STRING);

        Class holderClass = holderObj.getClass();
        ClassLoader classLoader = holderClass.getClassLoader();
        Class idClass;
        try {
            idClass = classLoader.loadClass(idPkg);
            Method method = idClass.getDeclaredMethod(methodName);
            return String.valueOf(method.invoke(fieldObj)).trim();
        } catch (ClassNotFoundException | NoSuchMethodException |
                InvocationTargetException | IllegalAccessException e) {
            throw new ModelConverterException(e);
        }
    }

    private static YangIdentity getDerivedIdentity(Object fieldObj,
                                                   YangIdentityRef ir) {
        YangIdentity id = ir.getReferredIdentity();
        String idName = id.getJavaClassNameOrBuiltInType();
        String[] objValue = fieldObj.toString().split("\\.");
        String value = objValue[objValue.length - 1];
        if (value.equalsIgnoreCase(idName)) {
            return id;
        }
        List identities = id.getExtendList();
        if (identities != null && !identities.isEmpty()) {
            for (YangIdentity identity : identities) {
                if (identity.getJavaClassNameOrBuiltInType()
                        .equalsIgnoreCase(value)) {
                    return identity;
                }
            }
        }

        return null;
    }

    /**
     * Returns true, if the data type is primitive; false otherwise.
     *
     * @param dataType data type
     * @return true if the data type is primitive; false otherwise
     */
    private static boolean isPrimitiveDataType(YangDataTypes dataType) {
        return PRIMITIVE_TYPES.contains(dataType);
    }

    /**
     * Returns true, if processing of the node is not required; false otherwise.
     * For the nodes such as notification, RPC, augment there is a different
     * flow, so these nodes are skipped in normal conditions.
     *
     * @param yangNode node to be checked
     * @return true if node processing is not required; false otherwise.
     */
    static boolean isNonProcessableNode(YangNode yangNode) {
        return yangNode != null &&
                yangNode instanceof YangNotification ||
                yangNode instanceof YangRpc ||
                yangNode instanceof YangAugment;
    }

    /**
     * Returns true, if multi instance node; false otherwise.
     *
     * @param yangNode YANG node
     * @return true, if multi instance node; false otherwise.
     */
    static boolean isMultiInstanceNode(YangNode yangNode) {
        return yangNode.getYangSchemaNodeType() == YANG_MULTI_INSTANCE_NODE;
    }

    /**
     * Returns true, if augment node; false otherwise.
     *
     * @param yangNode YANG node
     * @return true, if augment node; false otherwise.
     */
    static boolean isAugmentNode(YangNode yangNode) {
        return yangNode.getYangSchemaNodeType() == YANG_AUGMENT_NODE;
    }

    /**
     * Returns string for throwing error when empty object is given as input
     * to YTB.
     *
     * @param objName name of the object
     * @return error message
     */
    static String emptyObjErrMsg(String objName) {
        return "The " + objName + " given for tree creation cannot be null";
    }

    /**
     * Returns the java name for the nodes, leaf/leaf-list.
     *
     * @param node YANG node
     * @return node java name
     */
    static String getJavaName(Object node) {
        return ((YangSchemaNode) node).getJavaAttributeName();
    }

    /**
     * Returns true, if the string is not null and non-empty; false otherwise.
     *
     * @param str string value
     * @return true, if the string is not null and non-empty; false otherwise.
     */
    static boolean nonEmpty(String str) {
        return str != null && !str.isEmpty();
    }

    /**
     * Returns true when the node processing of RPC and notification is
     * completed; false otherwise. For RPC and notification, processing of
     * other nodes are invalid, so once node gets completed, it must be stopped.
     *
     * @param curNode current node
     * @param type    current traversal of the node
     * @return true, if the node processing is completed; false otherwise.
     */
    static boolean isNodeProcessCompleted(YangNode curNode, TraversalType type) {
        return type == PARENT &&
                curNode instanceof YangNotification ||
                curNode instanceof YangOutput;
    }

    /**
     * Returns package for the given atomic path.
     *
     * @param path atomic path
     * @return package for the given path
     */
    static String fetchPackage(AtomicPath path) {
        switch (path.type()) {
            case SINGLE_INSTANCE_NODE:
                SingleInstanceNode sin = (SingleInstanceNode) path;
                return sin.container().getName();
            case MULTI_INSTANCE_NODE:
                MultiInstanceNode min = (MultiInstanceNode) path;
                return min.listClass().getName();
            case SINGLE_INSTANCE_LEAF_VALUE_NODE:
                SingleInstanceLeaf sil = (SingleInstanceLeaf) path;
                return getNameWithOutSpecialChar(
                        sil.leafIdentifier().toString().toLowerCase());
            case MULTI_INSTANCE_LEAF_VALUE_NODE:
                MultiInstanceLeaf mil = (MultiInstanceLeaf) path;
                return getNameWithOutSpecialChar(
                        mil.leafIdentifier().toString().toLowerCase());
            default:
                throw new ModelConverterException("leaf/leaf-list can't be at this " +
                                                          "position");
        }
    }

    /**
     * Removes underscore from the given name.
     *
     * @param name name of instance
     * @return name
     */
    private static String getNameWithOutSpecialChar(String name) {
        String[] str = name.split(UNDER_SCORE);
        StringBuilder builder = new StringBuilder();
        for (String s : str) {
            builder.append(s);
        }
        return builder.toString();
    }

    /**
     * Returns leaf object when value is present. For primitive types, in
     * order to avoid default values, the value select is set or not is checked
     * and then returned.
     *
     * @param holder    leaf holder
     * @param leaf      YANG leaf
     * @param parentObj leaf holder object
     * @param leafObj   object of leaf type
     * @param isRoot    if it is root leaf object
     * @return processed leaf object
     */
    static Object getLeafObject(YangSchemaNode holder, YangLeaf leaf,
                                Object parentObj, Object leafObj,
                                boolean isRoot) {
        String jLeaf = getJavaName(leaf);
        YangType type = leaf.getDataType();
        if (!isRoot && isTypePrimitive(type)) {
            if (!isLeafValueSet(holder, parentObj, jLeaf)) {
                return null;
            }
        }

        if (leafObj == null) {
            return null;
        }
        return getObjFromType(holder, parentObj, leaf, jLeaf,
                              leafObj, type);
    }

    /**
     * Returns processed leaf-list objects from the data type.
     *
     * @param holder    leaf-list holder
     * @param leafList  YANG leaf-list
     * @param parentObj leaf-list holder object
     * @param objects   leaf-list objects
     * @return processed leaf-list objects
     */
    static Set getLeafListObject(YangSchemaNode holder,
                                         YangLeafList leafList,
                                         Object parentObj,
                                         List objects) {
        Set leafListVal = new LinkedHashSet<>();
        YangType type = leafList.getDataType();
        for (Object object : objects) {
            Object obj = getObjFromType(holder, parentObj, leafList,
                                        getJavaName(leafList), object, type);
            leafListVal.add(obj);
        }
        return leafListVal;
    }


    /**
     * Returns the value as true if direct or referred type from leaf-ref or
     * derived points to empty data type; false otherwise.
     *
     * @param dataType type of the leaf
     * @return true if type is empty; false otherwise.
     */
    static boolean isTypeEmpty(YangType dataType) {
        switch (dataType.getDataType()) {
            case EMPTY:
                return true;

            case LEAFREF:
                YangLeafRef leafRef = (YangLeafRef) dataType
                        .getDataTypeExtendedInfo();
                return isTypeEmpty(leafRef.getEffectiveDataType());
            case DERIVED:
                YangDerivedInfo info = (YangDerivedInfo) dataType
                        .getDataTypeExtendedInfo();
                YangDataTypes type = info.getEffectiveBuiltInType();
                return type == EMPTY;

            default:
                return false;
        }
    }
}