| //$Id: Example.java,v 1.5 2004/06/04 01:27:39 steveebersole Exp $ | ||
| package net.sf.hibernate.expression; | ||
| import java.util.ArrayList; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import net.sf.hibernate.HibernateException; | ||
| import net.sf.hibernate.engine.SessionFactoryImplementor; | ||
| import net.sf.hibernate.engine.TypedValue; | ||
| import net.sf.hibernate.metadata.ClassMetadata; | ||
| import net.sf.hibernate.type.AbstractComponentType; | ||
| import net.sf.hibernate.type.Type; | ||
| import net.sf.hibernate.util.StringHelper; | ||
| /** | ||
| * Support for query by example. | ||
| * <pre> | ||
| * List results = session.createCriteria(Parent.class) | ||
| * .add( Example.create(parent).ignoreCase() ) | ||
| * .createCriteria("child") | ||
| * .add( Example.create( parent.getChild() ) ) | ||
| * .list(); | ||
| * </pre> | ||
| * "Examples" may be mixed and matched with "Expressions" in the same <tt>Criteria</tt>. | ||
| * @see net.sf.hibernate.Criteria | ||
| * @author Gavin King | ||
| */ | ||
| public class Example extends AbstractCriterion { | ||
| private final Object entity; | ||
| 22x | private final Set excludedProperties = new HashSet(); | |
| private PropertySelector selector; | ||
| private boolean isLikeEnabled; | ||
| private boolean isIgnoreCaseEnabled; | ||
| private MatchMode matchMode; | ||
| /** | ||
| * A strategy for choosing property values for inclusion in the query | ||
| * criteria | ||
| */ | ||
| public static interface PropertySelector { | ||
| public boolean include(Object propertyValue, String propertyName, Type type); | ||
| } | ||
| private static final PropertySelector NOT_NULL = new NotNullPropertySelector(); | ||
| private static final PropertySelector ALL = new AllPropertySelector(); | ||
| private static final PropertySelector NOT_NULL_OR_ZERO = new NotNullOrZeroPropertySelector(); | ||
| 6x | static final class AllPropertySelector implements PropertySelector { | |
| public boolean include(Object object, String propertyName, Type type) { | ||
| 24x | return true; | |
| } | ||
| } | ||
| 6x | static final class NotNullPropertySelector implements PropertySelector { | |
| public boolean include(Object object, String propertyName, Type type) { | ||
| 2/2 128x | return object!=null; | |
| } | ||
| } | ||
| 6x | static final class NotNullOrZeroPropertySelector implements PropertySelector { | |
| public boolean include(Object object, String propertyName, Type type) { | ||
| 6/6 196x | return object!=null && ( | |
| !(object instanceof Number) || ( (Number) object ).longValue()!=0 | ||
| ); | ||
| } | ||
| } | ||
| /** | ||
| * Set the property selector | ||
| */ | ||
| public Example setPropertySelector(PropertySelector selector) { | ||
| 10x | this.selector = selector; | |
| 10x | return this; | |
| } | ||
| /** | ||
| * Exclude zero-valued properties | ||
| */ | ||
| public Example excludeZeroes() { | ||
| 6x | setPropertySelector(NOT_NULL_OR_ZERO); | |
| 6x | return this; | |
| } | ||
| /** | ||
| * Don't exclude null or zero-valued properties | ||
| */ | ||
| public Example excludeNone() { | ||
| 4x | setPropertySelector(ALL); | |
| 4x | return this; | |
| } | ||
| /** | ||
| * Use the "like" operator for all string-valued properties | ||
| */ | ||
| public Example enableLike(MatchMode matchMode) { | ||
| 12x | isLikeEnabled = true; | |
| 12x | this.matchMode = matchMode; | |
| 12x | return this; | |
| } | ||
| /** | ||
| * Use the "like" operator for all string-valued properties | ||
| */ | ||
| public Example enableLike() { | ||
| 10x | return enableLike(MatchMode.EXACT); | |
| } | ||
| /** | ||
| * Ignore case for all string-valued properties | ||
| */ | ||
| public Example ignoreCase() { | ||
| 4x | isIgnoreCaseEnabled = true; | |
| 4x | return this; | |
| } | ||
| /** | ||
| * Exclude a particular named property | ||
| */ | ||
| public Example excludeProperty(String name) { | ||
| 16x | excludedProperties.add(name); | |
| 16x | return this; | |
| } | ||
| /** | ||
| * Create a new instance, which includes all non-null properties | ||
| * by default | ||
| * @param entity | ||
| * @return a new instance of <tt>Example</tt> | ||
| */ | ||
| public static Example create(Object entity) { | ||
| 1/2 22x | if (entity==null) throw new NullPointerException("null example"); | |
| 22x | return new Example(entity, NOT_NULL); | |
| } | ||
| 22x | protected Example(Object entity, PropertySelector selector) { | |
| 22x | this.entity = entity; | |
| 22x | this.selector = selector; | |
| 22x | } | |
| public String toString() { | ||
| 0x | return entity.toString(); | |
| } | ||
| private boolean isPropertyIncluded(Object value, String name, Type type) { | ||
| 6/6 504x | return !excludedProperties.contains(name) && | |
| !type.isAssociationType() && | ||
| selector.include(value, name, type); | ||
| } | ||
| public String toSqlString( | ||
| SessionFactoryImplementor sessionFactory, | ||
| Class persistentClass, | ||
| String alias, | ||
| Map aliasClasses) | ||
| throws HibernateException { | ||
| 24x | StringBuffer buf = new StringBuffer().append(StringHelper.OPEN_PAREN); | |
| 24x | ClassMetadata meta = sessionFactory.getClassMetadata(persistentClass); | |
| 24x | String[] propertyNames = meta.getPropertyNames(); | |
| 24x | Type[] propertyTypes = meta.getPropertyTypes(); | |
| 24x | Object[] propertyValues = meta.getPropertyValues(entity); | |
| 2/2 254x | for (int i=0; i<propertyNames.length; i++) { | |
| 230x | Object propertyValue = propertyValues[i]; | |
| 230x | String propertyName = propertyNames[i]; | |
| 4/4 230x | boolean isPropertyIncluded = i!=meta.getVersionProperty() && | |
| isPropertyIncluded( propertyValue, propertyName, propertyTypes[i] ); | ||
| 2/2 230x | if (isPropertyIncluded) { | |
| 2/2 44x | if ( propertyTypes[i].isComponentType() ) { | |
| 10x | appendComponentCondition( | |
| propertyName, | ||
| propertyValue, | ||
| (AbstractComponentType) propertyTypes[i], | ||
| persistentClass, | ||
| alias, | ||
| aliasClasses, | ||
| sessionFactory, | ||
| buf | ||
| ); | ||
| } | ||
| else { | ||
| 34x | appendPropertyCondition( | |
| propertyName, | ||
| propertyValue, | ||
| persistentClass, | ||
| alias, | ||
| aliasClasses, | ||
| sessionFactory, | ||
| buf | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| 1/2 24x | if ( buf.length()==1 ) buf.append("1=1"); //yuck! | |
| 24x | return buf.append(StringHelper.CLOSE_PAREN).toString(); | |
| } | ||
| private static final Object[] TYPED_VALUES = new TypedValue[0]; | ||
| public TypedValue[] getTypedValues( | ||
| SessionFactoryImplementor sessionFactory, | ||
| Class persistentClass, | ||
| Map aliasClasses) | ||
| throws HibernateException { | ||
| 24x | ClassMetadata meta = sessionFactory.getClassMetadata(persistentClass); | |
| 24x | String[] propertyNames = meta.getPropertyNames(); | |
| 24x | Type[] propertyTypes = meta.getPropertyTypes(); | |
| 24x | Object[] values = meta.getPropertyValues(entity); | |
| 24x | List list = new ArrayList(); | |
| 2/2 254x | for (int i=0; i<propertyNames.length; i++) { | |
| 230x | Object value = values[i]; | |
| 230x | Type type = propertyTypes[i]; | |
| 230x | String name = propertyNames[i]; | |
| 4/4 230x | boolean isPropertyIncluded = i!=meta.getVersionProperty() && | |
| isPropertyIncluded(value, name, type); | ||
| 2/2 230x | if (isPropertyIncluded) { | |
| 2/2 44x | if ( propertyTypes[i].isComponentType() ) { | |
| 10x | addComponentTypedValues(name, value, (AbstractComponentType) type, list); | |
| } | ||
| else { | ||
| 34x | addPropertyTypedValue(value, type, list); | |
| } | ||
| } | ||
| } | ||
| 24x | return (TypedValue[]) list.toArray(TYPED_VALUES); | |
| } | ||
| protected void addPropertyTypedValue(Object value, Type type, List list) { | ||
| 2/2 52x | if ( value!=null ) { | |
| 2/2 50x | if ( value instanceof String ) { | |
| 30x | String string = (String) value; | |
| 2/2 30x | if (isIgnoreCaseEnabled) string = string.toLowerCase(); | |
| 2/2 30x | if (isLikeEnabled) string = matchMode.toMatchString(string); | |
| 30x | value = string; | |
| } | ||
| 50x | list.add( new TypedValue(type, value) ); | |
| } | ||
| 52x | } | |
| protected void addComponentTypedValues(String path, Object component, AbstractComponentType type, List list) | ||
| throws HibernateException { | ||
| 1/2 18x | if (component!=null) { | |
| 18x | String[] propertyNames = type.getPropertyNames(); | |
| 18x | Type[] subtypes = type.getSubtypes(); | |
| 18x | Object[] values = type.getPropertyValues(component); | |
| 2/2 54x | for (int i=0; i<propertyNames.length; i++) { | |
| 36x | Object value = values[i]; | |
| 36x | Type subtype = subtypes[i]; | |
| 36x | String subpath = StringHelper.qualify( path, propertyNames[i] ); | |
| 2/2 36x | if ( isPropertyIncluded(value, subpath, subtype) ) { | |
| 2/2 26x | if ( subtype.isComponentType() ) { | |
| 8x | addComponentTypedValues(subpath, value, (AbstractComponentType) subtype, list); | |
| } | ||
| else { | ||
| 18x | addPropertyTypedValue(value, subtype, list); | |
| } | ||
| } | ||
| } | ||
| } | ||
| 18x | } | |
| protected void appendPropertyCondition( | ||
| String propertyName, | ||
| Object propertyValue, | ||
| Class persistentClass, | ||
| String alias, | ||
| Map aliasClasses, | ||
| SessionFactoryImplementor sessionFactory, | ||
| StringBuffer buf) | ||
| throws HibernateException { | ||
| 2/2 52x | if ( buf.length()>1 ) buf.append(" and "); | |
| Criterion crit; | ||
| 2/2 52x | if ( propertyValue!=null ) { | |
| 50x | boolean isString = propertyValue instanceof String; | |
| 8/8 50x | crit = ( isLikeEnabled && isString ) ? | |
| (Criterion) new LikeExpression( propertyName, propertyValue, isIgnoreCaseEnabled ) : | ||
| (Criterion) new EqExpression( propertyName, propertyValue, isIgnoreCaseEnabled && isString ); | ||
| } | ||
| else { | ||
| 2x | crit = new NullExpression(propertyName); | |
| } | ||
| 52x | buf.append( crit.toSqlString(sessionFactory, persistentClass, alias, aliasClasses) ); | |
| 52x | } | |
| protected void appendComponentCondition( | ||
| String path, | ||
| Object component, | ||
| AbstractComponentType type, | ||
| Class persistentClass, | ||
| String alias, | ||
| Map aliasClasses, | ||
| SessionFactoryImplementor sessionFactory, | ||
| StringBuffer buf) | ||
| throws HibernateException { | ||
| 1/2 18x | if (component!=null) { | |
| 18x | String[] propertyNames = type.getPropertyNames(); | |
| 18x | Object[] values = type.getPropertyValues(component); | |
| 18x | Type[] subtypes = type.getSubtypes(); | |
| 2/2 54x | for (int i=0; i<propertyNames.length; i++) { | |
| 36x | String subpath = StringHelper.qualify( path, propertyNames[i] ); | |
| 36x | Object value = values[i]; | |
| 2/2 36x | if ( isPropertyIncluded( value, subpath, subtypes[i] ) ) { | |
| 26x | Type subtype = subtypes[i]; | |
| 2/2 26x | if ( subtype.isComponentType() ) { | |
| 8x | appendComponentCondition( | |
| subpath, | ||
| value, | ||
| (AbstractComponentType) subtype, | ||
| persistentClass, | ||
| alias, | ||
| aliasClasses, | ||
| sessionFactory, | ||
| buf | ||
| ); | ||
| } | ||
| else { | ||
| 18x | appendPropertyCondition( | |
| subpath, | ||
| value, | ||
| persistentClass, | ||
| alias, | ||
| aliasClasses, | ||
| sessionFactory, | ||
| buf | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| 18x | } | |
| } |