From: Jakub Toth Date: Thu, 1 Jun 2017 06:10:08 +0000 (+0200) Subject: Binding v2 runtime context X-Git-Tag: release/carbon-sr1~77 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=335cbc7b48494f5b8f6e29dfefb3720129778a95;p=mdsal.git Binding v2 runtime context *preparing of binding runtime context via reflection *tests Change-Id: I02a3e568942fe4f31621299c12bf978bcaa4940b Signed-off-by: Jakub Toth (cherry picked from commit 319b4d682c190284488cad35e6ff58746d5fde9a) --- diff --git a/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/BindingGeneratorImpl.java b/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/BindingGeneratorImpl.java index fc5a202688..ba89513fd8 100644 --- a/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/BindingGeneratorImpl.java +++ b/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/BindingGeneratorImpl.java @@ -75,7 +75,7 @@ public class BindingGeneratorImpl implements BindingGenerator { * @throws IllegalStateException if context contain no modules */ @Override - public List generateTypes(SchemaContext context) { + public List generateTypes(final SchemaContext context) { Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL."); Preconditions.checkState(context.getModules() != null, "Schema Context does not contain defined modules."); final Set modules = context.getModules(); @@ -111,32 +111,32 @@ public class BindingGeneratorImpl implements BindingGenerator { * if context contain no modules */ @Override - public List generateTypes(SchemaContext context, Set modules) { + public List generateTypes(final SchemaContext context, final Set modules) { Preconditions.checkArgument(context != null, "Schema Context reference cannot be NULL."); Preconditions.checkState(context.getModules() != null, "Schema Context does not contain defined modules."); Preconditions.checkArgument(modules != null, "Set of Modules cannot be NULL."); - typeProvider = new TypeProviderImpl(context); + this.typeProvider = new TypeProviderImpl(context); final Module[] modulesArray = new Module[context.getModules().size()]; context.getModules().toArray(modulesArray); final List contextModules = ModuleDependencySort.sort(modulesArray); - genTypeBuilders = new HashMap<>(); + this.genTypeBuilders = new HashMap<>(); for (final Module contextModule : contextModules) { - genCtx = ModuleToGenType.generate(contextModule, genTypeBuilders, context, typeProvider, - genCtx, verboseClassComments); + this.genCtx = ModuleToGenType.generate(contextModule, this.genTypeBuilders, context, this.typeProvider, + this.genCtx, this.verboseClassComments); } for (final Module contextModule : contextModules) { - genCtx = AugmentToGenType.generate(contextModule, context, typeProvider, genCtx, - genTypeBuilders, verboseClassComments); + this.genCtx = AugmentToGenType.generate(contextModule, context, this.typeProvider, this.genCtx, + this.genTypeBuilders, this.verboseClassComments); } final List filteredGenTypes = new ArrayList<>(); for (final Module m : modules) { - final ModuleContext ctx = Preconditions.checkNotNull(genCtx.get(m), + final ModuleContext ctx = Preconditions.checkNotNull(this.genCtx.get(m), "Module context not found for module %s", m); filteredGenTypes.addAll(ctx.getGeneratedTypes()); - final Set additionalTypes = ((TypeProviderImpl) typeProvider).getAdditionalTypes().get(m); + final Set additionalTypes = ((TypeProviderImpl) this.typeProvider).getAdditionalTypes().get(m); if (additionalTypes != null) { filteredGenTypes.addAll(additionalTypes); } @@ -144,4 +144,16 @@ public class BindingGeneratorImpl implements BindingGenerator { return filteredGenTypes; } + + /** + * Return module contexts from generated types according to context. + * + * @param schemaContext + * - for generating types + * @return module contexts + */ + public Map getModuleContexts(final SchemaContext schemaContext) { + generateTypes(schemaContext); + return this.genCtx; + } } diff --git a/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/ModuleContext.java b/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/ModuleContext.java index 58cd2b0855..4a80b28c80 100644 --- a/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/ModuleContext.java +++ b/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/ModuleContext.java @@ -39,7 +39,7 @@ import org.opendaylight.yangtools.yang.model.api.TypeDefinition; * This class holds information about generated entities in context of YANG module */ @Beta -final class ModuleContext { +public final class ModuleContext { private GeneratedTypeBuilder moduleNode; private final List genTOs = new ArrayList<>(); private final Map typedefs = new HashMap<>(); @@ -56,42 +56,42 @@ final class ModuleContext { private final Map innerTypes = new HashMap<>(); List getGeneratedTypes() { - List result = new ArrayList<>(); + final List result = new ArrayList<>(); - if (moduleNode != null) { - result.add(moduleNode.toInstance()); + if (this.moduleNode != null) { + result.add(this.moduleNode.toInstance()); } - result.addAll(genTOs.stream().map(GeneratedTOBuilder::toInstance).collect(Collectors.toList())); - result.addAll(typedefs.values().stream().filter(b -> b != null).collect(Collectors.toList())); - result.addAll(childNodes.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList())); - result.addAll(groupings.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList())); - result.addAll(cases.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList())); - result.addAll(identities.values().stream().map(GeneratedTOBuilder::toInstance).collect(Collectors.toList())); - result.addAll(topLevelNodes.stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList())); - result.addAll(augmentations.stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList())); + result.addAll(this.genTOs.stream().map(GeneratedTOBuilder::toInstance).collect(Collectors.toList())); + result.addAll(this.typedefs.values().stream().filter(b -> b != null).collect(Collectors.toList())); + result.addAll(this.childNodes.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList())); + result.addAll(this.groupings.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList())); + result.addAll(this.cases.values().stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList())); + result.addAll(this.identities.values().stream().map(GeneratedTOBuilder::toInstance).collect(Collectors.toList())); + result.addAll(this.topLevelNodes.stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList())); + result.addAll(this.augmentations.stream().map(GeneratedTypeBuilder::toInstance).collect(Collectors.toList())); return ImmutableList.copyOf(result); } public Multimap getChoiceToCases() { - return Multimaps.unmodifiableMultimap(choiceToCases); + return Multimaps.unmodifiableMultimap(this.choiceToCases); } public GeneratedTypeBuilder getModuleNode() { - return moduleNode; + return this.moduleNode; } public GeneratedTypeBuilder getChildNode(final SchemaPath p) { - return childNodes.get(p); + return this.childNodes.get(p); } public GeneratedTypeBuilder getGrouping(final SchemaPath p) { - return groupings.get(p); + return this.groupings.get(p); } public GeneratedTypeBuilder getCase(final SchemaPath p) { - return cases.get(p); + return this.cases.get(p); } public void addModuleNode(final GeneratedTypeBuilder moduleNode) { @@ -99,83 +99,83 @@ final class ModuleContext { } public void addGeneratedTOBuilder(final GeneratedTOBuilder b) { - genTOs.add(b); + this.genTOs.add(b); } public void addChildNodeType(final SchemaNode p, final GeneratedTypeBuilder b) { - childNodes.put(p.getPath(), b); - typeToSchema.put(b,p); + this.childNodes.put(p.getPath(), b); + this.typeToSchema.put(b,p); } public void addGroupingType(final SchemaPath p, final GeneratedTypeBuilder b) { - groupings.put(p, b); + this.groupings.put(p, b); } public void addTypedefType(final SchemaPath p, final Type t) { - typedefs.put(p, t); + this.typedefs.put(p, t); } public void addCaseType(final SchemaPath p, final GeneratedTypeBuilder b) { - cases.put(p, b); + this.cases.put(p, b); } public void addIdentityType(final QName name,final GeneratedTOBuilder b) { - identities.put(name,b); + this.identities.put(name,b); } public void addTopLevelNodeType(final GeneratedTypeBuilder b) { - topLevelNodes.add(b); + this.topLevelNodes.add(b); } public void addAugmentType(final GeneratedTypeBuilder b) { - augmentations.add(b); + this.augmentations.add(b); } public Map getTypedefs() { - return typedefs; + return this.typedefs; } public Map getChildNodes() { - return Collections.unmodifiableMap(childNodes); + return Collections.unmodifiableMap(this.childNodes); } public Map getGroupings() { - return Collections.unmodifiableMap(groupings); + return Collections.unmodifiableMap(this.groupings); } public Map getCases() { - return Collections.unmodifiableMap(cases); + return Collections.unmodifiableMap(this.cases); } public Map getIdentities() { - return Collections.unmodifiableMap(identities); + return Collections.unmodifiableMap(this.identities); } public Set getTopLevelNodes() { - return Collections.unmodifiableSet(topLevelNodes); + return Collections.unmodifiableSet(this.topLevelNodes); } public List getAugmentations() { - return Collections.unmodifiableList(augmentations); + return Collections.unmodifiableList(this.augmentations); } public BiMap getTypeToAugmentation() { - return Maps.unmodifiableBiMap(typeToAugmentation); + return Maps.unmodifiableBiMap(this.typeToAugmentation); } public void addTypeToAugmentation(final GeneratedTypeBuilder builder, final AugmentationSchema schema) { - typeToAugmentation.put(builder, schema); - typeToSchema.put(builder, schema); + this.typeToAugmentation.put(builder, schema); + this.typeToSchema.put(builder, schema); } public void addChoiceToCaseMapping(final Type choiceType, final Type caseType, final ChoiceCaseNode schema) { - choiceToCases.put(choiceType, caseType); - caseTypeToSchema.put(caseType, schema); - typeToSchema.put(caseType, schema); + this.choiceToCases.put(choiceType, caseType); + this.caseTypeToSchema.put(caseType, schema); + this.typeToSchema.put(caseType, schema); } public BiMap getCaseTypeToSchemas() { - return Maps.unmodifiableBiMap(caseTypeToSchema); + return Maps.unmodifiableBiMap(this.caseTypeToSchema); } /** @@ -187,11 +187,11 @@ final class ModuleContext { * @return Mapping from type to corresponding schema */ public Map getTypeToSchema() { - return Collections.unmodifiableMap(typeToSchema); + return Collections.unmodifiableMap(this.typeToSchema); } - protected void addTypeToSchema(Type type, TypeDefinition typedef) { - typeToSchema.put(type, typedef); + protected void addTypeToSchema(final Type type, final TypeDefinition typedef) { + this.typeToSchema.put(type, typedef); } /** @@ -200,12 +200,12 @@ final class ModuleContext { * @param path * @param enumBuilder */ - void addInnerTypedefType(SchemaPath path, EnumBuilder enumBuilder) { - innerTypes.put(path, enumBuilder); + void addInnerTypedefType(final SchemaPath path, final EnumBuilder enumBuilder) { + this.innerTypes.put(path, enumBuilder); } - public Type getInnerType(SchemaPath path) { - return innerTypes.get(path); + public Type getInnerType(final SchemaPath path) { + return this.innerTypes.get(path); } } diff --git a/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingRuntimeContext.java b/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingRuntimeContext.java new file mode 100644 index 0000000000..7174b940a7 --- /dev/null +++ b/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingRuntimeContext.java @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.javav2.generator.impl.util; + +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.BiMap; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multimap; +import java.util.AbstractMap.SimpleEntry; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import javax.annotation.Nullable; +import org.opendaylight.mdsal.binding.javav2.generator.api.ClassLoadingStrategy; +import org.opendaylight.mdsal.binding.javav2.generator.impl.BindingGeneratorImpl; +import org.opendaylight.mdsal.binding.javav2.generator.impl.ModuleContext; +import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifier; +import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifierNormalizer; +import org.opendaylight.mdsal.binding.javav2.generator.util.ReferencedTypeImpl; +import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType; +import org.opendaylight.mdsal.binding.javav2.model.api.MethodSignature; +import org.opendaylight.mdsal.binding.javav2.model.api.ParameterizedType; +import org.opendaylight.mdsal.binding.javav2.model.api.Type; +import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilder; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation; +import org.opendaylight.mdsal.binding.javav2.spec.util.BindingReflections; +import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; +import org.opendaylight.yangtools.yang.model.api.AugmentationTarget; +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition; +import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema; +import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Runtime Context for Java YANG Binding classes + * + *

+ * Runtime Context provides additional insight in Java YANG Binding, binding + * classes and underlying YANG schema, it contains runtime information, which + * could not be derived from generated classes alone using + * {@link BindingReflections}. + *

+ * Some of this information are for example list of all available children for + * cases {@link #getChoiceCaseChildren(DataNodeContainer)}, since choices are + * augmentable and new choices may be introduced by additional models. + *

+ * Same goes for all possible augmentations. + * + */ +@Beta +public class BindingRuntimeContext implements Immutable { + private static final Logger LOG = LoggerFactory.getLogger(BindingRuntimeContext.class); + private static final char DOT = '.'; + private final ClassLoadingStrategy strategy; + private final SchemaContext schemaContext; + + private final Map augmentationToSchema = new HashMap<>(); + private final BiMap typeToDefiningSchema = HashBiMap.create(); + private final Multimap choiceToCases = HashMultimap.create(); + private final Map identities = new HashMap<>(); + + private final LoadingCache> identityClasses = CacheBuilder.newBuilder().weakValues().build( + new CacheLoader>() { + @Override + public Class load(final QName key) { + final Type identityType = BindingRuntimeContext.this.identities.get(key); + Preconditions.checkArgument(identityType != null, "Supplied QName %s is not a valid identity", key); + try { + return BindingRuntimeContext.this.strategy.loadClass(identityType); + } catch (final ClassNotFoundException e) { + throw new IllegalArgumentException("Required class " + identityType + "was not found.", e); + } + } + }); + + private BindingRuntimeContext(final ClassLoadingStrategy strategy, final SchemaContext schema) { + this.strategy = strategy; + this.schemaContext = schema; + + final BindingGeneratorImpl generator = new BindingGeneratorImpl(false); + final Map modules = generator.getModuleContexts(this.schemaContext); + + for (final ModuleContext ctx : modules.values()) { + this.augmentationToSchema.putAll(ctx.getTypeToAugmentation()); + this.typeToDefiningSchema.putAll(ctx.getTypeToSchema()); + + ctx.getTypedefs(); + this.choiceToCases.putAll(ctx.getChoiceToCases()); + this.identities.putAll(ctx.getIdentities()); + } + } + + /** + * Creates Binding Runtime Context from supplied class loading strategy and + * schema context. + * + * @param strategy + * - class loading strategy to retrieve generated Binding classes + * @param ctx + * - schema context which describes YANG model and to which + * Binding classes should be mapped + * @return Instance of BindingRuntimeContext for supplied schema context. + */ + public static final BindingRuntimeContext create(final ClassLoadingStrategy strategy, final SchemaContext ctx) { + return new BindingRuntimeContext(strategy, ctx); + } + + /** + * Returns a class loading strategy associated with this binding runtime context + * which is used to load classes. + * + * @return Class loading strategy. + */ + public ClassLoadingStrategy getStrategy() { + return this.strategy; + } + + /** + * Returns an stable immutable view of schema context associated with this Binding runtime context. + * + * @return stable view of schema context + */ + public SchemaContext getSchemaContext() { + return this.schemaContext; + } + + /** + * Returns schema of augmentation + *

+ * Returned schema is schema definition from which augmentation class was + * generated. This schema is isolated from other augmentations. This means + * it contains augmentation definition as was present in original YANG + * module. + *

+ * Children of returned schema does not contain any additional + * augmentations, which may be present in runtime for them, thus returned + * schema is unsuitable for use for validation of data. + *

+ * For retrieving {@link AugmentationSchema}, which will contains full model + * for child nodes, you should use method + * {@link #getResolvedAugmentationSchema(DataNodeContainer, Class)} which + * will return augmentation schema derived from supplied augmentation target + * schema. + * + * @param augClass + * - ugmentation class + * @return Schema of augmentation or null if augmentaiton is not known in + * this context + * @throws IllegalArgumentException + * - if supplied class is not an augmentation + */ + public @Nullable AugmentationSchema getAugmentationDefinition(final Class augClass) throws IllegalArgumentException { + Preconditions.checkArgument(Augmentation.class.isAssignableFrom(augClass), "Class %s does not represent augmentation", augClass); + return this.augmentationToSchema.get(referencedType(augClass)); + } + + /** + * Returns defining {@link DataSchemaNode} for supplied class. + * + *

+ * Returned schema is schema definition from which class was generated. This + * schema may be isolated from augmentations, if supplied class represent + * node, which was child of grouping or augmentation. + *

+ * For getting augmentation schema from augmentation class use + * {@link #getAugmentationDefinition(Class)} instead. + * + * @param cls + * - class which represents list, container, choice or case. + * @return Schema node, from which class was generated. + */ + public DataSchemaNode getSchemaDefinition(final Class cls) { + Preconditions.checkArgument(!Augmentation.class.isAssignableFrom(cls),"Supplied class must not be augmentation (%s is)", cls); + return (DataSchemaNode) this.typeToDefiningSchema.get(referencedType(cls)); + } + + /** + * Returns defining {@link AugmentationSchema} of target for supplied class. + * + * @param target + * - {@link DataNodeContainer} + * @param aug + * - supplied class + * @return entry of {@link AugmentationSchema} according to its identifier + * {@link AugmentationIdentifier} + */ + public Entry getResolvedAugmentationSchema(final DataNodeContainer target, + final Class> aug) { + final AugmentationSchema origSchema = getAugmentationDefinition(aug); + Preconditions.checkArgument(origSchema != null, "Augmentation %s is not known in current schema context",aug); + /* + * FIXME: Validate augmentation schema lookup + * + * Currently this algorithm, does not verify if instantiated child nodes + * are real one derived from augmentation schema. The problem with full + * validation is, if user used copy builders, he may use augmentation + * which was generated for different place. + * + * If this augmentations have same definition, we emit same identifier + * with data and it is up to underlying user to validate data. + * + */ + final Set childNames = new HashSet<>(); + final Set realChilds = new HashSet<>(); + for (final DataSchemaNode child : origSchema.getChildNodes()) { + final DataSchemaNode dataChildQNname = target.getDataChildByName(child.getQName()); + final String childLocalName = child.getQName().getLocalName(); + if (dataChildQNname == null) { + for (final DataSchemaNode dataSchemaNode : target.getChildNodes()) { + if (childLocalName.equals(dataSchemaNode.getQName().getLocalName())) { + realChilds.add(dataSchemaNode); + childNames.add(dataSchemaNode.getQName()); + } + } + } else { + realChilds.add(dataChildQNname); + childNames.add(child.getQName()); + } + } + + final AugmentationIdentifier identifier = new AugmentationIdentifier(childNames); + final AugmentationSchema proxy = new EffectiveAugmentationSchema(origSchema, realChilds); + return new SimpleEntry<>(identifier, proxy); + } + + /** + * Returns resolved case schema for supplied class + * + * @param schema + * - resolved parent choice schema + * @param childClass + * - class representing case. + * @return Optionally a resolved case schema, absent if the choice is not + * legal in the given context. + * @throws IllegalArgumentException + * - if supplied class does not represent case + */ + public Optional getCaseSchemaDefinition(final ChoiceSchemaNode schema, final Class childClass) throws IllegalArgumentException { + final DataSchemaNode origSchema = getSchemaDefinition(childClass); + Preconditions.checkArgument(origSchema instanceof ChoiceCaseNode, "Supplied schema %s is not case.", origSchema); + + /* + * FIXME: Make sure that if there are multiple augmentations of same + * named case, with same structure we treat it as equals this is due + * property of Binding specification and copy builders that user may be + * unaware that he is using incorrect case which was generated for + * choice inside grouping. + */ + final Optional found = BindingSchemaContextUtils.findInstantiatedCase(schema, + (ChoiceCaseNode) origSchema); + return found; + } + + private static Type referencedType(final Class type) { + return new ReferencedTypeImpl(type.getPackage().getName(), type.getSimpleName()); + } + + static Type referencedType(final String type) { + final int packageClassSeparator = type.lastIndexOf(DOT); + return new ReferencedTypeImpl(type.substring(0, packageClassSeparator), type.substring(packageClassSeparator + 1)); + } + + /** + * Returns schema ({@link DataSchemaNode}, {@link AugmentationSchema} or {@link TypeDefinition}) + * from which supplied class was generated. Returned schema may be augmented with + * additional information, which was not available at compile type + * (e.g. third party augmentations). + * + * @param type Binding Class for which schema should be retrieved. + * @return Instance of generated type (definition of Java API), along with + * {@link DataSchemaNode}, {@link AugmentationSchema} or {@link TypeDefinition} + * which was used to generate supplied class. + */ + public Entry getTypeWithSchema(final Class type) { + return getTypeWithSchema(referencedType(type)); + } + + public Entry getTypeWithSchema(final String type) { + return getTypeWithSchema(referencedType(type)); + } + + private Entry getTypeWithSchema(final Type referencedType) { + final Object schema = this.typeToDefiningSchema.get(referencedType); + Preconditions.checkNotNull(schema, "Failed to find schema for type %s", referencedType); + + final Type definedType = this.typeToDefiningSchema.inverse().get(schema); + Preconditions.checkNotNull(definedType, "Failed to find defined type for %s schema %s", referencedType, schema); + + if (definedType instanceof GeneratedTypeBuilder) { + return new SimpleEntry<>(((GeneratedTypeBuilder) definedType).toInstance(), schema); + } + Preconditions.checkArgument(definedType instanceof GeneratedType,"Type {} is not GeneratedType", referencedType); + return new SimpleEntry<>((GeneratedType) definedType,schema); + } + + public ImmutableMap> getChoiceCaseChildren(final DataNodeContainer schema) { + final Map> childToCase = new HashMap<>(); + for (final ChoiceSchemaNode choice : FluentIterable.from(schema.getChildNodes()).filter(ChoiceSchemaNode.class)) { + final ChoiceSchemaNode originalChoice = getOriginalSchema(choice); + final Type choiceType = referencedType(this.typeToDefiningSchema.inverse().get(originalChoice)); + final Collection cases = this.choiceToCases.get(choiceType); + + for (Type caze : cases) { + final Entry caseIdentifier = new SimpleEntry<>(choiceType,caze); + final HashSet caseChildren = new HashSet<>(); + if (caze instanceof GeneratedTypeBuilder) { + caze = ((GeneratedTypeBuilder) caze).toInstance(); + } + collectAllContainerTypes((GeneratedType) caze, caseChildren); + for (final Type caseChild : caseChildren) { + childToCase.put(caseChild, caseIdentifier); + } + } + } + return ImmutableMap.copyOf(childToCase); + } + + /** + * Map enum constants: yang - java + * + * @param enumClass enum generated class + * @return mapped enum constants from yang with their corresponding values in generated binding classes + */ + public BiMap getEnumMapping(final Class enumClass) { + final Entry typeWithSchema = getTypeWithSchema(enumClass); + return getEnumMapping(typeWithSchema); + } + + /** + * See {@link #getEnumMapping(Class)}} + */ + public BiMap getEnumMapping(final String enumClass) { + final Entry typeWithSchema = getTypeWithSchema(enumClass); + return getEnumMapping(typeWithSchema); + } + + private static BiMap getEnumMapping(final Entry typeWithSchema) { + final TypeDefinition typeDef = (TypeDefinition) typeWithSchema.getValue(); + + Preconditions.checkArgument(typeDef instanceof EnumTypeDefinition); + final EnumTypeDefinition enumType = (EnumTypeDefinition) typeDef; + + final HashBiMap mappedEnums = HashBiMap.create(); + + for (final EnumTypeDefinition.EnumPair enumPair : enumType.getValues()) { + mappedEnums.put(enumPair.getName(), + JavaIdentifierNormalizer.normalizeSpecificIdentifier(enumPair.getName(), JavaIdentifier.CLASS)); + } + + // TODO cache these maps for future use + + return mappedEnums; + } + + public Set> getCases(final Class choice) { + final Collection cazes = this.choiceToCases.get(referencedType(choice)); + final Set> ret = new HashSet<>(cazes.size()); + for(final Type caze : cazes) { + try { + final Class c = this.strategy.loadClass(caze); + ret.add(c); + } catch (final ClassNotFoundException e) { + LOG.warn("Failed to load class for case {}, ignoring it", caze, e); + } + } + return ret; + } + + public Class getClassForSchema(final SchemaNode childSchema) { + final SchemaNode origSchema = getOriginalSchema(childSchema); + final Type clazzType = this.typeToDefiningSchema.inverse().get(origSchema); + try { + return this.strategy.loadClass(clazzType); + } catch (final ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } + + public ImmutableMap getAvailableAugmentationTypes(final DataNodeContainer container) { + final Map identifierToType = new HashMap<>(); + if (container instanceof AugmentationTarget) { + final Set augments = ((AugmentationTarget) container).getAvailableAugmentations(); + for (final AugmentationSchema augment : augments) { + // Augmentation must have child nodes if is to be used with Binding classes + AugmentationSchema augOrig = augment; + while (augOrig.getOriginalDefinition().isPresent()) { + augOrig = augOrig.getOriginalDefinition().get(); + } + + if (!augment.getChildNodes().isEmpty()) { + final Type augType = this.typeToDefiningSchema.inverse().get(augOrig); + if (augType != null) { + identifierToType.put(getAugmentationIdentifier(augment),augType); + } + } + } + } + + return ImmutableMap.copyOf(identifierToType); + } + + private static AugmentationIdentifier getAugmentationIdentifier(final AugmentationSchema augment) { + final Set childNames = new HashSet<>(); + for (final DataSchemaNode child : augment.getChildNodes()) { + childNames.add(child.getQName()); + } + return new AugmentationIdentifier(childNames); + } + + private static Type referencedType(final Type type) { + if (type instanceof ReferencedTypeImpl) { + return type; + } + return new ReferencedTypeImpl(type.getPackageName(), type.getName()); + } + + private static Set collectAllContainerTypes(final GeneratedType type, final Set collection) { + for (final MethodSignature definition : type.getMethodDefinitions()) { + Type childType = definition.getReturnType(); + if (childType instanceof ParameterizedType) { + childType = ((ParameterizedType) childType).getActualTypeArguments()[0]; + } + if (childType instanceof GeneratedType || childType instanceof GeneratedTypeBuilder) { + collection.add(referencedType(childType)); + } + } + for (final Type parent : type.getImplements()) { + if (parent instanceof GeneratedType) { + collectAllContainerTypes((GeneratedType) parent, collection); + } + } + return collection; + } + + private static T getOriginalSchema(final T choice) { + @SuppressWarnings("unchecked") + final T original = (T) SchemaNodeUtils.getRootOriginalIfPossible(choice); + if (original != null) { + return original; + } + return choice; + } + + public Class getIdentityClass(final QName input) { + return this.identityClasses.getUnchecked(input); + } +} diff --git a/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingSchemaContextUtils.java b/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingSchemaContextUtils.java new file mode 100644 index 0000000000..5be37fd61a --- /dev/null +++ b/binding2/mdsal-binding2-generator-impl/src/main/java/org/opendaylight/mdsal/binding/javav2/generator/impl/util/BindingSchemaContextUtils.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.generator.impl.util; + +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import java.util.Iterator; +import java.util.Set; +import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifier; +import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifierNormalizer; +import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode; +import org.opendaylight.mdsal.binding.javav2.spec.runtime.YangModuleInfo; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation; +import org.opendaylight.mdsal.binding.javav2.spec.structural.TreeChildNode; +import org.opendaylight.mdsal.binding.javav2.spec.util.BindingReflections; +import org.opendaylight.mdsal.binding.javav2.util.BindingMapping; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; +import org.opendaylight.yangtools.yang.model.api.OperationDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils; + +@Beta +public final class BindingSchemaContextUtils { + + private BindingSchemaContextUtils() { + throw new UnsupportedOperationException("Utility class should not be instantiated"); + } + + /** + * Find data node container by binding path in schema context. + * + * FIXME: This method does not search in case augmentations. + * + * @param ctx + * - schema context + * @param path + * - binding path + * @return node container by binding path if exists, absent otherwise + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Optional findDataNodeContainer(final SchemaContext ctx, + final InstanceIdentifier path) { + final Iterator pathArguments = path.getPathArguments().iterator(); + TreeArgument currentArg = pathArguments.next(); + Preconditions.checkArgument(currentArg != null); + QName currentQName = BindingReflections.findQName(currentArg.getType()); + + Optional currentContainer = Optional.absent(); + if (BindingReflections.isNotification(currentArg.getType())) { + currentContainer = findNotification(ctx, currentQName); + } else if (BindingReflections.isRpcOrActionType(currentArg.getType())) { + currentContainer = findFirstDataNodeContainerInRpcOrAction(ctx, currentArg.getType()); + if(currentQName == null && currentContainer.isPresent()) { + currentQName = ((DataSchemaNode) currentContainer.get()).getQName(); + } + } else { + currentContainer = findDataNodeContainer(ctx, currentQName); + } + + while (currentContainer.isPresent() && pathArguments.hasNext()) { + currentArg = pathArguments.next(); + if (Augmentation.class.isAssignableFrom(currentArg.getType())) { + currentQName = BindingReflections.findQName(currentArg.getType()); + if(pathArguments.hasNext()) { + currentArg = pathArguments.next(); + } else { + return currentContainer; + } + } + if (TreeChildNode.class.isAssignableFrom(currentArg.getType()) + && BindingReflections.isAugmentationChild(currentArg.getType())) { + currentQName = BindingReflections.findQName(currentArg.getType()); + } else { + currentQName = QName.create(currentQName, BindingReflections.findQName(currentArg.getType()).getLocalName()); + } + final Optional potential = findDataNodeContainer(currentContainer.get(), currentQName); + if (potential.isPresent()) { + currentContainer = potential; + } else { + return Optional.absent(); + } + } + return currentContainer; + } + + private static Optional findNotification(final SchemaContext ctx, final QName notificationQName) { + for (final NotificationDefinition notification : ctx.getNotifications()) { + if (notification.getQName().equals(notificationQName)) { + return Optional. of(notification); + } + } + return Optional.absent(); + } + + private static Optional findDataNodeContainer(final DataNodeContainer ctx, + final QName targetQName) { + + for (final DataSchemaNode child : ctx.getChildNodes()) { + if (child instanceof ChoiceSchemaNode) { + final DataNodeContainer potential = findInCases(((ChoiceSchemaNode) child), targetQName); + if (potential != null) { + return Optional.of(potential); + } + } else if (child instanceof DataNodeContainer && child.getQName().equals(targetQName)) { + return Optional.of((DataNodeContainer) child); + } else if (child instanceof DataNodeContainer // + && child.isAddedByUses() // + && child.getQName().getLocalName().equals(targetQName.getLocalName())) { + return Optional.of((DataNodeContainer) child); + } + + } + return Optional.absent(); + } + + private static DataNodeContainer findInCases(final ChoiceSchemaNode choiceNode, final QName targetQName) { + for (final ChoiceCaseNode caze : choiceNode.getCases()) { + final Optional potential = findDataNodeContainer(caze, targetQName); + if (potential.isPresent()) { + return potential.get(); + } + } + return null; + } + + private static Optional findFirstDataNodeContainerInRpcOrAction(final SchemaContext ctx, + final Class targetType) { + final YangModuleInfo moduleInfo; + try { + moduleInfo = BindingReflections.getModuleInfo(targetType); + } catch (final Exception e) { + throw new IllegalArgumentException( + String.format("Failed to load module information for class %s", targetType), e); + } + Optional optional = null; + optional = findFirst(ctx.getOperations(), moduleInfo, targetType); + if (optional.isPresent()) { + return optional; + } else { + return findFirst(ctx.getActions(), moduleInfo, targetType); + } + } + + private static Optional findFirst(final Set operations, + final YangModuleInfo moduleInfo, final Class targetType) { + for (final OperationDefinition operation : operations) { + final String operationNamespace = operation.getQName().getNamespace().toString(); + final String operationRevision = operation.getQName().getFormattedRevision(); + if (moduleInfo.getNamespace().equals(operationNamespace) + && moduleInfo.getRevision().equals(operationRevision)) { + final Optional potential = findInputOutput(operation, targetType.getSimpleName()); + if(potential.isPresent()) { + return potential; + } + } + } + return Optional.absent(); + } + + private static Optional findInputOutput(final OperationDefinition operation, + final String targetType) { + final String operationName = + JavaIdentifierNormalizer.normalizeSpecificIdentifier(operation.getQName().getLocalName(), + JavaIdentifier.CLASS); + final String actionInputName = + new StringBuilder(operationName).append(BindingMapping.RPC_INPUT_SUFFIX).toString(); + final String actionOutputName = + new StringBuilder(operationName).append(BindingMapping.RPC_OUTPUT_SUFFIX).toString(); + if (targetType.equals(actionInputName)) { + return Optional. of(operation.getInput()); + } else if (targetType.equals(actionOutputName)) { + return Optional. of(operation.getOutput()); + } + return Optional.absent(); + } + + /** + * Find choice schema node in parent by binding class. + * + * @param parent + * - choice parent + * @param choiceClass + * - choice binding class + * @return choice schema node if exists, absent() otherwise + */ + public static Optional findInstantiatedChoice(final DataNodeContainer parent, final Class choiceClass) { + return findInstantiatedChoice(parent, BindingReflections.findQName(choiceClass)); + } + + /** + * Find choice schema node in parent node by qname of choice. + * + * @param ctxNode + * - parent node + * @param choiceName + * - qname of choice + * @return choice schema node if exists, absent() otherwise + */ + public static Optional findInstantiatedChoice(final DataNodeContainer ctxNode, final QName choiceName) { + final DataSchemaNode potential = ctxNode.getDataChildByName(choiceName); + if (potential instanceof ChoiceSchemaNode) { + return Optional.of((ChoiceSchemaNode) potential); + } + + return Optional.absent(); + } + + /** + * Find choice case node in choice schema node. + * + * @param instantiatedChoice + * - choice + * @param originalDefinition + * - choice case + * @return choice case node if exists, absent() otherwise + */ + public static Optional findInstantiatedCase(final ChoiceSchemaNode instantiatedChoice, final ChoiceCaseNode originalDefinition) { + ChoiceCaseNode potential = instantiatedChoice.getCaseNodeByName(originalDefinition.getQName()); + if(originalDefinition.equals(potential)) { + return Optional.of(potential); + } + if (potential != null) { + final SchemaNode potentialRoot = SchemaNodeUtils.getRootOriginalIfPossible(potential); + if (originalDefinition.equals(potentialRoot)) { + return Optional.of(potential); + } + } + // We try to find case by name, then lookup its root definition + // and compare it with original definition + // This solves case, if choice was inside grouping + // which was used in different module and thus namespaces are + // different, but local names are still same. + // + // Still we need to check equality of definition, because local name is not + // sufficient to uniquelly determine equality of cases + // + potential = instantiatedChoice.getCaseNodeByName(originalDefinition.getQName().getLocalName()); + if(potential != null && (originalDefinition.equals(SchemaNodeUtils.getRootOriginalIfPossible(potential)))) { + return Optional.of(potential); + } + return Optional.absent(); + } +} diff --git a/binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetter.java b/binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetter.java new file mode 100644 index 0000000000..d15a3be025 --- /dev/null +++ b/binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetter.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.spec.util; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.Map; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation; +import org.opendaylight.mdsal.binding.javav2.spec.structural.AugmentationHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Beta +abstract class AugmentationFieldGetter { + + private static final Logger LOG = LoggerFactory.getLogger(AugmentationFieldGetter.class); + + private static final String AUGMENTATION_FIELD = "augmentation"; + + private static final AugmentationFieldGetter DUMMY = new AugmentationFieldGetter() { + @Override + protected Map>, Augmentation> getAugmentations(final Object input) { + return Collections.emptyMap(); + } + }; + + private static final AugmentationFieldGetter AUGMENTATION_HOLDER_GETTER = new AugmentationFieldGetter() { + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected Map>, Augmentation> getAugmentations(final Object input) { + return (Map) ((AugmentationHolder) input).augmentations(); + } + }; + + private static final LoadingCache, AugmentationFieldGetter> AUGMENTATION_GETTERS = + CacheBuilder.newBuilder().weakKeys().build(new AugmentationGetterLoader()); + + /** + * Retrieves augmentations from supplied object. + * + * @param input + * - input Data object, from which augmentations should be + * extracted + * @return Map of Augmentation class to augmentation + */ + protected abstract Map>, Augmentation> getAugmentations(final Object input); + + public static AugmentationFieldGetter getGetter(final Class clz) { + if (AugmentationHolder.class.isAssignableFrom(clz)) { + return AUGMENTATION_HOLDER_GETTER; + } + return AUGMENTATION_GETTERS.getUnchecked(clz); + } + + private static final class AugmentationGetterLoader extends CacheLoader, AugmentationFieldGetter> { + private static final MethodType GETTER_TYPE = MethodType.methodType(Map.class, Object.class); + private static final Lookup LOOKUP = MethodHandles.lookup(); + + @Override + public AugmentationFieldGetter load(final Class key) throws IllegalAccessException { + final Field field; + try { + field = key.getDeclaredField(AUGMENTATION_FIELD); + field.setAccessible(true); + } catch (NoSuchFieldException | SecurityException e) { + LOG.warn("Failed to acquire augmentation field {}, ignoring augmentations in class {}", + AUGMENTATION_FIELD, key, e); + return DUMMY; + } + if (!Map.class.isAssignableFrom(field.getType())) { + LOG.warn("Class {} field {} is not a Map, ignoring augmentations", key, + AUGMENTATION_FIELD); + return DUMMY; + } + + return new ReflectionAugmentationFieldGetter(LOOKUP.unreflectGetter(field).asType(GETTER_TYPE)); + } + } + + private static final class ReflectionAugmentationFieldGetter extends AugmentationFieldGetter { + private final MethodHandle fieldGetter; + + ReflectionAugmentationFieldGetter(final MethodHandle mh) { + this.fieldGetter = Preconditions.checkNotNull(mh); + } + + @Override + protected Map>, Augmentation> getAugmentations(final Object input) { + try { + return (Map>, Augmentation>) this.fieldGetter.invokeExact(input); + } catch (final Throwable e) { + throw new IllegalStateException("Failed to access augmentation field on " + input, e); + } + } + } +} diff --git a/binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflections.java b/binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflections.java new file mode 100644 index 0000000000..5474552ce4 --- /dev/null +++ b/binding2/mdsal-binding2-spec/src/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflections.java @@ -0,0 +1,665 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.javav2.spec.util; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import com.google.common.collect.Sets; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import org.opendaylight.mdsal.binding.javav2.spec.base.Action; +import org.opendaylight.mdsal.binding.javav2.spec.base.BaseIdentity; +import org.opendaylight.mdsal.binding.javav2.spec.base.Instantiable; +import org.opendaylight.mdsal.binding.javav2.spec.base.Notification; +import org.opendaylight.mdsal.binding.javav2.spec.base.Rpc; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode; +import org.opendaylight.mdsal.binding.javav2.spec.runtime.YangModelBindingProvider; +import org.opendaylight.mdsal.binding.javav2.spec.runtime.YangModuleInfo; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentable; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation; +import org.opendaylight.mdsal.binding.javav2.spec.structural.TreeChildNode; +import org.opendaylight.yangtools.util.ClassLoaderUtils; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Beta +public final class BindingReflections { + + private static final Logger LOG = LoggerFactory.getLogger(BindingReflections.class); + + private static final long EXPIRATION_TIME = 60; + private static final String QNAME_STATIC_FIELD_NAME = "QNAME"; + private static final String RPC_ACTION_OUTPUT_SUFFIX = "Output"; + private static final String MODULE_INFO_CLASS_NAME = "$YangModuleInfoImpl"; + private static final String PACKAGE_PREFIX = "org.opendaylight.mdsal.gen.javav2"; + private static final String ROOT_PACKAGE_PATTERN_STRING = + "(org.opendaylight.mdsal.gen.javav2.[a-z0-9_\\.]*\\.rev[0-9][0-9][0-1][0-9][0-3][0-9])"; + + private static final Pattern ROOT_PACKAGE_PATTERN = Pattern.compile(ROOT_PACKAGE_PATTERN_STRING); + + private static final LoadingCache, Optional> CLASS_TO_QNAME = CacheBuilder.newBuilder().weakKeys() + .expireAfterAccess(EXPIRATION_TIME, TimeUnit.SECONDS).build(new ClassToQNameLoader()); + + private BindingReflections() { + throw new UnsupportedOperationException("Utility class."); + } + + /** + * Find augmentation target class from concrete Augmentation class + * + * This method uses first generic argument of implemented + * {@link Augmentation} interface. + * + * @param augmentation + * {@link Augmentation} subclass for which we want to determine + * augmentation target. + * @return Augmentation target - class which augmentation provides + * additional extensions. + */ + public static Class> findAugmentationTarget( + final Class> augmentation) { + return ClassLoaderUtils.findFirstGenericArgument(augmentation, Augmentation.class); + } + + /** + * Find data hierarchy parent from concrete Tree Node class + * + * This method uses first generic argument of implemented + * {@link TreeChildNode} interface. + * + * @param childClass + * - child class for which we want to find the parent class + * @return Parent class, e.g. class of which the childClass is ChildOf + */ + static Class findHierarchicalParent(final Class> childClass) { + return ClassLoaderUtils.findFirstGenericArgument(childClass, TreeChildNode.class); + } + + /** + * Returns a QName associated to supplied type + * + * @param dataType + * - type of data + * @return QName associated to supplied dataType. If dataType is + * Augmentation method does not return canonical QName, but QName + * with correct namespace revision, but virtual local name, since + * augmentations do not have name. + * + * May return null if QName is not present. + */ + public static QName findQName(final Class dataType) { + return CLASS_TO_QNAME.getUnchecked(dataType).orNull(); + } + + /** + * Checks if method is RPC or Action invocation + * + * @param possibleMethod + * - method to check + * @return true if method is RPC or Action invocation, false otherwise. + */ + public static boolean isRpcOrActionMethod(final Method possibleMethod) { + return possibleMethod != null && (Rpc.class.isAssignableFrom(possibleMethod.getDeclaringClass()) + || Action.class.isAssignableFrom(possibleMethod.getDeclaringClass())) + && Future.class.isAssignableFrom(possibleMethod.getReturnType()) + // length <= 2: it seemed to be impossible to get correct RpcMethodInvoker because of + // resolveRpcInputClass() check.While RpcMethodInvoker counts with one argument for + // non input type and two arguments for input type, resolveRpcInputClass() counting + // with zero for non input and one for input type + && possibleMethod.getParameterTypes().length <= 2; + } + + /** + * Extracts Output class for RPC method + * + * @param targetMethod + * method to scan + * @return Optional.absent() if result type could not be get, or return type + * is Void. + */ + @SuppressWarnings("rawtypes") + public static Optional> resolveRpcOutputClass(final Method targetMethod) { + checkState(isRpcOrActionMethod(targetMethod), "Supplied method is not a RPC or Action invocation method"); + final Type futureType = targetMethod.getGenericReturnType(); + final Type rpcResultType = ClassLoaderUtils.getFirstGenericParameter(futureType); + final Type rpcResultArgument = ClassLoaderUtils.getFirstGenericParameter(rpcResultType); + if (rpcResultArgument instanceof Class && !Void.class.equals(rpcResultArgument)) { + return Optional.of((Class) rpcResultArgument); + } + return Optional.absent(); + } + + /** + * Extracts input class for RPC or Action + * + * @param targetMethod + * - method to scan + * @return Optional.absent() if RPC or Action has no input, RPC input type + * otherwise. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Optional>> resolveRpcInputClass(final Method targetMethod) { + for (final Class clazz : targetMethod.getParameterTypes()) { + if (Instantiable.class.isAssignableFrom(clazz)) { + return Optional.of(clazz); + } + } + return Optional.absent(); + } + + /** + * Find qname of base identity context. + * + * @param context + * - base identity type context + * @return QName of base identity context + */ + public static QName getQName(final Class context) { + return findQName(context); + } + + /** + * Checks if class is child of augmentation. + * + * @param clazz + * - class to check + * @return true if is augmentation, false otherwise + */ + public static boolean isAugmentationChild(final Class clazz) { + // FIXME: Current resolver could be still confused when child node was + // added by grouping + checkArgument(clazz != null); + + @SuppressWarnings({ "rawtypes", "unchecked" }) + final Class parent = findHierarchicalParent((Class) clazz); + if (parent == null) { + LOG.debug("Did not find a parent for class {}", clazz); + return false; + } + + final String clazzModelPackage = getModelRootPackageName(clazz.getPackage()); + final String parentModelPackage = getModelRootPackageName(parent.getPackage()); + + return !clazzModelPackage.equals(parentModelPackage); + } + + /** + * Returns root package name for supplied package. + * + * @param pkg + * Package for which find model root package. + * @return Package of model root. + */ + public static String getModelRootPackageName(final Package pkg) { + return getModelRootPackageName(pkg.getName()); + } + + /** + * Returns root package name for supplied package name. + * + * @param name + * - package for which find model root package + * @return Package of model root + */ + public static String getModelRootPackageName(final String name) { + checkArgument(name != null, "Package name should not be null."); + checkArgument(name.startsWith(PACKAGE_PREFIX), "Package name not starting with %s, is: %s", PACKAGE_PREFIX, + name); + final Matcher match = ROOT_PACKAGE_PATTERN.matcher(name); + checkArgument(match.find(), "Package name '%s' does not match required pattern '%s'", name, + ROOT_PACKAGE_PATTERN_STRING); + return match.group(0); + } + + /** + * Get QName module of specific Binding object class. + * + * @param clz + * - class of binding object + * @return QName module of binding object + */ + public static final QNameModule getQNameModule(final Class clz) { + if (Instantiable.class.isAssignableFrom(clz) || BaseIdentity.class.isAssignableFrom(clz)) { + return findQName(clz).getModule(); + } + try { + final YangModuleInfo modInfo = BindingReflections.getModuleInfo(clz); + return getQNameModule(modInfo); + } catch (final Exception e) { + throw new IllegalStateException("Unable to get QName of defining model.", e); + } + } + + /** + * Returns module QName. + * + * @param modInfo + * - module info + * @return {@link QNameModule} from module info + */ + public static final QNameModule getQNameModule(final YangModuleInfo modInfo) { + return QNameModule.create(URI.create(modInfo.getNamespace()), QName.parseRevision(modInfo.getRevision())); + } + + /** + * Returns instance of {@link YangModuleInfo} of declaring model for + * specific class. + * + * @param cls + * - class for getting info + * @return Instance of {@link YangModuleInfo} associated with model, from + * which this class was derived. + * @throws Exception + */ + public static YangModuleInfo getModuleInfo(final Class cls) throws Exception { + checkArgument(cls != null); + final String packageName = getModelRootPackageName(cls.getPackage()); + final String potentialClassName = getModuleInfoClassName(packageName); + return ClassLoaderUtils.withClassLoader(cls.getClassLoader(), (Callable) () -> { + final Class moduleInfoClass = Thread.currentThread().getContextClassLoader().loadClass(potentialClassName); + return (YangModuleInfo) moduleInfoClass.getMethod("getInstance").invoke(null); + }); + } + + /** + * Returns name of module info class. + * + * @param packageName + * - package name + * @return name of module info class + */ + public static String getModuleInfoClassName(final String packageName) { + return packageName + "." + MODULE_INFO_CLASS_NAME; + } + + /** + * Check if supplied class is derived from YANG model. + * + * @param cls + * - class to check + * @return true if class is derived from YANG model. + */ + public static boolean isBindingClass(final Class cls) { + if (Instantiable.class.isAssignableFrom(cls) || Augmentation.class.isAssignableFrom(cls) + || TreeNode.class.isAssignableFrom(cls)) { + return true; + } + return cls.getName().startsWith(PACKAGE_PREFIX); + } + + /** + * Checks if supplied method is callback for notifications. + * + * @param method + * - method for check + * @return true if method is notification callback + */ + public static boolean isNotificationCallback(final Method method) { + checkArgument(method != null); + if (method.getName().startsWith("on") && method.getParameterTypes().length == 1) { + final Class potentialNotification = method.getParameterTypes()[0]; + if (isNotification(potentialNotification) + && method.getName().equals("on" + potentialNotification.getSimpleName())) { + return true; + } + } + return false; + } + + /** + * Check if supplied class is Notification. + * + * @param potentialNotification + * - class to check + * @return true if class is notification, false otherwise + */ + public static boolean isNotification(final Class potentialNotification) { + checkArgument(potentialNotification != null, "potentialNotification must not be null."); + return Notification.class.isAssignableFrom(potentialNotification); + } + + /** + * Loads {@link YangModuleInfo} info available on current classloader. + * + * This method is shorthand for {@link #loadModuleInfos(ClassLoader)} with + * {@link Thread#getContextClassLoader()} for current thread. + * + * @return Set of {@link YangModuleInfo} available for current classloader. + */ + public static ImmutableSet loadModuleInfos() { + return loadModuleInfos(Thread.currentThread().getContextClassLoader()); + } + + /** + * Loads {@link YangModuleInfo} info available on supplied classloader. + * + * {@link YangModuleInfo} are discovered using {@link ServiceLoader} for + * {@link YangModelBindingProvider}. {@link YangModelBindingProvider} are + * simple classes which holds only pointers to actual instance + * {@link YangModuleInfo}. + * + * When {@link YangModuleInfo} is available, all dependencies are + * recursively collected into returning set by collecting results of + * {@link YangModuleInfo#getImportedModules()}. + * + * @param loader + * - classloader for which {@link YangModuleInfo} should be + * retrieved + * @return Set of {@link YangModuleInfo} available for supplied classloader. + */ + public static ImmutableSet loadModuleInfos(final ClassLoader loader) { + final Builder moduleInfoSet = ImmutableSet.builder(); + final ServiceLoader serviceLoader = ServiceLoader.load(YangModelBindingProvider.class, + loader); + for (final YangModelBindingProvider bindingProvider : serviceLoader) { + final YangModuleInfo moduleInfo = bindingProvider.getModuleInfo(); + checkState(moduleInfo != null, "Module Info for %s is not available.", bindingProvider.getClass()); + collectYangModuleInfo(bindingProvider.getModuleInfo(), moduleInfoSet); + } + return moduleInfoSet.build(); + } + + private static void collectYangModuleInfo(final YangModuleInfo moduleInfo, + final Builder moduleInfoSet) { + moduleInfoSet.add(moduleInfo); + for (final YangModuleInfo dependency : moduleInfo.getImportedModules()) { + collectYangModuleInfo(dependency, moduleInfoSet); + } + } + + /** + * Checks if supplied class represents RPC or Action input/output. + * + * @param targetType + * - class to be checked + * @return true if class represents RPC or Action input/output class + */ + public static boolean isRpcOrActionType(final Class targetType) { + return Instantiable.class.isAssignableFrom(targetType) && !TreeChildNode.class.isAssignableFrom(targetType) + && !Notification.class.isAssignableFrom(targetType) + && (targetType.getName().endsWith("Input") || targetType.getName().endsWith("Output")); + } + + /** + * Scans supplied class and returns an iterable of all data children + * classes. + * + * @param type + * - YANG Modeled Entity derived from DataContainer + * @return Iterable of all data children, which have YANG modeled entity + */ + @SuppressWarnings("unchecked") + public static Iterable> getChildrenClasses(final Class> type) { + checkArgument(type != null, "Target type must not be null"); + checkArgument(Instantiable.class.isAssignableFrom(type), "Supplied type must be derived from Instantiable"); + final List> ret = new LinkedList<>(); + for (final Method method : type.getMethods()) { + final Optional>> entity = getYangModeledReturnType(method); + if (entity.isPresent()) { + ret.add((Class) entity.get()); + } + } + return ret; + } + + /** + * Scans supplied class and returns an iterable of all data children + * classes. + * + * @param type + * - YANG Modeled Entity derived from DataContainer + * @return Iterable of all data children, which have YANG modeled entity + */ + public static Map, Method> getChildrenClassToMethod(final Class type) { + checkArgument(type != null, "Target type must not be null"); + checkArgument(Instantiable.class.isAssignableFrom(type), "Supplied type must be derived from Instantiable"); + final Map, Method> ret = new HashMap<>(); + for (final Method method : type.getMethods()) { + final Optional>> entity = getYangModeledReturnType(method); + if (entity.isPresent()) { + ret.put(entity.get(), method); + } + } + return ret; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Optional>> getYangModeledReturnType(final Method method) { + if ("getClass".equals(method.getName()) || !method.getName().startsWith("get") + || method.getParameterTypes().length > 0) { + return Optional.absent(); + } + + final Class returnType = method.getReturnType(); + if (Instantiable.class.isAssignableFrom(returnType)) { + return Optional.of(returnType); + } else if (List.class.isAssignableFrom(returnType)) { + try { + return ClassLoaderUtils.withClassLoader(method.getDeclaringClass().getClassLoader(), + (Callable>>>) () -> { + final Type listResult = ClassLoaderUtils.getFirstGenericParameter(method.getGenericReturnType()); + if (listResult instanceof Class + && Instantiable.class.isAssignableFrom((Class) listResult)) { + return Optional.of((Class) listResult); + } + return Optional.absent(); + }); + } catch (final Exception e) { + /* + * + * It is safe to log this this exception on debug, since this + * method should not fail. Only failures are possible if the + * runtime / backing. + */ + LOG.debug("Unable to find YANG modeled return type for {}", method, e); + } + } + return Optional.absent(); + } + + private static class ClassToQNameLoader extends CacheLoader, Optional> { + + @Override + public Optional load(@Nonnull final Class key) throws Exception { + return resolveQNameNoCache(key); + } + + /** + * Tries to resolve QName for supplied class. + * + * Looks up for static field with name from constant {@link #QNAME_STATIC_FIELD_NAME} and returns + * value if present. + * + * If field is not present uses {@link #computeQName(Class)} to compute QName for missing types. + * + * @param key + * - class for resolving QName + * @return resolved QName + */ + private static Optional resolveQNameNoCache(final Class key) { + try { + final Field field = key.getField(QNAME_STATIC_FIELD_NAME); + final Object obj = field.get(null); + if (obj instanceof QName) { + return Optional.of((QName) obj); + } + + } catch (final NoSuchFieldException e) { + return Optional.of(computeQName(key)); + } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) { + /* + * It is safe to log this this exception on debug, since this method + * should not fail. Only failures are possible if the runtime / + * backing. + */ + LOG.debug("Unexpected exception during extracting QName for {}", key, e); + } + return Optional.absent(); + } + + /** + * Computes QName for supplied class + * + * Namespace and revision are same as {@link YangModuleInfo} associated + * with supplied class. + *

+ * If class is + *

    + *
  • rpc/action input: local name is "input". + *
  • rpc/action output: local name is "output". + *
  • augmentation: local name is "module name". + *
+ * + * There is also fallback, if it is not possible to compute QName using + * following algorithm returns module QName. + * + * FIXME: Extend this algorithm to also provide QName for YANG modeled + * simple types. + * + * @throws IllegalStateException + * - if YangModuleInfo could not be resolved + * @throws IllegalArgumentException + * - if supplied class was not derived from YANG model + * + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static QName computeQName(final Class key) { + if (isBindingClass(key)) { + YangModuleInfo moduleInfo; + try { + moduleInfo = getModuleInfo(key); + } catch (final Exception e) { + throw new IllegalStateException("Unable to get QName for " + key + + ". YangModuleInfo was not found.", e); + } + final QName module = getModuleQName(moduleInfo).intern(); + if (Augmentation.class.isAssignableFrom(key)) { + return module; + } else if (isRpcOrActionType(key)) { + final String className = key.getSimpleName(); + if (className.endsWith(RPC_ACTION_OUTPUT_SUFFIX)) { + return QName.create(module, "output").intern(); + } else { + return QName.create(module, "input").intern(); + } + } + /* + * Fallback for Binding types which do not have QNAME field + */ + return module; + } else { + throw new IllegalArgumentException("Supplied class " + key + "is not derived from YANG."); + } + } + + } + + /** + * Given a {@link YangModuleInfo}, create a QName representing it. The QName + * is formed by reusing the module's namespace and revision using the + * module's name as the QName's local name. + * + * @param moduleInfo + * module information + * @return QName representing the module + */ + public static QName getModuleQName(final YangModuleInfo moduleInfo) { + checkArgument(moduleInfo != null, "moduleInfo must not be null."); + return QName.create(moduleInfo.getNamespace(), moduleInfo.getRevision(), moduleInfo.getName()); + } + + /** + * Extracts augmentation from Binding DTO field using reflection + * + * @param input + * Instance of DataObject which is augmentable and may contain + * augmentation + * @return Map of augmentations if read was successful, otherwise empty map. + */ + public static Map>, Augmentation> getAugmentations(final Augmentable input) { + return AugmentationFieldGetter.getGetter(input.getClass()).getAugmentations(input); + } + + /** + * Determines if two augmentation classes or case classes represents same + * data. + *

+ * Two augmentations or cases could be substituted only if and if: + *

    + *
  • Both implements same interfaces
  • + *
  • Both have same children
  • + *
  • If augmentations: Both have same augmentation target class. Target + * class was generated for data node in grouping.
  • + *
  • If cases: Both are from same choice. Choice class was generated for + * data node in grouping.
  • + *
+ *

+ * Explanation: Binding Specification reuses classes generated for + * groupings as part of normal data tree, this classes from grouping could + * be used at various locations and user may not be aware of it and may use + * incorrect case or augmentation in particular subtree (via copy + * constructors, etc). + * + * @param potential + * - class which is potential substitution + * @param target + * - class which should be used at particular subtree + * @return true if and only if classes represents same data. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static boolean isSubstitutionFor(final Class potential, final Class target) { + final HashSet subImplemented = Sets.newHashSet(potential.getInterfaces()); + final HashSet targetImplemented = Sets.newHashSet(target.getInterfaces()); + if (!subImplemented.equals(targetImplemented)) { + return false; + } + if (Augmentation.class.isAssignableFrom(potential) + && !BindingReflections.findAugmentationTarget(potential).equals( + BindingReflections.findAugmentationTarget(target))) { + return false; + } + for (final Method potentialMethod : potential.getMethods()) { + try { + final Method targetMethod = target.getMethod(potentialMethod.getName(), potentialMethod.getParameterTypes()); + if (!potentialMethod.getReturnType().equals(targetMethod.getReturnType())) { + return false; + } + } catch (final NoSuchMethodException e) { + // Counterpart method is missing, so classes could not be + // substituted. + return false; + } catch (final SecurityException e) { + throw new IllegalStateException("Could not compare methods", e); + } + } + return true; + } +} diff --git a/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetterTest.java b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetterTest.java new file mode 100644 index 0000000000..1c9c3dd484 --- /dev/null +++ b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/AugmentationFieldGetterTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.javav2.spec.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.opendaylight.mdsal.binding.javav2.spec.util.AugmentationFieldGetter.getGetter; + +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation; +import org.opendaylight.mdsal.binding.javav2.spec.structural.AugmentationHolder; + +public class AugmentationFieldGetterTest { + + @SuppressWarnings("rawtypes") + @Test + public void getGetterTest() throws Exception { + assertNotNull(getGetter(AugmentationHolder.class)); + assertTrue(getGetter(AugmentationHolder.class).getAugmentations(mock(AugmentationHolder.class)).isEmpty()); + assertTrue(getGetter(Object.class).getAugmentations(null).isEmpty()); + assertTrue(getGetter(TestAugmentationWrongTypeClass.class).getAugmentations(null).isEmpty()); + + final AugmentationFieldGetter augmentationFieldGetter = getGetter(TestAugmentationClass.class); + final Augmentation augmentation = mock(Augmentation.class); + final TestAugmentationClass testAugmentationClass = new TestAugmentationClass(); + + testAugmentationClass.addAugmentation(augmentation, augmentation); + assertNotNull(augmentationFieldGetter.getAugmentations(testAugmentationClass)); + assertEquals(1, augmentationFieldGetter.getAugmentations(testAugmentationClass).size()); + } + + @Test(expected = IllegalStateException.class) + public void getWrongGetterTest() throws Exception { + final AugmentationFieldGetter augmentationFieldGetter = getGetter(TestAugmentationClass.class); + augmentationFieldGetter.getAugmentations(new String()); + fail("Expected IllegalStateException"); + } + + @Test + public void getNoGetterTest() throws Exception { + assertTrue(getGetter(Object.class).getAugmentations(null).isEmpty()); + } + + private final class TestAugmentationClass { + @SuppressWarnings("rawtypes") + private final Map augmentation = new HashMap(); + + @SuppressWarnings({ "rawtypes", "unchecked" }) + void addAugmentation(final Augmentation key, final Augmentation value) { + this.augmentation.put(key, value); + } + } + + private final class TestAugmentationWrongTypeClass { + @SuppressWarnings("unused") + private String augmentation; + } +} diff --git a/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflectionsTest.java b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflectionsTest.java new file mode 100644 index 0000000000..83789fc72e --- /dev/null +++ b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/BindingReflectionsTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.javav2.spec.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import java.lang.reflect.Constructor; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Future; +import org.junit.Test; +import org.opendaylight.mdsal.binding.javav2.spec.base.BaseIdentity; +import org.opendaylight.mdsal.binding.javav2.spec.base.Input; +import org.opendaylight.mdsal.binding.javav2.spec.base.Rpc; +import org.opendaylight.mdsal.binding.javav2.spec.base.RpcCallback; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation; +import org.opendaylight.mdsal.binding.javav2.spec.structural.TreeChildNode; +import org.opendaylight.mdsal.binding.javav2.spec.util.test.mock.FooChild; +import org.opendaylight.mdsal.binding.javav2.spec.util.test.mock.GroupingFoo; +import org.opendaylight.yangtools.yang.common.QName; + +public class BindingReflectionsTest { + + @Test + public void testBindingWithDummyObject() throws Exception { + assertEquals("Package name should be equal to string", "org.opendaylight.mdsal.gen.javav2.test.rev990939", + BindingReflections.getModelRootPackageName("org.opendaylight.mdsal.gen.javav2.test.rev990939")); + assertEquals("ModuleInfoClassName should be equal to string", "test.$YangModuleInfoImpl", + BindingReflections.getModuleInfoClassName("test")); + assertEquals("Module info should be empty Set", Collections.EMPTY_SET, BindingReflections.loadModuleInfos()); + assertFalse("Should not be RpcType", BindingReflections.isRpcOrActionType(TreeNode.class)); + assertFalse("Should not be AugmentationChild", BindingReflections.isAugmentationChild(TreeNode.class)); + assertTrue("Should be BindingClass", BindingReflections.isBindingClass(TreeNode.class)); + assertFalse("Should not be Notification", BindingReflections.isNotification(TreeNode.class)); + + assertNull(mock(TreeChildNode.class).treeParent()); + + assertEquals(GroupingFoo.class, BindingReflections.findHierarchicalParent(FooChild.class)); + + assertTrue(BindingReflections.isRpcOrActionMethod(TestImplementation.class.getDeclaredMethod("rpcMethodTest"))); + assertEquals(TestImplementation.class, BindingReflections.findAugmentationTarget(TestImplementation.class)); + + assertEquals(Object.class, BindingReflections + .resolveRpcOutputClass(TestImplementation.class.getDeclaredMethod("rpcMethodTest")).get()); + assertFalse(BindingReflections + .resolveRpcOutputClass(TestImplementation.class.getDeclaredMethod("rpcMethodTest2")).isPresent()); + + assertTrue(BindingReflections.getQName(TestImplementation.class).toString().equals("test")); + } + + @SuppressWarnings("rawtypes") + @Test(expected = UnsupportedOperationException.class) + public void testPrivateConstructor() throws Throwable { + assertFalse(BindingReflections.class.getDeclaredConstructor().isAccessible()); + final Constructor constructor = BindingReflections.class.getDeclaredConstructor(); + constructor.setAccessible(true); + try { + constructor.newInstance(); + } catch (final Exception e) { + throw e.getCause(); + } + } + + @SuppressWarnings({ "rawtypes", "unused" }) + private static final class TestImplementation extends BaseIdentity + implements Augmentation, Rpc { + + public static final QName QNAME = QName.create("test"); + + Future> rpcMethodTest() { + return null; + } + + Future rpcMethodTest2() { + return null; + } + + @Override + public void invoke(final Input input, final RpcCallback callback) { + } + } +} diff --git a/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/FooChild.java b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/FooChild.java new file mode 100644 index 0000000000..c6743c93bc --- /dev/null +++ b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/FooChild.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.javav2.spec.util.test.mock; + +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument; +import org.opendaylight.mdsal.binding.javav2.spec.structural.TreeChildNode; + +public interface FooChild extends TreeChildNode> { + +} diff --git a/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/GroupingFoo.java b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/GroupingFoo.java new file mode 100644 index 0000000000..795e234b73 --- /dev/null +++ b/binding2/mdsal-binding2-spec/test/main/java/org/opendaylight/mdsal/binding/javav2/spec/util/test/mock/GroupingFoo.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.javav2.spec.util.test.mock; + +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode; + +public interface GroupingFoo extends TreeNode { + +} diff --git a/binding2/mdsal-binding2-util/src/main/java/org/opendaylight/mdsal/binding/javav2/util/BindingMapping.java b/binding2/mdsal-binding2-util/src/main/java/org/opendaylight/mdsal/binding/javav2/util/BindingMapping.java index 6b3860e8c6..ef0067fc79 100644 --- a/binding2/mdsal-binding2-util/src/main/java/org/opendaylight/mdsal/binding/javav2/util/BindingMapping.java +++ b/binding2/mdsal-binding2-util/src/main/java/org/opendaylight/mdsal/binding/javav2/util/BindingMapping.java @@ -47,6 +47,8 @@ public final class BindingMapping { public static final String MODEL_BINDING_PROVIDER_CLASS_NAME = "$YangModelBindingProvider"; public static final String PATTERN_CONSTANT_NAME = "PATTERN_CONSTANTS"; public static final String MEMBER_PATTERN_LIST = "patterns"; + public static final String RPC_INPUT_SUFFIX = "Input"; + public static final String RPC_OUTPUT_SUFFIX = "Output"; private static final ThreadLocal PACKAGE_DATE_FORMAT = new ThreadLocal() {