001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.common.util;
017
018import java.lang.reflect.AccessibleObject;
019import java.lang.reflect.Field;
020import java.lang.reflect.Modifier;
021import java.security.AccessController;
022import java.security.PrivilegedAction;
023import java.text.CharacterIterator;
024import java.text.StringCharacterIterator;
025import org.modeshape.common.annotation.Immutable;
026
027/**
028 * Static utilities for working with classes.
029 */
030@Immutable
031public final class ClassUtil {
032
033    private static void addObjectString( Object object,
034                                         int includeInheritedFieldDepth,
035                                         Class<?> clazz,
036                                         StringBuilder text ) {
037
038        // Add class's name
039        text.append(nonPackageQualifiedName(clazz));
040
041        text.append('(');
042
043        // Add class's field names and object's corresponding values
044        Field[] flds = clazz.getDeclaredFields();
045        boolean separatorNeeded = false;
046        for (int ndx = 0, len = flds.length; ndx < len; ++ndx) {
047            Field fld = flds[ndx];
048            try {
049
050                // Attempt to ensure fields is accessible. Getting the value will throw an exception if the attempt failed.
051                makeAccessible(fld);
052                Object val = fld.get(object);
053
054                // Skip static fields
055                if ((fld.getModifiers() & Modifier.STATIC) != 0) {
056                    continue;
057                }
058
059                // Skip synthetic fields
060                String name = fld.getName();
061                if (name.indexOf('$') >= 0) {
062                    continue;
063                }
064
065                // Add separator in text between fields
066                separatorNeeded = addSeparator(separatorNeeded, text);
067
068                // Add field's name and value to text
069                text.append(fld.getName());
070                text.append('=');
071                text.append(val);
072
073            } catch (Exception err) {
074            }
075        }
076
077        // Add inheritied fields if requested
078        if (includeInheritedFieldDepth > 0) {
079            separatorNeeded = addSeparator(separatorNeeded, text);
080            addObjectString(object, includeInheritedFieldDepth - 1, clazz.getSuperclass(), text);
081        }
082
083        text.append(')');
084    }
085
086    private static boolean addSeparator( boolean separatorNeeded,
087                                         StringBuilder text ) {
088        if (separatorNeeded) {
089            text.append(", ");
090        }
091        return true;
092    }
093
094    public static void makeAccessible( final AccessibleObject object ) {
095        if (!object.isAccessible()) {
096            if (System.getSecurityManager() == null) {
097                object.setAccessible(true);
098            } else {
099                AccessController.doPrivileged(new PrivilegedAction<Object>() {
100
101                    @Override
102                    public Object run() {
103                        object.setAccessible(true);
104                        return null;
105                    }
106                });
107            }
108        }
109    }
110
111    /**
112     * @param clazz A class.
113     * @return The non-package-qualified name of the specified class. Note, inner class names will still be qualified by their
114     *         enclosing class names and a "$" delimiter.
115     */
116    public static String nonPackageQualifiedName( final Class<?> clazz ) {
117        // if (clazz == null) {
118        // throw new IllegalArgumentException(I18n.format(CommonI18n.mustNotBeNull, "Class"));
119        // }
120        String name = clazz.getName();
121        return name.substring(name.lastIndexOf('.') + 1);
122    }
123
124    /**
125     * @param object An object.
126     * @return The non-package-qualified name of the class of the specified object. Note, inner class names will still be
127     *         qualified by their enclosing class names and a "$" delimiter.
128     */
129    public static String nonPackageQualifiedName( final Object object ) {
130        // if (object == null) {
131        // throw new IllegalArgumentException(I18n.format(CommonI18n.mustNotBeNull, "Object"));
132        // }
133        return nonPackageQualifiedName(object.getClass());
134    }
135
136    /**
137     * @param object
138     * @param includeInheritedFieldDepth
139     * @return A string representation of the specified object, consisting of its class name, properties, and property values.
140     */
141    public static String toString( Object object,
142                                   int includeInheritedFieldDepth ) {
143        StringBuilder text = new StringBuilder();
144        addObjectString(object, includeInheritedFieldDepth, object.getClass(), text);
145        return text.toString();
146    }
147
148    /**
149     * Determine whether the supplied string represents a well-formed fully-qualified Java classname. This utility method enforces
150     * no conventions (e.g., packages are all lowercase) nor checks whether the class is available on the classpath.
151     * 
152     * @param classname
153     * @return true if the string is a fully-qualified class name
154     */
155    public static boolean isFullyQualifiedClassname( String classname ) {
156        if (classname == null) return false;
157        String[] parts = classname.split("[\\.]");
158        if (parts.length == 0) return false;
159        for (String part : parts) {
160            CharacterIterator iter = new StringCharacterIterator(part);
161            // Check first character (there should at least be one character for each part) ...
162            char c = iter.first();
163            if (c == CharacterIterator.DONE) return false;
164            if (!Character.isJavaIdentifierStart(c) && !Character.isIdentifierIgnorable(c)) return false;
165            c = iter.next();
166            // Check the remaining characters, if there are any ...
167            while (c != CharacterIterator.DONE) {
168                if (!Character.isJavaIdentifierPart(c) && !Character.isIdentifierIgnorable(c)) return false;
169                c = iter.next();
170            }
171        }
172        return true;
173    }
174
175    private ClassUtil() {
176    }
177}