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