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