# ServiceInjectionProviderFactory.java

    /*
     * Copyright (c) 2007, 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.provider;

    import java.lang.annotation.Annotation;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import org.qi4j.api.service.NoSuchServiceException;
    import org.qi4j.api.service.ServiceReference;
    import org.qi4j.api.service.qualifier.Qualifier;
    import org.qi4j.api.util.Annotations;
    import org.qi4j.api.util.Classes;
    import org.qi4j.bootstrap.InvalidInjectionException;
    import org.qi4j.functional.Function;
    import org.qi4j.functional.Iterables;
    import org.qi4j.functional.Specification;
    import org.qi4j.functional.Specifications;
    import org.qi4j.runtime.injection.DependencyModel;
    import org.qi4j.runtime.injection.InjectionContext;
    import org.qi4j.runtime.injection.InjectionProvider;
    import org.qi4j.runtime.injection.InjectionProviderFactory;
    import org.qi4j.runtime.model.Resolution;

    import static org.qi4j.api.util.Annotations.hasAnnotation;
    import static org.qi4j.functional.Iterables.filter;
    import static org.qi4j.functional.Iterables.first;
    import static org.qi4j.functional.Iterables.iterable;

    public final class ServiceInjectionProviderFactory
        implements InjectionProviderFactory
    {
        @Override
        @SuppressWarnings( "unchecked" )
        public InjectionProvider newInjectionProvider( Resolution resolution, DependencyModel dependencyModel )
            throws InvalidInjectionException
        {
            // TODO This could be changed to allow multiple @Qualifier annotations
            Annotation qualifierAnnotation = first( filter( Specifications.translate( Annotations.type(), hasAnnotation( Qualifier.class ) ), iterable( dependencyModel
                                                                                                                                                            .annotations() ) ) );
            Specification<ServiceReference<?>> serviceQualifier = null;
            if( qualifierAnnotation != null )
            {
                Qualifier qualifier = qualifierAnnotation.annotationType().getAnnotation( Qualifier.class );
                try
                {
                    serviceQualifier = qualifier.value().newInstance().qualifier( qualifierAnnotation );
                }
                catch( Exception e )
                {
                    throw new InvalidInjectionException( "Could not instantiate qualifier serviceQualifier", e );
                }
            }

            if( dependencyModel.rawInjectionType().equals( Iterable.class ) )
            {
                Type iterableType = ( (ParameterizedType) dependencyModel.injectionType() ).getActualTypeArguments()[ 0 ];
                if( Classes.RAW_CLASS.map( iterableType ).equals( ServiceReference.class ) )
                {
                    // @Service Iterable<ServiceReference<MyService<Foo>> serviceRefs
                    Type serviceType = ( (ParameterizedType) iterableType ).getActualTypeArguments()[ 0 ];

                    return new IterableServiceReferenceProvider( serviceType, serviceQualifier );
                }
                else
                {
                    // @Service Iterable<MyService<Foo>> services
                    return new IterableServiceProvider( iterableType, serviceQualifier );
                }
            }
            else if( dependencyModel.rawInjectionType().equals( ServiceReference.class ) )
            {
                // @Service ServiceReference<MyService<Foo>> serviceRef
                Type referencedType = ( (ParameterizedType) dependencyModel.injectionType() ).getActualTypeArguments()[ 0 ];
                return new ServiceReferenceProvider( referencedType, serviceQualifier );
            }
            else
            {
                // @Service MyService<Foo> service
                return new ServiceProvider( dependencyModel.injectionType(), serviceQualifier );
            }
        }

        private static class IterableServiceReferenceProvider
            extends ServiceInjectionProvider
        {
            private IterableServiceReferenceProvider( Type serviceType,
                                                      Specification<ServiceReference<?>> serviceQualifier
            )
            {
                super( serviceType, serviceQualifier );
            }

            @Override
            public synchronized Object provideInjection( InjectionContext context )
                throws InjectionProviderException
            {
                return getServiceReferences( context );
            }
        }

        private static class IterableServiceProvider
            extends ServiceInjectionProvider
            implements Function<ServiceReference<?>, Object>
        {
            private IterableServiceProvider( Type serviceType,
                                             Specification<ServiceReference<?>> serviceQualifier
            )
            {
                super( serviceType, serviceQualifier );
            }

            @Override
            public synchronized Object provideInjection( final InjectionContext context )
                throws InjectionProviderException
            {
                return Iterables.map( this, getServiceReferences( context ) );
            }

            @Override
            public Object map( ServiceReference<?> objectServiceReference )
            {
                return objectServiceReference.get();
            }
        }

        private static class ServiceReferenceProvider
            extends ServiceInjectionProvider
        {
            ServiceReferenceProvider( Type serviceType, Specification<ServiceReference<?>> qualifier )
            {
                super( serviceType, qualifier );
            }

            @Override
            public synchronized Object provideInjection( InjectionContext context )
                throws InjectionProviderException
            {
                return getServiceReference( context );
            }
        }

        private static class ServiceProvider
            extends ServiceInjectionProvider
        {
            ServiceProvider( Type serviceType, Specification<ServiceReference<?>> qualifier )
            {
                super( serviceType, qualifier );
            }

            @Override
            public synchronized Object provideInjection( InjectionContext context )
                throws InjectionProviderException
            {
                ServiceReference<?> ref = getServiceReference( context );

                if( ref != null )
                {
                    return ref.get();
                }
                else
                {
                    return null;
                }
            }
        }

        private abstract static class ServiceInjectionProvider
            implements InjectionProvider
        {
            private final Type serviceType;
            private final Specification<ServiceReference<?>> serviceQualifier;

            private ServiceInjectionProvider( Type serviceType,
                                                Specification<ServiceReference<?>> serviceQualifier
            )
            {
                this.serviceType = serviceType;
                this.serviceQualifier = serviceQualifier;
            }

            protected ServiceReference<Object> getServiceReference( InjectionContext context )
            {
                try
                {
                    if( serviceQualifier == null )
                    {
                        return context.module().findService( serviceType );
                    }
                    else
                    {
                        return Iterables.first( Iterables.filter( serviceQualifier, context.module()
                            .findServices( serviceType ) ) );
                    }
                }
                catch( NoSuchServiceException e )
                {
                    return null;
                }
            }

            protected Iterable<ServiceReference<Object>> getServiceReferences( final InjectionContext context )
            {
                if( serviceQualifier == null )
                {
                    return context.module().findServices( serviceType );
                }
                else
                {
                    return Iterables.filter( serviceQualifier, context.module().findServices( serviceType ) );
                }
            }
        }
    }