org.fixb.meta.FixMetaScanner.java Source code

Java tutorial

Introduction

Here is the source code for org.fixb.meta.FixMetaScanner.java

Source

/*
 * Copyright 2013 YTEQ Ltd.
 *
 * 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.fixb.meta;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.fixb.FixException;
import org.fixb.annotations.*;
import org.joda.time.Instant;
import org.joda.time.LocalDate;
import org.reflections.Reflections;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.*;

import static java.util.Arrays.asList;
import static org.fixb.FixConstants.MSG_TYPE_TAG;
import static org.fixb.meta.FixFieldMeta.fixFieldMeta;
import static org.fixb.meta.FixFieldMeta.fixGroupMeta;

/**
 * Scans Java classes annotated with FixB annotations and builds FIX bindings metadata dictionary.
 * The MutableFixMetaDictionary is used to optimise the scanning process allowing each class to be scanned only once
 * i.e. some classes appear as components of other classes.
 *
 * @author vladyslav.yatsenko
 */
public class FixMetaScanner {

    /**
     * Scans the given packages for classes annotated with @FixMessage and @FixEnum and adds them to the resulting dictionary.
     *
     * @param packageNames a name of the package containing FIX mapped classes
     */
    public static FixMetaDictionary scanClassesIn(String... packageNames) {
        final MutableFixMetaDictionary dictionary = new MutableFixMetaDictionary();
        for (String packageName : packageNames) {
            try {
                for (Class<?> type : getClasses(packageName)) {
                    if (type.isEnum()) {
                        dictionary.addMeta(FixEnumMeta.forClass((Class<? extends Enum>) type));
                    } else {
                        dictionary.addMeta(scanClass(type, dictionary));
                    }
                }
            } catch (Exception e) {
                throw new IllegalStateException(
                        "Error registering classes in package " + packageName + ": " + e.getMessage(), e);
            }
        }
        return dictionary;
    }

    /**
     * Scans the given class for FIX mapping annotations.
     *
     * @param model the domain class to scan
     * @param <T>   the domain type to scan
     * @return a FixBlockMeta for the given type.
     * @throws FixException if the given class is not properly annotated.
     */
    public static <T> FixBlockMeta<T> scanClass(Class<T> model) {
        return scanClassAndAddToDictionary(model, new MutableFixMetaDictionary());
    }

    static <T> FixBlockMeta<T> scanClassAndAddToDictionary(Class<T> model,
            MutableFixMetaDictionary fixMetaDictionary) {
        if (fixMetaDictionary.containsMeta(model)) {
            return fixMetaDictionary.getComponentMeta(model);
        }

        final FixBlockMeta<T> result = scanClass(model, fixMetaDictionary);
        fixMetaDictionary.addMeta(result);
        return result;
    }

    static <T> FixBlockMeta<T> scanClass(Class<T> model, MutableFixMetaDictionary dictionary) {
        if (dictionary.containsMeta(model)) {
            return dictionary.getComponentMeta(model);
        }

        final FixBlockMeta<T> result;

        if (model.getConstructors().length == 0) {
            throw new FixException("Class [" + model.getName() + "] does not provide a public constructor.");
        }

        @SuppressWarnings("unchecked")
        Optional<Constructor<T>> constructor = Optional.of((Constructor<T>) model.getConstructors()[0]);

        final int c = numberOfFixParameters(constructor.get());

        if (c == 0) {
            constructor = Optional.absent();
        } else if (c != constructor.get().getParameterTypes().length) {
            throw new FixException(
                    "Some constructor parameters don't have FIX mapping in class [" + model.getName() + "].");
        }

        if (model.isAnnotationPresent(FixMessage.class)) {
            final FixMessage messageAnnotation = model.getAnnotation(FixMessage.class);
            ImmutableList.Builder<FixFieldMeta> allFieldsBuilder = ImmutableList.builder();
            allFieldsBuilder.addAll(processConstantFields(messageAnnotation)); // add constant fields
            allFieldsBuilder.addAll(processFields(model, constructor, dictionary)); // add all other fields
            result = new FixMessageMeta<>(model, messageAnnotation.type(), allFieldsBuilder.build(),
                    constructor.isPresent());
        } else if (model.isAnnotationPresent(FixBlock.class)) {
            final List<FixFieldMeta> fixFields = processFields(model, constructor, dictionary);
            result = new FixBlockMeta<>(model, fixFields, constructor.isPresent());
        } else {
            throw new FixException(
                    "Neither @FixBlock nor @FixMessage annotation present on class [" + model.getName() + "].");
        }

        return result;
    }

    /**
     * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
     *
     * @param packageName the base package
     * @return All found classes with FIX bindings.
     */
    private static Set<Class<?>> getClasses(final String packageName) {
        final Reflections reflections = new Reflections(packageName);
        final Set<Class<?>> allClasses = new HashSet<>();
        allClasses.addAll(reflections.getTypesAnnotatedWith(FixMessage.class));
        allClasses.addAll(reflections.getTypesAnnotatedWith(FixEnum.class));

        // Process classes in constant order
        final TreeSet<Class<?>> sortedClasses = new TreeSet<>(new Comparator<Class<?>>() {
            @Override
            public int compare(Class<?> o1, Class<?> o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });

        sortedClasses.addAll(allClasses);

        return sortedClasses;
    }

    private static int numberOfFixParameters(Constructor<?> constructor) {
        int fixAnnotationCount = 0;
        for (Annotation[] annotations : constructor.getParameterAnnotations()) {
            if (hasFixParamAnnotation(annotations)) {
                fixAnnotationCount++;
            }
        }
        return fixAnnotationCount;
    }

    private static boolean hasFixParamAnnotation(Annotation[] annotations) {
        if (annotations.length == 0) {
            return false;
        }
        final Set<Class<? extends Annotation>> fixAnnotationType = new HashSet<>(
                asList(FixBlock.class, FixField.class, FixGroup.class));
        for (Annotation annotation : annotations) {
            if (fixAnnotationType.contains(annotation.annotationType())) {
                return true;
            }
        }
        return false;
    }

    private static List<FixFieldMeta> processConstantFields(FixMessage messageAnnotation) {
        final List<FixFieldMeta> headerFields = new ArrayList<>();
        headerFields.add(fixFieldMeta(MSG_TYPE_TAG, messageAnnotation.type(), true));
        for (FixMessage.Field f : messageAnnotation.header()) {
            headerFields.add(fixFieldMeta(f.tag(), f.value(), true));
        }
        for (FixMessage.Field f : messageAnnotation.body()) {
            headerFields.add(fixFieldMeta(f.tag(), f.value(), false));
        }
        return headerFields;
    }

    private static <T> List<FixFieldMeta> processFields(final Class<T> model,
            final Optional<Constructor<T>> constructor, final MutableFixMetaDictionary dictionary) {
        final ImmutableMap<Integer, FixFieldMeta> fixFields = scanFields(model, dictionary);

        if (constructor.isPresent()) {
            final FixFieldMeta[] orderedFixFields = new FixFieldMeta[fixFields.size()];
            orderFixFields(constructor.get(), fixFields, orderedFixFields, dictionary, 0);
            return asList(orderedFixFields);
        } else {
            return new ArrayList<>(fixFields.values());
        }
    }

    private static int orderFixFields(Constructor<?> constructor, ImmutableMap<Integer, FixFieldMeta> fixFields,
            FixFieldMeta[] ordered, MutableFixMetaDictionary dictionary, int offset) {
        final Annotation[][] annotations = constructor.getParameterAnnotations();
        for (int i = 0; i < annotations.length; i++) {
            if (hasFixAnnotation(annotations[i], FixBlock.class)) {
                final Class<?>[] paramTypes = constructor.getParameterTypes();
                for (FixFieldMeta fixFieldMeta : dictionary.getComponentMeta(paramTypes[i]).getFields()) {
                    ordered[offset++] = fixFields.get(fixFieldMeta.getTag());
                }
            } else {
                final Optional<Integer> tag = getFixTagFromAnnotations(annotations[i]);
                if (!tag.isPresent()) {
                    throw new FixException("Some constructor parameters don't have FIX mapping in class ["
                            + constructor.getDeclaringClass().getName() + "].");
                }

                final FixFieldMeta fixFieldMeta = fixFields.get(tag.get());
                if (fixFieldMeta == null) {
                    throw new FixException("No field with tag [" + tag.get()
                            + "] found, however constructor parameters exist in class ["
                            + constructor.getDeclaringClass().getName() + "].");
                }

                ordered[offset++] = fixFieldMeta;
            }
        }

        return offset;
    }

    private static boolean hasFixAnnotation(Annotation[] annotations, Class<? extends Annotation> annotationType) {
        for (Annotation x : annotations) {
            if (x.annotationType() == annotationType) {
                return true;
            }
        }
        return false;
    }

    private static Optional<Integer> getFixTagFromAnnotations(Annotation... annotations) {
        Integer tag = null;
        for (Annotation x : annotations) {
            Class<? extends Annotation> annType = x.annotationType();
            if (annType == FixField.class) {
                tag = ((FixField) x).tag();
                break;
            } else if (annType == FixGroup.class) {
                tag = ((FixGroup) x).tag();
                break;
            }
        }

        return Optional.fromNullable(tag);
    }

    private static <T> ImmutableMap<Integer, FixFieldMeta> scanFields(final Class<T> model,
            final MutableFixMetaDictionary dictionary, final Field... parentPath) {
        Map<Integer, FixFieldMeta> fixFields = Maps.newLinkedHashMap();

        for (Field f : model.getDeclaredFields()) {
            final Class<?> type = f.getType();
            final Field[] path = newPath(parentPath, f);

            for (Annotation annotation : f.getDeclaredAnnotations()) {
                if (FixField.class == annotation.annotationType()) {
                    FixField fixField = (FixField) annotation;
                    int tag = fixField.tag();
                    if (fixFields.containsKey(tag)) {
                        throw new FixException("There are more than one fields mapped with FIX tag [" + tag + "].");
                    }
                    fixFields.put(tag, fixFieldMeta(tag, fixField.header(), fixField.optional(), path));

                } else if (FixBlock.class == annotation.annotationType()) {
                    for (FixFieldMeta fixFieldMeta : scanClassAndAddToDictionary(type, dictionary).getFields()) {
                        Field[] fieldPath = ((FixDynamicFieldMeta) fixFieldMeta).getPath();
                        Field[] newFieldPath = Arrays.copyOf(path, path.length + fieldPath.length);
                        for (int i = path.length; i < newFieldPath.length; i++) {
                            newFieldPath[i] = fieldPath[i - path.length];
                        }
                        fixFields.put(fixFieldMeta.getTag(), new FixDynamicFieldMeta(fixFieldMeta.getTag(),
                                fixFieldMeta.isHeader(), fixFieldMeta.isOptional(), newFieldPath));
                    }
                } else if (FixGroup.class == annotation.annotationType()) {
                    if (Collection.class.isAssignableFrom(type)) {
                        FixGroup fixGroup = (FixGroup) annotation;
                        if (!fixFields.containsKey(fixGroup.tag())) {
                            Class<?> componentType = getComponentType(fixGroup, f.getGenericType());
                            FixFieldMeta fieldMeta = isSimpleType(componentType)
                                    ? fixGroupMeta(fixGroup.tag(), fixGroup.header(), fixGroup.optional(),
                                            fixGroup.componentTag(), componentType, path)
                                    : fixGroupMeta(fixGroup.tag(), fixGroup.header(), fixGroup.optional(),
                                            dictionary.getOrCreateComponentMeta(componentType), path);
                            fixFields.put(fixGroup.tag(), fieldMeta);
                        }
                    } else {
                        throw new FixException("Only Collection can represent a FIX group: [" + f.getName()
                                + "] in class [" + model.getName() + "].");
                    }
                }
            }
        }

        final Class<? super T> superclass = model.getSuperclass();
        if (superclass != null && superclass != Object.class) {
            fixFields.putAll(scanFields(superclass, dictionary));
        }

        return ImmutableMap.copyOf(fixFields);
    }

    private static Class<?> getComponentType(FixGroup annotation, Type genericType) {
        Class<?> explicitComponent = annotation.component();
        return explicitComponent == Void.class ? getElementType(genericType) : explicitComponent;
    }

    private static Class<?> getElementType(Type collectionType) {
        if (collectionType instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) collectionType;
            Type[] typeArguments = pType.getActualTypeArguments();
            if (typeArguments.length == 1 && typeArguments[0] instanceof Class) {
                return (Class<?>) typeArguments[0];
            }
        }
        return Object.class;
    }

    private static Field[] newPath(Field[] parentPath, Field f) {
        Field[] path = Arrays.copyOf(parentPath, parentPath.length + 1);
        path[path.length - 1] = f;
        return path;
    }

    private static boolean isSimpleType(Class<?> type) {
        return type.isPrimitive() || SIMPLE_CLASSES.contains(type);
    }

    @SuppressWarnings("unchecked")
    private static final List<? extends Class<?>> SIMPLE_CLASSES = asList(Boolean.class, Character.class,
            Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, String.class,
            BigDecimal.class, Instant.class, LocalDate.class, Enum.class);
}