package ns.foundation; import java.util.concurrent.ConcurrentHashMap; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtField; import javassist.CtMember; import javassist.CtMethod; import javassist.CtPrimitiveType; import javassist.NotFoundException; import javassist.bytecode.AccessFlag; import ns.foundation.NSKeyValueCoding._KeyBinding; import ns.foundation.NSKeyValueCoding._KeyBindingCreation._KeyBindingFactory; import ns.foundation.NSKeyValueCoding._KeyBindingCreation._KeyBindingFactory._BindingStorage; public class _NSPropertyAccessor { private static final _KeyBinding _NotAvailableIndicator = new NSKeyValueCoding._KeyBinding(null, null); private static final ConcurrentHashMap<_KeyBinding, _BindingStorage> _bindingStorageMapTable = new ConcurrentHashMap<_KeyBinding, _BindingStorage>(256); private static ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); private static int counter = 0; private ClassPool ctPool; private Object targetObject; private Class<?> targetClass; private CtClass targetCtClass; public _NSPropertyAccessor(Object object) { targetObject = object; targetClass = object.getClass(); ctPool = new ClassPool(); ctPool.appendSystemPath(); try { targetCtClass = ctPool.getCtClass(targetClass.getName()); } catch (NotFoundException e) { throw new NSForwardException(e); } } public static void _flushCaches() { _bindingStorageMapTable.clear(); } public static boolean _canAccessFieldsDirectlyForClass(Class<?> objectClass) { //return _NSReflectionUtilities._staticBooleanMethodValue("canAccessFieldsDirectly", null, null, objectClass, NSKeyValueCoding.class, true); return true; } protected static class _LegacyCompatibleKeyBinding extends _KeyBinding { private final _KeyBinding _delegate; private Class<?> _valueType; _LegacyCompatibleKeyBinding(_KeyBinding keyBinding) { super(keyBinding.targetClass(), keyBinding.key()); _delegate = keyBinding; } @Override public boolean isScalarProperty() { return _delegate.isScalarProperty(); } @Override public void setValueInObject(Object value, Object object) { _delegate.setValueInObject(value, object); } @Override public Class<?> valueType() { if (_valueType == null) { Class<?> valueType = _delegate.valueType(); if (valueType.isPrimitive() && _NSUtilities._isClassANumberOrABoolean(valueType)) { valueType = _NSUtilities.classObjectForClass(valueType); } _valueType = valueType; } return _valueType; } @Override public Object valueInObject(Object object) { return _delegate.valueInObject(object); } @Override public String toString() { return _delegate.toString(); } } public static _KeyBinding _createKeyBindingForKey(Object object, String key, int lookupOrder[], boolean trueForSetAndFalseForGet) { _KeyBinding keyBinding = new _NSPropertyAccessor(object)._createKeyBindingForKey(key, lookupOrder, trueForSetAndFalseForGet); return keyBinding == null ? null : new _LegacyCompatibleKeyBinding(keyBinding); } private NSKeyValueCoding._KeyBinding _createKeyBindingForKey(String key, int[] lookupOrder, boolean trueForSetAndFalseForGet) { if ((key == null) || (key.length() == 0)) { return null; } Class<?> objectClass = targetObject.getClass(); boolean canAccessFieldsDirectlyTestPerformed = false; boolean canAccessFieldsDirectly = false; // we use a KeyBinding as key for the _BindingStorage object map table since it gives us exactly what we need: a class and a key - but we have to create a new lookup key binding to avoid synchronizing the read lookup (and we need a new instance for the write access) NSKeyValueCoding._KeyBinding lookupBinding = new NSKeyValueCoding._KeyBinding(objectClass, key); _BindingStorage bindingStorage = _bindingStorageMapTable.get(lookupBinding); if (bindingStorage == null) { bindingStorage = new _KeyBindingFactory._BindingStorage(); _bindingStorageMapTable.put(lookupBinding, bindingStorage); } _KeyBindingFactory.Callback keyBindingCreationCallbackObject = (targetObject instanceof _KeyBindingFactory.Callback) ? (_KeyBindingFactory.Callback) targetObject : null; NSKeyValueCoding._KeyBinding keyBindings[] = (trueForSetAndFalseForGet) ? bindingStorage._keySetBindings : bindingStorage._keyGetBindings; for (int i = 0; i < lookupOrder.length; i++) { int lookup = lookupOrder[i]; NSKeyValueCoding._KeyBinding keyBinding = ((lookup >= _KeyBindingFactory.MethodLookup) && (lookup <= _KeyBindingFactory.UnderbarFieldLookup)) ? keyBindings[lookup] : null; if (keyBinding == null) { Class<?> valueType = null; if (trueForSetAndFalseForGet) { _KeyBinding getKeyBinding = _createKeyBindingForKey(key, lookupOrder, false); valueType = getKeyBinding != null ? getKeyBinding.valueType() : null; } switch (lookup) { case _KeyBindingFactory.MethodLookup: String methodName = prefixedKey((trueForSetAndFalseForGet) ? "set" : "get", key); if (trueForSetAndFalseForGet) { // look up 'setKey' keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._methodKeySetBinding(key, methodName) : _methodKeySetBinding(key, methodName, valueType); } else { // look up 'getKey' keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._methodKeyGetBinding(key, methodName) : _methodKeyGetBinding(key, methodName); if (keyBinding == null) { // look up 'key' keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._methodKeyGetBinding(key, key) : _methodKeyGetBinding(key, key); } if (keyBinding == null) { // look up 'isKey' methodName = new String(prefixedKey("is", key)); keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._methodKeyGetBinding(key, methodName) : _methodKeyGetBinding(key, methodName); } } break; case _KeyBindingFactory.UnderbarMethodLookup: String underbarMethodName = prefixedKey((trueForSetAndFalseForGet) ? "_set" : "_get", key); if (trueForSetAndFalseForGet) { // look up '_setKey' keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._methodKeySetBinding(key, underbarMethodName) : _methodKeySetBinding(key, underbarMethodName, valueType); } else { // look up '_getKey' keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._methodKeyGetBinding(key, underbarMethodName) : _methodKeyGetBinding(key, underbarMethodName); if (keyBinding == null) { // look up '_key' underbarMethodName = prefixedKey("_", key); keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._methodKeyGetBinding(key, underbarMethodName) : _methodKeyGetBinding(key, underbarMethodName); } if (keyBinding == null) { // look up '_isKey' underbarMethodName = prefixedKey("_is", key); keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._methodKeyGetBinding(key, underbarMethodName) : _methodKeyGetBinding(key, underbarMethodName); } } break; case _KeyBindingFactory.FieldLookup: if (!canAccessFieldsDirectlyTestPerformed) { canAccessFieldsDirectlyTestPerformed = true; canAccessFieldsDirectly = _canAccessFieldsDirectlyForClass(objectClass); } if (canAccessFieldsDirectly) { // look up 'key' keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._fieldKeyBinding(key, key) : _fieldKeyBinding(key, key); if (keyBinding == null) { // look up 'isKey' String fieldName = prefixedKey("is", key); keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._fieldKeyBinding(key, fieldName) : _fieldKeyBinding(key, fieldName); } } break; case _KeyBindingFactory.UnderbarFieldLookup: if (!canAccessFieldsDirectlyTestPerformed) { canAccessFieldsDirectlyTestPerformed = true; canAccessFieldsDirectly = _canAccessFieldsDirectlyForClass(objectClass); } if (canAccessFieldsDirectly) { // look up '_key' String underbarFieldName = prefixedKey("_", key); keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._fieldKeyBinding(key, underbarFieldName) : _fieldKeyBinding(key, underbarFieldName); if (keyBinding == null) { // look up '_isKey' underbarFieldName = prefixedKey("_is", key); keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._fieldKeyBinding(key, underbarFieldName) : _fieldKeyBinding(key, underbarFieldName); } } break; case _KeyBindingFactory.OtherStorageLookup: keyBinding = (keyBindingCreationCallbackObject != null) ? keyBindingCreationCallbackObject._otherStorageBinding(key) : null; break; } if (keyBinding == null) { keyBinding = _NotAvailableIndicator; } if ((lookup == _KeyBindingFactory.FieldLookup) || (lookup == _KeyBindingFactory.UnderbarFieldLookup)) { // set and get bindings are the same for fields (but not for methods since the name of set and get methods are actually different) bindingStorage._keySetBindings[lookup] = bindingStorage._keyGetBindings[lookup] = keyBinding; } else if ((lookup == _KeyBindingFactory.MethodLookup) || (lookup == _KeyBindingFactory.UnderbarMethodLookup)) { keyBindings[lookup] = keyBinding; } } if ((keyBinding != null) && (keyBinding != _NotAvailableIndicator)) { return keyBinding; } } return null; } public NSKeyValueCoding._KeyBinding _fieldKeyBinding(String key, String fieldName) { Class<?> objectClass = targetObject.getClass(); NSKeyValueCoding.ValueAccessor valueAccessor = NSKeyValueCoding.ValueAccessor._valueAccessorForClass(objectClass); boolean publicFieldOnly = (valueAccessor == null); try { CtField field = targetCtClass.getField(fieldName); if ((publicFieldOnly && !AccessFlag.isPublic(field.getModifiers())) || AccessFlag.isPrivate((field.getModifiers()))) { return null; } CtClass valueType = field.getType(); CtClass wrapper = _keyBindingClassForMember(key, field); CtMethod getter = CtMethod.make("public Object valueInObject(Object object) {" + "return " + box(valueType, unbox(targetCtClass, "object") + "." + fieldName) + "; }", wrapper); StringBuffer code = new StringBuffer("public void setValueInObject(Object value, Object object) {"); if (valueType.isPrimitive()) { code.append("if (value == null) {"); code.append(NSKeyValueCoding.Utility.class.getName() + ".unableToSetNullForKey(object,\"" + key + "\");"); code.append("return; }"); } code.append("((" + field.getDeclaringClass().getName() +")object)." + fieldName + " = "+ convert(valueType, "value") + "; }"); CtMethod setter = CtMethod.make(code.toString(), wrapper); wrapper.addMethod(getter); wrapper.addMethod(setter); _addMethodsForValueType(wrapper, valueType, valueType.isPrimitive()); @SuppressWarnings("unchecked") Class<_KeyBinding> wrapperClass = wrapper.toClass(classLoader, getClass().getProtectionDomain()); _KeyBinding binding = wrapperClass.newInstance(); return binding; } catch (NotFoundException e) { return null; } catch (Exception e) { throw new NSForwardException(e); } } public NSKeyValueCoding._KeyBinding _methodKeyGetBinding(String key, String methodName) { Class<?> objectClass = targetObject.getClass(); NSKeyValueCoding.ValueAccessor valueAccessor = NSKeyValueCoding.ValueAccessor._valueAccessorForClass(objectClass); boolean publicMethodOnly = (valueAccessor == null); try { CtMethod method = null; for (CtMethod target : targetCtClass.getMethods()) { if (!target.getName().equals(methodName) || target.getParameterTypes().length != 0) { continue; } if (( publicMethodOnly && !AccessFlag.isPublic(target.getModifiers())) || (AccessFlag.isPrivate(target.getModifiers()))) { continue; } method = target; break; } if (method == null) { return null; } CtClass valueType = method.getReturnType(); CtClass wrapper = _keyBindingClassForMember(key, method); CtMethod getter = CtMethod.make("public Object valueInObject(Object object) {" + "return " + box(valueType, unbox(method.getDeclaringClass(), "object") + "." + methodName + "()") + "; }", wrapper); wrapper.addMethod(getter); _addMethodsForValueType(wrapper, valueType, false); @SuppressWarnings("unchecked") Class<_KeyBinding> wrapperClass = wrapper.toClass(classLoader, getClass().getProtectionDomain()); _KeyBinding binding = wrapperClass.newInstance(); return binding; } catch (NotFoundException e) { return null; } catch (Exception e) { throw new NSForwardException(e); } } public NSKeyValueCoding._KeyBinding _methodKeySetBinding(String key, String methodName, Class<?> targetValueType) { Class<?> objectClass = targetObject.getClass(); NSKeyValueCoding.ValueAccessor valueAccessor = NSKeyValueCoding.ValueAccessor._valueAccessorForClass(objectClass); boolean publicMethodOnly = (valueAccessor == null); if (targetValueType == null) { targetValueType = Object.class; } try { CtMethod method = null; for (CtMethod target : targetCtClass.getMethods()) { if (!target.getName().equals(methodName) || target.getParameterTypes().length != 1) { continue; } if ((publicMethodOnly && !AccessFlag.isPublic(target.getModifiers())) || AccessFlag.isPrivate(target.getModifiers())) { continue; } CtClass clazz = target.getParameterTypes()[0]; if (clazz.getName().equals(targetValueType.getName())) { method = target; break; } else if (boxedTypeName(clazz).equals(_NSUtilities.classObjectForClass(targetValueType).getName())) { method = target; } else if (method == null) { method = target; } } if (method == null) { return null; } CtClass valueType = method.getParameterTypes()[0]; StringBuffer code = new StringBuffer ("public void setValueInObject(Object value, Object object) {"); if (valueType.isPrimitive()) { code.append("if (value == null) {"); code.append(NSKeyValueCoding.Utility.class.getName() + ".unableToSetNullForKey(object,\"" + key + "\");"); code.append("return; }"); } code.append(unbox(method.getDeclaringClass(), "object") + "." + methodName + "("+ convert(valueType, "value") +"); }"); CtClass wrapper = _keyBindingClassForMember(key, method); CtMethod setter = CtMethod.make(code.toString(), wrapper); wrapper.addMethod(setter); _addMethodsForValueType(wrapper, valueType, valueType.isPrimitive()); @SuppressWarnings("unchecked") Class<_KeyBinding> wrapperClass = wrapper.toClass(classLoader, getClass().getProtectionDomain()); _KeyBinding binding = wrapperClass.newInstance(); return binding; } catch (NotFoundException e) { return null; } catch (Exception e) { throw new NSForwardException(e); } } private CtClass _keyBindingClassForMember(String key, CtMember member) { try { String declaringClassName = member.getDeclaringClass().getName(); CtClass ctAccessor = ctPool.getCtClass(_KeyBinding.class.getName()); String wrapperName = declaringClassName + "$" + key + "$KVCWrapper" + counter++; if (targetClass.getPackage().getName().startsWith("java.")) { wrapperName = "com.webobjects.kvc." + wrapperName; } CtClass wrapper = ctPool.makeClass(wrapperName, ctAccessor); CtConstructor c = new CtConstructor(new CtClass[0], wrapper); c.setBody("super(" + targetCtClass.getName() +".class, \"" + key + "\");"); wrapper.addConstructor(c); return wrapper; } catch (Exception e) { throw new NSForwardException(e); } } private void _addMethodsForValueType(CtClass wrapper, CtClass valueType, boolean isScalar) throws CannotCompileException { if (valueType != null) { StringBuffer code = new StringBuffer("public Class valueType() {"); if (valueType.isPrimitive()) { code.append("return " + boxedTypeName(valueType) + ".TYPE;"); } else { code.append("return " + valueType.getName() + ".class;"); } code.append("}"); CtMethod vt = CtMethod.make(code.toString(), wrapper); wrapper.addMethod(vt); code = new StringBuffer("public boolean isScalarProperty() {"); code.append(isScalar ? "return true;" :"return false;"); code.append("}"); CtMethod sp = CtMethod.make(code.toString(), wrapper); wrapper.addMethod(sp); } } private String box(CtClass type, String arg) { boolean isPrimitive = type.isPrimitive(); if (isPrimitive && (type == CtClass.intType)) return "(Integer.valueOf((int)" + arg + "))"; if (isPrimitive && (type == CtClass.longType)) return "(Long.valueOf((long)" + arg + "))"; if (isPrimitive && (type == CtClass.shortType)) return "(Short.valueOf((short)" + arg + "))"; if (isPrimitive && (type == CtClass.floatType)) return "(Float.valueOf((float)" + arg + "))"; if (isPrimitive && (type == CtClass.doubleType)) return "(Double.valueOf((double)" + arg + "))"; if (isPrimitive && (type == CtClass.charType)) return "(Character.valueOf((char)" + arg + "))"; if (isPrimitive && (type == CtClass.byteType)) return "(Byte.valueOf((byte)" + arg + "))"; if (isPrimitive && (type == CtClass.booleanType)) return "(Boolean.valueOf((boolean)" + arg + "))"; return "((" + type.getName() + ") " + arg + ")"; } private String unbox(CtClass type, String arg) { boolean isPrimitive = type.isPrimitive(); if (isPrimitive && (type == CtClass.intType)) return "((Number)" + arg + ").intValue()"; if (isPrimitive && (type == CtClass.longType)) return "((Number)" + arg + ").longValue()"; if (isPrimitive && (type == CtClass.shortType)) return "((Number)" + arg + ").shortValue()"; if (isPrimitive && (type == CtClass.floatType)) return "((Number)" + arg + ").floatValue()"; if (isPrimitive && (type == CtClass.doubleType)) return "((Number)" + arg + ").doubleValue()"; if (isPrimitive && (type == CtClass.charType)) return "((Character)" + arg + ").charValue()"; if (isPrimitive && (type == CtClass.byteType)) return "((Number)" + arg + ").byteValue()"; if (isPrimitive && (type == CtClass.booleanType)) return "((Boolean)" + arg + ").booleanValue()"; return "((" + type.getName() + ") " + arg + ")"; } private String convert(CtClass type, String arg) { String converted = "(" + _NSPropertyUtilities.class.getName() + ".convertObjectIntoCompatibleValue(" + arg + "," + boxedTypeName(type) + ".class" + "))"; boolean isPrimitive = type.isPrimitive(); if (isPrimitive && (type == CtClass.intType)) return "((Number)" + converted + ").intValue()"; if (isPrimitive && (type == CtClass.longType)) return "((Number)" + converted + ").longValue()"; if (isPrimitive && (type == CtClass.shortType)) return "((Number)" + converted + ").shortValue()"; if (isPrimitive && (type == CtClass.floatType)) return "((Number)" + converted + ").floatValue()"; if (isPrimitive && (type == CtClass.doubleType)) return "((Number)" + converted + ").doubleValue()"; if (isPrimitive && (type == CtClass.charType)) return "((Character)" + converted + ").charValue()"; if (isPrimitive && (type == CtClass.byteType)) return "((Number)" + converted + ").byteValue()"; if (isPrimitive && (type == CtClass.booleanType)) return "((Boolean)" + converted + ").booleanValue()"; if (_NSPropertyUtilities._isCtClassABoolean(type)) return "((Boolean)" + converted + ")"; if (_NSPropertyUtilities._isCtClassANumber(type)) return "((Number)" + converted + ")"; return "((" + type.getName() + ") " + converted + ")"; } private String boxedTypeName(CtClass type) { if (type.isPrimitive()) return ((CtPrimitiveType)type).getWrapperName(); return type.getName(); } private String prefixedKey(String prefix, String key) { if (prefix == null) { return key; } StringBuffer sb = new StringBuffer(prefix.length() + key.length()); sb.append(prefix); if ("_".equals(prefix)) { return sb.append(key).toString(); } return sb.append(_NSStringUtilities.capitalizedString(key)).toString(); } }