/*==========================================================================*\ | $Id: KVCAttributeFinder.java,v 1.2 2012/03/28 13:48:08 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2012 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.core; import com.webobjects.eocontrol.EOGenericRecord; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSComparator; import com.webobjects.foundation.NSKeyValueCoding; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; import er.extensions.eof.ERXGenericRecord; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.webcat.core.KVCAttributeInfo; //------------------------------------------------------------------------- /** * Provides a static utility method (and an internal cache) for looking * up the KVC-accessible attributes for a class. * * @author Tony Allevato * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.2 $, $Date: 2012/03/28 13:48:08 $ */ public class KVCAttributeFinder { //~ Constructor ........................................................... // ---------------------------------------------------------- /** * This class provides only static utility methods, and so no instance * should ever be created. Should it be a singleton instead? */ private KVCAttributeFinder() { // Nothing to do } //~ Public Methods ........................................................ // ---------------------------------------------------------- public static NSArray<KVCAttributeInfo> attributesForClass( Class<?> klass, String prefix) { NSArray<KVCAttributeInfo> unfiltered; if (cache.containsKey(klass)) { unfiltered = cache.objectForKey(klass); } else { NSMutableArray<KVCAttributeInfo> attrs = new NSMutableArray<KVCAttributeInfo>(); if (NSKeyValueCoding.class.isAssignableFrom(klass)) { addMethods(klass, klass.getMethods(), attrs); addFields(klass.getFields(), attrs); } try { attrs.sortUsingComparator( NSComparator.AscendingCaseInsensitiveStringComparator); } catch (Exception e) { // ??? } cache.setObjectForKey(attrs, klass); unfiltered = attrs; } NSMutableArray<KVCAttributeInfo> filtered = new NSMutableArray<KVCAttributeInfo>(); for (KVCAttributeInfo attr : unfiltered) { String key = attr.name(); if (key.toLowerCase().startsWith(prefix.toLowerCase())) { filtered.addObject(attr); } } return filtered; } //~ Private Methods ....................................................... // ---------------------------------------------------------- private static boolean isMethodFromSuperclass(Class<?> klass, Method method) { if ("toString".equals(method.getName())) { // Special dispensation for the toString() method, since it returns // a useful string representation of most objects that some report // designers might find useful. return false; } Class<?> methodClass = method.getDeclaringClass(); boolean isInGenericRecord = methodClass.isAssignableFrom(ERXGenericRecord.class) || methodClass.isAssignableFrom(EOGenericRecord.class); if (!isInGenericRecord) { // The method might have been overridden, causing its declaring // class to be the class itself. Check this by explicitly trying // to find the method in a superclass. try { ERXGenericRecord.class.getMethod( method.getName(), method.getParameterTypes()); isInGenericRecord = true; } catch (SecurityException e) { // ignore it } catch (NoSuchMethodException e) { // ignore it } try { EOGenericRecord.class.getMethod( method.getName(), method.getParameterTypes()); isInGenericRecord = true; } catch (SecurityException e) { // ignore it } catch (NoSuchMethodException e) { // ignore it } } return isInGenericRecord; } // ---------------------------------------------------------- private static boolean methodHasSpecialName(Method method) { boolean isSpecial = false; String methodName = method.getName(); if (methodName.startsWith("create") && methodName.endsWith("Relationship")) { isSpecial = true; } else if (methodName.equals("changedProperties")) { isSpecial = true; } else if (methodName.startsWith("updateMutableFields")) { isSpecial = true; } return isSpecial; } // ---------------------------------------------------------- private static void addMethods(Class<?> klass, Method[] methods, NSMutableArray<KVCAttributeInfo> attrs) { for (Method method : methods) { String name = method.getName(); int modifiers = method.getModifiers(); boolean isFromSuperclass = isMethodFromSuperclass(klass, method); boolean isSpecialName = methodHasSpecialName(method); if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & Modifier.STATIC) == 0 && method.getParameterTypes().length == 0 && !isFromSuperclass && !isSpecialName && isTypeAcceptable(method.getReturnType())) { if (name.startsWith("_get")) { name = initialLower(name.substring("_get".length())); } else if (name.startsWith("get")) { name = initialLower(name.substring("get".length())); } else if (name.startsWith("_is")) { name = initialLower(name.substring("_is".length())); } else if (name.startsWith("is")) { name = initialLower(name.substring("is".length())); } else if (name.startsWith("_")) { name = name.substring("_".length()); } if (!attrs.containsObject(name)) { KVCAttributeInfo attr = new KVCAttributeInfo( name, method.getReturnType().getSimpleName(), method); attrs.addObject(attr); } } } } // ---------------------------------------------------------- private static void addFields( Field[] fields, NSMutableArray<KVCAttributeInfo> attrs) { for (Field field : fields) { String name = field.getName(); int modifiers = field.getModifiers(); if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & Modifier.STATIC) == 0 && isTypeAcceptable(field.getType())) { if (name.startsWith("_is")) { name = initialLower(name.substring("_is".length())); } else if (name.startsWith("is")) { name = initialLower(name.substring("is".length())); } else if (name.startsWith("_")) { name = name.substring("_".length()); } if (!attrs.containsObject(name)) { KVCAttributeInfo attr = new KVCAttributeInfo( name, field.getType().getSimpleName(), field); attrs.addObject(attr); } } } } // ---------------------------------------------------------- private static boolean isTypeAcceptable(Class<?> klass) { return klass != Void.class && klass != Void.TYPE; } // ---------------------------------------------------------- private static String initialLower(String str) { if (str != null && str.length() > 0) { return "" + Character.toLowerCase(str.charAt(0)) + str.substring(1); } else { return str; } } //~ Instance/static variables ............................................. private static final NSMutableDictionary<Class<?>, NSArray<KVCAttributeInfo>> cache = new NSMutableDictionary<Class<?>, NSArray<KVCAttributeInfo>>(); }