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.Constructor;
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022import java.lang.reflect.Modifier;
023
024import org.apache.commons.discovery.DiscoveryException;
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027
028/**
029 * Various utilities to interact with {@code Class} types.
030 */
031public class ClassUtils {
032
033    private static Log log = LogFactory.getLog(ClassUtils.class);
034
035    /**
036     * Sets the {@code Log} for this class.
037     *
038     * @param _log This class {@code Log}
039     * @deprecated This method is not thread-safe
040     */
041    @Deprecated
042    public static void setLog(Log _log) {
043        log = _log;
044    }
045
046    /**
047     * Get package name.
048     * Not all class loaders 'keep' package information,
049     * in which case Class.getPackage() returns null.
050     * This means that calling Class.getPackage().getName()
051     * is unreliable at best.
052     *
053     * @param clazz The class from which the package has to be extracted
054     * @return The string representation of the input class package
055     */
056    public static String getPackageName(Class<?> clazz) {
057        Package clazzPackage = clazz.getPackage();
058        String packageName;
059        if (clazzPackage != null) {
060            packageName = clazzPackage.getName();
061        } else {
062            String clazzName = clazz.getName();
063            packageName = clazzName.substring(0, clazzName.lastIndexOf('.'));
064        }
065        return packageName;
066    }
067
068    /**
069     * Looks for {@code public static returnType methodName(paramTypes)}.
070     *
071     * @param clazz The class where looking for the method
072     * @param returnType The method return type
073     * @param methodName The method name
074     * @param paramTypes The method arguments types
075     * @return Method {@code public static returnType methodName(paramTypes)},
076     *         if found to be <strong>directly</strong> implemented by clazz.
077     */
078    public static Method findPublicStaticMethod(Class<?> clazz,
079                                                Class<?> returnType,
080                                                String methodName,
081                                                Class<?>[] paramTypes) {
082        boolean problem = false;
083        Method method = null;
084
085        // verify '<methodName>(<paramTypes>)' is directly in class.
086        try {
087            method = clazz.getDeclaredMethod(methodName, paramTypes);
088        } catch(NoSuchMethodException e) {
089            problem = true;
090            log.debug("Class " + clazz.getName() + ": missing method '" + methodName + "(...)", e);
091        }
092
093        // verify 'public static <returnType>'
094        if (!problem  &&
095            !(Modifier.isPublic(method.getModifiers()) &&
096              Modifier.isStatic(method.getModifiers()) &&
097              method.getReturnType() == returnType)) {
098            if (log.isDebugEnabled()) {
099                if (!Modifier.isPublic(method.getModifiers())) {
100                    log.debug(methodName + "() is not public");
101                }
102                if (!Modifier.isStatic(method.getModifiers())) {
103                    log.debug(methodName + "() is not static");
104                }
105                if (method.getReturnType() != returnType) {
106                    log.debug("Method returns: "
107                            + method.getReturnType().getName()
108                            + "@@"
109                            + method.getReturnType().getClassLoader());
110                    log.debug("Should return:  "
111                            + returnType.getName()
112                            + "@@"
113                            + returnType.getClassLoader());
114                }
115            }
116            problem = true;
117            method = null;
118        }
119
120        return method;
121    }
122
123    /**
124     * Creates a new instance of the input class using the following policy:
125     *
126     * <ul>
127     * <li>if <code>paramClasses</code> or <code>params</code> is null,
128     * the default constructor will be used;</li>
129     * <li>the public constructor with <code>paramClasses</code> arguments type,
130     * with <code>params</code> as arguments value, will be used.</li>
131     * </ul>
132     *
133     * @param <T> The class type has to be instantiated
134     * @param impl The class has to be instantiated
135     * @param paramClasses The constructor arguments types (can be {@code null})
136     * @param params The constructor arguments values (can be {@code null})
137     * @return A new class instance
138     * @throws DiscoveryException if the class implementing
139     *            the SPI cannot be found, cannot be loaded and
140     *            instantiated, or if the resulting class does not implement
141     *            (or extend) the SPI
142     * @throws InstantiationException see {@link Class#newInstance()}
143     * @throws IllegalAccessException see {@link Class#newInstance()}
144     * @throws NoSuchMethodException see {@link Class#newInstance()}
145     * @throws InvocationTargetException see {@link Class#newInstance()}
146     */
147    public static <T> T newInstance(Class<T> impl, Class<?> paramClasses[], Object params[]) throws DiscoveryException,
148               InstantiationException,
149               IllegalAccessException,
150               NoSuchMethodException,
151               InvocationTargetException {
152        if (paramClasses == null || params == null) {
153            return impl.newInstance();
154        }
155
156        Constructor<T> constructor = impl.getConstructor(paramClasses);
157        return constructor.newInstance(params);
158    }
159
160    /**
161     * Throws exception if {@code impl} does not
162     * implement or extend the SPI.
163     *
164     * @param spi The SPI type
165     * @param impl The class has to be verified is a SPI implementation/extension
166     * @throws DiscoveryException if the input implementation class is not an SPI implementation
167     */
168    public static void verifyAncestory(Class<?> spi, Class<?> impl) throws DiscoveryException {
169        if (spi == null) {
170            throw new DiscoveryException("No interface defined!");
171        }
172
173        if (impl == null) {
174            throw new DiscoveryException("No implementation defined for " + spi.getName());
175        }
176
177        if (!spi.isAssignableFrom(impl)) {
178            throw new DiscoveryException("Class "
179                    + impl.getName()
180                    + " does not implement "
181                    + spi.getName());
182        }
183    }
184
185}