/* * Copyright 2008-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.nominanuda.zen.obj.wrap; import static com.nominanuda.zen.obj.wrap.Wrap.WF; import static java.util.Arrays.asList; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.function.Function; import com.nominanuda.zen.common.Check; import com.nominanuda.zen.common.Tuple2; import com.nominanuda.zen.obj.Arr; import com.nominanuda.zen.obj.JixSrc; import com.nominanuda.zen.obj.JsonType; import com.nominanuda.zen.obj.Obj; import com.nominanuda.zen.obj.Stru; import com.nominanuda.zen.obj.StruFactory; import com.nominanuda.zen.stereotype.Copyable; import com.nominanuda.zen.stereotype.Value; class WrapperInvocationHandler implements InvocationHandler { private final Obj o; private final Set<Method> roleMethods; private final Set<Method> defaultMethods; private static final HashSet<Method> nonRoleMethods = new HashSet<Method>(); static { nonRoleMethods.addAll(asList(Object.class.getDeclaredMethods())); nonRoleMethods.addAll(asList(ObjWrapper.class.getDeclaredMethods())); nonRoleMethods.addAll(asList(Obj.class.getDeclaredMethods())); nonRoleMethods.addAll(asList(Stru.class.getDeclaredMethods())); nonRoleMethods.addAll(asList(Map.class.getDeclaredMethods())); nonRoleMethods.addAll(asList(Iterable.class.getDeclaredMethods())); nonRoleMethods.addAll(asList(JixSrc.class.getDeclaredMethods())); nonRoleMethods.addAll(asList(Value.class.getDeclaredMethods())); nonRoleMethods.addAll(asList(Copyable.class.getDeclaredMethods())); nonRoleMethods.addAll(asList(StruFactory.class.getDeclaredMethods())); } public WrapperInvocationHandler(Obj o, Class<?> role) { this.o = o != null ? o : Obj.make(); roleMethods = new HashSet<Method>(); defaultMethods = new HashSet<Method>(); for (Method m : role.getMethods()) { if (m.isDefault()) { defaultMethods.add(m); } else if(nonRoleMethods.contains(m)) { // standard behaviour } else { roleMethods.add(m); } } } @SuppressWarnings("unchecked") @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); try { if (defaultMethods.contains(method)) { Field f = Lookup.class.getDeclaredField("IMPL_LOOKUP"); f.setAccessible(true); Lookup lookup = (Lookup)f.get(null); final Object result = lookup .unreflectSpecial(method, method.getDeclaringClass()) .bindTo(proxy) .invokeWithArguments(args); return result; } else if ("unwrap".equals(name)) { return o; } else if (roleMethods.contains(method)) { Class<?> type = method.getReturnType(); int argsL = (args == null ? 0 : args.length); if (argsL == 0) { // any getter if (Collection.class.isAssignableFrom(type)) { // collection getter Arr arr = o.getArr(name); if (arr != null) { if (type.equals(Arr.class)) { return arr; } Collection<Object> coll = type.isInterface() ? new LinkedList<>() : (Collection<Object>) type.newInstance(); Class<?> itemType = null; try { itemType = getCollectionReturnComponentType(method); } catch(Exception e) { // dynamic mode on } for (Object v : arr) { if (itemType == null && v == null) { coll.add(null); } else { if (itemType == null) { itemType = v.getClass(); } coll.add(fromObjValue(v, itemType)); } } return coll; } else { return null; } } else if (Map.class.isAssignableFrom(type)) { // map getter Obj obj = o.getObj(name); if (obj != null) { if (type.equals(Obj.class)) { return obj; } if (type.isInterface()) { type = LinkedHashMap.class; } Map<String, Object> map = (Map<String, Object>) type.newInstance(); Class<?> itemType = null; try { Tuple2<Class<?>, Class<?>> keyValTypes = getMapReturnComponentTypes(method); itemType = keyValTypes.get1(); } catch(Exception e) { // dynamic mode on } for (String key : obj.keySet()) { Object val = obj.get(key); if (itemType == null && val == null) { map.put(key, null); } else { if(itemType == null) { itemType = val.getClass(); } map.put(key, fromObjValue(val, itemType)); } } return map; } else { return null; } } else { // simple getter return fromObjValue(o.get(name), type); } } else if (argsL == 1) {//setter o.put(name, toDataObjectValue(args[0])); return proxy; } else { // bail out throw new RuntimeException(); } } else if ("equals".equals(name) && args.length == 1) { // allows [Proxy].equals([Proxy]) return args[0] == proxy; } else { return method.invoke(o, args); } } catch(InvocationTargetException e) { throw Check.ifNull((Exception)e.getCause(), e); } } private Class<?> getCollectionReturnComponentType(Method method) throws ClassNotFoundException { Cls cls = method.getAnnotation(Cls.class); if (cls != null) { return cls.value(); } Type t1 = method.getGenericReturnType(); if (t1 instanceof ParameterizedType) { ParameterizedType t2 = (ParameterizedType)t1; Type[] actualTypeArgs = t2.getActualTypeArguments(); if (actualTypeArgs != null && actualTypeArgs.length == 1) { try { return Class.forName(actualTypeArgs[0].getTypeName()); } catch (ClassNotFoundException e) { throw e; } } } throw new ClassNotFoundException( "could not determine generic return type for method "+method.toString()); } private Tuple2<Class<?>, Class<?>> getMapReturnComponentTypes(Method method) throws ClassNotFoundException { Cls cls = method.getAnnotation(Cls.class); if (cls != null) { return new Tuple2<>(String.class, cls.value()); } Type t1 = method.getGenericReturnType(); if (t1 instanceof ParameterizedType) { ParameterizedType t2 = (ParameterizedType)t1; Type[] actualTypeArgs = t2.getActualTypeArguments(); if (actualTypeArgs != null && actualTypeArgs.length == 2) { try { return new Tuple2<>( Class.forName(actualTypeArgs[0].getTypeName()), Class.forName(actualTypeArgs[1].getTypeName())); } catch (ClassNotFoundException e) { throw e; } } } throw new ClassNotFoundException( "could not determine generic return type for method "+method.toString()); } private Object fromObjValue(Object v, Class<?> type) { if (null == v) { return null; } else if (Boolean.TYPE.equals(type)) { // expected boolean return Boolean.TRUE.equals((Boolean)v); // force true/false (also when v == null) } else if (JsonType.isNullablePrimitive(v)) { if (Double.class.equals(type) || double.class.equals(type)) { return ((Number)v).doubleValue(); } else if (Float.class.equals(type) || float.class.equals(type)) { return ((Number)v).floatValue(); } else if (Integer.class.equals(type) || int.class.equals(type)) { return ((Number)v).intValue(); } else if (Long.class.equals(type) || long.class.equals(type)) { return ((Number)v).longValue(); } else { return v; } } else if (JsonType.isObj(v)) { Obj o = (Obj)v; WrapType wrapType = type.getAnnotation(WrapType.class); if (wrapType != null) { Function<Obj, Object> builder = createBuilder(wrapType, type); return builder.apply(o); } else if (WrapperItemFactory.class.isAssignableFrom(type)) { try { Method factoryMethod = findWrapMethod(type); return factoryMethod.invoke(null, v); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { throw new RuntimeException(e); } } else if (ObjWrapper.class.isAssignableFrom(type)) { // sub object return WF.wrap(o, type); } else { throw new IllegalArgumentException("cannot convert value:"+v+" to type:"+type.getName()); } } else { throw new IllegalArgumentException("cannot convert value:"+v+" to type:"+type.getName()); } } private static final Map<Class<?>, Function<Obj, Object>> BUILDER_CACHE = new HashMap<>(); private Function<Obj, Object> createBuilder(WrapType wrapType, Class<?> type) { Function<Obj, Object> builder = BUILDER_CACHE.get(type); if (builder == null) { String field = wrapType.field(); Map<String, Class<?>> typeMap = new HashMap<>(); String[] values = wrapType.values(); Class<?>[] types = wrapType.types(); for (int i = 0; i < values.length; i++) { typeMap.put(values[i], types[i]); builder = (o) -> WF.wrap(o, typeMap.get(o.fetch(field))); BUILDER_CACHE.put(type, builder); } } return builder; } private Method findWrapMethod(Class<?> type) throws NoSuchMethodException { try { return type.getMethod("wrap", Obj.class); } catch (NoSuchMethodException e) { for (Class<?> ancestor : type.getInterfaces()) { if (WrapperItemFactory.class.isAssignableFrom(ancestor)) { try { return findWrapMethod(ancestor); } catch(NoSuchMethodException e1) {} } } throw new NoSuchMethodException(); } } @SuppressWarnings("unchecked") private Object toDataObjectValue(Object v) { if (v instanceof Collection) { Arr arr = Arr.make(); for (Object o : (Collection<Object>)v) { arr.add(toDataObjectValue(o)); } return arr; } else if (v instanceof Map) { Obj obj = Obj.make(); for (Entry<String, Object> entry : ((Map<String, Object>)v).entrySet()) { obj.store(entry.getKey(), toDataObjectValue(entry.getValue())); } return obj; } else if (v instanceof ObjWrapper) { // sub object return ((ObjWrapper)v).unwrap(); } return v; } }