868a93412198e43d5a6c3789c4a230af4e6e056c
[mdsal.git] / binding2 / mdsal-binding2-runtime / src / main / java / org / opendaylight / mdsal / binding / javav2 / runtime / context / BindingRuntimeContext.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.mdsal.binding.javav2.runtime.context;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.Preconditions;
12 import com.google.common.cache.CacheBuilder;
13 import com.google.common.cache.CacheLoader;
14 import com.google.common.cache.LoadingCache;
15 import com.google.common.collect.BiMap;
16 import com.google.common.collect.FluentIterable;
17 import com.google.common.collect.HashBiMap;
18 import com.google.common.collect.HashMultimap;
19 import com.google.common.collect.ImmutableMap;
20 import com.google.common.collect.Multimap;
21 import java.lang.reflect.Method;
22 import java.util.AbstractMap.SimpleEntry;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Optional;
29 import java.util.Set;
30 import javax.annotation.Nullable;
31 import org.opendaylight.mdsal.binding.javav2.generator.api.ClassLoadingStrategy;
32 import org.opendaylight.mdsal.binding.javav2.generator.context.ModuleContext;
33 import org.opendaylight.mdsal.binding.javav2.generator.impl.BindingGeneratorImpl;
34 import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifier;
35 import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifierNormalizer;
36 import org.opendaylight.mdsal.binding.javav2.generator.util.ReferencedTypeImpl;
37 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
38 import org.opendaylight.mdsal.binding.javav2.model.api.MethodSignature;
39 import org.opendaylight.mdsal.binding.javav2.model.api.ParameterizedType;
40 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
41 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilder;
42 import org.opendaylight.mdsal.binding.javav2.runtime.context.util.BindingSchemaContextUtils;
43 import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections;
44 import org.opendaylight.mdsal.binding.javav2.spec.base.Action;
45 import org.opendaylight.mdsal.binding.javav2.spec.base.Rpc;
46 import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
47 import org.opendaylight.yangtools.concepts.Immutable;
48 import org.opendaylight.yangtools.yang.common.QName;
49 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
50 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
51 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
53 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
55 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
56 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
57 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
58 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
59 import org.opendaylight.yangtools.yang.model.api.Module;
60 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
61 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
62 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
63 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
64 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
65 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
67 import org.opendaylight.yangtools.yang.model.api.meta.StatementSource;
68 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
69 import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
70 import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils;
71 import org.slf4j.Logger;
72 import org.slf4j.LoggerFactory;
73
74 /**
75  * Runtime Context for Java YANG Binding classes
76  *
77  * <p>
78  * Runtime Context provides additional insight in Java YANG Binding, binding
79  * classes and underlying YANG schema, it contains runtime information, which
80  * could not be derived from generated classes alone using
81  * {@link BindingReflections}.
82  * <p>
83  * Some of this information are for example list of all available children for
84  * cases {@link #getChoiceCaseChildren(DataNodeContainer)}, since choices are
85  * augmentable and new choices may be introduced by additional models.
86  * <p>
87  * Same goes for all possible augmentations.
88  *
89  */
90 @Beta
91 public class BindingRuntimeContext implements Immutable {
92
93     private static final Logger LOG = LoggerFactory.getLogger(BindingRuntimeContext.class);
94     private static final char DOT = '.';
95     private final ClassLoadingStrategy strategy;
96     private final SchemaContext schemaContext;
97     private final Multimap<Type, AugmentationSchemaNode> augmentationToSchemas = HashMultimap.create();
98     private final BiMap<SchemaPath,Type> targetToAugmentation = HashBiMap.create();
99
100     private final BiMap<Type, WithStatus> typeToDefiningSchema = HashBiMap.create();
101     private final Multimap<Type, Type> choiceToCases = HashMultimap.create();
102     private final Map<QName, Type> identities = new HashMap<>();
103
104     private final LoadingCache<QName, Class<?>> identityClasses = CacheBuilder.newBuilder().weakValues().build(
105         new CacheLoader<QName, Class<?>>() {
106             @Override
107             public Class<?> load(final QName key) {
108                 final Type identityType = BindingRuntimeContext.this.identities.get(key);
109                 Preconditions.checkArgument(identityType != null, "Supplied QName %s is not a valid identity", key);
110                 try {
111                     return BindingRuntimeContext.this.strategy.loadClass(identityType);
112                 } catch (final ClassNotFoundException e) {
113                     throw new IllegalArgumentException("Required class " + identityType + "was not found.", e);
114                 }
115             }
116         });
117
118     private BindingRuntimeContext(final ClassLoadingStrategy strategy, final SchemaContext schema) {
119         this.strategy = strategy;
120         this.schemaContext = schema;
121
122         final BindingGeneratorImpl generator = new BindingGeneratorImpl(false);
123         final Map<Module, ModuleContext> modules = generator.getModuleContexts(this.schemaContext);
124
125         for (final ModuleContext ctx : modules.values()) {
126             this.augmentationToSchemas.putAll(ctx.getTypeToAugmentations());
127             this.targetToAugmentation.putAll(ctx.getTargetToAugmentation());
128             this.typeToDefiningSchema.putAll(ctx.getTypeToSchema());
129
130             ctx.getTypedefs();
131             this.choiceToCases.putAll(ctx.getChoiceToCases());
132             this.identities.putAll(ctx.getIdentities());
133         }
134     }
135
136     /**
137      * Creates Binding Runtime Context from supplied class loading strategy and
138      * schema context.
139      *
140      * @param strategy
141      *            - class loading strategy to retrieve generated Binding classes
142      * @param ctx
143      *            - schema context which describes YANG model and to which
144      *            Binding classes should be mapped
145      * @return Instance of BindingRuntimeContext for supplied schema context.
146      */
147     public static final BindingRuntimeContext create(final ClassLoadingStrategy strategy, final SchemaContext ctx) {
148         return new BindingRuntimeContext(strategy, ctx);
149     }
150
151     /**
152      * Returns a class loading strategy associated with this binding runtime context
153      * which is used to load classes.
154      *
155      * @return Class loading strategy.
156      */
157     public ClassLoadingStrategy getStrategy() {
158         return this.strategy;
159     }
160
161     /**
162      * Returns an stable immutable view of schema context associated with this Binding runtime context.
163      *
164      * @return stable view of schema context
165      */
166     public SchemaContext getSchemaContext() {
167         return this.schemaContext;
168     }
169
170     /**
171      * Returns schema of augmentation
172      * <p>
173      * Returned schema is schema definition from which augmentation class was
174      * generated. This schema is isolated from other augmentations. This means
175      * it contains augmentation definition as was present in original YANG
176      * module.
177      * <p>
178      * Children of returned schema does not contain any additional
179      * augmentations, which may be present in runtime for them, thus returned
180      * schema is unsuitable for use for validation of data.
181      * <p>
182      * For retrieving {@link AugmentationSchemaNode}, which will contains full model
183      * for child nodes, you should use method
184      * {@link #getResolvedAugmentationSchema(DataNodeContainer, Class)} which
185      * will return augmentation schema derived from supplied augmentation target
186      * schema.
187      *
188      * @param augClass
189      *            - ugmentation class
190      * @return Schema of augmentation or null if augmentaiton is not known in
191      *         this context
192      * @throws IllegalArgumentException
193      *             - if supplied class is not an augmentation
194      */
195     public @Nullable Entry<Type, Collection<AugmentationSchemaNode>>
196     getAugmentationDefinition(final Class<?> augClass) {
197         Preconditions.checkArgument(Augmentation.class.isAssignableFrom(augClass),
198             "Class %s does not represent augmentation", augClass);
199
200         final Type refType = referencedType(augClass);
201         for (Entry<Type, Collection<AugmentationSchemaNode>> entry : this.augmentationToSchemas.asMap().entrySet()) {
202             if (refType.equals(entry.getKey())) {
203                 return entry;
204             }
205         }
206
207         throw new IllegalArgumentException("Supplied class is not an augmentation.");
208     }
209
210     /**
211      * Returns defining {@link DataSchemaNode} for supplied class.
212      *
213      * <p>
214      * Returned schema is schema definition from which class was generated. This
215      * schema may be isolated from augmentations, if supplied class represent
216      * node, which was child of grouping or augmentation.
217      * <p>
218      * For getting augmentation schema from augmentation class use
219      * {@link #getAugmentationDefinition(Class)} instead.
220      *
221      * @param cls
222      *            - class which represents list, container, choice or case.
223      * @return Schema node, from which class was generated.
224      */
225     public DataSchemaNode getSchemaDefinition(final Class<?> cls) {
226         Preconditions.checkArgument(!Augmentation.class.isAssignableFrom(cls),"Supplied class must not be augmentation (%s is)", cls);
227         return (DataSchemaNode) this.typeToDefiningSchema.get(referencedType(cls));
228     }
229
230     public RpcDefinition getRpcDefinition(final Class<? extends Rpc<?, ?>> cls) {
231         return (RpcDefinition) typeToDefiningSchema.get(referencedType(cls));
232     }
233
234     public ActionDefinition getActionDefinition(final Class<? extends Action<?, ?, ?, ?>> cls) {
235         return (ActionDefinition) typeToDefiningSchema.get(referencedType(cls));
236     }
237
238     /**
239      * Returns defining {@link AugmentationSchemaNode} of target for supplied class.
240      *
241      * @param target
242      *            - {@link DataNodeContainer}
243      * @param aug
244      *            - supplied class
245      * @return entry of {@link AugmentationSchemaNode} according to its identifier
246      *         {@link AugmentationIdentifier}
247      */
248     public Entry<AugmentationIdentifier, AugmentationSchemaNode> getResolvedAugmentationSchema(
249             final DataNodeContainer target, final Class<? extends Augmentation<?>> aug) {
250         final Collection<AugmentationSchemaNode> origSchemas = getAugmentationDefinition(aug).getValue();
251         Preconditions.checkArgument(origSchemas != null, "Augmentation %s is not known in current schema context",aug);
252         /*
253          * FIXME: Validate augmentation schema lookup
254          *
255          * Currently this algorithm, does not verify if instantiated child nodes
256          * are real one derived from augmentation schema. The problem with full
257          * validation is, if user used copy builders, he may use augmentation
258          * which was generated for different place.
259          *
260          * If this augmentations have same definition, we emit same identifier
261          * with data and it is up to underlying user to validate data.
262          *
263          */
264         final Set<QName> childNames = new HashSet<>();
265         final Set<DataSchemaNode> realChilds = new HashSet<>();
266         for (final AugmentationSchemaNode origSchema : origSchemas) {
267             for (final DataSchemaNode child : origSchema.getChildNodes()) {
268                 final DataSchemaNode dataChildQNname = target.getDataChildByName(child.getQName());
269                 final String childLocalName = child.getQName().getLocalName();
270                 if (dataChildQNname == null) {
271                     for (final DataSchemaNode dataSchemaNode : target.getChildNodes()) {
272                         if (childLocalName.equals(dataSchemaNode.getQName().getLocalName())) {
273                             realChilds.add(dataSchemaNode);
274                             childNames.add(dataSchemaNode.getQName());
275                         }
276                     }
277                 } else {
278                     realChilds.add(dataChildQNname);
279                     childNames.add(child.getQName());
280                 }
281             }
282         }
283
284         final AugmentationIdentifier identifier = new AugmentationIdentifier(childNames);
285         final AugmentationSchemaNode proxy = new EffectiveAugmentationSchema(origSchemas.stream().findFirst().get(),
286             realChilds);
287         return new SimpleEntry<>(identifier, proxy);
288     }
289
290     /**
291      * Returns resolved case schema for supplied class
292      *
293      * @param schema
294      *            - resolved parent choice schema
295      * @param childClass
296      *            - class representing case.
297      * @return Optionally a resolved case schema,.empty if the choice is not
298      *         legal in the given context.
299      * @throws IllegalArgumentException
300      *             - if supplied class does not represent case
301      */
302     public Optional<CaseSchemaNode> getCaseSchemaDefinition(final ChoiceSchemaNode schema, final Class<?> childClass) throws IllegalArgumentException {
303         final DataSchemaNode origSchema = getSchemaDefinition(childClass);
304         Preconditions.checkArgument(origSchema instanceof CaseSchemaNode, "Supplied schema %s is not case.", origSchema);
305
306         /*
307          * FIXME: Make sure that if there are multiple augmentations of same
308          * named case, with same structure we treat it as equals this is due
309          * property of Binding specification and copy builders that user may be
310          * unaware that he is using incorrect case which was generated for
311          * choice inside grouping.
312          */
313         final Optional<CaseSchemaNode> found = BindingSchemaContextUtils.findInstantiatedCase(schema,
314                 (CaseSchemaNode) origSchema);
315         return found;
316     }
317
318     private static Type referencedType(final Class<?> type) {
319         return new ReferencedTypeImpl(type.getPackage().getName(), type.getSimpleName(), true, null);
320     }
321
322     static Type referencedType(final String type) {
323         final int packageClassSeparator = type.lastIndexOf(DOT);
324         return new ReferencedTypeImpl(type.substring(0, packageClassSeparator),
325             type.substring(packageClassSeparator + 1), true, null);
326     }
327
328     /**
329      * Returns schema ({@link DataSchemaNode}, {@link AugmentationSchemaNode} or {@link TypeDefinition})
330      * from which supplied class was generated. Returned schema may be augmented with
331      * additional information, which was not available at compile type
332      * (e.g. third party augmentations).
333      *
334      * @param type Binding Class for which schema should be retrieved.
335      * @return Instance of generated type (definition of Java API), along with
336      *     {@link DataSchemaNode}, {@link AugmentationSchemaNode} or {@link TypeDefinition}
337      *     which was used to generate supplied class.
338      */
339     public Entry<GeneratedType, Object> getTypeWithSchema(final Class<?> type) {
340         return getTypeWithSchema(referencedType(type));
341     }
342
343     public Entry<GeneratedType, Object> getTypeWithSchema(final String type) {
344         return getTypeWithSchema(referencedType(type));
345     }
346
347     private Entry<GeneratedType, Object> getTypeWithSchema(final Type referencedType) {
348         final Object schema = this.typeToDefiningSchema.get(referencedType);
349         Preconditions.checkNotNull(schema, "Failed to find schema for type %s", referencedType);
350
351         final Type definedType = this.typeToDefiningSchema.inverse().get(schema);
352         Preconditions.checkNotNull(definedType, "Failed to find defined type for %s schema %s", referencedType, schema);
353
354         if (definedType instanceof GeneratedTypeBuilder) {
355             return new SimpleEntry<>(((GeneratedTypeBuilder) definedType).toInstance(), schema);
356         }
357         Preconditions.checkArgument(definedType instanceof GeneratedType,"Type {} is not GeneratedType", referencedType);
358         return new SimpleEntry<>((GeneratedType) definedType,schema);
359     }
360
361     public ImmutableMap<Type, Entry<Type, Type>> getChoiceCaseChildren(final DataNodeContainer schema) {
362         final Map<Type,Entry<Type,Type>> childToCase = new HashMap<>();
363         for (final ChoiceSchemaNode choice :  FluentIterable.from(schema.getChildNodes()).filter(ChoiceSchemaNode.class)) {
364             final ChoiceSchemaNode originalChoice = getOriginalSchema(choice);
365             final Type choiceType = referencedType(this.typeToDefiningSchema.inverse().get(originalChoice));
366             final Collection<Type> cases = this.choiceToCases.get(choiceType);
367
368             for (Type caze : cases) {
369                 final Entry<Type,Type> caseIdentifier = new SimpleEntry<>(choiceType,caze);
370                 final HashSet<Type> caseChildren = new HashSet<>();
371                 if (caze instanceof GeneratedTypeBuilder) {
372                     caze = ((GeneratedTypeBuilder) caze).toInstance();
373                 }
374                 collectAllContainerTypes((GeneratedType) caze, caseChildren);
375                 for (final Type caseChild : caseChildren) {
376                     childToCase.put(caseChild, caseIdentifier);
377                 }
378             }
379         }
380         return ImmutableMap.copyOf(childToCase);
381     }
382
383     /**
384      * Map enum constants: yang - java
385      *
386      * @param enumClass enum generated class
387      * @return mapped enum constants from yang with their corresponding values in generated binding classes
388      */
389     public BiMap<String, String> getEnumMapping(final Class<?> enumClass) {
390         final Entry<GeneratedType, Object> typeWithSchema = getTypeWithSchema(enumClass);
391         return getEnumMapping(typeWithSchema);
392     }
393
394     /**
395      * See {@link #getEnumMapping(Class)}}
396      */
397     public BiMap<String, String> getEnumMapping(final String enumClass) {
398         final Entry<GeneratedType, Object> typeWithSchema = getTypeWithSchema(enumClass);
399         return getEnumMapping(typeWithSchema);
400     }
401
402     private static BiMap<String, String> getEnumMapping(final Entry<GeneratedType, Object> typeWithSchema) {
403         final TypeDefinition<?> typeDef = (TypeDefinition<?>) typeWithSchema.getValue();
404
405         Preconditions.checkArgument(typeDef instanceof EnumTypeDefinition);
406         final EnumTypeDefinition enumType = (EnumTypeDefinition) typeDef;
407
408         final HashBiMap<String, String> mappedEnums = HashBiMap.create();
409
410         for (final EnumTypeDefinition.EnumPair enumPair : enumType.getValues()) {
411             mappedEnums.put(enumPair.getName(),
412                     JavaIdentifierNormalizer.normalizeSpecificIdentifier(enumPair.getName(), JavaIdentifier.CLASS));
413         }
414
415         // TODO cache these maps for future use
416
417         return mappedEnums;
418     }
419
420     public Set<Class<?>> getCases(final Class<?> choice) {
421         final Collection<Type> cazes = this.choiceToCases.get(referencedType(choice));
422         final Set<Class<?>> ret = new HashSet<>(cazes.size());
423         for(final Type caze : cazes) {
424             try {
425                 final Class<?> c = this.strategy.loadClass(caze);
426                 ret.add(c);
427             } catch (final ClassNotFoundException e) {
428                 LOG.warn("Failed to load class for case {}, ignoring it", caze, e);
429             }
430         }
431         return ret;
432     }
433
434     public Class<?> getClassForSchema(final SchemaNode childSchema) {
435         final SchemaNode origSchema = getOriginalSchema(childSchema);
436         final Type clazzType = this.typeToDefiningSchema.inverse().get(origSchema);
437         try {
438             return this.strategy.loadClass(clazzType);
439         } catch (final ClassNotFoundException e) {
440             throw new IllegalStateException(e);
441         }
442     }
443
444     private static boolean isLocalAugment(final AugmentationTarget container, final AugmentationSchemaNode augment) {
445         Preconditions.checkState(container instanceof SchemaNode);
446         final QName root = ((SchemaNode) container).getPath().getPathFromRoot().iterator().next();
447         // findFirst makes no sense but just pick up one child to judge whether the target node is
448         // in the same module.
449         final java.util.Optional<DataSchemaNode> child = augment.getChildNodes().stream().findFirst();
450         if (child.isPresent()) {
451             return root.getModule().equals(child.get().getQName().getModule());
452         }
453         return false;
454     }
455
456     public ImmutableMap<AugmentationIdentifier,Type> getAvailableAugmentationTypes(final DataNodeContainer container) {
457         final Map<AugmentationIdentifier,Type> identifierToType = new HashMap<>();
458         if (container instanceof AugmentationTarget) {
459             for (final AugmentationSchemaNode augment : ((AugmentationTarget) container).getAvailableAugmentations()) {
460                 if (!isLocalAugment((AugmentationTarget) container, augment)) {
461                     // Augmentation must have child nodes if is to be used with Binding classes
462                     AugmentationSchemaNode augOrig = augment;
463                     while (augOrig.getOriginalDefinition().isPresent()) {
464                         augOrig = augOrig.getOriginalDefinition().get();
465                     }
466
467                     if (!augment.getChildNodes().isEmpty()) {
468                         final Type augType = this.targetToAugmentation.get(augOrig.getTargetPath());
469                         if (augType != null) {
470                             identifierToType.put(getAugmentationIdentifier(augment), augType);
471                         }
472                     }
473                 }
474             }
475         }
476
477         return ImmutableMap.copyOf(identifierToType);
478     }
479
480     private static AugmentationIdentifier getAugmentationIdentifier(final AugmentationSchemaNode augment) {
481         final Set<QName> childNames = new HashSet<>();
482         for (final DataSchemaNode child : augment.getChildNodes()) {
483             childNames.add(child.getQName());
484         }
485         return new AugmentationIdentifier(childNames);
486     }
487
488     private static Type referencedType(final Type type) {
489         if (type instanceof ReferencedTypeImpl) {
490             return type;
491         }
492         return new ReferencedTypeImpl(type.getPackageName(), type.getName(), true, null);
493     }
494
495     private static Set<Type> collectAllContainerTypes(final GeneratedType type, final Set<Type> collection) {
496         for (final MethodSignature definition : type.getMethodDefinitions()) {
497             Type childType = definition.getReturnType();
498             if (childType instanceof ParameterizedType) {
499                 childType = ((ParameterizedType) childType).getActualTypeArguments()[0];
500             }
501             if (childType instanceof GeneratedType || childType instanceof GeneratedTypeBuilder) {
502                 collection.add(referencedType(childType));
503             }
504         }
505         for (final Type parent : type.getImplements()) {
506             if (parent instanceof GeneratedType) {
507                 collectAllContainerTypes((GeneratedType) parent, collection);
508             }
509         }
510         return collection;
511     }
512
513     private static <T extends SchemaNode> T getOriginalSchema(final T choice) {
514         @SuppressWarnings("unchecked")
515         final T original = (T) SchemaNodeUtils.getRootOriginalIfPossible(choice);
516         if (original != null) {
517             return original;
518         }
519         return choice;
520     }
521
522     public Class<?> getIdentityClass(final QName input) {
523         return this.identityClasses.getUnchecked(input);
524     }
525
526     public Method findOperationMethod(final Class<?> key, final OperationDefinition operationDef)
527             throws NoSuchMethodException {
528         final String methodName =
529                 JavaIdentifierNormalizer.normalizeSpecificIdentifier(operationDef.getQName().getLocalName(),
530                 JavaIdentifier.METHOD);
531         if (operationDef.getInput() != null && isExplicitStatement(operationDef.getInput())) {
532             final Class<?> inputClz = this.getClassForSchema(operationDef.getInput());
533             return key.getMethod(methodName, inputClz);
534         }
535         return key.getMethod(methodName);
536     }
537
538     @SuppressWarnings("rawtypes")
539     private static boolean isExplicitStatement(final ContainerSchemaNode node) {
540         return node instanceof EffectiveStatement
541                 && ((EffectiveStatement) node).getDeclared().getStatementSource() == StatementSource.DECLARATION;
542     }
543 }