# CompositeAssemblyImpl.java

    /*
     * Copyright (c) 2007-2011, Rickard Öberg. All Rights Reserved.
     * Copyright (c) 2008-2013, Niclas Hedhman. 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.bootstrap;

    import java.lang.annotation.Annotation;
    import java.lang.reflect.AccessibleObject;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Member;
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.Type;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    import org.qi4j.api.common.MetaInfo;
    import org.qi4j.api.common.Optional;
    import org.qi4j.api.common.QualifiedName;
    import org.qi4j.api.common.UseDefaults;
    import org.qi4j.api.common.Visibility;
    import org.qi4j.api.composite.InvalidCompositeException;
    import org.qi4j.api.concern.Concerns;
    import org.qi4j.api.constraint.Constraint;
    import org.qi4j.api.constraint.ConstraintDeclaration;
    import org.qi4j.api.constraint.Constraints;
    import org.qi4j.api.constraint.Name;
    import org.qi4j.api.entity.Lifecycle;
    import org.qi4j.api.injection.scope.State;
    import org.qi4j.api.injection.scope.This;
    import org.qi4j.api.mixin.Initializable;
    import org.qi4j.api.mixin.Mixins;
    import org.qi4j.api.property.GenericPropertyInfo;
    import org.qi4j.api.property.Immutable;
    import org.qi4j.api.property.Property;
    import org.qi4j.api.sideeffect.SideEffects;
    import org.qi4j.api.type.HasTypes;
    import org.qi4j.api.util.Annotations;
    import org.qi4j.api.util.Classes;
    import org.qi4j.api.util.Fields;
    import org.qi4j.bootstrap.StateDeclarations;
    import org.qi4j.functional.ForEach;
    import org.qi4j.functional.Function;
    import org.qi4j.functional.HierarchicalVisitorAdapter;
    import org.qi4j.functional.Iterables;
    import org.qi4j.functional.Specification;
    import org.qi4j.functional.Visitor;
    import org.qi4j.runtime.composite.AbstractConstraintModel;
    import org.qi4j.runtime.composite.CompositeConstraintModel;
    import org.qi4j.runtime.composite.CompositeMethodModel;
    import org.qi4j.runtime.composite.CompositeMethodsModel;
    import org.qi4j.runtime.composite.ConcernModel;
    import org.qi4j.runtime.composite.ConcernsModel;
    import org.qi4j.runtime.composite.ConstraintModel;
    import org.qi4j.runtime.composite.ConstraintsModel;
    import org.qi4j.runtime.composite.GenericSpecification;
    import org.qi4j.runtime.composite.MixinModel;
    import org.qi4j.runtime.composite.MixinsModel;
    import org.qi4j.runtime.composite.SideEffectModel;
    import org.qi4j.runtime.composite.SideEffectsModel;
    import org.qi4j.runtime.composite.StateModel;
    import org.qi4j.runtime.composite.ValueConstraintsInstance;
    import org.qi4j.runtime.composite.ValueConstraintsModel;
    import org.qi4j.runtime.injection.DependencyModel;
    import org.qi4j.runtime.property.PropertiesModel;
    import org.qi4j.runtime.property.PropertyModel;

    import static java.util.Arrays.asList;
    import static org.qi4j.api.util.Annotations.hasAnnotation;
    import static org.qi4j.api.util.Annotations.isType;
    import static org.qi4j.api.util.Annotations.type;
    import static org.qi4j.api.util.Classes.classHierarchy;
    import static org.qi4j.api.util.Classes.interfacesOf;
    import static org.qi4j.api.util.Classes.isAssignableFrom;
    import static org.qi4j.api.util.Classes.typeOf;
    import static org.qi4j.api.util.Classes.typesOf;
    import static org.qi4j.api.util.Classes.wrapperClass;
    import static org.qi4j.functional.Iterables.addAll;
    import static org.qi4j.functional.Iterables.cast;
    import static org.qi4j.functional.Iterables.empty;
    import static org.qi4j.functional.Iterables.filter;
    import static org.qi4j.functional.Iterables.first;
    import static org.qi4j.functional.Iterables.flatten;
    import static org.qi4j.functional.Iterables.flattenIterables;
    import static org.qi4j.functional.Iterables.iterable;
    import static org.qi4j.functional.Iterables.map;
    import static org.qi4j.functional.Iterables.matchesAny;
    import static org.qi4j.functional.Iterables.toList;
    import static org.qi4j.functional.Specifications.and;
    import static org.qi4j.functional.Specifications.in;
    import static org.qi4j.functional.Specifications.not;
    import static org.qi4j.functional.Specifications.or;
    import static org.qi4j.functional.Specifications.translate;

    /**
     * Declaration of a Composite.
     */
    public abstract class CompositeAssemblyImpl
        implements HasTypes
    {
        protected List<Class<?>> concerns = new ArrayList<>();
        protected List<Class<?>> sideEffects = new ArrayList<>();
        protected List<Class<?>> mixins = new ArrayList<>();
        protected List<Class<?>> types = new ArrayList<>();
        protected MetaInfo metaInfo = new MetaInfo();
        protected Visibility visibility = Visibility.module;

        protected boolean immutable;
        protected PropertiesModel propertiesModel;
        protected StateModel stateModel;
        protected MixinsModel mixinsModel;
        protected CompositeMethodsModel compositeMethodsModel;
        private AssemblyHelper helper;
        protected StateDeclarations stateDeclarations;

        protected Set<String> registeredStateNames = new HashSet<>();

        public CompositeAssemblyImpl( Class<?> mainType )
        {
            types.add( mainType );
        }

        @Override
        public Iterable<Class<?>> types()
        {
            return types;
        }

        protected StateModel createStateModel()
        {
            return new StateModel( propertiesModel );
        }

        protected MixinsModel createMixinsModel()
        {
            return new MixinsModel();
        }

        protected void buildComposite( AssemblyHelper helper,
                                       StateDeclarations stateDeclarations
        )
        {
            this.stateDeclarations = stateDeclarations;
            this.helper = helper;
            for( Class<?> compositeType : types )
            {
                metaInfo = new MetaInfo( metaInfo ).withAnnotations( compositeType );
                addAnnotationsMetaInfo( compositeType, metaInfo );
            }

            immutable = metaInfo.get( Immutable.class ) != null;
            propertiesModel = new PropertiesModel();
            stateModel = createStateModel();
            mixinsModel = createMixinsModel();
            compositeMethodsModel = new CompositeMethodsModel( mixinsModel );

            // Implement composite methods
            ArrayList<Type> allTypes = getTypes( this.types );
            Iterable<Class<? extends Constraint<?, ?>>> constraintClasses = constraintDeclarations( getTypes( this.types ) );
            Iterable<Class<?>> concernClasses = flatten( concerns, concernDeclarations( allTypes ) );
            Iterable<Class<?>> sideEffectClasses = flatten( sideEffects, sideEffectDeclarations( allTypes ) );
            Iterable<Class<?>> mixinClasses = flatten( mixins, mixinDeclarations( this.types ) );
            implementMixinType( types, constraintClasses, concernClasses, sideEffectClasses, mixinClasses );

            // Add state from methods and fields
            addState( constraintClasses );
        }

        protected void addAnnotationsMetaInfo( Class<?> type, MetaInfo compositeMetaInfo )
        {
            Class[] declaredInterfaces = type.getInterfaces();
            for( int i = declaredInterfaces.length - 1; i >= 0; i-- )
            {
                addAnnotationsMetaInfo( declaredInterfaces[ i], compositeMetaInfo );
            }
            compositeMetaInfo.withAnnotations( type );
        }

        protected void implementMixinType( Iterable<? extends Class<?>> types,
                                           Iterable<Class<? extends Constraint<?, ?>>> constraintClasses,
                                           Iterable<Class<?>> concernClasses,
                                           Iterable<Class<?>> sideEffectClasses,
                                           Iterable<Class<?>> mixinClasses
        )
        {
            Set<Class<?>> thisDependencies = new HashSet<>();
            for( Class<?> mixinType : types )
            {
                for( Method method : mixinType.getMethods() )
                {
                    if( !compositeMethodsModel.isImplemented( method )
                        && !Proxy.class.equals( method.getDeclaringClass().getSuperclass() )
                        && !Proxy.class.equals( method.getDeclaringClass() )
                        && !Modifier.isStatic( method.getModifiers() ) )
                    {
                        MixinModel mixinModel = implementMethod( method, mixinClasses );
                        ConcernsModel concernsModel = concernsFor(
                            method,
                            mixinModel.mixinClass(),
                            Iterables.<Class<?>>flatten( concernDeclarations( mixinModel.mixinClass() ),
                                                         concernClasses )
                        );
                        SideEffectsModel sideEffectsModel = sideEffectsFor(
                            method,
                            mixinModel.mixinClass(),
                            Iterables.<Class<?>>flatten( sideEffectDeclarations( mixinModel.mixinClass() ),
                                                         sideEffectClasses )
                        );
                        method.setAccessible( true );
                        ConstraintsModel constraints = constraintsFor(
                            method,
                            Iterables.<Class<? extends Constraint<?, ?>>>flatten( constraintDeclarations( mixinModel.mixinClass() ),
                                                                                  constraintClasses )
                        );
                        CompositeMethodModel methodComposite = new CompositeMethodModel(
                            method,
                            constraints,
                            concernsModel,
                            sideEffectsModel,
                            mixinsModel
                        );

                        // Implement @This references
                        Iterable<Class<?>> map = map( new DependencyModel.InjectionTypeFunction(),
                                                      filter( new DependencyModel.ScopeSpecification( This.class ),
                                                              methodComposite.dependencies() ) );
                        Iterable<Class<?>> map1 = map( new DependencyModel.InjectionTypeFunction(),
                                                       filter( new DependencyModel.ScopeSpecification( This.class ),
                                                               mixinModel.dependencies() ) );
                        @SuppressWarnings( "unchecked" )
                        Iterable<Class<?>> filter = filter(
                            not( in( Initializable.class, Lifecycle.class, InvocationHandler.class ) ),
                            map( Classes.RAW_CLASS, interfacesOf( mixinModel.mixinClass() ) )
                        );
                        Iterable<? extends Class<?>> flatten = flatten( map, map1, filter );
                        addAll( thisDependencies, flatten );

                        compositeMethodsModel.addMethod( methodComposite );
                    }
                }
                // Add type to set of mixin types
                mixinsModel.addMixinType( mixinType );
            }

            // Implement all @This dependencies that were found
            for( Class<?> thisDependency : thisDependencies )
            {
                // Add additional declarations from the @This type
                Iterable<Class<? extends Constraint<?, ?>>> typeConstraintClasses = flatten(
                    constraintClasses,
                    constraintDeclarations( thisDependency ) );
                Iterable<Class<?>> typeConcernClasses = flatten(
                    concernClasses,
                    concernDeclarations( thisDependency ) );
                Iterable<Class<?>> typeSideEffectClasses = flatten(
                    sideEffectClasses,
                    sideEffectDeclarations( thisDependency ) );
                Iterable<Class<?>> typeMixinClasses = flatten(
                    mixinClasses,
                    mixinDeclarations( thisDependency ) );

                @SuppressWarnings( "unchecked" )
                Iterable<? extends Class<?>> singleton = iterable( thisDependency );
                implementMixinType( singleton, typeConstraintClasses, typeConcernClasses, typeSideEffectClasses, typeMixinClasses );
            }
        }

        @SuppressWarnings( "raw" )
        protected MixinModel implementMethod( Method method, Iterable<Class<?>> mixinDeclarations )
        {
            MixinModel implementationModel = mixinsModel.mixinFor( method );
            if( implementationModel != null )
            {
                return implementationModel;
            }
            Class mixinClass = findTypedImplementation( method, mixinDeclarations );
            if( mixinClass != null )
            {
                return implementMethodWithClass( method, mixinClass );
            }

            // Check generic implementations
            mixinClass = findGenericImplementation( method, mixinDeclarations );
            if( mixinClass != null )
            {
                return implementMethodWithClass( method, mixinClass );
            }

            throw new InvalidCompositeException( "No implementation found for method \n    " + method.toGenericString()
                                                 + "\nin\n    " + types );
        }

        @SuppressWarnings( {"raw", "unchecked"} )
        private Class findTypedImplementation( final Method method, Iterable<Class<?>> mixins )
        {
            // Check if mixinClass implements the method. If so, check if the mixinClass is generic or if the filter passes.
            // If a mixinClass is both generic AND non-generic at the same time, then the filter applies to the non-generic
            // side only.
            Specification<Class<?>> appliesToSpec = new Specification<Class<?>>()
            {
                @Override
                public boolean satisfiedBy( Class<?> item )
                {
                    return helper.appliesTo( item, method, types, item );
                }
            };
            return first( filter( and( isAssignableFrom( method.getDeclaringClass() ),
                                       or( GenericSpecification.INSTANCE, appliesToSpec ) ),
                                  mixins ) );
        }

        @SuppressWarnings( "unchecked" )
        private Class<?> findGenericImplementation( final Method method, Iterable<Class<?>> mixins )
        {
            // Check if mixinClass is generic and the applies-to filter passes
            return first( filter( and( GenericSpecification.INSTANCE, new Specification<Class<?>>()
            {
                @Override
                public boolean satisfiedBy( Class<?> item )
                {
                    return helper.appliesTo( item, method, types, item );
                }
            } ), mixins ) );
        }

        private MixinModel implementMethodWithClass( Method method, Class mixinClass )
        {
            MixinModel mixinModel = mixinsModel.getMixinModel( mixinClass );
            if( mixinModel == null )
            {
                mixinModel = helper.getMixinModel( mixinClass );
                mixinsModel.addMixinModel( mixinModel );
            }

            mixinsModel.addMethodMixin( method, mixinModel );

            return mixinModel;
        }

        protected void addState( final Iterable<Class<? extends Constraint<?, ?>>> constraintClasses )
        {
            // Add method state
            compositeMethodsModel.accept( new HierarchicalVisitorAdapter<Object, Object, RuntimeException>()
            {
                @Override
                public boolean visitEnter( Object visited )
                    throws RuntimeException
                {
                    if( visited instanceof CompositeMethodModel )
                    {
                        CompositeMethodModel methodModel = (CompositeMethodModel) visited;
                        if( methodModel.method().getParameterTypes().length == 0 )
                        {
                            addStateFor( methodModel.method(), constraintClasses );
                        }

                        return false;
                    }

                    return super.visitEnter( visited );
                }
            } );

            // Add field state
            mixinsModel.accept( new HierarchicalVisitorAdapter<Object, Object, RuntimeException>()
            {
                @Override
                public boolean visitEnter( Object visited )
                    throws RuntimeException
                {
                    if( visited instanceof MixinModel )
                    {
                        MixinModel model = (MixinModel) visited;
                        Visitor<Field, RuntimeException> addState = new Visitor<Field, RuntimeException>()
                        {
                            @Override
                            public boolean visit( Field visited )
                                throws RuntimeException
                            {
                                addStateFor( visited, constraintClasses );
                                return true;
                            }
                        };
                        ForEach.forEach( Fields.FIELDS_OF.map( model.mixinClass() ) ).
                            filter( Annotations.hasAnnotation( State.class ) ).
                            visit( addState );
                        return false;
                    }
                    return super.visitEnter( visited );
                }
            } );
        }

        protected void addStateFor( AccessibleObject accessor,
                                    Iterable<Class<? extends Constraint<?, ?>>> constraintClasses
        )
        {
            String stateName = QualifiedName.fromAccessor( accessor ).name();

            if( registeredStateNames.contains( stateName ) )
            {
                return; // Skip already registered names
            }

            if( Property.class.isAssignableFrom( Classes.RAW_CLASS.map( typeOf( accessor ) ) ) )
            {
                propertiesModel.addProperty( newPropertyModel( accessor, constraintClasses ) );
                registeredStateNames.add( stateName );
            }
        }

        protected PropertyModel newPropertyModel( AccessibleObject accessor,
                                                  Iterable<Class<? extends Constraint<?, ?>>> constraintClasses
        )
        {
            Iterable<Annotation> annotations = Annotations.findAccessorAndTypeAnnotationsIn( accessor );
            boolean optional = first( filter( isType( Optional.class ), annotations ) ) != null;
            ValueConstraintsModel valueConstraintsModel = constraintsFor(
                annotations,
                GenericPropertyInfo.propertyTypeOf( accessor ),
                ( (Member) accessor ).getName(),
                optional,
                constraintClasses,
                accessor );
            ValueConstraintsInstance valueConstraintsInstance = null;
            if( valueConstraintsModel.isConstrained() )
            {
                valueConstraintsInstance = valueConstraintsModel.newInstance();
            }
            MetaInfo metaInfo = stateDeclarations.metaInfoFor( accessor );
            Object initialValue = stateDeclarations.initialValueOf( accessor );
            boolean useDefaults = metaInfo.get( UseDefaults.class ) != null || stateDeclarations.useDefaults( accessor );
            boolean immutable = this.immutable || metaInfo.get( Immutable.class ) != null;
            PropertyModel propertyModel = new PropertyModel(
                accessor,
                immutable,
                useDefaults,
                valueConstraintsInstance,
                metaInfo,
                initialValue );
            return propertyModel;
        }

        // Model
        private ConstraintsModel constraintsFor( Method method,
                                                 Iterable<Class<? extends Constraint<?, ?>>> constraintClasses
        )
        {
            List<ValueConstraintsModel> parameterConstraintModels = Collections.emptyList();
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            Type[] parameterTypes = method.getGenericParameterTypes();
            boolean constrained = false;
            for( int i = 0; i < parameterAnnotations.length; i++ )
            {
                Annotation[] parameterAnnotation = parameterAnnotations[i];

                Name nameAnnotation = (Name) first( filter( isType( Name.class ), iterable( parameterAnnotation ) ) );
                String name = nameAnnotation == null ? "param" + ( i + 1 ) : nameAnnotation.value();

                boolean optional = first( filter( isType( Optional.class ), iterable( parameterAnnotation ) ) ) != null;
                ValueConstraintsModel parameterConstraintsModel = constraintsFor(
                    asList( parameterAnnotation ),
                    parameterTypes[i],
                    name,
                    optional,
                    constraintClasses,
                    method );
                if( parameterConstraintsModel.isConstrained() )
                {
                    constrained = true;
                }

                if( parameterConstraintModels.isEmpty() )
                {
                    parameterConstraintModels = new ArrayList<>();
                }
                parameterConstraintModels.add( parameterConstraintsModel );
            }

            if( !constrained )
            {
                return new ConstraintsModel( Collections.<ValueConstraintsModel>emptyList() );
            }
            else
            {
                return new ConstraintsModel( parameterConstraintModels );
            }
        }

        protected ValueConstraintsModel constraintsFor(
            Iterable<Annotation> constraintAnnotations,
            Type valueType,
            String name,
            boolean optional,
            Iterable<Class<? extends Constraint<?, ?>>> constraintClasses,
            AccessibleObject accessor
        )
        {
            valueType = wrapperClass( valueType );

            List<AbstractConstraintModel> constraintModels = new ArrayList<>();
            nextConstraint:
            for( Annotation constraintAnnotation : filter( translate( type(), hasAnnotation( ConstraintDeclaration.class ) ),
                                                           constraintAnnotations ) )
            {
                // Check composite declarations first
                Class<? extends Annotation> annotationType = constraintAnnotation.annotationType();
                for( Class<? extends Constraint<?, ?>> constraint : constraintClasses )
                {
                    if( helper.appliesTo( constraint, annotationType, valueType ) )
                    {
                        constraintModels.add( new ConstraintModel( constraintAnnotation, constraint ) );
                        continue nextConstraint;
                    }
                }

                // Check the annotation itself
                Constraints constraints = annotationType.getAnnotation( Constraints.class );
                if( constraints != null )
                {
                    for( Class<? extends Constraint<?, ?>> constraintClass : constraints.value() )
                    {
                        if( helper.appliesTo( constraintClass, annotationType, valueType ) )
                        {
                            constraintModels.add( new ConstraintModel( constraintAnnotation, constraintClass ) );
                            continue nextConstraint;
                        }
                    }
                }

                // No implementation found!
                // Check if if it's a composite constraints
                Iterable<Annotation> annotations = iterable( annotationType.getAnnotations() );
                if( matchesAny( translate( type(), hasAnnotation( ConstraintDeclaration.class ) ), annotations ) )
                {
                    ValueConstraintsModel valueConstraintsModel = constraintsFor(
                        annotations,
                        valueType,
                        name,
                        optional,
                        constraintClasses,
                        accessor );
                    CompositeConstraintModel compositeConstraintModel = new CompositeConstraintModel(
                        constraintAnnotation,
                        valueConstraintsModel );
                    constraintModels.add( compositeConstraintModel );
                    continue nextConstraint;
                }

                throw new InvalidCompositeException(
                    "Cannot find implementation of constraint @"
                    + annotationType.getSimpleName()
                    + " for "
                    + valueType
                    + " in method "
                    + ( (Member) accessor ).getName()
                    + " of composite " + types );
            }

            return new ValueConstraintsModel( constraintModels, name, optional );
        }

        private ConcernsModel concernsFor( Method method,
                                           Class<?> mixinClass,
                                           Iterable<Class<?>> concernClasses
        )
        {
            List<ConcernModel> concernsFor = new ArrayList<>();
            for( Class<?> concern : concernClasses )
            {
                if( helper.appliesTo( concern, method, types, mixinClass ) )
                {
                    concernsFor.add( helper.getConcernModel( concern ) );
                }
                else
                {
                    // Lookup method in mixin
                    if( !InvocationHandler.class.isAssignableFrom( mixinClass ) )
                    {
                        try
                        {
                            Method mixinMethod = mixinClass.getMethod( method.getName(), method.getParameterTypes() );
                            if( helper.appliesTo( concern, mixinMethod, types, mixinClass ) )
                            {
                                concernsFor.add( helper.getConcernModel( concern ) );
                            }
                        }
                        catch( NoSuchMethodException e )
                        {
                            // Ignore
                        }
                    }
                }
            }

            // Check annotations on method that have @Concerns annotations themselves
            for( Annotation annotation : method.getAnnotations() )
            {
                @SuppressWarnings( "raw" )
                Concerns concerns = annotation.annotationType().getAnnotation( Concerns.class );
                if( concerns != null )
                {
                    for( Class<?> concern : concerns.value() )
                    {
                        if( helper.appliesTo( concern, method, types, mixinClass ) )
                        {
                            concernsFor.add( helper.getConcernModel( concern ) );
                        }
                    }
                }
            }

            if( concernsFor.isEmpty() )
            {
                return ConcernsModel.EMPTY_CONCERNS;
            }
            else
            {
                return new ConcernsModel( concernsFor );
            }
        }

        private SideEffectsModel sideEffectsFor( Method method,
                                                 Class<?> mixinClass,
                                                 Iterable<Class<?>> sideEffectClasses
        )
        {
            List<SideEffectModel> sideEffectsFor = new ArrayList<>();
            for( Class<?> sideEffect : sideEffectClasses )
            {
                if( helper.appliesTo( sideEffect, method, types, mixinClass ) )
                {
                    sideEffectsFor.add( helper.getSideEffectModel( sideEffect ) );
                }
                else
                {
                    // Lookup method in mixin
                    if( !InvocationHandler.class.isAssignableFrom( mixinClass ) )
                    {
                        try
                        {
                            Method mixinMethod = mixinClass.getMethod( method.getName(), method.getParameterTypes() );
                            if( helper.appliesTo( sideEffect, mixinMethod, types, mixinClass ) )
                            {
                                sideEffectsFor.add( helper.getSideEffectModel( sideEffect ) );
                            }
                        }
                        catch( NoSuchMethodException e )
                        {
                            // Ignore
                        }
                    }
                }
            }

            if( sideEffectsFor.isEmpty() )
            {
                return SideEffectsModel.EMPTY_SIDEEFFECTS;
            }
            else
            {
                return new SideEffectsModel( sideEffectsFor );
            }
        }

        @SuppressWarnings( "unchecked" )
        private Iterable<Class<? extends Constraint<?, ?>>> constraintDeclarations( Class<?> type )
        {
            ArrayList<Type> allTypes = getTypes( type );
            return constraintDeclarations( allTypes );
        }

        private Iterable<Class<? extends Constraint<?, ?>>> constraintDeclarations( ArrayList<Type> allTypes )
        {
            // Find all constraints and flatten them into an iterable
            Function<Type, Iterable<Class<? extends Constraint<?, ?>>>> function = new Function<Type, Iterable<Class<? extends Constraint<?, ?>>>>()
            {
                @Override
                public Iterable<Class<? extends Constraint<?, ?>>> map( Type type )
                {
                    Constraints constraints = Annotations.annotationOn( type, Constraints.class );
                    if( constraints == null )
                    {
                        return empty();
                    }
                    else
                    {
                        return iterable( constraints.value() );
                    }
                }
            };
            Iterable<Class<? extends Constraint<?, ?>>> flatten = flattenIterables( map( function, allTypes ) );
            return toList( flatten );
        }

        @SuppressWarnings( "unchecked" )
        private Iterable<Class<?>> concernDeclarations( Class<?> type )
        {
            Iterable<? extends Class<?>> iterable = iterable( type );
            return concernDeclarations( getTypes( iterable ) );
        }

        private Iterable<Class<?>> concernDeclarations( ArrayList<Type> allTypes )
        {
            // Find all concerns and flattern them into an iterable
            Function<Type, Iterable<Class<?>>> function = new Function<Type, Iterable<Class<?>>>()
            {
                @Override
                public Iterable<Class<?>> map( Type type )
                {
                    Concerns concerns = Annotations.annotationOn( type, Concerns.class );
                    if( concerns == null )
                    {
                        return empty();
                    }
                    else
                    {
                        return iterable( concerns.value() );
                    }
                }
            };
            Iterable<Class<?>> flatten = flattenIterables( map( function, allTypes ) );
            return toList( flatten );
        }

        @SuppressWarnings( "unchecked" )
        protected Iterable<Class<?>> sideEffectDeclarations( Class<?> type )
        {
            Iterable<? extends Class<?>> iterable = iterable( type );
            return sideEffectDeclarations( getTypes( iterable ) );
        }

        protected Iterable<Class<?>> sideEffectDeclarations( ArrayList<Type> allTypes )
        {
            // Find all side-effects and flattern them into an iterable
            Function<Type, Iterable<Class<?>>> function = new Function<Type, Iterable<Class<?>>>()
            {
                @Override
                public Iterable<Class<?>> map( Type type )
                {
                    SideEffects sideEffects = Annotations.annotationOn( type, SideEffects.class );
                    if( sideEffects == null )
                    {
                        return empty();
                    }
                    else
                    {
                        return iterable( sideEffects.value() );
                    }
                }
            };
            Iterable<Class<?>> flatten = flattenIterables( map( function, allTypes ) );
            return toList( flatten );
        }

        private ArrayList<Type> getTypes( Class<?> type )
        {
            Iterable<? extends Class<?>> iterable = iterable( type );
            return getTypes( iterable );
        }

        private ArrayList<Type> getTypes( Iterable<? extends Class<?>> typess )
        {
            // Find side-effect declarations
            ArrayList<Type> allTypes = new ArrayList<>();
            for( Class<?> type : typess )
            {
                Iterable<Type> types;
                if( type.isInterface() )
                {
                    types = typesOf( type );
                }
                else
                {
                    types = cast( classHierarchy( type ) );
                }
                addAll( allTypes, types );
            }
            return allTypes;
        }

        @SuppressWarnings( "unchecked" )
        protected Iterable<Class<?>> mixinDeclarations( Class<?> type )
        {
            Iterable<? extends Class<?>> iterable = iterable( type );
            return mixinDeclarations( iterable );
        }

        protected Iterable<Class<?>> mixinDeclarations( Iterable<? extends Class<?>> typess )
        {
            // Find mixin declarations
            ArrayList<Type> allTypes = new ArrayList<>();
            for( Class<?> type : typess )
            {
                Iterable<Type> types = typesOf( type );
                addAll( allTypes, types );
            }

            // Find all mixins and flattern them into an iterable
            Function<Type, Iterable<Class<?>>> function = new Function<Type, Iterable<Class<?>>>()
            {
                @Override
                public Iterable<Class<?>> map( Type type )
                {
                    Mixins mixins = Annotations.annotationOn( type, Mixins.class );
                    if( mixins == null )
                    {
                        return empty();
                    }
                    else
                    {
                        return iterable( mixins.value() );
                    }
                }
            };
            Iterable<Class<?>> flatten = flattenIterables( map( function, allTypes ) );
            return toList( flatten );
        }
    }