001/*
002 *  Copyright 2011 Chris Pheby
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.jadira.bindings.core.general.marshaller;
017
018import java.lang.invoke.MethodHandle;
019import java.lang.invoke.MethodHandles;
020import java.lang.reflect.Method;
021import java.lang.reflect.Modifier;
022
023import org.jadira.bindings.core.api.BindingException;
024import org.jadira.bindings.core.api.ToMarshaller;
025
026/**
027 * Base class providing capability to perform marshalling of source object type
028 * to target. This class uses reflection.
029 * <p>
030 * The marshal method must either
031 * </p>
032 * <p>
033 * a) be instance scoped and defined as part of class S. It must accept no
034 * parameters and return a type of T. For example:
035 * </p>
036 * <p>
037 * {@code public String marshal()}
038 * </p>
039 * <p>
040 * b) be statically scoped. It must accept a single parameter of type S and
041 * return a type of T. For example:
042 * </p>
043 * <p>
044 * {@code public static String marshal(BoundType param)}
045 * </p>
046 * @param <S> Source type for the conversion
047 * @param <T> Source type
048 */
049public class MethodToMarshaller<S, T> implements ToMarshaller<S, T> {
050
051        private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
052        
053    private final Class<S> boundClass;
054
055    private final Class<T> targetClass;
056    
057    private final MethodHandle marshalHandle;
058
059    /**
060     * Create a new instance
061     * @param boundClass Bound class
062     * @param targetClass Destination class
063     * @param marshal Marshal instance method on the target class
064     */
065    public MethodToMarshaller(Class<S> boundClass, Class<T> targetClass, Method marshal) {
066        
067        if (marshal.getParameterTypes().length == 0 && Modifier.isStatic(marshal.getModifiers())) {
068            throw new IllegalStateException("marshal method must either be instance scope or define a single parameter");
069        } else if (marshal.getParameterTypes().length == 1 && (!Modifier.isStatic(marshal.getModifiers()))) {
070            throw new IllegalStateException("marshal method must either be instance scope or define a single parameter");
071        } else if (marshal.getParameterTypes().length >= 2) {
072            throw new IllegalStateException("marshal method must either be instance scope or define a single parameter");
073        }
074        
075        if (!targetClass.isAssignableFrom(marshal.getReturnType())) {
076            throw new IllegalStateException("marshal method must return an instance of target class");
077        }
078        if (!marshal.getDeclaringClass().isAssignableFrom(boundClass) && !Modifier.isStatic(marshal.getModifiers())) {
079            throw new IllegalStateException("marshal method must be defined as part of " + boundClass.getSimpleName());
080        }
081
082        this.boundClass = boundClass;
083        this.targetClass = targetClass;
084
085        try {
086                        this.marshalHandle = LOOKUP.unreflect(marshal);
087                } catch (IllegalAccessException e) {
088                        throw new IllegalStateException("Method is not accessible" + marshal);
089                }
090
091    }
092
093        /**
094         * {@inheritDoc}
095         */
096        /* @Override */
097    public T marshal(S object) {
098
099        try {
100                final T result = (T) marshalHandle.invoke(object);
101            return result;
102        } catch (Throwable ex) {
103                if (ex.getCause() instanceof RuntimeException) {
104                throw (RuntimeException) ex.getCause();
105            }
106            throw new BindingException(ex.getMessage(), ex.getCause());
107        }
108    }
109   
110        /**
111         * {@inheritDoc}
112         */
113        /* @Override */
114    public Class<S> getBoundClass() {
115        return boundClass;
116    }
117    
118        /**
119         * {@inheritDoc}
120         */
121        /* @Override */
122        public Class<T> getTargetClass() {
123                return targetClass;
124        }
125}