/** * == @Spearal ==> * * Copyright (C) 2014 Franck WOLFF & William DRAI (http://www.spearal.io) * * 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 org.spearal.impl.partial; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javassist.util.proxy.MethodFilter; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyFactory; import javassist.util.proxy.ProxyObject; import org.spearal.SpearalContext; import org.spearal.configuration.PartialObjectFactory; import org.spearal.configuration.PropertyFactory.Property; import org.spearal.impl.cache.AnyMap.ValueProvider; import org.spearal.impl.cache.CopyOnWriteMap; import org.spearal.impl.instantiator.ProxyInstantiator; /** * @author Franck WOLFF */ public class JavassistPartialObjectFactory implements PartialObjectFactory, ValueProvider<Class<?>, Object, Class<?>> { private final CopyOnWriteMap<Class<?>, Object, Class<?>> proxyClassesCache; public JavassistPartialObjectFactory() { this.proxyClassesCache = new CopyOnWriteMap<Class<?>, Object, Class<?>>(true, this); } @Override public Class<?> createValue(SpearalContext context, Class<?> key, Object unused) { context.getSecurizer().checkDecodable(key); ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setFilter(new PartialObjectFilter(context, key)); proxyFactory.setSuperclass(key); proxyFactory.setInterfaces(new Class<?>[] { ExtendedPartialObjectProxy.class }); return proxyFactory.createClass(); } @Override public Object instantiatePartial(SpearalContext context, Class<?> cls, Property[] partialProperties) throws InstantiationException, IllegalAccessException { if (Proxy.isProxyClass(cls)) return ProxyInstantiator.instantiatePartial(context, cls, partialProperties); Class<?> proxyClass = proxyClassesCache.getOrPutIfAbsent(context, cls); ProxyObject proxyObject = (ProxyObject)proxyClass.newInstance(); proxyObject.setHandler(new PartialObjectProxyHandler(context, cls, partialProperties)); return proxyObject; } private static class PartialObjectFilter implements MethodFilter { private static final Method[] partialObjectProxyMethods = PartialObjectProxy.class.getMethods(); private final Set<Method> accessors; public PartialObjectFilter(SpearalContext ctx, Class<?> cls) { this.accessors = new HashSet<Method>(); for (Property property : ctx.getProperties(cls)) { if (property.hasGetter()) accessors.add(property.getGetter()); if (property.hasSetter()) accessors.add(property.getSetter()); } } @Override public boolean isHandled(Method method) { return accessors.contains(method) || isPartialObjectProxyMethod(method); } private static boolean isPartialObjectProxyMethod(Method method) { for (Method partialObjectProxyMethod : partialObjectProxyMethods) { if (partialObjectProxyMethod.equals(method)) return true; } return false; } } private static class PartialObjectProxyHandler implements MethodHandler { private final Property[] allProperties; private final Map<String, Property> definedProperties; public PartialObjectProxyHandler(SpearalContext context, Class<?> cls, Property[] partialProperties) { this.allProperties = context.getProperties(cls); this.definedProperties = new HashMap<String, Property>(partialProperties.length); for (Property property : partialProperties) { if (property != null) this.definedProperties.put(property.getName(), property); } } public Object invoke(Object obj, Method method, Method proceed, Object[] args) throws Exception { // Proxy methods. if (method.getDeclaringClass() == PartialObjectProxy.class) { String name = method.getName(); if ("$hasUndefinedProperties".equals(name)) return Boolean.valueOf(definedProperties.size() < allProperties.length); if ("$isDefined".equals(name) && args.length == 1) return Boolean.valueOf(definedProperties.containsKey(args[0])); if ("$undefine".equals(name) && args.length == 1) return definedProperties.remove(args[0]); if ("$getDefinedProperties".equals(name)) return definedProperties.values().toArray(new Property[definedProperties.size()]); if ("$getActualClass".equals(name)) return obj.getClass().getSuperclass(); throw new UnsupportedOperationException("Internal error: " + method.toString()); } // Setters. if (method.getReturnType() == void.class) { for (Property property : allProperties) { if (method.equals(property.getSetter())) { proceed.invoke(obj, args); definedProperties.put(property.getName(), property); return null; } } throw new UnsupportedOperationException("Internal error: " + method.toString()); } // Getters. for (Property property : definedProperties.values()) { if (method.equals(property.getGetter())) return proceed.invoke(obj, args); } throw new UndefinedPropertyException(method.toString()); } } }