final Class<?> parentClass, final Map<String, DataSchemaNode> getterToLeafSchema) {
final Map<Method, ValueNodeCodecContext> leaves = new HashMap<>();
for (final Method method : parentClass.getMethods()) {
- if (method.getParameterCount() == 0) {
+ // Only consider non-bridge methods with no arguments
+ if (method.getParameterCount() == 0 && !method.isBridge()) {
final DataSchemaNode schema = getterToLeafSchema.get(method.getName());
final ValueNodeCodecContext valueNode;
--- /dev/null
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.dom.codec.impl;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+import org.opendaylight.yang.gen.v1.mdsal426.norev.BarCont;
+import org.opendaylight.yang.gen.v1.mdsal426.norev.BarContBuilder;
+import org.opendaylight.yang.gen.v1.mdsal426.norev.BooleanCont;
+import org.opendaylight.yang.gen.v1.mdsal426.norev.BooleanContBuilder;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class SpecializingLeafrefTest extends AbstractBindingCodecTest {
+ private static final InstanceIdentifier<BooleanCont> BOOLEAN_CONT_II = InstanceIdentifier
+ .builder(BooleanCont.class).build();
+
+ private static final InstanceIdentifier<BarCont> BAR_CONT_II = InstanceIdentifier
+ .builder(BarCont.class).build();
+
+ @Test
+ public void specifiedBooleanLeafTest() {
+ final BooleanCont booleanCont = new BooleanContBuilder().setIsFoo(true).build();
+
+ final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> res = codecContext
+ .toNormalizedNode(BOOLEAN_CONT_II, booleanCont);
+
+ final BooleanCont booleanContBinding = (BooleanCont)codecContext
+ .fromNormalizedNode(res.getKey(), res.getValue()).getValue();
+
+ assertTrue(booleanContBinding.isIsFoo());
+ }
+
+ @Test
+ public void specifiedCommonLeafTest() {
+ final BarCont barCont = new BarContBuilder().setLeaf2("foo").build();
+
+ final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> res = codecContext
+ .toNormalizedNode(BAR_CONT_II, barCont);
+
+ final BarCont booleanContBinding = (BarCont)codecContext
+ .fromNormalizedNode(res.getKey(), res.getValue()).getValue();
+
+ assertEquals(booleanContBinding.getLeaf2(), "foo");
+ }
+
+ @Test
+ public void specifiedLeafListTest() {
+ final List<String> testList = ImmutableList.of("test");
+ final BarCont barCont = new BarContBuilder().setLeafList1(testList).build();
+
+ final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> res = codecContext
+ .toNormalizedNode(BAR_CONT_II, barCont);
+
+ final BarCont barContAfterConverting = (BarCont)codecContext
+ .fromNormalizedNode(res.getKey(), res.getValue()).getValue();
+
+ assertEquals(barContAfterConverting.getLeafList1(), testList);
+ }
+}
import static org.opendaylight.mdsal.binding.model.util.Types.STRING;
import static org.opendaylight.mdsal.binding.model.util.Types.classType;
import static org.opendaylight.mdsal.binding.model.util.Types.listTypeFor;
+import static org.opendaylight.mdsal.binding.model.util.Types.listTypeWildcard;
import static org.opendaylight.mdsal.binding.model.util.Types.listenableFutureTypeFor;
import static org.opendaylight.mdsal.binding.model.util.Types.mapTypeFor;
+import static org.opendaylight.mdsal.binding.model.util.Types.objectType;
import static org.opendaylight.mdsal.binding.model.util.Types.primitiveBooleanType;
import static org.opendaylight.mdsal.binding.model.util.Types.primitiveIntType;
import static org.opendaylight.mdsal.binding.model.util.Types.primitiveVoidType;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.binding.model.api.AccessModifier;
+import org.opendaylight.mdsal.binding.model.api.AnnotationType;
import org.opendaylight.mdsal.binding.model.api.Constant;
import org.opendaylight.mdsal.binding.model.api.DefaultType;
import org.opendaylight.mdsal.binding.model.api.Enumeration;
import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject;
import org.opendaylight.mdsal.binding.model.api.GeneratedType;
import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
+import org.opendaylight.mdsal.binding.model.api.MethodSignature;
import org.opendaylight.mdsal.binding.model.api.MethodSignature.ValueMechanics;
import org.opendaylight.mdsal.binding.model.api.ParameterizedType;
import org.opendaylight.mdsal.binding.model.api.Restrictions;
import org.slf4j.LoggerFactory;
abstract class AbstractTypeGenerator {
+ private enum InheritedGetter {
+ /**
+ * There is no matching method present.
+ */
+ NOT_PRESENT,
+ /**
+ * There is a matching method and its return type is resolved.
+ */
+ RESOLVED,
+ /**
+ * There is a matching method and its return type is unresolved -- i.e. in case of a leafref pointing to outside
+ * of its parent grouping.
+ */
+ UNRESOLVED;
+
+ /**
+ * We are using {@code @Override} annotation to indicate specialization, hence we can differentiate between
+ * resolved and unresolved methods based on them.
+ *
+ * @param annotations Method annotations
+ * @return Either {@link #RESOLVED} or {@link #UNRESOLVED}.
+ */
+ static InheritedGetter fromAnnotations(final List<AnnotationType> annotations) {
+ for (AnnotationType annotation : annotations) {
+ if (OVERRIDE_ANNOTATION.equals(annotation.getIdentifier())) {
+ return InheritedGetter.RESOLVED;
+ }
+ }
+ return InheritedGetter.UNRESOLVED;
+ }
+ }
+
private static final Logger LOG = LoggerFactory.getLogger(AbstractTypeGenerator.class);
private static final Splitter COLON_SPLITTER = Splitter.on(':');
private static final JavaTypeName DEPRECATED_ANNOTATION = JavaTypeName.create(Deprecated.class);
return genType;
}
- private void containerToGenType(final ModuleContext context, final GeneratedTypeBuilder parent,
+ private Type containerToGenType(final ModuleContext context, final GeneratedTypeBuilder parent,
final Type baseInterface, final ContainerSchemaNode node, final boolean inGrouping) {
final GeneratedTypeBuilder genType = processDataSchemaNode(context, baseInterface, node, inGrouping);
if (genType != null) {
actionsToGenType(context, genType, node, null, inGrouping);
notificationsToGenType(context, genType, node, null, inGrouping);
}
+ return genType;
}
- private void listToGenType(final ModuleContext context, final GeneratedTypeBuilder parent,
+ private GeneratedTypeBuilder listToGenType(final ModuleContext context, final GeneratedTypeBuilder parent,
final Type baseInterface, final ListSchemaNode node, final boolean inGrouping) {
final GeneratedTypeBuilder genType = processDataSchemaNode(context, baseInterface, node, inGrouping);
if (genType != null) {
typeBuildersToGenTypes(context, genType, keyTypeBuilder);
}
+ return genType;
}
private void processUsesAugments(final DataNodeContainer node, final ModuleContext context,
if (schemaNodes != null && parent != null) {
final Type baseInterface = childOf == null ? DATA_OBJECT : childOf(childOf);
for (final DataSchemaNode schemaNode : schemaNodes) {
- if (!schemaNode.isAugmenting() && !schemaNode.isAddedByUses()) {
+ if (!schemaNode.isAugmenting()) {
addSchemaNodeToBuilderAsMethod(context, schemaNode, parent, baseInterface, inGrouping);
}
}
return parent;
}
+ private void addSchemaNodeToBuilderAsMethod(final ModuleContext context, final DataSchemaNode schemaNode,
+ final GeneratedTypeBuilder parent, final Type baseInterface, final boolean inGrouping) {
+ if (!schemaNode.isAddedByUses()) {
+ addUnambiguousNodeToBuilderAsMethod(context, schemaNode, parent, baseInterface, inGrouping);
+ } else if (needGroupingMethodOverride(schemaNode, parent)) {
+ addLeafrefNodeToBuilderAsMethod(context, (TypedDataSchemaNode) schemaNode, parent, inGrouping);
+ }
+ }
+
+ /**
+ * Determine whether a particular node, added from a grouping, needs to be reflected as a method. This method
+ * performs a check for {@link TypedDataSchemaNode} and defers to
+ * {@link #needGroupingMethodOverride(TypedDataSchemaNode, GeneratedTypeBuilder)}.
+ *
+ * @param parent {@code GeneratedType} where method should be defined
+ * @param child node from which method should be defined
+ * @return True if an override method is needed
+ */
+ private static boolean needGroupingMethodOverride(final DataSchemaNode child, final GeneratedTypeBuilder parent) {
+ return child instanceof TypedDataSchemaNode && needGroupingMethodOverride((TypedDataSchemaNode) child, parent);
+ }
+
+ /**
+ * Determine whether a particular {@link TypedDataSchemaNode}, added from a grouping, needs to be reflected as a
+ * method.
+ *
+ * <p>
+ * This check would be super easy were it not for relative leafrefs in groupings. These can legally point outside of
+ * the grouping -- which means we cannot inherently cannot determine their type, as they are polymorphic.
+ *
+ * @param parent {@code GeneratedType} where method should be defined
+ * @param child node from which method should be defined
+ * @return True if an override method is needed
+ */
+ private static boolean needGroupingMethodOverride(final TypedDataSchemaNode child,
+ final GeneratedTypeBuilder parent) {
+ // This is a child added through uses and it is is data-bearing, i.e. leaf or leaf-list. Under normal
+ // circumstances we would not bother, but if the target type is a leafref we have more checks to do.
+ return isRelativeLeafref(child.getType()) && needMethodDefinition(child.getQName().getLocalName(), parent);
+ }
+
+ private static boolean needMethodDefinition(final String localName, final GeneratedTypeBuilder parent) {
+ for (Type implementsType : parent.getImplementsTypes()) {
+ if (implementsType instanceof GeneratedType) {
+ final InheritedGetter precision = findInheritedGetter(localName, (GeneratedType) implementsType);
+ switch (precision) {
+ case RESOLVED:
+ return false;
+ case UNRESOLVED:
+ return true;
+ default:
+ // No-op
+ }
+ }
+ }
+ throw new IllegalStateException(localName + " should be present in " + parent
+ + " or in one of its ancestors as a getter");
+ }
+
+ private static InheritedGetter findInheritedGetter(final String localName, final GeneratedType impl) {
+ return findInheritedGetter(impl, BindingMapping.getGetterMethodName(localName));
+ }
+
+ private static InheritedGetter findInheritedGetter(final GeneratedType type, final String getter) {
+ for (MethodSignature method : type.getMethodDefinitions()) {
+ if (getter.equals(method.getName())) {
+ return InheritedGetter.fromAnnotations(method.getAnnotations());
+ }
+ }
+
+ // Try to find the method in other interfaces we implement
+ for (Type implementsType : type.getImplements()) {
+ if (implementsType instanceof GeneratedType) {
+ final InheritedGetter found = findInheritedGetter((GeneratedType) implementsType, getter);
+ if (found != InheritedGetter.NOT_PRESENT) {
+ return found;
+ }
+ }
+ }
+ return InheritedGetter.NOT_PRESENT;
+ }
+
+ private static boolean isRelativeLeafref(final TypeDefinition<? extends TypeDefinition<?>> type) {
+ return type instanceof LeafrefTypeDefinition && !((LeafrefTypeDefinition) type).getPathStatement().isAbsolute();
+ }
+
/**
* Adds the methods to <code>typeBuilder</code> what represents subnodes of node for which <code>typeBuilder</code>
* was created.
}
/**
- * Adds to <code>typeBuilder</code> a method which is derived from <code>schemaNode</code>.
+ * Adds to {@code typeBuilder} a method which is derived from {@code schemaNode}.
*
* @param node data schema node which is added to <code>typeBuilder</code> as a method
* @param typeBuilder generated type builder to which is <code>schemaNode</code> added as a method.
* @param childOf parent type
* @param module current module
*/
- private void addSchemaNodeToBuilderAsMethod(final ModuleContext context, final DataSchemaNode node,
- final GeneratedTypeBuilder typeBuilder, final Type baseInterface, final boolean inGrouping) {
+ private void addLeafrefNodeToBuilderAsMethod(final ModuleContext context, final TypedDataSchemaNode node,
+ final GeneratedTypeBuilder typeBuilder, final boolean inGrouping) {
if (node != null && typeBuilder != null) {
if (node instanceof LeafSchemaNode) {
- resolveLeafSchemaNodeAsMethod(typeBuilder, (LeafSchemaNode) node, context, inGrouping);
+ resolveLeafLeafrefNodeAsMethod(typeBuilder, (LeafSchemaNode) node, context, inGrouping);
} else if (node instanceof LeafListSchemaNode) {
- resolveLeafListSchemaNode(typeBuilder, (LeafListSchemaNode) node, context, inGrouping);
- } else if (node instanceof ContainerSchemaNode) {
- containerToGenType(context, typeBuilder, baseInterface, (ContainerSchemaNode) node, inGrouping);
- } else if (node instanceof ListSchemaNode) {
- listToGenType(context, typeBuilder, baseInterface, (ListSchemaNode) node, inGrouping);
- } else if (node instanceof ChoiceSchemaNode) {
- choiceToGeneratedType(context, typeBuilder, (ChoiceSchemaNode) node, inGrouping);
- } else if (node instanceof AnyxmlSchemaNode || node instanceof AnydataSchemaNode) {
- opaqueToGeneratedType(context, typeBuilder, node);
+ resolveLeafListLeafrefNode(typeBuilder, (LeafListSchemaNode) node, context, inGrouping);
} else {
- LOG.debug("Unable to add schema node {} as method in {}: unsupported type of node.", node.getClass(),
- typeBuilder.getFullyQualifiedName());
+ logUnableToAddNodeAsMethod(node, typeBuilder);
}
}
}
+ private void addUnambiguousNodeToBuilderAsMethod(final ModuleContext context, final DataSchemaNode node,
+ final GeneratedTypeBuilder typeBuilder, final Type baseInterface, final boolean inGrouping) {
+ if (node instanceof LeafSchemaNode) {
+ resolveUnambiguousLeafNodeAsMethod(typeBuilder, (LeafSchemaNode) node, context, inGrouping);
+ } else if (node instanceof LeafListSchemaNode) {
+ resolveUnambiguousLeafListNode(typeBuilder, (LeafListSchemaNode) node, context, inGrouping);
+ } else if (node instanceof ContainerSchemaNode) {
+ containerToGenType(context, typeBuilder, baseInterface, (ContainerSchemaNode) node, inGrouping);
+ } else if (node instanceof ListSchemaNode) {
+ listToGenType(context, typeBuilder, baseInterface, (ListSchemaNode) node, inGrouping);
+ } else if (node instanceof ChoiceSchemaNode) {
+ choiceToGeneratedType(context, typeBuilder, (ChoiceSchemaNode) node, inGrouping);
+ } else if (node instanceof AnyxmlSchemaNode || node instanceof AnydataSchemaNode) {
+ opaqueToGeneratedType(context, typeBuilder, node);
+ } else {
+ logUnableToAddNodeAsMethod(node, typeBuilder);
+ }
+ }
+
+ private static void logUnableToAddNodeAsMethod(final DataSchemaNode node, final GeneratedTypeBuilder typeBuilder) {
+ LOG.debug("Unable to add schema node {} as method in {}: unsupported type of node.", node.getClass(),
+ typeBuilder.getFullyQualifiedName());
+ }
+
/**
* Converts <code>choiceNode</code> to the list of generated types for choice and its cases. The package names
* for choice and for its cases are created as concatenation of the module package (<code>basePackageName</code>)
}
}
+ private Type resolveLeafLeafrefNodeAsMethod(final GeneratedTypeBuilder typeBuilder, final LeafSchemaNode leaf,
+ final ModuleContext context, final boolean inGrouping) {
+ final Module parentModule = findParentModule(schemaContext, leaf);
+ final Type returnType = resolveReturnType(typeBuilder, leaf, context, parentModule, inGrouping);
+ if (returnType != null && isTypeSpecified(returnType)) {
+ processContextRefExtension(leaf, constructOverrideGetter(typeBuilder, returnType, leaf), parentModule);
+ }
+ return returnType;
+ }
+
/**
- * Converts <code>leaf</code> to the getter method which is added to <code>typeBuilder</code>.
+ * Converts {@code leafList} to the getter method which is added to {@code typeBuilder}.
*
- * @param typeBuilder generated type builder to which is added getter method as <code>leaf</code> mapping
- * @param leaf leaf schema node which is mapped as getter method which is added to <code>typeBuilder</code>
- * @param module Module in which type was defined
- * @return boolean value
- * <ul>
- * <li>false - if <code>leaf</code> or <code>typeBuilder</code> are
- * null</li>
- * <li>true - in other cases</li>
- * </ul>
+ * @param context module in which type was defined
+ * @param typeBuilder generated type builder to which is added getter method as {@code leafList} mapping
+ * @param leafList leaf-list schema node which is mapped as getter method which is added to {@code typeBuilder}
*/
- private Type resolveLeafSchemaNodeAsMethod(final GeneratedTypeBuilder typeBuilder, final LeafSchemaNode leaf,
+ private void resolveLeafListNodeAsMethod(final GeneratedTypeBuilder typeBuilder, final LeafListSchemaNode leafList,
final ModuleContext context, final boolean inGrouping) {
- if (leaf == null || typeBuilder == null || leaf.isAddedByUses()) {
- return null;
+ if (!leafList.isAddedByUses()) {
+ resolveUnambiguousLeafListNode(typeBuilder, leafList, context, inGrouping);
+ } else if (needGroupingMethodOverride(leafList, typeBuilder)) {
+ resolveLeafListLeafrefNode(typeBuilder, leafList, context, inGrouping);
}
+ }
+ private Type resolveUnambiguousLeafNodeAsMethod(final GeneratedTypeBuilder typeBuilder, final LeafSchemaNode leaf,
+ final ModuleContext context, final boolean inGrouping) {
final Module parentModule = findParentModule(schemaContext, leaf);
+ final Type returnType = resolveReturnType(typeBuilder, leaf, context, parentModule, inGrouping);
+ if (returnType != null) {
+ processContextRefExtension(leaf, constructGetter(typeBuilder, returnType, leaf), parentModule);
+ }
+ return returnType;
+ }
+
+ private Type resolveReturnType(final GeneratedTypeBuilder typeBuilder, final LeafSchemaNode leaf,
+ final ModuleContext context, final Module parentModule, final boolean inGrouping) {
Type returnType = null;
final TypeDefinition<?> typeDef = CompatUtils.compatType(leaf);
typeProvider.putReferencedType(leaf.getPath(), returnType);
}
- final MethodSignatureBuilder getter = constructGetter(typeBuilder, returnType, leaf);
- processContextRefExtension(leaf, getter, parentModule);
return returnType;
}
+ private static boolean isTypeSpecified(final Type type) {
+ return !type.equals(objectType());
+ }
+
private static TypeDefinition<?> getBaseOrDeclaredType(final TypeDefinition<?> typeDef) {
// Returns DerivedType in case of new parser.
final TypeDefinition<?> baseType = typeDef.getBaseType();
return true;
}
+ private void resolveLeafListLeafrefNode(final GeneratedTypeBuilder typeBuilder, final LeafListSchemaNode node,
+ final ModuleContext context, final boolean inGrouping) {
+ final Type returnType = resolveLeafListItemsType(typeBuilder, node, context, inGrouping,
+ findParentModule(schemaContext, node));
+ if (isTypeSpecified(returnType)) {
+ constructOverrideGetter(typeBuilder, listTypeFor(returnType), node);
+ }
+ }
+
/**
* Converts <code>node</code> leaf list schema node to getter method of <code>typeBuilder</code>.
*
+ * @param context module
* @param typeBuilder generated type builder to which is <code>node</code> added as getter method
* @param node leaf list schema node which is added to <code>typeBuilder</code> as getter method
- * @param module module
- * @return boolean value
- * <ul>
- * <li>true - if <code>node</code>, <code>typeBuilder</code>,
- * nodeName equal null or <code>node</code> is added by <i>uses</i></li>
- * <li>false - other cases</li>
- * </ul>
*/
- private boolean resolveLeafListSchemaNode(final GeneratedTypeBuilder typeBuilder, final LeafListSchemaNode node,
+ private Type resolveUnambiguousLeafListNode(final GeneratedTypeBuilder typeBuilder, final LeafListSchemaNode node,
final ModuleContext context, final boolean inGrouping) {
- if (node == null || typeBuilder == null || node.isAddedByUses()) {
- return false;
+ final Module parentModule = findParentModule(schemaContext, node);
+ final Type listItemsType = resolveLeafListItemsType(typeBuilder, node, context, inGrouping, parentModule);
+ final Type returnType;
+ if (listItemsType.equals(objectType())) {
+ returnType = listTypeWildcard();
+ } else {
+ returnType = listTypeFor(listItemsType);
}
- final QName nodeName = node.getQName();
+ constructGetter(typeBuilder, returnType, node);
- final TypeDefinition<?> typeDef = node.getType();
- final Module parentModule = findParentModule(schemaContext, node);
+ return returnType;
+ }
- Type returnType = null;
+ private Type resolveLeafListItemsType(final GeneratedTypeBuilder typeBuilder, final LeafListSchemaNode node,
+ final ModuleContext context, final boolean inGrouping, final Module parentModule) {
+ final Type returnType;
+ final TypeDefinition<? extends TypeDefinition<?>> typeDef = node.getType();
if (typeDef.getBaseType() == null) {
if (typeDef instanceof EnumTypeDefinition) {
final EnumTypeDefinition enumTypeDef = (EnumTypeDefinition) typeDef;
- returnType = resolveInnerEnumFromTypeDefinition(enumTypeDef, nodeName, typeBuilder, context);
+ returnType = resolveInnerEnumFromTypeDefinition(enumTypeDef, node.getQName(), typeBuilder, context);
typeProvider.putReferencedType(node.getPath(), returnType);
} else if (typeDef instanceof UnionTypeDefinition) {
- final UnionTypeDefinition unionDef = (UnionTypeDefinition)typeDef;
+ final UnionTypeDefinition unionDef = (UnionTypeDefinition) typeDef;
returnType = addTOToTypeBuilder(unionDef, typeBuilder, node, parentModule);
} else if (typeDef instanceof BitsTypeDefinition) {
- final GeneratedTOBuilder genTOBuilder = addTOToTypeBuilder((BitsTypeDefinition)typeDef, typeBuilder,
- node, parentModule);
+ final GeneratedTOBuilder genTOBuilder = addTOToTypeBuilder((BitsTypeDefinition) typeDef, typeBuilder,
+ node, parentModule);
returnType = genTOBuilder.build();
} else {
final Restrictions restrictions = BindingGeneratorUtil.getRestrictions(typeDef);
returnType = typeProvider.javaTypeForSchemaDefinitionType(typeDef, node, restrictions, inGrouping);
addPatternConstant(typeBuilder, node.getQName().getLocalName(), restrictions.getPatternConstraints());
}
-
- constructGetter(typeBuilder, listTypeFor(returnType), node);
- return true;
+ return returnType;
}
private Type createReturnTypeForUnion(final GeneratedTOBuilder genTOBuilder, final UnionTypeDefinition typeDef,
return getMethod;
}
+ private MethodSignatureBuilder constructOverrideGetter(final GeneratedTypeBuilder interfaceBuilder,
+ final Type returnType, final SchemaNode node) {
+ final MethodSignatureBuilder getter = constructGetter(interfaceBuilder, returnType, node);
+ getter.addAnnotation(OVERRIDE_ANNOTATION);
+ return getter;
+ }
+
private static void constructNonnull(final GeneratedTypeBuilder interfaceBuilder, final Type returnType,
final ListSchemaNode node) {
final MethodSignatureBuilder getMethod = interfaceBuilder.addMethod(
if (schemaNode instanceof LeafSchemaNode) {
final LeafSchemaNode leaf = (LeafSchemaNode) schemaNode;
final String leafName = leaf.getQName().getLocalName();
- Type type = resolveLeafSchemaNodeAsMethod(typeBuilder, leaf, context, inGrouping);
+ final Type type;
+ if (!schemaNode.isAddedByUses()) {
+ type = resolveUnambiguousLeafNodeAsMethod(typeBuilder, leaf, context, inGrouping);
+ } else if (needGroupingMethodOverride(leaf, typeBuilder)) {
+ type = resolveLeafLeafrefNodeAsMethod(typeBuilder, leaf, context, inGrouping);
+ } else {
+ type = null;
+ }
+
if (listKeys.contains(leafName)) {
if (type == null) {
resolveLeafSchemaNodeAsProperty(genTOBuilder, leaf, true);
resolveLeafSchemaNodeAsProperty(genTOBuilder, leaf, type, true);
}
}
+ } else if (schemaNode instanceof LeafListSchemaNode) {
+ resolveLeafListNodeAsMethod(typeBuilder, (LeafListSchemaNode) schemaNode, context, inGrouping);
} else if (!schemaNode.isAddedByUses()) {
- if (schemaNode instanceof LeafListSchemaNode) {
- resolveLeafListSchemaNode(typeBuilder, (LeafListSchemaNode) schemaNode, context, inGrouping);
- } else if (schemaNode instanceof ContainerSchemaNode) {
- containerToGenType(context, typeBuilder, childOf(typeBuilder),
- (ContainerSchemaNode) schemaNode, inGrouping);
+ if (schemaNode instanceof ContainerSchemaNode) {
+ containerToGenType(context, typeBuilder, childOf(typeBuilder), (ContainerSchemaNode) schemaNode,
+ inGrouping);
} else if (schemaNode instanceof ChoiceSchemaNode) {
choiceToGeneratedType(context, typeBuilder, (ChoiceSchemaNode) schemaNode, inGrouping);
} else if (schemaNode instanceof ListSchemaNode) {
CacheBuilder.newBuilder().weakKeys().build(TYPE_LOADER);
public static final @NonNull ConcreteType BOOLEAN = typeForClass(Boolean.class);
- public static final @NonNull ConcreteType STRING = typeForClass(String.class);
- public static final @NonNull ConcreteType VOID = typeForClass(Void.class);
public static final @NonNull ConcreteType BYTE_ARRAY = typeForClass(byte[].class);
public static final @NonNull ConcreteType CLASS = typeForClass(Class.class);
+ public static final @NonNull ConcreteType STRING = typeForClass(String.class);
+ public static final @NonNull ConcreteType VOID = typeForClass(Void.class);
private static final @NonNull ConcreteType BUILDER = typeForClass(Builder.class);
private static final @NonNull ConcreteType LIST_TYPE = typeForClass(List.class);
private static final @NonNull ConcreteType PRIMITIVE_VOID = typeForClass(void.class);
private static final @NonNull ConcreteType SERIALIZABLE = typeForClass(Serializable.class);
private static final @NonNull ConcreteType SET_TYPE = typeForClass(Set.class);
+ private static final @NonNull ParameterizedType LIST_TYPE_WILDCARD = parameterizedTypeFor(LIST_TYPE);
/**
* It is not desirable to create instance of this class.
return parameterizedTypeFor(LIST_TYPE, valueType);
}
+ /**
+ * Returns an instance of {@link ParameterizedType} describing the typed {@link List}<?>.
+ *
+ * @return Description of type instance of List
+ */
+ public static @NonNull ParameterizedType listTypeWildcard() {
+ return LIST_TYPE_WILDCARD;
+ }
+
public static boolean isListType(final ParameterizedType type) {
return LIST_TYPE.equals(type.getRawType());
}
return new WildcardTypeImpl(identifier);
}
+ public static boolean strictTypeEquals(final Type type1, final Type type2) {
+ if (!type1.equals(type2)) {
+ return false;
+ }
+ if (type1 instanceof ParameterizedType) {
+ if (type2 instanceof ParameterizedType) {
+ return Arrays.equals(((ParameterizedType) type1).getActualTypeArguments(),
+ ((ParameterizedType) type2).getActualTypeArguments());
+ }
+ return false;
+ }
+ return !(type2 instanceof ParameterizedType);
+ }
+
public static @Nullable String getOuterClassName(final Type valueType) {
return valueType.getIdentifier().immediatelyEnclosingClass().map(Object::toString).orElse(null);
}
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import org.opendaylight.mdsal.binding.model.api.AbstractBaseType;
import org.opendaylight.mdsal.binding.model.api.AnnotationType;
import org.opendaylight.mdsal.binding.model.api.Constant;
}
}
+ protected static <T> Set<T> makeUnmodifiable(final Set<T> set) {
+ switch (set.size()) {
+ case 0:
+ return Collections.emptySet();
+ case 1:
+ return Collections.singleton(set.iterator().next());
+ default:
+ return Collections.unmodifiableSet(set);
+ }
+ }
+
private static List<GeneratedType> toUnmodifiableEnclosedTypes(
final List<GeneratedTypeBuilder> enclosedGenTypeBuilders,
final List<GeneratedTOBuilder> enclosedGenTOBuilders) {
return makeUnmodifiable(methods);
}
+ protected final Set<MethodSignature> toUnmodifiableMethods(final Set<MethodSignatureBuilder> getters) {
+ final Set<MethodSignature> methods = new HashSet<>(getters.size());
+ for (final MethodSignatureBuilder methodBuilder : getters) {
+ methods.add(methodBuilder.toInstance(this));
+ }
+ return makeUnmodifiable(methods);
+ }
+
protected final List<Enumeration> toUnmodifiableEnumerations(final List<EnumBuilder> enumBuilders) {
final List<Enumeration> enums = new ArrayList<>(enumBuilders.size());
for (final EnumBuilder enumBuilder : enumBuilders) {
}
return null
}
+
+ package static def hasNonDefaultMethods(GeneratedType type) {
+ !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
+ }
+
+ package static def nonDefaultMethods(GeneratedType type) {
+ type.methodDefinitions.filter([def | !def.isDefault])
+ }
}
"_" + property.name
}
- final protected static def propertyNameFromGetter(MethodSignature getter) {
- var String prefix;
- if (BindingMapping.isGetterMethodName(getter.name)) {
- prefix = BindingMapping.GETTER_PREFIX
- } else if (BindingMapping.isNonnullMethodName(getter.name)) {
- prefix = BindingMapping.NONNULL_PREFIX
- } else {
- throw new IllegalArgumentException(getter + " is not a getter")
- }
- return getter.name.substring(prefix.length).toFirstLower;
- }
-
/**
* Template method which generates the getter method for <code>field</code>
*
import java.util.Collection
import java.util.List
+import java.util.Optional
import org.opendaylight.mdsal.binding.model.api.AnnotationType
import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
import org.opendaylight.mdsal.binding.model.api.GeneratedType
+import org.opendaylight.mdsal.binding.model.api.MethodSignature
import org.opendaylight.mdsal.binding.model.api.MethodSignature.ValueMechanics
import org.opendaylight.mdsal.binding.model.api.Type
import org.opendaylight.mdsal.binding.model.util.Types
import org.opendaylight.yangtools.yang.binding.AbstractAugmentable
class BuilderImplTemplate extends AbstractBuilderTemplate {
- val Type builderType;
+ val BuilderTemplate builder;
new(BuilderTemplate builder, GeneratedType type) {
super(builder.javaType.getEnclosedType(type.identifier), type, builder.targetType, builder.properties,
builder.augmentType, builder.keyType)
- this.builderType = builder.type
+ this.builder = builder
}
override body() '''
«generateFields(true)»
- «generateCopyConstructor(builderType, type)»
+ «generateCopyConstructor(builder.type, type)»
- «generateGetters(true)»
+ «generateGetters()»
«generateHashCode()»
return generateAnnotation(ann)
}
+ def private generateGetters() '''
+ «IF keyType !== null»
+ @«OVERRIDE.importedName»
+ public «keyType.importedName» «BindingMapping.IDENTIFIABLE_KEY_NAME»() {
+ return key;
+ }
+
+ «ENDIF»
+ «IF !properties.empty»
+ «FOR field : properties SEPARATOR '\n'»
+ «field.getterMethod»
+ «ENDFOR»
+ «ENDIF»
+ '''
+
+ private static def Optional<MethodSignature> findGetter(GeneratedType implType, String getterName) {
+ val getter = getterByName(implType.nonDefaultMethods, getterName);
+ if (getter.isPresent) {
+ return getter;
+ }
+ for (ifc : implType.implements) {
+ if (ifc instanceof GeneratedType) {
+ val getterImpl = findGetter(ifc, getterName)
+ if (getterImpl.isPresent) {
+ return (getterImpl)
+ }
+ }
+ }
+ return Optional.empty
+ }
+
+ override getterMethod(GeneratedProperty field) '''
+ @«OVERRIDE.importedName»
+ public «field.returnType.importedName» «field.getterMethodName»() {
+ «val fieldName = field.fieldName»
+ «IF field.returnType.name.endsWith("[]")»
+ return «fieldName» == null ? null : «fieldName».clone();
+ «ELSE»
+ return «fieldName»;
+ «ENDIF»
+ }
+ '''
+
+ package def findGetter(String getterName) {
+ val ownGetter = getterByName(type.nonDefaultMethods, getterName);
+ if (ownGetter.isPresent) {
+ return ownGetter.get;
+ }
+ for (ifc : type.implements) {
+ if (ifc instanceof GeneratedType) {
+ val getter = findGetter(ifc, getterName)
+ if (getter.isPresent) {
+ return (getter.get)
+ }
+ }
+ }
+ throw new IllegalStateException(
+ String.format("%s should be present in %s type or in one of its ancestors as getter",
+ getterName.propertyNameFromGetter, type));
+ }
+
/**
* Template method which generates the method <code>hashCode()</code>.
*
import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME
import com.google.common.collect.ImmutableList
+import com.google.common.collect.Sets
import java.util.ArrayList
import java.util.Collection
import java.util.HashSet
import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
import org.opendaylight.mdsal.binding.model.api.GeneratedType
import org.opendaylight.mdsal.binding.model.api.JavaTypeName
+import org.opendaylight.mdsal.binding.model.api.MethodSignature;
import org.opendaylight.mdsal.binding.model.api.ParameterizedType
import org.opendaylight.mdsal.binding.model.api.Type
import org.opendaylight.mdsal.binding.model.util.TypeConstants
import org.opendaylight.mdsal.binding.model.util.Types
import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
import org.opendaylight.yangtools.concepts.Builder
+import com.google.common.collect.ImmutableSet
/**
* Template for generating JAVA builder classes.
static val BUILDER = JavaTypeName.create(Builder)
+ val BuilderImplTemplate implTemplate
+
/**
* Constructs new instance of this class.
* @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
*/
new(GeneratedType genType, GeneratedType targetType, Type keyType) {
super(genType, targetType, keyType)
+ implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0))
}
override isLocalInnerClass(JavaTypeName name) {
return new «type.enclosedTypes.get(0).importedName»(this);
}
- «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
+ «implTemplate.body»
}
'''
def private generateConstructorsFromIfcs() '''
public «type.name»() {
}
+
«IF (!(targetType instanceof GeneratedTransferObject))»
- «FOR impl : targetType.implements»
+ «FOR impl : targetType.implements SEPARATOR "\n"»
«generateConstructorFromIfc(impl)»
«ENDFOR»
«ENDIF»
«val ifc = implementedIfc as GeneratedType»
«FOR getter : ifc.nonDefaultMethods»
«IF BindingMapping.isGetterMethodName(getter.name)»
- this._«getter.propertyNameFromGetter» = arg.«getter.name»();
+ «val propertyName = getter.propertyNameFromGetter»
+ «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
«ENDIF»
«ENDFOR»
«FOR impl : ifc.implements»
- «printConstructorPropertySetter(impl)»
+ «printConstructorPropertySetter(impl, getSpecifiedGetters(ifc))»
+ «ENDFOR»
+ «ENDIF»
+ '''
+
+ def private Object printConstructorPropertySetter(Type implementedIfc, Set<MethodSignature> alreadySetProperties) '''
+ «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
+ «val ifc = implementedIfc as GeneratedType»
+ «FOR getter : ifc.nonDefaultMethods»
+ «IF BindingMapping.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
+ «val propertyName = getter.propertyNameFromGetter»
+ «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
+ «ENDIF»
+ «ENDFOR»
+ «FOR descendant : ifc.implements»
+ «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
«ENDFOR»
«ENDIF»
'''
+ def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
+ val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
+ for (MethodSignature method : type.getMethodDefinitions()) {
+ if (method.hasOverrideAnnotation) {
+ setBuilder.add(method)
+ }
+ }
+ return setBuilder.build()
+ }
+
/**
* Generate 'fieldsFrom' method to set builder properties based on type of given argument.
*/
* </ul>
*
* @param arg grouping object
- * @throws IllegalArgumentException if given argument is none of valid types
+ * @throws IllegalArgumentException if given argument is none of valid types or has property with incompatible value
*/
'''
«IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
«val ifc = implementedIfc as GeneratedType»
«FOR getter : ifc.nonDefaultMethods»
- «IF BindingMapping.isGetterMethodName(getter.name)»
- this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
+ «IF BindingMapping.isGetterMethodName(getter.name) && !hasOverrideAnnotation(getter)»
+ «printPropertySetter(getter, '''((«ifc.fullyQualifiedName»)arg).«getter.name»()''', getter.propertyNameFromGetter)»;
«ENDIF»
«ENDFOR»
«ENDIF»
'''
+ def private printPropertySetter(MethodSignature getter, String retrieveProperty, String propertyName) {
+ val ownGetter = implTemplate.findGetter(getter.name)
+ val ownGetterType = ownGetter.returnType
+ if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
+ return "this._" + propertyName + " = " + retrieveProperty
+ }
+ if (Types.isListType(ownGetterType)) {
+ val itemType = (ownGetterType as ParameterizedType).actualTypeArguments.get(0)
+ return '''
+ this._«propertyName» = «CODEHELPERS.importedName».checkListFieldCast(«itemType.fullyQualifiedName».class, "«propertyName»", «retrieveProperty»)'''
+ }
+ return '''
+ this._«propertyName» = «CODEHELPERS.importedName».checkFieldCast(«ownGetter.returnType.fullyQualifiedName».class, "«propertyName»", «retrieveProperty»)'''
+ }
+
private def List<Type> getBaseIfcs(GeneratedType type) {
val List<Type> baseIfcs = new ArrayList();
for (ifc : type.implements) {
}
'''
}
-
- private static def hasNonDefaultMethods(GeneratedType type) {
- !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
- }
-
- private static def nonDefaultMethods(GeneratedType type) {
- type.methodDefinitions.filter([def | !def.isDefault])
- }
}
import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.getGetterMethodForNonnull
import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.isGetterMethodName
import static extension org.opendaylight.mdsal.binding.spec.naming.BindingMapping.isNonnullMethodName
-import static org.opendaylight.mdsal.binding.model.util.Types.STRING;
+import static org.opendaylight.mdsal.binding.model.util.Types.BOOLEAN
+import static org.opendaylight.mdsal.binding.model.util.Types.STRING
import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_EQUALS_NAME
import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_HASHCODE_NAME
«ENDIF»
'''
+ def private generateAccessorAnnotations(MethodSignature method) '''
+ «val annotations = method.annotations»
+ «IF annotations !== null && !annotations.empty»
+ «FOR annotation : annotations»
+ «IF method.returnType != BOOLEAN || !(annotation.identifier == OVERRIDE)»
+ «annotation.generateAnnotation»
+ «ENDIF»
+ «ENDFOR»
+ «ENDIF»
+ '''
+
/**
* Template method which generates the interface name declaration.
*
def private generateAccessorMethod(MethodSignature method) {
return '''
«accessorJavadoc(method, "{@code null}")»
- «method.annotations.generateAnnotations»
+ «method.generateAccessorAnnotations»
«method.returnType.nullableType» «method.name»();
'''
}
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.xtext.xbase.lib.StringExtensions;
+import org.opendaylight.mdsal.binding.model.api.AnnotationType;
import org.opendaylight.mdsal.binding.model.api.ConcreteType;
import org.opendaylight.mdsal.binding.model.api.DefaultType;
import org.opendaylight.mdsal.binding.model.api.GeneratedProperty;
for (Type implementedIfc : implementedIfcs) {
if (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject)) {
final GeneratedType ifc = (GeneratedType) implementedIfc;
- methods.addAll(ifc.getMethodDefinitions());
+ addImplMethods(methods, ifc);
final ParameterizedType t = collectImplementedMethods(type, methods, ifc.getImplements());
if (t != null && augmentType == null) {
return augmentType;
}
+ private static void addImplMethods(final Set<MethodSignature> methods, final GeneratedType implType) {
+ for (final MethodSignature implMethod : implType.getMethodDefinitions()) {
+ if (hasOverrideAnnotation(implMethod)) {
+ methods.add(implMethod);
+ } else {
+ final String implMethodName = implMethod.getName();
+ if (BindingMapping.isGetterMethodName(implMethodName)
+ && getterByName(methods, implMethodName).isEmpty()) {
+
+ methods.add(implMethod);
+ }
+ }
+ }
+ }
+
+ protected static Optional<MethodSignature> getterByName(final Iterable<MethodSignature> methods,
+ final String implMethodName) {
+ for (MethodSignature method : methods) {
+ final String methodName = method.getName();
+ if (BindingMapping.isGetterMethodName(methodName)) {
+ if (isSameProperty(method.getName(), implMethodName)) {
+ return Optional.of(method);
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ protected static String propertyNameFromGetter(final MethodSignature getter) {
+ return propertyNameFromGetter(getter.getName());
+ }
+
+ protected static String propertyNameFromGetter(final String getterName) {
+ final String prefix;
+ if (BindingMapping.isGetterMethodName(getterName)) {
+ prefix = BindingMapping.GETTER_PREFIX;
+ } else if (BindingMapping.isNonnullMethodName(getterName)) {
+ prefix = BindingMapping.NONNULL_PREFIX;
+ } else {
+ throw new IllegalArgumentException(getterName + " is not a getter");
+ }
+ return StringExtensions.toFirstLower(getterName.substring(prefix.length()));
+ }
+
+ /**
+ * Check whether specified method has an attached annotation which corresponds to {@code @Override}.
+ *
+ * @param method Method to examine
+ * @return True if there is an override annotation
+ */
+ static boolean hasOverrideAnnotation(final MethodSignature method) {
+ for (final AnnotationType annotation : method.getAnnotations()) {
+ if (OVERRIDE.equals(annotation.getIdentifier())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isSameProperty(final String getterName1, final String getterName2) {
+ return propertyNameFromGetter(getterName1).equals(propertyNameFromGetter(getterName2));
+ }
+
/**
* Creates set of generated property instances from getter <code>methods</code>.
*
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
final MethodSignature methSign = mock(MethodSignature.class);
doReturn(methodeName).when(methSign).getName();
final Type methType = mock(Type.class);
+ when(methType.getFullyQualifiedName()).thenCallRealMethod();
doReturn(TYPE_NAME).when(methType).getIdentifier();
doReturn(TEST).when(methType).getName();
doReturn(methType).when(methSign).getReturnType();
CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
}
+ @Test
+ public void testMdsal426() throws Exception {
+ final File sourcesOutputDir = CompilationTestUtils.generatorOutput("mdsal426");
+ final File compiledOutputDir = CompilationTestUtils.compilerOutput("mdsal426");
+ generateTestSources("/compilation/mdsal426", sourcesOutputDir);
+ CompilationTestUtils.testCompilation(sourcesOutputDir, compiledOutputDir);
+ CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
+ }
+
@Test
public void testMdsal529() throws Exception {
final File sourcesOutputDir = CompilationTestUtils.generatorOutput("mdsal529");
CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
}
+ public void testMdsal533() throws Exception {
+ final File sourcesOutputDir = CompilationTestUtils.generatorOutput("mdsal533");
+ final File compiledOutputDir = CompilationTestUtils.compilerOutput("mdsal533");
+ generateTestSources("/compilation/mdsal533", sourcesOutputDir);
+ CompilationTestUtils.testCompilation(sourcesOutputDir, compiledOutputDir);
+ CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
+ }
+
private static void testReturnTypeIdentityref(final Class<?> clazz, final String methodName,
final String returnTypeStr) throws NoSuchMethodException {
Method method = clazz.getMethod(methodName);
*/
package org.opendaylight.mdsal.binding.java.api.generator.test;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
}
static void assertFileContains(final File file, final String searchText) throws IOException {
- assertFileContains(file, Files.readString(file.toPath()), searchText);
+ assertFileContains(Files.readString(file.toPath()), searchText);
}
- static void assertFileContains(final File file, final String fileContent, final String searchText) {
- if (!fileContent.contains(searchText)) {
- throw new AssertionError("File " + file + " does not contain '" + searchText + "'");
- }
+ static void assertFileContains(final String fileContent, final String searchText) {
+ assertThat(fileContent, containsString(searchText));
}
static void assertFileContainsConsecutiveLines(final File file, final String fileContent, final String ... lines) {
for (final String line : lines) {
- assertFileContains(file, fileContent, line);
+ assertFileContains(fileContent, line);
}
- assertFileContains(file, fileContent, String.join(LS, lines));
+ assertFileContains(fileContent, String.join(LS, lines));
}
static Map<String, File> getFiles(final File path) {
--- /dev/null
+/*
+ * Copyright (c) 2020 Pantheon Technologies, s.r.o. 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.java.api.generator.test;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.DOUBLE_TAB;
+import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.TAB;
+import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.TRIPLE_TAB;
+import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.doubleTab;
+import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.getFiles;
+import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.tab;
+import static org.opendaylight.mdsal.binding.java.api.generator.test.FileSearchUtil.tripleTab;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.mdsal.binding.model.api.GeneratedType;
+import org.opendaylight.mdsal.binding.model.api.MethodSignature;
+import org.opendaylight.mdsal.binding.model.api.Type;
+import org.opendaylight.mdsal.binding.model.util.Types;
+
+public class SpecializingLeafrefTest extends BaseCompilationTest {
+ private static final Type LIST_STRING_TYPE = Types.listTypeFor(Types.STRING);
+
+ public static final String BAR_CONT = "BarCont";
+ public static final String BOOLEAN_CONT = "BooleanCont";
+
+ private static final String BAR_LST = "BarLst";
+ private static final String BAZ_GRP = "BazGrp";
+ private static final String FOO_GRP = "FooGrp";
+ private static final String RESOLVED_LEAF_GRP = "ResolvedLeafGrp";
+ private static final String RESOLVED_LEAFLIST_GRP = "ResolvedLeafListGrp";
+ private static final String TRANSITIVE_GROUP = "TransitiveGroup";
+ private static final String UNRESOLVED_GROUPING = "UnresolvedGrouping";
+
+ private static final String GET_LEAF1_NAME = "getLeaf1";
+ private static final String GET_LEAFLIST1_NAME = "getLeafList1";
+ private static final String IS_LEAF1_NAME = "isLeaf1";
+
+ private static final String GET_LEAF1_TYPE_OBJECT = " Object getLeaf1();";
+ private static final String GET_LEAF1_TYPE_STRING = " String getLeaf1();";
+ private static final String GET_LEAFLIST1_WILDCARD = " @Nullable List<?> getLeafList1();";
+ private static final String GET_LEAFLIST1_STRING = " @Nullable List<String> getLeafList1();";
+ private static final String GET_LEAFLIST1_DECLARATION = " getLeafList1();";
+ private static final String GET_LEAF1_DECLARATION = " getLeaf1();";
+ private static final String IS_LEAF1_BOOLEAN = " Boolean isLeaf1();";
+
+ private static final char CLOSING_METHOD_BRACE = '}';
+ private static final String TAB_CLOSING_METHOD_BRACE = TAB + CLOSING_METHOD_BRACE;
+ private static final String DTAB_CLOSING_METHOD_BRACE = DOUBLE_TAB + CLOSING_METHOD_BRACE;
+
+ private static final String FOO_GRP_REF = "org.opendaylight.yang.gen.v1.mdsal426.norev.FooGrp";
+ private static final String RESOLVED_LEAF_GRP_REF = "org.opendaylight.yang.gen.v1.mdsal426.norev.ResolvedLeafGrp";
+
+ private static final String UNRESOLVED_GROUPING_REF =
+ "org.opendaylight.yang.gen.v1.mdsal426.norev.UnresolvedGrouping";
+
+ private static final String ARG_AS_FOO_GRP = "((" + FOO_GRP_REF + ")arg)";
+
+ private static final String LEAF2_ASSIGNMENT = "this._leaf2 = arg.getLeaf2();";
+
+ private static final String TAB_FIELDS_FROM_SIGNATURE = TAB + "public void fieldsFrom(DataObject arg) {";
+ private static final String TTAB_SET_IS_VALID_ARG_TRUE = TRIPLE_TAB + "isValidArg = true;";
+ private static final String DTAB_INIT_IS_VALID_ARG_FALSE = DOUBLE_TAB + "boolean isValidArg = false;";
+
+ private File sourcesOutputDir;
+ private File compiledOutputDir;
+ private List<Type> types;
+ private Map<String, File> files;
+
+ @Before
+ public void before() throws IOException, URISyntaxException {
+ sourcesOutputDir = CompilationTestUtils.generatorOutput("mdsal426");
+ compiledOutputDir = CompilationTestUtils.compilerOutput("mdsal426");
+ types = generateTestSources("/compilation/mdsal426", sourcesOutputDir);
+ CompilationTestUtils.testCompilation(sourcesOutputDir, compiledOutputDir);
+ files = getFiles(sourcesOutputDir);
+ }
+
+ @After
+ public void after() {
+ CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
+ }
+
+ @Test
+ public void testGroupingWithUnresolvedLeafRefs() throws IOException {
+ verifyReturnType(FOO_GRP, GET_LEAF1_NAME, Types.objectType());
+ verifyReturnType(FOO_GRP, GET_LEAFLIST1_NAME, Types.listTypeWildcard());
+
+ final String content = getFileContent(FOO_GRP);
+
+ assertThat(content, containsString(GET_LEAF1_TYPE_OBJECT));
+ assertThat(content, containsString(GET_LEAFLIST1_WILDCARD));
+ }
+
+ @Test
+ public void testLeafLeafrefPointsLeaf() throws IOException {
+ verifyReturnType(RESOLVED_LEAF_GRP, GET_LEAF1_NAME, Types.STRING);
+
+ final String content = getFileContent(RESOLVED_LEAF_GRP);
+
+ assertThat(content, containsString(GET_LEAF1_TYPE_STRING));
+ }
+
+ @Test
+ public void testLeafLeafrefPointsLeafList() throws IOException {
+ verifyReturnType(RESOLVED_LEAFLIST_GRP, GET_LEAF1_NAME, Types.STRING);
+
+ final String content = getFileContent(RESOLVED_LEAF_GRP);
+
+ assertThat(content, containsString(GET_LEAF1_TYPE_STRING));
+ }
+
+ @Test
+ public void testLeafListLeafrefPointsLeaf() throws IOException {
+ verifyReturnType(RESOLVED_LEAF_GRP, GET_LEAFLIST1_NAME, LIST_STRING_TYPE);
+
+ final String content = getFileContent(RESOLVED_LEAF_GRP);
+
+ assertOverriddenGetter(content, GET_LEAFLIST1_STRING);
+ }
+
+ @Test
+ public void testLeafListLeafrefPointsLeafList() throws IOException {
+ verifyReturnType(RESOLVED_LEAFLIST_GRP, GET_LEAFLIST1_NAME, LIST_STRING_TYPE);
+
+ final String content = getFileContent(RESOLVED_LEAFLIST_GRP);
+
+ assertOverriddenGetter(content, GET_LEAFLIST1_STRING);
+ }
+
+ @Test
+ public void testGroupingWhichInheritUnresolvedLeafrefAndDoesNotDefineIt() throws IOException {
+ verifyMethodAbsence(TRANSITIVE_GROUP, GET_LEAF1_NAME);
+ verifyMethodAbsence(TRANSITIVE_GROUP, GET_LEAFLIST1_NAME);
+
+ final String content = getFileContent(TRANSITIVE_GROUP);
+
+ assertThat(content, not(containsString(GET_LEAF1_DECLARATION)));
+ assertThat(content, not(containsString(GET_LEAFLIST1_DECLARATION)));
+ }
+
+ @Test
+ public void testLeafrefWhichPointsBoolean() throws IOException {
+ verifyReturnType(UNRESOLVED_GROUPING, GET_LEAF1_NAME, Types.objectType());
+ verifyReturnType(BOOLEAN_CONT, GET_LEAF1_NAME, Types.BOOLEAN);
+ verifyReturnType(BOOLEAN_CONT, IS_LEAF1_NAME, Types.BOOLEAN);
+
+ final String unresolvedGrouping = getFileContent(UNRESOLVED_GROUPING);
+ final String booleanCont = getFileContent(BOOLEAN_CONT);
+
+ assertNotOverriddenGetter(unresolvedGrouping, GET_LEAF1_TYPE_OBJECT);
+ assertThat(booleanCont, containsString(GET_LEAF1_DECLARATION));
+ }
+
+ @Test
+ public void testGroupingsUsageWhereLeafrefAlreadyResolved() throws IOException {
+ leafList1AndLeaf1Absence(BAR_CONT);
+ leafList1AndLeaf1Absence(BAR_LST);
+ leafList1AndLeaf1Absence(BAZ_GRP);
+ }
+
+ private void leafList1AndLeaf1Absence(final String typeName) throws IOException {
+ verifyMethodAbsence(typeName, GET_LEAF1_NAME);
+ verifyMethodAbsence(typeName, GET_LEAFLIST1_NAME);
+
+ final String content = getFileContent(typeName);
+
+ assertThat(content, not(containsString(GET_LEAF1_DECLARATION)));
+ assertThat(content, not(containsString(GET_LEAFLIST1_DECLARATION)));
+ }
+
+ private static void assertNotOverriddenGetter(final String fileContent, final String getterString) {
+ assertThat(fileContent, not(containsString("@Override" + System.lineSeparator() + getterString)));
+ assertThat(fileContent, containsString(getterString));
+ }
+
+ private static void assertOverriddenGetter(final String fileContent, final String getterString) {
+ assertThat(fileContent, containsString("@Override" + System.lineSeparator() + getterString));
+ }
+
+ @Test
+ public void barContBuilderDataObjectTest() throws IOException {
+ final File file = files.get(getJavaBuilderFileName(BAR_CONT));
+ final String content = Files.readString(file.toPath());
+
+ barContBuilderConstructorResolvedLeafGrpTest(file, content);
+ barContBuilderConstructorFooGrpTest(file, content);
+ barContBuilderFieldsFromTest(file, content);
+ }
+
+ private static void barContBuilderConstructorResolvedLeafGrpTest(final File file, final String content) {
+ FileSearchUtil.assertFileContainsConsecutiveLines(file, content,
+ tab("public BarContBuilder(" + RESOLVED_LEAF_GRP_REF + " arg) {"),
+ doubleTab("this._leaf1 = arg.getLeaf1();"),
+ doubleTab("this._leafList1 = arg.getLeafList1();"),
+ doubleTab("this._name = arg.getName();"),
+ doubleTab(LEAF2_ASSIGNMENT),
+ TAB_CLOSING_METHOD_BRACE);
+ }
+
+ private static void barContBuilderFieldsFromTest(final File file, final String content) {
+ FileSearchUtil.assertFileContainsConsecutiveLines(file, content,
+ TAB_FIELDS_FROM_SIGNATURE,
+ DTAB_INIT_IS_VALID_ARG_FALSE,
+ doubleTab("if (arg instanceof " + FOO_GRP_REF + ") {"),
+ tripleTab("this._leaf1 = CodeHelpers.checkFieldCast(java.lang.String.class, \"leaf1\", "
+ + ARG_AS_FOO_GRP + ".getLeaf1());"),
+ tripleTab("this._leafList1 = CodeHelpers.checkListFieldCast(java.lang.String.class, \"leafList1\", "
+ + ARG_AS_FOO_GRP + ".getLeafList1());"),
+ tripleTab("this._leaf2 = " + ARG_AS_FOO_GRP + ".getLeaf2();"),
+ TTAB_SET_IS_VALID_ARG_TRUE,
+ DTAB_CLOSING_METHOD_BRACE,
+ doubleTab("if (arg instanceof " + RESOLVED_LEAF_GRP_REF + ") {"),
+ tripleTab("this._name = ((" + RESOLVED_LEAF_GRP_REF + ")arg).getName();"),
+ TTAB_SET_IS_VALID_ARG_TRUE,
+ DTAB_CLOSING_METHOD_BRACE,
+ doubleTab("CodeHelpers.validValue(isValidArg, arg, \"[" + FOO_GRP_REF + ", " + RESOLVED_LEAF_GRP_REF
+ + "]\");"),
+ TAB_CLOSING_METHOD_BRACE);
+ }
+
+ private static void barContBuilderConstructorFooGrpTest(final File file, final String content) {
+ FileSearchUtil.assertFileContainsConsecutiveLines(file, content,
+ tab("public BarContBuilder(" + FOO_GRP_REF + " arg) {"),
+ doubleTab("this._leaf1 = CodeHelpers.checkFieldCast(java.lang.String.class, \"leaf1\", "
+ + "arg.getLeaf1());"),
+ doubleTab("this._leafList1 = CodeHelpers.checkListFieldCast(java.lang.String.class, \"leafList1\", "
+ + "arg.getLeafList1());"),
+ doubleTab(LEAF2_ASSIGNMENT),
+ TAB_CLOSING_METHOD_BRACE);
+ }
+
+ @Test
+ public void booleanContBuilderDataObjectTest() throws IOException {
+ final File file = files.get(getJavaBuilderFileName(BOOLEAN_CONT));
+ final String content = Files.readString(file.toPath());
+
+ booleanContBuilderFieldsFromTest(file, content);
+ booleanContBuilderConstructorTest(file, content);
+ }
+
+ private static void booleanContBuilderFieldsFromTest(final File file, final String content) {
+ FileSearchUtil.assertFileContainsConsecutiveLines(file, content,
+ TAB_FIELDS_FROM_SIGNATURE,
+ DTAB_INIT_IS_VALID_ARG_FALSE,
+ doubleTab("if (arg instanceof " + UNRESOLVED_GROUPING_REF + ") {"),
+ tripleTab("this._leaf1 = CodeHelpers.checkFieldCast(java.lang.Boolean.class, \"leaf1\", (("
+ + UNRESOLVED_GROUPING_REF + ")arg).getLeaf1());"),
+ TTAB_SET_IS_VALID_ARG_TRUE,
+ DTAB_CLOSING_METHOD_BRACE,
+ doubleTab("CodeHelpers.validValue(isValidArg, arg, \"[" + UNRESOLVED_GROUPING_REF + "]\");"),
+ TAB_CLOSING_METHOD_BRACE);
+ }
+
+ private static void booleanContBuilderConstructorTest(final File file, final String content) {
+ FileSearchUtil.assertFileContainsConsecutiveLines(file, content,
+ tab("public BooleanContBuilder(" + UNRESOLVED_GROUPING_REF + " arg) {"),
+ doubleTab("this._leaf1 = CodeHelpers.checkFieldCast(java.lang.Boolean.class, \"leaf1\", "
+ + "arg.getLeaf1());"),
+ TAB_CLOSING_METHOD_BRACE);
+ }
+
+ private static String getJavaFileName(final String name) {
+ return name + ".java";
+ }
+
+ private static String getJavaBuilderFileName(final String name) {
+ return getJavaFileName(name + "Builder");
+ }
+
+ private String getFileContent(final String fileName) throws IOException {
+ final File file = files.get(getJavaFileName(fileName));
+ assertNotNull(Files.lines(file.toPath()).findFirst());
+ final String content = Files.readString(Path.of(file.getAbsolutePath()));
+ assertNotNull(content);
+ return content;
+ }
+
+ private void verifyMethodAbsence(final String typeName, final String getterName) {
+ verifyReturnType(typeName, getterName, null);
+ }
+
+ private void verifyReturnType(final String typeName, final String getterName, final Type returnType) {
+ final Type type = typeByName(types, typeName);
+ assertThat(type, instanceOf(GeneratedType.class));
+ final GeneratedType generated = (GeneratedType)type;
+ assertEquals(returnType, returnTypeByMethodName(generated, getterName));
+ }
+
+ private static Type typeByName(final List<Type> types, final String name) {
+ for (final Type type : types) {
+ if (type.getName().equals(name)) {
+ return type;
+ }
+ }
+ return null;
+ }
+
+ private static Type returnTypeByMethodName(final GeneratedType type, final String name) {
+ for (final MethodSignature m : type.getMethodDefinitions()) {
+ if (m.getName().equals(name)) {
+ return m.getReturnType();
+ }
+ }
+ return null;
+ }
+}
--- /dev/null
+module mdsal426 {
+ yang-version 1;
+ namespace "mdsal426";
+ prefix "mdsal426";
+
+ grouping foo-grp {
+ leaf leaf1 {
+ type leafref {
+ path "../mdsal426:name";
+ }
+ }
+ leaf-list leaf-list1 {
+ type leafref {
+ path "../mdsal426:name";
+ }
+ }
+ leaf leaf2 {
+ type string;
+ }
+ }
+
+ grouping resolved-leaf-grp {
+ uses foo-grp;
+ leaf name {
+ type string;
+ }
+ }
+
+ grouping resolved-leaf-list-grp {
+ uses foo-grp;
+ leaf-list name {
+ type string;
+ }
+ }
+
+ container bar-cont {
+ uses resolved-leaf-grp;
+ }
+
+ container bar-lst {
+ uses resolved-leaf-grp;
+ }
+
+ grouping baz-grp {
+ uses resolved-leaf-grp;
+ }
+
+ grouping transitive-group {
+ uses foo-grp;
+ }
+
+ grouping unresolved-grouping {
+ leaf leaf1 {
+ type leafref {
+ path "../mdsal426:is-foo";
+ }
+ }
+ }
+
+ container boolean-cont {
+ uses unresolved-grouping;
+ leaf is-foo {
+ type boolean;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+module mdsal533 {
+ yang-version 1;
+ namespace "mdsal533";
+ prefix "mdsal533";
+
+ grouping foo_grp {
+ leaf key_leaf1 {
+ type leafref {
+ path "../../../mdsal533:foo_list/mdsal533:name";
+ }
+ }
+ leaf key_leaf2 {
+ type string;
+ }
+ leaf key_leaf3 {
+ type string;
+ }
+ }
+
+ container foo_cont {
+ list foo_list {
+ key "name";
+ leaf name {
+ type string {
+ length "1..255";
+ }
+ }
+ }
+ container foo_cont2 {
+ list foo_list2 {
+ key "key_leaf1 key_leaf2 key_leaf3";
+ uses foo_grp;
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+module mdsal426 {
+ yang-version 1;
+ namespace "mdsal426";
+ prefix "mdsal426";
+
+ grouping foo-grp {
+ leaf leaf1 {
+ type leafref {
+ path "../mdsal426:name";
+ }
+ }
+ leaf-list leaf-list1 {
+ type leafref {
+ path "../mdsal426:name";
+ }
+ }
+ leaf leaf2 {
+ type string;
+ }
+ }
+
+ grouping resolved-leaf-grp {
+ uses foo-grp;
+ leaf name {
+ type string;
+ }
+ }
+
+ grouping resolved-leaf-list-grp {
+ uses foo-grp;
+ leaf-list name {
+ type string;
+ }
+ }
+
+ container bar-cont {
+ uses resolved-leaf-grp;
+ }
+
+ container bar-lst {
+ uses resolved-leaf-grp;
+ }
+
+ grouping baz-grp {
+ uses resolved-leaf-grp;
+ }
+
+ grouping transitive-group {
+ uses foo-grp;
+ }
+
+ grouping unresolved-grouping {
+ leaf leaf1 {
+ type leafref {
+ path "../mdsal426:is-foo";
+ }
+ }
+ }
+
+ container boolean-cont {
+ uses unresolved-grouping;
+ leaf is-foo {
+ type boolean;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+module mdsal533 {
+ yang-version 1;
+ namespace "mdsal533";
+ prefix "mdsal533";
+
+ grouping foo_grp {
+ leaf key_leaf1 {
+ type leafref {
+ path "../../../mdsal533:foo_list/mdsal533:name";
+ }
+ }
+ leaf key_leaf2 {
+ type string;
+ }
+ leaf key_leaf3 {
+ type string;
+ }
+ }
+
+ container foo_cont {
+ list foo_list {
+ key "name";
+ leaf name {
+ type string {
+ length "1..255";
+ }
+ }
+ }
+ container foo_cont2 {
+ list foo_list2 {
+ key "key_leaf1 key_leaf2 key_leaf3";
+ uses foo_grp;
+ }
+ }
+ }
+}
\ No newline at end of file
? requiredClass.cast(obj) : null;
}
+ /**
+ * Utility method for checking whether a target object is compatible.
+ *
+ * @param requiredClass Required class
+ * @param fieldName name of the field being filled
+ * @param obj Object to check, may be null
+ * @return Object cast to required class, if its class matches requirement, or null
+ * @throws IllegalArgumentException if {@code obj} is not an instance of {@code requiredClass}
+ * @throws NullPointerException if {@code requiredClass} or {@code fieldName} is null
+ */
+ public static <T> @Nullable T checkFieldCast(final @NonNull Class<T> requiredClass, final @NonNull String fieldName,
+ final @Nullable Object obj) {
+ try {
+ return requiredClass.cast(obj);
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException("Invalid input value for property \"" + fieldName + "\"", e);
+ }
+ }
+
+ /**
+ * Utility method for checking whether the items of target list is compatible.
+ *
+ * @param requiredClass Required item class
+ * @param fieldName name of the field being filled
+ * @param list List, which items should be checked
+ * @throws IllegalArgumentException if a list item is not instance of {@code requiredItemClass}
+ * @throws NullPointerException if {@code requiredClass} or {@code fieldName} is null
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> @Nullable List<T> checkListFieldCast(final @NonNull Class<?> requiredClass,
+ final @NonNull String fieldName, final @Nullable List<?> list) {
+ if (list != null) {
+ try {
+ list.forEach(item -> requiredClass.cast(requireNonNull(item)));
+ } catch (ClassCastException | NullPointerException e) {
+ throw new IllegalArgumentException("Invalid input list item for property \"" + requireNonNull(fieldName)
+ + "\"", e);
+ }
+ }
+ return (List<T>) list;
+ }
+
/**
* The constant '31' is the result of folding this code:
* <pre>
--- /dev/null
+/*
+ * Copyright (c) 2020 PANTHEON.tech, 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.yangtools.yang.binding;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class CodeHelpersTest {
+ @Test
+ public void testCheckedFieldCast() {
+ assertNull(CodeHelpers.checkFieldCast(CodeHelpersTest.class, "foo", null));
+ assertSame(this, CodeHelpers.checkFieldCast(CodeHelpersTest.class, "foo", this));
+
+ final IllegalArgumentException iae = assertThrows(IllegalArgumentException.class,
+ () -> CodeHelpers.checkFieldCast(CodeHelpersTest.class, "foo", new Object()));
+ assertThat(iae.getCause(), instanceOf(ClassCastException.class));
+ }
+
+ @Test
+ public void testCheckListFieldCast() {
+ assertNull(CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", null));
+ assertSame(List.of(), CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", List.of()));
+ final var list = List.of(this);
+ assertSame(list, CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", list));
+
+ IllegalArgumentException iae = assertThrows(IllegalArgumentException.class,
+ () -> CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", Collections.singletonList(null)));
+ assertThat(iae.getCause(), instanceOf(NullPointerException.class));
+
+ iae = assertThrows(IllegalArgumentException.class,
+ () -> CodeHelpers.checkListFieldCast(CodeHelpersTest.class, "foo", List.of(new Object())));
+ assertThat(iae.getCause(), instanceOf(ClassCastException.class));
+ }
+}