How to use enum with different values to the enumeration names

1,859 views
Skip to first unread message

dav...@gmail.com

unread,
Apr 13, 2013, 9:29:34 PM4/13/13
to bea...@googlegroups.com
Hi, Keven

I have a question or enhancement request to handle enum with different values to the enum names.

For example, I have a enum class AccountNumberTypeEnum. The enum names have CARD_NUMBER, PENDING_APPLICATION, AAA_NUMBER... with the value of C, P, A, and etc. I am currently getting the error as below when trying to unmarshall the value "C" into CARD_NUMBER.

org.beanio.InvalidRecordException: Invalid 'Input' record
 ==> Invalid 'accountNumberType':  Type conversion error: Invalid AccountNumberTypeEnum enum value 'C'
    at org.beanio.internal.parser.UnmarshallingContext.validate(UnmarshallingContext.java:200)
    at org.beanio.internal.parser.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:203)
    at org.beanio.internal.parser.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:88)

This is my enum class...

public enum AccountNumberTypeEnum {
    CARD_NUMBER("C"),
    PENDING_APPLICATION("P"),
    AAA_NUMBER("A"),
    FREQUENT_FLYER_NUMBER("F"),
    ACCOUNT_IDENTIFIER("I");

    private final String value;

    AccountNumberTypeEnum(String v) {
        value = v;
    }

    public String value() {
        return value;
    }

    public static AccountNumberTypeEnum fromValue(String v) {
        for (AccountNumberTypeEnum c: AccountNumberTypeEnum.values()) {
            if (c.value.equals(v)) {
                return c;
            }
        }
        throw new IllegalArgumentException(v);
    }
}

dav...@gmail.com

unread,
Apr 13, 2013, 9:31:46 PM4/13/13
to bea...@googlegroups.com
This enum is created by xjc from an enumeration simpleType. Basically I need to use the static method fromValue("C") to get the enum type when unmarshalling "C" into CARD_NUMBER.

Kevin

unread,
Apr 14, 2013, 9:13:42 PM4/14/13
to bea...@googlegroups.com
Hello,

If enum values don't exactly match, you have 2 options:

1.  Add a toString() method to the enum that returns the values that will be used in the stream, and set format="toString" on the field configuration.

2.  Or register your own TypeHandler implementation for the enum.

Thanks,
Kevin

dav...@gmail.com

unread,
Apr 14, 2013, 9:39:49 PM4/14/13
to bea...@googlegroups.com
Thanks Kevin for your reply.

The 1st option is impossible for me. My enum is built by xjc in maven jaxb plugin against simpleType defined in xsd. I am looking at option 2, but I want to have a generic enum type handler for all my enums with different values. I am trying option 2 by copying default EnumTypeHandler, but I am getting the exception.

org.beanio.BeanIOConfigurationException: Failed to create type handler named 'enumHandler'
at org.beanio.internal.compiler.StreamCompiler.createTypeHandlerFactory(StreamCompiler.java:216)
at org.beanio.internal.compiler.StreamCompiler.createStreamDefinitions(StreamCompiler.java:142)
at org.beanio.internal.compiler.StreamCompiler.loadMapping(StreamCompiler.java:103)
at org.beanio.internal.DefaultStreamFactory.load(DefaultStreamFactory.java:58)
at org.beanio.StreamFactory.load(StreamFactory.java:267)
at org.beanio.StreamFactory.load(StreamFactory.java:243)
at org.beanio.StreamFactory.load(StreamFactory.java:232)
at com.chase.ccs.ess.mw.cpsca.messaging.CAAATest.testUnMarshallCAAAInput(CAAATest.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:69)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:48)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:292)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.eclipse.jdt.internal.launching.LongCommandLineLauncher.main(LongCommandLineLauncher.java:43)
Caused by: org.beanio.BeanIOConfigurationException: Cound not instantiate class 'class com.chase.ccs.ess.mw.cpsca.beanio.handler.EnumTypeHandler'
at org.beanio.internal.util.BeanUtil.createBean(BeanUtil.java:95)
at org.beanio.internal.util.BeanUtil.createBean(BeanUtil.java:60)
at org.beanio.internal.compiler.StreamCompiler.createTypeHandlerFactory(StreamCompiler.java:212)
... 35 more
Caused by: java.lang.InstantiationException: com.chase.ccs.ess.mw.cpsca.beanio.handler.EnumTypeHandler
at java.lang.Class.newInstance0(Class.java:340)
at java.lang.Class.newInstance(Class.java:308)
at org.beanio.internal.util.BeanUtil.createBean(BeanUtil.java:92)
... 37 more

Here is my EnumTypeHandler by copying default EnumTypeHandler and changed parse to take fromValue() instead of Enum.valueOf()... Please advise

import org.beanio.types.TypeConversionException;
import org.beanio.types.TypeHandler;

@SuppressWarnings({"unchecked", "rawtypes"})
public class EnumTypeHandler implements TypeHandler {

    private Class<Enum> type;
    
    /**
     * Constructs a new <tt>EnumTypeHandler</tt>.
     * @param type the Enum class
     */
    public EnumTypeHandler(Class<Enum> type) {
        this.type = type;
    }
    
    /*
     * (non-Javadoc)
     * @see org.beanio.types.TypeHandler#parse(java.lang.String)
     */
    public Object parse(String text) throws TypeConversionException {
        if (text == null || "".equals(text)) {
            return null;
        }
        try {
        Method m = type.getDeclaredMethod("fromValue", String.class);
        return m.invoke(null, text);
        }
        catch (IllegalArgumentException ex) {
            throw new TypeConversionException("Invalid " + getType().getSimpleName() + 
                " enum value '" + text + "'", ex);
        }
        catch (Exception ex) {
        throw new TypeConversionException("Enum reflection error ", ex);
        }
    }

    /*
     * (non-Javadoc)
     * @see org.beanio.types.TypeHandler#format(java.lang.Object)
     */
    public String format(Object value) {
        if (value == null) {
            return null;
        }
        return ((Enum)value).name();
    }

    /*
     * (non-Javadoc)
     * @see org.beanio.types.TypeHandler#getType()
     */
    public Class<?> getType() {
        return type;
    }

public void setType(Class<Enum> type) {
this.type = type;
}
}


Thanks

-Lei 

dav...@gmail.com

unread,
Apr 14, 2013, 9:58:18 PM4/14/13
to bea...@googlegroups.com
Kevin, I figure out the exception because default EnumTypeHandler does not have a typical java bean constructor without arguments. I added in my EnumTypeHandler, but I am still see some errors when I am trying to use it for unmarshalling. Here is my mapping.xml

  <stream name="AAAFile" format="fixedlength">
    <typeHandler name="enumHandler" class="my.handler.EnumTypeHandler" />
    <record name="AAAInput" class="message.v20130701.AAAInput">
 <field name="accNumberType" length="1" typeHandler="enumHandler" type="common.v20130701.AccountNumberTypeEnum"/>
    </record>
  </stream>

Here is the update EnumTypeHandler

package my.handler;

import java.lang.reflect.Method;

import org.beanio.types.TypeConversionException;
import org.beanio.types.TypeHandler;

@SuppressWarnings({"unchecked", "rawtypes"})
public class EnumTypeHandler implements TypeHandler {

    public EnumTypeHandler() {
super();
// TODO Auto-generated constructor stub

I am seeing the NPE as below,

java.lang.NullPointerException
at java.lang.Class.isAssignableFrom(Native Method)
at org.beanio.internal.util.TypeUtil.isAssignable(TypeUtil.java:57)
at org.beanio.internal.compiler.ParserFactorySupport.findTypeHandler(ParserFactorySupport.java:1361)
at org.beanio.internal.compiler.ParserFactorySupport.handleField(ParserFactorySupport.java:796)
at org.beanio.internal.compiler.ProcessorSupport.handleComponent(ProcessorSupport.java:139)
at org.beanio.internal.compiler.ProcessorSupport.handleComponent(ProcessorSupport.java:125)
at org.beanio.internal.compiler.ProcessorSupport.handleComponent(ProcessorSupport.java:109)
at org.beanio.internal.compiler.ProcessorSupport.process(ProcessorSupport.java:46)
at org.beanio.internal.compiler.ParserFactorySupport.createStream(ParserFactorySupport.java:92)
at org.beanio.internal.compiler.StreamCompiler.createStreamDefinitions(StreamCompiler.java:149)
at org.beanio.internal.compiler.StreamCompiler.loadMapping(StreamCompiler.java:103)
at org.beanio.internal.DefaultStreamFactory.load(DefaultStreamFactory.java:58)
at org.beanio.StreamFactory.load(StreamFactory.java:267)
at org.beanio.StreamFactory.load(StreamFactory.java:243)
at org.beanio.StreamFactory.load(StreamFactory.java:232)
...

Kevin

unread,
Apr 14, 2013, 10:11:06 PM4/14/13
to bea...@googlegroups.com
You're getting a NPE because the 'type' attribute is not set.  The TypeHandler interface wasn't really designed to handle multiple class/enum types in a single implementation (something I want to fix someday).  For now, you might be able to implement ConfigurableTypeHandler and pass the full enum class name using the 'format' attribute on the field.  Or else you can create register separate type handlers for each enum class and inject the type like this (you might have to use a String instead of a Class<?> argument for setType)...

<typeHandler type="xxx.Enum" class="my.handler.EnumTypeHandler">
    <property name="type" value="xxx.Enum" />
</typeHandler>

Thanks,
Kevin

dav...@gmail.com

unread,
Apr 15, 2013, 10:40:48 AM4/15/13
to bea...@googlegroups.com
Hi, Kevin
 
After evaluating the alternatives, I believe the best way is to give a format="fromValue", which will use another FromValueEnumTypeHandler, which use reflection to get the method fromValue, and invoke it to get the enumeration key from value.
 
 <record name="AAAInput" class="message.v20130701.AAAInput">
   <field name="accountNumberType" length="1" format="fromValue" type="com.jpmc.ca.ccs.ess.pega.works.tsys.common.v20130701.AccountNumberTypeEnum"/>
 </record>
 
The olnly changes will be in TyperHandlerFactory.java
 
    @SuppressWarnings("rawtypes")
    private TypeHandler getEnumHandler(Class<Enum> clazz, Properties properties) {
        String format = null;
        if (properties != null) {
            format = properties.getProperty("format");
        }
        if (format == null || "name".equals(format)) {
            return new EnumTypeHandler((Class<Enum>) clazz);
        }
        else if ("toString".equals(format)) {
            return new ToStringEnumTypeHandler((Class<Enum>) clazz);
        }
        else if ("fromValue".equal(format)) {
            return new FromValueEnumTypeHandler((Class<Enum>) clazz);
        }
        else {
            throw new BeanIOConfigurationException("Invalid format '" + format + "', " +
                "expected 'toString' or 'name' (default)");
        }
    }
 
FromValueEnumTypeHandler
 
package org.beanio.internal.util;
import java.lang.reflect.Method;
import org.beanio.types.*;
/**
* Default {@link Enum} type handler that uses {@link Enum#valueOf(Class, String)}
* to parse a value and {@link Enum#name()} to format a value.
*
 * @author Kevin Seim
* @since 2.0.1
*/

@SuppressWarnings({"unchecked", "rawtypes"})
public class FromValueEnumTypeHandler implements TypeHandler {
    private Class<Enum> type;
   
    /**
     * Constructs a new <tt>FromValueEnumTypeHandler</tt>.

     * @param type the Enum class
     */
    public FromValueEnumTypeHandler(Class<Enum> type) {
This will be the solution of all the generated enumerations by JAXB. Let me know your thought.
 
Here is the example of the JAXB generated enum
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlType;

/**
 * <p>Java class for AccountNumberTypeEnum.
 *
 * <p>The following schema fragment specifies the expected content contained within this class.
 * <p>
 * <pre>
 * &lt;simpleType name="AccountNumberTypeEnum">
 *   &lt;restriction base="{http://www.w3.org/2001/XMLSchema}string">
 *     &lt;enumeration value="C"/>
 *     &lt;enumeration value="P"/>
 *     &lt;enumeration value="A"/>
 *     &lt;enumeration value="F"/>
 *     &lt;enumeration value="I"/>
 *   &lt;/restriction>
 * &lt;/simpleType>
 * </pre>
 *
 */
@XmlType(name = "AccountNumberTypeEnum")
@XmlEnum
public enum AccountNumberTypeEnum {

    /**
     * Card Number
     *
     */
    @XmlEnumValue("C")
    CARD_NUMBER("C"),
    /**
     * Pending Application
     *
     */
    @XmlEnumValue("P")
    PENDING_APPLICATION("P"),
    /**
     * AAA Number
     *
     */
    @XmlEnumValue("A")
    AAA_NUMBER("A"),
    /**
     * Frequent Flyer Number
     *
     */
    @XmlEnumValue("F")
    FREQUENT_FLYER_NUMBER("F"),
    /**
     * Account Identifier
     *
     */
    @XmlEnumValue("I")

    ACCOUNT_IDENTIFIER("I");
    private final String value;
    AccountNumberTypeEnum(String v) {
        value = v;
    }
    public String value() {
        return value;
    }
    public static AccountNumberTypeEnum fromValue(String v) {
        for (AccountNumberTypeEnum c: AccountNumberTypeEnum.values()) {
            if (c.value.equals(v)) {
                return c;
            }
        }
        throw new IllegalArgumentException(v);
    }
}
 
Thanks
-Lei

Kevin

unread,
Apr 15, 2013, 10:55:49 AM4/15/13
to bea...@googlegroups.com
Agreed.  Long term I will probably allow you specify any static method in the format for an enum.  My backlog is getting pretty long, but I might be able to get that in 2.1.0.

Thanks,
Kevin
/**
* Default {...@link Enum} type handler that uses {...@link Enum#valueOf(Class, String)}
* to parse a value and {...@link Enum#name()} to format a value.

dav...@gmail.com

unread,
Apr 15, 2013, 11:54:09 AM4/15/13
to bea...@googlegroups.com
That's awesome. Hopefully it will be included in 2.1.0 to save my time of patching ;).

Thanks a lot.
Lei
Reply all
Reply to author
Forward
0 new messages