[ONOS-4799],[ONOS-4351] Augment inter file linker and Generated Code refactored.

Change-Id: Id1f3ac9c90a632373f51cc75d499c3110216be17
diff --git a/utils/yangutils/plugin/src/main/java/org/onosproject/yangutils/linker/impl/YangXpathLinker.java b/utils/yangutils/plugin/src/main/java/org/onosproject/yangutils/linker/impl/YangXpathLinker.java
new file mode 100644
index 0000000..681a52c
--- /dev/null
+++ b/utils/yangutils/plugin/src/main/java/org/onosproject/yangutils/linker/impl/YangXpathLinker.java
@@ -0,0 +1,711 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.yangutils.linker.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import org.onosproject.yangutils.datamodel.YangAtomicPath;
+import org.onosproject.yangutils.datamodel.YangAugment;
+import org.onosproject.yangutils.datamodel.YangImport;
+import org.onosproject.yangutils.datamodel.YangInclude;
+import org.onosproject.yangutils.datamodel.YangLeaf;
+import org.onosproject.yangutils.datamodel.YangLeafList;
+import org.onosproject.yangutils.datamodel.YangLeavesHolder;
+import org.onosproject.yangutils.datamodel.YangModule;
+import org.onosproject.yangutils.datamodel.YangNode;
+import org.onosproject.yangutils.datamodel.YangNodeIdentifier;
+import org.onosproject.yangutils.datamodel.YangSubModule;
+import org.onosproject.yangutils.datamodel.YangUses;
+import org.onosproject.yangutils.linker.exceptions.LinkerException;
+
+/**
+ * Represents x-path linking.
+ *
+ * @param <T> x-path linking can be done for target node or for target leaf/leaf-list
+ */
+public class YangXpathLinker<T> {
+
+    /**
+     * Enum for prefix resolver type when augment has come in path.
+     */
+    private static enum PrefixResolverType {
+
+        /**
+         * When prefix changes from inter file to intra file.
+         */
+        INTER_TO_INTRA,
+
+        /**
+         * When prefix changes from intra file to inter file.
+         */
+        INTRA_TO_INTER,
+
+        /**
+         * When prefix changes from one inter file to other inter file.
+         */
+        INTER_TO_INTER,
+
+        /**
+         * When no prefix change occurres.
+         */
+        NO_PREFIX_CHANGE_FOR_INTRA,
+
+        /**
+         * When no prefix change occurres.
+         */
+        NO_PREFIX_CHANGE_FOR_INTER
+    }
+
+    private List<YangAtomicPath> absPaths;
+    private YangNode rootNode;
+    private PrefixResolverType type;
+    private String curPrefix;
+    private Map<YangAtomicPath, YangNode> resolvedNodes;
+
+    /**
+     * Creates an instance of x-path linker.
+     */
+    public YangXpathLinker() {
+        absPaths = new ArrayList<>();
+        setResolvedNodes(new HashMap<>());
+    }
+
+    /**
+     * Returns list of target nodes paths.
+     *
+     * @return target nodes paths
+     */
+    private List<YangAtomicPath> getAbsPaths() {
+        return absPaths;
+    }
+
+    /**
+     * Sets target nodes paths.
+     *
+     * @param absPaths target nodes paths
+     */
+    private void setAbsPaths(List<YangAtomicPath> absPaths) {
+        this.absPaths = absPaths;
+    }
+
+    /**
+     * Returns current prefix.
+     *
+     * @return current prefix
+     */
+    private String getCurPrefix() {
+        return curPrefix;
+    }
+
+    /**
+     * Sets current prefix.
+     *
+     * @param curPrefix current prefix
+     */
+    private void setCurPrefix(String curPrefix) {
+        this.curPrefix = curPrefix;
+    }
+
+    /**
+     * Return root node.
+     *
+     * @return root Node
+     */
+    private YangNode getRootNode() {
+        return rootNode;
+    }
+
+    /**
+     * Sets root node.
+     *
+     * @param rootNode root node
+     */
+    private void setRootNode(YangNode rootNode) {
+        this.rootNode = rootNode;
+    }
+
+    /**
+     * Returns prefix resolver type.
+     *
+     * @return prefix resolver type
+     */
+    private PrefixResolverType getPrefixResolverType() {
+        return type;
+    }
+
+    /**
+     * Sets prefix resolver type.
+     *
+     * @param type prefix resolver type
+     */
+    private void setPrefixResolverType(PrefixResolverType type) {
+        this.type = type;
+    }
+
+    /**
+     * Returns resolved nodes.
+     *
+     * @return resolved nodes
+     */
+    public Map<YangAtomicPath, YangNode> getResolvedNodes() {
+        return resolvedNodes;
+    }
+
+    /**
+     * Sets resolved nodes.
+     *
+     * @param resolvedNodes resolved nodes
+     */
+    private void setResolvedNodes(Map<YangAtomicPath, YangNode> resolvedNodes) {
+        this.resolvedNodes = resolvedNodes;
+    }
+
+    /**
+     * Adds node to resolved nodes.
+     *
+     * @param path absolute path
+     * @param node resolved node
+     */
+    private void addToResolvedNodes(YangAtomicPath path, YangNode node) {
+        getResolvedNodes().put(path, node);
+    }
+
+    /**
+     * Returns list of augment nodes.
+     *
+     * @param node root node
+     * @return list of augment nodes
+     */
+    public List<YangAugment> getListOfYangAugment(YangNode node) {
+        node = node.getChild();
+        List<YangAugment> augments = new ArrayList<>();
+        while (node != null) {
+            if (node instanceof YangAugment) {
+                augments.add((YangAugment) node);
+            }
+            node = node.getNextSibling();
+        }
+        return augments;
+    }
+
+    /**
+     * Process absolute node path for target leaf.
+     *
+     * @param absPaths absolute path node list
+     * @param root root node
+     * @return linked target node
+     */
+    public T processLeafRefXpathLinking(List<YangAtomicPath> absPaths, YangNode root) {
+
+        YangNode targetNode = null;
+        setRootNode(root);
+        YangAtomicPath leafRefPath = absPaths.get(absPaths.size() - 1);
+
+        // When leaf-ref path contains only one absolute path.
+        if (absPaths.size() == 1) {
+            targetNode = getTargetNodewhenSizeIsOne(absPaths);
+        } else {
+            absPaths.remove(absPaths.size() - 1);
+
+            setAbsPaths(absPaths);
+            targetNode = parseData(root);
+        }
+        if (targetNode == null) {
+            targetNode = parsePath(getIncludedNode(root));
+        }
+
+        if (targetNode != null) {
+            YangLeaf targetLeaf = searchReferredLeaf(targetNode, leafRefPath.getNodeIdentifier().getName());
+            if (targetLeaf == null) {
+                YangLeafList targetLeafList = searchReferredLeafList(targetNode,
+                        leafRefPath.getNodeIdentifier().getName());
+                if (targetLeafList != null) {
+                    return (T) targetLeafList;
+                } else {
+                    throw new LinkerException(
+                            "YANG file error: Unable to find base leaf/leaf-list for given leafref "
+                                    + leafRefPath.getNodeIdentifier().getName());
+                }
+            }
+            return (T) targetLeaf;
+        }
+        return null;
+    }
+
+    /**
+     * Returns target node when leaf-ref has only one absolute path in list.
+     *
+     * @param absPaths absolute paths
+     * @return target node
+     */
+    private YangNode getTargetNodewhenSizeIsOne(List<YangAtomicPath> absPaths) {
+        if (absPaths.get(0).getNodeIdentifier().getPrefix() != null
+                && !absPaths.get(0).getNodeIdentifier().getPrefix().equals(getRootsPrefix(getRootNode()))) {
+            return getImportedNode(getRootNode(), absPaths.get(0).getNodeIdentifier());
+        }
+        return getRootNode();
+
+    }
+
+    /**
+     * Process absolute node path linking for augment.
+     *
+     * @param absPaths absolute path node list
+     * @param root root node
+     * @return linked target node
+     */
+    public YangNode processAugmentXpathLinking(List<YangAtomicPath> absPaths, YangNode root) {
+
+        setAbsPaths(absPaths);
+        setRootNode(root);
+
+        YangNode targetNode = parseData(root);
+
+        if (targetNode == null) {
+            targetNode = parsePath(getIncludedNode(root));
+        }
+        return targetNode;
+
+    }
+
+    /**
+     * Searches for the referred leaf in target node.
+     *
+     * @param targetNode target node
+     * @param leafName leaf name
+     * @return target leaf
+     */
+    private YangLeaf searchReferredLeaf(YangNode targetNode, String leafName) {
+        if (!(targetNode instanceof YangLeavesHolder)) {
+            throw new LinkerException("Refered node " + targetNode.getName() +
+                    "should be of type leaves holder ");
+        }
+        YangLeavesHolder holder = (YangLeavesHolder) targetNode;
+        List<YangLeaf> leaves = holder.getListOfLeaf();
+        for (YangLeaf leaf : leaves) {
+            if (leaf.getName().equals(leafName)) {
+                return leaf;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Searches for the referred leaf-list in target node.
+     *
+     * @param targetNode target node
+     * @param leafListName leaf-list name
+     * @return target leaf-list
+     */
+    private YangLeafList searchReferredLeafList(YangNode targetNode, String leafListName) {
+        if (!(targetNode instanceof YangLeavesHolder)) {
+            throw new LinkerException("Refered node " + targetNode.getName() +
+                    "should be of type leaves holder ");
+        }
+        YangLeavesHolder holder = (YangLeavesHolder) targetNode;
+        List<YangLeafList> leavesList = holder.getListOfLeafList();
+        for (YangLeafList leafList : leavesList) {
+            if (leafList.getName().equals(leafListName)) {
+                return leafList;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Process linking using for node identifier for inter/intra file.
+     *
+     * @param root root node
+     * @return linked target node
+     */
+    private YangNode parseData(YangNode root) {
+        String rootPrefix = getRootsPrefix(root);
+        Iterator<YangAtomicPath> pathIterator = getAbsPaths().iterator();
+        YangAtomicPath path = pathIterator.next();
+        if (path.getNodeIdentifier().getPrefix() != null
+                && !path.getNodeIdentifier().getPrefix().equals(rootPrefix)) {
+            return parsePath(getImportedNode(root, path.getNodeIdentifier()));
+        } else {
+            return parsePath(root);
+        }
+    }
+
+    /**
+     * Process linking of target node in root node.
+     *
+     * @param root root node
+     * @return linked target node
+     */
+    private YangNode parsePath(YangNode root) {
+
+        YangNode tempNode = root;
+        Stack<YangNode> linkerStack = new Stack<>();
+        Iterator<YangAtomicPath> pathIterator = getAbsPaths().iterator();
+        YangAtomicPath tempPath = pathIterator.next();
+        setCurPrefix(tempPath.getNodeIdentifier().getPrefix());
+        int index = 0;
+        YangNode tempAugment = null;
+        do {
+
+            if (tempNode instanceof YangUses) {
+                tempNode = handleUsesNode(tempNode, tempPath.getNodeIdentifier());
+                if (pathIterator.hasNext()) {
+                    tempPath = pathIterator.next();
+                    index++;
+                } else {
+                    addToResolvedNodes(tempPath, tempNode);
+                    return tempNode;
+                }
+            }
+
+            if (tempPath.getNodeIdentifier().getPrefix() == null) {
+                tempAugment = resolveIntraFileAugment(tempPath, root);
+            } else {
+                tempAugment = resolveInterFileAugment(tempPath, root);
+            }
+
+            if (tempAugment != null) {
+                linkerStack.push(tempNode);
+                tempNode = tempAugment;
+            }
+
+            tempNode = searchTargetNode(tempNode, tempPath.getNodeIdentifier());
+            if (tempNode == null && linkerStack.size() != 0) {
+                tempNode = linkerStack.peek();
+                linkerStack.pop();
+                tempNode = searchTargetNode(tempNode, tempPath.getNodeIdentifier());
+            }
+
+            if (tempNode != null) {
+                addToResolvedNodes(tempPath, tempNode);
+            }
+
+            if (index == getAbsPaths().size() - 1) {
+                break;
+            }
+            tempPath = pathIterator.next();
+            index++;
+        } while (validate(tempNode, index));
+        return tempNode;
+    }
+
+    /**
+     * Resolves intra file augment linking.
+     *
+     * @param tempPath temporary absolute path
+     * @param root root node
+     * @return linked target node
+     */
+    private YangNode resolveIntraFileAugment(YangAtomicPath tempPath, YangNode root) {
+        YangNode tempAugment = null;
+        setPrefixResolverType(PrefixResolverType.NO_PREFIX_CHANGE_FOR_INTRA);
+        if (getCurPrefix() != tempPath.getNodeIdentifier().getPrefix()) {
+            setPrefixResolverType(PrefixResolverType.INTRA_TO_INTER);
+            root = getIncludedNode(getRootNode());
+        }
+
+        setCurPrefix(tempPath.getNodeIdentifier().getPrefix());
+        tempAugment = getAugment(tempPath.getNodeIdentifier(), root, getAbsPaths());
+        if (tempAugment == null) {
+            tempAugment = getAugment(tempPath.getNodeIdentifier(), getRootNode(), getAbsPaths());
+        }
+        return tempAugment;
+    }
+
+    /**
+     * Resolves inter file augment linking.
+     *
+     * @param tempPath temporary absolute path
+     * @param root root node
+     * @return linked target node
+     */
+    private YangNode resolveInterFileAugment(YangAtomicPath tempPath, YangNode root) {
+
+        YangNode tempAugment = null;
+        if (tempPath.getNodeIdentifier().getPrefix().equals(getCurPrefix())) {
+            setPrefixResolverType(PrefixResolverType.NO_PREFIX_CHANGE_FOR_INTER);
+        } else {
+            setCurPrefix(tempPath.getNodeIdentifier().getPrefix());
+            setPrefixResolverType(PrefixResolverType.INTER_TO_INTER);
+            if (getCurPrefix() == null) {
+                setPrefixResolverType(PrefixResolverType.INTER_TO_INTRA);
+            }
+            root = getImportedNode(getRootNode(), tempPath.getNodeIdentifier());
+        }
+        tempAugment = getAugment(tempPath.getNodeIdentifier(), root, getAbsPaths());
+        if (tempAugment == null && getPrefixResolverType().equals(PrefixResolverType.INTER_TO_INTER)) {
+            return resolveInterToInterFileAugment(root);
+        }
+        return tempAugment;
+    }
+
+    /**
+     * Resolves augment when prefix changed from inter file to inter file.
+     * it may be possible that the prefix used in imported module is different the
+     * given list of node identifiers.
+     *
+     * @param root root node
+     * @return target node
+     */
+    private YangNode resolveInterToInterFileAugment(YangNode root) {
+        List<YangAugment> augments = getListOfYangAugment(root);
+        int index;
+        List<YangAtomicPath> absPaths = new ArrayList<>();
+        for (YangAugment augment : augments) {
+            index = 0;
+
+            for (YangAtomicPath path : augment.getTargetNode()) {
+
+                if (!searchForAugmentInImportedNode(path.getNodeIdentifier(), index)) {
+                    absPaths.clear();
+                    break;
+                }
+                absPaths.add(path);
+                index++;
+            }
+            if (!absPaths.isEmpty() && absPaths.size() == getAbsPaths().size() - 1) {
+                return augment;
+            } else {
+                absPaths.clear();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Searches for the augment node in imported module when prefix has changed from
+     * inter file to inter file.
+     * @param nodeId node id
+     * @param index index
+     * @return true if found
+     */
+    private boolean searchForAugmentInImportedNode(YangNodeIdentifier nodeId, int index) {
+        YangNodeIdentifier tempNodeId = getAbsPaths().get(index).getNodeIdentifier();
+        return nodeId.getName().equals(tempNodeId.getName());
+    }
+
+    /**
+     * Returns augment node.
+     *
+     * @param tempNodeId temporary absolute path id
+     * @param root root node
+     * @return linked target node
+     */
+    private YangNode getAugment(YangNodeIdentifier tempNodeId, YangNode root, List<YangAtomicPath> absPaths) {
+        String augmentName = getAugmentNodeIdentifier(tempNodeId, absPaths);
+        if (augmentName != null) {
+            return searchAugmentNode(root, augmentName);
+        }
+        return null;
+    }
+
+    /**
+     * Process linking using import list.
+     *
+     * @param root root node
+     * @param nodeId node identifier
+     * @return linked target node
+     */
+    private YangNode getImportedNode(YangNode root, YangNodeIdentifier nodeId) {
+
+        List<YangImport> importList = new ArrayList<>();
+
+        if (root instanceof YangModule) {
+            importList = ((YangModule) root).getImportList();
+        } else {
+            importList = ((YangSubModule) root).getImportList();
+        }
+
+        for (YangImport imported : importList) {
+            if (imported.getPrefixId().equals(nodeId.getPrefix())) {
+                return imported.getImportedNode();
+            }
+        }
+
+        return root;
+    }
+
+    /**
+     * Process linking using include list.
+     *
+     * @param root root node
+     * @return linked target node
+     */
+    private YangNode getIncludedNode(YangNode root) {
+
+        List<YangInclude> includeList = new ArrayList<>();
+
+        if (root instanceof YangModule) {
+            includeList = ((YangModule) root).getIncludeList();
+        } else {
+            includeList = ((YangSubModule) root).getIncludeList();
+        }
+
+        for (YangInclude included : includeList) {
+            return included.getIncludedNode();
+        }
+
+        return root;
+    }
+
+    /**
+     * Returns augments node id.
+     *
+     * @param nodeId node identifier
+     * @return augment node id
+     */
+    private String getAugmentNodeIdentifier(YangNodeIdentifier nodeId, List<YangAtomicPath> absPaths) {
+
+        Iterator<YangAtomicPath> nodeIdIterator = absPaths.iterator();
+        YangAtomicPath tempNodeId = null;
+        StringBuilder builder = new StringBuilder();
+        while (nodeIdIterator.hasNext()) {
+            tempNodeId = nodeIdIterator.next();
+            if (!tempNodeId.getNodeIdentifier().equals(nodeId)) {
+                if (tempNodeId.getNodeIdentifier().getPrefix() != null
+                        && (getPrefixResolverType().equals(PrefixResolverType.INTER_TO_INTER)
+                                || getPrefixResolverType().equals(PrefixResolverType.INTRA_TO_INTER))) {
+                    builder.append("/" + tempNodeId.getNodeIdentifier().getPrefix());
+                    builder.append(":" + tempNodeId.getNodeIdentifier().getName());
+                } else {
+                    builder.append("/" + tempNodeId.getNodeIdentifier().getName());
+                }
+            } else {
+                return builder.toString();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Searches augment node in root node.
+     *
+     * @param node root node
+     * @param tempNodeId node identifier
+     * @return target augment node
+     */
+    private YangNode searchAugmentNode(YangNode node, String tempNodeId) {
+        node = node.getChild();
+        while (node != null) {
+            if (node instanceof YangAugment) {
+                if (((YangAugment) node).getName().equals(tempNodeId)) {
+                    return node;
+                }
+            }
+            node = node.getNextSibling();
+        }
+        return null;
+    }
+
+    /**
+     * Validates for target node if target node found or not.
+     *
+     * @param tempNode temporary node
+     * @param index current index of list
+     * @return false if target node found
+     */
+    private boolean validate(YangNode tempNode, int index) {
+
+        int size = getAbsPaths().size();
+        if (tempNode != null && index != size) {
+            return true;
+        } else if (tempNode != null && index == size) {
+            return false;
+            // this is your target node.
+        } else if (tempNode == null && index != size) {
+            return false;
+            // this could be in submodule as well.
+        }
+        return false;
+    }
+
+    /**
+     * Searches target node in root node.
+     *
+     * @param node root node
+     * @param curNodeId YANG node identifier
+     * @return linked target node
+     */
+    private YangNode searchTargetNode(YangNode node, YangNodeIdentifier curNodeId) {
+
+        if (node != null) {
+            node = node.getChild();
+        }
+
+        while (node != null) {
+            if (node.getName().equals(curNodeId.getName())) {
+                return node;
+            }
+            node = node.getNextSibling();
+        }
+        return null;
+    }
+
+    /**
+     * Handles linking when uses node is present.
+     *
+     * @param node uses node
+     * @param curNodeId current node id
+     * @return linked node
+     */
+    private YangNode handleUsesNode(YangNode node, YangNodeIdentifier curNodeId) {
+        YangNode tempNode = null;
+        tempNode = searchInUsesNode((YangUses) node, curNodeId);
+        if (tempNode != null) {
+            return tempNode;
+        }
+        return null;
+    }
+
+    /**
+     * Searches target node in uses resolved list.
+     *
+     * @param uses uses node
+     * @param curNodeId current node id
+     * @return linked target node
+     */
+    private YangNode searchInUsesNode(YangUses uses, YangNodeIdentifier curNodeId) {
+
+        List<YangNode> resolvedNodes = uses.getUsesResolvedNodeList();
+        for (YangNode node : resolvedNodes) {
+            if (node.getName().equals(curNodeId.getName())) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns root prefix.
+     *
+     * @param root root node
+     * @return root prefix
+     */
+    private String getRootsPrefix(YangNode root) {
+        if (root instanceof YangModule) {
+            return ((YangModule) root).getPrefix();
+        } else {
+            return ((YangSubModule) root).getPrefix();
+        }
+    }
+
+}