Merge "BUG-1537: improved YangModuleInfo."
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / builder / impl / BuilderUtils.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.parser.builder.impl;
9
10 import com.google.common.base.Function;
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import com.google.common.base.Splitter;
14 import com.google.common.collect.Collections2;
15 import com.google.common.collect.Iterables;
16 import com.google.common.io.ByteSource;
17 import java.io.ByteArrayOutputStream;
18 import java.io.File;
19 import java.io.FileNotFoundException;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.net.URI;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Date;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.LinkedHashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33 import java.util.TreeMap;
34 import org.antlr.v4.runtime.tree.ParseTree;
35 import org.apache.commons.io.IOUtils;
36 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Belongs_to_stmtContext;
37 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Module_header_stmtsContext;
38 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Module_stmtContext;
39 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Namespace_stmtContext;
40 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Revision_stmtsContext;
41 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Submodule_header_stmtsContext;
42 import org.opendaylight.yangtools.antlrv4.code.gen.YangParser.Submodule_stmtContext;
43 import org.opendaylight.yangtools.yang.common.QName;
44 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
46 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
47 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
49 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
51 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
53 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.Module;
55 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
56 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
57 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
58 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
59 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
60 import org.opendaylight.yangtools.yang.model.util.ExtendedType;
61 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationSchemaBuilder;
62 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationTargetBuilder;
63 import org.opendaylight.yangtools.yang.parser.builder.api.Builder;
64 import org.opendaylight.yangtools.yang.parser.builder.api.DataNodeContainerBuilder;
65 import org.opendaylight.yangtools.yang.parser.builder.api.DataSchemaNodeBuilder;
66 import org.opendaylight.yangtools.yang.parser.builder.api.GroupingBuilder;
67 import org.opendaylight.yangtools.yang.parser.builder.api.GroupingMember;
68 import org.opendaylight.yangtools.yang.parser.builder.api.SchemaNodeBuilder;
69 import org.opendaylight.yangtools.yang.parser.builder.api.TypeDefinitionBuilder;
70 import org.opendaylight.yangtools.yang.parser.builder.api.UnknownSchemaNodeBuilder;
71 import org.opendaylight.yangtools.yang.parser.builder.api.UsesNodeBuilder;
72 import org.opendaylight.yangtools.yang.parser.impl.ParserListenerUtils;
73 import org.opendaylight.yangtools.yang.parser.impl.util.YangModelDependencyInfo;
74 import org.opendaylight.yangtools.yang.parser.util.NamedByteArrayInputStream;
75 import org.opendaylight.yangtools.yang.parser.util.NamedFileInputStream;
76 import org.opendaylight.yangtools.yang.parser.util.YangParseException;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
79
80 public final class BuilderUtils {
81
82     private static final Logger LOG = LoggerFactory.getLogger(BuilderUtils.class);
83     private static final Splitter COLON_SPLITTER = Splitter.on(':');
84     private static final Date NULL_DATE = new Date(0L);
85     private static final String INPUT = "input";
86     private static final String OUTPUT = "output";
87
88     private BuilderUtils() {
89     }
90
91     public static Collection<ByteSource> streamsToByteSources(final Collection<InputStream> streams) throws IOException {
92         Collection<ByteSource> result = new HashSet<>();
93         for (InputStream stream : streams) {
94             result.add(new ByteSourceImpl(stream));
95         }
96         return result;
97     }
98
99     public static ByteSource fileToByteSource(final File file) {
100         return new ByteSource() {
101             @Override
102             public InputStream openStream() throws IOException {
103                 return new NamedFileInputStream(file, file.getAbsolutePath());
104             }
105         };
106     }
107
108     public static Collection<ByteSource> filesToByteSources(final Collection<File> streams)
109             throws FileNotFoundException {
110         return Collections2.transform(streams, new Function<File, ByteSource>() {
111             @Override
112             public ByteSource apply(final File input) {
113                 return new ByteSource() {
114                     @Override
115                     public InputStream openStream() throws IOException {
116                         return new NamedFileInputStream(input, input.getAbsolutePath());
117                     }
118                 };
119             }
120         });
121     }
122
123     /**
124      * Create new SchemaPath from given path and qname.
125      *
126      * @param schemaPath
127      *            base path
128      * @param qname
129      *            one or more qnames added to base path
130      * @return new SchemaPath from given path and qname
131      *
132      * @deprecated Use {@link SchemaPath#createChild(QName...)} instead.
133      */
134     @Deprecated
135     public static SchemaPath createSchemaPath(final SchemaPath schemaPath, final QName... qname) {
136         return schemaPath.createChild(qname);
137     }
138
139     /**
140      * Find dependent module based on given prefix
141      *
142      * @param modules
143      *            all available modules
144      * @param module
145      *            current module
146      * @param prefix
147      *            target module prefix
148      * @param line
149      *            current line in yang model
150      * @return module builder if found, null otherwise
151      */
152     public static ModuleBuilder findModuleFromBuilders(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
153             final ModuleBuilder module, final String prefix, final int line) {
154         ModuleBuilder dependentModule;
155         Date dependentModuleRevision;
156
157         if (prefix == null) {
158             dependentModule = module;
159         } else if (prefix.equals(module.getPrefix())) {
160             dependentModule = module;
161         } else {
162             ModuleImport dependentModuleImport = module.getImport(prefix);
163             if (dependentModuleImport == null) {
164                 throw new YangParseException(module.getName(), line, "No import found with prefix '" + prefix + "'.");
165             }
166             String dependentModuleName = dependentModuleImport.getModuleName();
167             dependentModuleRevision = dependentModuleImport.getRevision();
168
169             TreeMap<Date, ModuleBuilder> moduleBuildersByRevision = modules.get(dependentModuleName);
170             if (moduleBuildersByRevision == null) {
171                 return null;
172             }
173             if (dependentModuleRevision == null) {
174                 dependentModule = moduleBuildersByRevision.lastEntry().getValue();
175             } else {
176                 dependentModule = moduleBuildersByRevision.get(dependentModuleRevision);
177             }
178         }
179         return dependentModule;
180     }
181
182     public static ModuleBuilder findModuleFromBuilders(ModuleImport imp, Iterable<ModuleBuilder> modules) {
183         String name = imp.getModuleName();
184         Date revision = imp.getRevision();
185         TreeMap<Date, ModuleBuilder> map = new TreeMap<>();
186         for (ModuleBuilder module : modules) {
187             if (module != null) {
188                 if (module.getName().equals(name)) {
189                     map.put(module.getRevision(), module);
190                 }
191             }
192         }
193         if (map.isEmpty()) {
194             return null;
195         }
196         if (revision == null) {
197             return map.lastEntry().getValue();
198         }
199         return map.get(revision);
200     }
201
202     /**
203      * Find module from context based on prefix.
204      *
205      * @param context
206      *            schema context
207      * @param currentModule
208      *            current module
209      * @param prefix
210      *            prefix used to reference dependent module
211      * @param line
212      *            current line in yang model
213      * @return module based on import with given prefix if found in context,
214      *         null otherwise
215      * @throws YangParseException
216      *             if no import found with given prefix
217      */
218     public static Module findModuleFromContext(final SchemaContext context, final ModuleBuilder currentModule,
219             final String prefix, final int line) {
220         TreeMap<Date, Module> modulesByRevision = new TreeMap<>();
221
222         ModuleImport dependentModuleImport = currentModule.getImport(prefix);
223         if (dependentModuleImport == null) {
224             throw new YangParseException(currentModule.getName(), line, "No import found with prefix '" + prefix + "'.");
225         }
226         String dependentModuleName = dependentModuleImport.getModuleName();
227         Date dependentModuleRevision = dependentModuleImport.getRevision();
228
229         for (Module contextModule : context.getModules()) {
230             if (contextModule.getName().equals(dependentModuleName)) {
231                 Date revision = contextModule.getRevision();
232                 if (revision == null) {
233                     revision = NULL_DATE;
234                 }
235                 modulesByRevision.put(revision, contextModule);
236             }
237         }
238
239         Module result;
240         if (dependentModuleRevision == null) {
241             result = modulesByRevision.get(modulesByRevision.firstKey());
242         } else {
243             result = modulesByRevision.get(dependentModuleRevision);
244         }
245         if (result == null) {
246             throw new YangParseException(currentModule.getName(), line, "Module not found for prefix " + prefix);
247         }
248
249         return result;
250     }
251
252     /**
253      * Add all augment's child nodes to given target.
254      *
255      * @param augment
256      *            builder of augment statement
257      * @param target
258      *            augmentation target node
259      */
260     public static void fillAugmentTarget(final AugmentationSchemaBuilder augment, final Builder target) {
261         if (target instanceof DataNodeContainerBuilder) {
262             fillAugmentTarget(augment, (DataNodeContainerBuilder) target);
263         } else if (target instanceof ChoiceBuilder) {
264             fillAugmentTarget(augment, (ChoiceBuilder) target);
265         } else {
266             throw new YangParseException(
267                     augment.getModuleName(),
268                     augment.getLine(),
269                     "Error in augment parsing: The target node MUST be either a container, list, choice, case, input, output, or notification node.");
270         }
271     }
272
273     /**
274      * Add all augment's child nodes to given target.
275      *
276      * @param augment
277      *            builder of augment statement
278      * @param target
279      *            augmentation target node
280      */
281     private static void fillAugmentTarget(final AugmentationSchemaBuilder augment, final DataNodeContainerBuilder target) {
282         for (DataSchemaNodeBuilder child : augment.getChildNodeBuilders()) {
283             DataSchemaNodeBuilder childCopy = CopyUtils.copy(child, target, false);
284             if (augment.getParent() instanceof UsesNodeBuilder) {
285                 setNodeAddedByUses(childCopy);
286             }
287             setNodeAugmenting(childCopy);
288             try {
289                 target.addChildNode(childCopy);
290             } catch (YangParseException e) {
291
292                 // more descriptive message
293                 throw new YangParseException(augment.getModuleName(), augment.getLine(),
294                         "Failed to perform augmentation: " + e.getMessage());
295             }
296         }
297     }
298
299     /**
300      * Add all augment's child nodes to given target.
301      *
302      * @param augment
303      *            builder of augment statement
304      * @param target
305      *            augmentation target choice node
306      */
307     private static void fillAugmentTarget(final AugmentationSchemaBuilder augment, final ChoiceBuilder target) {
308         for (DataSchemaNodeBuilder builder : augment.getChildNodeBuilders()) {
309             DataSchemaNodeBuilder childCopy = CopyUtils.copy(builder, target, false);
310             if (augment.getParent() instanceof UsesNodeBuilder) {
311                 setNodeAddedByUses(childCopy);
312             }
313             setNodeAugmenting(childCopy);
314             target.addCase(childCopy);
315         }
316         for (UsesNodeBuilder usesNode : augment.getUsesNodeBuilders()) {
317             if (usesNode != null) {
318                 throw new YangParseException(augment.getModuleName(), augment.getLine(),
319                         "Error in augment parsing: cannot augment choice with nodes from grouping");
320             }
321         }
322     }
323
324     /**
325      * Set augmenting flag to true for node and all its child nodes.
326      *
327      * @param node
328      */
329     private static void setNodeAugmenting(final DataSchemaNodeBuilder node) {
330         node.setAugmenting(true);
331         if (node instanceof DataNodeContainerBuilder) {
332             DataNodeContainerBuilder dataNodeChild = (DataNodeContainerBuilder) node;
333             for (DataSchemaNodeBuilder inner : dataNodeChild.getChildNodeBuilders()) {
334                 setNodeAugmenting(inner);
335             }
336         } else if (node instanceof ChoiceBuilder) {
337             ChoiceBuilder choiceChild = (ChoiceBuilder) node;
338             for (ChoiceCaseBuilder inner : choiceChild.getCases()) {
339                 setNodeAugmenting(inner);
340             }
341         }
342     }
343
344     /**
345      * Set addedByUses flag to true for node and all its child nodes.
346      *
347      * @param node
348      */
349     public static void setNodeAddedByUses(final GroupingMember node) {
350         node.setAddedByUses(true);
351         if (node instanceof DataNodeContainerBuilder) {
352             DataNodeContainerBuilder dataNodeChild = (DataNodeContainerBuilder) node;
353             for (DataSchemaNodeBuilder inner : dataNodeChild.getChildNodeBuilders()) {
354                 setNodeAddedByUses(inner);
355             }
356         } else if (node instanceof ChoiceBuilder) {
357             ChoiceBuilder choiceChild = (ChoiceBuilder) node;
358             for (ChoiceCaseBuilder inner : choiceChild.getCases()) {
359                 setNodeAddedByUses(inner);
360             }
361         }
362     }
363
364     public static SchemaNodeBuilder findSchemaNode(final Iterable<QName> path, final SchemaNodeBuilder parentNode) {
365         SchemaNodeBuilder node = null;
366         SchemaNodeBuilder parent = parentNode;
367         int size = Iterables.size(path);
368         int i = 0;
369         for (QName qname : path) {
370             String name = qname.getLocalName();
371             if (parent instanceof DataNodeContainerBuilder) {
372                 node = ((DataNodeContainerBuilder) parent).getDataChildByName(name);
373                 if (node == null) {
374                     node = findUnknownNode(name, parent);
375                 }
376             } else if (parent instanceof ChoiceBuilder) {
377                 node = ((ChoiceBuilder) parent).getCaseNodeByName(name);
378                 if (node == null) {
379                     node = findUnknownNode(name, parent);
380                 }
381             } else if (parent instanceof RpcDefinitionBuilder) {
382                 if ("input".equals(name)) {
383                     node = ((RpcDefinitionBuilder) parent).getInput();
384                 } else if ("output".equals(name)) {
385                     node = ((RpcDefinitionBuilder) parent).getOutput();
386                 } else {
387                     if (node == null) {
388                         node = findUnknownNode(name, parent);
389                     }
390                 }
391             } else {
392                 node = findUnknownNode(name, parent);
393             }
394
395             if (i < size - 1) {
396                 parent = node;
397             }
398             i = i + 1;
399         }
400
401         return node;
402     }
403
404     private static UnknownSchemaNodeBuilder findUnknownNode(final String name, final Builder parent) {
405         for (UnknownSchemaNodeBuilder un : parent.getUnknownNodes()) {
406             if (un.getQName().getLocalName().equals(name)) {
407                 return un;
408             }
409         }
410         return null;
411     }
412
413     /**
414      *
415      * Find a builder for node in data namespace of YANG module.
416      *
417      * Search is performed on full QName equals, this means builders and schema
418      * path MUST be resolved against imports and their namespaces.
419      *
420      * Search is done in data namespace, this means notification, rpc
421      * definitions and top level data definitions are considered as top-level
422      * items, from which it is possible to traverse.
423      *
424      *
425      * @param schemaPath
426      *            Schema Path to node
427      * @param module
428      *            ModuleBuilder to start lookup in
429      * @return Node Builder if found, {@link Optional#absent()} otherwise.
430      */
431     public static Optional<SchemaNodeBuilder> findSchemaNodeInModule(final SchemaPath schemaPath,
432             final ModuleBuilder module) {
433         Iterator<QName> path = schemaPath.getPathFromRoot().iterator();
434         Preconditions.checkArgument(path.hasNext(), "Schema Path must contain at least one element.");
435         QName first = path.next();
436         Optional<SchemaNodeBuilder> currentNode = getDataNamespaceChild(module, first);
437
438         while (currentNode.isPresent() && path.hasNext()) {
439             SchemaNodeBuilder currentParent = currentNode.get();
440             QName currentPath = path.next();
441             currentNode = findDataChild(currentParent, currentPath);
442             if (!currentNode.isPresent()) {
443                 for (SchemaNodeBuilder un : currentParent.getUnknownNodes()) {
444                     if (un.getQName().equals(currentPath)) {
445                         currentNode = Optional.of(un);
446                     }
447                 }
448             }
449         }
450         return currentNode;
451     }
452
453     private static Optional<SchemaNodeBuilder> findDataChild(final SchemaNodeBuilder parent, final QName child) {
454         if (parent instanceof DataNodeContainerBuilder) {
455             return castOptional(SchemaNodeBuilder.class,
456                     findDataChildInDataNodeContainer((DataNodeContainerBuilder) parent, child));
457         } else if (parent instanceof ChoiceBuilder) {
458             return castOptional(SchemaNodeBuilder.class, findCaseInChoice((ChoiceBuilder) parent, child));
459         } else if (parent instanceof RpcDefinitionBuilder) {
460             return castOptional(SchemaNodeBuilder.class, findContainerInRpc((RpcDefinitionBuilder) parent, child));
461
462         } else {
463             LOG.trace("Child {} not found in node {}", child, parent);
464             return Optional.absent();
465         }
466     }
467
468     /**
469      * Casts optional from one argument to other.
470      *
471      * @param cls
472      *            Class to be checked
473      * @param optional
474      *            Original value
475      * @return Optional object with type argument casted as cls
476      */
477     private static <T> Optional<T> castOptional(final Class<T> cls, final Optional<?> optional) {
478         if (optional.isPresent()) {
479             Object value = optional.get();
480             if (cls.isInstance(value)) {
481                 @SuppressWarnings("unchecked")
482                 // Actually checked by outer if
483                 T casted = (T) value;
484                 return Optional.of(casted);
485             }
486         }
487         return Optional.absent();
488     }
489
490     // FIXME: if rpc does not define input or output, this method creates it
491     /**
492      *
493      * Gets input / output container from {@link RpcDefinitionBuilder} if QName
494      * is input/output.
495      *
496      *
497      * @param parent
498      *            RPC Definition builder
499      * @param child
500      *            Child QName
501      * @return Optional of input/output if defined and QName is input/output.
502      *         Otherwise {@link Optional#absent()}.
503      */
504     private static Optional<ContainerSchemaNodeBuilder> findContainerInRpc(final RpcDefinitionBuilder parent,
505             final QName child) {
506         if (INPUT.equals(child.getLocalName())) {
507             if (parent.getInput() == null) {
508                 QName qname = QName.create(parent.getQName().getModule(), "input");
509                 final ContainerSchemaNodeBuilder inputBuilder = new ContainerSchemaNodeBuilder(parent.getModuleName(),
510                         parent.getLine(), qname, parent.getPath().createChild(qname));
511                 inputBuilder.setParent(parent);
512                 parent.setInput(inputBuilder);
513                 return Optional.of(inputBuilder);
514             }
515             return Optional.of(parent.getInput());
516         } else if (OUTPUT.equals(child.getLocalName())) {
517             if (parent.getOutput() == null) {
518                 QName qname = QName.create(parent.getQName().getModule(), "output");
519                 final ContainerSchemaNodeBuilder outputBuilder = new ContainerSchemaNodeBuilder(parent.getModuleName(),
520                         parent.getLine(), qname, parent.getPath().createChild(qname));
521                 outputBuilder.setParent(parent);
522                 parent.setOutput(outputBuilder);
523                 return Optional.of(outputBuilder);
524             }
525             return Optional.of(parent.getOutput());
526         }
527         LOG.trace("Child {} not found in node {}", child, parent);
528         return Optional.absent();
529     }
530
531     /**
532      * Finds case by QName in {@link ChoiceBuilder}
533      *
534      *
535      * @param parent
536      *            DataNodeContainer in which lookup should be performed
537      * @param child
538      *            QName of child
539      * @return Optional of child if found.
540      */
541
542     private static Optional<ChoiceCaseBuilder> findCaseInChoice(final ChoiceBuilder parent, final QName child) {
543         for (ChoiceCaseBuilder caze : parent.getCases()) {
544             if (caze.getQName().equals(child)) {
545                 return Optional.of(caze);
546             }
547         }
548         LOG.trace("Child {} not found in node {}", child, parent);
549         return Optional.absent();
550     }
551
552     /**
553      * Finds direct child by QName in {@link DataNodeContainerBuilder}
554      *
555      *
556      * @param parent
557      *            DataNodeContainer in which lookup should be performed
558      * @param child
559      *            QName of child
560      * @return Optional of child if found.
561      */
562     private static Optional<DataSchemaNodeBuilder> findDataChildInDataNodeContainer(final DataNodeContainerBuilder parent,
563             final QName child) {
564         for (DataSchemaNodeBuilder childNode : parent.getChildNodeBuilders()) {
565             if (childNode.getQName().equals(child)) {
566                 return Optional.of(childNode);
567             }
568         }
569         LOG.trace("Child {} not found in node {}", child, parent);
570         return Optional.absent();
571     }
572
573     /**
574      *
575      * Find a child builder for node in data namespace of YANG module.
576      *
577      * Search is performed on full QName equals, this means builders and schema
578      * path MUST be resolved against imports and their namespaces.
579      *
580      * Search is done in data namespace, this means notification, rpc
581      * definitions and top level data definitions are considered as top-level
582      * items, from which it is possible to traverse.
583      *
584      *
585      * @param child
586      *            Child QName.
587      * @param module
588      *            ModuleBuilder to start lookup in
589      * @return Node Builder if found, {@link Optional#absent()} otherwise.
590      */
591     private static Optional<SchemaNodeBuilder> getDataNamespaceChild(final ModuleBuilder module, final QName child) {
592         /*
593          * First we do lookup in data tree, if node is found we return it.
594          */
595         final Optional<SchemaNodeBuilder> dataTreeNode = getDataChildByQName(module, child);
596         if (dataTreeNode.isPresent()) {
597             return dataTreeNode;
598         }
599
600         /*
601          * We lookup in notifications
602          */
603         Set<NotificationBuilder> notifications = module.getAddedNotifications();
604         for (NotificationBuilder notification : notifications) {
605             if (notification.getQName().equals(child)) {
606                 return Optional.<SchemaNodeBuilder> of(notification);
607             }
608         }
609
610         /*
611          * We lookup in RPCs
612          */
613         Set<RpcDefinitionBuilder> rpcs = module.getAddedRpcs();
614         for (RpcDefinitionBuilder rpc : rpcs) {
615             if (rpc.getQName().equals(child)) {
616                 return Optional.<SchemaNodeBuilder> of(rpc);
617             }
618         }
619         LOG.trace("Child {} not found in data namespace of module {}", child, module);
620         return Optional.absent();
621     }
622
623     private static Optional<SchemaNodeBuilder> getDataChildByQName(final DataNodeContainerBuilder builder, final QName child) {
624         for (DataSchemaNodeBuilder childNode : builder.getChildNodeBuilders()) {
625             if (childNode.getQName().equals(child)) {
626                 return Optional.<SchemaNodeBuilder> of(childNode);
627             }
628         }
629         LOG.trace("Child {} not found in node {}", child, builder);
630         return Optional.absent();
631     }
632
633     /**
634      * Find augment target node and perform augmentation.
635      *
636      * @param augment
637      *            augment builder to process
638      * @param firstNodeParent
639      *            parent of first node in path
640      * @return true if augmentation process succeed, false otherwise
641      */
642     public static boolean processAugmentation(final AugmentationSchemaBuilder augment,
643             final ModuleBuilder firstNodeParent) {
644         Optional<SchemaNodeBuilder> potentialTargetNode = findSchemaNodeInModule(augment.getTargetPath(),
645                 firstNodeParent);
646         if (!potentialTargetNode.isPresent()) {
647             return false;
648         } else if (potentialTargetNode.get() instanceof UnknownSchemaNodeBuilder) {
649             LOG.warn("Error in augment parsing: unsupported augment target: {}", potentialTargetNode.get());
650             return true;
651         }
652         SchemaNodeBuilder targetNode = potentialTargetNode.get();
653         fillAugmentTarget(augment, targetNode);
654         Preconditions.checkState(targetNode instanceof AugmentationTargetBuilder,
655                 "Node refered by augmentation must be valid augmentation target");
656         ((AugmentationTargetBuilder) targetNode).addAugmentation(augment);
657         augment.setResolved(true);
658         return true;
659     }
660
661     public static IdentitySchemaNodeBuilder findBaseIdentity(final ModuleBuilder module, final String baseString,
662             final int line) {
663
664         // FIXME: optimize indexOf() away?
665         if (baseString.indexOf(':') != -1) {
666             final Iterator<String> it = COLON_SPLITTER.split(baseString).iterator();
667             final String prefix = it.next();
668             final String name = it.next();
669
670             if (it.hasNext()) {
671                 throw new YangParseException(module.getName(), line, "Failed to parse identityref base: " + baseString);
672             }
673
674             ModuleBuilder dependentModule = getModuleByPrefix(module, prefix);
675             if (dependentModule == null) {
676                 return null;
677             }
678
679             return findIdentity(dependentModule.getAddedIdentities(), name);
680         } else {
681             return findIdentity(module.getAddedIdentities(), baseString);
682         }
683     }
684
685     public static IdentitySchemaNodeBuilder findIdentity(final Set<IdentitySchemaNodeBuilder> identities,
686             final String name) {
687         for (IdentitySchemaNodeBuilder identity : identities) {
688             if (identity.getQName().getLocalName().equals(name)) {
689                 return identity;
690             }
691         }
692         return null;
693     }
694
695     /**
696      * Get module in which this node is defined.
697      *
698      * @param node
699      * @return builder of module where this node is defined
700      */
701     public static ModuleBuilder getParentModule(final Builder node) {
702         if (node instanceof ModuleBuilder) {
703             return (ModuleBuilder) node;
704         }
705         Builder parent = node.getParent();
706         while (!(parent instanceof ModuleBuilder)) {
707             parent = parent.getParent();
708         }
709         ModuleBuilder parentModule = (ModuleBuilder) parent;
710         if (parentModule.isSubmodule()) {
711             parentModule = parentModule.getParent();
712         }
713         return parentModule;
714     }
715
716     public static Set<DataSchemaNodeBuilder> wrapChildNodes(final String moduleName, final int line,
717             final Collection<DataSchemaNode> nodes, final SchemaPath parentPath, final QName parentQName) {
718         Set<DataSchemaNodeBuilder> result = new LinkedHashSet<>(nodes.size());
719
720         for (DataSchemaNode node : nodes) {
721             QName qname = QName.create(parentQName, node.getQName().getLocalName());
722             DataSchemaNodeBuilder wrapped = wrapChildNode(moduleName, line, node, parentPath, qname);
723             result.add(wrapped);
724         }
725         return result;
726     }
727
728     public static DataSchemaNodeBuilder wrapChildNode(final String moduleName, final int line,
729             final DataSchemaNode node, final SchemaPath parentPath, final QName qname) {
730
731         final SchemaPath schemaPath = parentPath.createChild(qname);
732
733         if (node instanceof AnyXmlSchemaNode) {
734             return new AnyXmlBuilder(moduleName, line, qname, schemaPath, ((AnyXmlSchemaNode) node));
735         } else if (node instanceof ChoiceNode) {
736             return new ChoiceBuilder(moduleName, line, qname, schemaPath, ((ChoiceNode) node));
737         } else if (node instanceof ContainerSchemaNode) {
738             return new ContainerSchemaNodeBuilder(moduleName, line, qname, schemaPath, ((ContainerSchemaNode) node));
739         } else if (node instanceof LeafSchemaNode) {
740             return new LeafSchemaNodeBuilder(moduleName, line, qname, schemaPath, ((LeafSchemaNode) node));
741         } else if (node instanceof LeafListSchemaNode) {
742             return new LeafListSchemaNodeBuilder(moduleName, line, qname, schemaPath, ((LeafListSchemaNode) node));
743         } else if (node instanceof ListSchemaNode) {
744             return new ListSchemaNodeBuilder(moduleName, line, qname, schemaPath, ((ListSchemaNode) node));
745         } else if (node instanceof ChoiceCaseNode) {
746             return new ChoiceCaseBuilder(moduleName, line, qname, schemaPath, ((ChoiceCaseNode) node));
747         } else {
748             throw new YangParseException(moduleName, line, "Failed to copy node: Unknown type of DataSchemaNode: "
749                     + node);
750         }
751     }
752
753     public static Set<GroupingBuilder> wrapGroupings(final String moduleName, final int line,
754             final Set<GroupingDefinition> nodes, final SchemaPath parentPath, final QName parentQName) {
755         Set<GroupingBuilder> result = new HashSet<>();
756         for (GroupingDefinition node : nodes) {
757             QName qname = QName.create(parentQName, node.getQName().getLocalName());
758             SchemaPath schemaPath = parentPath.createChild(qname);
759             result.add(new GroupingBuilderImpl(moduleName, line, qname, schemaPath, node));
760         }
761         return result;
762     }
763
764     public static Set<TypeDefinitionBuilder> wrapTypedefs(final String moduleName, final int line,
765             final DataNodeContainer dataNode, final SchemaPath parentPath, final QName parentQName) {
766         Set<TypeDefinition<?>> nodes = dataNode.getTypeDefinitions();
767         Set<TypeDefinitionBuilder> result = new HashSet<>();
768         for (TypeDefinition<?> node : nodes) {
769             QName qname = QName.create(parentQName, node.getQName().getLocalName());
770             SchemaPath schemaPath = parentPath.createChild(qname);
771             result.add(new TypeDefinitionBuilderImpl(moduleName, line, qname, schemaPath, ((ExtendedType) node)));
772         }
773         return result;
774     }
775
776     public static List<UnknownSchemaNodeBuilderImpl> wrapUnknownNodes(final String moduleName, final int line,
777             final List<UnknownSchemaNode> nodes, final SchemaPath parentPath, final QName parentQName) {
778         List<UnknownSchemaNodeBuilderImpl> result = new ArrayList<>();
779         for (UnknownSchemaNode node : nodes) {
780             QName qname = QName.create(parentQName, node.getQName().getLocalName());
781             SchemaPath schemaPath = parentPath.createChild(qname);
782             result.add(new UnknownSchemaNodeBuilderImpl(moduleName, line, qname, schemaPath, node));
783         }
784         return result;
785     }
786
787     private static final class ByteSourceImpl extends ByteSource {
788         private final String toString;
789         private final ByteArrayOutputStream output = new ByteArrayOutputStream();
790
791         private ByteSourceImpl(final InputStream input) throws IOException {
792             toString = input.toString();
793             IOUtils.copy(input, output);
794         }
795
796         @Override
797         public InputStream openStream() throws IOException {
798             return new NamedByteArrayInputStream(output.toByteArray(), toString);
799         }
800     }
801
802     public static ModuleBuilder getModuleByPrefix(final ModuleBuilder module, final String prefix) {
803         if (prefix == null || prefix.isEmpty() || prefix.equals(module.getPrefix())) {
804             return module;
805         } else {
806             return module.getImportedModule(prefix);
807         }
808     }
809
810     public static ModuleBuilder findModule(final QName qname, final Map<URI, TreeMap<Date, ModuleBuilder>> modules) {
811         TreeMap<Date, ModuleBuilder> map = modules.get(qname.getNamespace());
812         if (map == null) {
813             return null;
814         }
815         if (qname.getRevision() == null) {
816             return map.lastEntry().getValue();
817         }
818         return map.get(qname.getRevision());
819     }
820
821     public static Map<String, TreeMap<Date, URI>> createYangNamespaceContext(
822             final Collection<? extends ParseTree> modules, final Optional<SchemaContext> context) {
823         Map<String, TreeMap<Date, URI>> namespaceContext = new HashMap<>();
824         Set<Submodule_stmtContext> submodules = new HashSet<>();
825         // first read ParseTree collection and separate modules and submodules
826         for (ParseTree module : modules) {
827             for (int i = 0; i < module.getChildCount(); i++) {
828                 ParseTree moduleTree = module.getChild(i);
829                 if (moduleTree instanceof Submodule_stmtContext) {
830                     // put submodule context to separate collection
831                     submodules.add((Submodule_stmtContext) moduleTree);
832                 } else if (moduleTree instanceof Module_stmtContext) {
833                     // get name, revision and namespace from module
834                     Module_stmtContext moduleCtx = (Module_stmtContext) moduleTree;
835                     final String moduleName = ParserListenerUtils.stringFromNode(moduleCtx);
836                     Date rev = null;
837                     URI namespace = null;
838                     for (int j = 0; j < moduleCtx.getChildCount(); j++) {
839                         ParseTree moduleCtxChildTree = moduleCtx.getChild(j);
840                         if (moduleCtxChildTree instanceof Revision_stmtsContext) {
841                             String revisionDateStr = YangModelDependencyInfo
842                                     .getLatestRevision((Revision_stmtsContext) moduleCtxChildTree);
843                             if (revisionDateStr == null) {
844                                 rev = new Date(0);
845                             } else {
846                                 rev = QName.parseRevision(revisionDateStr);
847                             }
848                         }
849                         if (moduleCtxChildTree instanceof Module_header_stmtsContext) {
850                             Module_header_stmtsContext headerCtx = (Module_header_stmtsContext) moduleCtxChildTree;
851                             for (int k = 0; k < headerCtx.getChildCount(); k++) {
852                                 ParseTree ctx = headerCtx.getChild(k);
853                                 if (ctx instanceof Namespace_stmtContext) {
854                                     final String namespaceStr = ParserListenerUtils.stringFromNode(ctx);
855                                     namespace = URI.create(namespaceStr);
856                                     break;
857                                 }
858                             }
859                         }
860                     }
861                     // update namespaceContext
862                     TreeMap<Date, URI> revToNs = namespaceContext.get(moduleName);
863                     if (revToNs == null) {
864                         revToNs = new TreeMap<>();
865                         revToNs.put(rev, namespace);
866                         namespaceContext.put(moduleName, revToNs);
867                     }
868                     revToNs.put(rev, namespace);
869                 }
870             }
871         }
872         // after all ParseTree-s are parsed update namespaceContext with modules
873         // from SchemaContext
874         if (context.isPresent()) {
875             for (Module module : context.get().getModules()) {
876                 TreeMap<Date, URI> revToNs = namespaceContext.get(module.getName());
877                 if (revToNs == null) {
878                     revToNs = new TreeMap<>();
879                     revToNs.put(module.getRevision(), module.getNamespace());
880                     namespaceContext.put(module.getName(), revToNs);
881                 }
882                 revToNs.put(module.getRevision(), module.getNamespace());
883             }
884         }
885         // when all modules are processed, traverse submodules and update
886         // namespaceContext with mapping for submodules
887         for (Submodule_stmtContext submodule : submodules) {
888             final String moduleName = ParserListenerUtils.stringFromNode(submodule);
889             for (int i = 0; i < submodule.getChildCount(); i++) {
890                 ParseTree subHeaderCtx = submodule.getChild(i);
891                 if (subHeaderCtx instanceof Submodule_header_stmtsContext) {
892                     for (int j = 0; j < subHeaderCtx.getChildCount(); j++) {
893                         ParseTree belongsCtx = subHeaderCtx.getChild(j);
894                         if (belongsCtx instanceof Belongs_to_stmtContext) {
895                             final String belongsTo = ParserListenerUtils.stringFromNode(belongsCtx);
896                             TreeMap<Date, URI> ns = namespaceContext.get(belongsTo);
897                             if (ns == null) {
898                                 throw new YangParseException(moduleName, submodule.getStart().getLine(), String.format(
899                                         "Unresolved belongs-to statement: %s", belongsTo));
900                             }
901                             // submodule get namespace and revision from module
902                             TreeMap<Date, URI> subNs = new TreeMap<>();
903                             subNs.put(ns.firstKey(), ns.firstEntry().getValue());
904                             namespaceContext.put(moduleName, subNs);
905                         }
906                     }
907                 }
908             }
909         }
910         return namespaceContext;
911     }
912
913 }