package io.katharsis.resource.information;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.katharsis.resource.annotations.JsonApiId;
import io.katharsis.resource.annotations.JsonApiLinksInformation;
import io.katharsis.resource.annotations.JsonApiMetaInformation;
import io.katharsis.resource.annotations.JsonApiToMany;
import io.katharsis.resource.annotations.JsonApiToOne;
import io.katharsis.resource.exception.init.MultipleJsonApiLinksInformationException;
import io.katharsis.resource.exception.init.MultipleJsonApiMetaInformationException;
import io.katharsis.resource.exception.init.ResourceDuplicateIdException;
import io.katharsis.resource.exception.init.ResourceIdNotFoundException;
import io.katharsis.resource.field.ResourceAttributesBridge;
import io.katharsis.resource.field.ResourceField;
import io.katharsis.resource.field.ResourceFieldNameTransformer;
import io.katharsis.resource.information.field.FieldOrderedComparator;
import io.katharsis.resource.information.field.ResourceFieldWrapper;
import io.katharsis.utils.ClassUtils;
import io.katharsis.utils.java.Optional;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* A builder which creates ResourceInformation instances of a specific class. It extracts information about a resource
* from annotations and information about fields and getters.
*/
public final class ResourceInformationBuilder {
private final ResourceFieldNameTransformer resourceFieldNameTransformer;
public ResourceInformationBuilder(ResourceFieldNameTransformer resourceFieldNameTransformer) {
this.resourceFieldNameTransformer = resourceFieldNameTransformer;
}
public ResourceInformation build(Class<?> resourceClass) {
List<ResourceField> resourceFields = getResourceFields(resourceClass);
ResourceField idField = getIdField(resourceClass, resourceFields);
Optional<JsonPropertyOrder> propertyOrder = ClassUtils.getAnnotation(resourceClass, JsonPropertyOrder.class);
Set<ResourceField> basicFields = getBasicFields(resourceFields, idField, propertyOrder);
Set<ResourceField> relationshipFields = getRelationshipFields(resourceFields, idField, propertyOrder);
ResourceAttributesBridge<?> attributesBridge = new ResourceAttributesBridge(basicFields, resourceClass);
String metaFieldName = getMetaFieldName(resourceClass, resourceFields);
String linksFieldName = getLinksFieldName(resourceClass, resourceFields);
return new ResourceInformation(
resourceClass,
idField,
attributesBridge,
relationshipFields,
metaFieldName,
linksFieldName);
}
private List<ResourceField> getResourceFields(Class<?> resourceClass) {
List<Field> classFields = ClassUtils.getClassFields(resourceClass);
List<Method> classGetters = ClassUtils.getClassGetters(resourceClass);
List<ResourceFieldWrapper> resourceClassFields = getFieldResourceFields(classFields);
List<ResourceFieldWrapper> resourceGetterFields = getGetterResourceFields(classGetters);
return getResourceFields(resourceClassFields, resourceGetterFields);
}
private List<ResourceFieldWrapper> getFieldResourceFields(List<Field> classFields) {
List<ResourceFieldWrapper> fieldWrappers = new ArrayList<>(classFields.size());
for (Field field : classFields) {
String jsonName = resourceFieldNameTransformer.getName(field);
String underlyingName = field.getName();
List<Annotation> annotations = Arrays.asList(field.getAnnotations());
ResourceField resourceField = new ResourceField(jsonName, underlyingName, field.getType(), field.getGenericType(), annotations);
if (Modifier.isTransient(field.getModifiers()) ||
Modifier.isStatic(field.getModifiers())) {
fieldWrappers.add(new ResourceFieldWrapper(resourceField, true));
} else {
fieldWrappers.add(new ResourceFieldWrapper(resourceField, false));
}
}
return fieldWrappers;
}
private List<ResourceFieldWrapper> getGetterResourceFields(List<Method> classGetters) {
List<ResourceFieldWrapper> fieldWrappers = new ArrayList<>(classGetters.size());
for (Method getter : classGetters) {
String jsonName = resourceFieldNameTransformer.getName(getter);
String underlyingName = resourceFieldNameTransformer.getMethodName(getter);
List<Annotation> annotations = Arrays.asList(getter.getAnnotations());
ResourceField resourceField = new ResourceField(jsonName, underlyingName, getter.getReturnType(), getter.getGenericReturnType(), annotations);
if (Modifier.isStatic(getter.getModifiers())) {
fieldWrappers.add(new ResourceFieldWrapper(resourceField, true));
} else {
fieldWrappers.add(new ResourceFieldWrapper(resourceField, false));
}
}
return fieldWrappers;
}
private List<ResourceField> getResourceFields(List<ResourceFieldWrapper> resourceClassFields, List<ResourceFieldWrapper> resourceGetterFields) {
Map<String, ResourceField> resourceFieldMap = new HashMap<>();
for (ResourceFieldWrapper fieldWrapper : resourceClassFields) {
if (!fieldWrapper.isDiscarded())
resourceFieldMap.put(fieldWrapper.getResourceField().getUnderlyingName(), fieldWrapper.getResourceField());
}
for (ResourceFieldWrapper fieldWrapper : resourceGetterFields) {
if (!fieldWrapper.isDiscarded()) {
String originalName = fieldWrapper.getResourceField().getUnderlyingName();
ResourceField field = fieldWrapper.getResourceField();
if (resourceFieldMap.containsKey(originalName)) {
resourceFieldMap.put(originalName, mergeAnnotations(resourceFieldMap.get(originalName), field));
} else if (!hasDiscardedField(fieldWrapper, resourceClassFields)) {
resourceFieldMap.put(originalName, field);
}
}
}
return discardIgnoredField(resourceFieldMap.values());
}
private List<ResourceField> discardIgnoredField(Collection<ResourceField> resourceFieldValues) {
List<ResourceField> resourceFields = new LinkedList<>();
for (ResourceField resourceField : resourceFieldValues) {
if (!resourceField.isAnnotationPresent(JsonIgnore.class)) {
resourceFields.add(resourceField);
}
}
return resourceFields;
}
private static boolean hasDiscardedField(ResourceFieldWrapper fieldWrapper, List<ResourceFieldWrapper> resourceClassFields) {
for (ResourceFieldWrapper resourceFieldWrapper : resourceClassFields) {
if (fieldWrapper.getResourceField().getUnderlyingName()
.equals(resourceFieldWrapper.getResourceField().getUnderlyingName())) {
return true;
}
}
return false;
}
private static ResourceField mergeAnnotations(ResourceField fromField, ResourceField fromMethod) {
List<Annotation> annotations = new LinkedList<>(fromField.getAnnotations());
annotations.addAll(fromMethod.getAnnotations());
return new ResourceField(fromField.getJsonName(), fromField.getUnderlyingName(), fromField.getType(), fromField.getGenericType(), annotations);
}
private <T> ResourceField getIdField(Class<T> resourceClass, List<ResourceField> classFields) {
List<ResourceField> idFields = new ArrayList<>(1);
for (ResourceField field : classFields) {
if (field.isAnnotationPresent(JsonApiId.class)) {
idFields.add(field);
}
}
if (idFields.size() == 0) {
throw new ResourceIdNotFoundException(resourceClass.getCanonicalName());
} else if (idFields.size() > 1) {
throw new ResourceDuplicateIdException(resourceClass.getCanonicalName());
}
return idFields.get(0);
}
private <T> String getMetaFieldName(Class<T> resourceClass, List<ResourceField> classFields) {
List<ResourceField> metaFields = new ArrayList<>(1);
for (ResourceField field : classFields) {
if (field.isAnnotationPresent(JsonApiMetaInformation.class)) {
metaFields.add(field);
}
}
if (metaFields.size() == 0) {
return null;
} else if (metaFields.size() > 1) {
throw new MultipleJsonApiMetaInformationException(resourceClass.getCanonicalName());
}
return metaFields.get(0).getUnderlyingName();
}
private <T> String getLinksFieldName(Class<T> resourceClass, List<ResourceField> classFields) {
List<ResourceField> linksFields = new ArrayList<>(1);
for (ResourceField field : classFields) {
if (field.isAnnotationPresent(JsonApiLinksInformation.class)) {
linksFields.add(field);
}
}
if (linksFields.size() == 0) {
return null;
} else if (linksFields.size() > 1) {
throw new MultipleJsonApiLinksInformationException(resourceClass.getCanonicalName());
}
return linksFields.get(0).getUnderlyingName();
}
private Set<ResourceField> getBasicFields(List<ResourceField> classFields, ResourceField idField,
Optional<JsonPropertyOrder> propertyOrder) {
Set<ResourceField> basicFields = buildResourceFieldSet(propertyOrder);
for (ResourceField field : classFields) {
if (isBasicField(field) && !field.equals(idField)) {
basicFields.add(field);
}
}
return basicFields;
}
private boolean isBasicField(ResourceField field) {
return !isRelation(field) &&
!field.isAnnotationPresent(JsonApiMetaInformation.class) &&
!field.isAnnotationPresent(JsonApiLinksInformation.class);
}
private Set<ResourceField> getRelationshipFields(List<ResourceField> classFields, ResourceField idField,
Optional<JsonPropertyOrder> propertyOrder) {
Set<ResourceField> relationshipFields = buildResourceFieldSet(propertyOrder);
for (ResourceField field : classFields) {
if (isRelation(field) && !field.equals(idField)) {
relationshipFields.add(field);
}
}
return relationshipFields;
}
private static Set<ResourceField> buildResourceFieldSet(Optional<JsonPropertyOrder> propertyOrderOptional) {
Set<ResourceField> basicFields;
if (propertyOrderOptional.isPresent()) {
JsonPropertyOrder propertyOrder = propertyOrderOptional.get();
basicFields = new TreeSet<>(new FieldOrderedComparator(propertyOrder.value(), propertyOrder.alphabetic()));
} else {
basicFields = new HashSet<>();
}
return basicFields;
}
private boolean isRelation(ResourceField field) {
return field.isAnnotationPresent(JsonApiToMany.class) || field.isAnnotationPresent(JsonApiToOne.class);
}
}