#128 Adding support for enum mapping methods

This commit is contained in:
Gunnar Morling 2014-02-23 17:40:19 +01:00
parent 5e19394e69
commit f0f3335e28
14 changed files with 490 additions and 29 deletions

View File

@ -22,30 +22,31 @@ import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Configures the mapping of one bean attribute.
* Configures the mapping of one bean attribute or enum constant.
*
* @author Gunnar Morling
*/
public @interface Mapping {
/**
* The source name of the configured property as defined by the JavaBeans specification.
* The source name of the configured property as defined by the JavaBeans specification. If used to map an enum
* constant, the name of the constant member is to be given.
*
* @return The source name of the configured property.
* @return The source name of the configured property or enum constant
*/
String source();
/**
* The target name of the configured property as defined by the JavaBeans specification. Defaults to the
* source name if not given.
* The target name of the configured property as defined by the JavaBeans specification. Defaults to the source name
* if not given. If used to map an enum constant, the name of the constant member is to be given.
*
* @return The target name of the configured property.
* @return The target name of the configured property or enum constant
*/
String target() default "";
/**
* A format string as processable by {@link SimpleDateFormat} if the attribute is mapped from {@code String} to
* {@link Date} or vice-versa. Will be ignored for all other attribute types.
* {@link Date} or vice-versa. Will be ignored for all other attribute types and when mapping enum constants.
*
* @return A date format string as processable by {@link SimpleDateFormat}.
*/

View File

@ -0,0 +1,49 @@
/**
* Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.model;
import java.util.List;
import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.source.EnumMapping;
import org.mapstruct.ap.model.source.Method;
/**
* A {@link MappingMethod} which maps one enum type to another, optionally configured by one or more
* {@link EnumMapping}s.
*
* @author Gunnar Morling
*/
public class EnumMappingMethod extends MappingMethod {
private final List<EnumMapping> enumMappings;
public EnumMappingMethod(Method method, List<EnumMapping> enumMappings) {
super( method );
this.enumMappings = enumMappings;
}
public List<EnumMapping> getEnumMappings() {
return enumMappings;
}
public Parameter getSourceParameter() {
return getParameters().iterator().next();
}
}

View File

@ -18,10 +18,13 @@
*/
package org.mapstruct.ap.model.common;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
@ -57,6 +60,7 @@ public class Type extends ModelElement implements Comparable<Type> {
private final boolean isCollectionType;
private final boolean isMapType;
private final boolean isImported;
private final List<String> enumConstants;
//CHECKSTYLE:OFF
public Type(Types typeUtils, TypeMirror typeMirror, TypeElement typeElement, List<Type> typeParameters,
@ -77,6 +81,19 @@ public class Type extends ModelElement implements Comparable<Type> {
this.isCollectionType = isCollectionType;
this.isMapType = isMapType;
this.isImported = isImported;
if ( isEnumType ) {
enumConstants = new ArrayList<String>();
for ( Element element : typeElement.getEnclosedElements() ) {
if ( element.getKind() == ElementKind.ENUM_CONSTANT ) {
enumConstants.add( element.getSimpleName().toString() );
}
}
}
else {
enumConstants = Collections.emptyList();
}
}
//CHECKSTYLE:ON
@ -112,6 +129,13 @@ public class Type extends ModelElement implements Comparable<Type> {
return isEnumType;
}
/**
* Returns this type's enum constants in case it is an enum, an empty list otherwise.
*/
public List<String> getEnumConstants() {
return enumConstants;
}
/**
* Returns the implementation type to be instantiated in case this type is an interface iterable, collection or map
* type. The type will have the correct type arguments, so if this type e.g. represents {@code Set<String>}, the

View File

@ -0,0 +1,49 @@
/**
* Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.model.source;
/**
* Represents the mapping between one enum constant and another.
*
* @author Gunnar Morling
*/
public class EnumMapping {
private final String source;
private final String target;
public EnumMapping(String source, String target) {
this.source = source;
this.target = target;
}
/**
* Returns the name of the constant in the source enum.
*/
public String getSource() {
return source;
}
/**
* Returns the name of the constant in the target enum.
*/
public String getTarget() {
return target;
}
}

View File

@ -22,7 +22,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.util.Types;
@ -61,27 +60,28 @@ public class SourceMethod implements Method {
public static SourceMethod forMethodRequiringImplementation(ExecutableElement executable,
List<Parameter> parameters,
Type returnType, Map<String,
List<Mapping>> mappings,
List<Mapping>> mappings,
IterableMapping iterableMapping,
MapMapping mapMapping,
Types typeUtils ) {
Types typeUtils) {
return new SourceMethod(
null,
executable,
parameters,
returnType,
mappings,
iterableMapping,
mapMapping,
typeUtils );
null,
executable,
parameters,
returnType,
mappings,
iterableMapping,
mapMapping,
typeUtils
);
}
public static SourceMethod forReferencedMethod(Type declaringMapper,
ExecutableElement executable,
List<Parameter> parameters,
Type returnType,
Types typeUtils ) {
Types typeUtils) {
return new SourceMethod(
declaringMapper,
@ -96,7 +96,7 @@ public class SourceMethod implements Method {
}
public static SourceMethod forFactoryMethod(Type declaringMapper, ExecutableElement executable,
Type returnType, Types typeUtils) {
Type returnType, Types typeUtils) {
return new SourceMethod(
declaringMapper,
@ -117,7 +117,7 @@ public class SourceMethod implements Method {
Map<String, List<Mapping>> mappings,
IterableMapping iterableMapping,
MapMapping mapMapping,
Types typeUtils ) {
Types typeUtils) {
this.declaringMapper = declaringMapper;
this.executable = executable;
this.parameters = parameters;
@ -213,6 +213,9 @@ public class SourceMethod implements Method {
return accessibility;
}
/**
* Returns the {@link Mapping}s configured for this method, keyed by source property name.
*/
public Map<String, List<Mapping>> getMappings() {
return mappings;
}
@ -264,6 +267,11 @@ public class SourceMethod implements Method {
&& getResultType().isMapType();
}
public boolean isEnumMapping() {
return getSourceParameters().size() == 1 && getSourceParameters().iterator().next().getType().isEnumType()
&& getResultType().isEnumType();
}
/**
* Whether this method is configured by itself or by the corresponding reverse mapping method.
*
@ -291,7 +299,10 @@ public class SourceMethod implements Method {
return sb.toString();
}
public Mapping getMapping(String targetPropertyName) {
/**
* Returns the {@link Mapping} for the given target property. May return {@code null}.
*/
public Mapping getMappingByTargetPropertyName(String targetPropertyName) {
for ( Map.Entry<String, List<Mapping>> entry : mappings.entrySet() ) {
for ( Mapping mapping : entry.getValue() ) {
if ( mapping.getTargetName().equals( targetPropertyName ) ) {
@ -325,8 +336,8 @@ public class SourceMethod implements Method {
* {@inheritDoc} {@link Method}
*/
@Override
public boolean matches( Type sourceType, Type targetType ) {
MethodMatcher matcher = new MethodMatcher(typeUtils, this );
public boolean matches(Type sourceType, Type targetType) {
MethodMatcher matcher = new MethodMatcher( typeUtils, this );
return matcher.matches( sourceType, targetType );
}

View File

@ -26,7 +26,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.Messager;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
@ -41,6 +40,7 @@ import org.mapstruct.ap.conversion.ConversionProvider;
import org.mapstruct.ap.conversion.Conversions;
import org.mapstruct.ap.model.BeanMappingMethod;
import org.mapstruct.ap.model.DefaultMapperReference;
import org.mapstruct.ap.model.EnumMappingMethod;
import org.mapstruct.ap.model.IterableMappingMethod;
import org.mapstruct.ap.model.MapMappingMethod;
import org.mapstruct.ap.model.Mapper;
@ -55,6 +55,7 @@ import org.mapstruct.ap.model.common.DefaultConversionContext;
import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.common.TypeFactory;
import org.mapstruct.ap.model.source.EnumMapping;
import org.mapstruct.ap.model.source.Mapping;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
@ -184,7 +185,6 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
continue;
}
SourceMethod reverseMappingMethod = getReverseMappingMethod( methods, method );
boolean hasFactoryMethod = false;
@ -207,6 +207,19 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
hasFactoryMethod = mapMappingMethod.getFactoryMethod() != null;
mappingMethods.add( mapMappingMethod );
}
else if ( method.isEnumMapping() ) {
if ( method.getMappings().isEmpty() ) {
if ( reverseMappingMethod != null && !reverseMappingMethod.getMappings().isEmpty() ) {
method.setMappings( reverse( reverseMappingMethod.getMappings() ) );
}
}
MappingMethod enumMappingMethod = getEnumMappingMethod( method );
if ( enumMappingMethod != null ) {
mappingMethods.add( enumMappingMethod );
}
}
else {
if ( method.getMappings().isEmpty() ) {
if ( reverseMappingMethod != null && !reverseMappingMethod.getMappings().isEmpty() ) {
@ -305,7 +318,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
ExecutableElement targetAcessor,
Parameter parameter) {
String targetPropertyName = Executables.getPropertyName( targetAcessor );
Mapping mapping = method.getMapping( targetPropertyName );
Mapping mapping = method.getMappingByTargetPropertyName( targetPropertyName );
String dateFormat = mapping != null ? mapping.getDateFormat() : null;
String sourcePropertyName = mapping != null ? mapping.getSourcePropertyName() : targetPropertyName;
TypeElement parameterElement = parameter.getType().getTypeElement();
@ -351,6 +364,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
private BeanMappingMethod getBeanMappingMethod(List<MapperReference> mapperReferences, List<SourceMethod> methods,
SourceMethod method, ReportingPolicy unmappedTargetPolicy) {
List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
Set<String> mappedTargetProperties = new HashSet<String>();
@ -371,7 +385,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
for ( ExecutableElement targetAccessor : targetAccessors ) {
String targetPropertyName = Executables.getPropertyName( targetAccessor );
Mapping mapping = method.getMapping( targetPropertyName );
Mapping mapping = method.getMappingByTargetPropertyName( targetPropertyName );
PropertyMapping propertyMapping = null;
if ( mapping != null && mapping.getSourceParameterName() != null ) {
@ -740,6 +754,30 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
);
}
private EnumMappingMethod getEnumMappingMethod(SourceMethod method) {
List<EnumMapping> enumMappings = new ArrayList<EnumMapping>();
List<String> sourceEnumConstants = method.getSourceParameters().iterator().next().getType().getEnumConstants();
Map<String, List<Mapping>> mappings = method.getMappings();
for ( String enumConstant : sourceEnumConstants ) {
List<Mapping> mappedConstants = mappings.get( enumConstant );
if ( mappedConstants == null ) {
enumMappings.add( new EnumMapping( enumConstant, enumConstant ) );
}
else if ( mappedConstants.size() == 1 ) {
enumMappings.add( new EnumMapping( enumConstant, mappedConstants.iterator().next().getTargetName() ) );
}
else {
//TODO Raise error
}
}
return new EnumMappingMethod( method, enumMappings );
}
private TypeConversion getConversion(Type sourceType, Type targetType, String dateFormat, String sourceReference) {
ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType );

View File

@ -29,6 +29,7 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.model.common.Parameter;
@ -46,7 +47,6 @@ import org.mapstruct.ap.prism.MappingsPrism;
import org.mapstruct.ap.util.AnnotationProcessingException;
import static javax.lang.model.util.ElementFilter.methodsIn;
import javax.lang.model.util.Types;
/**
* A {@link ModelElementProcessor} which retrieves a list of {@link SourceMethod}s
@ -267,6 +267,24 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
return false;
}
if ( parameterType.isEnumType() && !resultType.isEnumType() ) {
messager.printMessage(
Kind.ERROR,
"Can't generate mapping method from enum type to non-enum type.",
method
);
return false;
}
if ( !parameterType.isEnumType() && resultType.isEnumType() ) {
messager.printMessage(
Kind.ERROR,
"Can't generate mapping method from non-enum type to enum type.",
method
);
return false;
}
return true;
}

View File

@ -0,0 +1,38 @@
<#--
Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
and/or other contributors as indicated by the @authors tag. See the
copyright.txt file in the distribution for a full listing of all
contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
@Override
public <@includeModel object=returnType/> ${name}(<@includeModel object=sourceParameter/>) {
if ( ${sourceParameter.name} == null ) {
return null;
}
<@includeModel object=resultType/> ${resultName};
switch ( ${sourceParameter.name} ) {
<#list enumMappings as enumMapping>
case ${enumMapping.source}: ${resultName} = <@includeModel object=returnType/>.${enumMapping.target};
break;
</#list>
default: throw new IllegalArgumentException( "Unexpected enum constant: " + ${sourceParameter.name} );
}
return ${resultName};
}

View File

@ -0,0 +1,67 @@
/**
* Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.enums;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.MapperTestBase;
import org.mapstruct.ap.testutil.WithClasses;
import org.testng.annotations.Test;
import static org.fest.assertions.Assertions.assertThat;
/**
* Test for the generation and invocation of enum mapping methods.
*
* @author Gunnar Morling
*/
@IssueKey("128")
@WithClasses({ OrderMapper.class, OrderEntity.class, OrderType.class, OrderDto.class, ExternalOrderType.class })
public class EnumMappingTest extends MapperTestBase {
@Test
public void shouldGenerateEnumMappingMethod() {
ExternalOrderType target = OrderMapper.INSTANCE.orderTypeToExternalOrderType( OrderType.B2B );
assertThat( target ).isEqualTo( ExternalOrderType.B2B );
target = OrderMapper.INSTANCE.orderTypeToExternalOrderType( OrderType.RETAIL );
assertThat( target ).isEqualTo( ExternalOrderType.RETAIL );
}
@Test
public void shouldConsiderConstantMappings() {
ExternalOrderType target = OrderMapper.INSTANCE.orderTypeToExternalOrderType( OrderType.EXTRA );
assertThat( target ).isEqualTo( ExternalOrderType.SPECIAL );
target = OrderMapper.INSTANCE.orderTypeToExternalOrderType( OrderType.STANDARD );
assertThat( target ).isEqualTo( ExternalOrderType.DEFAULT );
target = OrderMapper.INSTANCE.orderTypeToExternalOrderType( OrderType.NORMAL );
assertThat( target ).isEqualTo( ExternalOrderType.DEFAULT );
}
@Test
public void shouldInvokeEnumMappingMethodForPropertyMapping() {
OrderEntity order = new OrderEntity();
order.setOrderType( OrderType.EXTRA );
OrderDto orderDto = OrderMapper.INSTANCE.orderEntityToDto( order );
assertThat( orderDto ).isNotNull();
assertThat( orderDto.getOrderType() ).isEqualTo( ExternalOrderType.SPECIAL );
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.enums;
/**
* @author Gunnar Morling
*/
public enum ExternalOrderType {
RETAIL, B2B, SPECIAL, DEFAULT
}

View File

@ -0,0 +1,35 @@
/**
* Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.enums;
/**
* @author Gunnar Morling
*/
public class OrderDto {
private ExternalOrderType orderType;
public ExternalOrderType getOrderType() {
return orderType;
}
public void setOrderType(ExternalOrderType orderType) {
this.orderType = orderType;
}
}

View File

@ -0,0 +1,35 @@
/**
* Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.enums;
/**
* @author Gunnar Morling
*/
public class OrderEntity {
private OrderType orderType;
public OrderType getOrderType() {
return orderType;
}
public void setOrderType(OrderType orderType) {
this.orderType = orderType;
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.enums;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
/**
* @author Gunnar Morling
*/
@Mapper
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class );
OrderDto orderEntityToDto(OrderEntity order);
@Mappings({
@Mapping(source = "EXTRA", target = "SPECIAL"),
@Mapping(source = "STANDARD", target = "DEFAULT"),
@Mapping(source = "NORMAL", target = "DEFAULT")
})
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}

View File

@ -0,0 +1,27 @@
/**
* Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.enums;
/**
* @author Gunnar Morling
*/
public enum OrderType {
RETAIL, B2B, EXTRA, STANDARD, NORMAL
}