001// Copyright 2007, 2008 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.ioc.internal;
016
017import org.apache.tapestry5.ioc.*;
018import static org.apache.tapestry5.ioc.internal.ConfigurationType.*;
019import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
020import org.apache.tapestry5.ioc.internal.util.DelegatingInjectionResources;
021import org.apache.tapestry5.ioc.internal.util.InjectionResources;
022import org.apache.tapestry5.ioc.internal.util.MapInjectionResources;
023import org.slf4j.Logger;
024
025import java.lang.reflect.ParameterizedType;
026import java.lang.reflect.Type;
027import java.util.Collection;
028import java.util.List;
029import java.util.Map;
030
031/**
032 * Abstract implementation of {@link ObjectCreator} geared towards the creation of the core service implementation,
033 * either by invoking a service builder method on a module, or by invoking a constructor.
034 */
035public abstract class AbstractServiceCreator implements ObjectCreator
036{
037    protected final String serviceId;
038
039    private final Map<Class, Object> injectionResources = CollectionFactory.newMap();
040
041    protected final ServiceBuilderResources resources;
042
043    protected final Logger logger;
044
045    private final static Map<Class, ConfigurationType> PARAMETER_TYPE_TO_CONFIGURATION_TYPE = CollectionFactory.newMap();
046
047    protected final String creatorDescription;
048
049    static
050    {
051        PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(Collection.class, UNORDERED);
052        PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(List.class, ORDERED);
053        PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(Map.class, MAPPED);
054    }
055
056    public AbstractServiceCreator(ServiceBuilderResources resources, String creatorDescription)
057    {
058        serviceId = resources.getServiceId();
059
060        this.resources = resources;
061        this.creatorDescription = creatorDescription;
062        logger = resources.getLogger();
063
064        injectionResources.put(ObjectLocator.class, resources);
065        injectionResources.put(ServiceResources.class, resources);
066        injectionResources.put(Logger.class, logger);
067        injectionResources.put(Class.class, resources.getServiceInterface());
068        injectionResources.put(OperationTracker.class, resources.getTracker());
069    }
070
071    /**
072     * Returns a map (based on injectionResources) that includes (possibly) an additional mapping containing the
073     * collected configuration data. This involves scanning the parameters and generic types.
074     */
075    protected final InjectionResources createInjectionResources()
076    {
077        InjectionResources core = new MapInjectionResources(injectionResources);
078
079        InjectionResources configurations = new InjectionResources()
080        {
081            private boolean seenOne;
082
083            public <T> T findResource(Class<T> resourceType, Type genericType)
084            {
085                ConfigurationType thisType = PARAMETER_TYPE_TO_CONFIGURATION_TYPE.get(resourceType);
086
087                if (thisType == null) return null;
088
089                if (seenOne)
090                    throw new RuntimeException(IOCMessages.tooManyConfigurationParameters(creatorDescription));
091
092
093                seenOne = true;
094
095                switch (thisType)
096                {
097                    case UNORDERED:
098
099                        return resourceType.cast(getUnorderedConfiguration(genericType));
100
101                    case ORDERED:
102
103                        return resourceType.cast(getOrderedConfiguration(genericType));
104
105                    case MAPPED:
106
107                        return resourceType.cast(getMappedConfiguration(genericType));
108                }
109
110                return null;
111            }
112        };
113
114
115        return new DelegatingInjectionResources(core, configurations);
116    }
117
118    @SuppressWarnings("unchecked")
119    private List getOrderedConfiguration(Type genericType)
120    {
121        Class valueType = findParameterizedTypeFromGenericType(genericType);
122        
123        return resources.getOrderedConfiguration(valueType);
124    }
125
126
127    @SuppressWarnings("unchecked")
128    private Collection getUnorderedConfiguration(Type genericType)
129    {
130        Class valueType = findParameterizedTypeFromGenericType(genericType);
131
132        return resources.getUnorderedConfiguration(valueType);
133    }
134
135    @SuppressWarnings("unchecked")
136    private Map getMappedConfiguration(Type genericType)
137    {
138        Class keyType = findParameterizedTypeFromGenericType(genericType, 0);
139        Class valueType = findParameterizedTypeFromGenericType(genericType, 1);
140
141        if (keyType == null || valueType == null)
142            throw new IllegalArgumentException(IOCMessages.genericTypeNotSupported(genericType));
143
144        return resources.getMappedConfiguration(keyType, valueType);
145    }
146
147    /**
148     * Extracts from a generic type the underlying parameterized type. I.e., for List<Runnable>, will return Runnable.
149     * This is limited to simple parameterized types, not the more complex cases involving wildcards and upper/lower
150     * boundaries.
151     *
152     * @param type the genetic type of the parameter, i.e., List<Runnable>
153     * @return the parameterize type (i.e. Runnable.class if type represents List<Runnable>).
154     */
155
156    // package private for testing
157    static Class findParameterizedTypeFromGenericType(Type type)
158    {
159        Class result = findParameterizedTypeFromGenericType(type, 0);
160
161        if (result == null) throw new IllegalArgumentException(IOCMessages.genericTypeNotSupported(type));
162
163        return result;
164    }
165
166    /**
167     * "Sniffs" a generic type to find the underlying parameterized type. If the Type is a class, then Object.class is
168     * returned. Otherwise, the type must be a ParameterizedType. We check to make sure it has the correct number of a
169     * actual types (1 for a Collection or List, 2 for a Map). The actual types must be classes (wildcards just aren't
170     * supported)
171     *
172     * @param type      a Class or ParameterizedType to inspect
173     * @param typeIndex the index within the ParameterizedType to extract
174     * @return the actual type, or Object.class if the input type is not generic, or null if any other pre-condition is
175     *         not met
176     */
177    private static Class findParameterizedTypeFromGenericType(Type type, int typeIndex)
178    {
179        // For a raw Class type, it means the parameter is not parameterized (i.e. Collection, not
180        // Collection<Foo>), so we can return Object.class to allow no restriction.
181
182        if (type instanceof Class) return Object.class;
183
184        if (!(type instanceof ParameterizedType)) return null;
185
186        ParameterizedType pt = (ParameterizedType) type;
187
188        Type[] types = pt.getActualTypeArguments();
189
190        Type actualType = types[typeIndex];
191
192        return actualType instanceof Class ? (Class) actualType : null;
193    }
194}