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     */
017    package org.apache.commons.discovery.tools;
018    
019    import java.lang.reflect.Constructor;
020    import java.lang.reflect.InvocationTargetException;
021    import java.lang.reflect.Method;
022    import java.lang.reflect.Modifier;
023    
024    import org.apache.commons.discovery.DiscoveryException;
025    import org.apache.commons.logging.Log;
026    import org.apache.commons.logging.LogFactory;
027    
028    /**
029     * Various utilities to interact with {@code Class} types.
030     */
031    public 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    }