Bug 1123 - Fixed incorrect augment target resolving 87/7587/3
authorTony Tkacik <ttkacik@cisco.com>
Mon, 2 Jun 2014 14:10:24 +0000 (16:10 +0200)
committerTony Tkacik <ttkacik@cisco.com>
Tue, 3 Jun 2014 13:23:52 +0000 (15:23 +0200)
- Introduced new ParserUtils#findSchemaNodeInModule
  in parser utils
  which uses SchemaPath, Iterable and full QName match
  to lookup builders for augmentations

- Updated augmentation resolution to use findSchemaNodeInModule
  to lookup correct augmentation target using
  full QName match (namespace, revision, localName) instead
  original algorithm using localName only.

- Updated incorrect negative tests for augmentations
  Added conflicting augmentations.

Change-Id: I36b814a85759605ef43eb65485d9ec592871fb15
Signed-off-by: Tony Tkacik <ttkacik@cisco.com>
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/impl/YangParserImpl.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/util/ParserUtils.java
yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/impl/YangParserNegativeTest.java
yang/yang-parser-impl/src/test/resources/negative-scenario/duplicity/augment0.yang
yang/yang-parser-impl/src/test/resources/negative-scenario/duplicity/augment1.yang
yang/yang-parser-impl/src/test/resources/negative-scenario/duplicity/augment2.yang

index 1a463447a9c9eac5ee0a57293ac760f50623240d..d0af63da9b88f8babcf20de1755cb0cefb7178db 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
- *
+ * Copyright (c) 2013 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
@@ -96,11 +95,11 @@ import org.opendaylight.yangtools.yang.validator.YangModelBasicValidator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.HashBiMap;
 import com.google.common.io.ByteSource;
 
-
 public final class YangParserImpl implements YangContextParser {
     private static final Logger LOG = LoggerFactory.getLogger(YangParserImpl.class);
 
@@ -117,7 +116,8 @@ public final class YangParserImpl implements YangContextParser {
     }
 
     @Override
-    public SchemaContext parseFile(final File yangFile, final File directory) throws IOException, YangSyntaxErrorException {
+    public SchemaContext parseFile(final File yangFile, final File directory) throws IOException,
+    YangSyntaxErrorException {
         Preconditions.checkState(yangFile.exists(), yangFile + " does not exists");
         Preconditions.checkState(directory.exists(), directory + " does not exists");
         Preconditions.checkState(directory.isDirectory(), directory + " is not a directory");
@@ -181,7 +181,8 @@ public final class YangParserImpl implements YangContextParser {
     }
 
     @Override
-    public SchemaContext parseFiles(final Collection<File> yangFiles, final SchemaContext context) throws IOException, YangSyntaxErrorException {
+    public SchemaContext parseFiles(final Collection<File> yangFiles, final SchemaContext context) throws IOException,
+    YangSyntaxErrorException {
         if (yangFiles == null) {
             return resolveSchemaContext(Collections.<Module> emptySet());
         }
@@ -203,7 +204,8 @@ public final class YangParserImpl implements YangContextParser {
     }
 
     @Override
-    public SchemaContext parseSources(final Collection<ByteSource> sources) throws IOException, YangSyntaxErrorException {
+    public SchemaContext parseSources(final Collection<ByteSource> sources) throws IOException,
+    YangSyntaxErrorException {
         Collection<Module> unsorted = parseYangModelSources(sources).values();
         Set<Module> sorted = new LinkedHashSet<>(
                 ModuleDependencySort.sort(unsorted.toArray(new Module[unsorted.size()])));
@@ -222,7 +224,8 @@ public final class YangParserImpl implements YangContextParser {
     }
 
     @Override
-    public SchemaContext parseSources(final Collection<ByteSource> sources, final SchemaContext context) throws IOException, YangSyntaxErrorException {
+    public SchemaContext parseSources(final Collection<ByteSource> sources, final SchemaContext context)
+            throws IOException, YangSyntaxErrorException {
         if (sources == null) {
             return resolveSchemaContext(Collections.<Module> emptySet());
         }
@@ -303,16 +306,18 @@ public final class YangParserImpl implements YangContextParser {
 
     @Override
     public SchemaContext resolveSchemaContext(final Set<Module> modules) {
-        // after merging parse method with this one, add support for getting submodule sources.
+        // after merging parse method with this one, add support for getting
+        // submodule sources.
         Map<ModuleIdentifier, String> identifiersToSources = new HashMap<>();
-        for(Module module: modules) {
+        for (Module module : modules) {
             ModuleImpl moduleImpl = (ModuleImpl) module;
             identifiersToSources.put(module, moduleImpl.getSource());
         }
         return new SchemaContextImpl(modules, identifiersToSources);
     }
 
-    private Map<ByteSource, Module> parseYangModelSources(final Collection<ByteSource> sources) throws IOException, YangSyntaxErrorException {
+    private Map<ByteSource, Module> parseYangModelSources(final Collection<ByteSource> sources) throws IOException,
+    YangSyntaxErrorException {
         if (sources == null || sources.isEmpty()) {
             return Collections.emptyMap();
         }
@@ -344,13 +349,15 @@ public final class YangParserImpl implements YangContextParser {
      *         parsed from stream
      * @throws YangSyntaxErrorException
      */
-    private Map<ByteSource, ModuleBuilder> resolveSources(final Collection<ByteSource> streams) throws IOException, YangSyntaxErrorException {
+    private Map<ByteSource, ModuleBuilder> resolveSources(final Collection<ByteSource> streams) throws IOException,
+    YangSyntaxErrorException {
         Map<ByteSource, ModuleBuilder> builders = parseSourcesToBuilders(streams);
         Map<ByteSource, ModuleBuilder> result = resolveSubmodules(builders);
         return result;
     }
 
-    private Map<ByteSource, ModuleBuilder> parseSourcesToBuilders(final Collection<ByteSource> sources) throws IOException, YangSyntaxErrorException {
+    private Map<ByteSource, ModuleBuilder> parseSourcesToBuilders(final Collection<ByteSource> sources)
+            throws IOException, YangSyntaxErrorException {
         final ParseTreeWalker walker = new ParseTreeWalker();
         final Map<ByteSource, ParseTree> sourceToTree = parseYangSources(sources);
         final Map<ByteSource, ModuleBuilder> sourceToBuilder = new LinkedHashMap<>();
@@ -468,7 +475,8 @@ public final class YangParserImpl implements YangContextParser {
     }
 
     private Map<String, TreeMap<Date, ModuleBuilder>> resolveModuleBuilders(
-            final Collection<ByteSource> yangFileStreams, final SchemaContext context) throws IOException, YangSyntaxErrorException {
+            final Collection<ByteSource> yangFileStreams, final SchemaContext context) throws IOException,
+            YangSyntaxErrorException {
         Map<ByteSource, ModuleBuilder> parsedBuilders = resolveSources(yangFileStreams);
         ModuleBuilder[] builders = new ModuleBuilder[parsedBuilders.size()];
         parsedBuilders.values().toArray(builders);
@@ -522,7 +530,8 @@ public final class YangParserImpl implements YangContextParser {
      * @param filtered
      *            collection to fill up
      */
-    private void filterImports(final ModuleBuilder main, final Collection<ModuleBuilder> other, final Collection<ModuleBuilder> filtered) {
+    private void filterImports(final ModuleBuilder main, final Collection<ModuleBuilder> other,
+            final Collection<ModuleBuilder> filtered) {
         Set<ModuleImport> imports = main.getModuleImports();
 
         // if this is submodule, add parent to filtered and pick its imports
@@ -559,7 +568,8 @@ public final class YangParserImpl implements YangContextParser {
         }
     }
 
-    private Map<ByteSource, ParseTree> parseYangSources(final Collection<ByteSource> sources) throws IOException, YangSyntaxErrorException {
+    private Map<ByteSource, ParseTree> parseYangSources(final Collection<ByteSource> sources) throws IOException,
+    YangSyntaxErrorException {
         final Map<ByteSource, ParseTree> trees = new HashMap<>();
         for (ByteSource source : sources) {
             trees.put(source, parseYangSource(source));
@@ -568,7 +578,7 @@ public final class YangParserImpl implements YangContextParser {
     }
 
     private YangContext parseYangSource(final ByteSource source) throws IOException, YangSyntaxErrorException {
-       try (InputStream stream = source.openStream()) {
+        try (InputStream stream = source.openStream()) {
             final ANTLRInputStream input = new ANTLRInputStream(stream);
             final YangLexer lexer = new YangLexer(input);
             final CommonTokenStream tokens = new CommonTokenStream(lexer);
@@ -582,7 +592,7 @@ public final class YangParserImpl implements YangContextParser {
             errorListener.validate();
 
             return result;
-       }
+        }
     }
 
     public static YangContext parseStreamWithoutErrorListeners(final InputStream yangStream) {
@@ -892,7 +902,7 @@ public final class YangParserImpl implements YangContextParser {
             }
         }
         if (node instanceof ChoiceBuilder) {
-            for (ChoiceCaseBuilder child : ((ChoiceBuilder)node).getCases()) {
+            for (ChoiceCaseBuilder child : ((ChoiceBuilder) node).getCases()) {
                 correctPathForAugmentNodes(child, node.getPath());
             }
         }
@@ -988,22 +998,39 @@ public final class YangParserImpl implements YangContextParser {
 
         UsesNodeBuilder usesNode = (UsesNodeBuilder) augment.getParent();
         DataNodeContainerBuilder parentNode = usesNode.getParent();
-        SchemaNodeBuilder targetNode;
-        if (parentNode instanceof ModuleBuilder) {
-            targetNode = findSchemaNodeInModule(augment.getTargetPath().getPath(), (ModuleBuilder) parentNode);
+        Optional<SchemaNodeBuilder> potentialTargetNode;
+        SchemaPath resolvedTargetPath = augment.getTargetNodeSchemaPath();
+        if (parentNode instanceof ModuleBuilder && resolvedTargetPath.isAbsolute()) {
+            // Uses is directly used in module body, we lookup
+            // We lookup in data namespace to find correct augmentation target
+            potentialTargetNode = findSchemaNodeInModule(resolvedTargetPath, (ModuleBuilder) parentNode);
         } else {
-            targetNode = findSchemaNode(augment.getTargetPath().getPath(), (SchemaNodeBuilder) parentNode);
-        }
-
-        if (targetNode instanceof AugmentationTargetBuilder) {
-            fillAugmentTarget(augment, targetNode);
-            ((AugmentationTargetBuilder) targetNode).addAugmentation(augment);
-            augment.setResolved(true);
-            return true;
+            // Uses is used in local context (be it data namespace or grouping namespace,
+            // since all nodes via uses are imported to localName, it is safe to
+            // to proceed only with local names.
+            //
+            // Conflicting elements in other namespaces are still not present
+            // since resolveUsesAugment occurs before augmenting from external modules.
+            potentialTargetNode = Optional.<SchemaNodeBuilder> fromNullable(findSchemaNode(augment.getTargetPath()
+                    .getPath(), (SchemaNodeBuilder) parentNode));
+        }
+
+        if (potentialTargetNode.isPresent()) {
+            SchemaNodeBuilder targetNode = potentialTargetNode.get();
+            if (targetNode instanceof AugmentationTargetBuilder) {
+                fillAugmentTarget(augment, targetNode);
+                ((AugmentationTargetBuilder) targetNode).addAugmentation(augment);
+                augment.setResolved(true);
+                return true;
+            } else {
+                throw new YangParseException(module.getName(), augment.getLine(), String.format(
+                        "Failed to resolve augment in uses. Invalid augment target: %s", potentialTargetNode));
+            }
         } else {
-            throw new YangParseException(module.getName(), augment.getLine(),
-                    "Failed to resolve augment in uses. Invalid augment target: " + targetNode);
+            throw new YangParseException(module.getName(), augment.getLine(), String.format(
+                    "Failed to resolve augment in uses. Invalid augment target path: %s", augment.getTargetPath()));
         }
+
     }
 
     /**
@@ -1119,9 +1146,11 @@ public final class YangParserImpl implements YangContextParser {
                     final String baseIdentityName = identity.getBaseIdentityName();
                     final int line = identity.getLine();
                     if (baseIdentityName != null) {
-                        IdentitySchemaNodeBuilder baseIdentity = findBaseIdentity(modules, module, baseIdentityName, line);
+                        IdentitySchemaNodeBuilder baseIdentity = findBaseIdentity(modules, module, baseIdentityName,
+                                line);
                         if (baseIdentity == null) {
-                            throw new YangParseException(module.getName(), identity.getLine(), "Failed to find base identity");
+                            throw new YangParseException(module.getName(), identity.getLine(),
+                                    "Failed to find base identity");
                         } else {
                             identity.setBaseIdentity(baseIdentity);
                         }
@@ -1222,7 +1251,8 @@ public final class YangParserImpl implements YangContextParser {
      * @param context
      *            SchemaContext containing already resolved modules
      */
-    private void resolveUsesForGroupings(final Map<String, TreeMap<Date, ModuleBuilder>> modules, final SchemaContext context) {
+    private void resolveUsesForGroupings(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
+            final SchemaContext context) {
         final Set<GroupingBuilder> allGroupings = new HashSet<>();
         for (Map.Entry<String, TreeMap<Date, ModuleBuilder>> entry : modules.entrySet()) {
             for (Map.Entry<Date, ModuleBuilder> inner : entry.getValue().entrySet()) {
@@ -1248,7 +1278,8 @@ public final class YangParserImpl implements YangContextParser {
      * @param context
      *            SchemaContext containing already resolved modules
      */
-    private void resolveUsesForNodes(final Map<String, TreeMap<Date, ModuleBuilder>> modules, final SchemaContext context) {
+    private void resolveUsesForNodes(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
+            final SchemaContext context) {
         for (Map.Entry<String, TreeMap<Date, ModuleBuilder>> entry : modules.entrySet()) {
             for (Map.Entry<Date, ModuleBuilder> inner : entry.getValue().entrySet()) {
                 ModuleBuilder module = inner.getValue();
@@ -1272,8 +1303,8 @@ public final class YangParserImpl implements YangContextParser {
      * @param context
      *            SchemaContext containing already resolved modules
      */
-    private void resolveUses(final UsesNodeBuilder usesNode,
-            final Map<String, TreeMap<Date, ModuleBuilder>> modules, final SchemaContext context) {
+    private void resolveUses(final UsesNodeBuilder usesNode, final Map<String, TreeMap<Date, ModuleBuilder>> modules,
+            final SchemaContext context) {
         if (!usesNode.isResolved()) {
             DataNodeContainerBuilder parent = usesNode.getParent();
             ModuleBuilder module = ParserUtils.getParentModule(parent);
@@ -1299,8 +1330,7 @@ public final class YangParserImpl implements YangContextParser {
     }
 
     /**
-     * Copy target grouping child nodes to current location with
-     * new namespace.
+     * Copy target grouping child nodes to current location with new namespace.
      *
      * @param usesNode
      *            uses node to resolve
@@ -1322,35 +1352,34 @@ public final class YangParserImpl implements YangContextParser {
             rev = module.getRevision();
             pref = module.getPrefix();
             if (parent instanceof AugmentationSchemaBuilder) {
-                parentPath = ((AugmentationSchemaBuilder)parent).getTargetNodeSchemaPath();
+                parentPath = ((AugmentationSchemaBuilder) parent).getTargetNodeSchemaPath();
             } else {
-                parentPath = ((ModuleBuilder)parent).getPath();
+                parentPath = ((ModuleBuilder) parent).getPath();
             }
         } else {
             ns = ((DataSchemaNodeBuilder) parent).getQName().getNamespace();
             rev = ((DataSchemaNodeBuilder) parent).getQName().getRevision();
             pref = ((DataSchemaNodeBuilder) parent).getQName().getPrefix();
-            parentPath = ((DataSchemaNodeBuilder)parent).getPath();
+            parentPath = ((DataSchemaNodeBuilder) parent).getPath();
         }
 
         GroupingDefinition gd = usesNode.getGroupingDefinition();
 
-        Set<DataSchemaNodeBuilder> childNodes = wrapChildNodes(module.getModuleName(), line,
-                gd.getChildNodes(), parentPath, ns, rev, pref);
+        Set<DataSchemaNodeBuilder> childNodes = wrapChildNodes(module.getModuleName(), line, gd.getChildNodes(),
+                parentPath, ns, rev, pref);
         parent.getChildNodeBuilders().addAll(childNodes);
         for (DataSchemaNodeBuilder childNode : childNodes) {
             setNodeAddedByUses(childNode);
         }
 
-        Set<TypeDefinitionBuilder> typedefs = wrapTypedefs(module.getModuleName(), line, gd, parentPath, ns,
-                rev, pref);
+        Set<TypeDefinitionBuilder> typedefs = wrapTypedefs(module.getModuleName(), line, gd, parentPath, ns, rev, pref);
         parent.getTypeDefinitionBuilders().addAll(typedefs);
         for (TypeDefinitionBuilder typedef : typedefs) {
             setNodeAddedByUses(typedef);
         }
 
-        Set<GroupingBuilder> groupings = wrapGroupings(module.getModuleName(), line, usesNode
-                .getGroupingDefinition().getGroupings(), parentPath, ns, rev, pref);
+        Set<GroupingBuilder> groupings = wrapGroupings(module.getModuleName(), line, usesNode.getGroupingDefinition()
+                .getGroupings(), parentPath, ns, rev, pref);
         parent.getGroupingBuilders().addAll(groupings);
         for (GroupingBuilder gb : groupings) {
             setNodeAddedByUses(gb);
index 82d7014cfbf2fced74340464f753055b27d72341..9e123e343e63ac64c0abd671355cb6b5048386e7 100644 (file)
@@ -71,6 +71,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Splitter;
 import com.google.common.collect.Collections2;
@@ -81,6 +82,8 @@ public final class ParserUtils {
     private static final Logger LOG = LoggerFactory.getLogger(ParserUtils.class);
     private static final Splitter SLASH_SPLITTER = Splitter.on('/');
     private static final Splitter COLON_SPLITTER = Splitter.on(':');
+    private static final String INPUT = "input";
+    private static final String OUTPUT = "output";
 
     private ParserUtils() {
     }
@@ -102,7 +105,8 @@ public final class ParserUtils {
         };
     }
 
-    public static Collection<ByteSource> filesToByteSources(final Collection<File> streams) throws FileNotFoundException {
+    public static Collection<ByteSource> filesToByteSources(final Collection<File> streams)
+            throws FileNotFoundException {
         return Collections2.transform(streams, new Function<File, ByteSource>() {
             @Override
             public ByteSource apply(final File input) {
@@ -137,7 +141,12 @@ public final class ParserUtils {
                     try {
                         stream.close();
                     } catch (IOException e) {
-                        LOG.warn("Failed to close stream {}", stream);
+                        /*
+                         * Failed stream close does not prevent us from
+                         * continuing to work correctly, we just report that and
+                         * continue.
+                         */
+                        LOG.warn("Failed to close stream {}. Leaving stream unclosed.", stream, e);
                     }
                 }
             }
@@ -238,8 +247,8 @@ public final class ParserUtils {
      *            current line in yang model
      * @return module based on given prefix if found in context, null otherwise
      */
-    public static Module findModuleFromContext(final SchemaContext context, final ModuleBuilder currentModule, final String prefix,
-            final int line) {
+    public static Module findModuleFromContext(final SchemaContext context, final ModuleBuilder currentModule,
+            final String prefix, final int line) {
         if (context == null) {
             throw new YangParseException(currentModule.getName(), line, "Cannot find module with prefix '" + prefix
                     + "'.");
@@ -470,36 +479,197 @@ public final class ParserUtils {
         return node;
     }
 
-    public static SchemaNodeBuilder findSchemaNodeInModule(final List<QName> pathToNode, final ModuleBuilder module) {
-        List<QName> path = new ArrayList<>(pathToNode);
-        QName first = path.remove(0);
+    /**
+     *
+     * Find a builder for node in data namespace of YANG module.
+     *
+     * Search is performed on full QName equals, this means builders and schema
+     * path MUST be resolved against imports and their namespaces.
+     *
+     * Search is done in data namespace, this means notification, rpc
+     * definitions and top level data definitions are considered as top-level
+     * items, from which it is possible to traverse.
+     *
+     *
+     * @param schemaPath
+     *            Schema Path to node
+     * @param module
+     *            ModuleBuilder to start lookup in
+     * @return Node Builder if found, {@link Optional#absent()} otherwise.
+     */
+    public static Optional<SchemaNodeBuilder> findSchemaNodeInModule(final SchemaPath schemaPath,
+            final ModuleBuilder module) {
+        Iterator<QName> path = schemaPath.getPathFromRoot().iterator();
+        Preconditions.checkArgument(path.hasNext(), "Schema Path must contain at least one element.");
+        QName first = path.next();
+        Optional<SchemaNodeBuilder> currentNode = getDataNamespaceChild(module, first);
 
-        SchemaNodeBuilder node = module.getDataChildByName(first.getLocalName());
-        if (node == null) {
-            Set<NotificationBuilder> notifications = module.getAddedNotifications();
-            for (NotificationBuilder notification : notifications) {
-                if (notification.getQName().getLocalName().equals(first.getLocalName())) {
-                    node = notification;
-                }
+        while (currentNode.isPresent() && path.hasNext()) {
+            currentNode = findDataChild(currentNode.get(), path.next());
+        }
+        return currentNode;
+    }
+
+    private static Optional<SchemaNodeBuilder> findDataChild(SchemaNodeBuilder parent, QName child) {
+        if (parent instanceof DataNodeContainerBuilder) {
+            return castOptional(SchemaNodeBuilder.class,
+                    findDataChildInDataNodeContainer((DataNodeContainerBuilder) parent, child));
+        } else if (parent instanceof ChoiceBuilder) {
+            return castOptional(SchemaNodeBuilder.class, findCaseInChoice((ChoiceBuilder) parent, child));
+        } else if (parent instanceof RpcDefinitionBuilder) {
+            return castOptional(SchemaNodeBuilder.class, findContainerInRpc((RpcDefinitionBuilder) parent, child));
+
+        } else {
+            LOG.trace("Child {} not found in node {}", child, parent);
+            return Optional.absent();
+        }
+    }
+
+    /**
+     * Casts optional from one argument to other.
+     *
+     * @param cls
+     *            Class to be checked
+     * @param optional
+     *            Original value
+     * @return
+     */
+    private static <T> Optional<T> castOptional(Class<T> cls, Optional<?> optional) {
+        if (optional.isPresent()) {
+            Object value = optional.get();
+            if (cls.isInstance(value)) {
+                @SuppressWarnings("unchecked")
+                // Actually checked by outer if
+                T casted = (T) value;
+                return Optional.of(casted);
             }
         }
-        if (node == null) {
-            Set<RpcDefinitionBuilder> rpcs = module.getAddedRpcs();
-            for (RpcDefinitionBuilder rpc : rpcs) {
-                if (rpc.getQName().getLocalName().equals(first.getLocalName())) {
-                    node = rpc;
-                }
+        return Optional.absent();
+    }
+
+    /**
+     *
+     * Gets input / output container from {@link RpcDefinitionBuilder} if QName
+     * is input/output.
+     *
+     *
+     * @param parent
+     *            RPC Definition builder
+     * @param child
+     *            Child QName
+     * @return Optional of input/output if defined and QName is input/output.
+     *         Otherwise {@link Optional#absent()}.
+     */
+    private static Optional<ContainerSchemaNodeBuilder> findContainerInRpc(RpcDefinitionBuilder parent, QName child) {
+        if (INPUT.equals(child.getLocalName())) {
+            return Optional.of(parent.getInput());
+        } else if (OUTPUT.equals(child.getLocalName())) {
+            return Optional.of(parent.getOutput());
+        }
+        LOG.trace("Child {} not found in node {}", child, parent);
+        return Optional.absent();
+    }
+
+    /**
+     * Finds case by QName in {@link ChoiceBuilder}
+     *
+     *
+     * @param parent
+     *            DataNodeContainer in which lookup should be performed
+     * @param child
+     *            QName of child
+     * @return Optional of child if found.
+     */
+
+    private static Optional<ChoiceCaseBuilder> findCaseInChoice(ChoiceBuilder parent, QName child) {
+        for (ChoiceCaseBuilder caze : parent.getCases()) {
+            if (caze.getQName().equals(child)) {
+                return Optional.of(caze);
             }
         }
-        if (node == null) {
-            return null;
+        LOG.trace("Child {} not found in node {}", child, parent);
+        return Optional.absent();
+    }
+
+    /**
+     * Finds direct child by QName in {@link DataNodeContainerBuilder}
+     *
+     *
+     * @param parent
+     *            DataNodeContainer in which lookup should be performed
+     * @param child
+     *            QName of child
+     * @return Optional of child if found.
+     */
+    private static Optional<DataSchemaNodeBuilder> findDataChildInDataNodeContainer(DataNodeContainerBuilder parent,
+            QName child) {
+        for (DataSchemaNodeBuilder childNode : parent.getChildNodeBuilders()) {
+            if (childNode.getQName().equals(child)) {
+                return Optional.of(childNode);
+            }
+        }
+        LOG.trace("Child {} not found in node {}", child, parent);
+        return Optional.absent();
+    }
+
+    /**
+     *
+     * Find a child builder for node in data namespace of YANG module.
+     *
+     * Search is performed on full QName equals, this means builders and schema
+     * path MUST be resolved against imports and their namespaces.
+     *
+     * Search is done in data namespace, this means notification, rpc
+     * definitions and top level data definitions are considered as top-level
+     * items, from which it is possible to traverse.
+     *
+     *
+     * @param child
+     *            Child QName.
+     * @param module
+     *            ModuleBuilder to start lookup in
+     * @return Node Builder if found, {@link Optional#absent()} otherwise.
+     */
+    private static Optional<SchemaNodeBuilder> getDataNamespaceChild(ModuleBuilder module, QName child) {
+        /*
+         * First we do lookup in data tree, if node is found we return it.
+         */
+        final Optional<SchemaNodeBuilder> dataTreeNode = getDataChildByQName(module, child);
+        if (dataTreeNode.isPresent()) {
+            return dataTreeNode;
+        }
+
+        /*
+         * We lookup in notifications
+         */
+        Set<NotificationBuilder> notifications = module.getAddedNotifications();
+        for (NotificationBuilder notification : notifications) {
+            if (notification.getQName().equals(child)) {
+                return Optional.<SchemaNodeBuilder> of(notification);
+            }
         }
 
-        if (!path.isEmpty()) {
-            node = findSchemaNode(path, node);
+        /*
+         * We lookup in RPCs
+         */
+        Set<RpcDefinitionBuilder> rpcs = module.getAddedRpcs();
+        for (RpcDefinitionBuilder rpc : rpcs) {
+            if (rpc.getQName().equals(child)) {
+                return Optional.<SchemaNodeBuilder> of(rpc);
+            }
         }
+        LOG.trace("Child {} not found in data namespace of module {}", child, module);
+        return Optional.absent();
+    }
 
-        return node;
+    private static Optional<SchemaNodeBuilder> getDataChildByQName(DataNodeContainerBuilder builder, QName child) {
+        for (DataSchemaNodeBuilder childNode : builder.getChildNodeBuilders()) {
+            if (childNode.getQName().equals(child)) {
+                return Optional.<SchemaNodeBuilder> of(childNode);
+            }
+        }
+        LOG.trace("Child {} not found in node {}", child, builder);
+        return Optional.absent();
     }
 
     /**
@@ -512,14 +682,17 @@ public final class ParserUtils {
      *            path to augment target
      * @return true if augmentation process succeed, false otherwise
      */
-    public static boolean processAugmentation(final AugmentationSchemaBuilder augment, final ModuleBuilder firstNodeParent) {
-        List<QName> path = augment.getTargetPath().getPath();
-        Builder targetNode = findSchemaNodeInModule(path, firstNodeParent);
-        if (targetNode == null) {
+    public static boolean processAugmentation(final AugmentationSchemaBuilder augment,
+            final ModuleBuilder firstNodeParent) {
+        Optional<SchemaNodeBuilder> potentialTargetNode = findSchemaNodeInModule(augment.getTargetNodeSchemaPath(),
+                firstNodeParent);
+        if (!potentialTargetNode.isPresent()) {
             return false;
         }
-
+        SchemaNodeBuilder targetNode = potentialTargetNode.get();
         fillAugmentTarget(augment, targetNode);
+        Preconditions.checkState(targetNode instanceof AugmentationTargetBuilder,
+                "Node refered by augmentation must be valid augmentation target");
         ((AugmentationTargetBuilder) targetNode).addAugmentation(augment);
         augment.setResolved(true);
         return true;
@@ -549,7 +722,8 @@ public final class ParserUtils {
         }
     }
 
-    public static IdentitySchemaNodeBuilder findIdentity(final Set<IdentitySchemaNodeBuilder> identities, final String name) {
+    public static IdentitySchemaNodeBuilder findIdentity(final Set<IdentitySchemaNodeBuilder> identities,
+            final String name) {
         for (IdentitySchemaNodeBuilder identity : identities) {
             if (identity.getQName().getLocalName().equals(name)) {
                 return identity;
@@ -580,8 +754,9 @@ public final class ParserUtils {
         return parentModule;
     }
 
-    public static Set<DataSchemaNodeBuilder> wrapChildNodes(final String moduleName, final int line, final Set<DataSchemaNode> nodes,
-            final SchemaPath parentPath, final URI ns, final Date rev, final String pref) {
+    public static Set<DataSchemaNodeBuilder> wrapChildNodes(final String moduleName, final int line,
+            final Set<DataSchemaNode> nodes, final SchemaPath parentPath, final URI ns, final Date rev,
+            final String pref) {
         Set<DataSchemaNodeBuilder> result = new HashSet<>();
 
         for (DataSchemaNode node : nodes) {
@@ -592,8 +767,8 @@ public final class ParserUtils {
         return result;
     }
 
-    public static DataSchemaNodeBuilder wrapChildNode(final String moduleName, final int line, final DataSchemaNode node,
-            final SchemaPath parentPath, final QName qname) {
+    public static DataSchemaNodeBuilder wrapChildNode(final String moduleName, final int line,
+            final DataSchemaNode node, final SchemaPath parentPath, final QName qname) {
 
         final SchemaPath schemaPath = parentPath.createChild(qname);
 
@@ -617,8 +792,9 @@ public final class ParserUtils {
         }
     }
 
-    public static Set<GroupingBuilder> wrapGroupings(final String moduleName, final int line, final Set<GroupingDefinition> nodes,
-            final SchemaPath parentPath, final URI ns, final Date rev, final String pref) {
+    public static Set<GroupingBuilder> wrapGroupings(final String moduleName, final int line,
+            final Set<GroupingDefinition> nodes, final SchemaPath parentPath, final URI ns, final Date rev,
+            final String pref) {
         Set<GroupingBuilder> result = new HashSet<>();
         for (GroupingDefinition node : nodes) {
             QName qname = new QName(ns, rev, pref, node.getQName().getLocalName());
@@ -628,8 +804,9 @@ public final class ParserUtils {
         return result;
     }
 
-    public static Set<TypeDefinitionBuilder> wrapTypedefs(final String moduleName, final int line, final DataNodeContainer dataNode,
-            final SchemaPath parentPath, final URI ns, final Date rev, final String pref) {
+    public static Set<TypeDefinitionBuilder> wrapTypedefs(final String moduleName, final int line,
+            final DataNodeContainer dataNode, final SchemaPath parentPath, final URI ns, final Date rev,
+            final String pref) {
         Set<TypeDefinition<?>> nodes = dataNode.getTypeDefinitions();
         Set<TypeDefinitionBuilder> result = new HashSet<>();
         for (TypeDefinition<?> node : nodes) {
@@ -643,7 +820,8 @@ public final class ParserUtils {
     }
 
     public static List<UnknownSchemaNodeBuilder> wrapUnknownNodes(final String moduleName, final int line,
-            final List<UnknownSchemaNode> nodes, final SchemaPath parentPath, final URI ns, final Date rev, final String pref) {
+            final List<UnknownSchemaNode> nodes, final SchemaPath parentPath, final URI ns, final Date rev,
+            final String pref) {
         List<UnknownSchemaNodeBuilder> result = new ArrayList<>();
         for (UnknownSchemaNode node : nodes) {
             QName qname = new QName(ns, rev, pref, node.getQName().getLocalName());
index 52c7cfcb3469a969ad35cffccbcf5e848165b19c..92e6f31e64e835ee34af115caaa02fed728cb601 100644 (file)
@@ -11,8 +11,12 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import java.io.*;
-import java.util.*;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 import org.junit.Test;
 import org.opendaylight.yangtools.yang.model.parser.api.YangModelParser;
@@ -174,8 +178,9 @@ public class YangParserNegativeTest {
                 fail("YangParseException should by thrown");
             }
         } catch (YangParseException e) {
-            String expected = "Error in module 'augment1' at line 10: Failed to perform augmentation: Error in module 'augment0' at line 8: Can not add 'leaf id' to 'container bar' in module 'augment0': node with same name already declared at line 9";
-            assertEquals(expected, e.getMessage());
+            assertTrue(e.getMessage().contains("Error in module 'augment1'"));
+            assertTrue(e.getMessage().contains("Failed to perform augmentation:"));
+
         }
     }
 
@@ -189,8 +194,8 @@ public class YangParserNegativeTest {
                 fail("YangParseException should by thrown");
             }
         } catch (YangParseException e) {
-            String expected = "Error in module 'augment0' at line 17: Can not add 'anyxml delta' to node 'choice-ext' in module 'augment0': case with same name already declared at line 18";
-            assertEquals(expected, e.getMessage());
+            assertTrue(e.getMessage().contains("Error in module "));
+            assertTrue(e.getMessage().contains("case with same name already declared "));
         }
     }
 
index ff8519ccdbacd6e26a652f70c86a73bc12815925..27b0f3f8c58f0ba4c0b8639c4665ecd0bfb53366 100644 (file)
@@ -3,6 +3,8 @@ module augment0 {
     namespace "urn:simple.augment0.demo";
     prefix "a0";
 
+    revision "2014-06-02";
+
     container foo {
         description "foo container";
         container bar {
index d08ea8f062f9b9b0092d495272cde33bf73958b4..8ccafd560815a754d75c4052f81ab7afe70d8b4f 100644 (file)
@@ -1,12 +1,21 @@
 module augment1 {
     yang-version 1;
-    namespace "urn:simple.augment0.demo";
+    namespace "urn:simple.augment1.demo";
     prefix "a1";
 
+
     import augment0 {
         prefix "a0";
     }
 
+    revision "2014-06-02";
+
+    augment "/a0:foo/a0:bar" {
+        leaf id {
+            type string;
+        }
+    }
+
     augment "/a0:foo/a0:bar" {
         leaf id {
             type string;
index 4d87619ae25fea2b573e3ba054d7f070aef8de2c..632a6c5d926410c8049d81ac7372635ac910e6b3 100644 (file)
@@ -7,6 +7,12 @@ module augment2 {
         prefix "a0";
     }
 
+    revision "2014-06-02";
+
+    augment "/a0:foo/a0:bar/a0:choice-ext" {
+        anyxml delta;
+    }
+
     augment "/a0:foo/a0:bar/a0:choice-ext" {
         anyxml delta;
     }