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