/*
* Copyright 2007 Rickard Öberg.
* Copyright 2007 Niclas Hedhman.
* Copyright 2008 Alin Dreghiciu.
* Copyright 2012 Paul Merlin.
*
* 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
* ied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.qi4j.api.query;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import org.qi4j.api.association.Association;
import org.qi4j.api.association.GenericAssociationInfo;
import org.qi4j.api.association.ManyAssociation;
import org.qi4j.api.composite.Composite;
import org.qi4j.api.entity.Identity;
import org.qi4j.api.injection.scope.State;
import org.qi4j.api.property.GenericPropertyInfo;
import org.qi4j.api.property.Property;
import org.qi4j.api.query.grammar.AndSpecification;
import org.qi4j.api.query.grammar.AssociationFunction;
import org.qi4j.api.query.grammar.AssociationNotNullSpecification;
import org.qi4j.api.query.grammar.AssociationNullSpecification;
import org.qi4j.api.query.grammar.ContainsAllSpecification;
import org.qi4j.api.query.grammar.ContainsSpecification;
import org.qi4j.api.query.grammar.EqSpecification;
import org.qi4j.api.query.grammar.GeSpecification;
import org.qi4j.api.query.grammar.GtSpecification;
import org.qi4j.api.query.grammar.LeSpecification;
import org.qi4j.api.query.grammar.LtSpecification;
import org.qi4j.api.query.grammar.ManyAssociationContainsSpecification;
import org.qi4j.api.query.grammar.ManyAssociationFunction;
import org.qi4j.api.query.grammar.MatchesSpecification;
import org.qi4j.api.query.grammar.NeSpecification;
import org.qi4j.api.query.grammar.NotSpecification;
import org.qi4j.api.query.grammar.OrSpecification;
import org.qi4j.api.query.grammar.OrderBy;
import org.qi4j.api.query.grammar.PropertyFunction;
import org.qi4j.api.query.grammar.PropertyNotNullSpecification;
import org.qi4j.api.query.grammar.PropertyNullSpecification;
import org.qi4j.api.query.grammar.PropertyReference;
import org.qi4j.api.query.grammar.Variable;
import org.qi4j.api.util.NullArgumentException;
import org.qi4j.functional.Specification;
import static org.qi4j.functional.Iterables.prepend;
/**
* Static factory methods for query expressions and operators.
*/
public final class QueryExpressions
{
// This is used for eq(Association,Composite)
private static final Method IDENTITY_METHOD;
static
{
try
{
IDENTITY_METHOD = Identity.class.getMethod( "identity" );
}
catch( NoSuchMethodException e )
{
throw new InternalError( "Qi4j Core API codebase is corrupted. Contact Qi4j team: QueryExpressions" );
}
}
// Templates and variables -----------------------------------------------|
/**
* Create a Query Template using the given type.
*
* @param <T> the type of the template
* @param clazz a class declaring the type of the template
*
* @return a new Query Template
*/
public static <T> T templateFor( Class<T> clazz )
{
NullArgumentException.validateNotNull( "Template class", clazz );
if( clazz.isInterface() )
{
return clazz.cast( Proxy.newProxyInstance( clazz.getClassLoader(),
new Class<?>[] { clazz },
new TemplateHandler<T>( null, null, null ) ) );
}
else
{
try
{
T mixin = clazz.newInstance();
for( Field field : clazz.getFields() )
{
if( field.getAnnotation( State.class ) != null )
{
if( field.getType().equals( Property.class ) )
{
field.set( mixin,
Proxy.newProxyInstance( field.getType().getClassLoader(),
new Class<?>[]{ field.getType() },
new PropertyReferenceHandler<T>( new PropertyFunction<T>( null, null, null, field ) ) ) );
}
else if( field.getType().equals( Association.class ) )
{
field.set( mixin,
Proxy.newProxyInstance( field.getType().getClassLoader(),
new Class<?>[]{ field.getType() },
new AssociationReferenceHandler<T>( new AssociationFunction<T>( null, null, field ) ) ) );
}
else if( field.getType().equals( Property.class ) )
{
field.set( mixin,
Proxy.newProxyInstance( field.getType().getClassLoader(),
new Class<?>[]{ field.getType() },
new ManyAssociationReferenceHandler<T>( new ManyAssociationFunction<T>( null, null, field ) ) ) );
}
}
}
return mixin;
}
catch( Throwable e )
{
throw new IllegalArgumentException( "Cannot use class as template", e );
}
}
}
/**
* Create a Query Template using the given mixin class and association.
*
* @param <T> the type of the template
* @param mixinType a class declaring the type of the template
* @param association an association
*
* @return a new Query Template
*/
public static <T> T templateFor( final Class<T> mixinType, Association<?> association )
{
NullArgumentException.validateNotNull( "Mixin class", mixinType );
NullArgumentException.validateNotNull( "Association", association );
return mixinType.cast( Proxy.newProxyInstance( mixinType.getClassLoader(),
new Class<?>[]{ mixinType },
new TemplateHandler<T>( null, association( association ), null ) ) );
}
public static <T> T oneOf( final ManyAssociation<T> association )
{
NullArgumentException.validateNotNull( "Association", association );
return association.get( 0 );
}
/**
* Create a new Query Variable.
*
* @param name a name for the Variable
*
* @return a new Query Variable.
*/
public static Variable variable( String name )
{
NullArgumentException.validateNotNull( "Variable name", name );
return new Variable( name );
}
/**
* Create a new Query Template PropertyFunction.
*
* @param <T> type of the Property
* @param property a Property
*
* @return a new Query Template PropertyFunction
*/
@SuppressWarnings( "unchecked" )
public static <T> PropertyFunction<T> property( Property<T> property )
{
return ( ( PropertyReferenceHandler<T> ) Proxy.getInvocationHandler( property ) ).property();
}
/**
* Create a new Query Property instance.
*
* @param <T> type of the Property
* @param mixinClass mixin of the Property
* @param fieldName name of the Property field
*
* @return a new Query Property instance for the given mixin and property name.
*/
@SuppressWarnings( "unchecked" )
public static <T> Property<T> property( Class<?> mixinClass, String fieldName )
{
try
{
Field field = mixinClass.getField( fieldName );
if( !Property.class.isAssignableFrom( field.getType() ) )
{
throw new IllegalArgumentException( "Field must be of type Property<?>" );
}
return ( Property<T> ) Proxy.newProxyInstance(
mixinClass.getClassLoader(),
new Class<?>[]{ field.getType() },
new PropertyReferenceHandler<T>( new PropertyFunction<T>( null, null, null, field ) ) );
}
catch( NoSuchFieldException e )
{
throw new IllegalArgumentException( "No such field '" + fieldName + "' in mixin " + mixinClass.getName() );
}
}
/**
* Create a new Query Template AssociationFunction.
*
* @param <T> type of the Association
* @param association an Association
*
* @return a new Query Template AssociationFunction
*/
@SuppressWarnings( "unchecked" )
public static <T> AssociationFunction<T> association( Association<T> association )
{
return ( ( AssociationReferenceHandler<T> ) Proxy.getInvocationHandler( association ) ).association();
}
/**
* Create a new Query Template ManyAssociationFunction.
*
* @param <T> type of the ManyAssociation
* @param association a ManyAssociation
*
* @return a new Query Template ManyAssociationFunction
*/
@SuppressWarnings( "unchecked" )
public static <T> ManyAssociationFunction<T> manyAssociation( ManyAssociation<T> association )
{
return ( ( ManyAssociationReferenceHandler<T> ) Proxy.getInvocationHandler( association ) ).manyAssociation();
}
// And/Or/Not ------------------------------------------------------------|
/**
* Create a new AND specification.
*
* @param left first operand
* @param right second operand
* @param optionalRight optional operands
*
* @return a new AND specification
*/
public static AndSpecification and( Specification<Composite> left,
Specification<Composite> right,
Specification<Composite>... optionalRight
)
{
return new AndSpecification( prepend( left, prepend( right, Arrays.asList( optionalRight ) ) ) );
}
/**
* Create a new OR specification.
*
* @param left first operand
* @param right second operand
* @param optionalRight optional operands
*
* @return a new OR specification
*/
public static OrSpecification or( Specification<Composite> left,
Specification<Composite> right,
Specification<Composite>... optionalRight
)
{
return new OrSpecification( prepend( left, prepend( right, Arrays.asList( optionalRight ) ) ) );
}
/**
* Create a new NOT specification.
*
* @param operand specification to be negated
*
* @return a new NOT specification
*/
public static NotSpecification not( Specification<Composite> operand )
{
return new NotSpecification( operand );
}
// Comparisons -----------------------------------------------------------|
/**
* Create a new EQUALS specification for a Property.
*
* @param property a Property
* @param value its value
*
* @return a new EQUALS specification for a Property.
*/
public static <T> EqSpecification<T> eq( Property<T> property, T value )
{
return new EqSpecification<T>( property( property ), value );
}
/**
* Create a new EQUALS specification for a Property using a named Variable.
*
* @param property a Property
* @param variable a Query Variable
*
* @return a new EQUALS specification for a Property using a named Variable.
*/
public static <T> EqSpecification<T> eq( Property<T> property, Variable variable )
{
return new EqSpecification( property( property ), variable );
}
/**
* Create a new EQUALS specification for an Association.
*
* @param association an Association
* @param value its value
*
* @return a new EQUALS specification for an Association.
*/
public static <T> EqSpecification<String> eq( Association<T> association, T value )
{
return new EqSpecification<String>( new PropertyFunction<String>( null,
association( association ),
null,
IDENTITY_METHOD ),
value.toString() );
}
/**
* Create a new GREATER OR EQUALS specification for a Property.
*
* @param property a Property
* @param value its value
*
* @return a new GREATER OR EQUALS specification for a Property.
*/
public static <T> GeSpecification<T> ge( Property<T> property, T value )
{
return new GeSpecification<T>( property( property ), value );
}
/**
* Create a new GREATER OR EQUALS specification for a Property using a named Variable.
*
* @param property a Property
* @param variable a Query Variable
*
* @return a new GREATER OR EQUALS specification for a Property using a named Variable.
*/
public static <T> GeSpecification<T> ge( Property<T> property, Variable variable )
{
return new GeSpecification( property( property ), variable );
}
/**
* Create a new GREATER THAN specification for a Property.
*
* @param property a Property
* @param value its value
*
* @return a new GREATER THAN specification for a Property.
*/
public static <T> GtSpecification<T> gt( Property<T> property, T value )
{
return new GtSpecification<T>( property( property ), value );
}
/**
* Create a new GREATER THAN specification for a Property using a named Variable.
*
* @param property a Property
* @param variable a Query Variable
*
* @return a new GREATER THAN specification for a Property using a named Variable.
*/
public static <T> GtSpecification<T> gt( Property<T> property, Variable variable )
{
return new GtSpecification( property( property ), variable );
}
/**
* Create a new LESS OR EQUALS specification for a Property.
*
* @param property a Property
* @param value its value
*
* @return a new LESS OR EQUALS specification for a Property.
*/
public static <T> LeSpecification<T> le( Property<T> property, T value )
{
return new LeSpecification<T>( property( property ), value );
}
/**
* Create a new LESS OR EQUALS specification for a Property using a named Variable.
*
* @param property a Property
* @param variable a Query Variable
*
* @return a new LESS OR EQUALS specification for a Property using a named Variable.
*/
public static <T> LeSpecification<T> le( Property<T> property, Variable variable )
{
return new LeSpecification( property( property ), variable );
}
/**
* Create a new LESSER THAN specification for a Property.
*
* @param property a Property
* @param value its value
*
* @return a new LESSER THAN specification for a Property.
*/
public static <T> LtSpecification<T> lt( Property<T> property, T value )
{
return new LtSpecification<T>( property( property ), value );
}
/**
* Create a new LESSER THAN specification for a Property using a named Variable.
*
* @param property a Property
* @param variable a Query Variable
*
* @return a new LESSER THAN specification for a Property using a named Variable.
*/
public static <T> LtSpecification<T> lt( Property<T> property, Variable variable )
{
return new LtSpecification( property( property ), variable );
}
/**
* Create a new NOT EQUALS specification for a Property.
*
* @param property a Property
* @param value its value
*
* @return a new NOT EQUALS specification for a Property.
*/
public static <T> NeSpecification<T> ne( Property<T> property, T value )
{
return new NeSpecification<T>( property( property ), value );
}
/**
* Create a new NOT EQUALS specification for a Property using a named Variable.
*
* @param property a Property
* @param variable a Query Variable
*
* @return a new NOT EQUALS specification for a Property using a named Variable.
*/
public static <T> NeSpecification<T> ne( Property<T> property, Variable variable )
{
return new NeSpecification( property( property ), variable );
}
/**
* Create a new REGULAR EXPRESSION specification for a Property.
*
* @param property a Property
* @param regexp its value
*
* @return a new REGULAR EXPRESSION specification for a Property.
*/
public static MatchesSpecification matches( Property<String> property, String regexp )
{
return new MatchesSpecification( property( property ), regexp );
}
/**
* Create a new REGULAR EXPRESSION specification for a Property using a named Variable.
*
* @param property a Property
* @param variable a Query Variable
*
* @return a new REGULAR EXPRESSION specification for a Property using a named Variable.
*/
public static MatchesSpecification matches( Property<String> property, Variable variable )
{
return new MatchesSpecification( property( property ), variable );
}
// Null checks -----------------------------------------------------------|
/**
* Create a new NOT NULL specification for a Property.
*
* @param property a Property
*
* @return a new NOT NULL specification for a Property.
*/
public static <T> PropertyNotNullSpecification<T> isNotNull( Property<T> property )
{
return new PropertyNotNullSpecification<T>( property( property ) );
}
/**
* Create a new NULL specification for a Property.
*
* @param property a Property
*
* @return a new NULL specification for a Property.
*/
public static <T> PropertyNullSpecification<T> isNull( Property<T> property )
{
return new PropertyNullSpecification<T>( property( property ) );
}
/**
* Create a new NOT NULL specification for an Association.
*
* @param association an Association
*
* @return a new NOT NULL specification for an Association.
*/
public static <T> AssociationNotNullSpecification<T> isNotNull( Association<T> association )
{
return new AssociationNotNullSpecification<T>( association( association ) );
}
/**
* Create a new NULL specification for an Association.
*
* @param association an Association
*
* @return a new NULL specification for an Association.
*/
public static <T> AssociationNullSpecification<T> isNull( Association<T> association )
{
return new AssociationNullSpecification<T>( association( association ) );
}
// Collections -----------------------------------------------------------|
/**
* Create a new CONTAINS ALL specification for a Collection Property.
*
* @param collectionProperty a Collection Property
* @param values its values
*
* @return a new CONTAINS ALL specification for a Collection Property.
*/
public static <T> ContainsAllSpecification<T> containsAll( Property<? extends Collection<T>> collectionProperty,
Iterable<T> values )
{
NullArgumentException.validateNotNull( "Values", values );
return new ContainsAllSpecification<T>( property( collectionProperty ), values );
}
/**
* Create a new CONTAINS ALL specification for a Collection Property using named Variables.
*
* @param collectionProperty a Collection Property
* @param variables named Variables
*
* @return a new CONTAINS ALL specification for a Collection Property using named Variables.
*/
public static <T> ContainsAllSpecification<T> containsAllVariables(
Property<? extends Collection<T>> collectionProperty,
Iterable<Variable> variables )
{
NullArgumentException.validateNotNull( "Variables", variables );
return new ContainsAllSpecification( property( collectionProperty ), variables );
}
/**
* Create a new CONTAINS specification for a Collection Property.
*
* @param collectionProperty a Collection Property
* @param value the value
*
* @return a new CONTAINS specification for a Collection Property.
*/
public static <T> ContainsSpecification<T> contains( Property<? extends Collection<T>> collectionProperty,
T value )
{
NullArgumentException.validateNotNull( "Value", value );
return new ContainsSpecification<T>( property( collectionProperty ), value );
}
/**
* Create a new CONTAINS specification for a Collection Property using named Variables.
*
* @param collectionProperty a Collection Property
* @param variable named Variable
*
* @return a new CONTAINS specification for a Collection Property using named Variables.
*/
public static <T> ContainsSpecification<T> contains( Property<? extends Collection<T>> collectionProperty,
Variable variable )
{
NullArgumentException.validateNotNull( "Variable", variable );
return new ContainsSpecification( property( collectionProperty ), variable );
}
/**
* Create a new CONTAINS specification for a ManyAssociation.
*
* @param manyAssoc a ManyAssociation
* @param value the value
*
* @return a new CONTAINS specification for a ManyAssociation.
*/
public static <T> ManyAssociationContainsSpecification<T> contains( ManyAssociation<T> manyAssoc,
T value )
{
return new ManyAssociationContainsSpecification<T>( manyAssociation( manyAssoc ), value );
}
// Ordering --------------------------------------------------------------|
/**
* Create a new Query ascending order segment for a Property.
*
* @param <T> type of the Property
* @param property a Property
*
* @return a new Query ascending order segment for a Property.
*/
public static <T> OrderBy orderBy( final Property<T> property )
{
return orderBy( property, OrderBy.Order.ASCENDING );
}
/**
* Create a new Query ordering segment for a Property.
*
* @param <T> type of the Property
* @param property a Property
* @param order ascending or descending
*
* @return a new Query ordering segment for a Property.
*/
public static <T> OrderBy orderBy( final Property<T> property, final OrderBy.Order order )
{
return new OrderBy( property( property ), order );
}
// Query Templates InvocationHandlers ------------------------------------|
private static class TemplateHandler<T>
implements InvocationHandler
{
private PropertyFunction<?> compositeProperty;
private AssociationFunction<?> compositeAssociation;
private ManyAssociationFunction<?> compositeManyAssociation;
private TemplateHandler( PropertyFunction<?> CompositeProperty,
AssociationFunction<?> CompositeAssociation,
ManyAssociationFunction<?> CompositeManyAssociation
)
{
this.compositeProperty = CompositeProperty;
this.compositeAssociation = CompositeAssociation;
this.compositeManyAssociation = CompositeManyAssociation;
}
@Override
public Object invoke( Object o, Method method, Object[] objects )
throws Throwable
{
if( Property.class.isAssignableFrom( method.getReturnType() ) )
{
return Proxy.newProxyInstance(
method.getReturnType().getClassLoader(),
new Class<?>[]{ method.getReturnType() },
new PropertyReferenceHandler<T>( new PropertyFunction<T>( compositeProperty,
compositeAssociation,
compositeManyAssociation,
method ) ) );
}
else if( Association.class.isAssignableFrom( method.getReturnType() ) )
{
return Proxy.newProxyInstance(
method.getReturnType().getClassLoader(),
new Class<?>[]{ method.getReturnType() },
new AssociationReferenceHandler<T>( new AssociationFunction<T>( compositeAssociation,
compositeManyAssociation,
method ) ) );
}
else if( ManyAssociation.class.isAssignableFrom( method.getReturnType() ) )
{
return Proxy.newProxyInstance(
method.getReturnType().getClassLoader(),
new Class<?>[]{ method.getReturnType() },
new ManyAssociationReferenceHandler<T>( new ManyAssociationFunction<T>( compositeAssociation,
compositeManyAssociation,
method ) ) );
}
return null;
}
}
private static class PropertyReferenceHandler<T>
implements InvocationHandler
{
private PropertyFunction<T> property;
private PropertyReferenceHandler( PropertyFunction<T> property )
{
this.property = property;
}
private PropertyFunction<T> property()
{
return property;
}
@Override
public Object invoke( Object o, final Method method, Object[] objects )
throws Throwable
{
if( method.equals( Property.class.getMethod( "get" ) ) )
{
Type propertyType = GenericPropertyInfo.propertyTypeOf( property.accessor() );
if( propertyType.getClass().equals( Class.class ) )
{
return Proxy.newProxyInstance( method.getDeclaringClass().getClassLoader(),
new Class<?>[]{ (Class<?>) propertyType, PropertyReference.class },
new TemplateHandler<T>( property, null, null ) );
}
}
return null;
}
}
private static class AssociationReferenceHandler<T>
implements InvocationHandler
{
private AssociationFunction<T> association;
private AssociationReferenceHandler( AssociationFunction<T> association )
{
this.association = association;
}
private AssociationFunction<T> association()
{
return association;
}
@Override
public Object invoke( Object o, final Method method, Object[] objects )
throws Throwable
{
if( method.equals( Association.class.getMethod( "get" ) ) )
{
Type associationType = GenericAssociationInfo.associationTypeOf( association.accessor() );
if( associationType.getClass().equals( Class.class ) )
{
return Proxy.newProxyInstance( method.getDeclaringClass().getClassLoader(),
new Class<?>[]{ (Class) associationType, PropertyReference.class },
new TemplateHandler<T>( null, association, null ) );
}
}
return null;
}
}
private static class ManyAssociationReferenceHandler<T>
implements InvocationHandler
{
private ManyAssociationFunction<T> manyAssociation;
public ManyAssociationReferenceHandler( ManyAssociationFunction<T> manyAssociation )
{
this.manyAssociation = manyAssociation;
}
public ManyAssociationFunction<T> manyAssociation()
{
return manyAssociation;
}
@Override
public Object invoke( Object o, final Method method, Object[] objects )
throws Throwable
{
if( method.equals( ManyAssociation.class.getMethod( "get", Integer.TYPE ) ) )
{
Type manyAssociationType = GenericAssociationInfo.associationTypeOf( manyAssociation.accessor() );
if( manyAssociationType.getClass().equals( Class.class ) )
{
return Proxy.newProxyInstance( method.getDeclaringClass().getClassLoader(),
new Class<?>[]{ (Class) manyAssociationType, PropertyReference.class },
new TemplateHandler<T>( null, null, manyAssociation ) );
}
}
return null;
}
}
}