* fix : yang-generator can't find SIE if its base is another SIE
[controller.git] / opendaylight / config / yang-jmx-generator / src / main / java / org / opendaylight / controller / config / yangjmxgenerator / ModuleMXBeanEntry.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.Optional;
12 import com.google.common.collect.Sets;
13 import java.util.Arrays;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.Map;
18 import java.util.Map.Entry;
19 import java.util.Set;
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
22 import org.opendaylight.controller.config.yangjmxgenerator.attribute.AbstractDependencyAttribute;
23 import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
24 import org.opendaylight.controller.config.yangjmxgenerator.attribute.DependencyAttribute;
25 import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
26 import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute;
27 import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListDependenciesAttribute;
28 import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute;
29 import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FullyQualifiedNameHelper;
30 import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.NameConflictException;
31 import org.opendaylight.yangtools.binding.generator.util.BindingGeneratorUtil;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
34 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
35 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
37 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.Module;
43 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
44 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
45 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
46 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
48 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.UsesNode;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52 import static com.google.common.base.Preconditions.checkNotNull;
53 import static com.google.common.base.Preconditions.checkState;
54 import static java.lang.String.format;
55 import static org.opendaylight.controller.config.yangjmxgenerator.ConfigConstants.createConfigQName;
56
57 /**
58  * Represents part of yang model that describes a module.
59  *
60  * Example:
61  * <p>
62  * <blockquote>
63  *
64  * <pre>
65  *  identity threadpool-dynamic {
66  *      base config:module-type;
67  *      description "threadpool-dynamic description";
68  *      config:provided-service "th2:threadpool";
69  *      config:provided-service "th2:scheduled-threadpool";
70  *      config:java-name-prefix DynamicThreadPool
71  *  }
72  *  augment "/config:modules/config:module/config:module-type" {
73  *     case threadpool-dynamic {
74  *         when "/config:modules/config:module/config:module-type = 'threadpool-dynamic'";
75  *
76  *         container "configuration" {
77  *             // regular java attribute
78  *             leaf core-size {
79  *                 type uint32;
80  *          }
81  *
82  *             ...
83  *          // dependency
84  *             container threadfactory {
85  *                 uses config:service-ref {
86  *                     refine type {
87  *                         config:required-identity th:threadfactory;
88  *                  }
89  *              }
90  *          }
91  *      }
92  * }
93  * </pre>
94  *
95  * </blockquote>
96  * </p>
97  */
98 public class ModuleMXBeanEntry extends AbstractEntry {
99     private static final Logger logger = LoggerFactory
100             .getLogger(ModuleMXBeanEntry.class);
101
102     // TODO: the XPath should be parsed by code generator IMO
103     private static final String MAGIC_STRING = "MAGIC_STRING";
104     private static final String MODULE_CONDITION_XPATH_TEMPLATE = "^/MAGIC_STRING:modules/MAGIC_STRING:module/MAGIC_STRING:type\\s*=\\s*['\"](.+)['\"]$";
105     private static final SchemaPath expectedConfigurationAugmentationSchemaPath = new SchemaPath(
106             Arrays.asList(createConfigQName("modules"),
107                     createConfigQName("module"),
108                     createConfigQName("configuration")), true);
109     private static final SchemaPath expectedStateAugmentationSchemaPath = new SchemaPath(
110             Arrays.asList(createConfigQName("modules"),
111                     createConfigQName("module"), createConfigQName("state")),
112             true);
113
114     private static final Pattern PREFIX_COLON_LOCAL_NAME = Pattern
115             .compile("^(.+):(.+)$");
116
117     private static final String MODULE_SUFFIX = "Module";
118     private static final String FACTORY_SUFFIX = MODULE_SUFFIX + "Factory";
119     private static final String CLASS_NAME_SUFFIX = MODULE_SUFFIX + "MXBean";
120     private static final String ABSTRACT_PREFIX = "Abstract";
121
122     /*
123      * threadpool-dynamic from the example above, taken from when condition, not
124      * the case name
125      */
126     private final String globallyUniqueName;
127
128     private Map<String, AttributeIfc> yangToAttributes;
129
130     private final String nullableDescription, packageName, javaNamePrefix,
131             namespace;
132
133     private final Map<String, QName> providedServices;
134
135     private Collection<RuntimeBeanEntry> runtimeBeans;
136     private final QName yangModuleQName;
137
138     public ModuleMXBeanEntry(IdentitySchemaNode id,
139             Map<String, AttributeIfc> yangToAttributes, String packageName,
140             Map<String, QName> providedServices2, String javaNamePrefix,
141             String namespace, Collection<RuntimeBeanEntry> runtimeBeans,
142             QName yangModuleQName) {
143         this.globallyUniqueName = id.getQName().getLocalName();
144         this.yangToAttributes = yangToAttributes;
145         this.nullableDescription = id.getDescription();
146         this.packageName = packageName;
147         this.javaNamePrefix = checkNotNull(javaNamePrefix);
148         this.namespace = checkNotNull(namespace);
149         this.providedServices = Collections.unmodifiableMap(providedServices2);
150         this.runtimeBeans = runtimeBeans;
151         this.yangModuleQName = yangModuleQName;
152     }
153
154     public String getMXBeanInterfaceName() {
155         return javaNamePrefix + CLASS_NAME_SUFFIX;
156     }
157
158     public String getStubFactoryName() {
159         return javaNamePrefix + FACTORY_SUFFIX;
160     }
161
162     public String getAbstractFactoryName() {
163         return ABSTRACT_PREFIX + getStubFactoryName();
164     }
165
166     public String getStubModuleName() {
167         return javaNamePrefix + MODULE_SUFFIX;
168     }
169
170     public String getAbstractModuleName() {
171         return ABSTRACT_PREFIX + getStubModuleName();
172     }
173
174     public String getFullyQualifiedName(String typeName) {
175         return FullyQualifiedNameHelper.getFullyQualifiedName(packageName,
176                 typeName);
177     }
178
179     public String getGloballyUniqueName() {
180         return globallyUniqueName;
181     }
182
183     public String getPackageName() {
184         return packageName;
185     }
186
187     /**
188      * @return services implemented by this module. Keys are fully qualified
189      *         java names of generated ServiceInterface classes, values are
190      *         identity local names.
191      */
192     public Map<String, QName> getProvidedServices() {
193         return providedServices;
194     }
195
196     public void setRuntimeBeans(Collection<RuntimeBeanEntry> newRuntimeBeans) {
197         runtimeBeans = newRuntimeBeans;
198     }
199
200     public Collection<RuntimeBeanEntry> getRuntimeBeans() {
201         return runtimeBeans;
202     }
203
204     public String getJavaNamePrefix() {
205         return javaNamePrefix;
206     }
207
208     public String getNamespace() {
209         return namespace;
210     }
211
212     @VisibleForTesting
213     static Matcher getWhenConditionMatcher(String prefix,
214             RevisionAwareXPath whenConstraint) {
215         String xpathRegex = MODULE_CONDITION_XPATH_TEMPLATE.replace(
216                 MAGIC_STRING, prefix);
217         Pattern pattern = Pattern.compile(xpathRegex);
218         return pattern.matcher(whenConstraint.toString());
219     }
220
221     static String getConfigModulePrefixFromImport(Module currentModule) {
222         for (ModuleImport currentImport : currentModule.getImports()) {
223             if (currentImport.getModuleName().equals(
224                     ConfigConstants.CONFIG_MODULE)) {
225                 return currentImport.getPrefix();
226             }
227         }
228         throw new IllegalArgumentException("Cannot find import "
229                 + ConfigConstants.CONFIG_MODULE + " in " + currentModule);
230     }
231
232     /**
233      * Transform module to zero or more ModuleMXBeanEntry instances. Each
234      * instance must have a globally unique local name.
235      *
236      * @return Map of identity local names as keys, and ModuleMXBeanEntry
237      *         instances as values
238      */
239     public static Map<String/* identity local name */, ModuleMXBeanEntry> create(
240             Module currentModule,
241             Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
242             SchemaContext schemaContext,
243             TypeProviderWrapper typeProviderWrapper, String packageName) {
244         Map<String, QName> uniqueGeneratedClassesNames = new HashMap<>();
245         logger.debug("Generating ModuleMXBeans of {} to package {}",
246                 currentModule.getNamespace(), packageName);
247         String configModulePrefix;
248         try {
249             configModulePrefix = getConfigModulePrefixFromImport(currentModule);
250         } catch (IllegalArgumentException e) {
251             // this module does not import config module
252             return Collections.emptyMap();
253         }
254
255         // get identities of base config:module-type
256         Map<String, IdentitySchemaNode> moduleIdentities = new HashMap<>();
257
258         for (IdentitySchemaNode id : currentModule.getIdentities()) {
259             if (id.getBaseIdentity() != null
260                     && ConfigConstants.MODULE_TYPE_Q_NAME.equals(id
261                             .getBaseIdentity().getQName())) {
262                 String identityLocalName = id.getQName().getLocalName();
263                 if (moduleIdentities.containsKey(identityLocalName)) {
264                     throw new IllegalStateException(
265                             "Module name already defined in this module: "
266                                     + identityLocalName);
267                 } else {
268                     moduleIdentities.put(identityLocalName, id);
269                     logger.debug("Found identity {}", identityLocalName);
270                 }
271                 // validation check on unknown schema nodes
272                 boolean providedServiceWasSet = false;
273                 for (UnknownSchemaNode unknownNode : id.getUnknownSchemaNodes()) {
274                     // TODO: test this
275                     if (ConfigConstants.PROVIDED_SERVICE_EXTENSION_QNAME
276                             .equals(unknownNode.getNodeType())) {
277                         // no op: 0 or more provided identities are allowed
278                     } else if (ConfigConstants.JAVA_NAME_PREFIX_EXTENSION_QNAME
279                             .equals(unknownNode.getNodeType())) {
280                         // 0..1 allowed
281                         checkState(
282                                 providedServiceWasSet == false,
283                                 format("More than one language extension %s is not allowed here: %s",
284                                         ConfigConstants.JAVA_NAME_PREFIX_EXTENSION_QNAME,
285                                         id));
286                         providedServiceWasSet = true;
287                     } else {
288                         throw new IllegalStateException(
289                                 "Unexpected language extension "
290                                         + unknownNode.getNodeType());
291                     }
292                 }
293             }
294         }
295         Map<String, ModuleMXBeanEntry> result = new HashMap<>();
296         // each module name should have an augmentation defined
297         Map<String, IdentitySchemaNode> unaugmentedModuleIdentities = new HashMap<>(
298                 moduleIdentities);
299         for (AugmentationSchema augmentation : currentModule.getAugmentations()) {
300             Set<DataSchemaNode> childNodes = augmentation.getChildNodes();
301             if (childNodes.size() == 1) {
302                 DataSchemaNode when = childNodes.iterator().next();
303                 if (when instanceof ChoiceCaseNode) {
304                     ChoiceCaseNode choiceCaseNode = (ChoiceCaseNode) when;
305                     if (choiceCaseNode.getConstraints() == null
306                             || choiceCaseNode.getConstraints()
307                                     .getWhenCondition() == null) {
308                         continue;
309                     }
310                     RevisionAwareXPath xPath = choiceCaseNode.getConstraints()
311                             .getWhenCondition();
312                     Matcher matcher = getWhenConditionMatcher(
313                             configModulePrefix, xPath);
314                     if (matcher.matches() == false) {
315                         continue;
316                     }
317                     String moduleLocalNameFromXPath = matcher.group(1);
318                     IdentitySchemaNode moduleIdentity = moduleIdentities
319                             .get(moduleLocalNameFromXPath);
320                     unaugmentedModuleIdentities
321                             .remove(moduleLocalNameFromXPath);
322                     checkState(moduleIdentity != null, "Cannot find identity "
323                             + moduleLocalNameFromXPath
324                             + " matching augmentation " + augmentation);
325                     Map<String, QName> providedServices = findProvidedServices(
326                             moduleIdentity, currentModule, qNamesToSIEs,
327                             schemaContext);
328
329                     if (moduleIdentity == null) {
330                         throw new IllegalStateException(
331                                 "Cannot find identity specified by augmentation xpath constraint: "
332                                         + moduleLocalNameFromXPath + " of "
333                                         + augmentation);
334                     }
335                     String javaNamePrefix = findJavaNamePrefix(moduleIdentity);
336
337                     Map<String, AttributeIfc> yangToAttributes = null;
338                     // runtime-data
339                     Collection<RuntimeBeanEntry> runtimeBeans = null;
340
341                     if (expectedConfigurationAugmentationSchemaPath
342                             .equals(augmentation.getTargetPath())) {
343                         logger.debug("Parsing configuration of {}",
344                                 moduleLocalNameFromXPath);
345                         yangToAttributes = fillConfiguration(choiceCaseNode,
346                                 currentModule, typeProviderWrapper,
347                                 qNamesToSIEs, schemaContext, packageName);
348                         checkUniqueAttributesWithGeneratedClass(
349                                 uniqueGeneratedClassesNames, when.getQName(),
350                                 yangToAttributes);
351                     } else if (expectedStateAugmentationSchemaPath
352                             .equals(augmentation.getTargetPath())) {
353                         logger.debug("Parsing state of {}",
354                                 moduleLocalNameFromXPath);
355                         try {
356                             runtimeBeans = fillRuntimeBeans(choiceCaseNode,
357                                     currentModule, typeProviderWrapper,
358                                     packageName, moduleLocalNameFromXPath,
359                                     javaNamePrefix);
360                         } catch (NameConflictException e) {
361                             throw new NameConflictException(
362                                     e.getConflictingName(), when.getQName(),
363                                     when.getQName());
364                         }
365                         checkUniqueRuntimeBeansGeneratedClasses(
366                                 uniqueGeneratedClassesNames, when, runtimeBeans);
367                         Set<RuntimeBeanEntry> runtimeBeanEntryValues = Sets
368                                 .newHashSet(runtimeBeans);
369                         for (RuntimeBeanEntry entry : runtimeBeanEntryValues) {
370                             checkUniqueAttributesWithGeneratedClass(
371                                     uniqueGeneratedClassesNames,
372                                     when.getQName(),
373                                     entry.getYangPropertiesToTypesMap());
374                         }
375
376                     } else {
377                         throw new IllegalArgumentException(
378                                 "Cannot parse augmentation " + augmentation);
379                     }
380                     if (result.containsKey(moduleLocalNameFromXPath)) {
381                         // either fill runtimeBeans or yangToAttributes
382                         ModuleMXBeanEntry moduleMXBeanEntry = result
383                                 .get(moduleLocalNameFromXPath);
384                         if (yangToAttributes != null
385                                 && moduleMXBeanEntry.getAttributes() == null) {
386                             moduleMXBeanEntry
387                                     .setYangToAttributes(yangToAttributes);
388                         } else if (runtimeBeans != null
389                                 && moduleMXBeanEntry.getRuntimeBeans() == null) {
390                             moduleMXBeanEntry.setRuntimeBeans(runtimeBeans);
391                         }
392                     } else {
393                         // construct ModuleMXBeanEntry
394                         ModuleMXBeanEntry moduleMXBeanEntry = new ModuleMXBeanEntry(
395                                 moduleIdentity, yangToAttributes, packageName,
396                                 providedServices, javaNamePrefix, currentModule
397                                         .getNamespace().toString(),
398                                 runtimeBeans,
399                                 ModuleUtil.getQName(currentModule));
400                         moduleMXBeanEntry.setYangModuleName(currentModule
401                                 .getName());
402                         moduleMXBeanEntry
403                                 .setYangModuleLocalname(moduleLocalNameFromXPath);
404                         result.put(moduleLocalNameFromXPath, moduleMXBeanEntry);
405                     }
406                 } // skip if child node is not ChoiceCaseNode
407             } // skip if childNodes != 1
408         }
409         // clean up nulls
410         for (Entry<String, ModuleMXBeanEntry> entry : result.entrySet()) {
411             ModuleMXBeanEntry module = entry.getValue();
412             if (module.getAttributes() == null) {
413                 module.setYangToAttributes(Collections
414                         .<String, AttributeIfc> emptyMap());
415             } else if (module.getRuntimeBeans() == null) {
416                 module.setRuntimeBeans(Collections
417                         .<RuntimeBeanEntry> emptyList());
418             }
419         }
420         // check attributes name uniqueness
421         for (Entry<String, ModuleMXBeanEntry> entry : result.entrySet()) {
422             checkUniqueRuntimeBeanAttributesName(entry.getValue(),
423                     uniqueGeneratedClassesNames);
424         }
425         if (unaugmentedModuleIdentities.size() > 0) {
426             logger.warn("Augmentation not found for all module identities: {}",
427                     unaugmentedModuleIdentities.keySet());
428         }
429
430         logger.debug("Number of ModuleMXBeans to be generated: {}",
431                 result.size());
432         return result;
433     }
434
435     private static void checkUniqueRuntimeBeansGeneratedClasses(
436             Map<String, QName> uniqueGeneratedClassesNames,
437             DataSchemaNode when, Collection<RuntimeBeanEntry> runtimeBeans) {
438         for (RuntimeBeanEntry runtimeBean : runtimeBeans) {
439             final String javaNameOfRuntimeMXBean = runtimeBean
440                     .getJavaNameOfRuntimeMXBean();
441             if (uniqueGeneratedClassesNames
442                     .containsKey(javaNameOfRuntimeMXBean)) {
443                 QName firstDefinedQName = uniqueGeneratedClassesNames
444                         .get(javaNameOfRuntimeMXBean);
445                 throw new NameConflictException(javaNameOfRuntimeMXBean,
446                         firstDefinedQName, when.getQName());
447             }
448             uniqueGeneratedClassesNames.put(javaNameOfRuntimeMXBean,
449                     when.getQName());
450         }
451     }
452
453     private static void checkUniqueRuntimeBeanAttributesName(
454             ModuleMXBeanEntry mxBeanEntry,
455             Map<String, QName> uniqueGeneratedClassesNames) {
456         for (RuntimeBeanEntry runtimeBeanEntry : mxBeanEntry.getRuntimeBeans()) {
457             for (String runtimeAttName : runtimeBeanEntry
458                     .getYangPropertiesToTypesMap().keySet()) {
459                 if (mxBeanEntry.getAttributes().keySet()
460                         .contains(runtimeAttName)) {
461                     QName qName1 = uniqueGeneratedClassesNames
462                             .get(runtimeBeanEntry.getJavaNameOfRuntimeMXBean());
463                     QName qName2 = uniqueGeneratedClassesNames.get(mxBeanEntry
464                             .getGloballyUniqueName());
465                     throw new NameConflictException(runtimeAttName, qName1,
466                             qName2);
467                 }
468             }
469         }
470     }
471
472     private static void checkUniqueAttributesWithGeneratedClass(
473             Map<String, QName> uniqueGeneratedClassNames, QName parentQName,
474             Map<String, AttributeIfc> yangToAttributes) {
475         for (Entry<String, AttributeIfc> attr : yangToAttributes.entrySet()) {
476             if (attr.getValue() instanceof TOAttribute) {
477                 checkUniqueTOAttr(uniqueGeneratedClassNames, parentQName,
478                         (TOAttribute) attr.getValue());
479             } else if (attr.getValue() instanceof ListAttribute
480                     && ((ListAttribute) attr.getValue()).getInnerAttribute() instanceof TOAttribute) {
481                 checkUniqueTOAttr(uniqueGeneratedClassNames, parentQName,
482                         (TOAttribute) ((ListAttribute) attr.getValue())
483                                 .getInnerAttribute());
484             }
485         }
486     }
487
488     private static void checkUniqueTOAttr(
489             Map<String, QName> uniqueGeneratedClassNames, QName parentQName,
490             TOAttribute attr) {
491         final String upperCaseCammelCase = attr.getUpperCaseCammelCase();
492         if (uniqueGeneratedClassNames.containsKey(upperCaseCammelCase)) {
493             QName firstDefinedQName = uniqueGeneratedClassNames
494                     .get(upperCaseCammelCase);
495             throw new NameConflictException(upperCaseCammelCase,
496                     firstDefinedQName, parentQName);
497         } else {
498             uniqueGeneratedClassNames.put(upperCaseCammelCase, parentQName);
499         }
500     }
501
502     private static Collection<RuntimeBeanEntry> fillRuntimeBeans(
503             ChoiceCaseNode choiceCaseNode, Module currentModule,
504             TypeProviderWrapper typeProviderWrapper, String packageName,
505             String moduleLocalNameFromXPath, String javaNamePrefix) {
506
507         return RuntimeBeanEntry.extractClassNameToRuntimeBeanMap(packageName,
508                 choiceCaseNode, moduleLocalNameFromXPath, typeProviderWrapper,
509                 javaNamePrefix, currentModule).values();
510
511     }
512
513     private static Map<String, AttributeIfc> fillConfiguration(
514             ChoiceCaseNode choiceCaseNode, Module currentModule,
515             TypeProviderWrapper typeProviderWrapper,
516             Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
517             SchemaContext schemaContext, String packageName) {
518         Map<String, AttributeIfc> yangToAttributes = new HashMap<>();
519         for (DataSchemaNode attrNode : choiceCaseNode.getChildNodes()) {
520             AttributeIfc attributeValue = getAttributeValue(attrNode,
521                     currentModule, qNamesToSIEs, typeProviderWrapper,
522                     schemaContext, packageName);
523             yangToAttributes.put(attributeValue.getAttributeYangName(),
524                     attributeValue);
525         }
526         return yangToAttributes;
527     }
528
529     private static Map<String, QName> findProvidedServices(
530             IdentitySchemaNode moduleIdentity, Module currentModule,
531             Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
532             SchemaContext schemaContext) {
533         Map<String, QName> result = new HashMap<>();
534         for (UnknownSchemaNode unknownNode : moduleIdentity
535                 .getUnknownSchemaNodes()) {
536             if (ConfigConstants.PROVIDED_SERVICE_EXTENSION_QNAME
537                     .equals(unknownNode.getNodeType())) {
538                 String prefixAndIdentityLocalName = unknownNode
539                         .getNodeParameter();
540                 ServiceInterfaceEntry sie = findSIE(prefixAndIdentityLocalName,
541                         currentModule, qNamesToSIEs, schemaContext);
542                 result.put(sie.getFullyQualifiedName(), sie.getQName());
543             }
544         }
545         return result;
546     }
547
548     /**
549      * For input node, find if it contains config:java-name-prefix extension. If
550      * not found, convert local name of node converted to cammel case.
551      */
552     public static String findJavaNamePrefix(SchemaNode schemaNode) {
553         return convertToJavaName(schemaNode, true);
554     }
555
556     public static String findJavaParameter(SchemaNode schemaNode) {
557         return convertToJavaName(schemaNode, false);
558     }
559
560     public static String convertToJavaName(SchemaNode schemaNode,
561             boolean capitalizeFirstLetter) {
562         for (UnknownSchemaNode unknownNode : schemaNode.getUnknownSchemaNodes()) {
563             if (ConfigConstants.JAVA_NAME_PREFIX_EXTENSION_QNAME
564                     .equals(unknownNode.getNodeType())) {
565                 String value = unknownNode.getNodeParameter();
566                 return convertToJavaName(value, capitalizeFirstLetter);
567             }
568         }
569         return convertToJavaName(schemaNode.getQName().getLocalName(),
570                 capitalizeFirstLetter);
571     }
572
573     public static String convertToJavaName(String localName,
574             boolean capitalizeFirstLetter) {
575         if (capitalizeFirstLetter) {
576             return BindingGeneratorUtil.parseToClassName(localName);
577         } else {
578             return BindingGeneratorUtil.parseToValidParamName(localName);
579         }
580     }
581
582     private static int getChildNodeSizeWithoutUses(DataNodeContainer csn) {
583         int result = 0;
584         for (DataSchemaNode dsn : csn.getChildNodes()) {
585             if (dsn.isAddedByUses() == false) {
586                 result++;
587             }
588         }
589         return result;
590     }
591
592     private static AttributeIfc getAttributeValue(DataSchemaNode attrNode,
593             Module currentModule,
594             Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
595             TypeProviderWrapper typeProviderWrapper,
596             SchemaContext schemaContext, String packageName) {
597
598         if (attrNode instanceof LeafSchemaNode) {
599             // simple type
600             LeafSchemaNode leaf = (LeafSchemaNode) attrNode;
601             return new JavaAttribute(leaf, typeProviderWrapper);
602         } else if (attrNode instanceof ContainerSchemaNode) {
603             // reference or TO
604             ContainerSchemaNode containerSchemaNode = (ContainerSchemaNode) attrNode;
605             Optional<? extends AbstractDependencyAttribute> dependencyAttributeOptional = extractDependency(
606                     containerSchemaNode, attrNode, currentModule, qNamesToSIEs,
607                     schemaContext);
608             if (dependencyAttributeOptional.isPresent()) {
609                 return dependencyAttributeOptional.get();
610             } else {
611                 return TOAttribute.create(containerSchemaNode,
612                         typeProviderWrapper, packageName);
613             }
614
615         } else if (attrNode instanceof LeafListSchemaNode) {
616             return ListAttribute.create((LeafListSchemaNode) attrNode,
617                     typeProviderWrapper);
618         } else if (attrNode instanceof ListSchemaNode) {
619             ListSchemaNode listSchemaNode = (ListSchemaNode) attrNode;
620             Optional<? extends AbstractDependencyAttribute> dependencyAttributeOptional = extractDependency(
621                     listSchemaNode, attrNode, currentModule, qNamesToSIEs,
622                     schemaContext);
623             if (dependencyAttributeOptional.isPresent()) {
624                 return dependencyAttributeOptional.get();
625             } else {
626                 return ListAttribute.create(listSchemaNode,
627                         typeProviderWrapper, packageName);
628             }
629         } else {
630             throw new UnsupportedOperationException(
631                     "Unknown configuration node " + attrNode.toString());
632         }
633     }
634
635     private static Optional<? extends AbstractDependencyAttribute> extractDependency(
636             DataNodeContainer dataNodeContainer, DataSchemaNode attrNode,
637             Module currentModule,
638             Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
639             SchemaContext schemaContext) {
640         if (dataNodeContainer.getUses().size() == 1
641                 && getChildNodeSizeWithoutUses(dataNodeContainer) == 0) {
642             // reference
643             UsesNode usesNode = dataNodeContainer.getUses().iterator().next();
644             checkState(usesNode.getRefines().size() == 1,
645                     "Unexpected 'refine' child node size of "
646                             + dataNodeContainer);
647             LeafSchemaNode refine = (LeafSchemaNode) usesNode.getRefines()
648                     .values().iterator().next();
649             checkState(refine.getUnknownSchemaNodes().size() == 1,
650                     "Unexpected unknown schema node size of " + refine);
651             UnknownSchemaNode requiredIdentity = refine.getUnknownSchemaNodes()
652                     .iterator().next();
653             checkState(
654                     ConfigConstants.REQUIRED_IDENTITY_EXTENSION_QNAME.equals(requiredIdentity
655                             .getNodeType()), "Unexpected language extension "
656                             + requiredIdentity);
657             String prefixAndIdentityLocalName = requiredIdentity
658                     .getNodeParameter();
659             // import should point to a module
660             ServiceInterfaceEntry serviceInterfaceEntry = findSIE(
661                     prefixAndIdentityLocalName, currentModule, qNamesToSIEs,
662                     schemaContext);
663             boolean mandatory = refine.getConstraints().isMandatory();
664             AbstractDependencyAttribute reference;
665             if (dataNodeContainer instanceof ContainerSchemaNode) {
666                 reference = new DependencyAttribute(attrNode,
667                         serviceInterfaceEntry, mandatory,
668                         attrNode.getDescription());
669             } else {
670                 reference = new ListDependenciesAttribute(attrNode,
671                         serviceInterfaceEntry, mandatory,
672                         attrNode.getDescription());
673             }
674             return Optional.of(reference);
675         }
676         return Optional.absent();
677     }
678
679     private static ServiceInterfaceEntry findSIE(
680             String prefixAndIdentityLocalName, Module currentModule,
681             Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
682             SchemaContext schemaContext) {
683
684         Matcher m = PREFIX_COLON_LOCAL_NAME.matcher(prefixAndIdentityLocalName);
685         Module foundModule;
686         String localSIName;
687         if (m.matches()) {
688             // if there is a prefix, look for ModuleImport with this prefix. Get
689             // Module from SchemaContext
690             String prefix = m.group(1);
691             ModuleImport moduleImport = findModuleImport(currentModule, prefix);
692             foundModule = schemaContext.findModuleByName(
693                     moduleImport.getModuleName(), moduleImport.getRevision());
694             checkState(
695                     foundModule != null,
696                     format("Module not found in SchemaContext by %s",
697                             moduleImport));
698             localSIName = m.group(2);
699         } else {
700             foundModule = currentModule; // no prefix => SIE is in currentModule
701             localSIName = prefixAndIdentityLocalName;
702         }
703         QName siQName = new QName(foundModule.getNamespace(),
704                 foundModule.getRevision(), localSIName);
705         ServiceInterfaceEntry sie = qNamesToSIEs.get(siQName);
706         checkState(sie != null, "Cannot find referenced Service Interface by "
707                 + prefixAndIdentityLocalName);
708         return sie;
709     }
710
711     private static ModuleImport findModuleImport(Module module, String prefix) {
712         for (ModuleImport moduleImport : module.getImports()) {
713             if (moduleImport.getPrefix().equals(prefix)) {
714                 return moduleImport;
715             }
716         }
717         throw new IllegalStateException(format(
718                 "Import not found with prefix %s in %s", prefix, module));
719     }
720
721     public Map<String, AttributeIfc> getAttributes() {
722         return yangToAttributes;
723     }
724
725     private void setYangToAttributes(Map<String, AttributeIfc> newAttributes) {
726         this.yangToAttributes = newAttributes;
727
728     }
729
730     public String getNullableDescription() {
731         return nullableDescription;
732     }
733
734     public QName getYangModuleQName() {
735         return yangModuleQName;
736     }
737
738     @Override
739     public String toString() {
740         return "ModuleMXBeanEntry{" + "globallyUniqueName='"
741                 + globallyUniqueName + '\'' + ", packageName='" + packageName
742                 + '\'' + '}';
743     }
744 }