com.viadeo.kasper.common.exposition.query.DefaultQueryFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.viadeo.kasper.common.exposition.query.DefaultQueryFactory.java

Source

// ----------------------------------------------------------------------------
//  This file is part of the Kasper framework.
//
//  The Kasper framework is free software: you can redistribute it and/or 
//  modify it under the terms of the GNU Lesser General Public License as 
//  published by the Free Software Foundation, either version 3 of the 
//  License, or (at your option) any later version.
//
//  Kasper framework is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License
//  along with the framework Kasper.  
//  If not, see <http://www.gnu.org/licenses/>.
// --
//  Ce fichier fait partie du framework logiciel Kasper
//
//  Ce programme est un logiciel libre ; vous pouvez le redistribuer ou le 
//  modifier suivant les termes de la GNU Lesser General Public License telle 
//  que publie par la Free Software Foundation ; soit la version 3 de la 
//  licence, soit ( votre gr) toute version ultrieure.
//
//  Ce programme est distribu dans l'espoir qu'il sera utile, mais SANS 
//  AUCUNE GARANTIE ; sans mme la garantie tacite de QUALIT MARCHANDE ou 
//  d'ADQUATION  UN BUT PARTICULIER. Consultez la GNU Lesser General Public 
//  License pour plus de dtails.
//
//  Vous devez avoir reu une copie de la GNU Lesser General Public License en 
//  mme temps que ce programme ; si ce n'est pas le cas, consultez 
//  <http://www.gnu.org/licenses>
// ----------------------------------------------------------------------------
// ============================================================================
//                 KASPER - Kasper is the treasure keeper
//    www.viadeo.com - mobile.viadeo.com - api.viadeo.com - dev.viadeo.com
//
//           Viadeo Framework for effective CQRS/DDD architecture
// ============================================================================
package com.viadeo.kasper.common.exposition.query;

import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.ObjectArrays;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
import com.thoughtworks.paranamer.*;
import com.viadeo.kasper.api.component.query.Query;
import com.viadeo.kasper.common.exposition.Feature;
import com.viadeo.kasper.common.exposition.FeatureConfiguration;
import com.viadeo.kasper.common.exposition.TypeAdapter;
import com.viadeo.kasper.common.exposition.adapters.NullSafeTypeAdapter;
import com.viadeo.kasper.common.exposition.adapters.TypeAdapterFactory;
import com.viadeo.kasper.common.exposition.exception.KasperQueryAdapterException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentMap;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * This class is responsible of locating and doing all the wiring between
 * TypeAdapters. You can use it in your custom {@link com.viadeo.kasper.common.exposition.adapters.TypeAdapterFactory} in
 * order to delegate the "serialization" to existing mechanism.
 * 
 * @see com.viadeo.kasper.common.exposition.TypeAdapter
 * @see com.viadeo.kasper.common.exposition.adapters.TypeAdapterFactory
 */
public class DefaultQueryFactory implements QueryFactory {

    private static final String PREFIX_METHOD_IS = "is";
    private static final int PREFIX_METHOD_IS_LEN = 2;
    private static final String PREFIX_METHOD_GET = "get";
    private static final int PREFIX_METHOD_GET_LEN = 3;

    private static final String PREFIX_METHOD_SET = "set";
    private static final int PREFIX_METHOD_SET_LEN = 3;

    private static final Comparator<Constructor> LEAST_PARAM_COUNT_CTR_COMPARATOR = new Comparator<Constructor>() {
        @Override
        public int compare(final Constructor o1, final Constructor o2) {
            return o1.getParameterTypes().length - o2.getParameterTypes().length;
        }
    };

    private final ConcurrentMap<Type, TypeAdapter> adapters;
    private final Map<Type, BeanAdapter> beanAdapters;
    private final List<TypeAdapterFactory> factories;
    private final FeatureConfiguration features;

    private final VisibilityFilter visibilityFilter;
    private final Paranamer paranamer = new CachingParanamer(
            new AdaptiveParanamer(new AnnotationParanamer(new DefaultParanamer()), new BytecodeReadingParanamer()));

    // ------------------------------------------------------------------------

    public DefaultQueryFactory(final FeatureConfiguration features, final Map<Type, TypeAdapter> adapters,
            final Map<Type, BeanAdapter> beanAdapters, final List<? extends TypeAdapterFactory> factories,
            final VisibilityFilter visibilityFilter) {

        this.features = checkNotNull(features);
        this.visibilityFilter = checkNotNull(visibilityFilter);
        this.factories = Lists.newArrayList(checkNotNull(factories));

        this.beanAdapters = Maps.newHashMap(checkNotNull(beanAdapters));

        this.adapters = Maps.newConcurrentMap();
        this.adapters.putAll(checkNotNull(adapters));
    }

    // ------------------------------------------------------------------------

    @SuppressWarnings("unchecked") // Safe: the library is providing type-safety
                                   // through TypeToken and reflection
    @Override
    public <T> TypeAdapter<T> create(final TypeToken<T> typeToken) {

        // - first lets check if a TypeAdapter is available for that class
        TypeAdapter<T> adapter = (TypeAdapter<T>) adapters.get(typeToken.getType());

        if (null == adapter) {
            for (final TypeAdapterFactory candidateFactory : factories) {
                if (TypeToken.of(candidateFactory.getClass())
                        .resolveType(TypeAdapterFactory.class.getTypeParameters()[0]).isAssignableFrom(typeToken)) {

                    final TypeAdapterFactory<T> factory = (TypeAdapterFactory<T>) candidateFactory;
                    final Optional<TypeAdapter<T>> adapterOpt = factory.create(typeToken, this);

                    if (adapterOpt.isPresent()) {
                        adapter = adapterOpt.get();
                        break;
                    }
                }
            }

            if (null == adapter) {
                if (!Query.class.isAssignableFrom(typeToken.getRawType())) {
                    throw new KasperQueryAdapterException(
                            "Could not find any valid TypeAdapter for type " + typeToken.getRawType());
                }
                adapter = (TypeAdapter<T>) provideBeanQueryMapper((TypeToken<Class<? extends Query>>) typeToken);
            }
            checkNotNull(adapter);

            adapter = new NullSafeTypeAdapter<T>(adapter);
            adapters.putIfAbsent(typeToken.getType(), adapter);
        }

        return adapter;
    }

    // ------------------------------------------------------------------------

    private TypeAdapter<? extends Query> provideBeanQueryMapper(final TypeToken<Class<? extends Query>> typeToken) {

        final Set<PropertyAdapter> retAdapters = Sets.newHashSet();

        final Map<String, Method> accessors = new HashMap<String, Method>();
        final Map<String, Method> mutators = new HashMap<String, Method>();

        // no need to look at interfaces as we are interested only in
        // implementations
        Class superClass = typeToken.getRawType();
        while ((null != superClass) && (!superClass.equals(Object.class))) {
            collectAccessors(superClass, accessors);
            collectMutators(superClass, mutators);
            superClass = superClass.getSuperclass();
        }

        final BeanConstructor creator = resolveBeanConstructor(typeToken.getRawType());

        // now we need to create the PropertyAdapters
        /*
         * !!! We must handle the case of properties that have an accessor but
         * have no mutator or ctr argument. Lets throw an exception if all
         * properties with a mutator are not covered by an accessor or a ctr
         * param (consider only selected ctr, the others don't matter as we will
         * not use them)!!!
         */
        for (final Map.Entry<String, Method> accessorEntry : accessors.entrySet()) {
            @SuppressWarnings("unchecked")
            final TypeToken<Object> accessorType = (TypeToken<Object>) typeToken
                    .resolveType(accessorEntry.getValue().getGenericReturnType());

            final Method mutator = mutators.get(accessorEntry.getKey());
            final BeanConstructorProperty ctrProperty = creator.parameters().get(accessorEntry.getKey());

            PropertyAdapter propertyAdapter;

            // we have a ctr with args, this property will be set using the ctr
            // => do not use the mutator
            if (null != ctrProperty) {
                final TypeToken ctrTypeToken = typeToken.resolveType(ctrProperty.type());

                // FIXME do we want to check it or be more permissive?
                if (!accessorType.equals(ctrTypeToken)) {
                    throw new KasperQueryAdapterException(String.format(
                            "Parameter[%s] of type[%s] and accessor[%s] of type[%s] in %s does not match",
                            ctrProperty.name(), ctrTypeToken.getRawType().getSimpleName(),
                            accessorEntry.getValue().getName(), accessorType.getRawType().getSimpleName(),
                            typeToken.getRawType().getSimpleName()));
                }

                propertyAdapter = createPropertyAdapter(mutator, accessorEntry.getValue(), ctrProperty.name(),
                        accessorType);

                if (!retAdapters.contains(propertyAdapter)) {
                    retAdapters.add(propertyAdapter);
                }

            } else {

                if (null != mutator) {

                    final TypeToken mutatorTypeToken = typeToken.resolveType(mutator.getGenericParameterTypes()[0]);

                    // FIXME do we want to check it or be more permissive?
                    if (!accessorType.equals(mutatorTypeToken)) {
                        throw new KasperQueryAdapterException(String.format(
                                "Mutator[%s] of type[%s] and accessor[%s] of type[%s] in %s does not match",
                                mutator.getName(), mutatorTypeToken.getRawType().getSimpleName(),
                                accessorEntry.getValue().getName(), accessorType.getRawType().getSimpleName(),
                                typeToken.getRawType().getSimpleName()));
                    }

                    propertyAdapter = createPropertyAdapter(mutator, accessorEntry.getValue(),
                            accessorEntry.getKey(), accessorType);

                    if (!retAdapters.contains(propertyAdapter)) {
                        retAdapters.add(propertyAdapter);
                    }

                }
                /* else --
                 * for the moment just lets ignore silently methods that have a
                 * set method and no get, and the inverse
                 */
            }
        }

        if (features.has(Feature.FAIL_ON_EMPTY_BEANS) && retAdapters.isEmpty()) {
            throw new KasperQueryAdapterException(
                    "No property has been discovered for query " + typeToken.getRawType());
        }

        return new BeanQueryMapper(creator, retAdapters);
    }

    // ------------------------------------------------------------------------

    @SuppressWarnings("unchecked")
    private PropertyAdapter createPropertyAdapter(final Method mutator, final Method accessor, final String name,
            final TypeToken<Object> propertyType) {

        // for the moment lets mix accessor and mutator annotations as things
        // must be symetric, latter if we need to
        // support more complex cases we will change things
        final Annotation[] propertyAnnotations;

        // Need to check for null if the property does not have a mutator but
        // uses the ctr.
        // We could do it differently but its fine like that for the moment
        // I prefer avoiding to add more and more classes until we really need
        // it
        // deleting code is harder than writing!
        if (null == mutator) {
            propertyAnnotations = accessor.getAnnotations();
        } else {
            propertyAnnotations = ObjectArrays.concat(mutator.getAnnotations(), accessor.getAnnotations(),
                    Annotation.class);
        }

        final BeanProperty property = new BeanProperty(name, accessor.getDeclaringClass(), propertyAnnotations,
                propertyType);

        final boolean handleName;
        final TypeAdapter<Object> delegateAdapter;

        @SuppressWarnings("unchecked")
        // type safety guaranteed by the lib
        BeanAdapter<Object> beanAdapter = (BeanAdapter<Object>) beanAdapters.get(property.getTypeToken().getType());

        // If didn't found an exact match, looking for a bean adapter of a super type.
        if (null == beanAdapter) {
            final TypeToken propertyTypeToken = property.getTypeToken();
            for (final Type knownType : beanAdapters.keySet()) {
                if (TypeToken.of(knownType).isAssignableFrom(propertyTypeToken)) {
                    beanAdapter = beanAdapters.get(knownType);
                    beanAdapters.put(propertyTypeToken.getType(), beanAdapter);
                    break;
                }
            }
        }

        if (null == beanAdapter) {
            handleName = true;
            delegateAdapter = create(propertyType);
        } else {
            handleName = false;
            delegateAdapter = new NullSafeTypeAdapter<Object>(
                    new DecoratedBeanAdapter<Object>(property, beanAdapter));
        }

        if (null != delegateAdapter) {

            return new PropertyAdapter(property, accessor, mutator, delegateAdapter, handleName);

        } else {
            throw new KasperQueryAdapterException("Complex Queries are not supported! "
                    + "Please flatten your Pojo in order to contain only java literal "
                    + "properties or register custom a TypeAdapter.");
        }
    }

    // ------------------------------------------------------------------------

    private BeanConstructor resolveBeanConstructor(final Class forClass) {
        final Constructor[] ctrs = forClass.getDeclaredConstructors();

        /*
         * we want the no arg ctr on top
         */
        Arrays.sort(ctrs, LEAST_PARAM_COUNT_CTR_COMPARATOR);

        /*
         * we choose the first one, because it is crazy to try to guess which is
         * better to use so the rule is choose the ctr with the less number of
         * params
         */
        @SuppressWarnings("unchecked")
        final Constructor<Object> ctr = (Constructor<Object>) ctrs[0];

        /*
         * we now must resolve the parameter names
         */
        final String[] names = paranamer.lookupParameterNames(ctr);
        if (names.length != ctr.getParameterTypes().length) {
            throw new KasperQueryAdapterException(
                    String.format("Could not resolve constructor[%s] parameter names", ctr));
        }

        final Map<String, BeanConstructorProperty> parameters = new HashMap<String, BeanConstructorProperty>();

        for (int i = 0; i < names.length; i++) {
            parameters.put(names[i], new BeanConstructorProperty(i, ctr.getParameterAnnotations()[i], names[i],
                    ctr.getGenericParameterTypes()[i]));
        }

        return new BeanConstructor(ctr, parameters);
    }

    // ------------------------------------------------------------------------

    private void collectAccessors(final Class fromClass, final Map<String, Method> accessors) {
        final Method[] methods = fromClass.getDeclaredMethods();

        for (final Method m : methods) {
            if (visibilityFilter.isVisible(m) && isAccessor(m)) {
                final String name = resolveName(m);
                if (!accessors.containsKey(name)) {
                    accessors.put(name, m);
                }
            }
        }
    }

    // ------------------------------------------------------------------------

    private void collectMutators(final Class fromClass, final Map<String, Method> mutators) {
        final Method[] methods = fromClass.getDeclaredMethods();

        for (final Method m : methods) {
            if (visibilityFilter.isVisible(m) && isMutator(m)) {
                final String name = resolveName(m);
                if (!mutators.containsKey(name)) {
                    mutators.put(name, m);
                }
            }
        }
    }

    // ------------------------------------------------------------------------

    private String resolveName(final Method method) {
        final String methodName = method.getName();

        if (methodName.startsWith(PREFIX_METHOD_IS)) {
            return firstCharToLowerCase(methodName.substring(PREFIX_METHOD_IS_LEN));
        }

        if (methodName.startsWith(PREFIX_METHOD_GET)) {
            return firstCharToLowerCase(methodName.substring(PREFIX_METHOD_GET_LEN));
        }

        if (methodName.startsWith(PREFIX_METHOD_SET)) {
            return firstCharToLowerCase(methodName.substring(PREFIX_METHOD_SET_LEN));
        }

        throw new IllegalStateException("Method must respect Java Bean conventions and start with is, get or set.");
    }

    // --

    private String firstCharToLowerCase(final String str) {
        final String newStr = str.substring(0, 1).toLowerCase();

        if (str.length() > 1) {
            return newStr + str.substring(1);
        } else {
            return newStr;
        }
    }

    // --

    private boolean isAccessor(final Method method) {

        final boolean getMethod = method.getName().startsWith(PREFIX_METHOD_GET)
                && (method.getName().length() > PREFIX_METHOD_GET_LEN);

        final boolean isMethod = (!getMethod) && method.getName().startsWith(PREFIX_METHOD_IS)
                && method.getName().length() > PREFIX_METHOD_IS_LEN;

        final boolean hasNoParameters = method.getParameterTypes().length == 0;

        return (getMethod || isMethod) && hasNoParameters;
    }

    // --

    private boolean isMutator(final Method method) {

        return method.getName().startsWith(PREFIX_METHOD_SET) && (method.getName().length() > PREFIX_METHOD_SET_LEN)
                && (method.getParameterTypes().length == 1);

    }

    // ------------------------------------------------------------------------

    // in fact it is a beanadapter adapted to TypeAdapter, but naming it
    // AdaptedBeanAdapter
    // would sound lolish...(a la spring) :p, this class allows us benefit from
    // what has been done for TypeAdapters
    static class DecoratedBeanAdapter<T> implements TypeAdapter<T> {
        private final BeanProperty property;
        private final BeanAdapter<T> adapter;

        public DecoratedBeanAdapter(final BeanProperty property, final BeanAdapter<T> adapter) {
            this.property = property;
            this.adapter = adapter;
        }

        @Override
        public void adapt(final T value, final QueryBuilder builder) throws Exception {
            adapter.adapt(value, builder, property);
        }

        @Override
        public T adapt(final QueryParser parser) throws Exception {
            return adapter.adapt(parser, property);
        }
    }

}