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