org.hibernate.search.backend.elasticsearch.gson.impl.AbstractConfiguredExtraPropertiesJsonAdapterFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.search.backend.elasticsearch.gson.impl.AbstractConfiguredExtraPropertiesJsonAdapterFactory.java

Source

/*
 * Hibernate Search, full-text search for your domain model
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.search.backend.elasticsearch.gson.impl;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.hibernate.search.backend.elasticsearch.gson.impl.AbstractExtraPropertiesJsonAdapter.ExtraPropertyAdapter;
import org.hibernate.search.backend.elasticsearch.gson.impl.AbstractExtraPropertiesJsonAdapter.FieldAdapter;
import org.hibernate.search.util.AssertionFailure;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

/**
 * @author Yoann Rodiere
 */
public abstract class AbstractConfiguredExtraPropertiesJsonAdapterFactory implements TypeAdapterFactory {

    @Override
    public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        Builder<T> builder = new Builder<>(gson, type);
        addFields(builder);
        return builder.build();
    }

    protected abstract <T> void addFields(Builder<T> builder);

    protected static final class Builder<T> {

        private final Gson gson;
        private final TypeToken<T> type;
        private final Map<String, FieldAdapter<? super T>> fieldAdapters = new LinkedHashMap<>();

        private Builder(Gson gson, TypeToken<T> type) {
            super();
            this.gson = gson;
            this.type = type;
        }

        public <F> Builder<T> add(String fieldName, Class<F> fieldType) {
            return add(fieldName, TypeToken.get(fieldType));
        }

        public <F> Builder<T> add(String fieldName, TypeToken<F> fieldType) {
            TypeAdapter<F> adapter = gson.getAdapter(fieldType);
            return add(fieldName, adapter);
        }

        public <F> Builder<T> add(String fieldName, TypeAdapter<F> adapter) {
            Field field = getField(type, fieldName);

            boolean first = true;
            for (String name : getFieldNames(field)) {
                boolean serialized = first;
                fieldAdapters.put(name, new ReflectiveFieldAdapter<T, F>(field, adapter, serialized));
                first = false;
            }

            return this;
        }

        private TypeAdapter<T> build() {
            return new Adapter<>(fieldAdapters, getExtraPropertyAdapter(gson, type), getConstructor(type));
        }
    }

    private static Field getField(TypeToken<?> type, String fieldName) {
        Class<?> rawType = type.getRawType();
        while (rawType != null) {
            try {
                Field field = rawType.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field;
            } catch (NoSuchFieldException ignored) {
            }
            rawType = rawType.getSuperclass();
        }
        throw new AssertionFailure("Missing or inaccessible field " + fieldName + " on type " + type);
    }

    private static <T> ExtraPropertyAdapter<T> getExtraPropertyAdapter(Gson gson, TypeToken<T> type) {
        Class<?> rawType = type.getRawType();
        while (rawType != null) {
            for (Field field : rawType.getDeclaredFields()) {
                SerializeExtraProperties annotation = field.getAnnotation(SerializeExtraProperties.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    return new ReflectiveExtraPropertyAdapter<>(field, gson);
                }
            }
            rawType = rawType.getSuperclass();
        }
        throw new AssertionFailure("Missing or inaccessible field annotated with " + SerializeExtraProperties.class
                + " on type " + type);
    }

    @SuppressWarnings("unchecked")
    private static <T> Constructor<T> getConstructor(TypeToken<T> type) {
        try {
            return (Constructor<T>) type.getRawType().getConstructor();
        } catch (NoSuchMethodException | SecurityException e) {
            throw new AssertionFailure("Missing or inaccessible no-arg constructor on type " + type);
        }
    }

    private static List<String> getFieldNames(Field f) {
        SerializedName serializedName = f.getAnnotation(SerializedName.class);
        List<String> fieldNames = new LinkedList<String>();
        if (serializedName == null) {
            fieldNames.add(f.getName());
        } else {
            fieldNames.add(serializedName.value());
            for (String alternate : serializedName.alternate()) {
                fieldNames.add(alternate);
            }
        }
        return fieldNames;
    }

    private static class ReflectiveExtraPropertyAdapter<T, F> implements ExtraPropertyAdapter<T> {

        private final Field extraPropertiesField;
        private final TypeAdapter<JsonElement> propertyValueAdapter;

        public ReflectiveExtraPropertyAdapter(Field extraPropertiesField, Gson gson) {
            super();
            this.extraPropertiesField = extraPropertiesField;
            this.propertyValueAdapter = gson.getAdapter(JsonElement.class);
        }

        @Override
        public void readOne(JsonReader in, String name, T instance) throws IOException {
            JsonElement propertyValue = propertyValueAdapter.read(in);
            Map<String, JsonElement> extraProperties = getExtraProperties(instance);
            if (extraProperties == null) {
                extraProperties = new LinkedHashMap<>();
                setExtraProperties(instance, extraProperties);
            }
            extraProperties.put(name, propertyValue);
        }

        @Override
        public void writeAll(JsonWriter out, T instance) throws IOException {
            Map<String, JsonElement> extraProperties = getExtraProperties(instance);
            if (extraProperties == null) {
                return;
            }
            for (Map.Entry<String, JsonElement> entry : extraProperties.entrySet()) {
                out.name(entry.getKey());
                propertyValueAdapter.write(out, entry.getValue());
            }
        }

        private void setExtraProperties(T instance, Map<String, JsonElement> extraProperties) {
            try {
                extraPropertiesField.set(instance, extraProperties);
            } catch (IllegalArgumentException e) {
                throw new AssertionFailure("Field " + extraPropertiesField + " annotated with "
                        + SerializeExtraProperties.class + " has the wrong type on " + instance.getClass());
            } catch (IllegalAccessException e) {
                throw new AssertionFailure("Field " + extraPropertiesField + " annotated with "
                        + SerializeExtraProperties.class + " is inaccessible on " + instance.getClass());
            }
        }

        @SuppressWarnings("unchecked")
        private Map<String, JsonElement> getExtraProperties(T instance) {
            try {
                return (Map<String, JsonElement>) extraPropertiesField.get(instance);
            } catch (IllegalAccessException e) {
                throw new AssertionFailure("Field " + extraPropertiesField + " annotated with "
                        + SerializeExtraProperties.class + " is inaccessible on " + instance.getClass());
            }
        }
    }

    private static class ReflectiveFieldAdapter<T, F> implements FieldAdapter<T> {

        private final Field field;
        private final TypeAdapter<F> typeAdapter;
        private final boolean serialized;

        public ReflectiveFieldAdapter(Field field, TypeAdapter<F> typeAdapter, boolean serialized) {
            super();
            this.field = field;
            this.typeAdapter = typeAdapter;
            this.serialized = serialized;
        }

        @Override
        public void read(JsonReader in, T instance) throws IOException {
            try {
                field.set(instance, typeAdapter.read(in));
            } catch (IllegalAccessException e) {
                throw new AssertionFailure("Field " + field + " is not accessible.", e);
            }
        }

        @Override
        public boolean serialized() {
            return serialized;
        }

        @Override
        @SuppressWarnings("unchecked")
        public void write(JsonWriter out, T instance) throws IOException {
            if (!serialized) {
                throw new AssertionFailure("The property with this name should not be serialized");
            }
            try {
                typeAdapter.write(out, (F) field.get(instance));
            } catch (IllegalAccessException e) {
                throw new AssertionFailure("Field " + field + " is not accessible.", e);
            }
        }
    }

    private static class Adapter<T> extends AbstractExtraPropertiesJsonAdapter<T> {

        private final Constructor<T> constructor;

        public Adapter(Map<String, ? extends FieldAdapter<? super T>> fieldAdapters,
                ExtraPropertyAdapter<? super T> extraPropertyAdapter, Constructor<T> constructor) {
            super(fieldAdapters, extraPropertyAdapter);
            this.constructor = constructor;
        }

        @Override
        protected T createInstance() {
            try {
                return constructor.newInstance();
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                throw new AssertionFailure("Constructor " + constructor + " is not accessible.", e);
            }
        }
    }

}