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