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