/*
* EuroCarbDB, a framework for carbohydrate bioinformatics
*
* Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
* A copy of this license accompanies this distribution in the file LICENSE.txt.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* Last commit: $Rev: 1147 $ by $Author: glycoslave $ on $Date:: 2009-06-04 #$
*/
package org.eurocarbdb.dataaccess.hibernate;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import org.hibernate.HibernateException;
import org.hibernate.type.NullableType;
import org.hibernate.type.TypeFactory;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
/**
* Hibernate {@link UserType} to support the use of
* <a href="http://java.sun.com/j2se/1.5.0/docs/guide/language/enums.html">Java 1.5 enums</a>.
*
* A large part of the code for this class was copied from
* <a href="http://www.hibernate.org/272.html">http://www.hibernate.org/272.html</a>.
*
*<h4>Simple example mapping for a String-based Enum - inline <type> tag</h4>
*<pre>
* <class ...>
* <property name='suit'>
* <column ... />
* <type name="EnumUserType">
* <param name="enumClassName">com.company.project.Suit</param>
* </type>
* </property>
* </class>
*</pre>
*
*<h4>Simple example mapping for a String-based Enum - using <typedef></h4>
*<pre>
* <typedef name="suit" class='EnumUserType'>
* <param name="enumClassName">com.company.project.Suit</param>
* </typedef>
*
* <class ...>
* <property name='suit' type='suit'>
* <column .../>
* </property>
* </class>
*</pre>
*
*<h4>Example Mapping for a non-String-based Enum</h4>
*<p>
* If the mapping of the enum to the SQL column type is not String (for example,
* you may want your enum class to map to a char or int column), then you need
* a small amount of extra configuration.
*</p>
*<p>
*<ul>
* <li>The enum type being represented by the certain user type must be set
* by the 'enumClass' property.</li>
* <li>The identifier representing the enum value is retrieved by the method named
* by the '<tt>identifierMethod</tt>' property. That is, given an Enum object,
* this is the method that returns the corresponding SQL value of the given
* {@link Enum} object. If not specified, the enum's value is determined by
* the {@link Enum#name} method. Identifier type (Class) is automatically
* determined by the return-type of the identifierMethod.</li>
* <li>Similarly, the value of the property '<tt>valueOfMethod</tt>' must provide
* the name of a method that returns the correct Enum object for a given identifier.
* That is, given an SQL value, this method must returning the corresponding
* Enum object. The default valueOfMethod method is {@link Enum#valueOf}.</li>
*</p>
*<p>
* Example of an enum type represented by an int value:
*<pre><code>
* public enum SimpleNumber
* {
* Unknown(-1), Zero(0), One(1), Two(2), Three(3);
* int value;
*
* protected SimpleNumber(int value)
* {
* this.value = value;
* }
*
* public int toInt() { return value; }
*
* public static SimpleNumber fromInt( int value )
* {
* switch(value)
* {
* case 0: return Zero;
* case 1: return One;
* case 2: return Two;
* case 3: return Three;
* default: return Unknown;
* }
* }
* }
*</code>
*</pre>
*</p>
*<p>
*The corresponding hibernate mapping config would look like this:
*<pre><code>
* <typedef name="SimpleNumber" class="GenericEnumUserType">
* <param name="enumClass">SimpleNumber</param>
* <param name="identifierMethod">toInt</param>
* <param name="valueOfMethod">fromInt</param>
* </typedef>
* <class ...>
* ...
* <property name="number" column="number" type="SimpleNumber"/>
* </class>
*</code></pre>
*</p>
* @see http://www.hibernate.org/272.html
* @see http://java.sun.com/j2se/1.5.0/docs/guide/language/enums.html
* @author mjh
*/
public class GenericEnumUserType implements UserType, ParameterizedType
{
private static final String DEFAULT_IDENTIFIER_METHOD_NAME = "name";
private static final String DEFAULT_VALUE_OF_METHOD_NAME = "valueOf";
private Class<? extends Enum> enumClass;
private Class<?> identifierType;
private Method identifierMethod;
private Method valueOfMethod;
private NullableType type;
private int[] sqlTypes;
/**
* Called by hibernate with all propertiies given as params in
* a given mapping. Valid values for this class are:
* enumClassName, identifierMethod, and valueOfMethod, as described
* in the class preamble.
*/
public void setParameterValues(Properties parameters)
{
String enumClassName = parameters.getProperty("enumClassName");
try
{
enumClass = Class.forName( enumClassName ).asSubclass( Enum.class );
}
catch (ClassNotFoundException cfne)
{
throw new HibernateException(
"Enum class given by property 'enumClassName' not found", cfne );
}
String identifierMethodName
= parameters.getProperty(
"identifierMethod", DEFAULT_IDENTIFIER_METHOD_NAME);
try
{
identifierMethod = enumClass.getMethod( identifierMethodName, new Class[0] );
identifierType = identifierMethod.getReturnType();
}
catch ( Exception e )
{
throw new HibernateException(
"Failed to obtain identifier method named '"
+ identifierMethodName
+ "' in class '"
+ enumClass.getName()
+ "' given by property 'identifierMethod'"
, e
);
}
type = (NullableType) TypeFactory.basic( identifierType.getName() );
if (type == null)
throw new HibernateException(
"Unsupported identifier type " + identifierType.getName());
sqlTypes = new int[] { type.sqlType() };
String valueOfMethodName
= parameters.getProperty(
"valueOfMethod", DEFAULT_VALUE_OF_METHOD_NAME);
try
{
valueOfMethod = enumClass.getMethod( valueOfMethodName, new Class[] { identifierType });
}
catch (Exception e)
{
throw new HibernateException(
"Failed to obtain a method named '"
+ valueOfMethodName
+ "', which accepts an argument of type '"
+ identifierType
+ "' in class '"
+ enumClass.getName()
+ "' for property 'valueOfMethod'"
, e
);
}
}
public Class returnedClass()
{
return enumClass;
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException
{
Object identifier = type.get(rs, names[0]);
if (rs.wasNull())
{
return null;
}
try
{
return valueOfMethod.invoke(enumClass, new Object[] { identifier });
}
catch (Exception e)
{
throw new HibernateException(
"Exception while invoking valueOf method '"
+ valueOfMethod.getName()
+ "' of enumeration class '"
+ enumClass
+ "'"
, e
);
}
}
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException
{
try
{
if (value == null)
{
st.setNull( index, type.sqlType() );
}
else
{
Object identifier = identifierMethod.invoke( value, new Object[0] );
type.set( st, identifier, index );
}
}
catch (Exception e)
{
throw new HibernateException(
"Exception while invoking identifierMethod '"
+ identifierMethod.getName()
+ "' of enumeration class '"
+ enumClass
+ "'"
, e
);
}
}
public int[] sqlTypes()
{
return sqlTypes;
}
public Object assemble(Serializable cached, Object owner)
throws HibernateException
{
return cached;
}
public Object deepCopy(Object value) throws HibernateException
{
return value;
}
public Serializable disassemble(Object value) throws HibernateException
{
return (Serializable) value;
}
public boolean equals(Object x, Object y) throws HibernateException
{
return x == y;
}
public int hashCode(Object x) throws HibernateException
{
return x.hashCode();
}
public boolean isMutable()
{
return false;
}
public Object replace(Object original, Object target, Object owner)
throws HibernateException
{
return original;
}
}