package er.extensions.foundation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import org.apache.commons.lang3.ObjectUtils; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSForwardException; import com.webobjects.foundation.NSKeyValueCoding; import com.webobjects.foundation.NSKeyValueCodingAdditions; import com.webobjects.foundation.NSMutableArray; import er.extensions.eof.ERXConstant; /** * Utilities for use with key value coding. You could instantiate one of these in your app-startup: * <pre><code> * ERXKeyValueCodingUtilities.registerClass(SomeClass.class); * NSKeyValueCodingAdditions statics = ERXKeyValueCodingUtilities.Statics; * myValue = statics.valueForKeyPath("SomeClass.SOME_FIELD"); * </code></pre> * Also has utilities for getting and private fields and methods on an object. * @author ak */ public class ERXKeyValueCodingUtilities { private static Map<String, Class> _classes = new Hashtable<>(); /** * Registers the class in the KVC resolving system, so you can use * <code>valueForKeyPath("MyClass.SOME_KEY")</code>. Inner classes * are registered with a "$", i.e. <code>MyClass$InnerClass</code> * * @param clazz */ public static void registerClass(Class clazz) { _classes.put(ERXStringUtilities.lastPropertyKeyInKeyPath(clazz.getName()), clazz); } /** * Extends key-value coding to a class. Java arrays and collections are * morphed into NSArrays. The implementation is pretty slow, but I didn't * exactly want to re-implement all of NSKeyValueCoding here. * * @param clazz * @param key * @return value object */ public static Object classValueForKey(Class clazz, String key) { Object result = null; if(key != null) { try { String getKey = "get" + ERXStringUtilities.capitalize(key); Method methods[] = clazz.getDeclaredMethods(); boolean found = false; for (int i = 0; i < methods.length && !found; i++) { Method current = methods[i]; if(current.getParameterTypes().length == 0) { if(current.getName().equals(key) || current.getName().equals(getKey)) { result = current.invoke(clazz, ERXConstant.EmptyObjectArray); found = true; } } } if(!found) { Field fields[] = clazz.getDeclaredFields(); for (int i = 0; i < fields.length && !found; i++) { Field current = fields[i]; if(current.getName().equals(key)) { // AK: should have a check for existence of KeyValueCodingProtectedAccessor here // AK: disabled, only for testing // boolean isAccessible = current.isAccessible(); // current.setAccessible(true); result = current.get(clazz); // current.setAccessible(isAccessible); found = true; } } } if(!found) { throw new NSKeyValueCoding.UnknownKeyException("Key + " + key + " not found in class " + clazz.getName(), clazz, key); } } catch (Exception e) { throw new NSForwardException(e); } if(result != null) { if(result.getClass().getComponentType() != null) { result = new NSArray((Object[])result); } else if(result instanceof Collection) { NSMutableArray array = new NSMutableArray(((Collection)result).size()); for (Iterator iter = ((Collection)result).iterator(); iter.hasNext();) { array.addObject(iter.next()); } result = array; } } } return result; } /** * Returns final strings constants from an interface or class. Useful in particular when you want to create * selection lists from your interfaces automatically. * @param c * @return list of final string constants */ public static NSArray<ERXKeyValuePair> staticStringsForClass(Class c) { NSMutableArray<ERXKeyValuePair> result = new NSMutableArray(); if(c.getSuperclass() != null) { result.addObjectsFromArray(staticStringsForClass(c.getSuperclass())); } Field[] fields = c.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; try { if(Modifier.isFinal(field.getModifiers()) && field.getType().equals(String.class)) { String key = field.getName(); String value = (String) field.get(c); result.addObject(new ERXKeyValuePair(key, value)); } } catch (IllegalArgumentException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } catch (IllegalAccessException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } } return result; } public static final NSKeyValueCodingAdditions Statics = new NSKeyValueCodingAdditions() { /** * @see com.webobjects.foundation.NSKeyValueCodingAdditions#valueForKeyPath(java.lang.String) */ public Object valueForKeyPath(String arg0) { String name = ERXStringUtilities.firstPropertyKeyInKeyPath(arg0); String rest = ERXStringUtilities.keyPathWithoutFirstProperty(arg0); Class clazz = _classes.get(name); if(clazz == null) { throw new IllegalArgumentException("Class not found: " + arg0); } String field = ERXStringUtilities.keyPathWithoutFirstProperty(arg0); Object result = ERXKeyValueCodingUtilities.classValueForKey(clazz, field); if(field.length() > rest.length()) { rest = ERXStringUtilities.keyPathWithoutFirstProperty(arg0); result = NSKeyValueCodingAdditions.Utility.valueForKeyPath(result, rest); } return result; } /** * @see com.webobjects.foundation.NSKeyValueCodingAdditions#takeValueForKeyPath(java.lang.Object, java.lang.String) */ public void takeValueForKeyPath(Object arg0, String arg1) { throw new UnsupportedOperationException("Can't set values on class yet"); } /** * @see com.webobjects.foundation.NSKeyValueCoding#valueForKey(java.lang.String) */ public Object valueForKey(String arg0) { return valueForKeyPath(arg0); } /** * @see com.webobjects.foundation.NSKeyValueCoding#takeValueForKey(java.lang.Object, java.lang.String) */ public void takeValueForKey(Object arg0, String arg1) { takeValueForKeyPath(arg0, arg1); } }; public static Object privateValueForKey(Object target, String key) { Field field = accessibleFieldForKey(target, key); try { if(field != null) { return field.get(target); } Method method = accessibleMethodForKey(target, key); if(method != null) { return method.invoke(target, (Object[]) null); } throw new NSKeyValueCoding.UnknownKeyException("Key "+ key + " not found", target, key); } catch (IllegalArgumentException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } catch (IllegalAccessException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } catch (InvocationTargetException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } } public static void takePrivateValueForKey(Object target, Object value, String key) { Field field = accessibleFieldForKey(target, key); try { if(field != null) { field.set(target, value); } else { throw new NSKeyValueCoding.UnknownKeyException("Key "+ key + " not found", target, key); } } catch (IllegalArgumentException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } catch (IllegalAccessException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } } private static Field accessibleFieldForKey(Object target, String key) { Field f = fieldForKey(target, key); if(f != null) { f.setAccessible(true); } return f; } private static Method accessibleMethodForKey(Object target, String key) { Method f = methodForKey(target, key); if(f != null) { f.setAccessible(true); } return f; } public static Field fieldForKey(Object target, String key) { Field result = null; Class c = target.getClass(); while (c != null) { try { result = c.getDeclaredField(key); if(result != null) { return result; } } catch (SecurityException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } catch (NoSuchFieldException e) { c = c.getSuperclass(); } } return null; } public static Method methodForKey(Object target, String key) { Method result = null; Class c = target.getClass(); while (c != null) { try { result = c.getDeclaredMethod(key); if(result != null) { return result; } } catch (SecurityException e) { throw NSForwardException._runtimeExceptionForThrowable(e); } catch (NoSuchMethodException e) { c = c.getSuperclass(); } } return null; } /**  * Works like takeValuesFromDictionary, except that it only calls takeValueForKey if the current value  * is different than the new value.  * * @param object the object on which to operate * @param dictionary the key-value pairs to set  */ public static void takeChangedValuesFromDictionary(Object object, NSDictionary<String, ?> dictionary) { if (dictionary == null) { return; } // do this check only once in the beginning instead of using NSKeyValueCoding.Utility below NSKeyValueCoding keyValueCodingObject = (object instanceof NSKeyValueCoding) ? (NSKeyValueCoding)object : null; NSArray<String> keys = dictionary.allKeys(); int count = keys.count(); for (int i = 0; i < count; i++) { String key = keys.objectAtIndex(i); Object value = dictionary.objectForKey(key); if (value == NSKeyValueCoding.NullValue) { value = null; } if (keyValueCodingObject != null) { if (ObjectUtils.notEqual(value, keyValueCodingObject.valueForKey(key))) { keyValueCodingObject.takeValueForKey(value, key); } } else { if (ObjectUtils.notEqual(value, NSKeyValueCoding.DefaultImplementation.valueForKey(object, key))) { NSKeyValueCoding.DefaultImplementation.takeValueForKey(object, value, key); } } } } }