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

Reply via email to