e74029b424750b2b318f04df447fb7292a26c6cd
[controller.git] / opendaylight / sal / yang-prototype / code-generator / yang-model-parser-impl / src / main / java / org / opendaylight / controller / yang / parser / impl / YangParserImpl.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.controller.yang.parser.impl;
9
10 import static org.opendaylight.controller.yang.parser.util.ParserUtils.*;
11
12 import java.io.File;
13 import java.io.FileInputStream;
14 import java.io.FileNotFoundException;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Date;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.LinkedHashMap;
24 import java.util.LinkedHashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.NoSuchElementException;
29 import java.util.Set;
30 import java.util.TreeMap;
31
32 import org.antlr.v4.runtime.ANTLRInputStream;
33 import org.antlr.v4.runtime.CommonTokenStream;
34 import org.antlr.v4.runtime.tree.ParseTree;
35 import org.antlr.v4.runtime.tree.ParseTreeWalker;
36 import org.opendaylight.controller.antlrv4.code.gen.YangLexer;
37 import org.opendaylight.controller.antlrv4.code.gen.YangParser;
38 import org.opendaylight.controller.yang.common.QName;
39 import org.opendaylight.controller.yang.model.api.GroupingDefinition;
40 import org.opendaylight.controller.yang.model.api.IdentitySchemaNode;
41 import org.opendaylight.controller.yang.model.api.Module;
42 import org.opendaylight.controller.yang.model.api.SchemaContext;
43 import org.opendaylight.controller.yang.model.api.SchemaPath;
44 import org.opendaylight.controller.yang.model.api.TypeDefinition;
45 import org.opendaylight.controller.yang.model.parser.api.YangModelParser;
46 import org.opendaylight.controller.yang.model.util.ExtendedType;
47 import org.opendaylight.controller.yang.model.util.IdentityrefType;
48 import org.opendaylight.controller.yang.model.util.UnknownType;
49 import org.opendaylight.controller.yang.parser.builder.api.AugmentationSchemaBuilder;
50 import org.opendaylight.controller.yang.parser.builder.api.Builder;
51 import org.opendaylight.controller.yang.parser.builder.api.DataNodeContainerBuilder;
52 import org.opendaylight.controller.yang.parser.builder.api.GroupingBuilder;
53 import org.opendaylight.controller.yang.parser.builder.api.SchemaNodeBuilder;
54 import org.opendaylight.controller.yang.parser.builder.api.TypeAwareBuilder;
55 import org.opendaylight.controller.yang.parser.builder.api.TypeDefinitionBuilder;
56 import org.opendaylight.controller.yang.parser.builder.api.UsesNodeBuilder;
57 import org.opendaylight.controller.yang.parser.builder.impl.IdentitySchemaNodeBuilder;
58 import org.opendaylight.controller.yang.parser.builder.impl.IdentityrefTypeBuilder;
59 import org.opendaylight.controller.yang.parser.builder.impl.ModuleBuilder;
60 import org.opendaylight.controller.yang.parser.builder.impl.RpcDefinitionBuilder;
61 import org.opendaylight.controller.yang.parser.builder.impl.UnionTypeBuilder;
62 import org.opendaylight.controller.yang.parser.builder.impl.UnknownSchemaNodeBuilder;
63 import org.opendaylight.controller.yang.parser.util.ModuleDependencySort;
64 import org.opendaylight.controller.yang.parser.util.RefineHolder;
65 import org.opendaylight.controller.yang.parser.util.RefineUtils;
66 import org.opendaylight.controller.yang.parser.util.TypeConstraints;
67 import org.opendaylight.controller.yang.parser.util.YangParseException;
68 import org.opendaylight.controller.yang.validator.YangModelBasicValidator;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
71
72 import com.google.common.collect.Lists;
73 import com.google.common.collect.Maps;
74 import com.google.common.collect.Sets;
75
76 public final class YangParserImpl implements YangModelParser {
77
78     private static final Logger logger = LoggerFactory.getLogger(YangParserImpl.class);
79
80     @Override
81     public Set<Module> parseYangModels(final List<File> yangFiles) {
82         return Sets.newLinkedHashSet(parseYangModelsMapped(yangFiles).values());
83     }
84
85     @Override
86     public Set<Module> parseYangModels(final List<File> yangFiles, final SchemaContext context) {
87         if (yangFiles != null) {
88             final Map<InputStream, File> inputStreams = Maps.newHashMap();
89
90             for (final File yangFile : yangFiles) {
91                 try {
92                     inputStreams.put(new FileInputStream(yangFile), yangFile);
93                 } catch (FileNotFoundException e) {
94                     logger.warn("Exception while reading yang file: " + yangFile.getName(), e);
95                 }
96             }
97
98             Map<ModuleBuilder, InputStream> builderToStreamMap = Maps.newHashMap();
99
100             final Map<String, TreeMap<Date, ModuleBuilder>> modules = resolveModuleBuilders(
101                     Lists.newArrayList(inputStreams.keySet()), builderToStreamMap);
102
103             for (InputStream is : inputStreams.keySet()) {
104                 try {
105                     is.close();
106                 } catch (IOException e) {
107                     logger.debug("Failed to close stream.");
108                 }
109             }
110
111             return new LinkedHashSet<Module>(buildWithContext(modules, context).values());
112         }
113         return Collections.emptySet();
114     }
115
116     @Override
117     public Set<Module> parseYangModelsFromStreams(final List<InputStream> yangModelStreams) {
118         return Sets.newHashSet(parseYangModelsFromStreamsMapped(yangModelStreams).values());
119     }
120
121     @Override
122     public Set<Module> parseYangModelsFromStreams(final List<InputStream> yangModelStreams, SchemaContext context) {
123         if (yangModelStreams != null) {
124             Map<ModuleBuilder, InputStream> builderToStreamMap = Maps.newHashMap();
125             final Map<String, TreeMap<Date, ModuleBuilder>> modules = resolveModuleBuildersWithContext(
126                     yangModelStreams, builderToStreamMap, context);
127             return new LinkedHashSet<Module>(buildWithContext(modules, context).values());
128         }
129         return Collections.emptySet();
130     }
131
132     @Override
133     public Map<File, Module> parseYangModelsMapped(List<File> yangFiles) {
134         if (yangFiles != null) {
135             final Map<InputStream, File> inputStreams = Maps.newHashMap();
136
137             for (final File yangFile : yangFiles) {
138                 try {
139                     inputStreams.put(new FileInputStream(yangFile), yangFile);
140                 } catch (FileNotFoundException e) {
141                     logger.warn("Exception while reading yang file: " + yangFile.getName(), e);
142                 }
143             }
144
145             Map<ModuleBuilder, InputStream> builderToStreamMap = Maps.newHashMap();
146             final Map<String, TreeMap<Date, ModuleBuilder>> modules = resolveModuleBuilders(
147                     Lists.newArrayList(inputStreams.keySet()), builderToStreamMap);
148
149             for (InputStream is : inputStreams.keySet()) {
150                 try {
151                     is.close();
152                 } catch (IOException e) {
153                     logger.debug("Failed to close stream.");
154                 }
155             }
156
157             Map<File, Module> retVal = Maps.newLinkedHashMap();
158             Map<ModuleBuilder, Module> builderToModuleMap = build(modules);
159
160             for (Entry<ModuleBuilder, Module> builderToModule : builderToModuleMap.entrySet()) {
161                 retVal.put(inputStreams.get(builderToStreamMap.get(builderToModule.getKey())),
162                         builderToModule.getValue());
163             }
164
165             return retVal;
166         }
167         return Collections.emptyMap();
168     }
169
170     @Override
171     public Map<InputStream, Module> parseYangModelsFromStreamsMapped(final List<InputStream> yangModelStreams) {
172         Map<ModuleBuilder, InputStream> builderToStreamMap = Maps.newHashMap();
173
174         final Map<String, TreeMap<Date, ModuleBuilder>> modules = resolveModuleBuilders(yangModelStreams,
175                 builderToStreamMap);
176         Map<InputStream, Module> retVal = Maps.newLinkedHashMap();
177         Map<ModuleBuilder, Module> builderToModuleMap = build(modules);
178
179         for (Entry<ModuleBuilder, Module> builderToModule : builderToModuleMap.entrySet()) {
180             retVal.put(builderToStreamMap.get(builderToModule.getKey()), builderToModule.getValue());
181         }
182         return retVal;
183     }
184
185     @Override
186     public SchemaContext resolveSchemaContext(final Set<Module> modules) {
187         return new SchemaContextImpl(modules);
188     }
189
190     private ModuleBuilder[] parseModuleBuilders(List<InputStream> inputStreams,
191             Map<ModuleBuilder, InputStream> streamToBuilderMap) {
192
193         final ParseTreeWalker walker = new ParseTreeWalker();
194         final List<ParseTree> trees = parseStreams(inputStreams);
195         final ModuleBuilder[] builders = new ModuleBuilder[trees.size()];
196
197         // validate yang
198         new YangModelBasicValidator(walker).validate(trees);
199
200         YangParserListenerImpl yangModelParser = null;
201         for (int i = 0; i < trees.size(); i++) {
202             yangModelParser = new YangParserListenerImpl();
203             walker.walk(yangModelParser, trees.get(i));
204             ModuleBuilder moduleBuilder = yangModelParser.getModuleBuilder();
205
206             // We expect the order of trees and streams has to be the same
207             streamToBuilderMap.put(moduleBuilder, inputStreams.get(i));
208             builders[i] = moduleBuilder;
209         }
210         return builders;
211     }
212
213     private Map<String, TreeMap<Date, ModuleBuilder>> resolveModuleBuilders(final List<InputStream> yangFileStreams,
214             Map<ModuleBuilder, InputStream> streamToBuilderMap) {
215         return resolveModuleBuildersWithContext(yangFileStreams, streamToBuilderMap, null);
216     }
217
218     private Map<String, TreeMap<Date, ModuleBuilder>> resolveModuleBuildersWithContext(
219             final List<InputStream> yangFileStreams, final Map<ModuleBuilder, InputStream> streamToBuilderMap,
220             final SchemaContext context) {
221         final ModuleBuilder[] builders = parseModuleBuilders(yangFileStreams, streamToBuilderMap);
222
223         // Linked Hash Map MUST be used because Linked Hash Map preserves ORDER
224         // of items stored in map.
225         final LinkedHashMap<String, TreeMap<Date, ModuleBuilder>> modules = new LinkedHashMap<String, TreeMap<Date, ModuleBuilder>>();
226
227         // module dependency graph sorted
228         List<ModuleBuilder> sorted = null;
229         if (context == null) {
230             sorted = ModuleDependencySort.sort(builders);
231         } else {
232             sorted = ModuleDependencySort.sortWithContext(context, builders);
233         }
234
235         for (final ModuleBuilder builder : sorted) {
236             if (builder == null) {
237                 continue;
238             }
239             final String builderName = builder.getName();
240             Date builderRevision = builder.getRevision();
241             if (builderRevision == null) {
242                 builderRevision = new Date(0L);
243             }
244             TreeMap<Date, ModuleBuilder> builderByRevision = modules.get(builderName);
245             if (builderByRevision == null) {
246                 builderByRevision = new TreeMap<Date, ModuleBuilder>();
247             }
248             builderByRevision.put(builderRevision, builder);
249             modules.put(builderName, builderByRevision);
250         }
251         return modules;
252     }
253
254     private List<ParseTree> parseStreams(final List<InputStream> yangStreams) {
255         final List<ParseTree> trees = new ArrayList<ParseTree>();
256         for (InputStream yangStream : yangStreams) {
257             trees.add(parseStream(yangStream));
258         }
259         return trees;
260     }
261
262     private ParseTree parseStream(final InputStream yangStream) {
263         ParseTree result = null;
264         try {
265             final ANTLRInputStream input = new ANTLRInputStream(yangStream);
266             final YangLexer lexer = new YangLexer(input);
267             final CommonTokenStream tokens = new CommonTokenStream(lexer);
268             final YangParser parser = new YangParser(tokens);
269             parser.removeErrorListeners();
270             parser.addErrorListener(new YangErrorListener());
271
272             result = parser.yang();
273         } catch (IOException e) {
274             logger.warn("Exception while reading yang file: " + yangStream, e);
275         }
276         return result;
277     }
278
279     private Map<ModuleBuilder, Module> build(final Map<String, TreeMap<Date, ModuleBuilder>> modules) {
280         // fix unresolved nodes
281         for (Map.Entry<String, TreeMap<Date, ModuleBuilder>> entry : modules.entrySet()) {
282             for (Map.Entry<Date, ModuleBuilder> childEntry : entry.getValue().entrySet()) {
283                 final ModuleBuilder moduleBuilder = childEntry.getValue();
284                 fixUnresolvedNodes(modules, moduleBuilder);
285             }
286         }
287         resolveAugments(modules);
288
289         // build
290         // LinkedHashMap MUST be used otherwise the values will not maintain
291         // order!
292         // http://docs.oracle.com/javase/6/docs/api/java/util/LinkedHashMap.html
293         final Map<ModuleBuilder, Module> result = new LinkedHashMap<ModuleBuilder, Module>();
294         for (Map.Entry<String, TreeMap<Date, ModuleBuilder>> entry : modules.entrySet()) {
295             final Map<Date, Module> modulesByRevision = new HashMap<Date, Module>();
296             for (Map.Entry<Date, ModuleBuilder> childEntry : entry.getValue().entrySet()) {
297                 final ModuleBuilder moduleBuilder = childEntry.getValue();
298                 final Module module = moduleBuilder.build();
299                 modulesByRevision.put(childEntry.getKey(), module);
300                 result.put(moduleBuilder, module);
301             }
302         }
303         return result;
304     }
305
306     private Map<ModuleBuilder, Module> buildWithContext(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
307             SchemaContext context) {
308         // fix unresolved nodes
309         for (Map.Entry<String, TreeMap<Date, ModuleBuilder>> entry : modules.entrySet()) {
310             for (Map.Entry<Date, ModuleBuilder> childEntry : entry.getValue().entrySet()) {
311                 final ModuleBuilder moduleBuilder = childEntry.getValue();
312                 fixUnresolvedNodesWithContext(modules, moduleBuilder, context);
313             }
314         }
315         resolveAugmentsWithContext(modules, context);
316
317         // build
318         // LinkedHashMap MUST be used otherwise the values will not maintain
319         // order!
320         // http://docs.oracle.com/javase/6/docs/api/java/util/LinkedHashMap.html
321         final Map<ModuleBuilder, Module> result = new LinkedHashMap<ModuleBuilder, Module>();
322         for (Map.Entry<String, TreeMap<Date, ModuleBuilder>> entry : modules.entrySet()) {
323             final Map<Date, Module> modulesByRevision = new HashMap<Date, Module>();
324             for (Map.Entry<Date, ModuleBuilder> childEntry : entry.getValue().entrySet()) {
325                 final ModuleBuilder moduleBuilder = childEntry.getValue();
326                 final Module module = moduleBuilder.build();
327                 modulesByRevision.put(childEntry.getKey(), module);
328                 result.put(moduleBuilder, module);
329             }
330         }
331         return result;
332     }
333
334     private void fixUnresolvedNodes(final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder builder) {
335         resolveDirtyNodes(modules, builder);
336         resolveIdentities(modules, builder);
337         resolveUsesRefine(modules, builder);
338         resolveUnknownNodes(modules, builder);
339     }
340
341     private void fixUnresolvedNodesWithContext(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
342             final ModuleBuilder builder, final SchemaContext context) {
343         resolveDirtyNodesWithContext(modules, builder, context);
344         resolveIdentitiesWithContext(modules, builder, context);
345         resolveUsesRefineWithContext(modules, builder, context);
346         resolveUnknownNodesWithContext(modules, builder, context);
347     }
348
349     /**
350      * Search for dirty nodes (node which contains UnknownType) and resolve
351      * unknown types.
352      *
353      * @param modules
354      *            all available modules
355      * @param module
356      *            current module
357      */
358     private void resolveDirtyNodes(final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder module) {
359         final Map<List<String>, TypeAwareBuilder> dirtyNodes = module.getDirtyNodes();
360         if (!dirtyNodes.isEmpty()) {
361             for (Map.Entry<List<String>, TypeAwareBuilder> entry : dirtyNodes.entrySet()) {
362                 final TypeAwareBuilder nodeToResolve = entry.getValue();
363
364                 if (nodeToResolve instanceof UnionTypeBuilder) {
365                     // special handling for union types
366                     resolveTypeUnion((UnionTypeBuilder) nodeToResolve, modules, module);
367                 } else if (nodeToResolve.getTypedef() instanceof IdentityrefTypeBuilder) {
368                     // special handling for identityref types
369                     IdentityrefTypeBuilder idref = (IdentityrefTypeBuilder) nodeToResolve.getTypedef();
370                     nodeToResolve.setType(new IdentityrefType(findFullQName(modules, module, idref), idref.getPath()));
371                 } else {
372                     resolveType(nodeToResolve, modules, module);
373                 }
374             }
375         }
376     }
377
378     private void resolveDirtyNodesWithContext(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
379             final ModuleBuilder module, SchemaContext context) {
380         final Map<List<String>, TypeAwareBuilder> dirtyNodes = module.getDirtyNodes();
381         if (!dirtyNodes.isEmpty()) {
382             for (Map.Entry<List<String>, TypeAwareBuilder> entry : dirtyNodes.entrySet()) {
383                 final TypeAwareBuilder nodeToResolve = entry.getValue();
384
385                 if (nodeToResolve instanceof UnionTypeBuilder) {
386                     // special handling for union types
387                     resolveTypeUnionWithContext((UnionTypeBuilder) nodeToResolve, modules, module, context);
388                 } else if (nodeToResolve.getTypedef() instanceof IdentityrefTypeBuilder) {
389                     // special handling for identityref types
390                     IdentityrefTypeBuilder idref = (IdentityrefTypeBuilder) nodeToResolve.getTypedef();
391                     nodeToResolve.setType(new IdentityrefType(findFullQName(modules, module, idref), idref.getPath()));
392                 } else {
393                     resolveTypeWithContext(nodeToResolve, modules, module, context);
394                 }
395             }
396         }
397     }
398
399     /**
400      * Resolve unknown type of node. It is assumed that type of node is either
401      * UnknownType or ExtendedType with UnknownType as base type.
402      *
403      * @param nodeToResolve
404      *            node with type to resolve
405      * @param modules
406      *            all loaded modules
407      * @param module
408      *            current module
409      */
410     private void resolveType(final TypeAwareBuilder nodeToResolve,
411             final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder module) {
412         TypeDefinitionBuilder resolvedType = null;
413         final int line = nodeToResolve.getLine();
414         final TypeDefinition<?> nodeToResolveType = nodeToResolve.getType();
415         final QName unknownTypeQName = nodeToResolveType.getBaseType().getQName();
416         final ModuleBuilder dependentModule = findDependentModuleBuilder(modules, module, unknownTypeQName.getPrefix(),
417                 line);
418
419         final TypeDefinitionBuilder targetTypeBuilder = findTypeDefinitionBuilder(nodeToResolve.getPath(),
420                 dependentModule, unknownTypeQName.getLocalName(), module.getName(), line);
421
422         if (nodeToResolveType instanceof ExtendedType) {
423             final ExtendedType extType = (ExtendedType) nodeToResolveType;
424             final TypeDefinitionBuilder newType = extendedTypeWithNewBaseTypeBuilder(targetTypeBuilder, extType,
425                     modules, module, nodeToResolve.getLine());
426             resolvedType = newType;
427         } else {
428             resolvedType = targetTypeBuilder;
429         }
430
431         // validate constraints
432         final TypeConstraints constraints = findConstraintsFromTypeBuilder(nodeToResolve,
433                 new TypeConstraints(module.getName(), nodeToResolve.getLine()), modules, module, null);
434         constraints.validateConstraints();
435
436         nodeToResolve.setTypedef(resolvedType);
437     }
438
439     /**
440      * Resolve unknown type of node. It is assumed that type of node is either
441      * UnknownType or ExtendedType with UnknownType as base type.
442      *
443      * @param nodeToResolve
444      *            node with type to resolve
445      * @param modules
446      *            all loaded modules
447      * @param module
448      *            current module
449      * @param context
450      *            SchemaContext containing already resolved modules
451      */
452     private void resolveTypeWithContext(final TypeAwareBuilder nodeToResolve,
453             final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder module,
454             final SchemaContext context) {
455         TypeDefinitionBuilder resolvedType = null;
456         final int line = nodeToResolve.getLine();
457         final TypeDefinition<?> nodeToResolveType = nodeToResolve.getType();
458         final QName unknownTypeQName = nodeToResolveType.getBaseType().getQName();
459         final ModuleBuilder dependentModuleBuilder = findDependentModuleBuilder(modules, module,
460                 unknownTypeQName.getPrefix(), line);
461
462         if (dependentModuleBuilder == null) {
463             final Module dependentModule = findModuleFromContext(context, module, unknownTypeQName.getPrefix(), line);
464             final Set<TypeDefinition<?>> types = dependentModule.getTypeDefinitions();
465             final TypeDefinition<?> type = findTypeByName(types, unknownTypeQName.getLocalName());
466
467             if (nodeToResolveType instanceof ExtendedType) {
468                 final ExtendedType extType = (ExtendedType) nodeToResolveType;
469                 final TypeDefinitionBuilder newType = extendedTypeWithNewBaseType(type, extType, module,
470                         nodeToResolve.getLine());
471
472                 nodeToResolve.setTypedef(newType);
473             } else {
474                 if(nodeToResolve instanceof TypeDefinitionBuilder) {
475                     TypeDefinitionBuilder tdb = (TypeDefinitionBuilder)nodeToResolve;
476                     TypeConstraints tc = findConstraintsFromTypeBuilder(nodeToResolve, new TypeConstraints(module.getName(), nodeToResolve.getLine()), modules, module, context);
477                     tdb.setLengths(tc.getLength());
478                     tdb.setPatterns(tc.getPatterns());
479                     tdb.setRanges(tc.getRange());
480                     tdb.setFractionDigits(tc.getFractionDigits());
481                 }
482                 nodeToResolve.setType(type);
483             }
484
485         } else {
486             final TypeDefinitionBuilder targetTypeBuilder = findTypeDefinitionBuilder(nodeToResolve.getPath(),
487                     dependentModuleBuilder, unknownTypeQName.getLocalName(), module.getName(), line);
488
489             if (nodeToResolveType instanceof ExtendedType) {
490                 final ExtendedType extType = (ExtendedType) nodeToResolveType;
491                 final TypeDefinitionBuilder newType = extendedTypeWithNewBaseTypeBuilder(targetTypeBuilder, extType,
492                         modules, module, nodeToResolve.getLine());
493                 resolvedType = newType;
494             } else {
495                 resolvedType = targetTypeBuilder;
496             }
497
498             // validate constraints
499             final TypeConstraints constraints = findConstraintsFromTypeBuilder(nodeToResolve, new TypeConstraints(
500                     module.getName(), nodeToResolve.getLine()), modules, module, context);
501             constraints.validateConstraints();
502
503             nodeToResolve.setTypedef(resolvedType);
504         }
505     }
506
507     private void resolveTypeUnion(final UnionTypeBuilder union,
508             final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder builder) {
509
510         final List<TypeDefinition<?>> unionTypes = union.getTypes();
511         final List<TypeDefinition<?>> toRemove = new ArrayList<TypeDefinition<?>>();
512         for (TypeDefinition<?> unionType : unionTypes) {
513             if (unionType instanceof UnknownType) {
514                 final UnknownType ut = (UnknownType) unionType;
515                 final ModuleBuilder dependentModule = findDependentModuleBuilder(modules, builder, ut.getQName()
516                         .getPrefix(), union.getLine());
517                 final TypeDefinitionBuilder resolvedType = findTypeDefinitionBuilder(union.getPath(), dependentModule,
518                         ut.getQName().getLocalName(), builder.getName(), union.getLine());
519                 union.setTypedef(resolvedType);
520                 toRemove.add(ut);
521             } else if (unionType instanceof ExtendedType) {
522                 final ExtendedType extType = (ExtendedType) unionType;
523                 final TypeDefinition<?> extTypeBase = extType.getBaseType();
524                 if (extTypeBase instanceof UnknownType) {
525                     final UnknownType ut = (UnknownType) extTypeBase;
526                     final ModuleBuilder dependentModule = findDependentModuleBuilder(modules, builder, ut.getQName()
527                             .getPrefix(), union.getLine());
528                     final TypeDefinitionBuilder targetTypeBuilder = findTypeDefinitionBuilder(union.getPath(),
529                             dependentModule, ut.getQName().getLocalName(), builder.getName(), union.getLine());
530
531                     final TypeDefinitionBuilder newType = extendedTypeWithNewBaseTypeBuilder(targetTypeBuilder,
532                             extType, modules, builder, union.getLine());
533
534                     union.setTypedef(newType);
535                     toRemove.add(extType);
536                 }
537             }
538         }
539         unionTypes.removeAll(toRemove);
540     }
541
542     private void resolveTypeUnionWithContext(final UnionTypeBuilder union,
543             final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder builder,
544             final SchemaContext context) {
545
546         final List<TypeDefinition<?>> unionTypes = union.getTypes();
547         final List<TypeDefinition<?>> toRemove = new ArrayList<TypeDefinition<?>>();
548         for (TypeDefinition<?> unionType : unionTypes) {
549             if (unionType instanceof UnknownType) {
550                 final UnknownType ut = (UnknownType) unionType;
551                 final QName utQName = ut.getQName();
552                 final ModuleBuilder dependentModuleBuilder = findDependentModuleBuilder(modules, builder,
553                         utQName.getPrefix(), union.getLine());
554
555                 if (dependentModuleBuilder == null) {
556                     Module dependentModule = findModuleFromContext(context, builder, utQName.getPrefix(),
557                             union.getLine());
558                     Set<TypeDefinition<?>> types = dependentModule.getTypeDefinitions();
559                     TypeDefinition<?> type = findTypeByName(types, utQName.getLocalName());
560                     union.setType(type);
561                     toRemove.add(ut);
562                 } else {
563                     final TypeDefinitionBuilder resolvedType = findTypeDefinitionBuilder(union.getPath(),
564                             dependentModuleBuilder, utQName.getLocalName(), builder.getName(), union.getLine());
565                     union.setTypedef(resolvedType);
566                     toRemove.add(ut);
567                 }
568
569             } else if (unionType instanceof ExtendedType) {
570                 final ExtendedType extType = (ExtendedType) unionType;
571                 TypeDefinition<?> extTypeBase = extType.getBaseType();
572                 if (extTypeBase instanceof UnknownType) {
573                     final UnknownType ut = (UnknownType) extTypeBase;
574                     final QName utQName = ut.getQName();
575                     final ModuleBuilder dependentModuleBuilder = findDependentModuleBuilder(modules, builder,
576                             utQName.getPrefix(), union.getLine());
577
578                     if (dependentModuleBuilder == null) {
579                         final Module dependentModule = findModuleFromContext(context, builder, utQName.getPrefix(),
580                                 union.getLine());
581                         Set<TypeDefinition<?>> types = dependentModule.getTypeDefinitions();
582                         TypeDefinition<?> type = findTypeByName(types, utQName.getLocalName());
583                         final TypeDefinitionBuilder newType = extendedTypeWithNewBaseType(type, extType, builder, 0);
584
585                         union.setTypedef(newType);
586                         toRemove.add(extType);
587                     } else {
588                         final TypeDefinitionBuilder targetTypeBuilder = findTypeDefinitionBuilder(union.getPath(),
589                                 dependentModuleBuilder, utQName.getLocalName(), builder.getName(), union.getLine());
590
591                         final TypeDefinitionBuilder newType = extendedTypeWithNewBaseTypeBuilder(targetTypeBuilder,
592                                 extType, modules, builder, union.getLine());
593
594                         union.setTypedef(newType);
595                         toRemove.add(extType);
596                     }
597                 }
598             }
599         }
600         unionTypes.removeAll(toRemove);
601     }
602
603     /**
604      * Go through all augment definitions and resolve them. It is expected that
605      * modules are already sorted by their dependencies. This method also finds
606      * augment target node and add child nodes to it.
607      *
608      * @param modules
609      *            all available modules
610      */
611     private void resolveAugments(final Map<String, TreeMap<Date, ModuleBuilder>> modules) {
612         final List<ModuleBuilder> allModulesList = new ArrayList<ModuleBuilder>();
613         final Set<ModuleBuilder> allModulesSet = new HashSet<ModuleBuilder>();
614         for (Map.Entry<String, TreeMap<Date, ModuleBuilder>> entry : modules.entrySet()) {
615             for (Map.Entry<Date, ModuleBuilder> inner : entry.getValue().entrySet()) {
616                 allModulesList.add(inner.getValue());
617                 allModulesSet.add(inner.getValue());
618             }
619         }
620
621         for (int i = 0; i < allModulesList.size(); i++) {
622             final ModuleBuilder module = allModulesList.get(i);
623             // try to resolve augments in module
624             resolveAugment(modules, module);
625             // while all augments are not resolved
626             final Iterator<ModuleBuilder> allModulesIterator = allModulesSet.iterator();
627             while (!(module.getAugmentsResolved() == module.getAugments().size())) {
628                 ModuleBuilder nextModule = null;
629                 // try resolve other module augments
630                 try {
631                     nextModule = allModulesIterator.next();
632                     resolveAugment(modules, nextModule);
633                 } catch (NoSuchElementException e) {
634                     throw new YangParseException("Failed to resolve augments in module '" + module.getName() + "'.", e);
635                 }
636                 // then try to resolve first module again
637                 resolveAugment(modules, module);
638             }
639         }
640     }
641
642     /**
643      * Tries to resolve augments in given module. If augment target node is not
644      * found, do nothing.
645      *
646      * @param modules
647      *            all available modules
648      * @param module
649      *            current module
650      */
651     private void resolveAugment(final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder module) {
652         if (module.getAugmentsResolved() < module.getAugments().size()) {
653             for (AugmentationSchemaBuilder augmentBuilder : module.getAugments()) {
654
655                 if (!augmentBuilder.isResolved()) {
656                     final SchemaPath augmentTargetSchemaPath = augmentBuilder.getTargetPath();
657                     final List<QName> path = augmentTargetSchemaPath.getPath();
658
659                     final QName qname = path.get(0);
660                     String prefix = qname.getPrefix();
661                     if (prefix == null) {
662                         prefix = module.getPrefix();
663                     }
664
665                     final ModuleBuilder dependentModule = findDependentModuleBuilder(modules, module, prefix,
666                             augmentBuilder.getLine());
667                     processAugmentation(augmentBuilder, path, module, qname, dependentModule);
668                 }
669
670             }
671         }
672     }
673
674     /**
675      * Go through all augment definitions and resolve them. This method works in
676      * same way as {@link #resolveAugments(Map)} except that if target node is not
677      * found in loaded modules, it search for target node in given context.
678      *
679      * @param modules
680      *            all loaded modules
681      * @param context
682      *            SchemaContext containing already resolved modules
683      */
684     private void resolveAugmentsWithContext(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
685             final SchemaContext context) {
686         final List<ModuleBuilder> allModulesList = new ArrayList<ModuleBuilder>();
687         final Set<ModuleBuilder> allModulesSet = new HashSet<ModuleBuilder>();
688         for (Map.Entry<String, TreeMap<Date, ModuleBuilder>> entry : modules.entrySet()) {
689             for (Map.Entry<Date, ModuleBuilder> inner : entry.getValue().entrySet()) {
690                 allModulesList.add(inner.getValue());
691                 allModulesSet.add(inner.getValue());
692             }
693         }
694
695         for (int i = 0; i < allModulesList.size(); i++) {
696             final ModuleBuilder module = allModulesList.get(i);
697             // try to resolve augments in module
698             resolveAugmentWithContext(modules, module, context);
699             // while all augments are not resolved
700             final Iterator<ModuleBuilder> allModulesIterator = allModulesSet.iterator();
701             while (!(module.getAugmentsResolved() == module.getAugments().size())) {
702                 ModuleBuilder nextModule = null;
703                 // try resolve other module augments
704                 try {
705                     nextModule = allModulesIterator.next();
706                     resolveAugmentWithContext(modules, nextModule, context);
707                 } catch (NoSuchElementException e) {
708                     throw new YangParseException("Failed to resolve augments in module '" + module.getName() + "'.", e);
709                 }
710                 // then try to resolve first module again
711                 resolveAugmentWithContext(modules, module, context);
712             }
713         }
714     }
715
716     /**
717      * Tries to resolve augments in given module. If augment target node is not
718      * found, do nothing.
719      *
720      * @param modules
721      *            all available modules
722      * @param module
723      *            current module
724      */
725     private void resolveAugmentWithContext(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
726             final ModuleBuilder module, final SchemaContext context) {
727         if (module.getAugmentsResolved() < module.getAugments().size()) {
728
729             for (AugmentationSchemaBuilder augmentBuilder : module.getAugments()) {
730                 final int line = augmentBuilder.getLine();
731
732                 if (!augmentBuilder.isResolved()) {
733                     final List<QName> path = augmentBuilder.getTargetPath().getPath();
734                     final QName qname = path.get(0);
735                     String prefix = qname.getPrefix();
736                     if (prefix == null) {
737                         prefix = module.getPrefix();
738                     }
739
740                     // try to find augment target module in loaded modules...
741                     final ModuleBuilder dependentModuleBuilder = findDependentModuleBuilder(modules, module, prefix,
742                             line);
743                     if (dependentModuleBuilder == null) {
744                         // perform augmentation on module from context and
745                         // continue to next augment
746                         processAugmentationOnContext(augmentBuilder, path, module, prefix, line, context);
747                         continue;
748                     } else {
749                         processAugmentation(augmentBuilder, path, module, qname, dependentModuleBuilder);
750                     }
751                 }
752
753             }
754         }
755     }
756
757     /**
758      * Go through identity statements defined in current module and resolve
759      * their 'base' statement if present.
760      *
761      * @param modules
762      *            all modules
763      * @param module
764      *            module being resolved
765      */
766     private void resolveIdentities(final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder module) {
767         final Set<IdentitySchemaNodeBuilder> identities = module.getIdentities();
768         for (IdentitySchemaNodeBuilder identity : identities) {
769             final String baseIdentityName = identity.getBaseIdentityName();
770             if (baseIdentityName != null) {
771                 String baseIdentityPrefix = null;
772                 String baseIdentityLocalName = null;
773                 if (baseIdentityName.contains(":")) {
774                     final String[] splitted = baseIdentityName.split(":");
775                     baseIdentityPrefix = splitted[0];
776                     baseIdentityLocalName = splitted[1];
777                 } else {
778                     baseIdentityPrefix = module.getPrefix();
779                     baseIdentityLocalName = baseIdentityName;
780                 }
781                 final ModuleBuilder dependentModule = findDependentModuleBuilder(modules, module, baseIdentityPrefix,
782                         identity.getLine());
783
784                 final Set<IdentitySchemaNodeBuilder> dependentModuleIdentities = dependentModule.getIdentities();
785                 for (IdentitySchemaNodeBuilder idBuilder : dependentModuleIdentities) {
786                     if (idBuilder.getQName().getLocalName().equals(baseIdentityLocalName)) {
787                         identity.setBaseIdentity(idBuilder);
788                     }
789                 }
790             }
791         }
792     }
793
794     /**
795      * Go through identity statements defined in current module and resolve
796      * their 'base' statement. Method tries to find base identity in given
797      * modules. If base identity is not found, method will search it in context.
798      *
799      * @param modules
800      *            all loaded modules
801      * @param module
802      *            current module
803      * @param context
804      *            SchemaContext containing already resolved modules
805      */
806     private void resolveIdentitiesWithContext(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
807             final ModuleBuilder module, SchemaContext context) {
808         final Set<IdentitySchemaNodeBuilder> identities = module.getIdentities();
809         for (IdentitySchemaNodeBuilder identity : identities) {
810             final String baseIdentityName = identity.getBaseIdentityName();
811             if (baseIdentityName != null) {
812                 String baseIdentityPrefix = null;
813                 String baseIdentityLocalName = null;
814                 if (baseIdentityName.contains(":")) {
815                     final String[] splitted = baseIdentityName.split(":");
816                     baseIdentityPrefix = splitted[0];
817                     baseIdentityLocalName = splitted[1];
818                 } else {
819                     baseIdentityPrefix = module.getPrefix();
820                     baseIdentityLocalName = baseIdentityName;
821                 }
822                 final ModuleBuilder dependentModuleBuilder = findDependentModuleBuilder(modules, module,
823                         baseIdentityPrefix, identity.getLine());
824
825                 if (dependentModuleBuilder == null) {
826                     final Module dependentModule = findModuleFromContext(context, module, baseIdentityPrefix,
827                             identity.getLine());
828                     final Set<IdentitySchemaNode> dependentModuleIdentities = dependentModule.getIdentities();
829                     for (IdentitySchemaNode idNode : dependentModuleIdentities) {
830                         if (idNode.getQName().getLocalName().equals(baseIdentityLocalName)) {
831                             identity.setBaseIdentity(idNode);
832                         }
833                     }
834                 } else {
835                     final Set<IdentitySchemaNodeBuilder> dependentModuleIdentities = dependentModuleBuilder
836                             .getIdentities();
837                     for (IdentitySchemaNodeBuilder idBuilder : dependentModuleIdentities) {
838                         if (idBuilder.getQName().getLocalName().equals(baseIdentityLocalName)) {
839                             identity.setBaseIdentity(idBuilder);
840                         }
841                     }
842                 }
843             }
844         }
845     }
846
847     /**
848      * Go through uses statements defined in current module and resolve their
849      * refine statements.
850      *
851      * @param modules
852      *            all modules
853      * @param module
854      *            module being resolved
855      */
856     private void resolveUsesRefine(final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder module) {
857         final Map<List<String>, UsesNodeBuilder> moduleUses = module.getUsesNodes();
858         for (Map.Entry<List<String>, UsesNodeBuilder> entry : moduleUses.entrySet()) {
859             final UsesNodeBuilder usesNode = entry.getValue();
860             final int line = usesNode.getLine();
861             final GroupingBuilder targetGrouping = getTargetGroupingFromModules(usesNode, modules, module);
862             usesNode.setGroupingPath(targetGrouping.getPath());
863             for (RefineHolder refine : usesNode.getRefines()) {
864                 final SchemaNodeBuilder nodeToRefine = RefineUtils.getRefineNodeFromGroupingBuilder(targetGrouping,
865                         refine, module.getName());
866                 RefineUtils.performRefine(nodeToRefine, refine, line);
867                 usesNode.addRefineNode(nodeToRefine);
868             }
869         }
870     }
871
872     /**
873      * Tries to search target grouping in given modules and resolve refine
874      * nodes. If grouping is not found in modules, method tries to find it in
875      * modules from context.
876      *
877      * @param modules
878      *            all loaded modules
879      * @param module
880      *            current module
881      * @param context
882      *            SchemaContext containing already resolved modules
883      */
884     private void resolveUsesRefineWithContext(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
885             final ModuleBuilder module, SchemaContext context) {
886         final Map<List<String>, UsesNodeBuilder> moduleUses = module.getUsesNodes();
887         for (Map.Entry<List<String>, UsesNodeBuilder> entry : moduleUses.entrySet()) {
888             final UsesNodeBuilder usesNode = entry.getValue();
889             final int line = usesNode.getLine();
890
891             final GroupingBuilder targetGroupingBuilder = getTargetGroupingFromModules(usesNode, modules, module);
892             if (targetGroupingBuilder == null) {
893                 final GroupingDefinition targetGrouping = getTargetGroupingFromContext(usesNode, module, context);
894                 usesNode.setGroupingPath(targetGrouping.getPath());
895                 for (RefineHolder refine : usesNode.getRefines()) {
896                     final SchemaNodeBuilder nodeToRefine = RefineUtils.getRefineNodeFromGroupingDefinition(
897                             targetGrouping, refine, module.getName());
898                     RefineUtils.performRefine(nodeToRefine, refine, line);
899                     usesNode.addRefineNode(nodeToRefine);
900                 }
901             } else {
902                 usesNode.setGroupingPath(targetGroupingBuilder.getPath());
903                 for (RefineHolder refine : usesNode.getRefines()) {
904                     final SchemaNodeBuilder nodeToRefine = RefineUtils.getRefineNodeFromGroupingBuilder(
905                             targetGroupingBuilder, refine, module.getName());
906                     RefineUtils.performRefine(nodeToRefine, refine, line);
907                     usesNode.addRefineNode(nodeToRefine);
908                 }
909             }
910         }
911     }
912
913     /**
914      * Search given modules for grouping by name defined in uses node.
915      *
916      * @param usesBuilder
917      *            builder of uses statement
918      * @param modules
919      *            all loaded modules
920      * @param module
921      *            current module
922      * @return grouping with given name if found, null otherwise
923      */
924     private GroupingBuilder getTargetGroupingFromModules(final UsesNodeBuilder usesBuilder,
925             final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder module) {
926         final int line = usesBuilder.getLine();
927         final String groupingString = usesBuilder.getGroupingName();
928         String groupingPrefix;
929         String groupingName;
930
931         if (groupingString.contains(":")) {
932             String[] splitted = groupingString.split(":");
933             if (splitted.length != 2 || groupingString.contains("/")) {
934                 throw new YangParseException(module.getName(), line, "Invalid name of target grouping");
935             }
936             groupingPrefix = splitted[0];
937             groupingName = splitted[1];
938         } else {
939             groupingPrefix = module.getPrefix();
940             groupingName = groupingString;
941         }
942
943         ModuleBuilder dependentModule = null;
944         if (groupingPrefix.equals(module.getPrefix())) {
945             dependentModule = module;
946         } else {
947             dependentModule = findDependentModuleBuilder(modules, module, groupingPrefix, line);
948         }
949
950         if (dependentModule == null) {
951             return null;
952         }
953
954         List<QName> path = usesBuilder.getPath().getPath();
955         GroupingBuilder result = null;
956         Set<GroupingBuilder> groupings = dependentModule.getModuleGroupings();
957         result = findGroupingBuilder(groupings, groupingName);
958
959         if (result == null) {
960             Builder currentNode = null;
961             final List<String> currentPath = new ArrayList<String>();
962             currentPath.add(dependentModule.getName());
963
964             for (int i = 0; i < path.size(); i++) {
965                 QName qname = path.get(i);
966                 currentPath.add(qname.getLocalName());
967                 currentNode = dependentModule.getModuleNode(currentPath);
968
969                 if (currentNode instanceof RpcDefinitionBuilder) {
970                     groupings = ((RpcDefinitionBuilder) currentNode).getGroupings();
971                 } else if (currentNode instanceof DataNodeContainerBuilder) {
972                     groupings = ((DataNodeContainerBuilder) currentNode).getGroupings();
973                 } else {
974                     groupings = Collections.emptySet();
975                 }
976
977                 result = findGroupingBuilder(groupings, groupingName);
978                 if (result != null) {
979                     break;
980                 }
981             }
982         }
983
984         return result;
985     }
986
987     /**
988      * Search context for grouping by name defined in uses node.
989      *
990      * @param usesBuilder
991      *            builder of uses statement
992      * @param module
993      *            current module
994      * @param context
995      *            SchemaContext containing already resolved modules
996      * @return grouping with given name if found, null otherwise
997      */
998     private GroupingDefinition getTargetGroupingFromContext(final UsesNodeBuilder usesBuilder,
999             final ModuleBuilder module, SchemaContext context) {
1000         final int line = usesBuilder.getLine();
1001         String groupingString = usesBuilder.getGroupingName();
1002         String groupingPrefix;
1003         String groupingName;
1004
1005         if (groupingString.contains(":")) {
1006             String[] splitted = groupingString.split(":");
1007             if (splitted.length != 2 || groupingString.contains("/")) {
1008                 throw new YangParseException(module.getName(), line, "Invalid name of target grouping");
1009             }
1010             groupingPrefix = splitted[0];
1011             groupingName = splitted[1];
1012         } else {
1013             groupingPrefix = module.getPrefix();
1014             groupingName = groupingString;
1015         }
1016
1017         Module dependentModule = findModuleFromContext(context, module, groupingPrefix, line);
1018         return findGroupingDefinition(dependentModule.getGroupings(), groupingName);
1019     }
1020
1021     private QName findFullQName(final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder module,
1022             final IdentityrefTypeBuilder idref) {
1023         QName result = null;
1024         String baseString = idref.getBaseString();
1025         if (baseString.contains(":")) {
1026             String[] splittedBase = baseString.split(":");
1027             if (splittedBase.length > 2) {
1028                 throw new YangParseException(module.getName(), idref.getLine(), "Failed to parse identityref base: "
1029                         + baseString);
1030             }
1031             String prefix = splittedBase[0];
1032             String name = splittedBase[1];
1033             ModuleBuilder dependentModule = findDependentModuleBuilder(modules, module, prefix, idref.getLine());
1034             result = new QName(dependentModule.getNamespace(), dependentModule.getRevision(), prefix, name);
1035         } else {
1036             result = new QName(module.getNamespace(), module.getRevision(), module.getPrefix(), baseString);
1037         }
1038         return result;
1039     }
1040
1041     private void resolveUnknownNodes(final Map<String, TreeMap<Date, ModuleBuilder>> modules, final ModuleBuilder module) {
1042         for (UnknownSchemaNodeBuilder usnb : module.getUnknownNodes()) {
1043             QName nodeType = usnb.getNodeType();
1044             if (nodeType.getNamespace() == null || nodeType.getRevision() == null) {
1045                 try {
1046                     ModuleBuilder dependentModule = findDependentModuleBuilder(modules, module, nodeType.getPrefix(),
1047                             usnb.getLine());
1048                     QName newNodeType = new QName(dependentModule.getNamespace(), dependentModule.getRevision(),
1049                             nodeType.getPrefix(), nodeType.getLocalName());
1050                     usnb.setNodeType(newNodeType);
1051                 } catch (YangParseException e) {
1052                     logger.debug(module.getName(), usnb.getLine(), "Failed to find unknown node type: " + nodeType);
1053                 }
1054             }
1055         }
1056     }
1057
1058     private void resolveUnknownNodesWithContext(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
1059             final ModuleBuilder module, SchemaContext context) {
1060         for (UnknownSchemaNodeBuilder unknownNodeBuilder : module.getUnknownNodes()) {
1061             QName nodeType = unknownNodeBuilder.getNodeType();
1062             if (nodeType.getNamespace() == null || nodeType.getRevision() == null) {
1063                 try {
1064                     ModuleBuilder dependentModuleBuilder = findDependentModuleBuilder(modules, module,
1065                             nodeType.getPrefix(), unknownNodeBuilder.getLine());
1066
1067                     QName newNodeType = null;
1068                     if (dependentModuleBuilder == null) {
1069                         Module dependentModule = findModuleFromContext(context, module, nodeType.getPrefix(),
1070                                 unknownNodeBuilder.getLine());
1071                         newNodeType = new QName(dependentModule.getNamespace(), dependentModule.getRevision(),
1072                                 nodeType.getPrefix(), nodeType.getLocalName());
1073                     } else {
1074                         newNodeType = new QName(dependentModuleBuilder.getNamespace(),
1075                                 dependentModuleBuilder.getRevision(), nodeType.getPrefix(), nodeType.getLocalName());
1076                     }
1077
1078                     unknownNodeBuilder.setNodeType(newNodeType);
1079                 } catch (YangParseException e) {
1080                     logger.debug(module.getName(), unknownNodeBuilder.getLine(), "Failed to find unknown node type: "
1081                             + nodeType);
1082                 }
1083             }
1084         }
1085     }
1086
1087 }