001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 *   This file is part of the Granite Data Services Platform.
006 *
007 *   Granite Data Services is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   Granite Data Services is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 *   General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public
018 *   License along with this library; if not, write to the Free Software
019 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 *   USA, or see <http://www.gnu.org/licenses/>.
021 */
022package org.granite.tide.data;
023
024import java.lang.reflect.Method;
025import java.lang.reflect.Type;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.concurrent.ConcurrentMap;
028
029import javassist.util.proxy.MethodFilter;
030import javassist.util.proxy.MethodHandler;
031import javassist.util.proxy.ProxyFactory;
032import javassist.util.proxy.ProxyObject;
033
034import org.granite.messaging.amf.io.convert.Converter;
035import org.granite.messaging.amf.io.convert.Converters;
036import org.granite.util.TypeUtil;
037
038
039/**
040 * @author William DRAI
041 */
042public class ChangeSetConverter extends Converter {
043        
044        private ConcurrentMap<Class<?>, Class<?>> proxyClasses = new ConcurrentHashMap<Class<?>, Class<?>>();
045
046        public ChangeSetConverter(Converters converters) {
047                super(converters);
048        }
049
050        @Override
051        protected boolean internalCanConvert(Object value, Type targetType) {
052                if (value instanceof ChangeSet && targetType instanceof Class<?>) {
053                        if (((ChangeSet)value).getChanges() == null || ((ChangeSet)value).getChanges().length == 0)
054                                throw new IllegalArgumentException("Incoming ChangeSet objects must contain at least one Change");
055                        
056                        String className = ((ChangeSet)value).getChanges()[0].getClassName();
057                        try {
058                                Class<?> clazz = TypeUtil.forName(className);
059                                return ((Class<?>)targetType).isAssignableFrom(clazz);
060                        } 
061                        catch (ClassNotFoundException e) {
062                                throw new RuntimeException("Cannot find class for ChangeSet argument " + className);
063                        }
064                }
065                return false;
066        }
067
068        @Override
069        protected Object internalConvert(Object value, Type targetType) {
070                ChangeSet changeSet = (ChangeSet)value;
071                String className = changeSet.getChanges()[0].getClassName();
072                try {
073                        Class<?> clazz = TypeUtil.forName(className);
074                        Class<?> proxyClass = proxyClasses.get(clazz);
075                        if (proxyClass == null) {
076                                ProxyFactory proxyFactory = new ProxyFactory();
077                                proxyFactory.setFilter(FINALIZE_FILTER);
078                                proxyFactory.setSuperclass(clazz);
079                                proxyFactory.setInterfaces(new Class<?>[] { ChangeSetProxy.class });
080                                proxyClass = proxyFactory.createClass();
081                                proxyClasses.put(clazz, proxyClass);
082                        }
083                                
084                        ProxyObject proxyObject = (ProxyObject)proxyClass.newInstance();
085                        proxyObject.setHandler(new ChangeSetProxyHandler(proxyObject, changeSet));
086                        return proxyObject;
087                }
088                catch (Exception e) {
089                        throw new RuntimeException("Cannot build proxy for Change argument " + className);
090                }
091        }
092
093        private static final MethodFilter FINALIZE_FILTER = new MethodFilter() {
094                public boolean isHandled(Method m) {
095                        return m.getParameterTypes().length > 0 || !m.getName().equals("finalize");
096                }
097        };
098
099        private static class ChangeSetProxyHandler implements MethodHandler {
100                
101                private final Object proxyObject;
102                private final ChangeSet changeSet;
103
104                public ChangeSetProxyHandler(ProxyObject proxyObject, ChangeSet changeSet) {
105                        this.proxyObject = proxyObject;
106                        this.changeSet = changeSet;
107                }
108
109                public Object invoke(Object object, Method method, Method method1, Object[] args) throws Exception {
110                        String name = method.getName();
111                        if ("toString".equals(name)) {
112                                return changeSet.getChanges()[0].getClassName() + "@" + System.identityHashCode(object);
113                        }
114                        else if ("equals".equals(name)) {
115                                return proxyObject == object;
116                        }
117                        else if ("hashCode".equals(name)) {
118                                return System.identityHashCode(object);
119                        }
120                        else if ("getChangeSetProxyData".equals(name) && method.getParameterTypes().length == 0) {
121                                return changeSet;
122                        }
123                        return null;
124                }
125        }
126}