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