com.birbit.jsonapi.JsonApiResourceDeserializer.java Source code

Java tutorial

Introduction

Here is the source code for com.birbit.jsonapi.JsonApiResourceDeserializer.java

Source

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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 com.birbit.jsonapi;

import com.birbit.jsonapi.annotations.Relationship;
import com.birbit.jsonapi.annotations.ResourceId;
import com.birbit.jsonapi.annotations.ResourceLink;
import com.google.gson.*;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.reflect.FieldUtils;

@SuppressWarnings("WeakerAccess")
public class JsonApiResourceDeserializer<T> {
    final Class<T> klass;
    private Map<String, Setter> relationshipSetters = new HashMap<>();
    private Map<String, Setter> linkSetters = new HashMap<>();
    private Setter idSetter;
    final String apiType;

    @SuppressWarnings("WeakerAccess")
    public JsonApiResourceDeserializer(String apiType, Class<T> klass) {
        this.klass = klass;
        this.apiType = apiType;

        for (Field field : FieldUtils.getAllFieldsList(klass)) {
            ResourceId resourceId = field.getAnnotation(ResourceId.class);
            if (resourceId != null) {
                validateResourceId(field.getType());
                idSetter = new FieldSetter(field);
            }
            Relationship relationship = field.getAnnotation(Relationship.class);
            if (relationship != null) {
                String name = validateRelationship(field.getType(), relationship);
                relationshipSetters.put(name, new FieldSetter(field));
            }
            ResourceLink resourceLink = field.getAnnotation(ResourceLink.class);
            if (resourceLink != null) {
                String name = validateResourceLink(field.getType(), resourceLink);
                linkSetters.put(name, new FieldSetter(field));
            }
        }

        for (Method method : klass.getDeclaredMethods()) {
            ResourceId resourceId = method.getAnnotation(ResourceId.class);
            if (resourceId != null) {
                validateMethodParameters(ResourceId.class, method);
                idSetter = new MethodSetter(method);
            }
            Relationship relationship = method.getAnnotation(Relationship.class);
            if (relationship != null) {
                Class<?> parameter = validateMethodParameters(Relationship.class, method);
                String name = validateRelationship(parameter, relationship);
                relationshipSetters.put(name, new MethodSetter(method));
            }
            ResourceLink resourceLink = method.getAnnotation(ResourceLink.class);
            if (resourceLink != null) {
                Class<?> parameter = validateMethodParameters(ResourceLink.class, method);
                String name = validateResourceLink(parameter, resourceLink);
                linkSetters.put(name, new MethodSetter(method));
            }
        }
        if (idSetter == null) {
            throw new IllegalStateException("Must provide a ResourceId for " + klass);
        }
    }

    private Class<?> validateMethodParameters(Class annotation, Method method) {
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (parameterTypes.length != 1) {
            throw new IllegalStateException(annotation.getSimpleName() + " method must receive a single parameter");
        }
        Class<?> parameter = parameterTypes[0];
        if (annotation == Relationship.class) {
            if (!parameter.isAssignableFrom(JsonApiRelationship.class)) {
                throw new IllegalStateException(
                        annotation.getSimpleName() + " method must receive a " + "JSonApiRelationship parameter");
            }
        } else {
            if (!parameter.isAssignableFrom(String.class)) {
                throw new IllegalStateException(
                        annotation.getSimpleName() + " method must receive a string parameter");
            }
        }
        return parameter;
    }

    private void validateResourceId(Class<?> type) {
        if (!type.isAssignableFrom(String.class)) {
            throw new IllegalStateException("Id type must be a string");
        }
        if (idSetter != null) {
            throw new IllegalStateException("Cannot have multiple ResourceId annotations in " + klass);
        }
    }

    private String validateResourceLink(Class<?> type, ResourceLink link) {
        String name = link.value().trim();
        if (name.length() == 0) {
            if (!type.isAssignableFrom(JsonApiLinks.class)) {
                throw new IllegalArgumentException(
                        "If ResourceLink value is empty, it must be of type JsonApiLinks");
            }
        } else {
            if (!type.isAssignableFrom(String.class)) {
                // TODO accept a Link array
                throw new IllegalStateException("If ResourceLink value is not empty, it must be of type String");
            }
        }

        if (linkSetters == null) {
            linkSetters = new HashMap<String, Setter>();
        } else if (linkSetters.containsKey(name)) {
            throw new IllegalStateException("Two different fields in " + klass + " has resource link " + name);
        }
        return name;
    }

    private String validateRelationship(Class<?> type, Relationship relationship) {
        if (!type.isAssignableFrom(JsonApiRelationshipList.class)
                && !type.isAssignableFrom(JsonApiRelationship.class) && !type.isAssignableFrom(String.class)
                && !type.isAssignableFrom(List.class)) {
            throw new IllegalStateException("Relationship type must be a JsonApiRelationship, "
                    + "JsonApiRelationshipList, String or List<String>");
        }
        String name = relationship.value().trim();
        if (name.length() == 0) {
            throw new IllegalArgumentException("Relationship value cannot be empty string");
        }
        if (relationshipSetters == null) {
            relationshipSetters = new HashMap<String, Setter>();
        } else if (relationshipSetters.containsKey(name)) {
            throw new IllegalStateException("Two different fields in " + klass + " has relationship " + name);
        }
        return name;
    }

    @SuppressWarnings("WeakerAccess")
    public T deserialize(String id, JsonElement json, JsonDeserializationContext context)
            throws JsonParseException {
        if (!json.isJsonObject()) {
            throw new JsonParseException("expected a json object to parse into " + klass + " but received " + json);
        }
        T t = null;
        try {
            JsonObject jsonObject = json.getAsJsonObject();
            t = parseObject(context, id, jsonObject);
            parseRelationships(context, t, jsonObject);
            parseLinks(context, t, jsonObject);
        } catch (IllegalAccessException e) {
            throw new JsonParseException("Cannot set ID/link on " + t, e);
        } catch (InvocationTargetException e) {
            throw new JsonParseException("Cannot set ID/link on " + t, e);
        } catch (InstantiationException e) {
            throw new JsonParseException(
                    "Cannot create an instance of " + klass + ". Make sure it has a no-arg" + " constructor", e);
        }
        return t;
    }

    private void parseLinks(JsonDeserializationContext context, T t, JsonObject jsonObject)
            throws IllegalAccessException, InvocationTargetException {
        JsonElement links = jsonObject.get("links");
        if (links != null && links.isJsonObject()) {
            JsonObject linksObject = links.getAsJsonObject();
            Setter linksObjectSetter = linkSetters.get("");
            if (linksObjectSetter != null) {
                linksObjectSetter.setOnObject(t, context.deserialize(links, JsonApiLinks.class));
            }
            for (Map.Entry<String, Setter> entry : linkSetters.entrySet()) {
                JsonElement link = linksObject.get(entry.getKey());
                if (link != null && link.isJsonPrimitive()) {
                    entry.getValue().setOnObject(t, link.getAsString());
                }
            }
        }
    }

    private void parseRelationships(JsonDeserializationContext context, T t, JsonObject jsonObject)
            throws IllegalAccessException, InvocationTargetException {
        JsonElement relationships = jsonObject.get("relationships");
        if (relationships != null && relationships.isJsonObject()) {
            JsonObject relationshipsObject = relationships.getAsJsonObject();
            for (Map.Entry<String, Setter> entry : relationshipSetters.entrySet()) {
                JsonElement relationship = relationshipsObject.get(entry.getKey());
                if (relationship != null && relationship.isJsonObject()) {
                    if (entry.getValue().type() == JsonApiRelationshipList.class) {
                        entry.getValue().setOnObject(t,
                                context.deserialize(relationship, JsonApiRelationshipList.class));
                    } else if (entry.getValue().type() == JsonApiRelationship.class) { // JsonApiRelationship
                        entry.getValue().setOnObject(t,
                                context.deserialize(relationship, JsonApiRelationship.class));
                    } else { // String list or id
                        JsonElement data = relationship.getAsJsonObject().get("data");
                        if (data != null) {
                            if (data.isJsonObject()) {
                                JsonElement relationshipIdElement = data.getAsJsonObject().get("id");
                                if (relationshipIdElement != null) {
                                    if (relationshipIdElement.isJsonPrimitive()) {
                                        entry.getValue().setOnObject(t, relationshipIdElement.getAsString());
                                    }
                                }
                            } else if (data.isJsonArray()) {
                                List<String> idList = parseIds(data.getAsJsonArray());
                                entry.getValue().setOnObject(t, idList);
                            }
                        }
                    }
                }
            }
        }
    }

    private T parseObject(JsonDeserializationContext context, String id, JsonObject jsonObject)
            throws InstantiationException, IllegalAccessException, InvocationTargetException {
        JsonElement attributesElement = jsonObject.get("attributes");
        T object;
        if (attributesElement != null && attributesElement.isJsonObject()) {
            object = context.deserialize(attributesElement, klass);
        } else {
            object = klass.newInstance();
        }
        idSetter.setOnObject(object, id);
        return object;
    }

    private List<String> parseIds(JsonArray jsonArray) {
        List<String> result = new ArrayList<String>(jsonArray.size());
        for (int i = 0; i < jsonArray.size(); i++) {
            JsonElement item = jsonArray.get(i);
            if (item.isJsonObject()) {
                JsonElement idField = item.getAsJsonObject().get("id");
                if (idField != null && idField.isJsonPrimitive()) {
                    result.add(idField.getAsString());
                }
            }
        }
        return result;
    }

    interface Setter {
        Class type();

        void setOnObject(Object object, Object id) throws IllegalAccessException, InvocationTargetException;
    }

    @SuppressWarnings("WeakerAccess")
    static class FieldSetter implements Setter {
        final Field field;

        FieldSetter(Field field) {
            this.field = field;
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
        }

        @Override
        public Class type() {
            return field.getType();
        }

        public void setOnObject(Object object, Object id) throws IllegalAccessException {
            field.set(object, id);
        }

    }

    @SuppressWarnings("WeakerAccess")
    static class MethodSetter implements Setter {
        final Method method;

        MethodSetter(Method method) {
            this.method = method;
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }
        }

        @Override
        public Class type() {
            return method.getParameterTypes()[0];
        }

        public void setOnObject(Object object, Object id) throws IllegalAccessException, InvocationTargetException {
            method.invoke(object, id);
        }

    }
}