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