001/*****************************************************************************
002 * Copyright (C) PicoContainer Organization. All rights reserved.            *
003 * ------------------------------------------------------------------------- *
004 * The software in this package is published under the terms of the BSD      *
005 * style license a copy of which has been included with this distribution in *
006 * the LICENSE.txt file.                                                     *
007 *                                                                           *
008 * Original code by                                                          *
009 *****************************************************************************/
010package org.picocontainer.parameters;
011
012import org.picocontainer.ComponentAdapter;
013import org.picocontainer.Parameter;
014import org.picocontainer.PicoContainer;
015import org.picocontainer.PicoVisitor;
016import org.picocontainer.NameBinding;
017import org.picocontainer.injectors.AbstractInjector;
018
019import java.lang.annotation.Annotation;
020import java.lang.reflect.ParameterizedType;
021import java.lang.reflect.Type;
022
023
024/**
025 * A ComponentParameter should be used to pass in a particular component as argument to a
026 * different component's constructor. This is particularly useful in cases where several
027 * components of the same type have been registered, but with a different key. Passing a
028 * ComponentParameter as a parameter when registering a component will give PicoContainer a hint
029 * about what other component to use in the constructor. Collecting parameter types are
030 * supported for {@link java.lang.reflect.Array},{@link java.util.Collection}and
031 * {@link java.util.Map}.
032 * 
033 * @author Jon Tirsén
034 * @author Aslak Hellesøy
035 * @author Jörg Schaible
036 * @author Thomas Heller
037 */
038@SuppressWarnings("serial")
039public class ComponentParameter
040        extends BasicComponentParameter {
041
042    /**
043     * <code>DEFAULT</code> is an instance of ComponentParameter using the default constructor.
044     */
045    public static final ComponentParameter DEFAULT = new ComponentParameter();
046    /**
047     * Use <code>ARRAY</code> as {@link Parameter}for an Array that must have elements.
048     */
049    public static final ComponentParameter ARRAY = new ComponentParameter(false);
050    /**
051     * Use <code>ARRAY_ALLOW_EMPTY</code> as {@link Parameter}for an Array that may have no
052     * elements.
053     */
054    public static final ComponentParameter ARRAY_ALLOW_EMPTY = new ComponentParameter(true);
055
056    private final Parameter collectionParameter;
057
058    /**
059     * Expect a parameter matching a component of a specific key.
060     * 
061     * @param componentKey the key of the desired addComponent
062     */
063    public ComponentParameter(Object componentKey) {
064        this(componentKey, null);
065    }
066
067    /**
068     * Expect any scalar parameter of the appropriate type or an {@link java.lang.reflect.Array}.
069     */
070    public ComponentParameter() {
071        this(false);
072    }
073
074    /**
075     * Expect any scalar parameter of the appropriate type or an {@link java.lang.reflect.Array}.
076     * Resolve the parameter even if no compnoent is of the array's component type.
077     * 
078     * @param emptyCollection <code>true</code> allows an Array to be empty
079     */
080    public ComponentParameter(boolean emptyCollection) {
081        this(null, emptyCollection ? CollectionComponentParameter.ARRAY_ALLOW_EMPTY : CollectionComponentParameter.ARRAY);
082    }
083
084    /**
085     * Expect any scalar parameter of the appropriate type or the collecting type
086     * {@link java.lang.reflect.Array},{@link java.util.Collection}or {@link java.util.Map}.
087     * The components in the collection will be of the specified type.
088     * 
089     * @param componentValueType the component's type (ignored for an Array)
090     * @param emptyCollection <code>true</code> allows the collection to be empty
091     */
092    public ComponentParameter(Class componentValueType, boolean emptyCollection) {
093        this(null, new CollectionComponentParameter(componentValueType, emptyCollection));
094    }
095
096    /**
097     * Expect any scalar parameter of the appropriate type or the collecting type
098     * {@link java.lang.reflect.Array},{@link java.util.Collection}or {@link java.util.Map}.
099     * The components in the collection will be of the specified type and their adapter's key
100     * must have a particular type.
101     * 
102     * @param componentKeyType the component adapter's key type
103     * @param componentValueType the component's type (ignored for an Array)
104     * @param emptyCollection <code>true</code> allows the collection to be empty
105     */
106    public ComponentParameter(Class componentKeyType, Class componentValueType, boolean emptyCollection) {
107        this(null, new CollectionComponentParameter(componentKeyType, componentValueType, emptyCollection));
108    }
109
110    private ComponentParameter(Object componentKey, Parameter collectionParameter) {
111        super(componentKey);
112        this.collectionParameter = collectionParameter;
113    }
114
115    public Resolver resolve(final PicoContainer container, final ComponentAdapter<?> forAdapter,
116                            final ComponentAdapter<?> injecteeAdapter, final Type expectedType, final NameBinding expectedNameBinding,
117                            final boolean useNames, final Annotation binding) {
118
119        return new Resolver() {
120            final Resolver resolver = ComponentParameter.super.resolve(container, forAdapter, injecteeAdapter, expectedType, expectedNameBinding, useNames, binding);
121            public boolean isResolved() {
122                boolean superResolved = resolver.isResolved();
123                if (!superResolved) {
124                    if (collectionParameter != null) {
125                        return collectionParameter.resolve(container, forAdapter, null, expectedType, expectedNameBinding,
126                                                                useNames, binding).isResolved();
127                    }
128                    return false;
129                }
130                return superResolved;
131            }
132
133            public Object resolveInstance() {
134                Object result = null;
135                if (expectedType instanceof Class) {
136                    result = ComponentParameter.super.resolve(container, forAdapter, injecteeAdapter, expectedType, expectedNameBinding, useNames, binding).resolveInstance();
137                } else if (expectedType instanceof ParameterizedType) {
138                    result = ComponentParameter.super.resolve(container, forAdapter, injecteeAdapter, ((ParameterizedType) expectedType).getRawType(), expectedNameBinding, useNames, binding).resolveInstance();
139                }
140                if (result == null && collectionParameter != null) {
141                    result = collectionParameter.resolve(container, forAdapter, injecteeAdapter, expectedType, expectedNameBinding,
142                                                                 useNames, binding).resolveInstance();
143                }
144                return result;
145            }
146
147            public ComponentAdapter<?> getComponentAdapter() {
148                return resolver.getComponentAdapter();
149            }
150        };
151    }
152
153    public void verify(PicoContainer container,
154                       ComponentAdapter<?> adapter,
155                       Type expectedType,
156                       NameBinding expectedNameBinding,
157                       boolean useNames, Annotation binding) {
158        try {
159            super.verify(container, adapter, expectedType, expectedNameBinding, useNames, binding);
160        } catch (AbstractInjector.UnsatisfiableDependenciesException e) {
161            if (collectionParameter != null) {
162                collectionParameter.verify(container, adapter, expectedType, expectedNameBinding, useNames, binding);
163                return;
164            }
165            throw e;
166        }
167    }
168
169    /**
170     * Accept the visitor for the current {@link Parameter}. If internally a
171     * {@link CollectionComponentParameter}is used, it is visited also.
172     * 
173     * @see BasicComponentParameter#accept(org.picocontainer.PicoVisitor)
174     */
175    public void accept(PicoVisitor visitor) {
176        super.accept(visitor);
177        if (collectionParameter != null) {
178            collectionParameter.accept(visitor);
179        }
180    }
181}