blob: a01d061960b0b769f16830f7cddd7e8ce008bee4 [file] [log] [blame]
/*
* Copyright (c) 2008, Rickard Öberg. All Rights Reserved.
*
* 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.qi4j.runtime.injection;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
import org.qi4j.api.common.ConstructionException;
import org.qi4j.api.common.Optional;
import org.qi4j.api.composite.DependencyDescriptor;
import org.qi4j.bootstrap.BindingException;
import org.qi4j.bootstrap.InvalidInjectionException;
import org.qi4j.functional.Function;
import org.qi4j.functional.Iterables;
import org.qi4j.functional.Specification;
import org.qi4j.functional.Visitable;
import org.qi4j.functional.Visitor;
import org.qi4j.runtime.injection.provider.InjectionProviderException;
import org.qi4j.runtime.model.Binder;
import org.qi4j.runtime.model.Resolution;
import static org.qi4j.api.util.Annotations.isType;
import static org.qi4j.functional.Iterables.iterable;
/**
* JAVADOC
* move all the extraction code to a TypeUtils class
*/
public final class DependencyModel
implements Binder, DependencyDescriptor, Visitable<DependencyModel>
{
public static boolean isOptional( Annotation injectionAnnotation, Annotation[] annotations )
{
if( Iterables.matchesAny( isType( Optional.class ), iterable( annotations ) ) )
{
return true;
}
Method[] methods = injectionAnnotation.annotationType().getMethods();
for( Method method : methods )
{
if( method.getName().equals( "optional" ) )
{
try
{
return (Boolean) method.invoke( injectionAnnotation );
}
catch( Throwable e )
{
return false;
}
}
}
return false;
}
// Model
private final Annotation injectionAnnotation;
private final Type injectionType;
private final Class<?> injectedClass;
private final Class<?> rawInjectionClass;
private final boolean optional;
private final Annotation[] annotations;
// Binding
private InjectionProvider injectionProvider;
public DependencyModel( Annotation injectionAnnotation,
Type genericType,
Class<?> injectedClass,
boolean optional,
Annotation[] annotations
)
{
this.injectionAnnotation = injectionAnnotation;
this.injectedClass = injectedClass;
this.injectionType = genericType;
this.optional = optional;
this.annotations = annotations;
this.rawInjectionClass = mapPrimitiveTypes( extractRawInjectionClass( injectedClass, injectionType ) );
}
@Override
public <ThrowableType extends Throwable> boolean accept( Visitor<? super DependencyModel, ThrowableType> visitor )
throws ThrowableType
{
return visitor.visit( this );
}
private Class<?> extractRawInjectionClass( Class<?> injectedClass, final Type injectionType )
{
// Calculate raw injection type
if( injectionType instanceof Class )
{
return (Class<?>) injectionType;
}
else if( injectionType instanceof ParameterizedType )
{
return (Class<?>) ( (ParameterizedType) injectionType ).getRawType();
}
else if( injectionType instanceof TypeVariable )
{
return extractRawInjectionClass( injectedClass, (TypeVariable<?>) injectionType );
}
throw new IllegalArgumentException(
"Could not extract the rawInjectionClass of " + injectedClass + " and " + injectionType );
}
private Class<?> extractRawInjectionClass( Class<?> injectedClass, TypeVariable<?> injectionTypeVariable )
{
int index = 0;
for( TypeVariable<?> typeVariable : injectionTypeVariable.getGenericDeclaration().getTypeParameters() )
{
if( injectionTypeVariable.getName().equals( typeVariable.getName() ) )
{
return (Class<?>) getActualType( injectedClass, index );
}
index++;
}
throw new IllegalArgumentException(
"Could not extract the rawInjectionClass of " + injectedClass + " and " + injectionTypeVariable );
}
// todo continue refactoring
private Type getActualType( Class<?> injectedClass, int index )
{
// Type index found - map it to actual type
Type genericType = injectedClass;
Type type = null;
while( !Object.class.equals( genericType ) && type == null )
{
genericType = ( (Class<?>) genericType ).getGenericSuperclass();
if( genericType instanceof ParameterizedType )
{
type = ( (ParameterizedType) genericType ).getActualTypeArguments()[ index ];
}
else
{
Type[] genericInterfaces = ( (Class<?>) genericType ).getGenericInterfaces();
if( genericInterfaces.length > index )
{
type = genericInterfaces[ index ];
if( type instanceof ParameterizedType )
{
type = ( (ParameterizedType) type ).getActualTypeArguments()[ index ];
}
// TODO type may still be one of the generic interfaces???
}
}
}
if( type == null )
{
type = Object.class; // Generic type with no constraints so Object is fine
}
return type;
}
// FIXME This method is unused, remove it.
private Type extractDependencyType( Type injectionType )
{
if( injectionType instanceof ParameterizedType )
{
return ( (ParameterizedType) injectionType ).getActualTypeArguments()[ 0 ];
}
else if( injectionType instanceof TypeVariable )
{
return ( (TypeVariable) injectionType ).getBounds()[ 0 ];
}
return injectionType;
}
// Model
@Override
public Annotation injectionAnnotation()
{
return injectionAnnotation;
}
@Override
public Type injectionType()
{
return injectionType;
}
@Override
public Class<?> injectedClass()
{
return injectedClass;
}
/**
* Get the raw dependency type.
* <p>
* If the dependency uses generics this is the raw type,
* and otherwise it is the type of the field.
* <p>
* Examples:
* <p>
* {@code @Service MyService service} -&gt; MyService
* <p>
* {@code @Entity Iterable<Foo> fooList} -&gt; Iterable
* <p>
* {@code @Entity Query<Foo> fooQuery} -&gt; Query
*
* @return raw injection type.
*/
@Override
public Class<?> rawInjectionType()
{
return rawInjectionClass;
}
@Override
public boolean optional()
{
return optional;
}
@Override
public Annotation[] annotations()
{
return annotations;
}
@Override
public void bind( Resolution resolution )
throws BindingException
{
InjectionProviderFactory providerFactory = resolution.application().injectionProviderFactory();
try
{
injectionProvider = providerFactory.newInjectionProvider( resolution, this );
if( injectionProvider == null && !optional )
{
String message =
"[Module " + resolution.module()
.name() + "] Non-optional @" + rawInjectionClass.getName() + " was not bound in " + injectedClass
.getName();
throw new ConstructionException( message );
}
}
catch( InvalidInjectionException e )
{
throw new BindingException( "Could not bind dependency injection", e );
}
}
// Context
public Object inject( InjectionContext context )
{
if( injectionProvider == null )
{
return null;
}
Object injectedValue;
try
{
injectedValue = injectionProvider.provideInjection( context );
}
catch( InjectionProviderException e )
{
Throwable ex = e;
if( ex.getCause() != null )
{
ex = ex.getCause();
}
String message = "[Module " + context.module().name() + "] InjectionProvider unable to resolve @" +
injectionAnnotation.annotationType().getSimpleName() + " " + injectionType.toString();
throw new ConstructionException( message, ex );
}
if( injectedValue == null && !optional )
{
String simpleName = injectionAnnotation.annotationType().getSimpleName();
String message = "[Module " + context.module().name() + "] Non-optional @" +
simpleName + " " + injectionType.toString() +
" was null in " + injectedClass.getName();
if( simpleName.toLowerCase().contains( "service" ) )
{
message = message + ". Did you mean the @Service injection scope?";
}
throw new ConstructionException( message );
}
return getInjectedValue( injectedValue );
}
@SuppressWarnings( "unchecked" )
private Object getInjectedValue( Object injectionResult )
{
if( injectionResult == null )
{
return null;
}
if( injectionResult instanceof Iterable )
{
if( Iterable.class.isAssignableFrom( rawInjectionClass ) || rawInjectionClass.isInstance(
injectionResult ) )
{
return injectionResult;
}
else
{
return Iterables.first( (Iterable) injectionResult );
}
}
else
{
if( Iterable.class.equals( injectionType ) )
{
return Collections.singleton( injectionResult );
}
}
return injectionResult;
}
private final static Class<?>[] primitiveTypeMapping = {
boolean.class, Boolean.class,
byte.class, Byte.class,
short.class, Short.class,
char.class, Character.class,
long.class, Long.class,
double.class, Double.class,
float.class, Float.class,
int.class, Integer.class,
};
private Class<?> mapPrimitiveTypes( Class<?> rawInjectionType )
{
if( rawInjectionType == null || !rawInjectionType.isPrimitive() )
{
return rawInjectionType;
}
for( int i = 0; i < primitiveTypeMapping.length; i += 2 )
{
if( primitiveTypeMapping[ i ].equals( rawInjectionType ) )
{
return primitiveTypeMapping[ i + 1 ];
}
}
return rawInjectionType;
}
public boolean hasScope( final Class<? extends Annotation> scope )
{
return scope == null || scope.equals( injectionAnnotation().annotationType() );
}
public Class<? extends Annotation> injectionAnnotationType()
{
if( injectionAnnotation == null )
{
return null;
}
return injectionAnnotation.annotationType();
}
@Override
public String toString()
{
return injectionAnnotation + " for " + injectionType + " in " + injectedClass.getName();
}
public static class ScopeSpecification
implements Specification<DependencyModel>
{
private final Class<? extends Annotation> scope;
public ScopeSpecification( Class<? extends Annotation> scope )
{
this.scope = scope;
}
@Override
public boolean satisfiedBy( DependencyModel model )
{
return model.hasScope( scope );
}
}
public static class InjectionTypeFunction
implements Function<DependencyModel, Class<?>>
{
@Override
public Class<?> map( DependencyModel dependencyModel )
{
return dependencyModel.rawInjectionType();
}
}
}