# AbstractModifierModel.java

    /*
     * 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.composite;

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import org.qi4j.api.common.ConstructionException;
    import org.qi4j.functional.HierarchicalVisitor;
    import org.qi4j.functional.VisitableHierarchy;
    import org.qi4j.runtime.injection.Dependencies;
    import org.qi4j.runtime.injection.DependencyModel;
    import org.qi4j.runtime.injection.InjectedFieldsModel;
    import org.qi4j.runtime.injection.InjectedMethodsModel;
    import org.qi4j.runtime.injection.InjectionContext;
    import org.qi4j.spi.module.ModuleSpi;

    import static org.qi4j.api.util.Classes.RAW_CLASS;
    import static org.qi4j.api.util.Classes.interfacesOf;
    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.toArray;
    import static org.qi4j.functional.Iterables.unique;

    /**
     * JAVADOC
     */
    public abstract class AbstractModifierModel
        implements Dependencies, VisitableHierarchy<Object, Object>
    {
        private final Class<?> modifierClass;

        private final ConstructorsModel constructorsModel;
        private final InjectedFieldsModel injectedFieldsModel;
        private final InjectedMethodsModel injectedMethodsModel;

        private final Class<?>[] nextInterfaces;

        @SuppressWarnings( "unchecked" )
        public AbstractModifierModel( Class<?> declaredModifierClass, Class<?> instantiationClass )
        {
            this.modifierClass = instantiationClass;
            constructorsModel = new ConstructorsModel( modifierClass );
            injectedFieldsModel = new InjectedFieldsModel( declaredModifierClass );
            injectedMethodsModel = new InjectedMethodsModel( declaredModifierClass );
            Class<Class<?>> componentType = (Class<Class<?>>) Class.class.cast( Class.class );
            nextInterfaces = toArray( componentType, unique( map( RAW_CLASS, interfacesOf( declaredModifierClass ) ) ) );
        }

        public Class<?> modifierClass()
        {
            return modifierClass;
        }

        @Override
        @SuppressWarnings( "unchecked" )
        public Iterable<DependencyModel> dependencies()
        {
            return flattenIterables( map( DEPENDENCIES_FUNCTION, iterable( constructorsModel, injectedFieldsModel, injectedMethodsModel ) ) );
        }

        public boolean isGeneric()
        {
            return InvocationHandler.class.isAssignableFrom( modifierClass );
        }

        @Override
        public <ThrowableType extends Throwable> boolean accept( HierarchicalVisitor<? super Object, ? super Object, ThrowableType> visitor )
            throws ThrowableType
        {
            if( visitor.visitEnter( this ) )
            {
                if( constructorsModel.accept( visitor ) )
                {
                    if( injectedFieldsModel.accept( visitor ) )
                    {
                        injectedMethodsModel.accept( visitor );
                    }
                }
            }

            return visitor.visitLeave( this );
        }

        // Context
        public InvocationHandler newInstance( ModuleSpi moduleInstance,
                                              InvocationHandler next,
                                              ProxyReferenceInvocationHandler proxyHandler,
                                              Method method
        )
        {
            InjectionContext injectionContext = new InjectionContext( moduleInstance, wrapNext( next ), proxyHandler );

            Object modifier = constructorsModel.newInstance( injectionContext );

            try
            {
                if( FragmentClassLoader.isGenerated( modifier ) )
                {
                    modifier.getClass().getField( "_instance" ).set( modifier, proxyHandler );
                }
            }
            catch( IllegalAccessException | NoSuchFieldException e )
            {
                e.printStackTrace();
            }

            injectedFieldsModel.inject( injectionContext, modifier );
            injectedMethodsModel.inject( injectionContext, modifier );

            if( isGeneric() )
            {
                return (InvocationHandler) modifier;
            }
            else
            {
                try
                {
                    Method invocationMethod = modifierClass.getMethod( "_" + method.getName(), method.getParameterTypes() );
                    TypedModifierInvocationHandler handler = new TypedModifierInvocationHandler();
                    handler.setFragment( modifier );
                    handler.setMethod( invocationMethod );
                    return handler;
                }
                catch( NoSuchMethodException e )
                {
                    throw new ConstructionException( "Could not find modifier method", e );
                }
            }
        }

        private Object wrapNext( InvocationHandler next )
        {
            if( isGeneric() )
            {
                return next;
            }
            else
            {
                return Proxy.newProxyInstance( modifierClass.getClassLoader(), nextInterfaces, next );
            }
        }

        @Override
        public boolean equals( Object o )
        {
            if( this == o )
            {
                return true;
            }
            if( o == null || getClass() != o.getClass() )
            {
                return false;
            }
            AbstractModifierModel that = (AbstractModifierModel) o;
            return modifierClass.equals( that.modifierClass );
        }

        @Override
        public int hashCode()
        {
            return modifierClass.hashCode();
        }
    }