001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.discovery.tools;
018
019import java.lang.reflect.InvocationTargetException;
020
021import org.apache.commons.discovery.DiscoveryException;
022
023/**
024 * Represents a Service Programming Interface (spi).
025 * - SPI's name
026 * - SPI's (provider) class
027 * - SPI's (alternate) override property name
028 *
029 * In addition, while there are many cases where this is NOT
030 * usefull, for those in which it is:
031 *
032 * - expected constructor argument types and parameters values.
033 *
034 * @param <T> The SPI type
035 */
036public class SPInterface<T> {
037
038    /**
039     * Construct object representing Class {@code provider}.
040     *
041     * @param <T> The SPI type
042     * @param provider The SPI class
043     * @return A new object representing Class {@code provider}
044     * @since 0.5
045     */
046    public static <T> SPInterface<T> newSPInterface(Class<T> provider) {
047        return newSPInterface(provider, provider.getName());
048    }
049
050    /**
051     * Construct object representing Class {@code provider}.
052     *
053     * @param <T> The SPI type
054     * @param provider The SPI class
055     * @param propertyName when looking for the name of a class implementing
056     *        the provider class, a discovery strategy may involve looking for
057     *        (system or other) properties having either the name of the class
058     *        (provider) or the <code>propertyName</code>.
059     * @return A new object representing Class {@code provider}
060     * @since 0.5
061     */
062    public static <T> SPInterface<T> newSPInterface(Class<T> provider, String propertyName) {
063        return new SPInterface<T>(provider, propertyName);
064    }
065
066    /**
067     * Construct object representing Class {@code provider}.
068     *
069     * @param <T> The SPI type
070     * @param provider The SPI class
071     * @param constructorParamClasses classes representing the
072     *        constructor argument types
073     * @param constructorParams objects representing the
074     *        constructor arguments
075     * @return A new object representing Class {@code provider}
076     * @since 0.5
077     */
078    public static <T> SPInterface<T> newSPInterface(Class<T> provider,
079            Class<?> constructorParamClasses[],
080            Object constructorParams[]) {
081        return newSPInterface(provider, provider.getName(), constructorParamClasses, constructorParams);
082    }
083
084    /**
085     * Construct object representing Class {@code provider}.
086     *
087     * @param <T> The SPI type
088     * @param provider The SPI class
089     * @param propertyName when looking for the name of a class implementing
090     *        the provider class, a discovery strategy may involve looking for
091     *        (system or other) properties having either the name of the class
092     *        (provider) or the <code>propertyName</code>.
093     * @param constructorParamClasses classes representing the
094     *        constructor argument types
095     * @param constructorParams objects representing the
096     *        constructor arguments
097     * @return A new object representing Class {@code provider}
098     * @since 0.5
099     */
100    public static <T> SPInterface<T> newSPInterface(Class<T> provider,
101            String propertyName,
102            Class<?> constructorParamClasses[],
103            Object constructorParams[]) {
104        return new SPInterface<T>(provider, propertyName, constructorParamClasses, constructorParams);
105    }
106
107    /**
108     * The service programming interface: intended to be
109     * an interface or abstract class, but not limited
110     * to those two.
111     */
112    private final Class<T> spi;
113
114    /**
115     * The property name to be used for finding the name of
116     * the SPI implementation class.
117     */
118    private final String propertyName;
119
120    private final Class<?>  paramClasses[];
121
122    private final Object params[];
123
124    /**
125     * Construct object representing Class <code>provider</code>.
126     *
127     * @param provider The SPI class
128     */
129    public SPInterface(Class<T> provider) {
130        this(provider, provider.getName());
131    }
132
133    /**
134     * Construct object representing Class <code>provider</code>.
135     *
136     * @param spi The SPI class
137     *
138     * @param propertyName when looking for the name of a class implementing
139     *        the provider class, a discovery strategy may involve looking for
140     *        (system or other) properties having either the name of the class
141     *        (provider) or the <code>propertyName</code>.
142     */
143    public SPInterface(Class<T> spi, String propertyName) {
144        this.spi = spi;
145        this.propertyName = propertyName;
146        this.paramClasses = null;
147        this.params = null;
148    }
149
150    /**
151     * Construct object representing Class <code>provider</code>.
152     *
153     * @param provider The SPI class
154     *
155     * @param constructorParamClasses classes representing the
156     *        constructor argument types.
157     *
158     * @param constructorParams objects representing the
159     *        constructor arguments.
160     */
161    public SPInterface(Class<T> provider,
162                       Class<?> constructorParamClasses[],
163                       Object constructorParams[]) {
164        this(provider,
165             provider.getName(),
166             constructorParamClasses,
167             constructorParams);
168    }
169
170    /**
171     * Construct object representing Class <code>provider</code>.
172     *
173     * @param spi The SPI class
174     *
175     * @param propertyName when looking for the name of a class implementing
176     *        the provider class, a discovery strategy may involve looking for
177     *        (system or other) properties having either the name of the class
178     *        (provider) or the <code>propertyName</code>.
179     *
180     * @param constructorParamClasses classes representing the
181     *        constructor argument types.
182     *
183     * @param constructorParams objects representing the
184     *        constructor arguments.
185     */
186    public SPInterface(Class<T> spi,
187                       String propertyName,
188                       Class<?> constructorParamClasses[],
189                       Object constructorParams[]) {
190        this.spi = spi;
191        this.propertyName = propertyName;
192        this.paramClasses = constructorParamClasses;
193        this.params = constructorParams;
194    }
195
196    /**
197     * Returns the SPI class name.
198     *
199     * @return The SPI class name
200     */
201    public String getSPName() {
202        return spi.getName();
203    }
204
205    /**
206     * Returns the SPI class.
207     *
208     * @return The SPI class
209     */
210    public Class<T> getSPClass() {
211        return spi;
212    }
213
214    /**
215     * Returns the property name to be used for finding
216     * the name of the SPI implementation class.
217     *
218     * @return The property name to be used for finding
219     *         the name of the SPI implementation class
220     */
221    public String getPropertyName() {
222        return propertyName;
223    }
224
225    /**
226     * Creates a new instance of the given SPI class.
227     *
228     * @param <S> Any type extends T
229     * @param impl The SPI class has to be instantiated
230     * @return A new instance of the given SPI class
231     * @throws DiscoveryException if the class implementing
232     *            the SPI cannot be found, cannot be loaded and
233     *            instantiated, or if the resulting class does not implement
234     *            (or extend) the SPI
235     * @throws InstantiationException see {@link Class#newInstance()}
236     * @throws IllegalAccessException see {@link Class#newInstance()}
237     * @throws NoSuchMethodException see {@link Class#newInstance()}
238     * @throws InvocationTargetException see {@link Class#newInstance()}
239     */
240    public <S extends T> S newInstance(Class<S> impl)
241        throws DiscoveryException,
242               InstantiationException,
243               IllegalAccessException,
244               NoSuchMethodException,
245               InvocationTargetException {
246        verifyAncestory(impl);
247
248        return ClassUtils.newInstance(impl, paramClasses, params);
249    }
250
251    /**
252     * Verifies the given SPI implementation is a SPI specialization.
253     *
254     * @param <S> Any type extends T
255     * @param impl The SPI instantance
256     */
257    public <S extends T> void verifyAncestory(Class<S> impl) {
258        ClassUtils.verifyAncestory(spi, impl);
259    }
260
261}