001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    
021    package org.apache.directory.shared.ldap.util;
022    
023    
024    import java.beans.Introspector;
025    import java.lang.reflect.Array;
026    import java.lang.reflect.Method;
027    import java.lang.reflect.Modifier;
028    import java.util.HashSet;
029    import java.util.Set;
030    
031    
032    /**
033     * Miscellaneous class utility methods. Mainly for internal use within the
034     * framework; consider Jakarta's Commons Lang for a more comprehensive suite
035     * of utilities.
036     *
037     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
038     */
039    public abstract class SpringClassUtils
040    {
041    
042        /** Suffix for array class names */
043        public static final String ARRAY_SUFFIX = "[]";
044    
045        /** All primitive classes */
046        private static Class[] PRIMITIVE_CLASSES =
047            { boolean.class, byte.class, char.class, short.class, int.class, long.class, float.class, double.class };
048    
049        /** The package separator character '.' */
050        private static final char PACKAGE_SEPARATOR_CHAR = '.';
051    
052        /** The inner class separator character '$' */
053        private static final char INNER_CLASS_SEPARATOR_CHAR = '$';
054    
055        /** The CGLIB class separator character "$$" */
056        private static final String CGLIB_CLASS_SEPARATOR_CHAR = "$$";
057    
058    
059        /**
060         * Return a default ClassLoader to use (never <code>null</code>).
061         * Returns the thread context ClassLoader, if available.
062         * The ClassLoader that loaded the ClassUtils class will be used as fallback.
063         * <p>Call this method if you intend to use the thread context ClassLoader
064         * in a scenario where you absolutely need a non-null ClassLoader reference:
065         * for example, for class path resource loading (but not necessarily for
066         * <code>Class.forName</code>, which accepts a <code>null</code> ClassLoader
067         * reference as well).
068         * @see java.lang.Thread#getContextClassLoader()
069         */
070        public static ClassLoader getDefaultClassLoader()
071        {
072            ClassLoader cl = Thread.currentThread().getContextClassLoader();
073            if ( cl == null )
074            {
075                // No thread context class loader -> use class loader of this class.
076                cl = SpringClassUtils.class.getClassLoader();
077            }
078            return cl;
079        }
080    
081    
082        /**
083         * Replacement for <code>Class.forName()</code> that also returns Class instances
084         * for primitives (like "int") and array class names (like "String[]").
085         * <p>Always uses the thread context class loader.
086         * @param name the name of the Class
087         * @return Class instance for the supplied name
088         * @see java.lang.Class#forName(String, boolean, ClassLoader)
089         * @see java.lang.Thread#getContextClassLoader()
090         */
091        public static Class forName( String name ) throws ClassNotFoundException
092        {
093            return forName( name, Thread.currentThread().getContextClassLoader() );
094        }
095    
096    
097        /**
098         * Replacement for <code>Class.forName()</code> that also returns Class instances
099         * for primitives (like "int") and array class names (like "String[]").
100         * @param name the name of the Class
101         * @param classLoader the class loader to use
102         * @return Class instance for the supplied name
103         * @see java.lang.Class#forName(String, boolean, ClassLoader)
104         * @see java.lang.Thread#getContextClassLoader()
105         */
106        public static Class forName( String name, ClassLoader classLoader ) throws ClassNotFoundException
107        {
108            Class clazz = resolvePrimitiveClassName( name );
109            if ( clazz != null )
110            {
111                return clazz;
112            }
113            if ( name.endsWith( ARRAY_SUFFIX ) )
114            {
115                // special handling for array class names
116                String elementClassName = name.substring( 0, name.length() - ARRAY_SUFFIX.length() );
117                Class elementClass = SpringClassUtils.forName( elementClassName, classLoader );
118                return Array.newInstance( elementClass, 0 ).getClass();
119            }
120            return Class.forName( name, true, classLoader );
121        }
122    
123    
124        /**
125         * Resolve the given class name as primitive class, if appropriate.
126         * @param name the name of the potentially primitive class
127         * @return the primitive class, or <code>null</code> if the name does not denote
128         * a primitive class
129         */
130        public static Class resolvePrimitiveClassName( String name )
131        {
132            // Most class names will be quite long, considering that they
133            // SHOULD sit in a package, so a length check is worthwhile.
134            if ( name.length() <= 8 )
135            {
136                // could be a primitive - likely
137                for ( int i = 0; i < PRIMITIVE_CLASSES.length; i++ )
138                {
139                    Class clazz = PRIMITIVE_CLASSES[i];
140                    if ( clazz.getName().equals( name ) )
141                    {
142                        return clazz;
143                    }
144                }
145            }
146            return null;
147        }
148    
149    
150        /**
151         * Return the short string name of a Java class in decapitalized
152         * JavaBeans property format.
153         * @param clazz the class
154         * @return the short name rendered in a standard JavaBeans property format
155         * @see java.beans.Introspector#decapitalize(String)
156         */
157        public static String getShortNameAsProperty( Class clazz )
158        {
159            return Introspector.decapitalize( getShortName( clazz ) );
160        }
161    
162    
163        /**
164         * Get the class name without the qualified package name.
165         * @param clazz the class to get the short name for
166         * @return the class name of the class without the package name
167         * @throws IllegalArgumentException if the class is null
168         */
169        public static String getShortName( Class clazz )
170        {
171            return getShortName( clazz.getName() );
172        }
173    
174    
175        /**
176         * Get the class name without the qualified package name.
177         * @param className the className to get the short name for
178         * @return the class name of the class without the package name
179         * @throws IllegalArgumentException if the className is empty
180         */
181        public static String getShortName( String className )
182        {
183            //Assert.hasLength(className, "class name must not be empty");
184            int lastDotIndex = className.lastIndexOf( PACKAGE_SEPARATOR_CHAR );
185            int nameEndIndex = className.indexOf( CGLIB_CLASS_SEPARATOR_CHAR );
186            if ( nameEndIndex == -1 )
187            {
188                nameEndIndex = className.length();
189            }
190            String shortName = className.substring( lastDotIndex + 1, nameEndIndex );
191            shortName = shortName.replace( INNER_CLASS_SEPARATOR_CHAR, PACKAGE_SEPARATOR_CHAR );
192            return shortName;
193        }
194    
195    
196        /**
197         * Return the qualified name of the given method, consisting of
198         * fully qualified interface/class name + "." + method name.
199         * @param method the method
200         * @return the qualified name of the method
201         */
202        public static String getQualifiedMethodName( Method method )
203        {
204            //Assert.notNull(method, "Method must not be empty");
205            return method.getDeclaringClass().getName() + "." + method.getName();
206        }
207    
208    
209        /**
210         * Determine whether the given class has a method with the given signature.
211         * Essentially translates <code>NoSuchMethodException</code> to "false".
212         * @param clazz the clazz to analyze
213         * @param methodName the name of the method
214         * @param paramTypes the parameter types of the method
215         */
216        public static boolean hasMethod( Class clazz, String methodName, Class[] paramTypes )
217        {
218            try
219            {
220                clazz.getMethod( methodName, paramTypes );
221                return true;
222            }
223            catch ( NoSuchMethodException ex )
224            {
225                return false;
226            }
227        }
228    
229    
230        /**
231         * Return the number of methods with a given name (with any argument types),
232         * for the given class and/or its superclasses. Includes non-public methods.
233         * @param clazz the clazz to check
234         * @param methodName the name of the method
235         * @return the number of methods with the given name
236         */
237        public static int getMethodCountForName( Class clazz, String methodName )
238        {
239            int count = 0;
240            do
241            {
242                for ( int i = 0; i < clazz.getDeclaredMethods().length; i++ )
243                {
244                    Method method = clazz.getDeclaredMethods()[i];
245                    if ( methodName.equals( method.getName() ) )
246                    {
247                        count++;
248                    }
249                }
250                clazz = clazz.getSuperclass();
251            }
252            while ( clazz != null );
253            return count;
254        }
255    
256    
257        /**
258         * Does the given class and/or its superclasses at least have one or more
259         * methods (with any argument types)? Includes non-public methods.
260         * @param clazz the clazz to check
261         * @param methodName the name of the method
262         * @return whether there is at least one method with the given name
263         */
264        public static boolean hasAtLeastOneMethodWithName( Class clazz, String methodName )
265        {
266            do
267            {
268                for ( int i = 0; i < clazz.getDeclaredMethods().length; i++ )
269                {
270                    Method method = clazz.getDeclaredMethods()[i];
271                    
272                    if ( methodName.equals( method.getName() ) )
273                    {
274                        return true;
275                    }
276                }
277                clazz = clazz.getSuperclass();
278            }
279            while ( clazz != null );
280            
281            return false;
282        }
283    
284    
285        /**
286         * Return a static method of a class.
287         * @param methodName the static method name
288         * @param clazz the class which defines the method
289         * @param args the parameter types to the method
290         * @return the static method, or <code>null</code> if no static method was found
291         * @throws IllegalArgumentException if the method name is blank or the clazz is null
292         */
293        public static Method getStaticMethod( Class clazz, String methodName, Class[] args )
294        {
295            try
296            {
297                Method method = clazz.getDeclaredMethod( methodName, args );
298                
299                if ( ( method.getModifiers() & Modifier.STATIC ) != 0 )
300                {
301                    return method;
302                }
303            }
304            catch ( NoSuchMethodException ex )
305            {
306            }
307            
308            return null;
309        }
310    
311    
312        /**
313         * Return a path suitable for use with ClassLoader.getResource (also
314         * suitable for use with Class.getResource by prepending a slash ('/') to
315         * the return value. Built by taking the package of the specified class
316         * file, converting all dots ('.') to slashes ('/'), adding a trailing slash
317         * if necesssary, and concatenating the specified resource name to this.
318         * <br/>As such, this function may be used to build a path suitable for
319         * loading a resource file that is in the same package as a class file,
320         * although {link org.springframework.core.io.ClassPathResource} is usually
321         * even more convenient.
322         * @param clazz the Class whose package will be used as the base
323         * @param resourceName the resource name to append. A leading slash is optional.
324         * @return the built-up resource path
325         * @see java.lang.ClassLoader#getResource
326         * @see java.lang.Class#getResource
327         */
328        public static String addResourcePathToPackagePath( Class clazz, String resourceName )
329        {
330            if ( !resourceName.startsWith( "/" ) )
331            {
332                return classPackageAsResourcePath( clazz ) + "/" + resourceName;
333            }
334    
335            return classPackageAsResourcePath( clazz ) + resourceName;
336        }
337    
338    
339        /**
340         * Given an input class object, return a string which consists of the
341         * class's package name as a pathname, i.e., all dots ('.') are replaced by
342         * slashes ('/'). Neither a leading nor trailing slash is added. The result
343         * could be concatenated with a slash and the name of a resource, and fed
344         * directly to ClassLoader.getResource(). For it to be fed to Class.getResource,
345         * a leading slash would also have to be prepended to the return value.
346         * @param clazz the input class. A null value or the default (empty) package
347         * will result in an empty string ("") being returned.
348         * @return a path which represents the package name
349         * @see java.lang.ClassLoader#getResource
350         * @see java.lang.Class#getResource
351         */
352        public static String classPackageAsResourcePath( Class clazz )
353        {
354            if ( clazz == null || clazz.getPackage() == null )
355            {
356                return "";
357            }
358            
359            return clazz.getPackage().getName().replace( '.', '/' );
360        }
361    
362    
363        /**
364         * Return all interfaces that the given object implements as array,
365         * including ones implemented by superclasses.
366         * @param object the object to analyse for interfaces
367         * @return all interfaces that the given object implements as array
368         */
369        public static Class[] getAllInterfaces( Object object )
370        {
371            Set<Class> interfaces = getAllInterfacesAsSet( object );
372            return interfaces.toArray( new Class[interfaces.size()] );
373        }
374    
375    
376        /**
377         * Return all interfaces that the given class implements as array,
378         * including ones implemented by superclasses.
379         * @param clazz the class to analyse for interfaces
380         * @return all interfaces that the given object implements as array
381         */
382        public static Class[] getAllInterfacesForClass( Class clazz )
383        {
384            Set<Class> interfaces = getAllInterfacesForClassAsSet( clazz );
385            return interfaces.toArray( new Class[interfaces.size()] );
386        }
387    
388    
389        /**
390         * Return all interfaces that the given object implements as List,
391         * including ones implemented by superclasses.
392         * @param object the object to analyse for interfaces
393         * @return all interfaces that the given object implements as List
394         */
395        public static Set<Class> getAllInterfacesAsSet( Object object )
396        {
397            return getAllInterfacesForClassAsSet( object.getClass() );
398        }
399    
400    
401        /**
402         * Return all interfaces that the given class implements as Set,
403         * including ones implemented by superclasses.
404         * @param clazz the class to analyse for interfaces
405         * @return all interfaces that the given object implements as Set
406         */
407        public static Set<Class> getAllInterfacesForClassAsSet( Class clazz )
408        {
409            Set<Class> interfaces = new HashSet<Class>();
410    
411            while ( clazz != null )
412            {
413                for ( int i = 0; i < clazz.getInterfaces().length; i++ )
414                {
415                    Class ifc = clazz.getInterfaces()[i];
416                    interfaces.add( ifc );
417                }
418    
419                clazz = clazz.getSuperclass();
420            }
421    
422            return interfaces;
423        }
424    
425    }