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