001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.common.util;
025    
026    import java.lang.reflect.AccessibleObject;
027    import java.lang.reflect.Field;
028    import java.lang.reflect.Modifier;
029    import java.security.AccessController;
030    import java.security.PrivilegedAction;
031    import java.text.CharacterIterator;
032    import java.text.StringCharacterIterator;
033    
034    /**
035     * Static utilities for working with classes.
036     * 
037     * @author John Verhaeg
038     */
039    public final class ClassUtil {
040    
041        private static void addObjectString( Object object,
042                                             int includeInheritedFieldDepth,
043                                             Class<?> clazz,
044                                             StringBuffer text ) {
045    
046            // Add class's name
047            text.append(nonPackageQualifiedName(clazz));
048    
049            text.append('(');
050    
051            // Add class's field names and object's corresponding values
052            Field[] flds = clazz.getDeclaredFields();
053            boolean separatorNeeded = false;
054            for (int ndx = 0, len = flds.length; ndx < len; ++ndx) {
055                Field fld = flds[ndx];
056                try {
057    
058                    // Attempt to ensure fields is accessible. Getting the value will throw an exception if the attempt failed.
059                    makeAccessible(fld);
060                    Object val = fld.get(object);
061    
062                    // Skip static fields
063                    if ((fld.getModifiers() & Modifier.STATIC) != 0) {
064                        continue;
065                    }
066    
067                    // Skip synthetic fields
068                    String name = fld.getName();
069                    if (name.indexOf('$') >= 0) {
070                        continue;
071                    }
072    
073                    // Add separator in text between fields
074                    separatorNeeded = addSeparator(separatorNeeded, text);
075    
076                    // Add field's name and value to text
077                    text.append(fld.getName());
078                    text.append('=');
079                    text.append(val);
080    
081                } catch (Exception err) {
082                }
083            }
084    
085            // Add inheritied fields if requested
086            if (includeInheritedFieldDepth > 0) {
087                separatorNeeded = addSeparator(separatorNeeded, text);
088                addObjectString(object, includeInheritedFieldDepth - 1, clazz.getSuperclass(), text);
089            }
090    
091            text.append(')');
092        }
093    
094        private static boolean addSeparator( boolean separatorNeeded,
095                                             StringBuffer text ) {
096            if (separatorNeeded) {
097                text.append(", "); //$NON-NLS-1$
098            }
099            return true;
100        }
101    
102        /**
103         * @param object
104         */
105        public static void makeAccessible( final AccessibleObject object ) {
106            if (!object.isAccessible()) {
107                if (System.getSecurityManager() == null) {
108                    object.setAccessible(true);
109                } else {
110                    AccessController.doPrivileged(new PrivilegedAction<Object>() {
111    
112                        public Object run() {
113                            object.setAccessible(true);
114                            return null;
115                        }
116                    });
117                }
118            }
119        }
120    
121        /**
122         * @param clazz A class.
123         * @return The non-package-qualified name of the specified class. Note, inner class names will still be qualified by their
124         *         enclosing class names and a "$" delimiter.
125         */
126        public static String nonPackageQualifiedName( final Class<?> clazz ) {
127            // if (clazz == null) {
128            // throw new IllegalArgumentException(I18n.format(CommonI18n.mustNotBeNull, "Class")); //$NON-NLS-1$
129            // }
130            String name = clazz.getName();
131            return name.substring(name.lastIndexOf('.') + 1);
132        }
133    
134        /**
135         * @param object An object.
136         * @return The non-package-qualified name of the class of the specified object. Note, inner class names will still be
137         *         qualified by their enclosing class names and a "$" delimiter.
138         */
139        public static String nonPackageQualifiedName( final Object object ) {
140            // if (object == null) {
141            // throw new IllegalArgumentException(I18n.format(CommonI18n.mustNotBeNull, "Object")); //$NON-NLS-1$
142            // }
143            return nonPackageQualifiedName(object.getClass());
144        }
145    
146        /**
147         * @param object
148         * @param includeInheritedFieldDepth
149         * @return A string representation of the specified object, consisting of its class name, properties, and property values.
150         */
151        public static String toString( Object object,
152                                       int includeInheritedFieldDepth ) {
153            StringBuffer text = new StringBuffer();
154            addObjectString(object, includeInheritedFieldDepth, object.getClass(), text);
155            return text.toString();
156        }
157    
158        /**
159         * Determine whether the supplied string represents a well-formed fully-qualified Java classname. This utility method enforces
160         * no conventions (e.g., packages are all lowercase) nor checks whether the class is available on the classpath.
161         * 
162         * @param classname
163         * @return true if the string is a fully-qualified class name
164         */
165        public static boolean isFullyQualifiedClassname( String classname ) {
166            if (classname == null) return false;
167            String[] parts = classname.split("[\\.]");
168            if (parts.length == 0) return false;
169            for (String part : parts) {
170                CharacterIterator iter = new StringCharacterIterator(part);
171                // Check first character (there should at least be one character for each part) ...
172                char c = iter.first();
173                if (c == CharacterIterator.DONE) return false;
174                if (!Character.isJavaIdentifierStart(c) && !Character.isIdentifierIgnorable(c)) return false;
175                c = iter.next();
176                // Check the remaining characters, if there are any ...
177                while (c != CharacterIterator.DONE) {
178                    if (!Character.isJavaIdentifierPart(c) && !Character.isIdentifierIgnorable(c)) return false;
179                    c = iter.next();
180                }
181            }
182            return true;
183        }
184    
185        private ClassUtil() {
186        }
187    }