After looking at the AbstractModelFactory the code there seemed very
complicated and not that understandable. A lot of code smells had
obviously crept into the class. Besides those already mentioned:
* DRY
* Single Responsibility
* Law Of Demeter
I refactored the whole class, think I found two errors and added notes
about methods that perhaps belong elsewhere.
The errors:
* idx was not incremented in the newMethodModel-method
* ? getGenericParameterTypes not from realConstructor but constructor in
getConstructorModels
I'd also create real classes for the List<PropertyModel> etc. as I
suppose that a lot of functionality belongs there which is by now
externally applied with a lot of iteration and calling into the depths
of the models. (Same for the other model holder lists).
I'd also favor creation methods for all the models and not using new
directly as you so have the opportunity to encapsulate the creation
behaviour and create subclasses or add/remove additional checks.
Code is attached, tests still run.
Have Fun
Michael
package org.qi4j.runtime.composite;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Collections;
import net.sf.cglib.proxy.Factory;
import org.qi4j.composite.Constraint;
import org.qi4j.composite.ConstraintDeclaration;
import org.qi4j.composite.Constraints;
import org.qi4j.composite.scope.AssociationField;
import org.qi4j.composite.scope.AssociationParameter;
import org.qi4j.composite.scope.PropertyField;
import org.qi4j.composite.scope.PropertyParameter;
import org.qi4j.injection.InjectionScope;
import org.qi4j.injection.Optional;
import org.qi4j.spi.composite.ConstraintModel;
import org.qi4j.spi.composite.ConstraintsModel;
import org.qi4j.spi.composite.ConstructorModel;
import org.qi4j.spi.composite.FieldModel;
import org.qi4j.spi.composite.InvalidCompositeException;
import org.qi4j.spi.composite.MethodModel;
import org.qi4j.spi.composite.ParameterModel;
import org.qi4j.spi.composite.ConstraintDeclarationModel;
import org.qi4j.spi.injection.AssociationInjectionModel;
import org.qi4j.spi.injection.DependencyInjectionModel;
import org.qi4j.spi.injection.InjectionModel;
import org.qi4j.spi.injection.PropertyInjectionModel;
import org.qi4j.spi.property.PropertyModel;
import com.sun.tools.javac.tree.Tree;
/**
* TODO
*/
public abstract class AbstractModelFactory
{
protected void addFieldModels( final Class<?> fragmentClass, final Class<?>
fieldClass, final List<FieldModel> fieldModels )
{
for( final Field field : fieldClass.getDeclaredFields() )
{
field.setAccessible( true );
final InjectionModel injectionModel = extractInjectionModel(
field.getAnnotations(), fragmentClass, field.getGenericType(), field );
// Only add fields which have injections
if( injectionModel != null )
{
fieldModels.add( new FieldModel( field, injectionModel ) );
}
}
// Add fields in superclass
final Class<?> parent = fieldClass.getSuperclass();
if( parent != null && parent != Object.class )
{
addFieldModels( fragmentClass, parent, fieldModels );
}
}
// ERROR getGenericParameterTypes not from constructor
protected void addConstructorModels( final Class<?> mixinClass, final
Class<?> compositeType, final List<ConstructorModel> constructorModels )
{
for( final Constructor constructor : mixinClass.getConstructors() )
{
final Constructor<?> realConstructor = getRealConstructor(
(Class<?>) mixinClass, (Class<?>) compositeType, constructor );
// This is required to be able to instantiate package protected
class
realConstructor.setAccessible( true );
final List<ParameterModel> parameterModels = createParameterModels(
mixinClass, realConstructor.getGenericParameterTypes(),
realConstructor.getParameterAnnotations() );
if ( checkParameterModels( mixinClass, compositeType,
parameterModels )) {
constructorModels.add( new ConstructorModel( constructor,
parameterModels, hasInjections( parameterModels )) );
}
}
}
private boolean checkParameterModels( final Class<?> mixinClass, final
Class<?> compositeType, final List<ParameterModel> parameterModels )
{
final boolean hasAllInjected = hasAllInjected( parameterModels );
final boolean hasInjections = hasInjections( parameterModels );
if( hasAllInjected )
{
return true;
}
if ( hasInjections && !hasAllInjected )
{
if( compositeType != null )
{
throw new InvalidCompositeException( "All parameters in
constructor for " + mixinClass.getName() + " must be injected", compositeType );
}
}
return false;
}
private boolean hasAllInjected( final List<ParameterModel> parameterModels )
{
for( final ParameterModel parameterModel : parameterModels )
{
if( !parameterModel.hasInjections() )
{
return false;
}
}
return true;
}
private Constructor<?> getRealConstructor( final Class<?> mixinClass, final
Class<?> compositeType, final Constructor constructor )
{
if( Tree.Factory.class.isAssignableFrom( mixinClass ) )
{
// Abstract mixin - get annotations from superclass
final Class<?> mixinSuperclass = mixinClass.getSuperclass();
try
{
return mixinSuperclass.getConstructor(
constructor.getParameterTypes() );
}
catch( NoSuchMethodException e )
{
throw new InvalidCompositeException( "Could not get constructor
from abstract fragment of type" + mixinSuperclass, compositeType );
}
}
else
{
return constructor;
}
}
// todo recursive ?
protected Collection<MethodModel> getMethodModels( final Class<?>
fragmentClass )
{
final Collection<MethodModel> models = new ArrayList<MethodModel>();
Class<?> methodClass = fragmentClass;
while( !Object.class.equals( methodClass ) )
{
for( final Method method : methodClass.getDeclaredMethods() )
{
models.add( newMethodModel( method, fragmentClass ) );
}
methodClass = methodClass.getSuperclass();
}
return models;
}
// had error idx++ missing
private MethodModel newMethodModel( final Method method, final Class<?>
fragmentClass )
{
final List<ParameterModel> parameterModels = createParameterModels(
fragmentClass,
method.getGenericParameterTypes(),
method.getParameterAnnotations() );
final boolean hasInjections = hasInjections( parameterModels );
if( hasInjections )
{
method.setAccessible( true );
}
return new MethodModel( method, parameterModels, hasInjections );
}
private boolean hasInjections( final List<ParameterModel> parameterModels )
{
for ( final ParameterModel parameterModel : parameterModels )
{
if( parameterModel.hasInjections() )
{
return true;
}
}
return false;
}
private List<ParameterModel> createParameterModels( final Class<?>
fragmentClass, final Type[] parameterTypes, final Annotation[][]
parameterAnnotations )
{
final List<ParameterModel> parameterModels = new
ArrayList<ParameterModel>(parameterTypes.length);
for( int idx = 0; idx < parameterTypes.length; idx++ )
{
final ParameterModel parameterModel = extractParameterModel(
parameterAnnotations[ idx ], fragmentClass, parameterTypes[ idx ] );
parameterModels.add( parameterModel );
}
return parameterModels;
}
protected ParameterModel extractParameterModel( final Annotation[]
parameterAnnotations, final Class<?> methodClass, final Type parameterType )
{
final List<Annotation> parameterConstraints = filterAnnotationsForType(
parameterAnnotations, ConstraintDeclaration.class);
final InjectionModel injectionModel = extractInjectionModel(
parameterAnnotations, methodClass, parameterType, null );
if( parameterConstraints.isEmpty() )
{
return new ParameterModel( parameterType, null, injectionModel );
}
return new ParameterModel( parameterType, new ConstraintsModel(
parameterConstraints ), injectionModel );
}
private InjectionModel extractInjectionModel( final Annotation[]
annotations, final Class<?> sourceClass, final Type type, final Field field )
{
final List<Annotation> injectionAnnotations = filterAnnotationsForType(
annotations, InjectionScope.class );
if( injectionAnnotations.isEmpty() )
{
return null;
}
checkInjectionAnnotations( injectionAnnotations, sourceClass );
return newInjectionModel( injectionAnnotations.get( 0 ), type,
sourceClass, field );
}
private void checkInjectionAnnotations( final List<Annotation>
injectionAnnotations, final Class<?> methodClass )
{
if( methodClass.isInterface() )
{
throw new InvalidCompositeException( "Not allowed to have injection
annotations on interface fields / parameters ", methodClass );
}
if( injectionAnnotations.size() > 1 )
{
throw new InvalidCompositeException( "Not allowed to have multiple
injection annotations on a single target", methodClass );
}
}
protected PropertyModel getPropertyModel( final Method method )
{
final List<Annotation> constraintAnnotations =
filterAnnotationsForType( method.getAnnotations(), ConstraintDeclaration.class
);
for( final Annotation constraintDeclAnnotation : constraintAnnotations )
{
createConstraintDeclarationModel(
constraintDeclAnnotation.annotationType() );
}
return new PropertyModel( method, new ConstraintsModel(
constraintAnnotations ) );
}
@Deprecated()
private void createConstraintDeclarationModel( final Class<? extends
Annotation> constraintDeclAnnotationType )
{
final List<ConstraintModel<?>> constraintModels = getConstraintModels(
constraintDeclAnnotationType );
final ConstraintDeclarationModel constraintDeclarationModel =
new ConstraintDeclarationModel( constraintDeclAnnotationType,
constraintModels );
}
private List<ConstraintModel<?>> getConstraintModels( final Class<? extends
Annotation> constraintDeclAnnotation )
{
final Constraints constraintsAnnotation =
constraintDeclAnnotation.getAnnotation( Constraints.class );
if( constraintsAnnotation == null )
{
return Collections.emptyList();
}
final List<ConstraintModel<?>> constraintModels = new
ArrayList<ConstraintModel<?>>();
for( final Class<? extends Constraint<?,?>> constraintImplementation :
constraintsAnnotation.value() )
{
constraintModels.add( new ConstraintModel(
constraintImplementation, constraintDeclAnnotation ) );
}
return constraintModels;
}
// todo move to AnnotationUtils
private List<Annotation> filterAnnotationsForType( final Annotation[]
annotations, final Class<? extends Annotation> annotationTypeClass ) {
final List<Annotation> result = new ArrayList<Annotation>();
for( final Annotation annotation : annotations )
{
final Class<? extends Annotation> annotationType =
annotation.annotationType();
if (null != annotationType.getAnnotation( annotationTypeClass )) {
result.add(annotation);
}
}
return result;
}
private InjectionModel newInjectionModel( final Annotation annotation,
final Type injectionType, final Class<?> injectedType, final Field field )
{
final Class<? extends Annotation> annotationType =
annotation.annotationType();
if( annotationType.equals( PropertyField.class ) )
{
return createPropertyInjectionModel( (PropertyField) annotation,
injectionType, injectedType, field.getName() );
}
else if( annotationType.equals( AssociationField.class ) )
{
return createAssociationInjectionModel( (AssociationField)
annotation, injectionType, injectedType, field.getName() );
}
else if( annotationType.equals( PropertyParameter.class ) )
{
return createPropertyInjectionModel( (PropertyParameter)
annotation, injectionType, injectedType );
}
else if( annotationType.equals( AssociationParameter.class ) )
{
return createAssociationInjectionModel( (AssociationParameter)
annotation, injectionType, injectedType );
}
else
{
return createDependencyInjectionModel( annotation, injectionType,
injectedType );
}
}
private DependencyInjectionModel createDependencyInjectionModel( final
Annotation annotation, final Type injectionType, final Class<?> injectedType )
{
return new DependencyInjectionModel( annotation.annotationType(),
injectionType, injectedType,
isOptional( annotation ) );
}
// todo move to AssociationParameter
private AssociationInjectionModel createAssociationInjectionModel( final
AssociationParameter associationParameter, final Type injectionType, final
Class<?> injectedType )
{
return new AssociationInjectionModel( AssociationParameter.class,
injectionType, injectedType,
false,
associationParameter.value() );
}
// todo move to PropertyParameter
private InjectionModel createPropertyInjectionModel( final
PropertyParameter propertyParameter, final Type injectionType, final Class<?>
injectedType )
{
return new PropertyInjectionModel( PropertyParameter.class,
injectionType, injectedType,
false, propertyParameter.value() );
}
// todo move to AssociationField
private InjectionModel createAssociationInjectionModel( final
AssociationField associationField, final Type injectionType, final Class<?>
injectedType, final String fieldName )
{
return new AssociationInjectionModel( AssociationField.class,
injectionType, injectedType,
associationField.optional(),
defaultIfEmtpy(
associationField.value(), fieldName ) );
}
// todo move to PropertyField
private InjectionModel createPropertyInjectionModel( final PropertyField
annotation, final Type injectionType, final Class<?> injectedType, final String
fieldName )
{
return new PropertyInjectionModel( PropertyField.class,
injectionType, injectedType,
annotation.optional(),
defaultIfEmtpy( annotation.value(),
fieldName ) );
}
private String defaultIfEmtpy( final String name, final String fieldName )
{
return name.equals( "" ) ? fieldName : name;
}
// todo move to AnnotationUtils
private boolean isOptional( final Annotation annotation )
{
final Method optionalMethod = getAnnotationMethod( Optional.class,
annotation.annotationType() );
if( optionalMethod == null )
{
return false;
}
try
{
return Boolean.class.cast( optionalMethod.invoke( annotation )
);
}
catch( Exception e )
{
throw new InvalidCompositeException( "Could not get optional flag
from annotation", annotation.getClass() );
}
}
// todo move to AnnotationUtils
private Method getAnnotationMethod( final Class<? extends Annotation>
anAnnotationClass, final Class<? extends Annotation> mainAnnotation )
{
for( final Method method : mainAnnotation.getMethods() )
{
if( method.getAnnotation( anAnnotationClass ) != null )
{
return method;
}
}
return null;
}
}
_______________________________________________
qi4j-dev mailing list
[email protected]
http://lists.ops4j.org/mailman/listinfo/qi4j-dev