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}