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