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