org.jmingo.document.id.IdFieldGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.jmingo.document.id.IdFieldGenerator.java

Source

/**
 * Copyright 2013-2014 The JMingo Team
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.jmingo.document.id;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.jmingo.document.annotation.GeneratedValue;
import org.jmingo.document.id.generator.IdGenerator;
import org.jmingo.document.id.generator.factory.IdGeneratorFactory;
import org.jmingo.exceptions.IdGenerationException;
import org.apache.commons.lang3.ClassUtils;
import org.jmingo.util.DocumentUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;

import static java.text.MessageFormat.format;

/**
 * Generates and sets new values for the fields that are annotated with @Id and @GeneratedValue.
 */
public class IdFieldGenerator {

    private IdGeneratorFactory idGeneratorFactory;

    private final static Table<Class<?>, Class<?>, Function> transformers = HashBasedTable.create();

    // messages
    private static final String INCOMPATIBLE_TYPES = "Incompatible types. Failed to set generated value: ''{0}'' (type: {1}) to the field: ''{2}'' (type: {3}) from {4}. Generator strategy: ''{5}''";
    private static final String GENERATOR_NOT_FOUND = "wasn't found any generators for strategy: ''{0}'' and type: ''{1}''";

    static {
        transformers.put(Long.class, String.class, Object::toString);
        transformers.put(Integer.class, String.class, Object::toString);
        transformers.put(Long.TYPE, String.class, Object::toString);
        transformers.put(Integer.TYPE, String.class, Object::toString);
    }

    public IdFieldGenerator(IdGeneratorFactory idGeneratorFactory) {
        this.idGeneratorFactory = idGeneratorFactory;
    }

    /**
     * Generates values for all pojo's ids fields including
     * inherited fields that annotated with {@link org.jmingo.document.annotation.Id} and with {@link org.jmingo.document.annotation.GeneratedValue}.
     * <p>
     * Generation depends on chosen strategy or a given id field type.
     * see predefined id generators strategies {@link org.jmingo.document.id.generator.IdGeneratorStrategy}.
     * <p>
     * Generates values for nested fields that also have an id field, for instance if the pojo has collection of objects
     * of a type that has a field that annotated with @Id and @GeneratedValue then for each object form a collection will
     * be generated new id value.
     * It applies for {@code Collection}, {@code Array} and {@code Map}.
     * <p>
     * If an id field is final, synthetic or static then new value will not be generated.
     *
     * @param pojo the pojo that has identifier field for that new value should be generated
     * @throws org.jmingo.exceptions.IdGenerationException
     */
    public void generateId(Object pojo) throws IdGenerationException {
        if (pojo != null) {
            doWithFields(pojo, pojo.getClass(), generateId, skipSyntheticField, skipFinalField, skipStaticField);
        }
    }

    private void generateIdForArray(Object[] objects) {
        generateIdForList(Arrays.asList(objects));
    }

    private void generateIdForList(Collection<?> objects) {
        objects.forEach(this::generateId);
    }

    private void generateIdForMap(Map<?, ?> objects) {
        objects.forEach((key, val) -> {
            generateId(key);
            generateId(val);
        });
    }

    private void checkAndGenerateId(Object pojo, Field field) throws IdGenerationException {
        if (field.isAnnotationPresent(GeneratedValue.class)) {
            GeneratedValue generatedValue = field.getAnnotation(GeneratedValue.class);
            IdGenerator idGenerator = idGeneratorFactory.create(generatedValue.strategy());
            if (idGenerator == null) {
                throw new IdGenerationException(
                        format(GENERATOR_NOT_FOUND, generatedValue.strategy(), field.getType()));
            }
            generateAndSet(pojo, field, idGenerator, generatedValue.strategy());
        }
    }

    private void generateAndSet(Object pojo, Field field, IdGenerator idGenerator, String strategy) {
        setValue(pojo, field, idGenerator.generate(), strategy);
    }

    private void setValue(Object pojo, Field field, Object val, String strategy) {
        try {
            if (!instanceOf(val.getClass(), field.getType())) {
                Function transformer = transformers.get(val.getClass(), field.getType());
                if (transformer == null) {
                    throw new IdGenerationException(format(INCOMPATIBLE_TYPES, val, val.getClass(), field.getName(),
                            field.getType(), field.getDeclaringClass(), strategy));
                }
                val = transformer.apply(val);
            }
            field.set(pojo, val);
        } catch (IllegalAccessException e) {
            throw new IdGenerationException(e);
        }
    }

    private boolean instanceOf(Class<?> cls, Class<?> toClass) {
        cls = ClassUtils.primitiveToWrapper(cls);
        toClass = ClassUtils.primitiveToWrapper(toClass);
        return ClassUtils.isAssignable(cls, toClass);
    }

    private FieldCallback generateId = (target, field) -> {
        try {
            field.setAccessible(true); //make field accessible to set/get value
            if (DocumentUtils.isId(field)) {
                checkAndGenerateId(target, field);
            } else {
                Object child = field.get(target);
                if (child != null) {
                    if (child instanceof Collection) {
                        generateIdForList((Collection) child);
                    } else if (child instanceof Object[]) {
                        generateIdForArray((Object[]) child);
                    } else if (child instanceof Map) {
                        generateIdForMap((Map) child);
                    } else {
                        generateId(child);
                    }
                }
            }

        } catch (Throwable e) {
            throw new IdGenerationException(e);
        }
    };

    private interface FieldCallback {
        void apply(Object target, Field field);
    }

    /**
     * Filters for fields.
     */
    private Predicate<Field> skipSyntheticField = field -> !field.isSynthetic();
    private Predicate<Field> skipFinalField = field -> !Modifier.isFinal(field.getModifiers());
    private Predicate<Field> skipStaticField = field -> !Modifier.isStatic(field.getModifiers());

    private void doWithFields(Object target, Class<?> clazz, FieldCallback fc, Predicate<Field>... filters)
            throws IllegalArgumentException {
        Class<?> targetClass = clazz;
        do {
            Field[] fields = targetClass.getDeclaredFields();
            for (Field field : fields) {
                if (filters != null && filters.length > 0 && !applyFilters(field, filters)) {
                    continue;
                }
                fc.apply(target, field);
            }
            targetClass = targetClass.getSuperclass();
        } while (targetClass != null && targetClass != Object.class);
    }

    private boolean applyFilters(Field field, Predicate<Field>... filters) {
        for (Predicate<Field> filter : Arrays.asList(filters)) {
            if (!filter.test(field)) {
                return false;
            }
        }
        return true;
    }

}