--- /dev/null
+/*
+ * Copyright (c) 2018 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.java.api.generator
+
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
+
+import com.google.common.base.MoreObjects
+import java.util.Collection
+import java.util.Collections
+import java.util.Map
+import java.util.Set
+import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
+import org.opendaylight.mdsal.binding.model.api.GeneratedType
+import org.opendaylight.mdsal.binding.model.api.Type
+import org.opendaylight.yangtools.yang.binding.CodeHelpers
+import java.util.ArrayList
+import org.opendaylight.mdsal.binding.model.util.Types
+import org.opendaylight.yangtools.yang.binding.Identifiable
+import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
+import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
+import com.google.common.collect.ImmutableMap
+import org.opendaylight.yangtools.yang.binding.AugmentationHolder
+import java.util.HashMap
+
+abstract class AbstractBuilderTemplate extends BaseTemplate {
+ /**
+ * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME.
+ */
+ protected val Type augmentType
+
+ /**
+ * Set of class attributes (fields) which are derived from the getter methods names.
+ */
+ protected val Set<GeneratedProperty> properties
+
+ /**
+ * GeneratedType for key type, null if this type does not have a key.
+ */
+ protected val Type keyType
+
+ protected val GeneratedType targetType;
+
+ new(AbstractJavaGeneratedType javaType, GeneratedType type, GeneratedType targetType,
+ Set<GeneratedProperty> properties, Type augmentType, Type keyType) {
+ super(javaType, type)
+ this.targetType = targetType
+ this.properties = properties
+ this.augmentType = augmentType
+ this.keyType = keyType
+ }
+
+ new(GeneratedType type, GeneratedType targetType, Set<GeneratedProperty> properties, Type augmentType,
+ Type keyType) {
+ super(type)
+ this.targetType = targetType
+ this.properties = properties
+ this.augmentType = augmentType
+ this.keyType = keyType
+ }
+
+ /**
+ * Template method which generates class attributes.
+ *
+ * @param makeFinal value which specify whether field is|isn't final
+ * @return string with class attributes and their types
+ */
+ def generateFields(boolean makeFinal) '''
+ «IF properties !== null»
+ «FOR f : properties»
+ private«IF makeFinal» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
+ «ENDFOR»
+ «ENDIF»
+ «IF keyType !== null»
+ private«IF makeFinal» final«ENDIF» «keyType.importedName» key;
+ «ENDIF»
+ '''
+
+ def generateAugmentField(boolean isPrivate) '''
+ «IF augmentType !== null»
+ «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentType.importedName»>, «augmentType.importedName»> «AUGMENTATION_FIELD» = «Collections.importedName».emptyMap();
+ «ENDIF»
+ '''
+
+ override generateToString(Collection<GeneratedProperty> properties) '''
+ «IF properties !== null»
+ @«Override.importedName»
+ public «String.importedName» toString() {
+ final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper("«targetType.name»");
+ «FOR property : properties»
+ «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
+ «ENDFOR»
+ «IF augmentType !== null»
+ «CodeHelpers.importedName».appendValue(helper, "«AUGMENTATION_FIELD»", «AUGMENTATION_FIELD».values());
+ «ENDIF»
+ return helper.toString();
+ }
+ «ENDIF»
+ '''
+
+ /**
+ * Template method which generate getter methods for IMPL class.
+ *
+ * @return string with getter methods
+ */
+ def generateGetters(boolean addOverride) '''
+ «IF keyType !== null»
+ «IF addOverride»@«Override.importedName»«ENDIF»
+ public «keyType.importedName» «BindingMapping.IDENTIFIABLE_KEY_NAME»() {
+ return key;
+ }
+
+ «ENDIF»
+ «IF !properties.empty»
+ «FOR field : properties SEPARATOR '\n'»
+ «IF addOverride»@«Override.importedName»«ENDIF»
+ «field.getterMethod»
+ «ENDFOR»
+ «ENDIF»
+ «IF augmentType !== null»
+
+ @SuppressWarnings("unchecked")
+ «IF addOverride»@«Override.importedName»«ENDIF»
+ public <E extends «augmentType.importedName»> E «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName»<E> augmentationType) {
+ return (E) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
+ }
+ «ENDIF»
+ '''
+
+ def CharSequence generateCopyConstructor(boolean impl, Type fromType, Type implType) '''
+ «IF impl»private«ELSE»public«ENDIF» «type.name»(«fromType.importedName» base) {
+ «val allProps = new ArrayList(properties)»
+ «val isList = implementsIfc(targetType, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), targetType))»
+ «IF isList && keyType !== null»
+ «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
+ «Collections.sort(keyProps, [ p1, p2 | return p1.name.compareTo(p2.name) ])»
+ «FOR field : keyProps»
+ «removeProperty(allProps, field.name)»
+ «ENDFOR»
+ if (base.«BindingMapping.IDENTIFIABLE_KEY_NAME»() == null) {
+ this.key = new «keyType.importedName»(
+ «FOR keyProp : keyProps SEPARATOR ", "»
+ base.«keyProp.getterMethodName»()
+ «ENDFOR»
+ );
+ «FOR field : keyProps»
+ this.«field.fieldName» = base.«field.getterMethodName»();
+ «ENDFOR»
+ } else {
+ this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
+ «FOR field : keyProps»
+ this.«field.fieldName» = key.«field.getterMethodName»();
+ «ENDFOR»
+ }
+ «ENDIF»
+ «FOR field : allProps»
+ this.«field.fieldName» = base.«field.getterMethodName»();
+ «ENDFOR»
+ «IF augmentType !== null»
+ «IF impl»
+ this.«AUGMENTATION_FIELD» = «ImmutableMap.importedName».copyOf(base.«AUGMENTATION_FIELD»);
+ «ELSE»
+ if (base instanceof «implType.importedName») {
+ «implType.importedName» impl = («implType.importedName») base;
+ if (!impl.«AUGMENTATION_FIELD».isEmpty()) {
+ this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>(impl.«AUGMENTATION_FIELD»);
+ }
+ } else if (base instanceof «AugmentationHolder.importedName») {
+ @SuppressWarnings("unchecked")
+ «AugmentationHolder.importedName»<«fromType.importedName»> casted =(«AugmentationHolder.importedName»<«fromType.importedName»>) base;
+ if (!casted.augmentations().isEmpty()) {
+ this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>(casted.augmentations());
+ }
+ }
+ «ENDIF»
+ «ENDIF»
+ }
+ '''
+
+ private def boolean implementsIfc(GeneratedType type, Type impl) {
+ for (Type ifc : type.implements) {
+ if (ifc.equals(impl)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private def void removeProperty(Collection<GeneratedProperty> props, String name) {
+ var GeneratedProperty toRemove = null
+ for (p : props) {
+ if (p.name.equals(name)) {
+ toRemove = p;
+ }
+ }
+ if (toRemove !== null) {
+ props.remove(toRemove);
+ }
+ }
+}
\ No newline at end of file
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Streams;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
for (GeneratedType type : Iterables.concat(genType.getEnclosedTypes(), genType.getEnumerations())) {
b.put(type.getIdentifier().simpleName(), new NestedJavaGeneratedType(this, type));
}
+ collectInheritedEnclosedTypes(b, genType);
+
enclosedTypes = b.build();
- conflictingNames = genType instanceof Enumeration
- ? ((Enumeration) genType).getValues().stream().map(Pair::getMappedName).collect(toImmutableSet())
- : ImmutableSet.of();
- }
- AbstractJavaGeneratedType(final JavaTypeName name, final GeneratedType genType) {
- this.name = requireNonNull(name);
- enclosedTypes = ImmutableMap.of();
+ if (genType instanceof Enumeration) {
+ conflictingNames = ((Enumeration) genType).getValues().stream().map(Pair::getMappedName)
+ .collect(toImmutableSet());
+ } else {
+ conflictingNames = ImmutableSet.of();
+ }
+ }
- // This is a workaround for BuilderTemplate, which does not model itself correctly -- it should generate
- // a GeneratedType for the Builder with a nested type for the implementation, which really should be
- // a different template which gets generated as an inner type.
- conflictingNames = Streams.concat(genType.getEnclosedTypes().stream(), genType.getEnumerations().stream())
- .map(type -> type.getIdentifier().simpleName()).collect(toImmutableSet());
+ private void collectInheritedEnclosedTypes(Builder<String, NestedJavaGeneratedType> builder,
+ GeneratedType type) {
+ for (Type impl : type.getImplements()) {
+ if (impl instanceof GeneratedType) {
+ final GeneratedType genType = (GeneratedType) impl;
+ for (GeneratedType inner : Iterables.concat(genType.getEnclosedTypes(), genType.getEnumerations())) {
+ builder.put(inner.getIdentifier().simpleName(), new NestedJavaGeneratedType(this, inner));
+ }
+ collectInheritedEnclosedTypes(builder, genType);
+ }
+ }
}
final JavaTypeName getName() {
*/
package org.opendaylight.mdsal.binding.java.api.generator;
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSortedSet;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import org.eclipse.xtext.xbase.lib.StringExtensions;
import org.opendaylight.mdsal.binding.model.api.CodeGenerator;
+import org.opendaylight.mdsal.binding.model.api.GeneratedProperty;
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.api.type.builder.GeneratedTOBuilder;
+import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilder;
+import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl;
+import org.opendaylight.mdsal.binding.model.util.Types;
+import org.opendaylight.mdsal.binding.model.util.generated.type.builder.CodegenGeneratedTOBuilder;
+import org.opendaylight.mdsal.binding.model.util.generated.type.builder.CodegenGeneratedTypeBuilder;
+import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
import org.opendaylight.yangtools.yang.binding.Augmentable;
import org.opendaylight.yangtools.yang.binding.Augmentation;
*
*/
public final class BuilderGenerator implements CodeGenerator {
+ private static final Comparator<MethodSignature> METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<>();
+ private static final Type AUGMENTATION_RET_TYPE;
- /**
- * Constant used as sufix for builder name.
- */
- public static final String BUILDER = "Builder";
+ static {
+ final Method m;
+ try {
+ m = Augmentable.class.getDeclaredMethod(AUGMENTABLE_AUGMENTATION_NAME, Class.class);
+ } catch (NoSuchMethodException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+
+ AUGMENTATION_RET_TYPE = new ReferencedTypeImpl(JavaTypeName.create(m.getReturnType()));
+ }
/**
* Passes via list of implemented types in <code>type</code>.
@Override
public String generate(Type type) {
if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
- final GeneratedType genType = (GeneratedType) type;
- final BuilderTemplate template = new BuilderTemplate(genType);
- return template.generate();
+ return templateForType((GeneratedType) type).generate();
}
return "";
}
@Override
public String getUnitName(Type type) {
- return type.getName() + BUILDER;
+ return type.getName() + BuilderTemplate.BUILDER;
+ }
+
+ @VisibleForTesting
+ static BuilderTemplate templateForType(GeneratedType type) {
+ final GeneratedType genType = type;
+ final JavaTypeName origName = genType.getIdentifier();
+
+ final Set<MethodSignature> methods = new LinkedHashSet<>();
+ final Type augmentType = createMethods(genType, methods);
+ final Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR)
+ .addAll(methods).build();
+
+ final GeneratedTypeBuilder builderTypeBuilder = new CodegenGeneratedTypeBuilder(
+ origName.createSibling(origName.simpleName() + BuilderTemplate.BUILDER));
+
+ final GeneratedTOBuilder implTypeBuilder = builderTypeBuilder.addEnclosingTransferObject(
+ origName.simpleName() + "Impl");
+ implTypeBuilder.addImplementsType(genType);
+
+ return new BuilderTemplate(builderTypeBuilder.build(), genType, propertiesFromMethods(sortedMethods),
+ augmentType, getKey(genType));
}
+ private static Type getKey(GeneratedType type) {
+ for (MethodSignature m : type.getMethodDefinitions()) {
+ if (BindingMapping.IDENTIFIABLE_KEY_NAME.equals(m.getName())) {
+ return m.getReturnType();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns set of method signature instances which contains all the methods of the <code>genType</code>
+ * and all the methods of the implemented interfaces.
+ *
+ * @returns set of method signature instances
+ */
+ private static ParameterizedType createMethods(GeneratedType type, Set<MethodSignature> methods) {
+ methods.addAll(type.getMethodDefinitions());
+ return collectImplementedMethods(type, methods, type.getImplements());
+ }
+
+ /**
+ * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
+ * and recursively their implemented interfaces.
+ *
+ * @param methods set of method signatures
+ * @param implementedIfcs list of implemented interfaces
+ */
+ private static ParameterizedType collectImplementedMethods(GeneratedType type, Set<MethodSignature> methods,
+ List<Type> implementedIfcs) {
+ if (implementedIfcs == null || implementedIfcs.isEmpty()) {
+ return null;
+ }
+
+ ParameterizedType augmentType = null;
+ for (Type implementedIfc : implementedIfcs) {
+ if (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject)) {
+ final GeneratedType ifc = (GeneratedType) implementedIfc;
+ methods.addAll(ifc.getMethodDefinitions());
+
+ final ParameterizedType t = collectImplementedMethods(type, methods, ifc.getImplements());
+ if (t != null && augmentType == null) {
+ augmentType = t;
+ }
+ } else if (Augmentable.class.getName().equals(implementedIfc.getFullyQualifiedName())) {
+ augmentType = Types.parameterizedTypeFor(AUGMENTATION_RET_TYPE,
+ new ReferencedTypeImpl(type.getIdentifier()));
+ }
+ }
+
+ return augmentType;
+ }
+
+ /**
+ * Creates set of generated property instances from getter <code>methods</code>.
+ *
+ * @param set of method signature instances which should be transformed to list of properties
+ * @return set of generated property instances which represents the getter <code>methods</code>
+ */
+ private static Set<GeneratedProperty> propertiesFromMethods(Collection<MethodSignature> methods) {
+ if (methods == null || methods.isEmpty()) {
+ return Collections.emptySet();
+ }
+ final Set<GeneratedProperty> result = new LinkedHashSet<>();
+ for (MethodSignature m : methods) {
+ final GeneratedProperty createdField = propertyFromGetter(m);
+ if (createdField != null) {
+ result.add(createdField);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates generated property instance from the getter <code>method</code> name and return type.
+ *
+ * @param method method signature from which is the method name and return type obtained
+ * @return generated property instance for the getter <code>method</code>
+ * @throws IllegalArgumentException<ul>
+ * <li>if the <code>method</code> equals <code>null</code></li>
+ * <li>if the name of the <code>method</code> equals <code>null</code></li>
+ * <li>if the name of the <code>method</code> is empty</li>
+ * <li>if the return type of the <code>method</code> equals <code>null</code></li>
+ * </ul>
+ */
+ private static GeneratedProperty propertyFromGetter(MethodSignature method) {
+ checkArgument(method != null);
+ checkArgument(method.getReturnType() != null);
+ checkArgument(method.getName() != null);
+ checkArgument(!method.getName().isEmpty());
+ final String prefix = Types.BOOLEAN.equals(method.getReturnType()) ? "is" : "get";
+ if (!method.getName().startsWith(prefix)) {
+ return null;
+ }
+
+ final String fieldName = StringExtensions.toFirstLower(method.getName().substring(prefix.length()));
+ final GeneratedTOBuilder tmpGenTO = new CodegenGeneratedTOBuilder(JavaTypeName.create("foo", "foo"));
+ tmpGenTO.addProperty(fieldName).setReturnType(method.getReturnType());
+ return tmpGenTO.build().getProperties().get(0);
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2018 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.java.api.generator
+
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
+
+import java.util.Arrays
+import java.util.Objects
+import java.util.Map
+import org.opendaylight.mdsal.binding.model.api.GeneratedType
+import org.opendaylight.mdsal.binding.model.api.Type
+import org.opendaylight.yangtools.yang.binding.DataObject
+
+class BuilderImplTemplate extends AbstractBuilderTemplate {
+ val Type builderType;
+
+ new(BuilderTemplate builder, GeneratedType type) {
+ super(builder.javaType.getEnclosedType(type.identifier), type, builder.targetType, builder.properties,
+ builder.augmentType, builder.keyType)
+ this.builderType = builder.type
+ }
+
+ override body() '''
+ private static final class «type.name» implements «targetType.importedName» {
+
+ «generateFields(true)»
+
+ «generateAugmentField(true)»
+
+ «generateCopyConstructor(true, builderType, type)»
+
+ @«Override.importedName»
+ public «Class.importedName»<«targetType.importedName»> getImplementedInterface() {
+ return «targetType.importedName».class;
+ }
+
+ «generateGetters(true)»
+
+ «generateHashCode()»
+
+ «generateEquals()»
+
+ «generateToString(properties)»
+ }
+ '''
+
+ /**
+ * Template method which generates the method <code>hashCode()</code>.
+ *
+ * @return string with the <code>hashCode()</code> method definition in JAVA format
+ */
+ def protected generateHashCode() '''
+ «IF !properties.empty || augmentType !== null»
+ private int hash = 0;
+ private volatile boolean hashValid = false;
+
+ @«Override.importedName»
+ public int hashCode() {
+ if (hashValid) {
+ return hash;
+ }
+
+ final int prime = 31;
+ int result = 1;
+ «FOR property : properties»
+ «IF property.returnType.name.contains("[")»
+ result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
+ «ELSE»
+ result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
+ «ENDIF»
+ «ENDFOR»
+ «IF augmentType !== null»
+ result = prime * result + «Objects.importedName».hashCode(«AUGMENTATION_FIELD»);
+ «ENDIF»
+
+ hash = result;
+ hashValid = true;
+ return result;
+ }
+ «ENDIF»
+ '''
+
+ /**
+ * Template method which generates the method <code>equals()</code>.
+ *
+ * @return string with the <code>equals()</code> method definition in JAVA format
+ */
+ def protected generateEquals() '''
+ «IF !properties.empty || augmentType !== null»
+ @«Override.importedName»
+ public boolean equals(«Object.importedName» obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof «DataObject.importedName»)) {
+ return false;
+ }
+ if (!«targetType.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
+ return false;
+ }
+ «targetType.importedName» other = («targetType.importedName»)obj;
+ «FOR property : properties»
+ «val fieldName = property.fieldName»
+ «IF property.returnType.name.contains("[")»
+ if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
+ «ELSE»
+ if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
+ «ENDIF»
+ return false;
+ }
+ «ENDFOR»
+ «IF augmentType !== null»
+ if (getClass() == obj.getClass()) {
+ // Simple case: we are comparing against self
+ «type.name» otherImpl = («type.name») obj;
+ if (!«Objects.importedName».equals(«AUGMENTATION_FIELD», otherImpl.«AUGMENTATION_FIELD»)) {
+ return false;
+ }
+ } else {
+ // Hard case: compare our augments with presence there...
+ for («Map.importedName».Entry<«Class.importedName»<? extends «augmentType.importedName»>, «augmentType.importedName»> e : «AUGMENTATION_FIELD».entrySet()) {
+ if (!e.getValue().equals(other.«AUGMENTABLE_AUGMENTATION_NAME»(e.getKey()))) {
+ return false;
+ }
+ }
+ // .. and give the other one the chance to do the same
+ if (!obj.equals(this)) {
+ return false;
+ }
+ }
+ «ENDIF»
+ return true;
+ }
+ «ENDIF»
+ '''
+}
\ No newline at end of file
import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
-import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
-import com.google.common.base.MoreObjects
-import com.google.common.collect.ImmutableMap
-import com.google.common.collect.ImmutableSortedSet
import com.google.common.collect.ImmutableList
import java.util.ArrayList
-import java.util.Arrays
import java.util.Collection
-import java.util.Collections
import java.util.HashMap
import java.util.HashSet
-import java.util.LinkedHashSet
import java.util.List
import java.util.Map
-import java.util.Objects
import java.util.Set
import java.util.regex.Pattern
import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
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.Type
import org.opendaylight.mdsal.binding.model.api.ParameterizedType
-import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl
-import org.opendaylight.mdsal.binding.model.util.Types
-import org.opendaylight.mdsal.binding.model.util.generated.type.builder.CodegenGeneratedTOBuilder
+import org.opendaylight.mdsal.binding.model.api.Type
import org.opendaylight.mdsal.binding.model.util.TypeConstants
-import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
+import org.opendaylight.mdsal.binding.model.util.Types
import org.opendaylight.yangtools.concepts.Builder
-import org.opendaylight.yangtools.yang.binding.Augmentable
-import org.opendaylight.yangtools.yang.binding.AugmentationHolder
import org.opendaylight.yangtools.yang.binding.CodeHelpers
import org.opendaylight.yangtools.yang.binding.DataObject
-import org.opendaylight.yangtools.yang.binding.Identifiable
/**
* Template for generating JAVA builder classes.
*/
-
-class BuilderTemplate extends BaseTemplate {
- /**
- * Constant with the suffix for builder classes.
- */
- val static BUILDER = 'Builder'
-
- /**
- * Constant with suffix for the classes which are generated from the builder classes.
- */
- val static IMPL = 'Impl'
-
- /**
- * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME.
- */
- var Type augmentType
-
- /**
- * Set of class attributes (fields) which are derived from the getter methods names.
- */
- val Set<GeneratedProperty> properties
-
+class BuilderTemplate extends AbstractBuilderTemplate {
/**
- * GeneratedType for key type, null if this type does not have a key.
+ * Constant used as suffix for builder name.
*/
- val Type keyType
-
- static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
+ public static val BUILDER = "Builder";
/**
* Constructs new instance of this class.
* @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
*/
- new(GeneratedType genType) {
- super(new TopLevelJavaGeneratedType(builderName(genType), genType), genType)
- this.properties = propertiesFromMethods(createMethods)
- keyType = genType.key
- }
-
- def static builderName(GeneratedType genType) {
- val name = genType.identifier
- name.createSibling(name.simpleName + "Builder")
- }
-
- /**
- * Returns set of method signature instances which contains all the methods of the <code>genType</code>
- * and all the methods of the implemented interfaces.
- *
- * @returns set of method signature instances
- */
- def private Set<MethodSignature> createMethods() {
- val Set<MethodSignature> methods = new LinkedHashSet();
- methods.addAll(type.methodDefinitions)
- collectImplementedMethods(methods, type.implements)
- val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
-
- return sortedMethods
- }
-
- /**
- * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
- * and recursively their implemented interfaces.
- *
- * @param methods set of method signatures
- * @param implementedIfcs list of implemented interfaces
- */
- def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
- if (implementedIfcs === null || implementedIfcs.empty) {
- return
- }
- for (implementedIfc : implementedIfcs) {
- if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
- val ifc = implementedIfc as GeneratedType
- methods.addAll(ifc.methodDefinitions)
- collectImplementedMethods(methods, ifc.implements)
- } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
- val m = Augmentable.getDeclaredMethod(AUGMENTABLE_AUGMENTATION_NAME, Class)
- val identifier = JavaTypeName.create(m.returnType)
- val refType = new ReferencedTypeImpl(identifier)
- val generic = new ReferencedTypeImpl(type.identifier)
- augmentType = Types.parameterizedTypeFor(refType, generic)
- }
- }
- }
-
- /**
- * Returns the first element of the list <code>elements</code>.
- *
- * @param list of elements
- */
- def private <E> first(List<E> elements) {
- elements.get(0)
- }
-
- /**
- * Creates set of generated property instances from getter <code>methods</code>.
- *
- * @param set of method signature instances which should be transformed to list of properties
- * @return set of generated property instances which represents the getter <code>methods</code>
- */
- def private propertiesFromMethods(Collection<MethodSignature> methods) {
- if (methods === null || methods.isEmpty()) {
- return Collections.emptySet
- }
- val Set<GeneratedProperty> result = new LinkedHashSet
- for (m : methods) {
- val createdField = m.propertyFromGetter
- if (createdField !== null) {
- result.add(createdField)
- }
- }
- return result
- }
-
- /**
- * Creates generated property instance from the getter <code>method</code> name and return type.
- *
- * @param method method signature from which is the method name and return type obtained
- * @return generated property instance for the getter <code>method</code>
- * @throws IllegalArgumentException<ul>
- * <li>if the <code>method</code> equals <code>null</code></li>
- * <li>if the name of the <code>method</code> equals <code>null</code></li>
- * <li>if the name of the <code>method</code> is empty</li>
- * <li>if the return type of the <code>method</code> equals <code>null</code></li>
- * </ul>
- */
- def private GeneratedProperty propertyFromGetter(MethodSignature method) {
- if (method === null || method.name === null || method.name.empty || method.returnType === null) {
- throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
- }
- var prefix = "get";
- if (Types.BOOLEAN.equals(method.returnType)) {
- prefix = "is";
- }
- if (method.name.startsWith(prefix)) {
- val fieldName = method.getName().substring(prefix.length()).toFirstLower
- val tmpGenTO = new CodegenGeneratedTOBuilder(JavaTypeName.create("foo", "foo"))
- tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
- return tmpGenTO.build.properties.first
- }
+ new(GeneratedType genType, GeneratedType targetType, Set<GeneratedProperty> properties, Type augmentType,
+ Type keyType) {
+ super(genType, targetType, properties, augmentType, keyType)
}
override isLocalInnerClass(JavaTypeName name) {
*/
override body() '''
«wrapToDocumentation(formatDataForJavaDoc(type))»
- public class «type.name»«BUILDER» implements «Builder.importedName»<«type.importedName»> {
+ public class «type.name» implements «Builder.importedName»<«targetType.importedName»> {
«generateFields(false)»
«generateAugmentField(false)»
- «generateConstructorsFromIfcs(type)»
+ «generateConstructorsFromIfcs()»
- «generateCopyConstructor(false)»
+ «generateCopyConstructor(false, targetType, type.enclosedTypes.get(0))»
- «generateMethodFieldsFrom(type)»
+ «generateMethodFieldsFrom()»
«generateGetters(false)»
«generateSetters»
@«Override.importedName»
- public «type.name» build() {
- return new «type.name»«IMPL»(this);
- }
-
- private static final class «type.name»«IMPL» implements «type.name» {
-
- «implementedInterfaceGetter»
-
- «generateFields(true)»
-
- «generateAugmentField(true)»
-
- «generateCopyConstructor(true)»
-
- «generateGetters(true)»
-
- «generateHashCode()»
-
- «generateEquals()»
-
- «generateToString(properties)»
+ public «targetType.name» build() {
+ return new «type.enclosedTypes.get(0).importedName»(this);
}
+ «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
}
'''
/**
* Generate default constructor and constructor for every implemented interface from uses statements.
*/
- def private generateConstructorsFromIfcs(Type type) '''
- public «type.name»«BUILDER»() {
+ def private generateConstructorsFromIfcs() '''
+ public «type.name»() {
}
- «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
- «val ifc = type as GeneratedType»
- «FOR impl : ifc.implements»
+ «IF (!(targetType instanceof GeneratedTransferObject))»
+ «FOR impl : targetType.implements»
«generateConstructorFromIfc(impl)»
«ENDFOR»
«ENDIF»
def private Object generateConstructorFromIfc(Type impl) '''
«IF (impl instanceof GeneratedType)»
«IF !(impl.methodDefinitions.empty)»
- public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
+ public «type.name»(«impl.fullyQualifiedName» arg) {
«printConstructorPropertySetter(impl)»
}
«ENDIF»
/**
* Generate 'fieldsFrom' method to set builder properties based on type of given argument.
*/
- def private generateMethodFieldsFrom(Type type) '''
- «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
- «val ifc = type as GeneratedType»
- «IF ifc.hasImplementsFromUses»
- «val List<Type> done = ifc.getBaseIfcs»
- «generateMethodFieldsFromComment(ifc)»
+ def private generateMethodFieldsFrom() '''
+ «IF (!(targetType instanceof GeneratedTransferObject))»
+ «IF targetType.hasImplementsFromUses»
+ «val List<Type> done = targetType.getBaseIfcs»
+ «generateMethodFieldsFromComment(targetType)»
public void fieldsFrom(«DataObject.importedName» arg) {
boolean isValidArg = false;
- «FOR impl : ifc.getAllIfcs»
+ «FOR impl : targetType.getAllIfcs»
«generateIfCheck(impl, done)»
«ENDFOR»
- «CodeHelpers.importedName».validValue(isValidArg, arg, "«ifc.getAllIfcs.toListOfNames»");
+ «CodeHelpers.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
}
«ENDIF»
«ENDIF»
return names
}
- /**
- * Template method which generates class attributes.
- *
- * @param boolean value which specify whether field is|isn't final
- * @return string with class attributes and their types
- */
- def private generateFields(boolean _final) '''
- «IF properties !== null»
- «FOR f : properties»
- private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
- «ENDFOR»
- «ENDIF»
- «IF keyType !== null»
- private«IF _final» final«ENDIF» «keyType.importedName» key;
- «ENDIF»
- '''
-
- def private generateAugmentField(boolean isPrivate) '''
- «IF augmentType !== null»
- «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentType.importedName»>, «augmentType.importedName»> «AUGMENTATION_FIELD» = «Collections.importedName».emptyMap();
- «ENDIF»
- '''
-
def private constantsDeclarations() '''
«FOR c : type.getConstantDefinitions»
«IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
«IF restrictions !== null»
«generateCheckers(field, restrictions, actualType)»
«ENDIF»
- public «type.getName»Builder set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
+ public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
«IF restrictions !== null»
if (values != null) {
for («actualType.getFullyQualifiedName» value : values) {
«generateCheckers(field, restrictions, actualType)»
«ENDIF»
- public «type.getName»Builder set«field.getName.toFirstUpper»(final «field.returnType.importedName» value) {
+ public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» value) {
«IF restrictions !== null»
if (value != null) {
«checkArgument(field, restrictions, actualType, "value")»
*/
def private generateSetters() '''
«IF keyType !== null»
- public «type.getName»Builder withKey(final «keyType.importedName» key) {
+ public «type.getName» withKey(final «keyType.importedName» key) {
this.key = key;
return this;
}
«ENDFOR»
«IF augmentType !== null»
- public «type.name»«BUILDER» add«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName»<? extends «augmentType.importedName»> augmentationType, «augmentType.importedName» augmentationValue) {
+ public «type.name» add«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName»<? extends «augmentType.importedName»> augmentationType, «augmentType.importedName» augmentationValue) {
if (augmentationValue == null) {
return remove«AUGMENTATION_FIELD.toFirstUpper»(augmentationType);
}
return this;
}
- public «type.name»«BUILDER» remove«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName»<? extends «augmentType.importedName»> augmentationType) {
+ public «type.name» remove«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName»<? extends «augmentType.importedName»> augmentationType) {
if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
this.«AUGMENTATION_FIELD».remove(augmentationType);
}
«ENDIF»
'''
- def private CharSequence generateCopyConstructor(boolean impl) '''
- «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
- «val allProps = new ArrayList(properties)»
- «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
- «IF isList && keyType !== null»
- «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
- «Collections.sort(keyProps, [ p1, p2 | return p1.name.compareTo(p2.name) ])»
- «FOR field : keyProps»
- «removeProperty(allProps, field.name)»
- «ENDFOR»
- if (base.«BindingMapping.IDENTIFIABLE_KEY_NAME»() == null) {
- this.key = new «keyType.importedName»(
- «FOR keyProp : keyProps SEPARATOR ", "»
- base.«keyProp.getterMethodName»()
- «ENDFOR»
- );
- «FOR field : keyProps»
- this.«field.fieldName» = base.«field.getterMethodName»();
- «ENDFOR»
- } else {
- this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
- «FOR field : keyProps»
- this.«field.fieldName» = key.«field.getterMethodName»();
- «ENDFOR»
- }
- «ENDIF»
- «FOR field : allProps»
- this.«field.fieldName» = base.«field.getterMethodName»();
- «ENDFOR»
- «IF augmentType !== null»
- «IF impl»
- this.«AUGMENTATION_FIELD» = «ImmutableMap.importedName».copyOf(base.«AUGMENTATION_FIELD»);
- «ELSE»
- if (base instanceof «type.name»«IMPL») {
- «type.name»«IMPL» impl = («type.name»«IMPL») base;
- if (!impl.«AUGMENTATION_FIELD».isEmpty()) {
- this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>(impl.«AUGMENTATION_FIELD»);
- }
- } else if (base instanceof «AugmentationHolder.importedName») {
- @SuppressWarnings("unchecked")
- «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
- if (!casted.augmentations().isEmpty()) {
- this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>(casted.augmentations());
- }
- }
- «ENDIF»
- «ENDIF»
- }
- '''
-
- private def boolean implementsIfc(GeneratedType type, Type impl) {
- for (Type ifc : type.implements) {
- if (ifc.equals(impl)) {
- return true;
- }
- }
- return false;
- }
-
- private def getKey(GeneratedType type) {
- for (m : type.methodDefinitions) {
- if (BindingMapping.IDENTIFIABLE_KEY_NAME.equals(m.name)) {
- return m.returnType;
- }
- }
- }
-
- private def void removeProperty(Collection<GeneratedProperty> props, String name) {
- var GeneratedProperty toRemove = null
- for (p : props) {
- if (p.name.equals(name)) {
- toRemove = p;
- }
- }
- if (toRemove !== null) {
- props.remove(toRemove);
- }
- }
-
- /**
- * Template method which generate getter methods for IMPL class.
- *
- * @return string with getter methods
- */
- def private generateGetters(boolean addOverride) '''
- «IF keyType !== null»
- «IF addOverride»@«Override.importedName»«ENDIF»
- public «keyType.importedName» «BindingMapping.IDENTIFIABLE_KEY_NAME»() {
- return key;
- }
-
- «ENDIF»
- «IF !properties.empty»
- «FOR field : properties SEPARATOR '\n'»
- «IF addOverride»@«Override.importedName»«ENDIF»
- «field.getterMethod»
- «ENDFOR»
- «ENDIF»
- «IF augmentType !== null»
-
- @SuppressWarnings("unchecked")
- «IF addOverride»@«Override.importedName»«ENDIF»
- public <E extends «augmentType.importedName»> E «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName»<E> augmentationType) {
- return (E) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
- }
- «ENDIF»
- '''
-
- /**
- * Template method which generates the method <code>hashCode()</code>.
- *
- * @return string with the <code>hashCode()</code> method definition in JAVA format
- */
- def protected generateHashCode() '''
- «IF !properties.empty || augmentType !== null»
- private int hash = 0;
- private volatile boolean hashValid = false;
-
- @«Override.importedName»
- public int hashCode() {
- if (hashValid) {
- return hash;
- }
-
- final int prime = 31;
- int result = 1;
- «FOR property : properties»
- «IF property.returnType.name.contains("[")»
- result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
- «ELSE»
- result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
- «ENDIF»
- «ENDFOR»
- «IF augmentType !== null»
- result = prime * result + «Objects.importedName».hashCode(«AUGMENTATION_FIELD»);
- «ENDIF»
-
- hash = result;
- hashValid = true;
- return result;
- }
- «ENDIF»
- '''
-
- /**
- * Template method which generates the method <code>equals()</code>.
- *
- * @return string with the <code>equals()</code> method definition in JAVA format
- */
- def protected generateEquals() '''
- «IF !properties.empty || augmentType !== null»
- @«Override.importedName»
- public boolean equals(«Object.importedName» obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof «DataObject.importedName»)) {
- return false;
- }
- if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
- return false;
- }
- «type.importedName» other = («type.importedName»)obj;
- «FOR property : properties»
- «val fieldName = property.fieldName»
- «IF property.returnType.name.contains("[")»
- if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
- «ELSE»
- if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
- «ENDIF»
- return false;
- }
- «ENDFOR»
- «IF augmentType !== null»
- if (getClass() == obj.getClass()) {
- // Simple case: we are comparing against self
- «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
- if (!«Objects.importedName».equals(«AUGMENTATION_FIELD», otherImpl.«AUGMENTATION_FIELD»)) {
- return false;
- }
- } else {
- // Hard case: compare our augments with presence there...
- for («Map.importedName».Entry<«Class.importedName»<? extends «augmentType.importedName»>, «augmentType.importedName»> e : «AUGMENTATION_FIELD».entrySet()) {
- if (!e.getValue().equals(other.«AUGMENTABLE_AUGMENTATION_NAME»(e.getKey()))) {
- return false;
- }
- }
- // .. and give the other one the chance to do the same
- if (!obj.equals(this)) {
- return false;
- }
- }
- «ENDIF»
- return true;
- }
- «ENDIF»
- '''
-
- override generateToString(Collection<GeneratedProperty> properties) '''
- «IF properties !== null»
- @«Override.importedName»
- public «String.importedName» toString() {
- final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper("«type.name»");
- «FOR property : properties»
- «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
- «ENDFOR»
- «IF augmentType !== null»
- «CodeHelpers.importedName».appendValue(helper, "«AUGMENTATION_FIELD»", «AUGMENTATION_FIELD».values());
- «ENDIF»
- return helper.toString();
- }
- «ENDIF»
- '''
-
- def implementedInterfaceGetter() '''
- @«Override.importedName»
- public «Class.importedName»<«type.importedName»> getImplementedInterface() {
- return «type.importedName».class;
- }
- '''
-
private def createDescription(GeneratedType type) {
return '''
Class that builds {@link «type.importedName»} instances.
super(genType);
}
- TopLevelJavaGeneratedType(final JavaTypeName name, final GeneratedType genType) {
- super(name, genType);
- }
-
@Override
String localTypeName(@NonNull final JavaTypeName type) {
// Locally-anchored type, this is simple: just strip the first local name component and concat the others
return new «String.importedName»(«field»);
«ELSEIF propRet.fullyQualifiedName.startsWith("java.lang") || propRet instanceof Enumeration
|| propRet.fullyQualifiedName.startsWith("java.math")»
- ««« type int*, uint, decimal64 or enumeration*
+ ««« type int*, uint, decimal64 or enumeration*
return «field».toString();
- «ELSEIF propRet instanceof GeneratedTransferObject
- && (propRet as GeneratedTransferObject).unionType»
+ «ELSEIF propRet instanceof GeneratedTransferObject && (propRet as GeneratedTransferObject).unionType»
««« union type
return «field».stringValue();
«ELSEIF propRet instanceof GeneratedTransferObject // Is it a GeneratedTransferObject
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
-import java.lang.reflect.Field;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
import org.junit.Test;
-import org.opendaylight.mdsal.binding.model.api.GeneratedProperty;
import org.opendaylight.mdsal.binding.model.api.GeneratedType;
import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
import org.opendaylight.mdsal.binding.model.api.MethodSignature;
return genType;
}
- @SuppressWarnings("unchecked")
private static CharSequence genToString(final GeneratedType genType) {
- try {
- final BuilderTemplate bt = new BuilderTemplate(genType);
- final Field propertiesField = bt.getClass().getDeclaredField(PROPERTIES_FIELD_NAME);
- propertiesField.setAccessible(true);
- return bt.generateToString((Collection<GeneratedProperty>) propertiesField.get(bt));
- } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
- throw new RuntimeException(e);
- }
+ final BuilderTemplate bt = BuilderGenerator.templateForType(genType);
+ return bt.generateToString(bt.properties);
}
private static GeneratedType mockGenType(final String methodeName) {
CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
}
+ @Test
+ public void testMdsal365() throws Exception {
+ final File sourcesOutputDir = CompilationTestUtils.generatorOutput("mdsal365");
+ final File compiledOutputDir = CompilationTestUtils.compilerOutput("mdsal365");
+ generateTestSources("/compilation/mdsal365", sourcesOutputDir);
+ CompilationTestUtils.testCompilation(sourcesOutputDir, compiledOutputDir);
+ CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
+ }
+
@Test
public void classNamesColisionTest() throws Exception {
final File sourcesOutputDir = CompilationTestUtils.generatorOutput("class-name-collision");
--- /dev/null
+module mdsal-365 {
+ namespace "mdsal-365";
+ prefix "mdsal365";
+
+ grouping special-next-hop-grouping {
+ leaf special-next-hop {
+ type enumeration {
+ enum blackhole {
+ description "Silently discard the packet.";
+ }
+ }
+ }
+ }
+
+ choice next-hop-options {
+ case special-next-hop {
+ uses special-next-hop-grouping;
+ }
+ }
+}
+