Merge "Allow getChildNodes() to become a Collection"
[controller.git] / opendaylight / config / yang-jmx-generator / src / main / java / org / opendaylight / controller / config / yangjmxgenerator / ModuleMXBeanEntryBuilder.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.config.yangjmxgenerator;
9
10 import static com.google.common.base.Preconditions.checkNotNull;
11 import static com.google.common.base.Preconditions.checkState;
12 import static java.lang.String.format;
13 import static org.opendaylight.controller.config.yangjmxgenerator.ConfigConstants.createConfigQName;
14
15 import com.google.common.annotations.VisibleForTesting;
16 import com.google.common.base.Function;
17 import com.google.common.base.Optional;
18 import com.google.common.collect.Collections2;
19 import com.google.common.collect.Maps;
20 import com.google.common.collect.Sets;
21
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.Objects;
27 import java.util.Set;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30
31 import javax.annotation.Nullable;
32
33 import org.opendaylight.controller.config.yangjmxgenerator.attribute.AbstractDependencyAttribute;
34 import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
35 import org.opendaylight.controller.config.yangjmxgenerator.attribute.DependencyAttribute;
36 import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
37 import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute;
38 import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListDependenciesAttribute;
39 import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute;
40 import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.NameConflictException;
41 import org.opendaylight.yangtools.yang.common.QName;
42 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
43 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
44 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
46 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.Module;
52 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
53 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
54 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
55 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
56 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
57 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
58 import org.opendaylight.yangtools.yang.model.api.UsesNode;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 final class ModuleMXBeanEntryBuilder {
63
64     private Module currentModule;
65     private Map<QName, ServiceInterfaceEntry> qNamesToSIEs;
66     private SchemaContext schemaContext;
67     private TypeProviderWrapper typeProviderWrapper;
68     private String packageName;
69
70     public ModuleMXBeanEntryBuilder setModule(final Module module) {
71         this.currentModule = module;
72         return this;
73     }
74
75     public ModuleMXBeanEntryBuilder setqNamesToSIEs(final Map<QName, ServiceInterfaceEntry> qNamesToSIEs) {
76         this.qNamesToSIEs = qNamesToSIEs;
77         return this;
78     }
79
80     public ModuleMXBeanEntryBuilder setSchemaContext(final SchemaContext schemaContext) {
81         this.schemaContext = schemaContext;
82         return this;
83     }
84
85     public ModuleMXBeanEntryBuilder setTypeProviderWrapper(final TypeProviderWrapper typeProviderWrapper) {
86         this.typeProviderWrapper = typeProviderWrapper;
87         return this;
88     }
89
90     public ModuleMXBeanEntryBuilder setPackageName(final String packageName) {
91         this.packageName = packageName;
92         return this;
93     }
94
95     private static final Logger logger = LoggerFactory
96             .getLogger(ModuleMXBeanEntryBuilder.class);
97
98     // TODO: the XPath should be parsed by code generator IMO
99     private static final String MAGIC_STRING = "MAGIC_STRING";
100     private static final String MODULE_CONDITION_XPATH_TEMPLATE = "^/MAGIC_STRING:modules/MAGIC_STRING:module/MAGIC_STRING:type\\s*=\\s*['\"](.+)['\"]$";
101     private static final SchemaPath expectedConfigurationAugmentationSchemaPath = SchemaPath.create(true,
102             createConfigQName("modules"), createConfigQName("module"), createConfigQName("configuration"));
103     private static final SchemaPath expectedStateAugmentationSchemaPath = SchemaPath.create(true,
104             createConfigQName("modules"), createConfigQName("module"), createConfigQName("state"));
105     private static final Pattern PREFIX_COLON_LOCAL_NAME = Pattern
106             .compile("^(.+):(.+)$");
107
108
109     public Map<String, ModuleMXBeanEntry> build() {
110         logger.debug("Generating ModuleMXBeans of {} to package {}",
111                 currentModule.getNamespace(), packageName);
112
113         String configModulePrefix;
114         try {
115             configModulePrefix = getConfigModulePrefixFromImport(currentModule);
116         } catch (IllegalArgumentException e) {
117             // this currentModule does not import config currentModule
118             return Collections.emptyMap();
119         }
120
121         // get identities of base config:currentModule-type
122         Map<String, IdentitySchemaNode> moduleIdentities =  getIdentityMap();
123
124         Map<String, QName> uniqueGeneratedClassesNames = new HashMap<>();
125
126         // each currentModule name should have an augmentation defined
127         Map<String, IdentitySchemaNode> unaugmentedModuleIdentities = new HashMap<>(
128                 moduleIdentities);
129
130         Map<String, ModuleMXBeanEntry> result = new HashMap<>();
131
132         for (AugmentationSchema augmentation : currentModule.getAugmentations()) {
133             Collection<DataSchemaNode> childNodes = augmentation.getChildNodes();
134             if (areAllChildrenChoiceCaseNodes(childNodes)) {
135                 for (ChoiceCaseNode childCase : castChildNodesToChoiceCases(childNodes)) {
136                     // TODO refactor, extract to standalone builder class
137                     processChoiceCaseNode(result, uniqueGeneratedClassesNames, configModulePrefix, moduleIdentities,
138                             unaugmentedModuleIdentities, augmentation, childCase);
139                 }
140             } // skip if child nodes are not all cases
141         }
142         // clean up nulls
143         cleanUpNulls(result);
144         // check attributes name uniqueness
145         checkAttributeNamesUniqueness(uniqueGeneratedClassesNames, result);
146         checkUnaugumentedIdentities(unaugmentedModuleIdentities);
147
148         logger.debug("Number of ModuleMXBeans to be generated: {}", result.size());
149
150         return result;
151     }
152
153     private static void cleanUpNulls(final Map<String, ModuleMXBeanEntry> result) {
154         for (Map.Entry<String, ModuleMXBeanEntry> entry : result.entrySet()) {
155             ModuleMXBeanEntry module = entry.getValue();
156             if (module.getAttributes() == null) {
157                 module.setYangToAttributes(Collections
158                         .<String, AttributeIfc> emptyMap());
159             } else if (module.getRuntimeBeans() == null) {
160                 module.setRuntimeBeans(Collections
161                         .<RuntimeBeanEntry> emptyList());
162             }
163         }
164     }
165
166     private static void checkUnaugumentedIdentities(final Map<String, IdentitySchemaNode> unaugmentedModuleIdentities) {
167         if (unaugmentedModuleIdentities.size() > 0) {
168             logger.warn("Augmentation not found for all currentModule identities: {}",
169                     unaugmentedModuleIdentities.keySet());
170         }
171     }
172
173     private static void checkAttributeNamesUniqueness(final Map<String, QName> uniqueGeneratedClassesNames, final Map<String, ModuleMXBeanEntry> result) {
174         for (Map.Entry<String, ModuleMXBeanEntry> entry : result.entrySet()) {
175             checkUniqueRuntimeBeanAttributesName(entry.getValue(),
176                     uniqueGeneratedClassesNames);
177         }
178     }
179
180     private Map<String, IdentitySchemaNode> getIdentityMap() {
181         Map<String, IdentitySchemaNode> moduleIdentities = Maps.newHashMap();
182
183         for (IdentitySchemaNode id : currentModule.getIdentities()) {
184             if (id.getBaseIdentity() != null
185                     && ConfigConstants.MODULE_TYPE_Q_NAME.equals(id.getBaseIdentity().getQName())) {
186                 String identityLocalName = id.getQName().getLocalName();
187                 if (moduleIdentities.containsKey(identityLocalName)) {
188                     throw new IllegalStateException("Module name already defined in this currentModule: "
189                             + identityLocalName);
190                 } else {
191                     moduleIdentities.put(identityLocalName, id);
192                     logger.debug("Found identity {}", identityLocalName);
193                 }
194                 // validation check on unknown schema nodes
195                 boolean providedServiceWasSet = false;
196                 for (UnknownSchemaNode unknownNode : id.getUnknownSchemaNodes()) {
197                     // TODO: test this
198                     boolean unknownNodeIsProvidedServiceExtension = ConfigConstants.PROVIDED_SERVICE_EXTENSION_QNAME.equals(unknownNode.getNodeType());
199                     // true => no op: 0 or more provided identities are allowed
200
201                     if (ConfigConstants.JAVA_NAME_PREFIX_EXTENSION_QNAME.equals(unknownNode.getNodeType())) {
202                         // 0..1 allowed
203                         checkState(
204                                 providedServiceWasSet == false,
205                                 format("More than one language extension %s is not allowed here: %s",
206                                         ConfigConstants.JAVA_NAME_PREFIX_EXTENSION_QNAME, id));
207                         providedServiceWasSet = true;
208                     } else if (unknownNodeIsProvidedServiceExtension == false) {
209                         throw new IllegalStateException("Unexpected language extension " + unknownNode.getNodeType());
210                     }
211                 }
212             }
213         }
214
215         return moduleIdentities;
216     }
217
218     private Collection<ChoiceCaseNode> castChildNodesToChoiceCases(final Collection<DataSchemaNode> childNodes) {
219         return Collections2.transform(childNodes, new Function<DataSchemaNode, ChoiceCaseNode>() {
220             @Nullable
221             @Override
222             public ChoiceCaseNode apply(@Nullable final DataSchemaNode input) {
223                 return (ChoiceCaseNode) input;
224             }
225         });
226     }
227
228     private boolean areAllChildrenChoiceCaseNodes(final Iterable<DataSchemaNode> childNodes) {
229         for (DataSchemaNode childNode : childNodes) {
230             if (childNode instanceof ChoiceCaseNode == false) {
231                 return false;
232             }
233         }
234         return true;
235     }
236
237     private <HAS_CHILDREN_AND_QNAME extends DataNodeContainer & SchemaNode> void processChoiceCaseNode(final Map<String, ModuleMXBeanEntry> result,
238             final Map<String, QName> uniqueGeneratedClassesNames, final String configModulePrefix,
239             final Map<String, IdentitySchemaNode> moduleIdentities,
240             final Map<String, IdentitySchemaNode> unaugmentedModuleIdentities, final AugmentationSchema augmentation,
241             final DataSchemaNode when) {
242
243         ChoiceCaseNode choiceCaseNode = (ChoiceCaseNode) when;
244         if (choiceCaseNode.getConstraints() == null || choiceCaseNode.getConstraints().getWhenCondition() == null) {
245             return;
246         }
247         RevisionAwareXPath xPath = choiceCaseNode.getConstraints().getWhenCondition();
248         Matcher matcher = getWhenConditionMatcher(configModulePrefix, xPath);
249         if (matcher.matches() == false) {
250             return;
251         }
252         String moduleLocalNameFromXPath = matcher.group(1);
253         IdentitySchemaNode moduleIdentity = moduleIdentities.get(moduleLocalNameFromXPath);
254         unaugmentedModuleIdentities.remove(moduleLocalNameFromXPath);
255         checkState(moduleIdentity != null, "Cannot find identity " + moduleLocalNameFromXPath
256                 + " matching augmentation " + augmentation);
257         Map<String, QName> providedServices = findProvidedServices(moduleIdentity, currentModule, qNamesToSIEs,
258                 schemaContext);
259
260         if (moduleIdentity == null) {
261             throw new IllegalStateException("Cannot find identity specified by augmentation xpath constraint: "
262                     + moduleLocalNameFromXPath + " of " + augmentation);
263         }
264         String javaNamePrefix = TypeProviderWrapper.findJavaNamePrefix(moduleIdentity);
265
266         Map<String, AttributeIfc> yangToAttributes = null;
267         // runtime-data
268         Collection<RuntimeBeanEntry> runtimeBeans = null;
269
270         HAS_CHILDREN_AND_QNAME dataNodeContainer = getDataNodeContainer(choiceCaseNode);
271
272         if (expectedConfigurationAugmentationSchemaPath.equals(augmentation.getTargetPath())) {
273             logger.debug("Parsing configuration of {}", moduleLocalNameFromXPath);
274             yangToAttributes = fillConfiguration(dataNodeContainer, currentModule, typeProviderWrapper, qNamesToSIEs,
275                     schemaContext, packageName);
276             checkUniqueAttributesWithGeneratedClass(uniqueGeneratedClassesNames, when.getQName(), yangToAttributes);
277         } else if (expectedStateAugmentationSchemaPath.equals(augmentation.getTargetPath())) {
278             logger.debug("Parsing state of {}", moduleLocalNameFromXPath);
279             try {
280                 runtimeBeans = fillRuntimeBeans(dataNodeContainer, currentModule, typeProviderWrapper, packageName,
281                         moduleLocalNameFromXPath, javaNamePrefix);
282             } catch (NameConflictException e) {
283                 throw new NameConflictException(e.getConflictingName(), when.getQName(), when.getQName());
284             }
285             checkUniqueRuntimeBeansGeneratedClasses(uniqueGeneratedClassesNames, when, runtimeBeans);
286             Set<RuntimeBeanEntry> runtimeBeanEntryValues = Sets.newHashSet(runtimeBeans);
287             for (RuntimeBeanEntry entry : runtimeBeanEntryValues) {
288                 checkUniqueAttributesWithGeneratedClass(uniqueGeneratedClassesNames, when.getQName(),
289                         entry.getYangPropertiesToTypesMap());
290             }
291
292         } else {
293             throw new IllegalArgumentException("Cannot parse augmentation " + augmentation);
294         }
295         boolean hasDummyContainer = choiceCaseNode.equals(dataNodeContainer) == false;
296
297         String nullableDummyContainerName = hasDummyContainer ? dataNodeContainer.getQName().getLocalName() : null;
298         if (result.containsKey(moduleLocalNameFromXPath)) {
299             // either fill runtimeBeans or yangToAttributes, merge
300             ModuleMXBeanEntry moduleMXBeanEntry = result.get(moduleLocalNameFromXPath);
301             if (yangToAttributes != null && moduleMXBeanEntry.getAttributes() == null) {
302                 moduleMXBeanEntry.setYangToAttributes(yangToAttributes);
303             } else if (runtimeBeans != null && moduleMXBeanEntry.getRuntimeBeans() == null) {
304                 moduleMXBeanEntry.setRuntimeBeans(runtimeBeans);
305             }
306             checkState(Objects.equals(nullableDummyContainerName, moduleMXBeanEntry.getNullableDummyContainerName()),
307                     "Mismatch in module " + moduleMXBeanEntry.toString() + " - dummy container must be present/missing in" +
308                     " both state and configuration");
309         } else {
310             ModuleMXBeanEntry.ModuleMXBeanEntryInitial initial = new ModuleMXBeanEntry.ModuleMXBeanEntryInitialBuilder()
311             .setIdSchemaNode(moduleIdentity).setPackageName(packageName).setJavaNamePrefix(javaNamePrefix)
312             .setNamespace(currentModule.getNamespace().toString()).setqName(ModuleUtil.getQName(currentModule))
313             .build();
314
315             // construct ModuleMXBeanEntry
316             ModuleMXBeanEntry moduleMXBeanEntry = new ModuleMXBeanEntry(initial, yangToAttributes, providedServices,
317                     runtimeBeans);
318
319             moduleMXBeanEntry.setYangModuleName(currentModule.getName());
320             moduleMXBeanEntry.setYangModuleLocalname(moduleLocalNameFromXPath);
321             moduleMXBeanEntry.setNullableDummyContainerName(nullableDummyContainerName);
322             result.put(moduleLocalNameFromXPath, moduleMXBeanEntry);
323         }
324     }
325
326     private void checkUniqueRuntimeBeansGeneratedClasses(final Map<String, QName> uniqueGeneratedClassesNames,
327             final DataSchemaNode when, final Collection<RuntimeBeanEntry> runtimeBeans) {
328         for (RuntimeBeanEntry runtimeBean : runtimeBeans) {
329             final String javaNameOfRuntimeMXBean = runtimeBean.getJavaNameOfRuntimeMXBean();
330             if (uniqueGeneratedClassesNames.containsKey(javaNameOfRuntimeMXBean)) {
331                 QName firstDefinedQName = uniqueGeneratedClassesNames.get(javaNameOfRuntimeMXBean);
332                 throw new NameConflictException(javaNameOfRuntimeMXBean, firstDefinedQName, when.getQName());
333             }
334             uniqueGeneratedClassesNames.put(javaNameOfRuntimeMXBean, when.getQName());
335         }
336     }
337
338     private static void checkUniqueRuntimeBeanAttributesName(final ModuleMXBeanEntry mxBeanEntry,
339             final Map<String, QName> uniqueGeneratedClassesNames) {
340         for (RuntimeBeanEntry runtimeBeanEntry : mxBeanEntry.getRuntimeBeans()) {
341             for (String runtimeAttName : runtimeBeanEntry.getYangPropertiesToTypesMap().keySet()) {
342                 if (mxBeanEntry.getAttributes().keySet().contains(runtimeAttName)) {
343                     QName qName1 = uniqueGeneratedClassesNames.get(runtimeBeanEntry.getJavaNameOfRuntimeMXBean());
344                     QName qName2 = uniqueGeneratedClassesNames.get(mxBeanEntry.getGloballyUniqueName());
345                     throw new NameConflictException(runtimeAttName, qName1, qName2);
346                 }
347             }
348         }
349     }
350
351     private void checkUniqueAttributesWithGeneratedClass(final Map<String, QName> uniqueGeneratedClassNames,
352             final QName parentQName, final Map<String, AttributeIfc> yangToAttributes) {
353         for (Map.Entry<String, AttributeIfc> attr : yangToAttributes.entrySet()) {
354             if (attr.getValue() instanceof TOAttribute) {
355                 checkUniqueTOAttr(uniqueGeneratedClassNames, parentQName, (TOAttribute) attr.getValue());
356             } else if (attr.getValue() instanceof ListAttribute
357                     && ((ListAttribute) attr.getValue()).getInnerAttribute() instanceof TOAttribute) {
358                 checkUniqueTOAttr(uniqueGeneratedClassNames, parentQName,
359                         (TOAttribute) ((ListAttribute) attr.getValue()).getInnerAttribute());
360             }
361         }
362     }
363
364     private void checkUniqueTOAttr(final Map<String, QName> uniqueGeneratedClassNames, final QName parentQName, final TOAttribute attr) {
365         final String upperCaseCamelCase = attr.getUpperCaseCammelCase();
366         if (uniqueGeneratedClassNames.containsKey(upperCaseCamelCase)) {
367             QName firstDefinedQName = uniqueGeneratedClassNames.get(upperCaseCamelCase);
368             throw new NameConflictException(upperCaseCamelCase, firstDefinedQName, parentQName);
369         } else {
370             uniqueGeneratedClassNames.put(upperCaseCamelCase, parentQName);
371         }
372     }
373
374     private Collection<RuntimeBeanEntry> fillRuntimeBeans(final DataNodeContainer dataNodeContainer, final Module currentModule,
375             final TypeProviderWrapper typeProviderWrapper, final String packageName, final String moduleLocalNameFromXPath,
376             final String javaNamePrefix) {
377
378         return RuntimeBeanEntry.extractClassNameToRuntimeBeanMap(packageName, dataNodeContainer, moduleLocalNameFromXPath,
379                 typeProviderWrapper, javaNamePrefix, currentModule).values();
380
381     }
382
383     /**
384      * Since each case statement within a module must provide unique child nodes, it is allowed to wrap
385      * the actual configuration with a container node with name equal to case name.
386      *
387      * @param choiceCaseNode state or configuration case statement
388      * @return either choiceCaseNode or its only child container
389      */
390     private <HAS_CHILDREN_AND_QNAME extends DataNodeContainer & SchemaNode> HAS_CHILDREN_AND_QNAME getDataNodeContainer(final ChoiceCaseNode choiceCaseNode) {
391         Collection<DataSchemaNode> childNodes = choiceCaseNode.getChildNodes();
392         if (childNodes.size() == 1) {
393             DataSchemaNode onlyChild = childNodes.iterator().next();
394             if (onlyChild instanceof ContainerSchemaNode) {
395                 ContainerSchemaNode onlyContainer = (ContainerSchemaNode) onlyChild;
396                 if (Objects.equals(onlyContainer.getQName().getLocalName(), choiceCaseNode.getQName().getLocalName())) {
397                     // the actual configuration is inside dummy container
398                     return (HAS_CHILDREN_AND_QNAME) onlyContainer;
399                 }
400             }
401         }
402         return (HAS_CHILDREN_AND_QNAME) choiceCaseNode;
403     }
404
405     private Map<String, AttributeIfc> fillConfiguration(final DataNodeContainer dataNodeContainer, final Module currentModule,
406             final TypeProviderWrapper typeProviderWrapper, final Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
407             final SchemaContext schemaContext, final String packageName) {
408         Map<String, AttributeIfc> yangToAttributes = new HashMap<>();
409         for (DataSchemaNode attrNode : dataNodeContainer.getChildNodes()) {
410             AttributeIfc attributeValue = getAttributeValue(attrNode, currentModule, qNamesToSIEs, typeProviderWrapper,
411                     schemaContext, packageName);
412             yangToAttributes.put(attributeValue.getAttributeYangName(), attributeValue);
413         }
414         return yangToAttributes;
415     }
416
417     private Map<String, QName> findProvidedServices(final IdentitySchemaNode moduleIdentity, final Module currentModule,
418             final Map<QName, ServiceInterfaceEntry> qNamesToSIEs, final SchemaContext schemaContext) {
419         Map<String, QName> result = new HashMap<>();
420         for (UnknownSchemaNode unknownNode : moduleIdentity.getUnknownSchemaNodes()) {
421             if (ConfigConstants.PROVIDED_SERVICE_EXTENSION_QNAME.equals(unknownNode.getNodeType())) {
422                 String prefixAndIdentityLocalName = unknownNode.getNodeParameter();
423                 ServiceInterfaceEntry sie = findSIE(prefixAndIdentityLocalName, currentModule, qNamesToSIEs,
424                         schemaContext);
425                 result.put(sie.getFullyQualifiedName(), sie.getQName());
426             }
427         }
428         return result;
429     }
430
431     private AttributeIfc getAttributeValue(final DataSchemaNode attrNode, final Module currentModule,
432             final Map<QName, ServiceInterfaceEntry> qNamesToSIEs, final TypeProviderWrapper typeProviderWrapper,
433             final SchemaContext schemaContext, final String packageName) {
434
435         if (attrNode instanceof LeafSchemaNode) {
436             // simple type
437             LeafSchemaNode leaf = (LeafSchemaNode) attrNode;
438             return new JavaAttribute(leaf, typeProviderWrapper);
439         } else if (attrNode instanceof ContainerSchemaNode) {
440             // reference or TO
441             ContainerSchemaNode containerSchemaNode = (ContainerSchemaNode) attrNode;
442             Optional<? extends AbstractDependencyAttribute> dependencyAttributeOptional = extractDependency(
443                     containerSchemaNode, attrNode, currentModule, qNamesToSIEs, schemaContext);
444             if (dependencyAttributeOptional.isPresent()) {
445                 return dependencyAttributeOptional.get();
446             } else {
447                 return TOAttribute.create(containerSchemaNode, typeProviderWrapper, packageName);
448             }
449
450         } else if (attrNode instanceof LeafListSchemaNode) {
451             return ListAttribute.create((LeafListSchemaNode) attrNode, typeProviderWrapper);
452         } else if (attrNode instanceof ListSchemaNode) {
453             ListSchemaNode listSchemaNode = (ListSchemaNode) attrNode;
454             Optional<? extends AbstractDependencyAttribute> dependencyAttributeOptional = extractDependency(
455                     listSchemaNode, attrNode, currentModule, qNamesToSIEs, schemaContext);
456             if (dependencyAttributeOptional.isPresent()) {
457                 return dependencyAttributeOptional.get();
458             } else {
459                 return ListAttribute.create(listSchemaNode, typeProviderWrapper, packageName);
460             }
461         } else {
462             throw new UnsupportedOperationException("Unknown configuration node " + attrNode.toString());
463         }
464     }
465
466     private Optional<? extends AbstractDependencyAttribute> extractDependency(final DataNodeContainer dataNodeContainer,
467             final DataSchemaNode attrNode, final Module currentModule, final Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
468             final SchemaContext schemaContext) {
469         if (dataNodeContainer.getUses().size() == 1 && getChildNodeSizeWithoutUses(dataNodeContainer) == 0) {
470             // reference
471             UsesNode usesNode = dataNodeContainer.getUses().iterator().next();
472             checkState(usesNode.getRefines().size() == 1, "Unexpected 'refine' child node size of " + dataNodeContainer);
473             LeafSchemaNode refine = (LeafSchemaNode) usesNode.getRefines().values().iterator().next();
474             checkState(refine.getUnknownSchemaNodes().size() == 1, "Unexpected unknown schema node size of " + refine);
475             UnknownSchemaNode requiredIdentity = refine.getUnknownSchemaNodes().iterator().next();
476             checkState(ConfigConstants.REQUIRED_IDENTITY_EXTENSION_QNAME.equals(requiredIdentity.getNodeType()),
477                     "Unexpected language extension " + requiredIdentity);
478             String prefixAndIdentityLocalName = requiredIdentity.getNodeParameter();
479             // import should point to a module
480             ServiceInterfaceEntry serviceInterfaceEntry = findSIE(prefixAndIdentityLocalName, currentModule,
481                     qNamesToSIEs, schemaContext);
482             boolean mandatory = refine.getConstraints().isMandatory();
483             AbstractDependencyAttribute reference;
484             if (dataNodeContainer instanceof ContainerSchemaNode) {
485                 reference = new DependencyAttribute(attrNode, serviceInterfaceEntry, mandatory,
486                         attrNode.getDescription());
487             } else {
488                 reference = new ListDependenciesAttribute(attrNode, serviceInterfaceEntry, mandatory,
489                         attrNode.getDescription());
490             }
491             return Optional.of(reference);
492         }
493         return Optional.absent();
494     }
495
496     private int getChildNodeSizeWithoutUses(final DataNodeContainer csn) {
497         int result = 0;
498         for (DataSchemaNode dsn : csn.getChildNodes()) {
499             if (dsn.isAddedByUses() == false) {
500                 result++;
501             }
502         }
503         return result;
504     }
505
506     private ServiceInterfaceEntry findSIE(final String prefixAndIdentityLocalName, final Module currentModule,
507             final Map<QName, ServiceInterfaceEntry> qNamesToSIEs, final SchemaContext schemaContext) {
508
509         Matcher m = PREFIX_COLON_LOCAL_NAME.matcher(prefixAndIdentityLocalName);
510         Module foundModule;
511         String localSIName;
512         if (m.matches()) {
513             // if there is a prefix, look for ModuleImport with this prefix. Get
514             // Module from SchemaContext
515             String prefix = m.group(1);
516             ModuleImport moduleImport = findModuleImport(currentModule, prefix);
517             foundModule = schemaContext.findModuleByName(moduleImport.getModuleName(), moduleImport.getRevision());
518             checkNotNull(foundModule, format("Module not found in SchemaContext by %s", moduleImport));
519             localSIName = m.group(2);
520         } else {
521             foundModule = currentModule; // no prefix => SIE is in currentModule
522             localSIName = prefixAndIdentityLocalName;
523         }
524         QName siQName = QName.create(foundModule.getNamespace(), foundModule.getRevision(), localSIName);
525         ServiceInterfaceEntry sie = qNamesToSIEs.get(siQName);
526         checkState(sie != null, "Cannot find referenced Service Interface by " + prefixAndIdentityLocalName);
527         return sie;
528     }
529
530     private ModuleImport findModuleImport(final Module module, final String prefix) {
531         for (ModuleImport moduleImport : module.getImports()) {
532             if (moduleImport.getPrefix().equals(prefix)) {
533                 return moduleImport;
534             }
535         }
536         throw new IllegalStateException(format("Import not found with prefix %s in %s", prefix, module));
537     }
538
539     @VisibleForTesting
540     static Matcher getWhenConditionMatcher(final String prefix, final RevisionAwareXPath whenConstraint) {
541         String xpathRegex = MODULE_CONDITION_XPATH_TEMPLATE.replace(MAGIC_STRING, prefix);
542         Pattern pattern = Pattern.compile(xpathRegex);
543         return pattern.matcher(whenConstraint.toString());
544     }
545
546     String getConfigModulePrefixFromImport(final Module currentModule) {
547         for (ModuleImport currentImport : currentModule.getImports()) {
548             if (currentImport.getModuleName().equals(ConfigConstants.CONFIG_MODULE)) {
549                 return currentImport.getPrefix();
550             }
551         }
552         throw new IllegalArgumentException("Cannot find import " + ConfigConstants.CONFIG_MODULE + " in "
553                 + currentModule);
554     }
555
556 }